diff --git a/.github/workflows/_publish-code.yml b/.github/workflows/_publish-code.yml index 9ccb1a0d4..fcc1a1799 100644 --- a/.github/workflows/_publish-code.yml +++ b/.github/workflows/_publish-code.yml @@ -19,14 +19,14 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v6 with: - node-version: 24 + node-version: 26 cache: "npm" - name: Install Node.js dependencies run: npm ci - name: Build - run: npm run build-for-dist + run: npm run build:dist - name: Publish run: | diff --git a/.github/workflows/_publish-docs.yml b/.github/workflows/_publish-docs.yml index 81d92d1a2..18fda0332 100644 --- a/.github/workflows/_publish-docs.yml +++ b/.github/workflows/_publish-docs.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v6 with: - node-version: 24 + node-version: 26 cache: "npm" - name: Install Node.js dependencies @@ -24,7 +24,7 @@ jobs: - name: Generate docs run: | - npm run docs-for-dist + npm run docs:dist - name: Deploy uses: peaceiris/actions-gh-pages@v3 diff --git a/.github/workflows/_static-analysis.yml b/.github/workflows/_static-analysis.yml index 3aea4f433..d4441628a 100644 --- a/.github/workflows/_static-analysis.yml +++ b/.github/workflows/_static-analysis.yml @@ -15,7 +15,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v6 with: - node-version: 24 + node-version: 26 cache: "npm" - name: Install Node.js dependencies @@ -24,8 +24,11 @@ jobs: - name: Test lint run: npm run lint - - name: Install NPM License Checker - run: npm install -g license-checker + - name: Test lint for dist + run: npm run lint:package - - name: Check licences, no GPL or APGL allowed - run: license-checker | grep -P -B1 '(?<=[A\W])GPL' && exit 1 || exit 0 + - name: Check for secrets + run: npm run lint:secrets + + - name: Test licenses + run: npm run license:check diff --git a/.github/workflows/_test-integrations.yml b/.github/workflows/_test-integrations.yml index 855d5ce17..95a50efcd 100644 --- a/.github/workflows/_test-integrations.yml +++ b/.github/workflows/_test-integrations.yml @@ -28,7 +28,8 @@ jobs: - "macos-latest" node-version: - "20" - - "24" + - "22" + - "26" runs-on: ${{ matrix.os }} steps: @@ -65,7 +66,7 @@ jobs: run: npm run build - name: Test code - run: npm run test-integration + run: npm run test:integration run-tests-without-optional-dependencies: @@ -101,4 +102,4 @@ jobs: run: npm run build - name: Test code - run: npm run test-integration-light + run: npm run test:integration:light diff --git a/.github/workflows/_test-smoke.yml b/.github/workflows/_test-smoke.yml index b87164d41..c559ea4d1 100644 --- a/.github/workflows/_test-smoke.yml +++ b/.github/workflows/_test-smoke.yml @@ -21,9 +21,9 @@ jobs: strategy: matrix: node-version: - - "20" - "22" - "24" + - "26" steps: - name: Check out Git repository @@ -41,7 +41,7 @@ jobs: run: npm ci - name: Build - run: npm run build-for-dist + run: npm run build:dist - name: Tests v2 sample code run: | diff --git a/.github/workflows/_test-units.yml b/.github/workflows/_test-units.yml index f93c6a4f8..bcda8d6c5 100644 --- a/.github/workflows/_test-units.yml +++ b/.github/workflows/_test-units.yml @@ -14,6 +14,7 @@ jobs: - "20" - "22" - "24" + - "26" runs-on: ${{ matrix.os }} steps: @@ -52,6 +53,7 @@ jobs: - "20" - "22" - "24" + - "26" runs-on: ${{ matrix.os }} steps: @@ -73,4 +75,4 @@ jobs: run: npm run build - name: Test code - run: npm run test-light + run: npm run test:light diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 000000000..b9bf11099 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npm run lint:full diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100644 index 000000000..26290029a --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,4 @@ +npm test +npm run lint:full +npm run license:check +npm run docs diff --git a/.mocharc.json b/.mocharc.json deleted file mode 100644 index c82ad1d55..000000000 --- a/.mocharc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/mocharc", - "extension": ["ts"], - "node-option": ["import=tsx"], - "spec": ["tests"] -} diff --git a/.secretlintrc.json b/.secretlintrc.json new file mode 100644 index 000000000..7a1a5df3c --- /dev/null +++ b/.secretlintrc.json @@ -0,0 +1,7 @@ +{ + "rules": [ + { + "id": "@secretlint/secretlint-rule-preset-recommend" + } + ] +} diff --git a/eslint.config.mjs b/eslint.config.mjs index 675b126fb..5d120e1cf 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,11 +1,13 @@ import typescriptEslint from "@typescript-eslint/eslint-plugin"; import jsdoc from "eslint-plugin-jsdoc"; +import security from "eslint-plugin-security"; +import sonarjs from "eslint-plugin-sonarjs"; import globals from "globals"; import tsParser from "@typescript-eslint/parser"; import path from "node:path"; -import { fileURLToPath } from "node:url"; +import {fileURLToPath} from "node:url"; import js from "@eslint/js"; -import { FlatCompat } from "@eslint/eslintrc"; +import {FlatCompat} from "@eslint/eslintrc"; /* eslint-disable @typescript-eslint/naming-convention */ @@ -19,10 +21,12 @@ const compat = new FlatCompat({ export default [{ ignores: [], -}, ...compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended"), { +}, ...compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended"), + sonarjs.configs.recommended, security.configs.recommended, { plugins: { "@typescript-eslint": typescriptEslint, jsdoc, + security, }, languageOptions: { @@ -33,7 +37,6 @@ export default [{ }, parser: tsParser, }, - rules: { "max-len": ["error", { code: 120, @@ -44,6 +47,7 @@ export default [{ "jsdoc/check-param-names": "error", "jsdoc/check-types": "error", "jsdoc/no-undefined-types": "error", + "jsdoc/require-jsdoc": "error", "@typescript-eslint/no-unused-vars": "error", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-inferrable-types": "off", @@ -78,5 +82,19 @@ export default [{ indent: ["error", 2], "eol-last": "error", "preserve-caught-error": "off", + "security/detect-non-literal-fs-filename": "off", + "security/detect-object-injection": "off", + "no-restricted-imports": ["error", { + "patterns": [{ + "group": ["../*"], + "message": "Import can be shortened. Please use the @/ path alias instead of relative parent paths." + }] + }], }, +}, +{ + files: ["tests/**/*"], + rules: { + "no-restricted-imports": "off" + } }]; diff --git a/package-lock.json b/package-lock.json index ba33c7e7b..846c0228e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,17 +19,23 @@ "mindeeV2": "bin/mindeeV2.js" }, "devDependencies": { + "@arethetypeswrong/cli": "^0.18.5", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "^10.0.1", - "@types/mocha": "^10.0.10", + "@secretlint/secretlint-rule-preset-recommend": "^13.0.4", "@types/node": "^20.19.37", "@types/tmp": "^0.2.6", "@typescript-eslint/eslint-plugin": "^8.57.0", "@typescript-eslint/parser": "^8.58.1", "eslint": "^10.6.0", "eslint-plugin-jsdoc": "^62.7.1", + "eslint-plugin-security": "^4.0.1", + "eslint-plugin-sonarjs": "^4.2.0", "globals": "^17.4.0", - "mocha": "^11.7.6", + "husky": "^9.1.7", + "license-checker-rseidelsohn": "^4.4.2", + "publint": "^0.3.22", + "secretlint": "^13.0.4", "tsc-alias": "^1.8.17", "tslib": "^2.8.1", "tsx": "^4.21.0", @@ -43,7 +49,131 @@ "@cantoo/pdf-lib": "^2.5.3", "node-poppler": "^9.1.1", "pdf.js-extract": "~0.2.1", - "sharp": "~0.34.5" + "sharp": "^0.35.3" + } + }, + "node_modules/@andrewbranch/untar.js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@andrewbranch/untar.js/-/untar.js-1.0.3.tgz", + "integrity": "sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw==", + "dev": true + }, + "node_modules/@arethetypeswrong/cli": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/@arethetypeswrong/cli/-/cli-0.18.5.tgz", + "integrity": "sha512-gM+8vRsQOD/Uc7EnBedUhkG5OCsDWE4uoak5QvomGpMpaky0Eh41p04nIMgrWb8EOmqZUJGc6zz9hsP6E56R7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@arethetypeswrong/core": "0.18.5", + "chalk": "^4.1.2", + "cli-table3": "^0.6.3", + "commander": "^10.0.1", + "marked": "^9.1.2", + "marked-terminal": "^7.1.0", + "semver": "^7.5.4" + }, + "bin": { + "attw": "dist/index.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@arethetypeswrong/cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@arethetypeswrong/core": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/@arethetypeswrong/core/-/core-0.18.5.tgz", + "integrity": "sha512-9ytjzGwxjm9Uz7I9avfbt5vlQt6uk9uRRESzJjqrznl6WKvI6dwYTo+vJ3U02Wrq/mR3iql/PzhvHhKdJIAjDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@andrewbranch/untar.js": "^1.0.3", + "@loaderkit/resolve": "^1.0.2", + "cjs-module-lexer": "^1.2.3", + "fflate": "^0.8.3", + "lru-cache": "^11.0.1", + "semver": "^7.5.4", + "typescript": "5.6.1-rc", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@arethetypeswrong/core/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@arethetypeswrong/core/node_modules/typescript": { + "version": "5.6.1-rc", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.1-rc.tgz", + "integrity": "sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@azu/format-text": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@azu/format-text/-/format-text-1.0.2.tgz", + "integrity": "sha512-Swi4N7Edy1Eqq82GxgEECXSSLyn6GOb5htRFPzBDdUkECGXtlf12ynO5oJSpWKPwCaUssOu7NfhDcCWpIC6Ywg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@azu/style-format": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azu/style-format/-/style-format-1.0.1.tgz", + "integrity": "sha512-AHcTojlNBdD/3/KxIKlg8sxIWHfOtQszLvOpagLTO+bjC3u7SAszu1lf//u7JJC50aUSH+BVWDD/KvaA6Gfn5g==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "@azu/format-text": "^1.0.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" } }, "node_modules/@borewit/text-codec": { @@ -56,6 +186,13 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/@braidai/lang": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@braidai/lang/-/lang-1.1.2.tgz", + "integrity": "sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA==", + "dev": true, + "license": "ISC" + }, "node_modules/@cantoo/pdf-lib": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/@cantoo/pdf-lib/-/pdf-lib-2.7.1.tgz", @@ -72,10 +209,21 @@ "tslib": ">=2" } }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@emnapi/runtime": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", - "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", "license": "MIT", "optional": true, "dependencies": { @@ -833,9 +981,9 @@ } }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", - "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz", + "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==", "cpu": [ "arm64" ], @@ -845,19 +993,19 @@ "darwin" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.4" + "@img/sharp-libvips-darwin-arm64": "1.3.2" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", - "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz", + "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==", "cpu": [ "x64" ], @@ -867,19 +1015,38 @@ "darwin" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.4" + "@img/sharp-libvips-darwin-x64": "1.3.2" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz", + "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", - "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz", + "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==", "cpu": [ "arm64" ], @@ -893,9 +1060,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", - "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz", + "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==", "cpu": [ "x64" ], @@ -909,9 +1076,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", - "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz", + "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==", "cpu": [ "arm" ], @@ -925,9 +1092,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", - "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz", + "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==", "cpu": [ "arm64" ], @@ -941,9 +1108,9 @@ } }, "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", - "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz", + "integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==", "cpu": [ "ppc64" ], @@ -957,9 +1124,9 @@ } }, "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", - "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz", + "integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==", "cpu": [ "riscv64" ], @@ -973,9 +1140,9 @@ } }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", - "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz", + "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==", "cpu": [ "s390x" ], @@ -989,9 +1156,9 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", - "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz", + "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==", "cpu": [ "x64" ], @@ -1005,9 +1172,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", - "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz", + "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==", "cpu": [ "arm64" ], @@ -1021,9 +1188,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", - "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz", + "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==", "cpu": [ "x64" ], @@ -1037,9 +1204,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", - "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz", + "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==", "cpu": [ "arm" ], @@ -1049,19 +1216,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.4" + "@img/sharp-libvips-linux-arm": "1.3.2" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", - "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz", + "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==", "cpu": [ "arm64" ], @@ -1071,19 +1238,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.4" + "@img/sharp-libvips-linux-arm64": "1.3.2" } }, "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", - "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz", + "integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==", "cpu": [ "ppc64" ], @@ -1093,19 +1260,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.4" + "@img/sharp-libvips-linux-ppc64": "1.3.2" } }, "node_modules/@img/sharp-linux-riscv64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", - "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz", + "integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==", "cpu": [ "riscv64" ], @@ -1115,19 +1282,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.2.4" + "@img/sharp-libvips-linux-riscv64": "1.3.2" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", - "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz", + "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==", "cpu": [ "s390x" ], @@ -1137,19 +1304,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.4" + "@img/sharp-libvips-linux-s390x": "1.3.2" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", - "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz", + "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==", "cpu": [ "x64" ], @@ -1159,19 +1326,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.4" + "@img/sharp-libvips-linux-x64": "1.3.2" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", - "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz", + "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==", "cpu": [ "arm64" ], @@ -1181,19 +1348,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", - "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz", + "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==", "cpu": [ "x64" ], @@ -1203,38 +1370,54 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + "@img/sharp-libvips-linuxmusl-x64": "1.3.2" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", - "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz", + "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.1" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz", + "integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==", "cpu": [ "wasm32" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "license": "Apache-2.0", "optional": true, "dependencies": { - "@emnapi/runtime": "^1.7.0" + "@img/sharp-wasm32": "0.35.3" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", - "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz", + "integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==", "cpu": [ "arm64" ], @@ -1244,16 +1427,16 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", - "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz", + "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==", "cpu": [ "ia32" ], @@ -1263,16 +1446,16 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": "^20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", - "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz", + "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==", "cpu": [ "x64" ], @@ -1282,7 +1465,7 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" @@ -1306,6 +1489,16 @@ "node": ">=12" } }, + "node_modules/@loaderkit/resolve": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@loaderkit/resolve/-/resolve-1.0.6.tgz", + "integrity": "sha512-G8FdIoF5CypfwmD9rl8BXod5HDn8JqB0CCNBXDTaRZ+yRYhARrrSToX1zg1zy9jX3zLqigsELwhT4gNtkdQAUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@braidai/lang": "^1.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1344,6 +1537,19 @@ "node": ">= 8" } }, + "node_modules/@npmcli/fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", + "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/@pdf-lib/standard-fonts": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", @@ -1375,6 +1581,221 @@ "node": ">=14" } }, + "node_modules/@publint/pack": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@publint/pack/-/pack-0.1.6.tgz", + "integrity": "sha512-3uVNyGcVplhPZSLVyeIpL7+cIRn1YCSNHLG/rUIlBQMVH8YuN9++YF+5+UDIIO9RW98dujiUoTltO7RDB5bFJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyexec": "^1.2.4" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://bjornlu.com/sponsor" + } + }, + "node_modules/@secretlint/config-creator": { + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-13.0.4.tgz", + "integrity": "sha512-rSYBD36alOtDch10H2fOgiCVMyOrn4IfzICnoBg6IeValIESj7iz22GVC8hEbm3WLBwKVBZ3RjK2SYOteh7Bkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "13.0.4" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@secretlint/config-loader": { + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-13.0.4.tgz", + "integrity": "sha512-K6jq7IDqlGdOWKqfiFiKSiWmMHryAIwnFs84u5A5rjWVvrkrHYjNqNRyk6iO7hXM/7cXkxO8Mm8zb77hF1HT1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "13.0.4", + "@secretlint/resolver": "13.0.4", + "@secretlint/types": "13.0.4", + "ajv": "^8.20.0", + "debug": "^4.4.3", + "rc-config-loader": "^4.1.4" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@secretlint/config-loader/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@secretlint/config-loader/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/core": { + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-13.0.4.tgz", + "integrity": "sha512-Wv49KcI5XX6xjLR1wxyjORA15PtMb5ar/M27ShimVudaSi6iAM04QCA5Ozx+uEahfHNefUUKbjKGpy/9pxuW7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "13.0.4", + "@secretlint/types": "13.0.4", + "debug": "^4.4.3", + "structured-source": "^4.0.0" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@secretlint/formatter": { + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-13.0.4.tgz", + "integrity": "sha512-si7tXQeOIMh/Wqu7lhXnutbnMheGXWGVivbQPhoUR9eMJwT32gTII7Bh3t30zPsUop+SGr/SIc+bPmKehPKb9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/resolver": "13.0.4", + "@secretlint/types": "13.0.4", + "@textlint/linter-formatter": "^15.7.0", + "@textlint/module-interop": "^15.7.0", + "@textlint/types": "^15.7.0", + "chalk": "^5.6.2", + "debug": "^4.4.3", + "pluralize": "^8.0.0", + "strip-ansi": "^7.2.0", + "table": "^6.9.0", + "terminal-link": "^5.0.0" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@secretlint/formatter/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@secretlint/node": { + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-13.0.4.tgz", + "integrity": "sha512-4HmD9yfA9etThrxhDbyTMBaHj556uKxrS+tOBIkP5AAnB6sXk4mmA9fjKDTrk8dkrpg7S/g2Uds3AKb+yeUNnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-loader": "13.0.4", + "@secretlint/core": "13.0.4", + "@secretlint/formatter": "13.0.4", + "@secretlint/profiler": "13.0.4", + "@secretlint/source-creator": "13.0.4", + "@secretlint/types": "13.0.4", + "debug": "^4.4.3", + "p-map": "^7.0.5" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@secretlint/profiler": { + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-13.0.4.tgz", + "integrity": "sha512-T2hSyZmJrQbGAe+Vl9AGNlMnoB0MP6m2BLh7EH80QcesvNM2t0pCzdiBvQ/yCe76w6/gZNpHlrSVayUeT43qVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/resolver": { + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-13.0.4.tgz", + "integrity": "sha512-+s4fRFctBlAbHu4H8lRLj/e6dyDIGpIM2QLAJWgaMJUn9bFxetjRBQEv/HD2U4ohpGHYURLMkDcnS3iZdSTmGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/secretlint-rule-preset-recommend": { + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-13.0.4.tgz", + "integrity": "sha512-Nbcr7tvyKuRF4BKh7RQSCEOaif4gFPH/qjK2ajcoUDGL7HAY/C6PzcsmVR1Y0qQCRqrBuMuXn1onXE+wxNNNsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@secretlint/source-creator": { + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-13.0.4.tgz", + "integrity": "sha512-YfVVY7GHbHV6CN/tLIf1BGiONVjX2fvmJyvCv2MhonUiWtLPVU4fnOQIrWMLFakeRJ/GVoV8FfqvPW3jnUHfVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "13.0.4", + "istextorbinary": "^9.5.0" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@secretlint/types": { + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-13.0.4.tgz", + "integrity": "sha512-on/DivRDZEFzRD2pZJO0wkIL+AvEY+KOoZLPCWcz4pjZnJ4NzMuHQjvTJc9KY0yht+ugcYg9AmtOUzpEk31IWA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@secretlint/walker": { + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/@secretlint/walker/-/walker-13.0.4.tgz", + "integrity": "sha512-ufAWro6oqdTkZYbMXHXlW56IAO+1YhKLwIDTqMYSzDna6bJdph2FXqm2IdnKTd4TNbdVficp1w7GoBm4JHxddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ignore": "^7.0.6", + "picomatch": "^4.0.5" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@secretlint/walker/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/@shikijs/engine-oniguruma": { "version": "3.23.0", "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", @@ -1437,40 +1858,159 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@tokenizer/inflate": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", - "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "token-types": "^6.1.1" - }, "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "license": "MIT" - }, - "node_modules/@types/esrecurse": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", - "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "node_modules/@textlint/ast-node-types": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.7.1.tgz", + "integrity": "sha512-Wii5UgUKFEh9Uv6wbq1zr4/Kf+dtjiUuzPrrXzKp8H+ifkvKNzi23V4Nz+6wVyHQn5T28AFuc8VH8OtzvGYecA==", "dev": true, "license": "MIT" }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "node_modules/@textlint/linter-formatter": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.7.1.tgz", + "integrity": "sha512-TdwZ/debWYFD05K3CcoHtwvnCrza29wZxD+BjDTk/V5N7iRqkK1dTTHSD4A8AIgROLiDkHJmIKQbasbmsg8AvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azu/format-text": "^1.0.2", + "@azu/style-format": "^1.0.1", + "@textlint/module-interop": "15.7.1", + "@textlint/resolver": "15.7.1", + "@textlint/types": "15.7.1", + "chalk": "^4.1.2", + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "lodash": "^4.18.1", + "pluralize": "^2.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "table": "^6.9.0", + "text-table": "^0.2.0" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter/node_modules/pluralize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-2.0.0.tgz", + "integrity": "sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/module-interop": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.7.1.tgz", + "integrity": "sha512-Jg+sQW2L/cRJypk59wtcMUVVpt8vmit5ZMT3gUnFwevP3A6Qp1HfOtUy9ObT4hBX3lOSGT/ekcCDxR1pL7uH1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/resolver": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.7.1.tgz", + "integrity": "sha512-8XnO0pgF6mXnm41VvWmBbEIdGPhiCUt31uLZkOis1ECeg/1SoUcIT6Mx/F0e1rukq8l0UlOSeY9a31CsvRMK0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/types": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.7.1.tgz", + "integrity": "sha512-Vye/GmFNBTgVzZFtIFJTmLB+s2A7oIADxNG6r9UhfPuY+Czv0z5G3xeyFZZudPlfxURsKUyPIU5XsjOFqVp33A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@textlint/ast-node-types": "15.7.1" + } + }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -1491,13 +2031,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mocha": { - "version": "10.0.10", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", - "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { "version": "20.19.43", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", @@ -1508,6 +2041,13 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/tmp": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.6.tgz", @@ -1804,6 +2344,16 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/acorn": { "version": "8.17.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", @@ -1844,6 +2394,22 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -1873,6 +2439,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -1917,6 +2490,16 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -1927,6 +2510,16 @@ "node": ">=8" } }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1947,6 +2540,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/binaryextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz", + "integrity": "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/boundary": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/boundary/-/boundary-2.0.0.tgz", + "integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/brace-expansion": { "version": "1.1.16", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", @@ -1971,12 +2587,28 @@ "node": ">=8" } }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", "dev": true, - "license": "ISC" + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, "node_modules/callsites": { "version": "3.1.0", @@ -1992,8 +2624,8 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "devOptional": true, "license": "MIT", + "optional": true, "engines": { "node": ">=10" }, @@ -2031,38 +2663,46 @@ "node": ">=8" } }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "node": ">=10" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", "dev": true, "license": "ISC", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" }, "engines": { - "node": ">=12" + "node": ">=8.0.0", + "npm": ">=5.0.0" } }, - "node_modules/cliui/node_modules/ansi-regex": { + "node_modules/cli-highlight/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", @@ -2072,14 +2712,26 @@ "node": ">=8" } }, - "node_modules/cliui/node_modules/emoji-regex": { + "node_modules/cli-highlight/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cli-highlight/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, - "node_modules/cliui/node_modules/string-width": { + "node_modules/cli-highlight/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", @@ -2094,7 +2746,7 @@ "node": ">=8" } }, - "node_modules/cliui/node_modules/strip-ansi": { + "node_modules/cli-highlight/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -2107,7 +2759,7 @@ "node": ">=8" } }, - "node_modules/cliui/node_modules/wrap-ansi": { + "node_modules/cli-highlight/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", @@ -2125,73 +2777,163 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "node_modules/cli-highlight/node_modules/yargs": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz", + "integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" }, "engines": { - "node": ">=12.5.0" + "node": ">=10" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, + "node_modules/cli-highlight/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", "engines": { - "node": ">=7.0.0" + "node": ">=10" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", - "license": "MIT", + "string-width": "^4.2.0" + }, "engines": { - "node": ">=20" + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" } }, - "node_modules/comment-parser": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.6.tgz", - "integrity": "sha512-ObxuY6vnbWTN6Od72xfwN9DbzC7Y2vv8u1Soi9ahRKL37gb6y1qk6/dgjs+3JWuXJHWvsg3BXIwzd/rkmAwavg==", + "node_modules/cli-table3/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 12.0.0" + "node": ">=8" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "node_modules/cli-table3/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-table3/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-table3/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/comment-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.6.tgz", + "integrity": "sha512-ObxuY6vnbWTN6Od72xfwN9DbzC7Y2vv8u1Soi9ahRKL37gb6y1qk6/dgjs+3JWuXJHWvsg3BXIwzd/rkmAwavg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, "license": "MIT" @@ -2235,19 +2977,6 @@ } } }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2265,16 +2994,6 @@ "node": ">=8" } }, - "node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -2302,6 +3021,23 @@ "dev": true, "license": "MIT" }, + "node_modules/editions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.22.0.tgz", + "integrity": "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "version-range": "^4.15.0" + }, + "engines": { + "ecmascript": ">= es5", + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -2309,6 +3045,13 @@ "dev": true, "license": "MIT" }, + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "dev": true, + "license": "MIT" + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -2322,6 +3065,19 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/esbuild": { "version": "0.28.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", @@ -2506,6 +3262,86 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-plugin-security": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-4.0.1.tgz", + "integrity": "sha512-/lZCkOxPOWaf1jXAqgICrS8St3BMBccIPvhOSUYuV6VCr1o5nFVG998FnTLt6w2Nxb8Uo0nM8fzmnhp+GY/aEg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-regex": "^2.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-sonarjs": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-4.2.0.tgz", + "integrity": "sha512-bqADfuNtTL7VK6RU29eoiFTtaaBKIpVPuX3bOl+rBpWSBa0zIBVZlqZNZQjfP6s4iXkAJokv5IsD8OsACkwApg==", + "dev": true, + "license": "LGPL-3.0-only", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "builtin-modules": "^3.3.0", + "bytes": "^3.1.2", + "functional-red-black-tree": "^1.0.1", + "globals": "^17.7.0", + "jsx-ast-utils-x": "^0.1.0", + "lodash.merge": "^4.6.2", + "minimatch": "^10.2.5", + "scslre": "^0.3.0", + "semver": "^7.8.5", + "ts-api-utils": "^2.5.0", + "typescript": ">=5 <6.1.0", + "yaml": "^2.9.0" + }, + "peerDependencies": { + "eslint": "^8.0.0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-sonarjs/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/eslint-plugin-sonarjs/node_modules/brace-expansion": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", + "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/eslint-plugin-sonarjs/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/eslint-scope": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", @@ -2736,6 +3572,23 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", + "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -2764,6 +3617,13 @@ } } }, + "node_modules/fflate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", + "dev": true, + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -2825,16 +3685,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -2888,6 +3738,23 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true, + "license": "MIT" + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -3006,6 +3873,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC", + "optional": true + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3016,14 +3891,50 @@ "node": ">=8" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "dev": true, "license": "MIT", - "bin": { - "he": "bin/he" + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/hosted-git-info": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.3.tgz", + "integrity": "sha512-HVJyzUrLIL1c0QmviVh5E8VGyUS7xCFPS6yydaVd1UegW+ibV/CohqTH9MkOLDp5o+rb82DMo77PTuc9F/8GKw==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" } }, "node_modules/html-entities": { @@ -3043,14 +3954,30 @@ ], "license": "MIT" }, - "node_modules/ice-barrage": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ice-barrage/-/ice-barrage-1.0.0.tgz", - "integrity": "sha512-xdp/aktysCDxBasbht01dMC2oSgmM4PrQITSCFdBjsxop4ZO1FwNIEEb+wGNSCUp/+8mz/lnkLR2pc4F9ubgIw==", + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, "license": "MIT", - "optional": true, - "engines": { - "node": ">=20" + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/ice-barrage": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ice-barrage/-/ice-barrage-1.0.0.tgz", + "integrity": "sha512-xdp/aktysCDxBasbht01dMC2oSgmM4PrQITSCFdBjsxop4ZO1FwNIEEb+wGNSCUp/+8mz/lnkLR2pc4F9ubgIw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/Fdawgs" @@ -3113,6 +4040,19 @@ "node": ">=0.8.19" } }, + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-arrayish": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", @@ -3133,6 +4073,22 @@ "node": ">=8" } }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3176,46 +4132,31 @@ "node": ">=0.12.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "license": "ISC" }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "node_modules/istextorbinary": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-9.5.0.tgz", + "integrity": "sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==", "dev": true, - "license": "MIT", + "license": "Artistic-2.0", + "dependencies": { + "binaryextensions": "^6.11.0", + "editions": "^6.21.0", + "textextensions": "^6.11.0" + }, "engines": { - "node": ">=10" + "node": ">=4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://bevry.me/fund" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -3232,6 +4173,13 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", @@ -3272,6 +4220,16 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3286,6 +4244,29 @@ "dev": true, "license": "MIT" }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils-x": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/jsx-ast-utils-x/-/jsx-ast-utils-x-0.1.0.tgz", + "integrity": "sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -3310,10 +4291,48 @@ "node": ">= 0.8.0" } }, + "node_modules/license-checker-rseidelsohn": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/license-checker-rseidelsohn/-/license-checker-rseidelsohn-4.4.2.tgz", + "integrity": "sha512-Sf8WaJhd2vELvCne+frS9AXqnY/vv591s2/nZcJDwTnoNgltG4mAmoenffVb8L2YPRYbxARLyrHJBC38AVfpuA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "chalk": "4.1.2", + "debug": "^4.3.4", + "lodash.clonedeep": "^4.5.0", + "mkdirp": "^1.0.4", + "nopt": "^7.2.0", + "read-installed-packages": "^2.0.1", + "semver": "^7.3.5", + "spdx-correct": "^3.1.1", + "spdx-expression-parse": "^3.0.1", + "spdx-satisfies": "^5.0.1", + "treeify": "^1.1.0" + }, + "bin": { + "license-checker-rseidelsohn": "bin/license-checker-rseidelsohn.js" + }, + "engines": { + "node": ">=18", + "npm": ">=8" + } + }, + "node_modules/license-checker-rseidelsohn/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, "node_modules/linkify-it": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.1.tgz", - "integrity": "sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", + "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", "dev": true, "funding": [ { @@ -3346,22 +4365,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "MIT" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" }, "node_modules/lru-cache": { "version": "10.4.3", @@ -3405,6 +4435,54 @@ "markdown-it": "bin/markdown-it.mjs" } }, + "node_modules/marked": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz", + "integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 16" + } + }, + "node_modules/marked-terminal": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz", + "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "ansi-regex": "^6.1.0", + "chalk": "^5.4.1", + "cli-highlight": "^2.1.11", + "cli-table3": "^0.6.5", + "node-emoji": "^2.2.0", + "supports-hyperlinks": "^3.1.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "marked": ">=1 <16" + } + }, + "node_modules/marked-terminal/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", @@ -3472,67 +4550,27 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/mocha": { - "version": "11.7.6", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.6.tgz", - "integrity": "sha512-nS9xOGbw2I3cjCpxwZAEJ9xK9lmJ08vEkQvLtz4du9ZrF9UrjRpeJGiIgl2Z+Qs++pmB4ecDe48Fwsh+j+j7xA==", + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, "license": "MIT", - "dependencies": { - "browser-stdout": "^1.3.1", - "chokidar": "^4.0.1", - "debug": "^4.3.5", - "diff": "^7.0.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^10.4.5", - "he": "^1.2.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^9.0.5", - "ms": "^2.1.3", - "picocolors": "^1.1.1", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^9.2.0", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1", - "yargs-unparser": "^2.0.0" - }, "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" + "mkdirp": "bin/cmd.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=10" } }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", - "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=4" } }, "node_modules/ms": { @@ -3555,6 +4593,18 @@ "url": "https://github.com/sponsors/raouldeheer" } }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -3562,6 +4612,22 @@ "dev": true, "license": "MIT" }, + "node_modules/node-emoji": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/node-html-better-parser": { "version": "1.5.8", "resolved": "https://registry.npmjs.org/node-html-better-parser/-/node-html-better-parser-1.5.8.tgz", @@ -3612,6 +4678,38 @@ "url": "https://github.com/sponsors/Fdawgs" } }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", + "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^6.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -3622,6 +4720,26 @@ "node": ">=0.10.0" } }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-deep-merge": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/object-deep-merge/-/object-deep-merge-2.0.1.tgz", @@ -3679,6 +4797,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-map": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.6.tgz", + "integrity": "sha512-I4Prw6ivkd6p8PiYR1tXASOAOBzIJwu0TB7fqaX0c/8c3QAehNYmX57EijyGGGBt3c/BIowGwV03RVBtXvHEVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -3686,6 +4817,13 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/package-manager-detector": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", + "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==", + "dev": true, + "license": "MIT" + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -3716,21 +4854,76 @@ "parse-statements": "1.0.11" } }, - "node_modules/parse-statements": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", - "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-statements": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", + "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" } }, "node_modules/path-key": { @@ -3792,9 +4985,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -3817,6 +5010,16 @@ "node": ">=12" } }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3827,6 +5030,28 @@ "node": ">= 0.8.0" } }, + "node_modules/publint": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/publint/-/publint-0.3.22.tgz", + "integrity": "sha512-6Z/scsr5CA7APdwyF35EY88CqgDj1textWuY788DVTJYPCWVv/Wn9G6KmLnrVRnStgYcahqN4wCDLZGSbQJ69w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@publint/pack": "^0.1.6", + "package-manager-detector": "^1.7.0", + "picocolors": "^1.1.1", + "sade": "^1.8.1" + }, + "bin": { + "publint": "src/cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://bjornlu.com/sponsor" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3878,28 +5103,149 @@ ], "license": "MIT" }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "node_modules/rc-config-loader": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.4.tgz", + "integrity": "sha512-3GiwEzklkbXTDp52UR5nT8iXgYAx1V9ZG/kDZT7p60u2GCv2XTwQq4NzinMoMpNtXhmt3WkhYXcj6HH8HdwCEQ==", "dev": true, "license": "MIT", "dependencies": { - "safe-buffer": "^5.1.0" + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "json5": "^2.2.3", + "require-from-string": "^2.0.2" } }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "node_modules/read-installed-packages": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/read-installed-packages/-/read-installed-packages-2.0.1.tgz", + "integrity": "sha512-t+fJOFOYaZIjBpTVxiV8Mkt7yQyy4E6MSrrnt5FmPd4enYvpU/9DYGirDmN1XQwkfeuWIhM/iu0t2rm6iSr0CA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^3.1.0", + "debug": "^4.3.4", + "read-package-json": "^6.0.0", + "semver": "2 || 3 || 4 || 5 || 6 || 7", + "slide": "~1.1.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.2" + } + }, + "node_modules/read-package-json": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.4.tgz", + "integrity": "sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw==", + "deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^5.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-pkg": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-10.1.0.tgz", + "integrity": "sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg==", "dev": true, "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.4", + "normalize-package-data": "^8.0.0", + "parse-json": "^8.3.0", + "type-fest": "^5.4.4", + "unicorn-magic": "^0.4.0" + }, "engines": { - "node": ">= 14.18.0" + "node": ">=20" }, "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", + "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/read-pkg/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-8.0.0.tgz", + "integrity": "sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^9.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/refa": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/refa/-/refa-0.12.1.tgz", + "integrity": "sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.8.0" + }, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/regexp-ast-analysis": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regexp-ast-analysis/-/regexp-ast-analysis-0.7.1.tgz", + "integrity": "sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.8.0", + "refa": "^0.12.1" + }, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "dev": true, + "license": "MIT", + "bin": { + "regexp-tree": "bin/regexp-tree" } }, "node_modules/require-directory": { @@ -3912,6 +5258,16 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/reserved-identifiers": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz", @@ -3980,26 +5336,66 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/safe-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", + "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "regexp-tree": "~0.1.1" + } + }, + "node_modules/scslre": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz", + "integrity": "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.8.0", + "refa": "^0.12.0", + "regexp-ast-analysis": "^0.7.0" + }, + "engines": { + "node": "^14.0.0 || >=16.0.0" + } + }, + "node_modules/secretlint": { + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-13.0.4.tgz", + "integrity": "sha512-OhNXAkLN/OAWWByVS/QcIzTwc/NkUJxzvVKvp5N8iAYNny7YdV/M321vfh8wh41tAB0UthYfahMRSPOfBXtbqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-creator": "13.0.4", + "@secretlint/formatter": "13.0.4", + "@secretlint/node": "13.0.4", + "@secretlint/profiler": "13.0.4", + "@secretlint/resolver": "13.0.4", + "@secretlint/walker": "13.0.4", + "debug": "^4.4.3", + "read-pkg": "^10.1.0" + }, + "bin": { + "secretlint": "bin/secretlint.js" + }, + "engines": { + "node": ">=22.0.0" + } }, "node_modules/semver": { "version": "7.8.5", @@ -4014,59 +5410,54 @@ "node": ">=10" } }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/sharp": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", - "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", - "hasInstallScript": true, + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz", + "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==", "license": "Apache-2.0", "optional": true, "dependencies": { - "@img/colour": "^1.0.0", + "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", - "semver": "^7.7.3" + "semver": "^7.8.5" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.5", - "@img/sharp-darwin-x64": "0.34.5", - "@img/sharp-libvips-darwin-arm64": "1.2.4", - "@img/sharp-libvips-darwin-x64": "1.2.4", - "@img/sharp-libvips-linux-arm": "1.2.4", - "@img/sharp-libvips-linux-arm64": "1.2.4", - "@img/sharp-libvips-linux-ppc64": "1.2.4", - "@img/sharp-libvips-linux-riscv64": "1.2.4", - "@img/sharp-libvips-linux-s390x": "1.2.4", - "@img/sharp-libvips-linux-x64": "1.2.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", - "@img/sharp-libvips-linuxmusl-x64": "1.2.4", - "@img/sharp-linux-arm": "0.34.5", - "@img/sharp-linux-arm64": "0.34.5", - "@img/sharp-linux-ppc64": "0.34.5", - "@img/sharp-linux-riscv64": "0.34.5", - "@img/sharp-linux-s390x": "0.34.5", - "@img/sharp-linux-x64": "0.34.5", - "@img/sharp-linuxmusl-arm64": "0.34.5", - "@img/sharp-linuxmusl-x64": "0.34.5", - "@img/sharp-wasm32": "0.34.5", - "@img/sharp-win32-arm64": "0.34.5", - "@img/sharp-win32-ia32": "0.34.5", - "@img/sharp-win32-x64": "0.34.5" + "@img/sharp-darwin-arm64": "0.35.3", + "@img/sharp-darwin-x64": "0.35.3", + "@img/sharp-freebsd-wasm32": "0.35.3", + "@img/sharp-libvips-darwin-arm64": "1.3.2", + "@img/sharp-libvips-darwin-x64": "1.3.2", + "@img/sharp-libvips-linux-arm": "1.3.2", + "@img/sharp-libvips-linux-arm64": "1.3.2", + "@img/sharp-libvips-linux-ppc64": "1.3.2", + "@img/sharp-libvips-linux-riscv64": "1.3.2", + "@img/sharp-libvips-linux-s390x": "1.3.2", + "@img/sharp-libvips-linux-x64": "1.3.2", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", + "@img/sharp-libvips-linuxmusl-x64": "1.3.2", + "@img/sharp-linux-arm": "0.35.3", + "@img/sharp-linux-arm64": "0.35.3", + "@img/sharp-linux-ppc64": "0.35.3", + "@img/sharp-linux-riscv64": "0.35.3", + "@img/sharp-linux-s390x": "0.35.3", + "@img/sharp-linux-x64": "0.35.3", + "@img/sharp-linuxmusl-arm64": "0.35.3", + "@img/sharp-linuxmusl-x64": "0.35.3", + "@img/sharp-webcontainers-wasm32": "0.35.3", + "@img/sharp-win32-arm64": "0.35.3", + "@img/sharp-win32-ia32": "0.35.3", + "@img/sharp-win32-x64": "0.35.3" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/shebang-command": { @@ -4115,6 +5506,19 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -4125,6 +5529,79 @@ "node": ">=8" } }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "*" + } + }, + "node_modules/spdx-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz", + "integrity": "sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-find-index": "^1.0.2", + "spdx-expression-parse": "^3.0.0", + "spdx-ranges": "^2.0.0" + } + }, + "node_modules/spdx-compare/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, "node_modules/spdx-exceptions": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", @@ -4150,41 +5627,257 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/spdx-ranges": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/spdx-ranges/-/spdx-ranges-2.1.1.tgz", + "integrity": "sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA==", + "dev": true, + "license": "(MIT AND CC-BY-3.0)" + }, + "node_modules/spdx-satisfies": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/spdx-satisfies/-/spdx-satisfies-5.0.1.tgz", + "integrity": "sha512-Nwor6W6gzFp8XX4neaKQ7ChV4wmpSh2sSDemMFSzHxpTw460jxFYeOn+jq4ybnSSw/5sc3pjka9MQPouksQNpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-compare": "^1.0.0", + "spdx-expression-parse": "^3.0.0", + "spdx-ranges": "^2.0.0" + } + }, + "node_modules/spdx-satisfies/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, - "license": "MIT", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strtok3": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/structured-source": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-4.0.0.tgz", + "integrity": "sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boundary": "^2.0.0" + } + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=10.0.0" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/table/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "engines": { - "node": ">=8" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { + "node_modules/table/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", @@ -4194,14 +5887,36 @@ "node": ">=8" } }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { + "node_modules/table/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/table/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -4214,89 +5929,120 @@ "node": ">=8" } }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, "engines": { - "node": ">=12" + "node": ">=20" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/terminal-link": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-5.0.0.tgz", + "integrity": "sha512-qFAy10MTMwjzjU8U16YS4YoZD+NQLHzLssFMNqgravjbvIPNiqkGFR4yjhJfmY9R5OFU7+yHxc6y+uGHkKwLRA==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-escapes": "^7.0.0", + "supports-hyperlinks": "^4.1.0" }, "engines": { - "node": ">=8" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "node_modules/terminal-link/node_modules/has-flag": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-5.0.1.tgz", + "integrity": "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "node_modules/terminal-link/node_modules/supports-hyperlinks": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-4.5.0.tgz", + "integrity": "sha512-ZW2OvfeCXrNTbLakPUzjQG922EeGCOteFSVoek5DKStTh898wf7zgtuFlzQN8HfZCxC3Eh02yJVrRW51hADf+w==", "dev": true, "license": "MIT", + "dependencies": { + "has-flag": "^5.0.1", + "supports-color": "^10.2.2" + }, "engines": { - "node": ">=8" + "node": ">=20" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" } }, - "node_modules/strtok3": { - "version": "10.3.5", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", - "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", - "license": "MIT", + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/textextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-6.11.0.tgz", + "integrity": "sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==", + "dev": true, + "license": "Artistic-2.0", "dependencies": { - "@tokenizer/token": "^0.3.0" + "editions": "^6.21.0" }, "engines": { - "node": ">=18" + "node": ">=4" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" + "url": "https://bevry.me/fund" } }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": ">=0.8" + } + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" } }, "node_modules/tinyglobby": { @@ -4373,6 +6119,16 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/treeify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", + "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -4521,6 +6277,22 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.8.0.tgz", + "integrity": "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typedoc": { "version": "0.28.19", "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.19.tgz", @@ -4633,6 +6405,29 @@ "dev": true, "license": "MIT" }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicorn-magic": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", + "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -4643,6 +6438,51 @@ "punycode": "^2.1.0" } }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/version-range": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz", + "integrity": "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==", + "dev": true, + "license": "Artistic-2.0", + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, "node_modules/web-streams-polyfill": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", @@ -4679,13 +6519,6 @@ "node": ">=0.10.0" } }, - "node_modules/workerpool": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", - "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -4807,96 +6640,6 @@ "url": "https://github.com/sponsors/eemeli" } }, - "node_modules/yargs": { - "version": "17.7.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", - "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 17e226378..f0fe83496 100644 --- a/package.json +++ b/package.json @@ -24,17 +24,23 @@ }, "scripts": { "build": "tsc --build && tsc-alias", - "build-for-dist": "tsc --build && tsc-alias && cp LICENSE README.md CHANGELOG.md ./dist", + "build:dist": "tsc --build && tsc-alias && cp LICENSE README.md CHANGELOG.md ./dist", "clean": "rm -rf ./dist ./docs/_build", - "test": "mocha --grep '#OptionalDepsRemoved' --invert 'tests/**/*.spec.ts'", - "test-light": "mocha --grep '#OptionalDepsRequired' --invert 'tests/**/*.spec.ts'", - "test-integration": "mocha --grep '#OptionalDepsRemoved' --invert 'tests/**/*.integration.ts'", - "test-integration-light": "mocha --grep '#OptionalDepsRequired' --invert 'tests/**/*.integration.ts'", - "test-v2": "tsc --noEmit && node --import tsx --test 'tests/v2/**/*.spec.ts'", + "test": "node tests/runner.mjs spec", + "test:light": "node tests/runner.mjs spec", + "test:integration": "node tests/runner.mjs integration", + "test:integration:light": "node tests/runner.mjs integration", + "test:v2": "tsc --noEmit && node tests/runner.mjs spec v2", "lint": "tsc --noEmit && eslint --report-unused-disable-directives './src/**/*.ts' './tests/**/*.ts'", - "lint-fix": "tsc --noEmit && eslint --fix --report-unused-disable-directives './src/**/*.ts' './tests/**/*.ts'", + "lint:check": "npm run lint", + "lint:fix": "tsc --noEmit && eslint --fix --report-unused-disable-directives './src/**/*.ts' './tests/**/*.ts'", + "lint:package": "npm run build:dist && publint ./dist && attw --pack ./dist --profile esm-only", + "lint:secrets": "secretlint '**/*'", + "lint:full": "npm run lint:check && npm run lint:package && npm run lint:secrets", + "license:check": "license-checker-rseidelsohn --onlyAllow \"MIT;Apache-2.0;BSD-2-Clause;BSD-3-Clause;ISC;LGPL-3.0-or-later;Python-2.0;BlueOak-1.0.0;CC-BY-3.0;CC0-1.0;0BSD;LGPL-3.0-only;WTFPL;Artistic-2.0;Unlicense\"", "docs": "typedoc --out docs/_build ./src/index.ts", - "docs-for-dist": "typedoc --out docs/_build ./src/index.ts && cp -r ./docs/code_samples ./docs/_build/" + "docs:dist": "typedoc --out docs/_build ./src/index.ts && cp -r ./docs/code_samples ./docs/_build/", + "prepare": "husky" }, "files": [ "src/*", @@ -56,20 +62,26 @@ "@cantoo/pdf-lib": "^2.5.3", "node-poppler": "^9.1.1", "pdf.js-extract": "~0.2.1", - "sharp": "~0.34.5" + "sharp": "^0.35.3" }, "devDependencies": { + "@arethetypeswrong/cli": "^0.18.5", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "^10.0.1", - "@types/mocha": "^10.0.10", + "@secretlint/secretlint-rule-preset-recommend": "^13.0.4", "@types/node": "^20.19.37", "@types/tmp": "^0.2.6", "@typescript-eslint/eslint-plugin": "^8.57.0", "@typescript-eslint/parser": "^8.58.1", "eslint": "^10.6.0", "eslint-plugin-jsdoc": "^62.7.1", + "eslint-plugin-security": "^4.0.1", + "eslint-plugin-sonarjs": "^4.2.0", "globals": "^17.4.0", - "mocha": "^11.7.6", + "husky": "^9.1.7", + "license-checker-rseidelsohn": "^4.4.2", + "publint": "^0.3.22", + "secretlint": "^13.0.4", "tsc-alias": "^1.8.17", "tslib": "^2.8.1", "tsx": "^4.21.0", diff --git a/src/geometry/bbox.ts b/src/geometry/bbox.ts index e44711f25..5462efa3e 100644 --- a/src/geometry/bbox.ts +++ b/src/geometry/bbox.ts @@ -2,9 +2,13 @@ import { mergeBbox } from "@/geometry/boundingBoxUtils.js"; /** A simple bounding box defined by 4 coordinates: xMin, yMin, xMax, yMax */ export class BBox { + /** Minimum X coordinate. */ xMin: number; + /** Minimum Y coordinate. */ yMin: number; + /** Maximum X coordinate. */ xMax: number; + /** Maximum Y coordinate. */ yMax: number; constructor(xMin: number, yMin: number, xMax: number, yMax: number) { @@ -14,6 +18,7 @@ export class BBox { this.yMax = yMax; } + /** Returns a bounding box that contains this box and another one. */ mergeBbox(bbox: BBox) { return mergeBbox(this, bbox); } diff --git a/src/geometry/minMax.ts b/src/geometry/minMax.ts index b5e82a8a1..14d2338f3 100644 --- a/src/geometry/minMax.ts +++ b/src/geometry/minMax.ts @@ -1 +1,7 @@ -export type MinMax = { min: number; max: number }; +/** Numeric range boundaries. */ +export type MinMax = { + /** Lower bound. */ + min: number; + /** Upper bound. */ + max: number; +}; diff --git a/src/geometry/polygonUtils.ts b/src/geometry/polygonUtils.ts index 062df1517..19e46f64b 100644 --- a/src/geometry/polygonUtils.ts +++ b/src/geometry/polygonUtils.ts @@ -93,6 +93,10 @@ export function relativeX(polygon: Array): number { return polygon.length * sum; } +/** + * Get the minimum Y coordinate in a given list of Points. + * @param polygon An array of points or polygon. + */ export function getMinYCoordinate(polygon: Array): number { return polygon.sort((point1, point2) => { if (point1[1] < point2[1]) { @@ -104,6 +108,10 @@ export function getMinYCoordinate(polygon: Array): number { })[0][1]; } +/** + * Get the minimum X coordinate in a given list of Points. + * @param polygon An array of points or polygon. + */ export function getMinXCoordinate(polygon: Array): number { return polygon.sort((point1, point2) => { if (point1[0] < point2[0]) { @@ -115,6 +123,11 @@ export function getMinXCoordinate(polygon: Array): number { })[0][0]; } +/** + * Compare two polygons on their minimum Y coordinate. + * @param polygon1 An array of points or polygon. + * @param polygon2 An array of points or polygon. + */ export function compareOnY(polygon1: Array, polygon2: Array): number { const sort: number = getMinYCoordinate(polygon1) - getMinYCoordinate(polygon2); @@ -124,6 +137,11 @@ export function compareOnY(polygon1: Array, polygon2: Array): numb return sort < 0 ? -1 : 1; } +/** + * Compare two polygons on their minimum X coordinate. + * @param polygon1 An array of points or polygon. + * @param polygon2 An array of points or polygon. + */ export function compareOnX(polygon1: Array, polygon2: Array): number { const sort: number = getMinXCoordinate(polygon1) - getMinXCoordinate(polygon2); @@ -133,6 +151,11 @@ export function compareOnX(polygon1: Array, polygon2: Array): numb return sort < 0 ? -1 : 1; } +/** + * Adjust a polygon for a given rotation. + * @param polygon An array of points or polygon. + * @param orientation The rotation angle in degrees. + */ export function adjustForRotation(polygon: Array, orientation: number): Array { if (orientation === 90) { return polygon.map(([x, y]) => [y, 1 - x]); diff --git a/src/http/baseSettings.ts b/src/http/baseSettings.ts index 5a9224350..7dd2dd71d 100644 --- a/src/http/baseSettings.ts +++ b/src/http/baseSettings.ts @@ -1,4 +1,5 @@ import { Dispatcher, getGlobalDispatcher } from "undici"; +// eslint-disable-next-line no-restricted-imports import packageJson from "../../package.json" with { type: "json" }; import * as os from "os"; import { TIMEOUT_SECS_DEFAULT } from "./apiCore.js"; @@ -9,9 +10,13 @@ export interface MindeeApiConstructorProps { } export abstract class BaseSettings { + /** API key used for authenticated requests. */ apiKey: string; + /** API hostname used for requests. */ hostname: string; + /** Request timeout in seconds. */ timeoutSecs: number; + /** HTTP dispatcher used by undici. */ dispatcher: Dispatcher; protected constructor(apiKey?: string, dispatcher?: Dispatcher) { @@ -27,6 +32,7 @@ export abstract class BaseSettings { : TIMEOUT_SECS_DEFAULT; } + /** Builds a default user-agent string for outgoing requests. */ protected getUserAgent(): string { let platform = os.type().toLowerCase(); if (platform.includes("darwin")) { diff --git a/src/http/index.ts b/src/http/index.ts index d740b7854..5b475e225 100644 --- a/src/http/index.ts +++ b/src/http/index.ts @@ -3,5 +3,5 @@ export { isValidSyncResponse, isValidAsyncResponse, cleanRequestData, -} from "../v1/http/responseValidation.js"; +} from "@/v1/http/responseValidation.js"; export { BaseSettings } from "./baseSettings.js"; diff --git a/src/image/extractedImage.ts b/src/image/extractedImage.ts index 8465598ef..fdcccb6a9 100644 --- a/src/image/extractedImage.ts +++ b/src/image/extractedImage.ts @@ -14,9 +14,13 @@ import { loadOptionalDependency } from "@/dependency/index.js"; * Generic class for image extraction. */ export class ExtractedImage { + /** Raw file bytes for the extracted image artifact. */ public buffer: Buffer; + /** Filename used when exporting the extracted image. */ public filename: string; + /** 1-based source page identifier. */ public readonly pageId: number; + /** Identifier of the extracted element on the page. */ public readonly elementId: number; constructor(buffer: Uint8Array, fileName: string, pageId: number, elementId: number) { diff --git a/src/image/extractedImages.ts b/src/image/extractedImages.ts index 2839523f1..3f917f355 100644 --- a/src/image/extractedImages.ts +++ b/src/image/extractedImages.ts @@ -1,5 +1,6 @@ import { ExtractedImage } from "@/image/index.js"; +/** Collection of extracted image artifacts. */ export class ExtractedImages extends Array { constructor(...items: ExtractedImage[]) { super(...items); diff --git a/src/image/imageExtractor.ts b/src/image/imageExtractor.ts index 16a62edcb..9ad133ff2 100644 --- a/src/image/imageExtractor.ts +++ b/src/image/imageExtractor.ts @@ -14,6 +14,9 @@ import type * as pdfLibTypes from "@cantoo/pdf-lib"; let pdfLib: typeof pdfLibTypes | null = null; +/** + * Load the PDF library if not already loaded. + */ async function getPdfLib(): Promise { if (!pdfLib) { const pdfLibImport = await loadOptionalDependency( @@ -45,7 +48,7 @@ export async function extractImagesFromPolygon( const extractions = await extractFromPage(pdfPage, polygons, true, quality); const extractedImages = extractions.map( (buffer, elementId) => - new ExtractedImage(buffer, inputSource.filename + `_page-${pageId}-item-${elementId}.jpg`, pageId, elementId) + new ExtractedImage(buffer, inputSource.filename + `_page${pageId}-${elementId}.jpg`, pageId, elementId) ); allExtractedImages.push(...extractedImages); } @@ -53,12 +56,56 @@ export async function extractImagesFromPolygon( } /** - * Extracts elements from a page based off of a list of bounding boxes. - * - * @param pdfPage PDF Page to extract from. - * @param polygons List of coordinates to pull the elements from. - * @param asImage Whether to return the extracted elements as images. - * @param quality JPEG quality of extracted images, given as number between 0 and 1. + * Helper function to handle the drawing math and orientation. + */ +function drawOrientedPage( + pdfLib: any, + samplePage: pdfLibTypes.PDFPage, + cropped: pdfLibTypes.PDFEmbeddedPage, + orientation: number, + finalWidth: number, + finalHeight: number, + scaledWidth: number, + scaledHeight: number +) { + if (orientation === 0) { + samplePage.drawPage(cropped, { + width: scaledWidth, + height: scaledHeight, + }); + } else if (orientation === 90) { + samplePage.drawPage(cropped, { + x: 0, + y: finalHeight, + width: scaledWidth, + height: scaledHeight, + rotate: pdfLib.degrees(270), + }); + } else if (orientation === 180) { + samplePage.drawPage(cropped, { + x: finalWidth, + y: finalHeight, + width: scaledWidth, + height: scaledHeight, + rotate: pdfLib.degrees(180), + }); + } else if (orientation === 270) { + samplePage.drawPage(cropped, { + x: finalWidth, + y: 0, + width: scaledWidth, + height: scaledHeight, + rotate: pdfLib.degrees(90), + }); + } +} + +/** + * Extract images from a PDF page. + * @param pdfPage The PDF page to extract images from. + * @param polygons The polygons to extract images from. + * @param asImage Whether to return the extracted images as images or as raw data. + * @param quality The quality of the extracted images. */ export async function extractFromPage( pdfPage: pdfLibTypes.PDFPage, @@ -69,16 +116,18 @@ export async function extractFromPage( const pdfLib = await getPdfLib(); const { width, height } = pdfPage.getSize(); const extractedElements: Uint8Array[] = []; - if (quality && (quality < 0)) { - throw new MindeeImageError("Quality must be a number between 0 and 1"); - } - if (quality && quality > 1) { - logger.warn("Quality is greater than 1, this operation will apply a manual upscale on the output." + - " Use only if you know what you are doing."); + if (quality !== undefined) { + if (quality < 0) { + throw new MindeeImageError("Quality must be a number between 0 and 1"); + } + if (quality > 1) { + logger.warn("Quality is greater than 1, this operation will apply a manual upscale on the output." + + " Use only if you know what you are doing."); + } } + const qualityScale = quality ?? 1; const orientation = pdfPage.getRotation().angle; - const sourceDoc = pdfPage.doc; const pageIndex = sourceDoc.getPages().indexOf(pdfPage); @@ -86,77 +135,46 @@ export async function extractFromPage( logger.debug(`Extracting image with polygon: ${origPolygon.toString()}`); const tempPdf = await pdfLib.PDFDocument.create(); - const [copiedPage] = await tempPdf.copyPages(sourceDoc, [pageIndex]); - const polygon = adjustForRotation(origPolygon, orientation); - const newWidth = width * (getMinMaxX(polygon).max - getMinMaxX(polygon).min); - const newHeight = height * (getMinMaxY(polygon).max - getMinMaxY(polygon).min); + const minX = getMinMaxX(polygon).min; + const maxX = getMinMaxX(polygon).max; + const minY = getMinMaxY(polygon).min; + const maxY = getMinMaxY(polygon).max; + + const newWidth = width * (maxX - minX); + const newHeight = height * (maxY - minY); const cropped = await tempPdf.embedPage(copiedPage, { - left: getMinMaxX(polygon).min * width, - right: getMinMaxX(polygon).max * width, - top: height - (getMinMaxY(polygon).min * height), - bottom: height - (getMinMaxY(polygon).max * height), + left: minX * width, + right: maxX * width, + top: height - (minY * height), + bottom: height - (maxY * height), }); - let finalWidth: number; - let finalHeight: number; - if (orientation === 90 || orientation === 270) { - finalWidth = newHeight * qualityScale; - finalHeight = newWidth * qualityScale; - } else { - finalWidth = newWidth * qualityScale; - finalHeight = newHeight * qualityScale; - } + const isVertical = orientation === 90 || orientation === 270; + const finalWidth = (isVertical ? newHeight : newWidth) * qualityScale; + const finalHeight = (isVertical ? newWidth : newHeight) * qualityScale; const samplePage = tempPdf.addPage([finalWidth, finalHeight]); samplePage.drawRectangle({ - x: 0, - y: 0, - width: finalWidth, - height: finalHeight, - color: pdfLib.rgb(1, 1, 1), + x: 0, y: 0, width: finalWidth, height: finalHeight, color: pdfLib.rgb(1, 1, 1), }); - if (orientation === 0) { - samplePage.drawPage(cropped, { - width: newWidth * qualityScale, - height: newHeight * qualityScale, - }); - } else if (orientation === 90) { - samplePage.drawPage(cropped, { - x: 0, - y: finalHeight, - width: newWidth * qualityScale, - height: newHeight * qualityScale, - rotate: pdfLib.degrees(270), - }); - } else if (orientation === 180) { - samplePage.drawPage(cropped, { - x: finalWidth, - y: finalHeight, - width: newWidth * qualityScale, - height: newHeight * qualityScale, - rotate: pdfLib.degrees(180), - }); - } else if (orientation === 270) { - samplePage.drawPage(cropped, { - x: finalWidth, - y: 0, - width: newWidth * qualityScale, - height: newHeight * qualityScale, - rotate: pdfLib.degrees(90), - }); - } + drawOrientedPage( + pdfLib, + samplePage, + cropped, + orientation, + finalWidth, + finalHeight, + newWidth * qualityScale, + newHeight * qualityScale + ); const pdfBuffer = Buffer.from(await tempPdf.save()); - if (asImage) { - extractedElements.push(await rasterizePage(pdfBuffer, 0, 100)); - } else { - extractedElements.push(pdfBuffer); - } + extractedElements.push(asImage ? await rasterizePage(pdfBuffer, 0, 100) : pdfBuffer); } return extractedElements; diff --git a/src/input/base64Input.ts b/src/input/base64Input.ts index d8c67fc3f..4f07815a0 100644 --- a/src/input/base64Input.ts +++ b/src/input/base64Input.ts @@ -7,8 +7,10 @@ interface Base64InputProps { filename: string; } +/** Local input source backed by a base64-encoded payload. */ export class Base64Input extends LocalInputSource { private inputString: string; + /** Decoded binary payload built from the base64 input. */ fileObject: Buffer = Buffer.alloc(0); constructor({ inputString, filename }: Base64InputProps) { @@ -19,6 +21,7 @@ export class Base64Input extends LocalInputSource { this.inputString = inputString; } + /** Decodes and validates the provided base64 content. */ async init() { if (this.initialized) { return; diff --git a/src/input/bufferInput.ts b/src/input/bufferInput.ts index f3cd71354..ad00dd4a2 100644 --- a/src/input/bufferInput.ts +++ b/src/input/bufferInput.ts @@ -7,6 +7,7 @@ interface BufferInputProps { filename: string; } +/** Local input source backed by an in-memory `Buffer`. */ export class BufferInput extends LocalInputSource { constructor({ buffer, filename }: BufferInputProps) { super({ @@ -16,6 +17,7 @@ export class BufferInput extends LocalInputSource { this.filename = filename; } + /** Validates and initializes the in-memory buffer input. */ async init(): Promise { if (this.initialized) { return; diff --git a/src/input/bytesInput.ts b/src/input/bytesInput.ts index 3387b34cd..ec3234598 100644 --- a/src/input/bytesInput.ts +++ b/src/input/bytesInput.ts @@ -7,8 +7,10 @@ interface BytesInputProps { filename: string; } +/** Local input source backed by a `Uint8Array`. */ export class BytesInput extends LocalInputSource { private inputBytes: Uint8Array; + /** Binary payload built from the byte array. */ fileObject: Buffer = Buffer.alloc(0); constructor({ inputBytes, filename }: BytesInputProps) { @@ -19,6 +21,7 @@ export class BytesInput extends LocalInputSource { this.inputBytes = inputBytes; } + /** Converts bytes to a buffer and validates MIME type. */ async init() { if (this.initialized) { return; diff --git a/src/input/inputSource.ts b/src/input/inputSource.ts index 8ad78358e..43c8a54af 100644 --- a/src/input/inputSource.ts +++ b/src/input/inputSource.ts @@ -14,14 +14,29 @@ export const INPUT_TYPE_BYTES = "bytes"; export const INPUT_TYPE_PATH = "path"; export const INPUT_TYPE_BUFFER = "buffer"; +/** + * Abstract class for input sources. + */ export abstract class InputSource { + /** + * The file object used by the input source. + */ fileObject: Buffer | string = ""; + /** + * Whether the input source has been initialized. + * @protected + */ + /** Whether the source has already been initialized. */ protected initialized: boolean = false; + /** Initializes the input source and populates its file object. */ async init() { throw new MindeeInputSourceError("not Implemented"); } + /** + * Returns whether the input source has been initialized. + */ public isInitialized() { return this.initialized; } diff --git a/src/input/localInputSource.ts b/src/input/localInputSource.ts index 2a0e0dea9..819b22dad 100644 --- a/src/input/localInputSource.ts +++ b/src/input/localInputSource.ts @@ -5,7 +5,7 @@ import { logger } from "@/logger.js"; import { compressImage } from "@/image/index.js"; import { compressPdf, countPages, extractPages, hasSourceText } from "@/pdf/index.js"; import { fileTypeFromBuffer } from "file-type"; -import { PageOptions } from "../input/pageOptions.js"; +import { PageOptions } from "@/input/pageOptions.js"; import { InputSource, InputConstructor, @@ -35,10 +35,15 @@ const ALLOWED_INPUT_TYPES = [ ]; export abstract class LocalInputSource extends InputSource { + /** Type of local input source (path, stream, bytes, ...). */ public inputType: string; + /** Original filename associated with the input. */ public filename: string = ""; + /** Original filepath when available. */ public filepath?: string; + /** MIME type detected for the source content. */ public mimeType: string = ""; + /** Binary payload for local sources. */ public fileObject!: Buffer | string; /** @@ -57,6 +62,7 @@ export abstract class LocalInputSource extends InputSource { logger.debug(`Initialized local input source of type: ${inputType}`); } + /** Detects and validates the MIME type from the current file object. */ protected async checkMimetype(): Promise { if (!(this.fileObject instanceof Buffer)) { throw new MindeeInputSourceError( diff --git a/src/input/pathInput.ts b/src/input/pathInput.ts index c6f058510..46d52b777 100644 --- a/src/input/pathInput.ts +++ b/src/input/pathInput.ts @@ -8,8 +8,11 @@ interface PathInputProps { inputPath: string; } +/** Local input source that reads a file from disk by path. */ export class PathInput extends LocalInputSource { + /** Path used to read the local file. */ readonly inputPath: string; + /** File content loaded from disk. */ fileObject: Buffer = Buffer.alloc(0); constructor({ inputPath }: PathInputProps) { @@ -20,6 +23,7 @@ export class PathInput extends LocalInputSource { this.filename = path.basename(this.inputPath); } + /** Reads the file from disk and validates MIME type. */ async init() { if (this.initialized) { return; diff --git a/src/input/streamInput.ts b/src/input/streamInput.ts index 5d5fea40c..9aa4d9ed7 100644 --- a/src/input/streamInput.ts +++ b/src/input/streamInput.ts @@ -9,8 +9,10 @@ interface StreamInputProps { filename: string; } +/** Local input source backed by a readable stream. */ export class StreamInput extends LocalInputSource { private readonly inputStream: Readable; + /** Buffered payload created from the stream. */ fileObject: Buffer = Buffer.alloc(0); constructor({ inputStream, filename }: StreamInputProps) { @@ -21,6 +23,7 @@ export class StreamInput extends LocalInputSource { this.inputStream = inputStream; } + /** Reads the stream to completion and validates MIME type. */ async init() { if (this.initialized) { return; @@ -31,6 +34,7 @@ export class StreamInput extends LocalInputSource { this.initialized = true; } + /** Converts a readable stream into a single buffer. */ async stream2buffer(stream: Readable, signal?: AbortSignal): Promise { return new Promise((resolve, reject) => { if (stream.closed || stream.destroyed) { diff --git a/src/input/urlInput.ts b/src/input/urlInput.ts index 18eca804f..6c9f09af0 100644 --- a/src/input/urlInput.ts +++ b/src/input/urlInput.ts @@ -8,8 +8,11 @@ import { logger } from "@/logger.js"; import { MindeeInputSourceError } from "@/errors/index.js"; import { BytesInput } from "./bytesInput.js"; +/** Remote input source represented by a validated HTTPS URL. */ export class UrlInput extends InputSource { + /** HTTPS URL of the remote input file. */ public readonly url: string; + /** Dispatcher used for HTTP requests. */ public readonly dispatcher; constructor( @@ -21,6 +24,7 @@ export class UrlInput extends InputSource { logger.debug("Initialized URL input source."); } + /** Initializes this source by validating and storing the URL. */ async init() { if (this.initialized) { return; @@ -66,6 +70,7 @@ export class UrlInput extends InputSource { return await this.makeRequest(this.url, headers, 0, maxRedirects); } + /** Downloads the URL content and writes it to disk. */ async saveToFile(options: { filepath: string; filename?: string; @@ -83,6 +88,7 @@ export class UrlInput extends InputSource { return fullPath; } + /** Downloads the URL content and returns it as a local bytes source. */ async asLocalInputSource(options: { filename?: string; username?: string; @@ -157,7 +163,6 @@ export class UrlInput extends InputSource { { method: "GET", headers: headers, - throwOnError: false, dispatcher: this.dispatcher, } ); diff --git a/src/logger.ts b/src/logger.ts index a8651b64c..fcd788bfa 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -23,7 +23,7 @@ class Logger implements LoggerInterface { if (!(levelToSet in LOG_LEVELS)) { this.level = LOG_LEVELS["debug"]; } - this.level = LOG_LEVELS[levelToSet]; + this.level = LOG_LEVELS[levelToSet.toString()]; } debug(...args: any[]) { diff --git a/src/parsing/dateParser.ts b/src/parsing/dateParser.ts index 37d762943..9d22f69dc 100644 --- a/src/parsing/dateParser.ts +++ b/src/parsing/dateParser.ts @@ -1,8 +1,12 @@ +/** + * Parse a date string into a Date object. + * @param dateString The date string to parse. + */ export function parseDate(dateString: string | null): Date | null { if (!dateString) { return null; } - if (!/Z$/.test(dateString) && !/[+-][0-9]{2}:[0-9]{2}$/.test(dateString)) { + if (!/Z$/.test(dateString) && !/[+-]\d{2}:\d{2}$/.test(dateString)) { dateString += "Z"; } return new Date(dateString); diff --git a/src/parsing/localResponseBase.ts b/src/parsing/localResponseBase.ts index d1f6cfa10..ed37541e1 100644 --- a/src/parsing/localResponseBase.ts +++ b/src/parsing/localResponseBase.ts @@ -1,7 +1,7 @@ import * as crypto from "crypto"; import * as fs from "node:fs/promises"; import { StringDict } from "@/parsing/stringDict.js"; -import { MindeeError } from "../errors/index.js"; +import { MindeeError } from "@/errors/index.js"; import { Buffer } from "buffer"; /** @@ -11,6 +11,7 @@ import { Buffer } from "buffer"; export abstract class LocalResponseBase { private file: Buffer; private readonly inputHandle: Buffer | string; + /** Whether the local response payload has been loaded. */ protected initialized = false; /** @@ -21,6 +22,7 @@ export abstract class LocalResponseBase { this.inputHandle = inputFile; } + /** Loads the local payload from a string, buffer, or file path. */ public async init() { /** * @param inputFile - The input file, which can be a Buffer, string, or PathLike. diff --git a/src/parsing/stringDict.ts b/src/parsing/stringDict.ts index c7a966d76..8412a10b4 100644 --- a/src/parsing/stringDict.ts +++ b/src/parsing/stringDict.ts @@ -1 +1,2 @@ +/** Generic string-keyed dictionary used for raw API payloads. */ export type StringDict = { [index: string]: any }; diff --git a/src/pdf/extractedPdf.ts b/src/pdf/extractedPdf.ts index 0c36f5a4d..6fbaea233 100644 --- a/src/pdf/extractedPdf.ts +++ b/src/pdf/extractedPdf.ts @@ -6,10 +6,15 @@ import { writeFile } from "fs/promises"; import { logger } from "@/logger.js"; import { writeFileSync } from "node:fs"; +/** Represents a PDF artifact produced by an extraction/splitting operation. */ export class ExtractedPdf { + /** Raw bytes of the extracted PDF segment. */ public readonly buffer: Buffer; + /** Filename suggested for export. */ public readonly filename: string; + /** Number of pages in this extracted PDF. */ public readonly pageCount: number; + /** Zero-based indexes of pages included in this extracted PDF. */ public readonly pageIndexes: number[]; constructor(pdfData: Buffer, filename: string, pageIndexes: number[]) { diff --git a/src/pdf/extractedPdfs.ts b/src/pdf/extractedPdfs.ts index 19771b0f1..fd72e9a4e 100644 --- a/src/pdf/extractedPdfs.ts +++ b/src/pdf/extractedPdfs.ts @@ -1,5 +1,6 @@ import { ExtractedPdf } from "@/pdf/extractedPdf.js"; +/** Collection of extracted PDF artifacts. */ export class ExtractedPdfs extends Array { constructor(...items: ExtractedPdf[]) { super(...items); diff --git a/src/pdf/pdfCompressor.ts b/src/pdf/pdfCompressor.ts index 9442f8c43..68a1201aa 100644 --- a/src/pdf/pdfCompressor.ts +++ b/src/pdf/pdfCompressor.ts @@ -8,6 +8,9 @@ import { ExtractedPdfInfo, extractTextFromPdf, hasSourceText, rasterizePage } fr let pdfLib: typeof pdfLibTypes | null = null; +/** + * Load the PDF library if not already loaded. + */ async function getPdfLib(): Promise { if (!pdfLib) { const pdfLibImport = await loadOptionalDependency( @@ -219,6 +222,11 @@ async function createNewPdfFromCompressedPages(compressedPages: Buffer[]): Promi return Buffer.from(compressedPdfBytes); } +/** + * Add text to a PDF page. + * @param page The PDF page to add text to. + * @param textInfo The extracted text information to add to the page. + */ async function addTextToPdfPage( page: pdfLibTypes.PDFPage, textInfo: ExtractedPdfInfo | null @@ -240,6 +248,10 @@ async function addTextToPdfPage( } } +/** + * Get a font from a given font name. + * @param fontName The name of the font to get. + */ async function getFontFromName(fontName: string): Promise { const pdfLib = await getPdfLib(); const pdfDoc = await pdfLib.PDFDocument.create(); diff --git a/src/pdf/pdfExtractor.ts b/src/pdf/pdfExtractor.ts index 0dcfefae3..ff110e963 100644 --- a/src/pdf/pdfExtractor.ts +++ b/src/pdf/pdfExtractor.ts @@ -12,6 +12,9 @@ import { createPdfFromInputSource, extractPages } from "@/pdf/pdfOperation.js"; let pdfLib: typeof pdfLibTypes | null = null; +/** + * Load the PDF library if not already loaded. + */ async function getPdfLib(): Promise { if (!pdfLib) { const pdfLibImport = await loadOptionalDependency("@cantoo/pdf-lib", "Text Embedding"); diff --git a/src/pdf/pdfOperation.ts b/src/pdf/pdfOperation.ts index 8af6b32a2..7bfa26f46 100644 --- a/src/pdf/pdfOperation.ts +++ b/src/pdf/pdfOperation.ts @@ -10,6 +10,9 @@ import { LocalInputSource } from "@/input/index.js"; let pdfLib: typeof pdfLibTypes | null = null; +/** + * Load the PDF library if not already loaded. + */ async function getPdfLib(): Promise { if (!pdfLib) { const pdfLibImport = await loadOptionalDependency("@cantoo/pdf-lib", "Text Embedding"); @@ -18,8 +21,11 @@ async function getPdfLib(): Promise { return pdfLib!; } +/** Result of a PDF split/extract operation. */ export interface SplitPdf { + /** PDF content after page extraction. */ file: Buffer; + /** Number of pages removed from the source PDF. */ totalPagesRemoved: number; } diff --git a/src/pdf/pdfUtils.ts b/src/pdf/pdfUtils.ts index 748d9c9bc..606c337a4 100644 --- a/src/pdf/pdfUtils.ts +++ b/src/pdf/pdfUtils.ts @@ -28,7 +28,11 @@ export interface ExtractedPdfInfo { getConcatenatedText: () => string; } - +/** + * Concatenate the text from all pages in a PDF document. + * @param pages The pages to concatenate. + * @returns The concatenated text. + */ function getConcatenatedText(pages: PageTextInfo[]): string { return pages.flatMap( page => page.content.map( diff --git a/src/v1/cli.ts b/src/v1/cli.ts index 6a628dcd4..12ebf8f92 100644 --- a/src/v1/cli.ts +++ b/src/v1/cli.ts @@ -1,20 +1,10 @@ -import { - Command, OptionValues, Option, -} from "commander"; -import { - Document, Inference, StringDict, -} from "@/v1/parsing/common/index.js"; -import { - Client, PredictOptions, -} from "./client.js"; -import { - PageOptions, PageOptionsOperation, PathInput, -} from "@/input/index.js"; +import { PageOptions, PageOptionsOperation, PathInput } from "@/input/index.js"; +import { Document, Inference, StringDict } from "@/v1/parsing/common/index.js"; +import { Command, Option, OptionValues } from "commander"; import * as console from "console"; -import { - CLI_COMMAND_CONFIG, COMMAND_GENERATED, ProductConfig, -} from "./product/cliProducts.js"; +import { Client, PredictOptions } from "./client.js"; import { Endpoint } from "./http/index.js"; +import { CLI_COMMAND_CONFIG, COMMAND_GENERATED, ProductConfig } from "./product/cliProducts.js"; const program = new Command(); @@ -23,6 +13,10 @@ const program = new Command(); // EXECUTE THE COMMANDS // +/** + * Load the client with the given options. + * @param options Command options. + */ function initClient(options: OptionValues): Client { return new Client({ apiKey: options.apiKey, @@ -30,6 +24,10 @@ function initClient(options: OptionValues): Client { }); } +/** + * Load the product configuration for the given command. + * @param command Command name. + */ function getConfig(command: string): ProductConfig { const conf = CLI_COMMAND_CONFIG.get(command); if (conf === undefined) { @@ -38,6 +36,10 @@ function getConfig(command: string): ProductConfig { return conf; } +/** + * Load the page options for the given command. + * @param options Command options. + */ function getPageOptions(options: any) { let pageOptions: PageOptions | undefined = undefined; if (options.cutPages) { @@ -50,6 +52,10 @@ function getPageOptions(options: any) { return pageOptions; } +/** + * Load the prediction parameters for the given command. + * @param options Command options. + */ function getPredictParams(options: any): PredictOptions { return { allWords: options.allWords, @@ -57,6 +63,13 @@ function getPredictParams(options: any): PredictOptions { }; } +/** + * Call the parse endpoint for the given command. + * @param productClass Product class. + * @param command Command name. + * @param inputPath Input path. + * @param options Command options. + */ async function callParse( productClass: new (httpResponse: StringDict) => T, command: string, @@ -90,6 +103,13 @@ async function callParse( printResponse(response.document, options); } +/** + * Call the enqueue and parse endpoint for the given command. + * @param productClass Product class. + * @param command Command name. + * @param inputPath Input path. + * @param options Command options. + */ async function callEnqueueAndParse( productClass: new (httpResponse: StringDict) => T, command: string, @@ -123,6 +143,12 @@ async function callEnqueueAndParse( printResponse(response.document, options); } +/** + * Call the get document endpoint for the given command. + * @param productClass Product class. + * @param documentId Document ID. + * @param options Command options. + */ async function callGetDocument( productClass: new (httpResponse: StringDict) => T, documentId: string, options: any @@ -132,6 +158,11 @@ async function callGetDocument( printResponse(response.document, options); } +/** + * Print the response for the given command. + * @param document Document. + * @param options Command options. + */ function printResponse( document: Document, options: any @@ -155,10 +186,19 @@ function printResponse( // BUILD THE COMMANDS // +/** + * Add the main options to the given command. + * @param prog Command. + */ function addMainOptions(prog: Command) { prog.option("-k, --api-key ", "API key for document endpoint"); } +/** + * Add the post options to the given command. + * @param prog Command. + * @param info Product configuration. + */ function addPostOptions(prog: Command, info: ProductConfig) { prog.option("-c, --cut-pages", "keep only the first 5 pages of the document"); if (info.allWords) { @@ -167,6 +207,10 @@ function addPostOptions(prog: Command, info: ProductConfig) { prog.argument("", "full path to the file"); } +/** + * Add the custom post options to the given command. + * @param prog Command. + */ function addCustomPostOptions(prog: Command) { prog.requiredOption( "-e, --endpoint ", @@ -182,10 +226,20 @@ function addCustomPostOptions(prog: Command) { ); } +/** + * Add the display options to the given command. + * @param prog Command. + */ function addDisplayOptions(prog: Command) { prog.option("-p, --pages", "show content of individual pages"); } +/** + * Route the command to the appropriate action. + * @param command Command. + * @param inputPath Input path. + * @param allOptions All options. + */ function routeSwitchboard( command: Command, inputPath: string, @@ -201,38 +255,29 @@ function routeSwitchboard( return callParse(docClass, command.name(), inputPath, allOptions); } +/** + * Add the predict action to the given command. + * @param prog Command. + */ function addPredictAction(prog: Command) { - if (prog.name() === COMMAND_GENERATED) { - prog.action(function ( - inputPath: string, - options: OptionValues, - command: Command - ) { - const allOptions = { - ...prog.parent?.parent?.opts(), - ...prog.parent?.opts(), - ...prog.opts(), - ...options, - }; - return routeSwitchboard(command, inputPath, allOptions); - }); - } else { - prog.action(function ( - inputPath: string, - options: OptionValues, - command: Command - ) { - const allOptions = { - ...prog.parent?.parent?.opts(), - ...prog.parent?.opts(), - ...prog.opts(), - ...options, - }; - return routeSwitchboard(command, inputPath, allOptions); - }); - } + prog.action(function ( + inputPath: string, + options: OptionValues, + command: Command + ) { + const allOptions = { + ...prog.parent?.parent?.opts(), + ...prog.parent?.opts(), + ...prog.opts(), + ...options, + }; + return routeSwitchboard(command, inputPath, allOptions); + }); } +/** + * Add the main CLI action. + */ export function cli() { program.name("mindee") .description("Command line interface for Mindee products.") diff --git a/src/v1/client.ts b/src/v1/client.ts index 51364859d..fd2e7668e 100644 --- a/src/v1/client.ts +++ b/src/v1/client.ts @@ -85,35 +85,36 @@ export interface WorkflowOptions extends BaseOptions { publicUrl?: string; } +/** Timer options forwarded to `setTimeout` in polling loops. */ +export interface TimerOptions { + /** Whether the timer should keep the event loop active. */ + ref?: boolean, + /** Optional signal used to abort timer waits. */ + signal?: AbortSignal +} + /** * Asynchronous polling parameters. */ export interface OptionalAsyncOptions extends PredictOptions { + /** Delay in seconds before the first polling attempt. */ initialDelaySec?: number; + /** Delay in seconds between polling attempts. */ delaySec?: number; + /** Maximum number of polling attempts. */ maxRetries?: number; - initialTimerOptions?: { - ref?: boolean, - signal?: AbortSignal - }; - recurringTimerOptions?: { - ref?: boolean, - signal?: AbortSignal - } + /** Timer options used for the first delay. */ + initialTimerOptions?: TimerOptions; + /** Timer options used for recurring polling delays. */ + recurringTimerOptions?: TimerOptions; } export interface AsyncOptions extends PredictOptions { initialDelaySec: number; delaySec: number; maxRetries: number; - initialTimerOptions?: { - ref?: boolean, - signal?: AbortSignal - }; - recurringTimerOptions?: { - ref?: boolean, - signal?: AbortSignal - } + initialTimerOptions?: TimerOptions; + recurringTimerOptions?: TimerOptions; } export interface ClientOptions { diff --git a/src/v1/extraction/invoiceSplitterExtractor/extractedInvoiceSplitterImage.ts b/src/v1/extraction/invoiceSplitterExtractor/extractedInvoiceSplitterImage.ts index 5c9bcaa8f..bd422a94a 100644 --- a/src/v1/extraction/invoiceSplitterExtractor/extractedInvoiceSplitterImage.ts +++ b/src/v1/extraction/invoiceSplitterExtractor/extractedInvoiceSplitterImage.ts @@ -4,7 +4,9 @@ import { ExtractedPdf } from "@/pdf/index.js"; * Wrapper class for extracted invoice pages. */ export class ExtractedInvoiceSplitterImage extends ExtractedPdf { + /** Start page index of the extracted invoice segment. */ readonly pageIdMin: number; + /** End page index of the extracted invoice segment. */ readonly pageIdMax: number; constructor(bytes: Uint8Array, pageIndices: [number, number]) { diff --git a/src/v1/extraction/invoiceSplitterExtractor/invoiceSplitterExtractor.ts b/src/v1/extraction/invoiceSplitterExtractor/invoiceSplitterExtractor.ts index b6de0479e..538101548 100644 --- a/src/v1/extraction/invoiceSplitterExtractor/invoiceSplitterExtractor.ts +++ b/src/v1/extraction/invoiceSplitterExtractor/invoiceSplitterExtractor.ts @@ -9,6 +9,9 @@ import { loadOptionalDependency } from "@/dependency/index.js"; let pdfLib: typeof pdfLibTypes | null = null; +/** + * Load the pdf-lib dependency. + */ async function getPdfLib(): Promise { if (!pdfLib) { const pdfLibImport = await loadOptionalDependency("@cantoo/pdf-lib", "Text Embedding"); @@ -17,6 +20,12 @@ async function getPdfLib(): Promise { return pdfLib!; } +/** + * Split the given PDF document into multiple PDF documents. + * @param pdfDoc PDF document. + * @param invoicePageGroups Page groups. + * @returns Extracted invoice splitter images. + */ async function splitPdf( pdfDoc: pdfLibTypes.PDFDocument, invoicePageGroups: number[][]): Promise { @@ -45,6 +54,11 @@ async function splitPdf( return generatedPdfs; } +/** + * Load the PDF document from the given input file. + * @param inputFile Input file. + * @returns PDF document. + */ async function getPdfDoc(inputFile: LocalInputSource): Promise { const pdfLib = await getPdfLib(); await inputFile.init(); diff --git a/src/v1/extraction/multiReceiptsExtractor/extractedMultiReceiptImage.ts b/src/v1/extraction/multiReceiptsExtractor/extractedMultiReceiptImage.ts index 452504657..0da6d6779 100644 --- a/src/v1/extraction/multiReceiptsExtractor/extractedMultiReceiptImage.ts +++ b/src/v1/extraction/multiReceiptsExtractor/extractedMultiReceiptImage.ts @@ -4,7 +4,9 @@ import { ExtractedImage } from "@/image/index.js"; * Wrapper class for extracted multiple-receipts images. */ export class ExtractedMultiReceiptImage extends ExtractedImage { + /** Receipt index within the source page. */ readonly receiptId: number; + /** Source page identifier. */ readonly pageId: number; constructor(buffer: Uint8Array, pageId: number, receiptId: number) { diff --git a/src/v1/extraction/multiReceiptsExtractor/multiReceiptsExtractor.ts b/src/v1/extraction/multiReceiptsExtractor/multiReceiptsExtractor.ts index a751b4014..af3437cee 100644 --- a/src/v1/extraction/multiReceiptsExtractor/multiReceiptsExtractor.ts +++ b/src/v1/extraction/multiReceiptsExtractor/multiReceiptsExtractor.ts @@ -13,6 +13,9 @@ import { loadOptionalDependency } from "@/dependency/index.js"; let pdfLib: typeof pdfLibTypes | null = null; +/** + * Load the pdf-lib dependency. + */ async function getPdfLib(): Promise { if (!pdfLib) { const pdfLibImport = await loadOptionalDependency("@cantoo/pdf-lib", "Text Embedding"); diff --git a/src/v1/http/apiSettingsV1.ts b/src/v1/http/apiSettingsV1.ts index ec4213d09..1c3b8fb23 100644 --- a/src/v1/http/apiSettingsV1.ts +++ b/src/v1/http/apiSettingsV1.ts @@ -1,14 +1,17 @@ import { logger } from "@/logger.js"; -import { BaseSettings, MindeeApiConstructorProps } from "../../http/baseSettings.js"; +import { BaseSettings, MindeeApiConstructorProps } from "@/http/baseSettings.js"; import { MindeeConfigurationError } from "@/errors/index.js"; +/** Default account owner for built-in v1 APIs. */ export const STANDARD_API_OWNER: string = "mindee"; const API_V1_KEY_ENVVAR_NAME: string = "MINDEE_API_KEY"; const API_V1_HOST_ENVVAR_NAME: string = "MINDEE_API_HOST"; const DEFAULT_MINDEE_API_HOST: string = "api.mindee.net"; +/** Runtime settings container for v1 API calls. */ export class ApiSettingsV1 extends BaseSettings { + /** Default headers sent with each v1 API request. */ baseHeaders: Record; constructor({ @@ -29,6 +32,7 @@ export class ApiSettingsV1 extends BaseSettings { }; } + /** Resolves the API key from environment variables. */ protected apiKeyFromEnv(): string { const envVarValue = process.env[API_V1_KEY_ENVVAR_NAME]; if (envVarValue) { @@ -40,6 +44,7 @@ export class ApiSettingsV1 extends BaseSettings { return ""; } + /** Resolves the API hostname from environment variables. */ protected hostnameFromEnv(): string { const envVarValue = process.env[API_V1_HOST_ENVVAR_NAME]; if (envVarValue) { diff --git a/src/v1/http/errors.ts b/src/v1/http/errors.ts index 46ac67730..9367df6d5 100644 --- a/src/v1/http/errors.ts +++ b/src/v1/http/errors.ts @@ -1,108 +1,94 @@ import { MindeeError } from "@/errors/index.js"; import { errorHandler } from "@/errors/handler.js"; import { StringDict } from "@/parsing/stringDict.js"; -import { BaseHttpResponse } from "../../http/apiCore.js"; +import { BaseHttpResponse } from "@/http/index.js"; -export function handleError( - urlName: string, - response: BaseHttpResponse, - serverError?: string -): void { - let code; +const HTML_ERROR_PATTERNS: ReadonlyArray<[string, StringDict]> = [ + ["Maximum pdf pages", { message: "TooManyPages", details: "Maximum amount of pdf pages reached." }], + ["Max file size is", { message: "FileTooLarge", details: "Maximum file size reached." }], + ["Invalid file type", { message: "InvalidFiletype", details: "Invalid file type." }], + ["Gateway timeout", { message: "RequestTimeout", details: "Request timed out." }], + ["Bad gateway", { message: "BadRequest", details: "Bad Gateway" }], + ["Too Many Requests", { message: "TooManyRequests", details: "Too Many Requests." }], +]; + +const ERROR_CLASSES_BY_CODE = new Map(); + +/** + * Extract the status code from the given response. + * @param response Response. + * @returns Status code. + */ +function extractStatusCode(response: BaseHttpResponse): number | undefined { try { if (response.data.api_request["status_code"] === 200 && response.data?.job?.error?.code) { - code = 500; response.data.api_request.error = response.data.job.error; - } else if (response.data) { - code = response.data.api_request["status_code"]; + return 500; + } + if (response.data) { + return response.data.api_request["status_code"]; } } catch { - code = 500; + return 500; + } + return undefined; +} + +/** + * Extract the error object from an HTML error response body. + * @param reconstructed Reconstructed response body. + * @returns Error object. + */ +function errorObjFromHtml(reconstructed: string): StringDict { + for (const [needle, obj] of HTML_ERROR_PATTERNS) { + if (reconstructed.includes(needle)) { + return { ...obj }; + } } - let errorObj: StringDict; + return { message: "Unknown Server Error.", details: reconstructed }; +} + +/** + * Extract the error object from the given response. + * @param response Response. + * @returns Error object. + */ +function extractErrorObj(response: BaseHttpResponse): StringDict { try { - //Regular instances where the returned error is in JSON format. - errorObj = response.data.api_request.error; + // Regular instances where the returned error is in JSON format. + return response.data.api_request.error; } catch { - //Rare instances where errors are returned as HTML instead of JSON. + // Rare instances where errors are returned as HTML instead of JSON. if (!("reconstructedResponse" in response.data)) { response.data.reconstructedResponse = ""; } - if (response.data.reconstructedResponse.includes("Maximum pdf pages")) { - errorObj = { - message: "TooManyPages", - details: "Maximum amount of pdf pages reached.", - }; - } else if (response.data.reconstructedResponse.includes("Max file size is")) { - errorObj = { - message: "FileTooLarge", - details: "Maximum file size reached.", - }; - } else if (response.data.reconstructedResponse.includes("Invalid file type")) { - errorObj = { - message: "InvalidFiletype", - details: "Invalid file type.", - }; - } else if (response.data.reconstructedResponse.includes("Gateway timeout")) { - errorObj = { - message: "RequestTimeout", - details: "Request timed out.", - }; - } else if (response.data.reconstructedResponse.includes("Bad gateway")) { - errorObj = { - message: "BadRequest", - details: "Bad Gateway", - }; - } else if (response.data.reconstructedResponse.includes("Too Many Requests")) { - errorObj = { - message: "TooManyRequests", - details: "Too Many Requests.", - }; - } else { - errorObj = { - message: "Unknown Server Error.", - details: response.data.reconstructedResponse, - }; - } + return errorObjFromHtml(response.data.reconstructedResponse); } - if (serverError !== undefined && - (!("message" in errorObj) || - !errorObj.message || - errorObj.message.length === 0) +} + +/** + * Handle the given error. + * @param urlName URL name. + * @param response Response. + * @param serverError Server error. + */ +export function handleError( + urlName: string, + response: BaseHttpResponse, + serverError?: string +): void { + const code = extractStatusCode(response); + const errorObj = extractErrorObj(response); + + if ( + serverError !== undefined && + (!("message" in errorObj) || !errorObj.message || errorObj.message.length === 0) ) { errorObj.message = serverError; } - let errorToThrow; - switch (code) { - case 400: - errorToThrow = new MindeeHttp400Error(errorObj, urlName, code); - break; - case 401: - errorToThrow = new MindeeHttp401Error(errorObj, urlName, code); - break; - case 403: - errorToThrow = new MindeeHttp403Error(errorObj, urlName, code); - break; - case 404: - errorToThrow = new MindeeHttp404Error(errorObj, urlName, code); - break; - case 413: - errorToThrow = new MindeeHttp413Error(errorObj, urlName, code); - break; - case 429: - errorToThrow = new MindeeHttp429Error(errorObj, urlName, code); - break; - case 500: - errorToThrow = new MindeeHttp500Error(errorObj, urlName, code); - break; - case 504: - errorToThrow = new MindeeHttp504Error(errorObj, urlName, code); - break; - default: - errorToThrow = new MindeeHttpErrorV1(errorObj, urlName, code); - break; - } - errorHandler.throw(errorToThrow); + + const errorClass = (code !== undefined && ERROR_CLASSES_BY_CODE.get(code)) || MindeeHttpErrorV1; + errorHandler.throw(new errorClass(errorObj, urlName, code)); } /** @@ -207,3 +193,12 @@ export class MindeeHttp504Error extends MindeeHttpErrorV1 { this.name = "MindeeHttp504Error"; } } + +ERROR_CLASSES_BY_CODE.set(400, MindeeHttp400Error); +ERROR_CLASSES_BY_CODE.set(401, MindeeHttp401Error); +ERROR_CLASSES_BY_CODE.set(403, MindeeHttp403Error); +ERROR_CLASSES_BY_CODE.set(404, MindeeHttp404Error); +ERROR_CLASSES_BY_CODE.set(413, MindeeHttp413Error); +ERROR_CLASSES_BY_CODE.set(429, MindeeHttp429Error); +ERROR_CLASSES_BY_CODE.set(500, MindeeHttp500Error); +ERROR_CLASSES_BY_CODE.set(504, MindeeHttp504Error); diff --git a/src/v1/http/httpParams.ts b/src/v1/http/httpParams.ts index 4cbab3377..9611780e9 100644 --- a/src/v1/http/httpParams.ts +++ b/src/v1/http/httpParams.ts @@ -2,20 +2,32 @@ import { InputSource, PageOptions } from "@/input/index.js"; import { ExecutionPriority } from "@/v1/parsing/common/index.js"; interface HTTPParams { + /** Input source to submit to the API. */ inputDoc: InputSource; + /** Enables full-text OCR output when supported. */ fullText: boolean; + /** Optional local page filtering options applied before upload. */ pageOptions?: PageOptions; + /** Enables Retrieval-Augmented Generation when supported. */ rag?: boolean; } +/** Parameters sent to synchronous/asynchronous prediction endpoints. */ export interface PredictParams extends HTTPParams { + /** Enables word-level OCR details in the response. */ includeWords: boolean; + /** Enables cropper extras in the response. */ cropper: boolean; + /** Optional workflow ID used for workflow-backed inference. */ workflowId?: string; } +/** Parameters sent to workflow execution endpoints. */ export interface WorkflowParams extends HTTPParams { + /** Custom alias assigned to the submitted file. */ alias?: string; + /** Processing priority of the workflow execution. */ priority?: ExecutionPriority; + /** Whether to request a public validation URL. */ publicUrl?: string; } diff --git a/src/v1/http/responseValidation.ts b/src/v1/http/responseValidation.ts index 8aa63ca9d..2a398f671 100644 --- a/src/v1/http/responseValidation.ts +++ b/src/v1/http/responseValidation.ts @@ -1,4 +1,4 @@ -import { BaseHttpResponse } from "../../http/apiCore.js"; +import { BaseHttpResponse } from "@/http/apiCore.js"; /** * Checks if the synchronous response is valid. Returns True if the response is valid. diff --git a/src/v1/parsing/common/asyncPredictResponse.ts b/src/v1/parsing/common/asyncPredictResponse.ts index ea4778923..3eb7cc6a1 100644 --- a/src/v1/parsing/common/asyncPredictResponse.ts +++ b/src/v1/parsing/common/asyncPredictResponse.ts @@ -41,9 +41,12 @@ export class Job { } } -// Hideous thing to make sure dates sent back by the server are parsed correctly in UTC. +/** + * Parse a date string into UTC. + * @param date Date string. + */ export function datetimeWithTimezone(date: string): Date { - if (date.search(/\+[0-9]{2}:[0-9]{2}$/) === -1) { + if (date.search(/\+\d{2}:\d{2}$/) === -1) { date += "+00:00"; } return new Date(date); diff --git a/src/v1/parsing/common/execution.ts b/src/v1/parsing/common/execution.ts index 4560c122a..8c4ace8c5 100644 --- a/src/v1/parsing/common/execution.ts +++ b/src/v1/parsing/common/execution.ts @@ -3,7 +3,7 @@ import { GeneratedV1Document } from "@/v1/product/generated/generatedV1Document. import { ExecutionFile } from "./executionFile.js"; import { StringDict } from "@/parsing/stringDict.js"; import { ExecutionPriority } from "./executionPriority.js"; -import { parseDate } from "../../../parsing/dateParser.js"; +import { parseDate } from "@/parsing/dateParser.js"; /** * Representation of an execution for a workflow. diff --git a/src/v1/parsing/common/executionPriority.ts b/src/v1/parsing/common/executionPriority.ts index 24d8ffc6b..708b481c0 100644 --- a/src/v1/parsing/common/executionPriority.ts +++ b/src/v1/parsing/common/executionPriority.ts @@ -1,5 +1,9 @@ +/** Execution priority levels available for workflow requests. */ export enum ExecutionPriority { + /** Low-priority background processing. */ low = "low", + /** Standard processing priority. */ medium = "medium", + /** High-priority processing. */ high = "high" } diff --git a/src/v1/parsing/common/extras/cropperExtra.ts b/src/v1/parsing/common/extras/cropperExtra.ts index 3731f1175..afeec24eb 100644 --- a/src/v1/parsing/common/extras/cropperExtra.ts +++ b/src/v1/parsing/common/extras/cropperExtra.ts @@ -1,9 +1,12 @@ import { PositionField } from "@/v1/parsing/standard/position.js"; import { StringDict } from "@/parsing/stringDict.js"; +// eslint-disable-next-line no-restricted-imports import { cleanOutString } from "../summaryHelper.js"; import { ExtraField } from "./extras.js"; +/** Cropper extra payload returned by compatible APIs. */ export class CropperExtra extends ExtraField { + /** Cropped regions detected on the page. */ cropping: PositionField[] = []; constructor(rawPrediction: StringDict, pageId?: number) { super(); diff --git a/src/v1/parsing/common/extras/extras.ts b/src/v1/parsing/common/extras/extras.ts index 5e936979c..c290bbe94 100644 --- a/src/v1/parsing/common/extras/extras.ts +++ b/src/v1/parsing/common/extras/extras.ts @@ -1,3 +1,4 @@ +/** Base class for optional extras attached to predictions. */ export abstract class ExtraField { } @@ -5,8 +6,10 @@ interface ExtraDict { [key: string]: T | (() => string); } +/** Dictionary-like container for extra fields. */ export class Extras implements ExtraDict { + /** Dynamic map of extra values keyed by extra name. */ [key: string]: ExtraT | (() => string); constructor(fields: Record) { diff --git a/src/v1/parsing/common/extras/fullTextOcrExtra.ts b/src/v1/parsing/common/extras/fullTextOcrExtra.ts index eaea594ab..a4ebca17d 100644 --- a/src/v1/parsing/common/extras/fullTextOcrExtra.ts +++ b/src/v1/parsing/common/extras/fullTextOcrExtra.ts @@ -1,8 +1,11 @@ import { StringDict } from "@/parsing/stringDict.js"; import { ExtraField } from "./extras.js"; +/** Full-text OCR extra payload returned by compatible APIs. */ export class FullTextOcrExtra extends ExtraField { + /** Concatenated OCR content. */ content?: string; + /** OCR language hints associated with the content. */ languages?: string; constructor(rawPrediction: StringDict) { diff --git a/src/v1/parsing/common/extras/ragExtra.ts b/src/v1/parsing/common/extras/ragExtra.ts index 563a1ff4b..22db08971 100644 --- a/src/v1/parsing/common/extras/ragExtra.ts +++ b/src/v1/parsing/common/extras/ragExtra.ts @@ -1,6 +1,7 @@ import { ExtraField } from "./extras.js"; import { StringDict } from "@/parsing/stringDict.js"; +/** RAG extra payload returned by compatible APIs. */ export class RAGExtra extends ExtraField { /** * ID reference of the document matched by the Retrieval-Augmented Generation. diff --git a/src/v1/parsing/common/feedback/feedbackResponse.ts b/src/v1/parsing/common/feedback/feedbackResponse.ts index 070114e3c..f93b95117 100644 --- a/src/v1/parsing/common/feedback/feedbackResponse.ts +++ b/src/v1/parsing/common/feedback/feedbackResponse.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line no-restricted-imports import { ApiResponse } from "../apiResponse.js"; import { StringDict } from "@/parsing/stringDict.js"; diff --git a/src/v1/parsing/common/index.ts b/src/v1/parsing/common/index.ts index 0b6084dcd..4dcedb4c0 100644 --- a/src/v1/parsing/common/index.ts +++ b/src/v1/parsing/common/index.ts @@ -5,14 +5,14 @@ export { ExecutionPriority } from "./executionPriority.js"; export { Inference } from "./inference.js"; export { FeedbackResponse } from "./feedback/feedbackResponse.js"; export { OrientationField } from "./orientation.js"; -export type { StringDict } from "../../../parsing/stringDict.js"; +export type { StringDict } from "@/parsing/stringDict.js"; export { AsyncPredictResponse } from "./asyncPredictResponse.js"; export { PredictResponse } from "./predictResponse.js"; export { Prediction } from "./prediction.js"; export { Page } from "./page.js"; export { - cleanOutString, lineSeparator, floatToString, cleanSpecialChars + cleanOutString, lineSeparator, floatToString, cleanSpecialChars, cleanAndTruncate } from "./summaryHelper.js"; export * as extras from "./extras/index.js"; -export { parseDate } from "../../../parsing/dateParser.js"; +export { parseDate } from "@/parsing/dateParser.js"; export { WorkflowResponse } from "./workflowResponse.js"; diff --git a/src/v1/parsing/common/inference.ts b/src/v1/parsing/common/inference.ts index be25f5144..d61019d77 100644 --- a/src/v1/parsing/common/inference.ts +++ b/src/v1/parsing/common/inference.ts @@ -8,6 +8,7 @@ import { import { MindeeConfigurationError } from "@/errors/index.js"; /** + * Generic base class for parsed v1 inference payloads. * * @typeParam DocT an extension of a `Prediction`. Is generic by default to * allow for easier optional `PageT` generic typing. @@ -64,6 +65,7 @@ export abstract class Inference< /** * Default string representation. */ + /** Returns a human-readable representation of the inference payload. */ toString() { let pages = ""; diff --git a/src/v1/parsing/common/predictResponse.ts b/src/v1/parsing/common/predictResponse.ts index b8ad6a41b..a801bbdbb 100644 --- a/src/v1/parsing/common/predictResponse.ts +++ b/src/v1/parsing/common/predictResponse.ts @@ -1,7 +1,7 @@ import { ApiResponse } from "./apiResponse.js"; import { Document } from "./document.js"; import { Inference } from "./inference.js"; -import { StringDict } from "../../../parsing/stringDict.js"; +import { StringDict } from "@/parsing/stringDict.js"; /** Wrapper for synchronous prediction response. * diff --git a/src/v1/parsing/common/prediction.ts b/src/v1/parsing/common/prediction.ts index 2ffe8f1fc..b8da52b14 100644 --- a/src/v1/parsing/common/prediction.ts +++ b/src/v1/parsing/common/prediction.ts @@ -1 +1,2 @@ +/** Marker base type for v1 prediction payload models. */ export class Prediction {} diff --git a/src/v1/parsing/common/summaryHelper.ts b/src/v1/parsing/common/summaryHelper.ts index 78b99a462..36a16a3f0 100644 --- a/src/v1/parsing/common/summaryHelper.ts +++ b/src/v1/parsing/common/summaryHelper.ts @@ -1,3 +1,7 @@ +/** + * Cleans a string by removing all new line characters. + * @param outStr Output string. + */ export function cleanOutString(outStr: string): string { const lines = / \n/gm; return outStr.replace(lines, "\n"); @@ -20,7 +24,7 @@ export function lineSeparator(columnSizes: number[], separator: string) { /** * Replaces all special characters like \n, \r, \t, with an equivalent that can be displayed on a single line. * Also trims line breaks at the end of the string. - * @param outStr + * @param outStr Output string. */ export function cleanSpecialChars(outStr: string) { return outStr @@ -29,6 +33,20 @@ export function cleanSpecialChars(outStr: string) { .replace(/\t/g, "\\t"); } +/** + * Cleans a string with `cleanSpecialChars` and truncates it to `maxLength` characters, + * appending "..." when truncation occurs. Returns an empty string for null/undefined/empty input. + * @param value String to clean and truncate. + * @param maxLength Maximum length of the returned string (including the "..." suffix when truncated). + */ +export function cleanAndTruncate(value: string | null | undefined, maxLength: number): string { + if (!value) { + return ""; + } + const cleaned = cleanSpecialChars(value); + return cleaned.length <= maxLength ? cleaned : cleaned.slice(0, maxLength - 3) + "..."; +} + /** * Return a float as a string with at least 2 levels of precision. */ diff --git a/src/v1/parsing/generated/generatedObject.ts b/src/v1/parsing/generated/generatedObject.ts index 6fb592eb2..f1b4035bd 100644 --- a/src/v1/parsing/generated/generatedObject.ts +++ b/src/v1/parsing/generated/generatedObject.ts @@ -26,47 +26,55 @@ export class GeneratedObjectField { for (const [fieldName, fieldValue] of Object.entries(prediction)) { if (fieldName === "page_id") { itemPageId = fieldValue; - } else if (["polygon", "rectangle", "quadrangle", "bounding_box"].includes(fieldName)) { - Object.assign( - this, - { - [fieldName]: new PositionField({ - prediction: { [fieldName]: fieldValue }, - valueKey: fieldName, - pageId: pageId, - }), - }); - this.printableValues.push(fieldName); } else if (fieldName === "confidence") { this.confidence = fieldValue; } else if (fieldName === "raw_value") { this.rawValue = fieldValue; + } else if (GeneratedObjectField.isPositionField(fieldName)) { + this.assignPositionField(fieldName, fieldValue, pageId); } else { - if ( - fieldValue !== null && - fieldValue !== undefined && - typeof fieldValue === "number" && - !isNaN(fieldValue) && - fieldName !== "degrees" - ) { - Object.assign(this, { [fieldName]: this.toNumberString(fieldValue) }); - } else if (typeof fieldValue === "boolean") { - Object.assign(this, { [fieldName]: fieldValue }); - } else { - Object.assign( - this, - { - [fieldName]: - (fieldValue !== undefined && fieldValue !== null) ? - String(fieldValue) : null, - }); - } - this.printableValues.push(fieldName); + this.assignScalarField(fieldName, fieldValue); } this.pageId = pageId ?? itemPageId; } } + private static readonly positionFieldNames: ReadonlySet = new Set([ + "polygon", "rectangle", "quadrangle", "bounding_box", + ]); + + private static isPositionField(fieldName: string): boolean { + return GeneratedObjectField.positionFieldNames.has(fieldName); + } + + private assignPositionField(fieldName: string, fieldValue: unknown, pageId?: number): void { + Object.assign(this, { + [fieldName]: new PositionField({ + prediction: { [fieldName]: fieldValue }, + valueKey: fieldName, + pageId: pageId, + }), + }); + this.printableValues.push(fieldName); + } + + private assignScalarField(fieldName: string, fieldValue: unknown): void { + if ( + typeof fieldValue === "number" && + !isNaN(fieldValue) && + fieldName !== "degrees" + ) { + Object.assign(this, { [fieldName]: this.toNumberString(fieldValue) }); + } else if (typeof fieldValue === "boolean") { + Object.assign(this, { [fieldName]: fieldValue }); + } else { + Object.assign(this, { + [fieldName]: (fieldValue !== undefined && fieldValue !== null) ? String(fieldValue) : null, + }); + } + this.printableValues.push(fieldName); + } + /** * ReSTructured-compliant string representation. Takes into account level of indentation & displays elements as list elements. @@ -94,10 +102,12 @@ export class GeneratedObjectField { return n.toString(); } + /** Returns the default string representation. */ toString() { return this.toStringLevel(); } + /** Returns a field value by key from this generated object. */ get(fieldName: string): string | number | boolean | object | undefined { return this[fieldName]; } diff --git a/src/v1/parsing/localResponse.ts b/src/v1/parsing/localResponse.ts index 9ba58346d..5fdfb875f 100644 --- a/src/v1/parsing/localResponse.ts +++ b/src/v1/parsing/localResponse.ts @@ -8,6 +8,7 @@ import { MindeeError } from "@/errors/index.js"; * Note: Has to be initialized through init() before use. */ export class LocalResponse extends LocalResponseBase { + /** Loads a local JSON payload into a typed prediction response wrapper. */ async loadPrediction( productClass: new (httpResponse: StringDict) => T ) { diff --git a/src/v1/parsing/standard/base.ts b/src/v1/parsing/standard/base.ts index f0d6a73fd..afc766212 100644 --- a/src/v1/parsing/standard/base.ts +++ b/src/v1/parsing/standard/base.ts @@ -6,10 +6,15 @@ import { StringDict } from "@/parsing/stringDict.js"; * @property {boolean} reconstructed - Is the object reconstructed (not extracted by the API). * @property {number} pageId - Page ID for multi-page document. */ +/** Constructor payload shared by standard v1 fields. */ export interface BaseFieldConstructor { + /** Raw field payload from API response. */ prediction: StringDict; + /** Key to read from `prediction` as field value. */ valueKey?: string; + /** Whether the field was reconstructed locally. */ reconstructed?: boolean; + /** Optional page identifier for multi-page documents. */ pageId?: number; } @@ -45,6 +50,7 @@ export class BaseField { } } + /** Compares two field values for semantic equality. */ compare(other: BaseField) { if (this.value === null && other.value === null) return true; if (this.value === null || other.value === null) return false; diff --git a/src/v1/parsing/standard/boolean.ts b/src/v1/parsing/standard/boolean.ts index f96e395bb..866383504 100644 --- a/src/v1/parsing/standard/boolean.ts +++ b/src/v1/parsing/standard/boolean.ts @@ -5,7 +5,7 @@ export interface FieldConstructor { prediction: StringDict; valueKey?: string; reconstructed?: boolean; - pageId?: number | undefined; + pageId?: number; } /** diff --git a/src/v1/parsing/standard/companyRegistration.ts b/src/v1/parsing/standard/companyRegistration.ts index d11fae3d8..2d3ea50da 100644 --- a/src/v1/parsing/standard/companyRegistration.ts +++ b/src/v1/parsing/standard/companyRegistration.ts @@ -20,6 +20,7 @@ export class CompanyRegistrationField extends Field { super({ prediction, valueKey, reconstructed, pageId }); this.type = prediction["type"]; } + /** Returns a row-formatted representation for table output. */ toTableLine(): string { const printable = this.printableValues(); return `| ${printable["type"].padEnd(15)} | ${printable["value"].padEnd(20)} `; diff --git a/src/v1/parsing/standard/date.ts b/src/v1/parsing/standard/date.ts index f7b18e317..a67e65e4a 100644 --- a/src/v1/parsing/standard/date.ts +++ b/src/v1/parsing/standard/date.ts @@ -38,6 +38,7 @@ export class DateField extends Field { } } + /** Compares two dates by calendar day (year/month/day). */ static compareDates(date1: Date, date2: Date): boolean { return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && diff --git a/src/v1/parsing/standard/paymentDetails.ts b/src/v1/parsing/standard/paymentDetails.ts index b56c6fda4..e38fd94a2 100644 --- a/src/v1/parsing/standard/paymentDetails.ts +++ b/src/v1/parsing/standard/paymentDetails.ts @@ -27,15 +27,15 @@ interface PaymentDetailsConstructor { */ export class PaymentDetailsField extends Field { /** Synonym for the `iban` property */ - value?: string | undefined; + value?: string; /** The account number. */ - accountNumber: string | undefined; + accountNumber?: string; /** The International Bank Account Number (IBAN). */ - iban: string | undefined; + iban?: string; /** The routing number. */ - routingNumber: string | undefined; + routingNumber?: string; /** The bank's SWIFT Business Identifier Code (BIC). */ - swift: string | undefined; + swift?: string; /** * @param {PaymentDetailsConstructor} constructor Constructor parameters. diff --git a/src/v1/parsing/standard/position.ts b/src/v1/parsing/standard/position.ts index 428c6e712..519b1c2f1 100644 --- a/src/v1/parsing/standard/position.ts +++ b/src/v1/parsing/standard/position.ts @@ -4,7 +4,7 @@ import { Polygon } from "@/geometry/index.js"; export interface PositionFieldConstructor { prediction: StringDict; valueKey?: string; - pageId?: number | undefined; + pageId?: number; } /** @@ -20,7 +20,7 @@ export class PositionField { /** Rectangle that may be oriented (can go beyond the canvas). */ rectangle: Polygon; /** The document page on which the information was found. */ - pageId: number | undefined; + pageId?: number; constructor({ prediction = {}, pageId }: PositionFieldConstructor) { this.pageId = pageId; diff --git a/src/v1/parsing/standard/tax.ts b/src/v1/parsing/standard/tax.ts index 4e63d3cae..858916f5c 100644 --- a/src/v1/parsing/standard/tax.ts +++ b/src/v1/parsing/standard/tax.ts @@ -109,6 +109,7 @@ export class TaxField extends Field { * Represent all items. */ export class Taxes extends Array { + /** Populates the taxes collection from raw prediction entries. */ init(prediction: StringDict[] = [], pageId: number | undefined) { for (const entry of prediction) { this.push( diff --git a/src/v1/parsing/standard/text.ts b/src/v1/parsing/standard/text.ts index 908815113..17ee6053f 100644 --- a/src/v1/parsing/standard/text.ts +++ b/src/v1/parsing/standard/text.ts @@ -5,7 +5,7 @@ export interface FieldConstructor { prediction: StringDict; valueKey?: string; reconstructed?: boolean; - pageId?: number | undefined; + pageId?: number; } /** diff --git a/src/v1/parsing/standard/word.ts b/src/v1/parsing/standard/word.ts index 6babe8b92..7c732442e 100644 --- a/src/v1/parsing/standard/word.ts +++ b/src/v1/parsing/standard/word.ts @@ -1,13 +1,16 @@ import { Polygon } from "@/geometry/index.js"; import { StringDict } from "@/parsing/index.js"; +/** OCR word item with text, confidence, and location polygon. */ export class Word { /** * Contains the relative vertices coordinates (points) of a polygon containing * the field in the document. */ polygon: Polygon; + /** OCR text content for the word. */ text: string; + /** Confidence score for the OCR word. */ confidence: number; constructor(rawPrediction: StringDict) { diff --git a/src/v1/product/financialDocument/financialDocumentV1LineItem.ts b/src/v1/product/financialDocument/financialDocumentV1LineItem.ts index 1ec5a2fc1..9daec38d4 100644 --- a/src/v1/product/financialDocument/financialDocumentV1LineItem.ts +++ b/src/v1/product/financialDocument/financialDocumentV1LineItem.ts @@ -1,4 +1,4 @@ -import { cleanSpecialChars, floatToString } from "@/v1/parsing/common/index.js"; +import { cleanAndTruncate, floatToString } from "@/v1/parsing/common/index.js"; import { StringDict } from "@/parsing/stringDict.js"; import { Polygon } from "@/geometry/index.js"; @@ -35,52 +35,12 @@ export class FinancialDocumentV1LineItem { constructor({ prediction = {} }: StringDict) { this.description = prediction["description"]; this.productCode = prediction["product_code"]; - if ( - prediction["quantity"] !== undefined && - prediction["quantity"] !== null && - !isNaN(prediction["quantity"]) - ) { - this.quantity = +parseFloat(prediction["quantity"]); - } else { - this.quantity = null; - } - if ( - prediction["tax_amount"] !== undefined && - prediction["tax_amount"] !== null && - !isNaN(prediction["tax_amount"]) - ) { - this.taxAmount = +parseFloat(prediction["tax_amount"]); - } else { - this.taxAmount = null; - } - if ( - prediction["tax_rate"] !== undefined && - prediction["tax_rate"] !== null && - !isNaN(prediction["tax_rate"]) - ) { - this.taxRate = +parseFloat(prediction["tax_rate"]); - } else { - this.taxRate = null; - } - if ( - prediction["total_amount"] !== undefined && - prediction["total_amount"] !== null && - !isNaN(prediction["total_amount"]) - ) { - this.totalAmount = +parseFloat(prediction["total_amount"]); - } else { - this.totalAmount = null; - } + this.quantity = FinancialDocumentV1LineItem.#parseNumber(prediction["quantity"]); + this.taxAmount = FinancialDocumentV1LineItem.#parseNumber(prediction["tax_amount"]); + this.taxRate = FinancialDocumentV1LineItem.#parseNumber(prediction["tax_rate"]); + this.totalAmount = FinancialDocumentV1LineItem.#parseNumber(prediction["total_amount"]); this.unitMeasure = prediction["unit_measure"]; - if ( - prediction["unit_price"] !== undefined && - prediction["unit_price"] !== null && - !isNaN(prediction["unit_price"]) - ) { - this.unitPrice = +parseFloat(prediction["unit_price"]); - } else { - this.unitPrice = null; - } + this.unitPrice = FinancialDocumentV1LineItem.#parseNumber(prediction["unit_price"]); this.pageId = prediction["page_id"]; this.confidence = prediction["confidence"] ? prediction.confidence : 0.0; if (prediction["polygon"]) { @@ -88,31 +48,26 @@ export class FinancialDocumentV1LineItem { } } + static #parseNumber(value: unknown): number | null { + if (value === undefined || value === null || isNaN(value as number)) { + return null; + } + return +parseFloat(value as string); + } + /** * Collection of fields as representable strings. */ #printableValues() { return { - description: this.description ? - this.description.length <= 36 ? - cleanSpecialChars(this.description) : - cleanSpecialChars(this.description).slice(0, 33) + "..." : - "", - productCode: this.productCode ? - this.productCode.length <= 12 ? - cleanSpecialChars(this.productCode) : - cleanSpecialChars(this.productCode).slice(0, 9) + "..." : - "", + description: cleanAndTruncate(this.description, 36), + productCode: cleanAndTruncate(this.productCode, 12), quantity: this.quantity !== undefined ? floatToString(this.quantity) : "", taxAmount: this.taxAmount !== undefined ? floatToString(this.taxAmount) : "", taxRate: this.taxRate !== undefined ? floatToString(this.taxRate) : "", totalAmount: this.totalAmount !== undefined ? floatToString(this.totalAmount) : "", - unitMeasure: this.unitMeasure ? - this.unitMeasure.length <= 15 ? - cleanSpecialChars(this.unitMeasure) : - cleanSpecialChars(this.unitMeasure).slice(0, 12) + "..." : - "", + unitMeasure: cleanAndTruncate(this.unitMeasure, 15), unitPrice: this.unitPrice !== undefined ? floatToString(this.unitPrice) : "", }; } diff --git a/src/v1/product/generated/generatedV1Prediction.ts b/src/v1/product/generated/generatedV1Prediction.ts index 925bd503a..e1dc9228d 100644 --- a/src/v1/product/generated/generatedV1Prediction.ts +++ b/src/v1/product/generated/generatedV1Prediction.ts @@ -3,6 +3,7 @@ import { GeneratedListField, GeneratedObjectField } from "@/v1/parsing/generated import { StringField } from "@/v1/parsing/standard/index.js"; +/** Dynamic prediction container for generated v1 products. */ export class GeneratedV1Prediction implements Prediction { /** Map of all fields in the document. */ fields: Map; @@ -13,7 +14,7 @@ export class GeneratedV1Prediction implements Prediction { toString(): string { let outStr = ""; - const pattern = /^(\n*[ ]*)( {2}):/; + const pattern = /^(\n* *)( {2}):/; this.fields.forEach((fieldValue, fieldName) => { let strValue = ""; diff --git a/src/v1/product/invoice/invoiceV4LineItem.ts b/src/v1/product/invoice/invoiceV4LineItem.ts index 0dd06f331..b9b95fea2 100644 --- a/src/v1/product/invoice/invoiceV4LineItem.ts +++ b/src/v1/product/invoice/invoiceV4LineItem.ts @@ -1,4 +1,4 @@ -import { cleanSpecialChars, floatToString } from "@/v1/parsing/common/index.js"; +import { cleanAndTruncate, floatToString } from "@/v1/parsing/common/index.js"; import { StringDict } from "@/parsing/stringDict.js"; import { Polygon } from "@/geometry/index.js"; @@ -32,6 +32,7 @@ export class InvoiceV4LineItem { */ polygon: Polygon = new Polygon(); + // eslint-disable-next-line sonarjs/cognitive-complexity constructor({ prediction = {} }: StringDict) { this.description = prediction["description"]; this.productCode = prediction["product_code"]; @@ -93,26 +94,14 @@ export class InvoiceV4LineItem { */ #printableValues() { return { - description: this.description ? - this.description.length <= 36 ? - cleanSpecialChars(this.description) : - cleanSpecialChars(this.description).slice(0, 33) + "..." : - "", - productCode: this.productCode ? - this.productCode.length <= 12 ? - cleanSpecialChars(this.productCode) : - cleanSpecialChars(this.productCode).slice(0, 9) + "..." : - "", + description: cleanAndTruncate(this.description, 36), + productCode: cleanAndTruncate(this.productCode, 12), quantity: this.quantity !== undefined ? floatToString(this.quantity) : "", taxAmount: this.taxAmount !== undefined ? floatToString(this.taxAmount) : "", taxRate: this.taxRate !== undefined ? floatToString(this.taxRate) : "", totalAmount: this.totalAmount !== undefined ? floatToString(this.totalAmount) : "", - unitMeasure: this.unitMeasure ? - this.unitMeasure.length <= 15 ? - cleanSpecialChars(this.unitMeasure) : - cleanSpecialChars(this.unitMeasure).slice(0, 12) + "..." : - "", + unitMeasure: cleanAndTruncate(this.unitMeasure, 15), unitPrice: this.unitPrice !== undefined ? floatToString(this.unitPrice) : "", }; } diff --git a/src/v1/product/receipt/receiptV5LineItem.ts b/src/v1/product/receipt/receiptV5LineItem.ts index 6e1ebffbc..7cf094d57 100644 --- a/src/v1/product/receipt/receiptV5LineItem.ts +++ b/src/v1/product/receipt/receiptV5LineItem.ts @@ -1,4 +1,4 @@ -import { cleanSpecialChars, floatToString } from "@/v1/parsing/common/index.js"; +import { cleanAndTruncate, floatToString } from "@/v1/parsing/common/index.js"; import { StringDict } from "@/parsing/stringDict.js"; import { Polygon } from "@/geometry/index.js"; @@ -65,11 +65,7 @@ export class ReceiptV5LineItem { */ #printableValues() { return { - description: this.description ? - this.description.length <= 36 ? - cleanSpecialChars(this.description) : - cleanSpecialChars(this.description).slice(0, 33) + "..." : - "", + description: cleanAndTruncate(this.description, 36), quantity: this.quantity !== undefined ? floatToString(this.quantity) : "", totalAmount: this.totalAmount !== undefined ? floatToString(this.totalAmount) : "", diff --git a/src/v1/product/resume/resumeV1Certificate.ts b/src/v1/product/resume/resumeV1Certificate.ts index 3956fc174..52ff5cb98 100644 --- a/src/v1/product/resume/resumeV1Certificate.ts +++ b/src/v1/product/resume/resumeV1Certificate.ts @@ -1,4 +1,4 @@ -import { cleanSpecialChars } from "@/v1/parsing/common/index.js"; +import { cleanAndTruncate } from "@/v1/parsing/common/index.js"; import { StringDict } from "@/parsing/stringDict.js"; import { Polygon } from "@/geometry/index.js"; @@ -41,26 +41,10 @@ export class ResumeV1Certificate { */ #printableValues() { return { - grade: this.grade ? - this.grade.length <= 10 ? - cleanSpecialChars(this.grade) : - cleanSpecialChars(this.grade).slice(0, 7) + "..." : - "", - name: this.name ? - this.name.length <= 30 ? - cleanSpecialChars(this.name) : - cleanSpecialChars(this.name).slice(0, 27) + "..." : - "", - provider: this.provider ? - this.provider.length <= 25 ? - cleanSpecialChars(this.provider) : - cleanSpecialChars(this.provider).slice(0, 22) + "..." : - "", - year: this.year ? - this.year.length <= 4 ? - cleanSpecialChars(this.year) : - cleanSpecialChars(this.year).slice(0, 1) + "..." : - "", + grade: cleanAndTruncate(this.grade, 10), + name: cleanAndTruncate(this.name, 30), + provider: cleanAndTruncate(this.provider, 25), + year: cleanAndTruncate(this.year, 4), }; } diff --git a/src/v1/product/resume/resumeV1Education.ts b/src/v1/product/resume/resumeV1Education.ts index 7b1de459b..4c3b9aed6 100644 --- a/src/v1/product/resume/resumeV1Education.ts +++ b/src/v1/product/resume/resumeV1Education.ts @@ -1,5 +1,5 @@ -import { cleanSpecialChars } from "@/v1/parsing/common/index.js"; +import { cleanAndTruncate } from "@/v1/parsing/common/index.js"; import { StringDict } from "@/parsing/stringDict.js"; import { Polygon } from "@/geometry/index.js"; @@ -51,41 +51,13 @@ export class ResumeV1Education { */ #printableValues() { return { - degreeDomain: this.degreeDomain ? - this.degreeDomain.length <= 15 ? - cleanSpecialChars(this.degreeDomain) : - cleanSpecialChars(this.degreeDomain).slice(0, 12) + "..." : - "", - degreeType: this.degreeType ? - this.degreeType.length <= 25 ? - cleanSpecialChars(this.degreeType) : - cleanSpecialChars(this.degreeType).slice(0, 22) + "..." : - "", - endMonth: this.endMonth ? - this.endMonth.length <= 9 ? - cleanSpecialChars(this.endMonth) : - cleanSpecialChars(this.endMonth).slice(0, 6) + "..." : - "", - endYear: this.endYear ? - this.endYear.length <= 8 ? - cleanSpecialChars(this.endYear) : - cleanSpecialChars(this.endYear).slice(0, 5) + "..." : - "", - school: this.school ? - this.school.length <= 25 ? - cleanSpecialChars(this.school) : - cleanSpecialChars(this.school).slice(0, 22) + "..." : - "", - startMonth: this.startMonth ? - this.startMonth.length <= 11 ? - cleanSpecialChars(this.startMonth) : - cleanSpecialChars(this.startMonth).slice(0, 8) + "..." : - "", - startYear: this.startYear ? - this.startYear.length <= 10 ? - cleanSpecialChars(this.startYear) : - cleanSpecialChars(this.startYear).slice(0, 7) + "..." : - "", + degreeDomain: cleanAndTruncate(this.degreeDomain, 15), + degreeType: cleanAndTruncate(this.degreeType, 25), + endMonth: cleanAndTruncate(this.endMonth, 9), + endYear: cleanAndTruncate(this.endYear, 8), + school: cleanAndTruncate(this.school, 25), + startMonth: cleanAndTruncate(this.startMonth, 11), + startYear: cleanAndTruncate(this.startYear, 10), }; } diff --git a/src/v1/product/resume/resumeV1Language.ts b/src/v1/product/resume/resumeV1Language.ts index b9eb36529..70389077b 100644 --- a/src/v1/product/resume/resumeV1Language.ts +++ b/src/v1/product/resume/resumeV1Language.ts @@ -1,5 +1,5 @@ -import { cleanSpecialChars } from "@/v1/parsing/common/index.js"; +import { cleanAndTruncate } from "@/v1/parsing/common/index.js"; import { StringDict } from "@/parsing/stringDict.js"; import { Polygon } from "@/geometry/index.js"; @@ -36,16 +36,8 @@ export class ResumeV1Language { */ #printableValues() { return { - language: this.language ? - this.language.length <= 8 ? - cleanSpecialChars(this.language) : - cleanSpecialChars(this.language).slice(0, 5) + "..." : - "", - level: this.level ? - this.level.length <= 20 ? - cleanSpecialChars(this.level) : - cleanSpecialChars(this.level).slice(0, 17) + "..." : - "", + language: cleanAndTruncate(this.language, 8), + level: cleanAndTruncate(this.level, 20), }; } diff --git a/src/v1/product/resume/resumeV1ProfessionalExperience.ts b/src/v1/product/resume/resumeV1ProfessionalExperience.ts index f1474e37e..847f99d84 100644 --- a/src/v1/product/resume/resumeV1ProfessionalExperience.ts +++ b/src/v1/product/resume/resumeV1ProfessionalExperience.ts @@ -1,5 +1,5 @@ -import { cleanSpecialChars } from "@/v1/parsing/common/index.js"; +import { cleanAndTruncate } from "@/v1/parsing/common/index.js"; import { StringDict } from "@/parsing/stringDict.js"; import { Polygon } from "@/geometry/index.js"; @@ -57,51 +57,15 @@ export class ResumeV1ProfessionalExperience { */ #printableValues() { return { - contractType: this.contractType ? - this.contractType.length <= 15 ? - cleanSpecialChars(this.contractType) : - cleanSpecialChars(this.contractType).slice(0, 12) + "..." : - "", - department: this.department ? - this.department.length <= 10 ? - cleanSpecialChars(this.department) : - cleanSpecialChars(this.department).slice(0, 7) + "..." : - "", - description: this.description ? - this.description.length <= 36 ? - cleanSpecialChars(this.description) : - cleanSpecialChars(this.description).slice(0, 33) + "..." : - "", - employer: this.employer ? - this.employer.length <= 25 ? - cleanSpecialChars(this.employer) : - cleanSpecialChars(this.employer).slice(0, 22) + "..." : - "", - endMonth: this.endMonth ? - this.endMonth.length <= 9 ? - cleanSpecialChars(this.endMonth) : - cleanSpecialChars(this.endMonth).slice(0, 6) + "..." : - "", - endYear: this.endYear ? - this.endYear.length <= 8 ? - cleanSpecialChars(this.endYear) : - cleanSpecialChars(this.endYear).slice(0, 5) + "..." : - "", - role: this.role ? - this.role.length <= 20 ? - cleanSpecialChars(this.role) : - cleanSpecialChars(this.role).slice(0, 17) + "..." : - "", - startMonth: this.startMonth ? - this.startMonth.length <= 11 ? - cleanSpecialChars(this.startMonth) : - cleanSpecialChars(this.startMonth).slice(0, 8) + "..." : - "", - startYear: this.startYear ? - this.startYear.length <= 10 ? - cleanSpecialChars(this.startYear) : - cleanSpecialChars(this.startYear).slice(0, 7) + "..." : - "", + contractType: cleanAndTruncate(this.contractType, 15), + department: cleanAndTruncate(this.department, 10), + description: cleanAndTruncate(this.description, 36), + employer: cleanAndTruncate(this.employer, 25), + endMonth: cleanAndTruncate(this.endMonth, 9), + endYear: cleanAndTruncate(this.endYear, 8), + role: cleanAndTruncate(this.role, 20), + startMonth: cleanAndTruncate(this.startMonth, 11), + startYear: cleanAndTruncate(this.startYear, 10), }; } diff --git a/src/v1/product/resume/resumeV1SocialNetworksUrl.ts b/src/v1/product/resume/resumeV1SocialNetworksUrl.ts index 85b73608c..9887634fa 100644 --- a/src/v1/product/resume/resumeV1SocialNetworksUrl.ts +++ b/src/v1/product/resume/resumeV1SocialNetworksUrl.ts @@ -1,5 +1,5 @@ -import { cleanSpecialChars } from "@/v1/parsing/common/index.js"; +import { cleanAndTruncate } from "@/v1/parsing/common/index.js"; import { StringDict } from "@/parsing/stringDict.js"; import { Polygon } from "@/geometry/index.js"; @@ -36,16 +36,8 @@ export class ResumeV1SocialNetworksUrl { */ #printableValues() { return { - name: this.name ? - this.name.length <= 20 ? - cleanSpecialChars(this.name) : - cleanSpecialChars(this.name).slice(0, 17) + "..." : - "", - url: this.url ? - this.url.length <= 50 ? - cleanSpecialChars(this.url) : - cleanSpecialChars(this.url).slice(0, 47) + "..." : - "", + name: cleanAndTruncate(this.name, 20), + url: cleanAndTruncate(this.url, 50), }; } diff --git a/src/v2/cli.ts b/src/v2/cli.ts index 8135f2d7b..efc2d1b04 100644 --- a/src/v2/cli.ts +++ b/src/v2/cli.ts @@ -23,6 +23,10 @@ const program = new Command(); // EXECUTE THE COMMANDS // +/** + * Initialize the client. + * @param options Options. + */ function initClient(options: OptionValues): Client { return new Client({ apiKey: options.apiKey, @@ -30,6 +34,12 @@ function initClient(options: OptionValues): Client { }); } +/** + * Enqueue and get inference. + * @param product Product. + * @param inputPath Input path. + * @param options Options. + */ async function enqueueAndGetInference( product: typeof BaseProduct, inputPath: string, @@ -58,6 +68,10 @@ async function enqueueAndGetInference( printResponse(response.inference); } +/** + * Print response. + * @param document Document. + */ function printResponse( document: BaseInference, ): void { @@ -70,6 +84,10 @@ function printResponse( // BUILD THE COMMANDS // +/** + * Add main options. + * @param prog Program. + */ function addMainOptions(prog: Command) { prog.requiredOption( "-m, --model ", @@ -78,6 +96,9 @@ function addMainOptions(prog: Command) { prog.argument("", "full path or URL to the file"); } +/** + * Build the CLI. + */ export function cli() { program.name("mindee") .description("Command line interface for Mindee V2 products.") diff --git a/src/v2/client.ts b/src/v2/client.ts index e066f093f..b1547342c 100644 --- a/src/v2/client.ts +++ b/src/v2/client.ts @@ -65,6 +65,7 @@ export class Client { return await this.mindeeApi.reqGetSearchModel(name, modelType); } + /** Enqueues a product inference job without waiting for completion. */ async enqueue

( product: P, inputSource: InputSource, diff --git a/src/v2/clientOptions/pollingOptions.ts b/src/v2/clientOptions/pollingOptions.ts index dca53eec6..c374dcafe 100644 --- a/src/v2/clientOptions/pollingOptions.ts +++ b/src/v2/clientOptions/pollingOptions.ts @@ -1,8 +1,11 @@ import { MindeeConfigurationError } from "@/errors/index.js"; import { logger } from "@/logger.js"; +/** Optional timer settings used by polling delays. */ export interface TimerOptions { + /** Whether the timer should keep the event loop active. */ ref?: boolean, + /** Optional signal used to abort timer waits. */ signal?: AbortSignal } @@ -89,6 +92,7 @@ export class PollingOptions { logger.debug(`Polling options initialized: ${this.toString()}`); } + /** Validates polling options against minimum accepted values. */ validateSettings() { if (this.delaySec < minDelaySec) { throw new MindeeConfigurationError( @@ -107,6 +111,7 @@ export class PollingOptions { } } + /** Returns a compact string representation of the polling options. */ toString(): string { return `{ initialDelaySec: ${this.initialDelaySec}, delaySec: ${this.delaySec}, maxRetries: ${this.maxRetries} }`; } diff --git a/src/v2/parsing/baseResponse.ts b/src/v2/parsing/baseResponse.ts index 994e3a2f4..f9d9af364 100644 --- a/src/v2/parsing/baseResponse.ts +++ b/src/v2/parsing/baseResponse.ts @@ -1,6 +1,7 @@ import { StringDict } from "@/parsing/stringDict.js"; import { logger } from "@/logger.js"; +/** Base response contract for v2 product responses. */ export abstract class BaseResponse { /** * Raw text representation of the API's response. @@ -24,4 +25,5 @@ export abstract class BaseResponse { } } +/** Constructor signature for typed v2 response classes. */ export type ResponseConstructor = new (serverResponse: StringDict) => T; diff --git a/src/v2/parsing/error/errorDetails.ts b/src/v2/parsing/error/errorDetails.ts index 6989e1ef5..55e04aeef 100644 --- a/src/v2/parsing/error/errorDetails.ts +++ b/src/v2/parsing/error/errorDetails.ts @@ -1,5 +1,6 @@ import { ErrorItem } from "./errorItem.js"; +/** Structured error payload returned by v2 APIs. */ export interface ErrorDetails { /** * The HTTP status code returned by the server. diff --git a/src/v2/parsing/inference/baseInference.ts b/src/v2/parsing/inference/baseInference.ts index 9df645bf5..8be4c6f56 100644 --- a/src/v2/parsing/inference/baseInference.ts +++ b/src/v2/parsing/inference/baseInference.ts @@ -3,6 +3,7 @@ import { InferenceModel } from "./inferenceModel.js"; import { InferenceFile } from "./inferenceFile.js"; import { InferenceJob } from "./inferenceJob.js"; +/** Shared metadata container for v2 inference payloads. */ export abstract class BaseInference { /** * Model info for the inference. @@ -28,6 +29,7 @@ export abstract class BaseInference { this.file = new InferenceFile(serverResponse["file"]); } + /** Returns a printable representation of inference metadata. */ toString(): string { return ( "Inference\n" + diff --git a/src/v2/parsing/inference/field/baseField.ts b/src/v2/parsing/inference/field/baseField.ts index d1fc37478..09814931e 100644 --- a/src/v2/parsing/inference/field/baseField.ts +++ b/src/v2/parsing/inference/field/baseField.ts @@ -3,8 +3,11 @@ import { StringDict } from "@/parsing/stringDict.js"; import { FieldLocation } from "./fieldLocation.js"; export abstract class BaseField { + /** Indentation level used when rendering field text output. */ protected _indentLevel: number; + /** Confidence level associated with this field. */ public confidence: FieldConfidence | undefined; + /** Optional list of source locations for this field. */ public locations: Array | undefined; protected constructor(rawResponse: StringDict, indentLevel = 0) { diff --git a/src/v2/parsing/inference/field/fieldConfidence.ts b/src/v2/parsing/inference/field/fieldConfidence.ts index 428b9757d..4b92105e0 100644 --- a/src/v2/parsing/inference/field/fieldConfidence.ts +++ b/src/v2/parsing/inference/field/fieldConfidence.ts @@ -4,9 +4,13 @@ * Confidence level of a field as returned by the V2 API. */ export enum FieldConfidence { + /** Maximum confidence level. */ Certain = "Certain", + /** High confidence level. */ High = "High", + /** Medium confidence level. */ Medium = "Medium", + /** Low confidence level. */ Low = "Low", } diff --git a/src/v2/parsing/inference/field/fieldFactory.ts b/src/v2/parsing/inference/field/fieldFactory.ts index b5f539d8a..50cc16d81 100644 --- a/src/v2/parsing/inference/field/fieldFactory.ts +++ b/src/v2/parsing/inference/field/fieldFactory.ts @@ -7,6 +7,11 @@ import { ListField } from "./listField.js"; import { ObjectField } from "./objectField.js"; import { SimpleField } from "./simpleField.js"; +/** + * Create a field from a server response. + * @param serverResponse Server response. + * @param indentLevel Indent level. + */ export function createField(serverResponse: StringDict, indentLevel = 0) { if (typeof serverResponse !== "object" || serverResponse === null) { throw new MindeeDeserializationError( diff --git a/src/v2/parsing/inference/field/fieldLocation.ts b/src/v2/parsing/inference/field/fieldLocation.ts index 58f012f3e..2346ae234 100644 --- a/src/v2/parsing/inference/field/fieldLocation.ts +++ b/src/v2/parsing/inference/field/fieldLocation.ts @@ -15,6 +15,7 @@ export class FieldLocation { this.page = serverResponse["page"]; } + /** Returns a readable location string. */ toString(): string { return `${this.polygon} on page ${this.page}`; } diff --git a/src/v2/parsing/inference/field/inferenceFields.ts b/src/v2/parsing/inference/field/inferenceFields.ts index af429761e..8f392ff3e 100644 --- a/src/v2/parsing/inference/field/inferenceFields.ts +++ b/src/v2/parsing/inference/field/inferenceFields.ts @@ -5,7 +5,9 @@ import type { SimpleField } from "./simpleField.js"; import { createField } from "./fieldFactory.js"; +/** Typed map of extraction fields returned by the API. */ export class InferenceFields extends Map { + /** Indentation level used when rendering text output. */ protected _indentLevel: number; constructor(serverResponse: StringDict, indentLevel = 0) { @@ -15,6 +17,7 @@ export class InferenceFields extends Map page.toString()).join("\n\n"); } diff --git a/src/v2/parsing/inference/field/simpleField.ts b/src/v2/parsing/inference/field/simpleField.ts index 5cf7d6220..1b89ed5aa 100644 --- a/src/v2/parsing/inference/field/simpleField.ts +++ b/src/v2/parsing/inference/field/simpleField.ts @@ -1,6 +1,7 @@ import { StringDict } from "@/parsing/stringDict.js"; import { BaseField } from "./baseField.js"; +/** Scalar inference field. */ export class SimpleField extends BaseField { /** * The untyped value of the field. @@ -48,6 +49,7 @@ export class SimpleField extends BaseField { return this.value as boolean; } + /** Returns a readable scalar representation. */ toString(): string { if (this.value === null) { return ""; diff --git a/src/v2/parsing/inference/inferenceFile.ts b/src/v2/parsing/inference/inferenceFile.ts index a8b212710..6efbee89e 100644 --- a/src/v2/parsing/inference/inferenceFile.ts +++ b/src/v2/parsing/inference/inferenceFile.ts @@ -1,5 +1,6 @@ import { StringDict } from "@/parsing/stringDict.js"; +/** File metadata attached to a v2 inference. */ export class InferenceFile { /** * Name of the file. @@ -25,6 +26,7 @@ export class InferenceFile { this.mimeType = serverResponse["mime_type"]; } + /** Returns a printable representation of file metadata. */ toString () { return( "File\n" + diff --git a/src/v2/parsing/inference/inferenceModel.ts b/src/v2/parsing/inference/inferenceModel.ts index 4d45be80f..0725b9ee1 100644 --- a/src/v2/parsing/inference/inferenceModel.ts +++ b/src/v2/parsing/inference/inferenceModel.ts @@ -1,5 +1,6 @@ import { StringDict } from "@/parsing/stringDict.js"; +/** Model metadata attached to a v2 inference. */ export class InferenceModel { /** * ID of the model. @@ -10,6 +11,7 @@ export class InferenceModel { this.id = serverResponse["id"]; } + /** Returns a printable representation of model metadata. */ toString(): string { return "Model\n" + "=====\n" + diff --git a/src/v2/product/classification/classification.ts b/src/v2/product/classification/classification.ts index 59c05eecf..268ae49a4 100644 --- a/src/v2/product/classification/classification.ts +++ b/src/v2/product/classification/classification.ts @@ -6,12 +6,15 @@ import { BaseProduct } from "@/v2/product/baseProduct.js"; * Automatically sort any image or scanned document into categories. */ export class Classification extends BaseProduct { + /** Parameter class accepted by this product. */ static get parametersClass() { return ClassificationParameters; } + /** Response class returned by this product. */ static get responseClass() { return ClassificationResponse; } + /** API slug for this product. */ static get slug() { return "classification"; } diff --git a/src/v2/product/classification/classificationClassifier.ts b/src/v2/product/classification/classificationClassifier.ts index 3344a1b39..7c5bf2d1b 100644 --- a/src/v2/product/classification/classificationClassifier.ts +++ b/src/v2/product/classification/classificationClassifier.ts @@ -4,6 +4,7 @@ import { ExtractionResponse } from "@/v2/product/index.js"; /** * Document level classification. */ +/** Classifier output for document-level classification. */ export class ClassificationClassifier { /** * The document type, as identified on given classification values. @@ -21,6 +22,7 @@ export class ClassificationClassifier { : undefined; } + /** Returns a readable classifier summary. */ toString(): string { return `Document Type: ${this.documentType}`; } diff --git a/src/v2/product/classification/classificationInference.ts b/src/v2/product/classification/classificationInference.ts index 872fa2ee7..f42fc4891 100644 --- a/src/v2/product/classification/classificationInference.ts +++ b/src/v2/product/classification/classificationInference.ts @@ -2,6 +2,7 @@ import { StringDict } from "@/parsing/index.js"; import { BaseInference } from "@/v2/parsing/inference/baseInference.js"; import { ClassificationResult } from "./classificationResult.js"; +/** Inference payload for classification responses. */ export class ClassificationInference extends BaseInference { /** * Result of a classification inference. @@ -13,6 +14,7 @@ export class ClassificationInference extends BaseInference { this.result = new ClassificationResult(serverResponse["result"]); } + /** Returns a readable representation of the inference. */ toString(): string { return ( super.toString() + diff --git a/src/v2/product/classification/classificationResponse.ts b/src/v2/product/classification/classificationResponse.ts index f1195ea2a..1c41b550f 100644 --- a/src/v2/product/classification/classificationResponse.ts +++ b/src/v2/product/classification/classificationResponse.ts @@ -2,6 +2,7 @@ import { StringDict } from "@/parsing/stringDict.js"; import { ClassificationInference } from "./classificationInference.js"; import { BaseResponse } from "@/v2/parsing/index.js"; +/** Response wrapper for classification product calls. */ export class ClassificationResponse extends BaseResponse { /** * The inference result for a classification utility request. diff --git a/src/v2/product/classification/classificationResult.ts b/src/v2/product/classification/classificationResult.ts index 99f16b08d..26678a1ed 100644 --- a/src/v2/product/classification/classificationResult.ts +++ b/src/v2/product/classification/classificationResult.ts @@ -1,6 +1,7 @@ import { StringDict } from "@/parsing/stringDict.js"; import { ClassificationClassifier } from "./classificationClassifier.js"; +/** Classification section of a classification inference result. */ export class ClassificationResult { /** * Fields contained in the inference. @@ -11,6 +12,7 @@ export class ClassificationResult { this.classification = new ClassificationClassifier(serverResponse["classification"]); } + /** Returns a readable classification summary. */ toString(): string { return `Classification\n==============\n${this.classification}`; } diff --git a/src/v2/product/crop/crop.ts b/src/v2/product/crop/crop.ts index 46dbf745d..223999feb 100644 --- a/src/v2/product/crop/crop.ts +++ b/src/v2/product/crop/crop.ts @@ -6,12 +6,15 @@ import { BaseProduct } from "@/v2/product/baseProduct.js"; * Identify the borders of documents on each page, matching each one to a category. */ export class Crop extends BaseProduct { + /** Parameter class accepted by this product. */ static get parametersClass() { return CropParameters; } + /** Response class returned by this product. */ static get responseClass() { return CropResponse; } + /** API slug for this product. */ static get slug() { return "crop"; } diff --git a/src/v2/product/crop/cropInference.ts b/src/v2/product/crop/cropInference.ts index ecfbc7131..d243f0bc5 100644 --- a/src/v2/product/crop/cropInference.ts +++ b/src/v2/product/crop/cropInference.ts @@ -2,6 +2,7 @@ import { StringDict } from "@/parsing/index.js"; import { BaseInference } from "@/v2/parsing/inference/baseInference.js"; import { CropResult } from "@/v2/product/crop/cropResult.js"; +/** Inference payload for crop responses. */ export class CropInference extends BaseInference { /** * Result of a crop utility inference. @@ -13,6 +14,7 @@ export class CropInference extends BaseInference { this.result = new CropResult(serverResponse["result"]); } + /** Returns a readable representation of the inference. */ toString(): string { return ( super.toString() + diff --git a/src/v2/product/crop/cropItem.ts b/src/v2/product/crop/cropItem.ts index cce46560d..cac8b98cd 100644 --- a/src/v2/product/crop/cropItem.ts +++ b/src/v2/product/crop/cropItem.ts @@ -5,6 +5,7 @@ import { extractSingleCrop } from "@/v2/fileOperations/crop.js"; import { ExtractedImage } from "@/image/index.js"; import { ExtractionResponse } from "@/v2/product/index.js"; +/** Single crop detection returned by the crop product. */ export class CropItem { /** * Type or classification of the detected object. @@ -27,6 +28,7 @@ export class CropItem { : undefined; } + /** Returns a readable crop item summary. */ toString(): string { return `* :Location: ${this.location}\n :Object Type: ${this.objectType}`; } diff --git a/src/v2/product/crop/cropResponse.ts b/src/v2/product/crop/cropResponse.ts index e9f7d2a39..0e17dba15 100644 --- a/src/v2/product/crop/cropResponse.ts +++ b/src/v2/product/crop/cropResponse.ts @@ -2,6 +2,7 @@ import { StringDict } from "@/parsing/stringDict.js"; import { BaseResponse } from "@/v2/parsing/index.js"; import { CropInference } from "./cropInference.js"; +/** Response wrapper for crop product calls. */ export class CropResponse extends BaseResponse { /** * Response for a crop utility inference. diff --git a/src/v2/product/crop/cropResult.ts b/src/v2/product/crop/cropResult.ts index d6d1957d5..f8a539814 100644 --- a/src/v2/product/crop/cropResult.ts +++ b/src/v2/product/crop/cropResult.ts @@ -4,6 +4,7 @@ import { LocalInputSource } from "@/input/index.js"; import { extractMultipleCrops } from "@/v2/fileOperations/crop.js"; import { ExtractedImages } from "@/image/extractedImages.js"; +/** Crop section of a crop inference result. */ export class CropResult { /** * Fields contained in the inference. @@ -14,6 +15,7 @@ export class CropResult { this.crops = serverResponse["crops"].map((cropItem: StringDict) => new CropItem(cropItem)); } + /** Returns a readable list of detected crops. */ toString(): string { const crops = this.crops.map(item => item.toString()).join("\n"); return `Crops\n=====\n${crops}`; diff --git a/src/v2/product/extraction/dataSchemaActiveOption.ts b/src/v2/product/extraction/dataSchemaActiveOption.ts index 841241ac4..30e68c56f 100644 --- a/src/v2/product/extraction/dataSchemaActiveOption.ts +++ b/src/v2/product/extraction/dataSchemaActiveOption.ts @@ -13,6 +13,7 @@ export class DataSchemaActiveOption { this.replace = serverResponse["replace"]; } + /** Returns a readable representation of active data schema options. */ toString() { return `Data Schema\n-----------\n:Replace: ${this.replace? "True" : "False"}`; } diff --git a/src/v2/product/extraction/extraction.ts b/src/v2/product/extraction/extraction.ts index 89eacc92e..f04dff202 100644 --- a/src/v2/product/extraction/extraction.ts +++ b/src/v2/product/extraction/extraction.ts @@ -6,12 +6,15 @@ import { BaseProduct } from "@/v2/product/baseProduct.js"; * Automatically extract structured data from any image or scanned document. */ export class Extraction extends BaseProduct { + /** Parameter class accepted by this product. */ static get parametersClass() { return ExtractionParameters; } + /** Response class returned by this product. */ static get responseClass() { return ExtractionResponse; } + /** API slug for this product. */ static get slug() { return "extraction"; } diff --git a/src/v2/product/extraction/extractionActiveOptions.ts b/src/v2/product/extraction/extractionActiveOptions.ts index 7846205fd..fb076a715 100644 --- a/src/v2/product/extraction/extractionActiveOptions.ts +++ b/src/v2/product/extraction/extractionActiveOptions.ts @@ -1,6 +1,7 @@ import { StringDict } from "@/parsing/stringDict.js"; import { DataSchemaActiveOption } from "./dataSchemaActiveOption.js"; +/** Active extraction options echoed by the API. */ export class ExtractionActiveOptions { /** * Whether the RAG feature was activated. @@ -38,6 +39,7 @@ export class ExtractionActiveOptions { this.dataSchema = new DataSchemaActiveOption(serverResponse["data_schema"]); } + /** Returns a readable representation of active options. */ toString(): string { return "Active Options\n" + "==============\n" + diff --git a/src/v2/product/extraction/extractionInference.ts b/src/v2/product/extraction/extractionInference.ts index aca2702c9..018ad0525 100644 --- a/src/v2/product/extraction/extractionInference.ts +++ b/src/v2/product/extraction/extractionInference.ts @@ -3,6 +3,7 @@ import { ExtractionResult } from "./extractionResult.js"; import { ExtractionActiveOptions } from "./extractionActiveOptions.js"; import { BaseInference } from "@/v2/parsing/inference/baseInference.js"; +/** Inference payload for extraction responses. */ export class ExtractionInference extends BaseInference { /** * Result of the inference. @@ -19,6 +20,7 @@ export class ExtractionInference extends BaseInference { this.activeOptions = new ExtractionActiveOptions(serverResponse["active_options"]); } + /** Returns a readable representation of the inference. */ toString(): string { return ( super.toString() + diff --git a/src/v2/product/extraction/extractionResponse.ts b/src/v2/product/extraction/extractionResponse.ts index 39e637b10..d67e8c53c 100644 --- a/src/v2/product/extraction/extractionResponse.ts +++ b/src/v2/product/extraction/extractionResponse.ts @@ -2,6 +2,7 @@ import { ExtractionInference } from "./extractionInference.js"; import { StringDict } from "@/parsing/stringDict.js"; import { BaseResponse } from "@/v2/parsing/index.js"; +/** Response wrapper for extraction product calls. */ export class ExtractionResponse extends BaseResponse { /** * The inference result for an extraction request. diff --git a/src/v2/product/extraction/extractionResult.ts b/src/v2/product/extraction/extractionResult.ts index 8e3ce1b0b..8f4dea36d 100644 --- a/src/v2/product/extraction/extractionResult.ts +++ b/src/v2/product/extraction/extractionResult.ts @@ -3,6 +3,7 @@ import { StringDict } from "@/parsing/stringDict.js"; import { RawText } from "@/v2/parsing/inference/field/rawText.js"; import { RagMetadata } from "@/v2/parsing/inference/field/ragMetadata.js"; +/** Extraction result payload containing fields and optional extras. */ export class ExtractionResult { /** * Fields contained in the inference. @@ -29,6 +30,7 @@ export class ExtractionResult { } } + /** Returns a readable representation of extracted fields. */ toString(): string { return `Fields\n======\n${this.fields}`; } diff --git a/src/v2/product/extraction/params/dataSchema.ts b/src/v2/product/extraction/params/dataSchema.ts index 29a23254e..88f9f49e1 100644 --- a/src/v2/product/extraction/params/dataSchema.ts +++ b/src/v2/product/extraction/params/dataSchema.ts @@ -1,5 +1,11 @@ import { StringDict } from "@/parsing/stringDict.js"; -import { DataSchemaReplace } from "./dataSchemaReplace.js"; +import { DataSchemaReplace, DataSchemaReplaceJson } from "./dataSchemaReplace.js"; + +/** JSON payload for top-level data schema options. */ +export interface DataSchemaJson { + /** Optional full replacement schema. */ + replace?: DataSchemaReplaceJson; +} /** * Modify the Data Schema. @@ -20,9 +26,11 @@ export class DataSchema { } } - toJSON() { + /** Serializes the data schema parameters to API format. */ + toJSON(): DataSchemaJson { return { replace: this.replace?.toJSON() }; } + /** Returns a JSON string representation of the data schema parameters. */ toString() { return JSON.stringify(this.toJSON()); } diff --git a/src/v2/product/extraction/params/dataSchemaField.ts b/src/v2/product/extraction/params/dataSchemaField.ts index 3b53fcd01..922f56058 100644 --- a/src/v2/product/extraction/params/dataSchemaField.ts +++ b/src/v2/product/extraction/params/dataSchemaField.ts @@ -1,5 +1,32 @@ import { StringDict } from "@/parsing/index.js"; +/** JSON payload for a single data schema field definition. */ +export interface DataSchemaFieldJson { + /** Field key used in results. */ + name: string; + /** Human-readable field title. */ + title: string; + /** Whether the field accepts multiple values. */ + // eslint-disable-next-line @typescript-eslint/naming-convention + is_array: boolean; + /** Field type name. */ + type: string; + /** Optional allowed classes for classification fields. */ + // eslint-disable-next-line @typescript-eslint/naming-convention + classification_values?: Array; + /** Optional deduplication behavior for array fields. */ + // eslint-disable-next-line @typescript-eslint/naming-convention + unique_values?: boolean; + /** Optional field description. */ + description?: string; + /** Optional extraction guidelines. */ + guidelines?: string; + /** Optional nested schema for object fields. */ + // eslint-disable-next-line @typescript-eslint/naming-convention + nested_fields?: StringDict; +} + +/** Data schema field definition used by extraction parameters. */ export class DataSchemaField { /** * Display name for the field, also impacts inference results. @@ -51,8 +78,9 @@ export class DataSchemaField { this.nestedFields = fields["nested_fields"]; } - toJSON() { - const out: Record = { + /** Serializes the field definition to API format. */ + toJSON(): DataSchemaFieldJson { + const out: DataSchemaFieldJson = { name: this.name, title: this.title, // eslint-disable-next-line @typescript-eslint/naming-convention,camelcase @@ -72,6 +100,7 @@ export class DataSchemaField { return out; } + /** Returns a JSON string representation of the field definition. */ toString() { return JSON.stringify(this.toJSON()); } diff --git a/src/v2/product/extraction/params/dataSchemaReplace.ts b/src/v2/product/extraction/params/dataSchemaReplace.ts index b56b8564f..836ef5be5 100644 --- a/src/v2/product/extraction/params/dataSchemaReplace.ts +++ b/src/v2/product/extraction/params/dataSchemaReplace.ts @@ -1,7 +1,13 @@ import { StringDict } from "@/parsing/index.js"; -import { DataSchemaField } from "./dataSchemaField.js"; +import { DataSchemaField, DataSchemaFieldJson } from "./dataSchemaField.js"; import { MindeeError } from "@/errors/index.js"; +/** JSON payload for replacement data schema options. */ +export interface DataSchemaReplaceJson { + /** Field definitions replacing the current schema. */ + fields: Array; +} + /** * The structure to completely replace the data schema of the model. */ @@ -21,10 +27,12 @@ export class DataSchemaReplace { this.fields = dataSchemaReplace["fields"].map((field: StringDict) => (new DataSchemaField(field))); } - toJSON() { + /** Serializes the replacement schema to API format. */ + toJSON(): DataSchemaReplaceJson { return { fields: this.fields.map(e => e.toJSON()) }; } + /** Returns a JSON string representation of the replacement schema. */ toString() { return JSON.stringify(this.toJSON()); } diff --git a/src/v2/product/ocr/ocr.ts b/src/v2/product/ocr/ocr.ts index 733943108..19329ac9f 100644 --- a/src/v2/product/ocr/ocr.ts +++ b/src/v2/product/ocr/ocr.ts @@ -6,12 +6,15 @@ import { BaseProduct } from "@/v2/product/baseProduct.js"; * Extract raw text (OCR) from any image or scanned document. */ export class Ocr extends BaseProduct { + /** Parameter class accepted by this product. */ static get parametersClass() { return OcrParameters; } + /** Response class returned by this product. */ static get responseClass() { return OcrResponse; } + /** API slug for this product. */ static get slug() { return "ocr"; } diff --git a/src/v2/product/ocr/ocrInference.ts b/src/v2/product/ocr/ocrInference.ts index 284461f8b..31097754a 100644 --- a/src/v2/product/ocr/ocrInference.ts +++ b/src/v2/product/ocr/ocrInference.ts @@ -2,6 +2,7 @@ import { StringDict } from "@/parsing/index.js"; import { BaseInference } from "@/v2/parsing/inference/baseInference.js"; import { OcrResult } from "@/v2/product/ocr/ocrResult.js"; +/** Inference payload for OCR responses. */ export class OcrInference extends BaseInference { /** * Result of an OCR inference. @@ -13,6 +14,7 @@ export class OcrInference extends BaseInference { this.result = new OcrResult(serverResponse["result"]); } + /** Returns a readable representation of OCR inference output. */ toString(): string { return ( "Inference\n" + diff --git a/src/v2/product/ocr/ocrPage.ts b/src/v2/product/ocr/ocrPage.ts index b531025b1..91f2808e0 100644 --- a/src/v2/product/ocr/ocrPage.ts +++ b/src/v2/product/ocr/ocrPage.ts @@ -1,6 +1,7 @@ import { OcrWord } from "./ocrWord.js"; import { StringDict } from "@/parsing/index.js"; +/** OCR payload for a single page. */ export class OcrPage { /** * List of words extracted from the document page. @@ -17,6 +18,7 @@ export class OcrPage { this.content = serverResponse["content"] as string; } + /** Returns a readable representation of page OCR content. */ toString(): string { let ocrWords = "\n"; if (this.words.length > 0) { diff --git a/src/v2/product/ocr/ocrResponse.ts b/src/v2/product/ocr/ocrResponse.ts index 2e6ddb3dd..af64dd4ad 100644 --- a/src/v2/product/ocr/ocrResponse.ts +++ b/src/v2/product/ocr/ocrResponse.ts @@ -2,6 +2,7 @@ import { StringDict } from "@/parsing/stringDict.js"; import { OcrInference } from "./ocrInference.js"; import { BaseResponse } from "@/v2/parsing/index.js"; +/** Response wrapper for OCR product calls. */ export class OcrResponse extends BaseResponse { /** * Response for an OCR utility inference. diff --git a/src/v2/product/ocr/ocrResult.ts b/src/v2/product/ocr/ocrResult.ts index 4473a5f80..21a441ea2 100644 --- a/src/v2/product/ocr/ocrResult.ts +++ b/src/v2/product/ocr/ocrResult.ts @@ -14,6 +14,7 @@ export class OcrResult { this.pages = serverResponse.pages.map((ocr: any) => new OcrPage(ocr)); } + /** Returns a readable OCR summary for all pages. */ toString(): string { let pages = "\n"; if (this.pages.length > 0) { diff --git a/src/v2/product/ocr/ocrWord.ts b/src/v2/product/ocr/ocrWord.ts index faa264fa7..f7c079d6f 100644 --- a/src/v2/product/ocr/ocrWord.ts +++ b/src/v2/product/ocr/ocrWord.ts @@ -1,6 +1,7 @@ import { Polygon } from "@/geometry/index.js"; import { StringDict } from "@/parsing/index.js"; +/** OCR word token with geometry. */ export class OcrWord { /** * Text content of the word. @@ -17,6 +18,7 @@ export class OcrWord { this.polygon = new Polygon(...serverResponse["polygon"]); } + /** Returns the OCR token text. */ toString(): string { return this.content; } diff --git a/src/v2/product/split/split.ts b/src/v2/product/split/split.ts index 50816b1d4..2b6ef48ed 100644 --- a/src/v2/product/split/split.ts +++ b/src/v2/product/split/split.ts @@ -6,12 +6,15 @@ import { BaseProduct } from "@/v2/product/baseProduct.js"; * Break a multipage source file into separate documents, associating a class for each one. */ export class Split extends BaseProduct { + /** Parameter class accepted by this product. */ static get parametersClass() { return SplitParameters; } + /** Response class returned by this product. */ static get responseClass() { return SplitResponse; } + /** API slug for this product. */ static get slug() { return "split"; } diff --git a/src/v2/product/split/splitInference.ts b/src/v2/product/split/splitInference.ts index eefcbb29f..ccb1bc89d 100644 --- a/src/v2/product/split/splitInference.ts +++ b/src/v2/product/split/splitInference.ts @@ -2,6 +2,7 @@ import { StringDict } from "@/parsing/index.js"; import { BaseInference } from "@/v2/parsing/inference/baseInference.js"; import { SplitResult } from "./splitResult.js"; +/** Inference payload for split responses. */ export class SplitInference extends BaseInference { /** * Result of a split inference. @@ -13,6 +14,7 @@ export class SplitInference extends BaseInference { this.result = new SplitResult(serverResponse["result"]); } + /** Returns a readable representation of the split inference. */ toString(): string { return ( super.toString() + diff --git a/src/v2/product/split/splitRange.ts b/src/v2/product/split/splitRange.ts index 30bb92090..cca9e8ded 100644 --- a/src/v2/product/split/splitRange.ts +++ b/src/v2/product/split/splitRange.ts @@ -30,6 +30,7 @@ export class SplitRange { : undefined; } + /** Returns a readable summary of the split range. */ toString(): string { const pageRange = this.pageRange.join(","); return `* :Page Range: ${pageRange}\n :Document Type: ${this.documentType}`; diff --git a/src/v2/product/split/splitResponse.ts b/src/v2/product/split/splitResponse.ts index 891cb30de..3999c05bc 100644 --- a/src/v2/product/split/splitResponse.ts +++ b/src/v2/product/split/splitResponse.ts @@ -2,6 +2,7 @@ import { StringDict } from "@/parsing/stringDict.js"; import { SplitInference } from "./splitInference.js"; import { BaseResponse } from "@/v2/parsing/index.js"; +/** Response wrapper for split product calls. */ export class SplitResponse extends BaseResponse { /** * Response for an OCR utility inference. diff --git a/src/v2/product/split/splitResult.ts b/src/v2/product/split/splitResult.ts index 08411d8af..ac1aa3f72 100644 --- a/src/v2/product/split/splitResult.ts +++ b/src/v2/product/split/splitResult.ts @@ -29,6 +29,7 @@ export class SplitResult { return await extractMultipleSplits(inputSource, splits); } + /** Returns a readable list of split ranges. */ toString(): string { let splits = "\n"; if (this.splits.length > 0) { diff --git a/tests/dependency/missingDependencies.spec.ts b/tests/dependency/missingDependencies.spec.ts index 9073daeea..eae858a0f 100644 --- a/tests/dependency/missingDependencies.spec.ts +++ b/tests/dependency/missingDependencies.spec.ts @@ -1,7 +1,10 @@ import assert from "node:assert/strict"; import { describe, it } from "node:test"; +import { hasAllOptionalDependencies } from "../helpers/optionalDeps.js"; -describe("MindeeV1 - Optional Dependencies #OptionalDepsRemoved", function () { +const hasOptionals = hasAllOptionalDependencies(); + +describe("MindeeV1 - Optional Dependencies #OptionalDepsRemoved", { skip: hasOptionals }, function () { const modules = [ "sharp", @@ -16,10 +19,7 @@ describe("MindeeV1 - Optional Dependencies #OptionalDepsRemoved", function () { await import(moduleName); assert.fail("sharp should not be installed in this environment, but it was found!"); } catch (error: any) { - const binaryMissing = error.message - && error.code === "ERR_MODULE_NOT_FOUND" - && error.message.includes(`Could not load the "${moduleName}" module`); - if (!binaryMissing) { + if (error?.code !== "ERR_MODULE_NOT_FOUND") { throw error; } } diff --git a/tests/geometry/bbox.spec.ts b/tests/geometry/bbox.spec.ts index 87b29b598..192a4366e 100644 --- a/tests/geometry/bbox.spec.ts +++ b/tests/geometry/bbox.spec.ts @@ -18,30 +18,30 @@ describe("Geometry functions - BBox", () => { ); const mergedBbox = firsBBox.mergeBbox(secondBBox); - assert.strictEqual(mergedBbox.xMin, 0.081); - assert.strictEqual(mergedBbox.yMin, 0.442); - assert.strictEqual(mergedBbox.xMax, 0.26); - assert.strictEqual(mergedBbox.yMax, 0.451); + assert.ok(Math.abs(mergedBbox.xMin - 0.081) < 1e-9); + assert.ok(Math.abs(mergedBbox.yMin - 0.442) < 1e-9); + assert.ok(Math.abs(mergedBbox.xMax - 0.26) < 1e-9); + assert.ok(Math.abs(mergedBbox.yMax - 0.451) < 1e-9); }); it("should get a polygon's bbox", () => { const bboxA = getBbox(polygonA); - assert.strictEqual(bboxA.xMin, 0.123); - assert.strictEqual(bboxA.yMin, 0.53); - assert.strictEqual(bboxA.xMax, 0.175); - assert.strictEqual(bboxA.yMax, 0.546); + assert.ok(Math.abs(bboxA.xMin - 0.123) < 1e-9); + assert.ok(Math.abs(bboxA.yMin - 0.53) < 1e-9); + assert.ok(Math.abs(bboxA.xMax - 0.175) < 1e-9); + assert.ok(Math.abs(bboxA.yMax - 0.546) < 1e-9); const bboxB = getBbox(polygonB); - assert.strictEqual(bboxB.xMin, 0.124); - assert.strictEqual(bboxB.yMin, 0.535); - assert.strictEqual(bboxB.xMax, 0.19); - assert.strictEqual(bboxB.yMax, 0.546); + assert.ok(Math.abs(bboxB.xMin - 0.124) < 1e-9); + assert.ok(Math.abs(bboxB.yMin - 0.535) < 1e-9); + assert.ok(Math.abs(bboxB.xMax - 0.19) < 1e-9); + assert.ok(Math.abs(bboxB.yMax - 0.546) < 1e-9); const bboxC = getBbox(polygonC); - assert.strictEqual(bboxC.xMin, 0.205); - assert.strictEqual(bboxC.yMin, 0.407); - assert.strictEqual(bboxC.xMax, 0.381); - assert.strictEqual(bboxC.yMax, 0.43); + assert.ok(Math.abs(bboxC.xMin - 0.205) < 1e-9); + assert.ok(Math.abs(bboxC.yMin - 0.407) < 1e-9); + assert.ok(Math.abs(bboxC.xMax - 0.381) < 1e-9); + assert.ok(Math.abs(bboxC.yMax - 0.43) < 1e-9); }); it("should get a polygon's bounding box", () => { diff --git a/tests/helpers/optionalDeps.ts b/tests/helpers/optionalDeps.ts new file mode 100644 index 000000000..24fb0f378 --- /dev/null +++ b/tests/helpers/optionalDeps.ts @@ -0,0 +1,31 @@ +import { createRequire } from "node:module"; + +const require = createRequire(import.meta.url); + +export const OPTIONAL_DEPENDENCIES = [ + "sharp", + "pdf.js-extract", + "@cantoo/pdf-lib", + "node-poppler", +] as const; + +/** + * Checks whether the optional dependencies are present. + * @param moduleName Name of the module to check. + */ +export function hasOptionalDependency(moduleName: string): boolean { + try { + require.resolve(moduleName); + return true; + } catch { + return false; + } +} + +/** + * Checks whether all the optional dependencies are present. + */ +export function hasAllOptionalDependencies(): boolean { + return OPTIONAL_DEPENDENCIES.every(hasOptionalDependency); +} + diff --git a/tests/http/apiCore.spec.ts b/tests/http/apiCore.spec.ts index 9931d0133..34ba794ea 100644 --- a/tests/http/apiCore.spec.ts +++ b/tests/http/apiCore.spec.ts @@ -15,6 +15,10 @@ const BASE_OPTIONS: RequestOptions = { timeoutSecs: 10, }; +/** + * Create an error that looks like a socket error. + * @param code Error code. + */ function socketError(code: string): Error { return Object.assign(new Error("socket closed"), { code }); } @@ -23,6 +27,10 @@ type Interceptor = | { error: Error } | { statusCode: number; body: string }; +/** + * Create a mock agent with interceptors. + * @param interceptors Interceptors. + */ function makeAgent(...interceptors: Interceptor[]): MockAgent { const mockAgent = new MockAgent(); const pool = mockAgent.get(`https://${HOST}`); @@ -37,6 +45,10 @@ function makeAgent(...interceptors: Interceptor[]): MockAgent { return mockAgent; } +/** + * Send a request and read the response. + * @param agent Mock agent. + */ async function sendRequest(agent: MockAgent): Promise { return sendRequestAndReadResponse(agent, BASE_OPTIONS); } diff --git a/tests/input/compression.spec.ts b/tests/input/compression.spec.ts index b82ebdecf..438fd9c12 100644 --- a/tests/input/compression.spec.ts +++ b/tests/input/compression.spec.ts @@ -8,8 +8,11 @@ import { compressPdf } from "@/pdf/index.js"; import { extractTextFromPdf } from "@/pdf/pdfUtils.js"; import { logger } from "@/logger.js"; import { RESOURCE_PATH, V1_PRODUCT_PATH } from "../index.js"; +import { hasAllOptionalDependencies } from "../helpers/optionalDeps.js"; -describe("Input Sources - compression and resize #OptionalDepsRequired", () => { +const hasOptionals = hasAllOptionalDependencies(); + +describe("Input Sources - compression and resize #OptionalDepsRequired", { skip: !hasOptionals }, () => { const outputPath = path.join(RESOURCE_PATH, "output"); before(async () => { diff --git a/tests/input/pageOperations.spec.ts b/tests/input/pageOperations.spec.ts index 5b0810c7c..36c906b29 100644 --- a/tests/input/pageOperations.spec.ts +++ b/tests/input/pageOperations.spec.ts @@ -8,8 +8,11 @@ import * as path from "path"; import { describe, it } from "node:test"; import assert from "node:assert/strict"; import { RESOURCE_PATH } from "../index.js"; +import { hasAllOptionalDependencies } from "../helpers/optionalDeps.js"; -describe("Input Sources - high level multi-page operations #OptionalDepsRequired", () => { +const hasOptionals = hasAllOptionalDependencies(); + +describe("Input Sources - high level multi-page operations #OptionalDepsRequired", { skip: !hasOptionals }, () => { it("should cut a PDF", async () => { const input = new PathInput({ inputPath: path.join(RESOURCE_PATH, "file_types/pdf/multipage.pdf"), diff --git a/tests/input/sources.spec.ts b/tests/input/sources.spec.ts index 26968e773..a46e04532 100644 --- a/tests/input/sources.spec.ts +++ b/tests/input/sources.spec.ts @@ -18,6 +18,9 @@ import { } from "@/input/index.js"; import { MindeeInputSourceError } from "@/errors/index.js"; import { RESOURCE_PATH, V1_PRODUCT_PATH } from "../index.js"; +import { hasAllOptionalDependencies } from "../helpers/optionalDeps.js"; + +const hasOptionals = hasAllOptionalDependencies(); describe("Input Sources - load different types of input", () => { @@ -241,7 +244,7 @@ describe("Input Sources - load different types of input", () => { assert.strictEqual(inputSource.filename, filename); assert.ok(inputSource.isPdf()); assert.ok(inputSource.fileObject instanceof Buffer); - await it("getPageCount #OptionalDepsRequired", async () => { + await it("getPageCount #OptionalDepsRequired", { skip: !hasOptionals }, async () => { assert.strictEqual(await inputSource.getPageCount(), 10); }); }); diff --git a/tests/input/urlInputSource.spec.ts b/tests/input/urlInputSource.spec.ts index d57e8e23c..11aa8e0ec 100644 --- a/tests/input/urlInputSource.spec.ts +++ b/tests/input/urlInputSource.spec.ts @@ -20,6 +20,7 @@ describe("Input Sources - URL input source", () => { }); it("should throw an error for non-HTTPS URL", async () => { + // eslint-disable-next-line sonarjs/no-clear-text-protocols const url = "http://dummy-host/file.pdf"; const urlSource = new UrlInput({ url, dispatcher: mockAgent }); @@ -128,27 +129,6 @@ describe("Input Sources - URL input source", () => { assert.deepStrictEqual(localInput.fileObject, fileContent); }); - it("should block redirect to loopback address", async () => { - const originalUrl = "https://dummy-host/file.pdf"; - - mockPool - .intercept({ path: "/file.pdf", method: "GET" }) - .reply(302, "", { headers: { location: "https://127.0.0.1/evil" } }); - - const urlInput = new UrlInput({ url: originalUrl, dispatcher: mockAgent }); - - try { - await urlInput.asLocalInputSource(); - assert.fail("Expected an error to be thrown"); - } catch (error) { - assert.ok(error instanceof Error); - assert.strictEqual( - (error as Error).message, - "URL host is a loopback or private address" - ); - } - }); - it("should throw an error for HTTP error responses", async () => { const url = "https://dummy-host/not-found.pdf"; diff --git a/tests/pdf/pdfOperation.spec.ts b/tests/pdf/pdfOperation.spec.ts index 944eb181d..c05e223aa 100644 --- a/tests/pdf/pdfOperation.spec.ts +++ b/tests/pdf/pdfOperation.spec.ts @@ -6,8 +6,11 @@ import { describe, it } from "node:test"; import { PageOptions, PageOptionsOperation } from "@/index.js"; import { PathInput } from "@/index.js"; import { RESOURCE_PATH } from "../index.js"; +import { hasAllOptionalDependencies } from "../helpers/optionalDeps.js"; -describe("Test PDF operation #OptionalDepsRequired", () => { +const hasOptionals = hasAllOptionalDependencies(); + +describe("Test PDF operation #OptionalDepsRequired", { skip: !hasOptionals }, () => { it("should cut a PDF to get 2 pages", async () => { const inputSource = new PathInput({ inputPath: path.join(RESOURCE_PATH, "file_types/pdf/multipage.pdf"), diff --git a/tests/pdf/pdfTypes.spec.ts b/tests/pdf/pdfTypes.spec.ts index 2cdc8fc81..10577f368 100644 --- a/tests/pdf/pdfTypes.spec.ts +++ b/tests/pdf/pdfTypes.spec.ts @@ -5,8 +5,11 @@ import * as pdf from "@/pdf/index.js"; import { PageOptions } from "@/input/index.js"; import { PageOptionsOperation, PathInput } from "@/index.js"; import { RESOURCE_PATH } from "../index.js"; +import { hasAllOptionalDependencies } from "../helpers/optionalDeps.js"; -describe("Test pdf lib #OptionalDepsRequired", () => { +const hasOptionals = hasAllOptionalDependencies(); + +describe("Test pdf lib #OptionalDepsRequired", { skip: !hasOptionals }, () => { it("should open a simple XFA form PDF.", async () => { const inputDoc = new PathInput( diff --git a/tests/runner.mjs b/tests/runner.mjs new file mode 100644 index 000000000..3e202a837 --- /dev/null +++ b/tests/runner.mjs @@ -0,0 +1,56 @@ +// Cross-version test runner entry point. +// +// Node's built-in test runner only gained glob-pattern support in Node 21, +// so `node --test 'tests/**/*.spec.ts'` fails on Node 20. This script performs +// the file discovery itself and passes explicit paths to `node --test`, which +// works on every supported Node version and every OS. +// +// Usage: node tests/runner.mjs [subdir] + +import { spawnSync } from "node:child_process"; +import { readdirSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; + +const testsDir = dirname(fileURLToPath(import.meta.url)); + +const suffix = process.argv[2] === "integration" ? ".integration.ts" : ".spec.ts"; +const subdir = process.argv[3]; +const searchDir = subdir ? join(testsDir, subdir) : testsDir; + +/** + * Recursively collect test files with the given suffix. + * @param {string} dir Directory to search. + * @returns {string[]} Matching file paths. + */ +function findTestFiles(dir) { + const found = []; + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const fullPath = join(dir, entry.name); + if (entry.isDirectory()) { + found.push(...findTestFiles(fullPath)); + } else if (entry.name.endsWith(suffix)) { + found.push(fullPath); + } + } + return found; +} + +const files = findTestFiles(searchDir); + +if (files.length === 0) { + console.error(`No '*${suffix}' test files found in ${searchDir}.`); + process.exit(1); +} + +const result = spawnSync( + process.execPath, + ["--import", "tsx", "--test", ...files], + { stdio: "inherit" } +); + +if (result.error) { + throw result.error; +} + +process.exit(result.status ?? 1); diff --git a/tests/v1/api/endpoint.spec.ts b/tests/v1/api/endpoint.spec.ts index 88b73e3f2..5dadaae49 100644 --- a/tests/v1/api/endpoint.spec.ts +++ b/tests/v1/api/endpoint.spec.ts @@ -14,6 +14,11 @@ const mockAgent = new MockAgent(); setGlobalDispatcher(mockAgent); const mockPool = mockAgent.get("https://v1-endpoint-host"); +/** + * Create an interceptor for a given HTTP code and response file. + * @param httpCode HTTP status code. + * @param httpResultFile Path to the response file. + */ function setInterceptor(httpCode: number, httpResultFile: string) { const filePath = path.resolve(path.join(V1_RESOURCE_PATH, httpResultFile)); mockPool diff --git a/tests/v1/clientInit.spec.ts b/tests/v1/clientInit.spec.ts index 51b4b12a5..188513adb 100644 --- a/tests/v1/clientInit.spec.ts +++ b/tests/v1/clientInit.spec.ts @@ -8,6 +8,7 @@ describe("Test client initialization", () => { delete process.env.MINDEE_API_KEY; assert.throws( () => { + // eslint-disable-next-line sonarjs/constructor-for-side-effects new Client({}); }, MindeeError diff --git a/tests/v1/extraction/invoiceSplitter.integration.ts b/tests/v1/extraction/invoiceSplitter.integration.ts index 98c51a45c..d09bd897c 100644 --- a/tests/v1/extraction/invoiceSplitter.integration.ts +++ b/tests/v1/extraction/invoiceSplitter.integration.ts @@ -6,49 +6,53 @@ import * as mindee from "@/index.js"; import { InvoiceSplitterV1 } from "@/v1/product/index.js"; import { levenshteinRatio } from "../../testingUtilities.js"; import { V1_PRODUCT_PATH } from "../../index.js"; +import { hasAllOptionalDependencies } from "../../helpers/optionalDeps.js"; -describe("MindeeV1 - Integration - InvoiceSplitterV1 #OptionalDepsRequired", { timeout: 60000 }, () => { - let client: mindee.v1.Client; +const hasOptionals = hasAllOptionalDependencies(); - beforeEach(() => { - client = new mindee.v1.Client(); - }); +describe("MindeeV1 - Integration - InvoiceSplitterV1 #OptionalDepsRequired", + { timeout: 60000, skip: !hasOptionals }, () => { + let client: mindee.v1.Client; - it("should extract invoices in strict mode.", async () => { - const sample = new mindee.PathInput({ - inputPath: path.join(V1_PRODUCT_PATH, "invoice_splitter/default_sample.pdf") + beforeEach(() => { + client = new mindee.v1.Client(); }); - const response = await client.enqueueAndParse( - mindee.v1.product.InvoiceSplitterV1, sample - ); - const invoiceSplitterInference = response.document?.inference; - assert.ok(invoiceSplitterInference instanceof InvoiceSplitterV1); - const invoices = await mindee.v1.extraction.extractInvoices( - sample, + it("should extract invoices in strict mode.", async () => { + const sample = new mindee.PathInput({ + inputPath: path.join(V1_PRODUCT_PATH, "invoice_splitter/default_sample.pdf") + }); + + const response = await client.enqueueAndParse( + mindee.v1.product.InvoiceSplitterV1, sample + ); + const invoiceSplitterInference = response.document?.inference; + assert.ok(invoiceSplitterInference instanceof InvoiceSplitterV1); + const invoices = await mindee.v1.extraction.extractInvoices( + sample, invoiceSplitterInference as InvoiceSplitterV1 - ); - assert.strictEqual(invoices.length, 2); - assert.strictEqual(invoices[0].asInputSource().filename, "invoice_p_0-0.pdf"); - assert.strictEqual(invoices[1].asInputSource().filename, "invoice_p_1-1.pdf"); + ); + assert.strictEqual(invoices.length, 2); + assert.strictEqual(invoices[0].asInputSource().filename, "invoice_p_0-0.pdf"); + assert.strictEqual(invoices[1].asInputSource().filename, "invoice_p_1-1.pdf"); - const invoiceResult = await client.parse( - mindee.v1.product.InvoiceV4, invoices[0].asInputSource() - ); - const testStringRstInvoice = await fs.readFile( - path.join(V1_PRODUCT_PATH, "invoices/response_v4/summary_full_invoice_p1.rst") - ); + const invoiceResult = await client.parse( + mindee.v1.product.InvoiceV4, invoices[0].asInputSource() + ); + const testStringRstInvoice = await fs.readFile( + path.join(V1_PRODUCT_PATH, "invoices/response_v4/summary_full_invoice_p1.rst") + ); - assert.ok( - levenshteinRatio( - invoiceResult.document.toString(), - testStringRstInvoice.toString() - ) >= 0.90 - ); + assert.ok( + levenshteinRatio( + invoiceResult.document.toString(), + testStringRstInvoice.toString() + ) >= 0.90 + ); + }); }); -}); -describe("MindeeV1 - Integration - InvoiceSplitterV1 #OptionalDepsRemoved", function () { +describe("MindeeV1 - Integration - InvoiceSplitterV1 #OptionalDepsRemoved", { skip: hasOptionals }, function () { let client: mindee.v1.Client; beforeEach(() => { diff --git a/tests/v1/extraction/invoiceSplitter.spec.ts b/tests/v1/extraction/invoiceSplitter.spec.ts index 2ebea8300..de992bc60 100644 --- a/tests/v1/extraction/invoiceSplitter.spec.ts +++ b/tests/v1/extraction/invoiceSplitter.spec.ts @@ -7,8 +7,11 @@ import { InvoiceSplitterV1 } from "@/v1/product/index.js"; import { extractInvoices } from "@/v1/extraction/index.js"; import { PathInput } from "@/index.js"; import { V1_PRODUCT_PATH } from "../../index.js"; +import { hasAllOptionalDependencies } from "../../helpers/optionalDeps.js"; -describe("MindeeV1 - Invoice Splitter Extraction #OptionalDepsRequired", () => { +const hasOptionals = hasAllOptionalDependencies(); + +describe("MindeeV1 - Invoice Splitter Extraction #OptionalDepsRequired", { skip: !hasOptionals }, () => { it("should be split into the proper invoices", async () => { const jsonData = await fs.readFile( path.join(V1_PRODUCT_PATH, "invoice_splitter/response_v1/complete.json") diff --git a/tests/v1/extraction/multiReceipts.integration.ts b/tests/v1/extraction/multiReceipts.integration.ts index 2085d99eb..0ef87aa0f 100644 --- a/tests/v1/extraction/multiReceipts.integration.ts +++ b/tests/v1/extraction/multiReceipts.integration.ts @@ -1,93 +1,97 @@ -import { before, describe, it } from "node:test"; -import assert from "node:assert/strict"; -import * as path from "path"; +import { LocalInputSource, PathInput } from "@/input/index.js"; +import { extractReceipts } from "@/v1/extraction/index.js"; import { Client } from "@/v1/index.js"; import { MultiReceiptsDetectorV1, ReceiptV5 } from "@/v1/product/index.js"; -import { extractReceipts } from "@/v1/extraction/index.js"; -import { V1_PRODUCT_PATH } from "../../index.js"; -import { LocalInputSource, PathInput } from "@/input/index.js"; +import assert from "node:assert/strict"; +import { before, describe, it } from "node:test"; import { setTimeout } from "node:timers/promises"; +import * as path from "path"; +import { hasAllOptionalDependencies } from "../../helpers/optionalDeps.js"; +import { V1_PRODUCT_PATH } from "../../index.js"; +const hasOptionals = hasAllOptionalDependencies(); const apiKey = process.env.MINDEE_API_KEY; let client: Client; let sourceDoc: LocalInputSource; -describe("MindeeV1 - Integration - Multi-Receipt Extraction #OptionalDepsRequired", { timeout: 60000 }, () => -{ - describe("A Multi-Receipt PDF", () => { - before(async () => { - sourceDoc = new PathInput({ - inputPath: path.join(V1_PRODUCT_PATH, "multi_receipts_detector/multipage_sample.pdf"), +describe("MindeeV1 - Integration - Multi-Receipt Extraction #OptionalDepsRequired", + { timeout: 60000, skip: !hasOptionals }, () => { + describe("A Multi-Receipt PDF", () => { + before(async () => { + sourceDoc = new PathInput({ + inputPath: path.join(V1_PRODUCT_PATH, "multi_receipts_detector/multipage_sample.pdf"), + }); + await sourceDoc.init(); + client = new Client({ apiKey }); }); - await sourceDoc.init(); - client = new Client({ apiKey }); - }); - it("should send to the server and cut properly", async () => { - const multiReceiptResult = await client.parse(MultiReceiptsDetectorV1, sourceDoc); - assert.strictEqual(multiReceiptResult.document?.inference.prediction.receipts.length, 5); - const extractedReceipts = await extractReceipts(sourceDoc, multiReceiptResult.document!.inference); - assert.strictEqual(extractedReceipts.length, 5); - assert.strictEqual(multiReceiptResult.document?.inference.pages[0].orientation?.value, 0); - assert.strictEqual(multiReceiptResult.document?.inference.pages[1].orientation?.value, 0); - const receiptsResults = []; - for (const extractedReceipt of extractedReceipts) { - const localInput = extractedReceipt.asInputSource(); - receiptsResults.push(await client.parse(ReceiptV5, localInput)); - await setTimeout(1000); - } - const firstPrediction = receiptsResults[0].document.inference.prediction; - assert.strictEqual(firstPrediction.lineItems.length, 5); - assert.strictEqual(firstPrediction.lineItems[0].totalAmount, 70); - assert.strictEqual(firstPrediction.lineItems[1].totalAmount, 12); - assert.strictEqual(firstPrediction.lineItems[2].totalAmount, 14); - assert.strictEqual(firstPrediction.lineItems[3].totalAmount, 11); - assert.strictEqual(firstPrediction.lineItems[4].totalAmount, 5.6); + it("should send to the server and cut properly", async () => { + const multiReceiptResult = await client.parse(MultiReceiptsDetectorV1, sourceDoc); + assert.strictEqual(multiReceiptResult.document?.inference.prediction.receipts.length, 5); + const extractedReceipts = await extractReceipts(sourceDoc, multiReceiptResult.document!.inference); + assert.strictEqual(extractedReceipts.length, 5); + assert.strictEqual(multiReceiptResult.document?.inference.pages[0].orientation?.value, 0); + assert.strictEqual(multiReceiptResult.document?.inference.pages[1].orientation?.value, 0); + const receiptsResults = []; + for (const extractedReceipt of extractedReceipts) { + const localInput = extractedReceipt.asInputSource(); + receiptsResults.push(await client.parse(ReceiptV5, localInput)); + await setTimeout(1000); + } + const firstPrediction = receiptsResults[0].document.inference.prediction; + assert.strictEqual(firstPrediction.lineItems.length, 5); + assert.strictEqual(firstPrediction.lineItems[0].totalAmount, 70); + assert.strictEqual(firstPrediction.lineItems[1].totalAmount, 12); + assert.strictEqual(firstPrediction.lineItems[2].totalAmount, 14); + assert.strictEqual(firstPrediction.lineItems[3].totalAmount, 11); + assert.ok(Math.abs((firstPrediction.lineItems[4].totalAmount ?? NaN) - 5.6) < 1e-9); - const secondPrediction = receiptsResults[1].document.inference.prediction; - assert.strictEqual(secondPrediction.lineItems.length, 7); - assert.strictEqual(secondPrediction.lineItems[0].totalAmount, 6); - assert.strictEqual(secondPrediction.lineItems[1].totalAmount, 11); - assert.strictEqual(secondPrediction.lineItems[2].totalAmount, 67.2); - assert.strictEqual(secondPrediction.lineItems[3].totalAmount, 19.2); - assert.strictEqual(secondPrediction.lineItems[4].totalAmount, 7); - assert.strictEqual(secondPrediction.lineItems[5].totalAmount, 5.5); - assert.strictEqual(secondPrediction.lineItems[6].totalAmount, 36); + const secondPrediction = receiptsResults[1].document.inference.prediction; + assert.strictEqual(secondPrediction.lineItems.length, 7); + assert.strictEqual(secondPrediction.lineItems[0].totalAmount, 6); + assert.strictEqual(secondPrediction.lineItems[1].totalAmount, 11); + assert.ok((Math.abs(secondPrediction.lineItems[2].totalAmount ?? NaN) - 67.2) < 1e-9); + assert.ok((Math.abs(secondPrediction.lineItems[3].totalAmount ?? NaN) - 19.2) < 1e-9); + assert.ok((Math.abs(secondPrediction.lineItems[4].totalAmount ?? NaN) - 7) < 1e-9); + assert.ok((Math.abs(secondPrediction.lineItems[5].totalAmount ?? NaN) - 5.5) < 1e-9); + assert.ok((Math.abs(secondPrediction.lineItems[6].totalAmount ?? NaN) - 36) < 1e-9); - const thirdPrediction = receiptsResults[2].document.inference.prediction; - assert.strictEqual(thirdPrediction.lineItems.length, 1); - assert.strictEqual(thirdPrediction.lineItems[0].totalAmount, 275); + const thirdPrediction = receiptsResults[2].document.inference.prediction; + assert.strictEqual(thirdPrediction.lineItems.length, 1); + assert.strictEqual(thirdPrediction.lineItems[0].totalAmount, 275); - const fourthPrediction = receiptsResults[3].document.inference.prediction; - assert.strictEqual(fourthPrediction.lineItems.length, 2); - assert.strictEqual(fourthPrediction.lineItems[0].totalAmount, 11.5); - assert.strictEqual(fourthPrediction.lineItems[1].totalAmount, 2); + const fourthPrediction = receiptsResults[3].document.inference.prediction; + assert.strictEqual(fourthPrediction.lineItems.length, 2); + assert.strictEqual(fourthPrediction.lineItems[0].totalAmount, 11.5); + assert.strictEqual(fourthPrediction.lineItems[1].totalAmount, 2); - const fifthPrediction = receiptsResults[4].document.inference.prediction; - assert.strictEqual(fifthPrediction.lineItems.length, 1); - assert.strictEqual(fifthPrediction.lineItems[0].totalAmount, 16.5); + const fifthPrediction = receiptsResults[4].document.inference.prediction; + assert.strictEqual(fifthPrediction.lineItems.length, 1); + assert.strictEqual(fifthPrediction.lineItems[0].totalAmount, 16.5); + }); }); - }); - describe("A Single-Receipt Image", () => { - before(async () => { - sourceDoc = new PathInput({ - inputPath: path.join(V1_PRODUCT_PATH, "expense_receipts/default_sample.jpg"), + describe("A Single-Receipt Image", () => { + before(async () => { + sourceDoc = new PathInput({ + inputPath: path.join(V1_PRODUCT_PATH, "expense_receipts/default_sample.jpg"), + }); + await sourceDoc.init(); + client = new Client({ apiKey }); }); - await sourceDoc.init(); - client = new Client({ apiKey }); - }); - it("should send to the server and cut properly", async () => { - const multiReceiptResult = await client.parse(MultiReceiptsDetectorV1, sourceDoc); - assert.strictEqual(multiReceiptResult.document?.inference.prediction.receipts.length, 1); - const receipts = await extractReceipts(sourceDoc, multiReceiptResult.document!.inference); - assert.strictEqual(receipts.length, 1); - const receiptResult = await client.parse(ReceiptV5, receipts[0].asInputSource()); - assert.strictEqual(receiptResult.document.inference.prediction.lineItems.length, 1); - assert.strictEqual(receiptResult.document.inference.prediction.lineItems[0].totalAmount, 10.2); - assert.strictEqual(receiptResult.document.inference.prediction.taxes.length, 1); - assert.strictEqual(receiptResult.document.inference.prediction.taxes[0].value, 1.7); + it("should send to the server and cut properly", async () => { + const multiReceiptResult = await client.parse(MultiReceiptsDetectorV1, sourceDoc); + assert.strictEqual(multiReceiptResult.document?.inference.prediction.receipts.length, 1); + const receipts = await extractReceipts(sourceDoc, multiReceiptResult.document!.inference); + assert.strictEqual(receipts.length, 1); + const receiptResult = await client.parse(ReceiptV5, receipts[0].asInputSource()); + assert.strictEqual(receiptResult.document.inference.prediction.lineItems.length, 1); + assert.ok( + (Math.abs(receiptResult.document.inference.prediction.lineItems[0].totalAmount ?? NaN) - 10.2) < 1e-9 + ); + assert.strictEqual(receiptResult.document.inference.prediction.taxes.length, 1); + assert.ok(Math.abs((receiptResult.document.inference.prediction.taxes[0].value ?? NaN) - 1.7) < 1e-9); + }); }); }); -}); diff --git a/tests/v1/extraction/multiReceipts.spec.ts b/tests/v1/extraction/multiReceipts.spec.ts index 659b00555..56bb13464 100644 --- a/tests/v1/extraction/multiReceipts.spec.ts +++ b/tests/v1/extraction/multiReceipts.spec.ts @@ -7,6 +7,7 @@ import { extractReceipts } from "@/v1/extraction/index.js"; import { PathInput } from "@/index.js"; import { Document } from "@/v1/index.js"; import { RESOURCE_PATH, V1_PRODUCT_PATH } from "../../index.js"; +import { hasAllOptionalDependencies } from "../../helpers/optionalDeps.js"; const dataPath = { complete: path.join(V1_PRODUCT_PATH, "multi_receipts_detector/response_v1/complete.json"), @@ -14,7 +15,9 @@ const dataPath = { completeMultiPage: path.join(V1_PRODUCT_PATH, "multi_receipts_detector/response_v1/multipage_sample.json"), multiPageSample: path.join(V1_PRODUCT_PATH, "multi_receipts_detector/multipage_sample.pdf"), }; -describe("MindeeV1 - Multi-Receipt Extraction #OptionalDepsRequired", () => { +const hasOptionals = hasAllOptionalDependencies(); + +describe("MindeeV1 - Multi-Receipt Extraction #OptionalDepsRequired", { skip: !hasOptionals }, () => { describe("A single-page multi-receipts document", () => { it("should be split properly.", async () => { const jsonDataNA = await fs.readFile(path.resolve(dataPath.complete)); diff --git a/tests/v1/extras/fullTextOcr.spec.ts b/tests/v1/extras/fullTextOcr.spec.ts index f2eadfb25..4c9e4a9cd 100644 --- a/tests/v1/extras/fullTextOcr.spec.ts +++ b/tests/v1/extras/fullTextOcr.spec.ts @@ -8,6 +8,9 @@ import { RESOURCE_PATH } from "../../index.js"; const fullTextOcrDir = path.join(RESOURCE_PATH, "v1/extras/full_text_ocr"); +/** + * Load a Full Text OCR prediction at document level. + */ async function loadDocument() { const jsonData = await fs.readFile(path.join(fullTextOcrDir, "complete.json")); return new AsyncPredictResponse( diff --git a/tests/v1/parsing/standard/amount.spec.ts b/tests/v1/parsing/standard/amount.spec.ts index 1370c2556..650910760 100644 --- a/tests/v1/parsing/standard/amount.spec.ts +++ b/tests/v1/parsing/standard/amount.spec.ts @@ -16,7 +16,7 @@ describe("Test AmountField field", () => { }; const amount = new AmountField({ prediction }); assert.strictEqual(amount.value, 2); - assert.strictEqual(amount.confidence, 0.1); + assert.ok(Math.abs(amount.confidence - 0.1) < 1e-9); assert.strictEqual(amount.toString(), "2.00"); }); @@ -27,7 +27,7 @@ describe("Test AmountField field", () => { }; const amount = new AmountField({ prediction }); assert.strictEqual(amount.value, undefined); - assert.strictEqual(amount.confidence, 0.0); + assert.ok(Math.abs(amount.confidence - 0.0) < 1e-9); assert.strictEqual(amount.toString(), ""); }); }); diff --git a/tests/v1/parsing/standard/field.spec.ts b/tests/v1/parsing/standard/field.spec.ts index 019ae62bd..7c3de18e7 100644 --- a/tests/v1/parsing/standard/field.spec.ts +++ b/tests/v1/parsing/standard/field.spec.ts @@ -16,7 +16,7 @@ describe("Test different inits of Field", () => { }; const field = new Field({ prediction }); assert.strictEqual(field.value, "test"); - assert.strictEqual(field.confidence, 0.1); + assert.ok(Math.abs(field.confidence - 0.1) < 1e-9); assert.ok(field.boundingBox.length > 0); }); @@ -58,13 +58,13 @@ describe("Test different inits of Field", () => { new Field({ prediction: { value: 1, confidence: 0.1 } }), new Field({ prediction: { value: 2, confidence: 0.8 } }), ]; - assert.strictEqual(Field.arrayConfidence(fields), 0.8 * 0.1); + assert.ok(Math.abs(Field.arrayConfidence(fields) - 0.8 * 0.1) < 1e-9); assert.strictEqual(Field.arraySum(fields), 3); const fields2 = [ new Field({ prediction: { value: undefined, confidence: undefined } }), new Field({ prediction: { value: 4, confidence: 0.8 } }), ]; - assert.strictEqual(Field.arrayConfidence(fields2), 0.0); + assert.ok(Math.abs(Field.arrayConfidence(fields2) - 0.0) < 1e-9); assert.strictEqual(Field.arraySum(fields2), 0.0); }); }); diff --git a/tests/v1/parsing/standard/locale.spec.ts b/tests/v1/parsing/standard/locale.spec.ts index c8880f4e6..978e76447 100644 --- a/tests/v1/parsing/standard/locale.spec.ts +++ b/tests/v1/parsing/standard/locale.spec.ts @@ -16,7 +16,7 @@ describe("Test LocaleField field", () => { assert.strictEqual(field.language, "en"); assert.strictEqual(field.country, "uk"); assert.strictEqual(field.currency, "GBP"); - assert.strictEqual(field.confidence, 0.1); + assert.ok(Math.abs(field.confidence - 0.1) < 1e-9); }); it("Should create a LocaleField without the value property", () => { @@ -31,7 +31,7 @@ describe("Test LocaleField field", () => { assert.strictEqual(field.language, "fr"); assert.strictEqual(field.country, "fr"); assert.strictEqual(field.currency, "EUR"); - assert.strictEqual(field.confidence, 0.15); + assert.ok(Math.abs(field.confidence - 0.15) < 1e-9); }); it("Should create a LocaleField with mainly empty fields", () => { diff --git a/tests/v1/parsing/standard/tax.spec.ts b/tests/v1/parsing/standard/tax.spec.ts index c60aa477a..d7f3d4765 100644 --- a/tests/v1/parsing/standard/tax.spec.ts +++ b/tests/v1/parsing/standard/tax.spec.ts @@ -19,8 +19,8 @@ describe("Test Tax field", () => { }; const tax = new TaxField({ prediction, valueKey: "value" }); assert.strictEqual(tax.value, 2); - assert.strictEqual(tax.confidence, 0.1); - assert.strictEqual(tax.rate, 0.2); + assert.ok(Math.abs(tax.confidence - 0.1) < 1e-9); + assert.ok((Math.abs(tax.rate ?? NaN) - 0.2) < 1e-9); assert.strictEqual(tax.boundingBox.length, 4); assert.strictEqual(tax.toString(), "Base: 5.00, Code: QST, Rate (%): 0.20, Amount: 2.00"); }); diff --git a/tests/v1/workflows/workflow.spec.ts b/tests/v1/workflows/workflow.spec.ts index 34197facc..0fa6cb7da 100644 --- a/tests/v1/workflows/workflow.spec.ts +++ b/tests/v1/workflows/workflow.spec.ts @@ -7,6 +7,11 @@ import { RESOURCE_PATH, V1_RESOURCE_PATH } from "../../index.js"; import { Client } from "@/v1/index.js"; import { PathInput } from "@/index.js"; +/** + * Creates an interceptor for a given HTTP code and response file. + * @param httpCode HTTP status code. + * @param jsonFilePath Path to the response file. + */ async function setInterceptor(httpCode: number, jsonFilePath: string): Promise { const mockAgent = new MockAgent(); const mockPool = mockAgent.get("https://v1-workflow-host"); @@ -17,6 +22,12 @@ async function setInterceptor(httpCode: number, jsonFilePath: string): Promise { }); it("should deserialize response correctly when sending a document to an execution", async () => { - const statusCode = 202; - process.env.MINDEE_API_HOST = `v1-dummy-host-${statusCode}`; - const jsonFilePath = path.join(V1_RESOURCE_PATH, "workflows", "success.json"); const mockAgent = await setInterceptor(202, jsonFilePath); const mockedExecution = await executeWorkflow( @@ -63,9 +71,6 @@ describe("MindeeV1 - Workflow executions", () => { it("should deserialize response correctly when sending a document to an execution with priority and alias", async () => { - const statusCode = 202; - process.env.MINDEE_API_HOST = `v1-dummy-host-${statusCode}`; - const jsonFilePath = path.join(V1_RESOURCE_PATH, "workflows", "success_low_priority.json"); const mockAgent = await setInterceptor(200, jsonFilePath); const mockedExecution = await executeWorkflow( diff --git a/tests/v2/client/client.integration.ts b/tests/v2/client/client.integration.ts index c2f1ea9e7..c155935ea 100644 --- a/tests/v2/client/client.integration.ts +++ b/tests/v2/client/client.integration.ts @@ -19,6 +19,10 @@ import * as fs from "node:fs"; import { RESOURCE_PATH, V2_PRODUCT_PATH } from "../../index.js"; import { Extraction } from "@/v2/product/index.js"; +/** + * Check if an error is a MindeeHttpErrorV2. + * @param err Error to check. + */ function check422(err: unknown) { assert.ok(err instanceof MindeeHttpErrorV2); const errObj = err as MindeeHttpErrorV2; @@ -29,6 +33,10 @@ function check422(err: unknown) { assert.ok(Array.isArray(errObj.errors)); } +/** + * Check if an inference has empty active options. + * @param inference Inference to check. + */ function checkEmptyActiveOptions(inference: ExtractionInference) { assert.notStrictEqual(inference.activeOptions, null); assert.equal(inference.activeOptions?.rag, false); diff --git a/tests/v2/client/client.spec.ts b/tests/v2/client/client.spec.ts index 5335af9fe..28caa6026 100644 --- a/tests/v2/client/client.spec.ts +++ b/tests/v2/client/client.spec.ts @@ -17,6 +17,12 @@ function dummyEnvvars(): void { process.env.MINDEE_V2_API_HOST = "v2-client-host"; } +/** + * Injects interceptors for a given status code and response file. + * @param mockPool Mock pool. + * @param statusCode HTTP status code. + * @param filePath Path to the response file. + */ async function setInterceptor(mockPool: Interceptable, statusCode: number, filePath: string): Promise { const fileObj = await fs.readFile(filePath, { encoding: "utf-8" }); mockPool @@ -24,6 +30,9 @@ async function setInterceptor(mockPool: Interceptable, statusCode: number, fileP .reply(statusCode, fileObj); } +/** + * Injects interceptors for all status codes and response files. + */ async function setAllInterceptors(): Promise { const mockAgent = new MockAgent(); const mockPool = mockAgent.get("https://v2-client-host"); diff --git a/tests/v2/client/pollingOptions.spec.ts b/tests/v2/client/pollingOptions.spec.ts index 385cde6b4..32c84465b 100644 --- a/tests/v2/client/pollingOptions.spec.ts +++ b/tests/v2/client/pollingOptions.spec.ts @@ -31,18 +31,21 @@ describe("MindeeV2 - Polling Options", () => { it("should disallow ridiculous values", () => { assert.throws( () => { + // eslint-disable-next-line sonarjs/constructor-for-side-effects new PollingOptions({ delaySec: 0.01 }); }, MindeeConfigurationError ); assert.throws( () => { + // eslint-disable-next-line sonarjs/constructor-for-side-effects new PollingOptions({ initialDelaySec: 0.01 }); }, MindeeConfigurationError ); assert.throws( () => { + // eslint-disable-next-line sonarjs/constructor-for-side-effects new PollingOptions({ maxRetries: 1 }); }, MindeeConfigurationError diff --git a/tests/v2/fileOperations/crop.integration.ts b/tests/v2/fileOperations/crop.integration.ts index 5235e6e51..193125e36 100644 --- a/tests/v2/fileOperations/crop.integration.ts +++ b/tests/v2/fileOperations/crop.integration.ts @@ -8,8 +8,14 @@ import { Crop } from "@/v2/product/crop/index.js"; import { Extraction, ExtractionResponse } from "@/v2/product/extraction/index.js"; import { V2_PRODUCT_PATH, OUTPUT_PATH } from "../../index.js"; import { SimpleField } from "@/v2/parsing/inference/field/index.js"; +import { hasAllOptionalDependencies } from "../../helpers/optionalDeps.js"; +const hasOptionals = hasAllOptionalDependencies(); +/** + * Checks if a findoc response has the expected structure. + * @param findocResponse Response from the findoc model. + */ function checkFindocReturn(findocResponse: ExtractionResponse) { assert.ok(findocResponse.inference.model.id.length > 0); const totalAmount = findocResponse.inference.result.fields.get("total_amount") as SimpleField; @@ -17,111 +23,114 @@ function checkFindocReturn(findocResponse: ExtractionResponse) { assert.ok((totalAmount.value as number) > 0); } -describe("MindeeV2 - Integration - FileOperation - Crop #OptionalDepsRequired", { timeout: 120000 }, () => { - let client: Client; - let cropModelId: string; - let findocModelId: string; - let cropExtractionModelId: string; - - const cropSample = path.join( - V2_PRODUCT_PATH, - "crop", - "default_sample.jpg" - ); - - beforeEach(() => { - const apiKey = process.env["MINDEE_V2_API_KEY"] ?? ""; - cropModelId = process.env["MINDEE_V2_SE_TESTS_CROP_MODEL_ID"] ?? ""; - findocModelId = process.env["MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID"] ?? ""; - cropExtractionModelId = process.env["MINDEE_V2_SE_TESTS_CROP_EXTRACTION_MODEL_ID"] ?? ""; - - client = new Client({ apiKey: apiKey, debug: true }); - }); +describe("MindeeV2 - Integration - FileOperation - Crop #OptionalDepsRequired", + { timeout: 120000, skip: !hasOptionals }, () => { + let client: Client; + let cropModelId: string; + let findocModelId: string; + let cropExtractionModelId: string; + + const cropSample = path.join( + V2_PRODUCT_PATH, + "crop", + "default_sample.jpg" + ); - after(() => { - const file1 = path.join(OUTPUT_PATH, "crop_001.jpg"); - const file2 = path.join(OUTPUT_PATH, "crop_002.jpg"); - if (fs.existsSync(file1)) fs.rmSync(file1); - if (fs.existsSync(file2)) fs.rmSync(file2); - }); + beforeEach(() => { + const apiKey = process.env["MINDEE_V2_API_KEY"] ?? ""; + cropModelId = process.env["MINDEE_V2_SE_TESTS_CROP_MODEL_ID"] ?? ""; + findocModelId = process.env["MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID"] ?? ""; + cropExtractionModelId = process.env["MINDEE_V2_SE_TESTS_CROP_EXTRACTION_MODEL_ID"] ?? ""; - it("extracts crops from image correctly", async () => { - const cropInput = new PathInput({ inputPath: cropSample }); + client = new Client({ apiKey: apiKey, debug: true }); + }); - const cropParams = { modelId: cropModelId }; + after(() => { + const file1 = path.join(OUTPUT_PATH, "crop_001.jpg"); + const file2 = path.join(OUTPUT_PATH, "crop_002.jpg"); + if (fs.existsSync(file1)) fs.rmSync(file1); + if (fs.existsSync(file2)) fs.rmSync(file2); + }); - const response = await client.enqueueAndGetResult( - Crop, cropInput, cropParams - ); + it("extracts crops from image correctly", async () => { + const cropInput = new PathInput({ inputPath: cropSample }); - assert.equal(response.inference.result.crops.length, 2); + const cropParams = { modelId: cropModelId }; - const extractedImages = await response.inference.result.extractFromInputSource(cropInput); + const response = await client.enqueueAndGetResult( + Crop, cropInput, cropParams + ); - assert.equal(extractedImages.length, 2); - assert.equal(extractedImages[0].filename, "default_sample.jpg_page0-0.jpg"); - assert.equal(extractedImages[1].filename, "default_sample.jpg_page0-1.jpg"); + assert.equal(response.inference.result.crops.length, 2); - const extractionInput = extractedImages[0].asInputSource(); - const findocParams = { modelId: findocModelId }; + const extractedImages = await response.inference.result.extractFromInputSource(cropInput); - const invoice0 = await client.enqueueAndGetResult( - Extraction, extractionInput, findocParams - ); + assert.equal(extractedImages.length, 2); + assert.strictEqual(extractedImages[0].filename, "default_sample.jpg_page0-0.jpg", "first crop filename"); + assert.strictEqual(extractedImages[1].filename, "default_sample.jpg_page0-1.jpg", "second crop filename"); - checkFindocReturn(invoice0); + const extractionInput = extractedImages[0].asInputSource(); + const findocParams = { modelId: findocModelId }; - const file1Path = path.join(OUTPUT_PATH, "crop_001.jpg"); - const file2Path = path.join(OUTPUT_PATH, "crop_002.jpg"); + const invoice0 = await client.enqueueAndGetResult( + Extraction, extractionInput, findocParams + ); - fs.writeFileSync(file1Path, extractedImages[0].buffer); - fs.writeFileSync(file2Path, extractedImages[1].buffer); + checkFindocReturn(invoice0); - const stat1 = fs.statSync(file1Path); - assert.ok(stat1.size >= 3100000 && stat1.size <= 3200000); + const file1Path = path.join(OUTPUT_PATH, "crop_001.jpg"); + const file2Path = path.join(OUTPUT_PATH, "crop_002.jpg"); - const stat2 = fs.statSync(file2Path); - assert.ok(stat2.size >= 3200000 && stat2.size <= 3300000); - }); + fs.writeFileSync(file1Path, extractedImages[0].buffer); + fs.writeFileSync(file2Path, extractedImages[1].buffer); - it("filled image – crop and extraction must succeed", async () => { - const cropInput = new PathInput({ inputPath: cropSample }); + const stat1 = fs.statSync(file1Path); + assert.ok(stat1.size >= 3100000); + assert.ok(stat1.size <= 3200000); - const cropParams = { - modelId: cropExtractionModelId, - alias: "nodejs_integration-test_crop_multipage", - }; + const stat2 = fs.statSync(file2Path); + assert.ok(stat2.size >= 3200000); + assert.ok(stat2.size <= 3300000); + }); - const response = await client.enqueueAndGetResult( - Crop, cropInput, cropParams - ); - assert.ok(response); + it("filled image – crop and extraction must succeed", async () => { + const cropInput = new PathInput({ inputPath: cropSample }); - const inference = response.inference; - assert.ok(inference); + const cropParams = { + modelId: cropExtractionModelId, + alias: "nodejs_integration-test_crop_multipage", + }; - const file = inference.file; - assert.ok(file); - assert.strictEqual(file.name, "default_sample.jpg"); - assert.strictEqual(file.pageCount, 1); + const response = await client.enqueueAndGetResult( + Crop, cropInput, cropParams + ); + assert.ok(response); - assert.ok(inference.model); - assert.strictEqual(inference.model.id, cropExtractionModelId); + const inference = response.inference; + assert.ok(inference); - const result = inference.result; - assert.ok(result); - assert.strictEqual(result.crops.length, 2); + const file = inference.file; + assert.ok(file); + assert.strictEqual(file.name, "default_sample.jpg"); + assert.strictEqual(file.pageCount, 1); - const crop0 = result.crops[0]; - assert.strictEqual(crop0.objectType, "receipt"); - assert.ok(crop0.location.polygon); - assert.strictEqual(crop0.location.page, 0); + assert.ok(inference.model); + assert.strictEqual(inference.model.id, cropExtractionModelId); - const extractionResponse0 = crop0.extractionResponse!; - assert.ok(extractionResponse0); - assert.strictEqual( - extractionResponse0.inference.result.fields.getSimpleField("supplier_name").stringValue, - "CHEZ ALAIN MIAM MIAM" - ); + const result = inference.result; + assert.ok(result); + assert.strictEqual(result.crops.length, 2); + + const crop0 = result.crops[0]; + assert.strictEqual(crop0.objectType, "receipt"); + assert.ok(crop0.location.polygon); + assert.strictEqual(crop0.location.page, 0); + + const extractionResponse0 = crop0.extractionResponse!; + assert.ok(extractionResponse0); + assert.strictEqual( + extractionResponse0.inference.result.fields.getSimpleField("supplier_name").stringValue, + "CHEZ ALAIN MIAM MIAM" + ); + }); }); -}); diff --git a/tests/v2/fileOperations/crop.spec.ts b/tests/v2/fileOperations/crop.spec.ts index 819a63bd1..00bf25747 100644 --- a/tests/v2/fileOperations/crop.spec.ts +++ b/tests/v2/fileOperations/crop.spec.ts @@ -6,14 +6,21 @@ import { LocalResponse } from "@/v2/parsing/index.js"; import { CropResponse } from "@/v2/product/crop/cropResponse.js"; import type * as pdfLibTypes from "@cantoo/pdf-lib"; import assert from "node:assert/strict"; -import { describe, it } from "node:test"; +import { before, describe, it } from "node:test"; import path from "path"; import type * as SharpTypes from "sharp"; import { V2_PRODUCT_PATH } from "../../index.js"; +import { hasAllOptionalDependencies } from "../../helpers/optionalDeps.js"; const cropPath = path.join(V2_PRODUCT_PATH, "crop"); let pdfLib: typeof pdfLibTypes | null = null; +const hasOptionals = hasAllOptionalDependencies(); +let sharp: any; +/** + * Loads the pdf-lib library if it is not already loaded. + * @returns The pdf-lib library. + */ async function getPdfLib(): Promise { if (!pdfLib) { const pdfLibImport = await loadOptionalDependency("@cantoo/pdf-lib", "Text Embedding"); @@ -22,6 +29,11 @@ async function getPdfLib(): Promise { return pdfLib!; } +/** + * Loads a V2 crop response from a local file. + * @param resourcePath Path to the local file. + * @returns The loaded crop response. + */ async function loadV2Crop(resourcePath: string): Promise { const localResponse = new LocalResponse(resourcePath); return localResponse.deserializeResponse(CropResponse); @@ -44,11 +56,13 @@ async function getFileDimensions(buffer: Buffer, sharpInstance: any) { } -describe("MindeeV2 - FileOperation - Crop #OptionalDepsRequired", async () => { - const sharpLoaded = await loadOptionalDependency("sharp", "Image compression"); - const sharp = (sharpLoaded as any).default || sharpLoaded; +describe("MindeeV2 - FileOperation - Crop #OptionalDepsRequired", { skip: !hasOptionals }, () => { + before(async () => { + const sharpLoaded = await loadOptionalDependency("sharp", "Image compression"); + sharp = (sharpLoaded as any).default || sharpLoaded; + }); - await it("should process single page crop correctly", async () => { + it("should process single page crop correctly", async () => { const inputSample = new PathInput({ inputPath: path.join(cropPath, "default_sample.jpg") @@ -70,7 +84,7 @@ describe("MindeeV2 - FileOperation - Crop #OptionalDepsRequired", async () => { assert.ok(localExtract.buffer.equals(extractedCrops[0].buffer)); }); - await it("should extract and still work with lower quality", async () => { + it("should extract and still work with lower quality", async () => { const inputSample = new PathInput({ inputPath: path.join(cropPath, "default_sample.jpg") @@ -92,7 +106,7 @@ describe("MindeeV2 - FileOperation - Crop #OptionalDepsRequired", async () => { assert.ok(localExtract.buffer.equals(extractedCrops[0].buffer)); }); - await it("should process multi page receipt crops correctly", async () => { + it("should process multi page receipt crops correctly", async () => { const inputSample = new PathInput({ inputPath: path.join(cropPath, "multipage_sample.pdf") diff --git a/tests/v2/fileOperations/split.integration.ts b/tests/v2/fileOperations/split.integration.ts index 5d34010bb..2d352344c 100644 --- a/tests/v2/fileOperations/split.integration.ts +++ b/tests/v2/fileOperations/split.integration.ts @@ -2,6 +2,7 @@ import { after, beforeEach, describe, it } from "node:test"; import assert from "node:assert/strict"; import path from "node:path"; import * as fs from "node:fs"; +import { fileURLToPath } from "node:url"; import { Client, PathInput } from "@/index.js"; import { Split } from "@/v2/product/split/index.js"; @@ -9,8 +10,15 @@ import { Extraction, ExtractionResponse } from "@/v2/product/extraction/index.js import { SplitFiles } from "@/v2/fileOperations/splitFiles.js"; import { V2_PRODUCT_PATH } from "../../index.js"; import { SimpleField } from "@/v2/parsing/inference/field/index.js"; -const OUTPUT_DIR = path.join(__dirname, "output"); - +import { hasAllOptionalDependencies } from "../../helpers/optionalDeps.js"; +const dirname = path.dirname(fileURLToPath(import.meta.url)); +const OUTPUT_DIR = path.join(dirname, "output"); +const hasOptionals = hasAllOptionalDependencies(); + +/** + * Checks if a findoc response has the expected structure. + * @param findocResponse Response from the findoc model. + */ function checkFindocReturn(findocResponse: ExtractionResponse) { assert.ok(findocResponse.inference.model.id.length > 0); const totalAmount = findocResponse.inference.result.fields.get("total_amount") as SimpleField; @@ -18,72 +26,74 @@ function checkFindocReturn(findocResponse: ExtractionResponse) { assert.ok((totalAmount.value as number) > 0); } -describe("MindeeV2 - Integration - Product - Split #OptionalDepsRequired", { timeout: 120000 }, () => { - let client: Client; - let splitModelId: string; - let findocModelId: string; +describe("MindeeV2 - Integration - Product - Split #OptionalDepsRequired", + { timeout: 120000, skip: !hasOptionals }, () => { + let client: Client; + let splitModelId: string; + let findocModelId: string; - const splitSample = path.join( - V2_PRODUCT_PATH, - "split", - "default_sample.pdf" - ); + const splitSample = path.join( + V2_PRODUCT_PATH, + "split", + "default_sample.pdf" + ); - beforeEach(() => { - const apiKey = process.env["MINDEE_V2_API_KEY"] ?? ""; - splitModelId = process.env["MINDEE_V2_SE_TESTS_SPLIT_MODEL_ID"] ?? ""; - findocModelId = process.env["MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID"] ?? ""; + beforeEach(() => { + const apiKey = process.env["MINDEE_V2_API_KEY"] ?? ""; + splitModelId = process.env["MINDEE_V2_SE_TESTS_SPLIT_MODEL_ID"] ?? ""; + findocModelId = process.env["MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID"] ?? ""; - client = new Client({ apiKey: apiKey, debug: true }); - }); + client = new Client({ apiKey: apiKey, debug: true }); + fs.mkdirSync(OUTPUT_DIR, { recursive: true }); + }); - after(() => { - const file1 = path.join(OUTPUT_DIR, "split_001.pdf"); - const file2 = path.join(OUTPUT_DIR, "split_002.pdf"); - if (fs.existsSync(file1)) fs.rmSync(file1); - if (fs.existsSync(file2)) fs.rmSync(file2); - }); + after(() => { + const file1 = path.join(OUTPUT_DIR, "split_001.pdf"); + const file2 = path.join(OUTPUT_DIR, "split_002.pdf"); + if (fs.existsSync(file1)) fs.rmSync(file1); + if (fs.existsSync(file2)) fs.rmSync(file2); + }); - it("extracts splits from pdf correctly", async () => { - const splitInput = new PathInput({ inputPath: splitSample }); + it("extracts splits from pdf correctly", async () => { + const splitInput = new PathInput({ inputPath: splitSample }); - const splitParams = { modelId: splitModelId }; + const splitParams = { modelId: splitModelId }; - const response: any = await client.enqueueAndGetResult( - Split, splitInput, splitParams - ); + const response = await client.enqueueAndGetResult( + Split, splitInput, splitParams + ); - assert.equal(response.inference.file.pageCount, 2); + assert.equal(response.inference.file.pageCount, 2); - const extractedPdfs: SplitFiles = await response.extractFromFile(splitInput); + const extractedPdfs: SplitFiles = await response.inference.result.extractFromInputSource(splitInput); - assert.equal(extractedPdfs.length, 2); - assert.equal(extractedPdfs[0].filename, "default_sample_page_001-001.pdf"); - assert.equal(extractedPdfs[1].filename, "default_sample_page_002-002.pdf"); + assert.equal(extractedPdfs.length, 2); + assert.equal(extractedPdfs[0].filename, "default_sample_page_001-001.pdf"); + assert.equal(extractedPdfs[1].filename, "default_sample_page_002-002.pdf"); - const extractionInput = extractedPdfs[0].asInputSource(); + const extractionInput = extractedPdfs[0].asInputSource(); - const findocParams = { modelId: findocModelId }; + const findocParams = { modelId: findocModelId }; - const invoice0 = await client.enqueueAndGetResult( - Extraction, extractionInput, findocParams - ); + const invoice0 = await client.enqueueAndGetResult( + Extraction, extractionInput, findocParams + ); - checkFindocReturn(invoice0 as ExtractionResponse); + checkFindocReturn(invoice0 as ExtractionResponse); - const file1Path = path.join(OUTPUT_DIR, "split_001.pdf"); - const file2Path = path.join(OUTPUT_DIR, "split_002.pdf"); + const file1Path = path.join(OUTPUT_DIR, "split_001.pdf"); + const file2Path = path.join(OUTPUT_DIR, "split_002.pdf"); - await extractedPdfs[0].saveToFileAsync(file1Path); - await extractedPdfs[1].saveToFileAsync(file2Path); + await extractedPdfs[0].saveToFileAsync(file1Path); + await extractedPdfs[1].saveToFileAsync(file2Path); - const inputSource1 = new PathInput({ inputPath: file1Path }); - const pageCount1 = await inputSource1.getPageCount(); - assert.equal(pageCount1, extractedPdfs[0].pageCount); + const inputSource1 = new PathInput({ inputPath: file1Path }); + const pageCount1 = await inputSource1.getPageCount(); + assert.equal(pageCount1, extractedPdfs[0].pageCount); - const inputSource2 = new PathInput({ inputPath: file1Path }); - const pageCount2 = await inputSource2.getPageCount(); - assert.equal(pageCount2, extractedPdfs[1].pageCount); + const inputSource2 = new PathInput({ inputPath: file1Path }); + const pageCount2 = await inputSource2.getPageCount(); + assert.equal(pageCount2, extractedPdfs[1].pageCount); + }); }); -}); diff --git a/tests/v2/fileOperations/split.spec.ts b/tests/v2/fileOperations/split.spec.ts index 89bad7b1d..0e85a00a9 100644 --- a/tests/v2/fileOperations/split.spec.ts +++ b/tests/v2/fileOperations/split.spec.ts @@ -6,16 +6,24 @@ import assert from "node:assert/strict"; import { describe, it } from "node:test"; import path from "path"; import { V2_PRODUCT_PATH } from "../../index.js"; +import { hasAllOptionalDependencies } from "../../helpers/optionalDeps.js"; const splitPath = path.join(V2_PRODUCT_PATH, "split"); const financialDocumentPath = path.join(V2_PRODUCT_PATH, "extraction", "financial_document"); +/** + * Loads a V2 split response from a local file. + * @param resourcePath Path to the local file. + * @returns The loaded split response. + */ async function loadV2Split(resourcePath: string): Promise { const localResponse = new LocalResponse(resourcePath); return localResponse.deserializeResponse(SplitResponse); } -describe("MindeeV2 - Product - SplitResponse #OptionalDepsRequired", async () => { +const hasOptionals = hasAllOptionalDependencies(); + +describe("MindeeV2 - Product - SplitResponse #OptionalDepsRequired", { skip: !hasOptionals }, async () => { await it("should process single page split correctly", async () => { const inputSample = new PathInput({ diff --git a/tests/v2/parsing/job.spec.ts b/tests/v2/parsing/job.spec.ts index 31340f4d5..4c7a9f3fb 100644 --- a/tests/v2/parsing/job.spec.ts +++ b/tests/v2/parsing/job.spec.ts @@ -11,6 +11,11 @@ import { V2_RESOURCE_PATH } from "../../index.js"; const jobPath = path.join(V2_RESOURCE_PATH, "job"); +/** + * Loads a V2 job response from a local file. + * @param resourcePath Path to the local file. + * @returns The loaded job response. + */ async function loadV2Job(resourcePath: string): Promise { const localResponse = new LocalResponse(resourcePath); await localResponse.init(); diff --git a/tests/v2/parsing/localResponse.spec.ts b/tests/v2/parsing/localResponse.spec.ts index 798b21b3b..5eccd53dd 100644 --- a/tests/v2/parsing/localResponse.spec.ts +++ b/tests/v2/parsing/localResponse.spec.ts @@ -12,6 +12,10 @@ const signature: string = "e51bdf80f1a08ed44ee161100fc30a25cb35b4ede671b0a575dc9 const dummySecretKey: string = "ogNjY44MhvKPGTtVsI8zG82JqWQa68woYQH"; const filePath: string = path.join(V2_PRODUCT_PATH, "extraction/standard_field_types.json"); +/** + * Asserts that a local response is valid. + * @param localResponse The local response to validate. + */ async function assertLocalResponse(localResponse: LocalResponse) { await localResponse.init(); assert.notStrictEqual(localResponse.asDict(), null); diff --git a/tests/v2/product/crop.spec.ts b/tests/v2/product/crop.spec.ts index b6df72db3..e2af3b7b3 100644 --- a/tests/v2/product/crop.spec.ts +++ b/tests/v2/product/crop.spec.ts @@ -40,14 +40,14 @@ describe("MindeeV2 - Crop Response", async () => { const polygon: Polygon = firstCrop.location.polygon!; assert.strictEqual(polygon.length, 4); assert.strictEqual(polygon.length, 4); - assert.strictEqual(polygon[0][0], 0.15); - assert.strictEqual(polygon[0][1], 0.254); - assert.strictEqual(polygon[1][0], 0.85); - assert.strictEqual(polygon[1][1], 0.254); - assert.strictEqual(polygon[2][0], 0.85); - assert.strictEqual(polygon[2][1], 0.947); - assert.strictEqual(polygon[3][0], 0.15); - assert.strictEqual(polygon[3][1], 0.947); + assert.ok(Math.abs(polygon[0][0] - 0.15) < 1e-9); + assert.ok(Math.abs(polygon[0][1] - 0.254) < 1e-9); + assert.ok(Math.abs(polygon[1][0] - 0.85) < 1e-9); + assert.ok(Math.abs(polygon[1][1] - 0.254) < 1e-9); + assert.ok(Math.abs(polygon[2][0] - 0.85) < 1e-9); + assert.ok(Math.abs(polygon[2][1] - 0.947) < 1e-9); + assert.ok(Math.abs(polygon[3][0] - 0.15) < 1e-9); + assert.ok(Math.abs(polygon[3][1] - 0.947) < 1e-9); const rstString = await fs.readFile( path.join(V2_PRODUCT_PATH, "crop", "crop_single.rst"), "utf8" @@ -84,14 +84,14 @@ describe("MindeeV2 - Crop Response", async () => { assert.strictEqual(firstCrop.location.page, 0); const firstPolygon: Polygon = firstCrop.location.polygon!; assert.strictEqual(firstPolygon.length, 4); - assert.strictEqual(firstPolygon[0][0], 0.214); - assert.strictEqual(firstPolygon[0][1], 0.079); - assert.strictEqual(firstPolygon[1][0], 0.476); - assert.strictEqual(firstPolygon[1][1], 0.079); - assert.strictEqual(firstPolygon[2][0], 0.476); - assert.strictEqual(firstPolygon[2][1], 0.979); - assert.strictEqual(firstPolygon[3][0], 0.214); - assert.strictEqual(firstPolygon[3][1], 0.979); + assert.ok(Math.abs(firstPolygon[0][0] - 0.214) < 1e-9); + assert.ok(Math.abs(firstPolygon[0][1] - 0.079) < 1e-9); + assert.ok(Math.abs(firstPolygon[1][0] - 0.476) < 1e-9); + assert.ok(Math.abs(firstPolygon[1][1] - 0.079) < 1e-9); + assert.ok(Math.abs(firstPolygon[2][0] - 0.476) < 1e-9); + assert.ok(Math.abs(firstPolygon[2][1] - 0.979) < 1e-9); + assert.ok(Math.abs(firstPolygon[3][0] - 0.214) < 1e-9); + assert.ok(Math.abs(firstPolygon[3][1] - 0.979) < 1e-9); // Validate second crop item const secondCrop: crop.CropItem = crops[1]; @@ -99,14 +99,14 @@ describe("MindeeV2 - Crop Response", async () => { assert.strictEqual(secondCrop.location.page, 0); const secondPolygon: Polygon = secondCrop.location.polygon!; assert.strictEqual(secondPolygon.length, 4); - assert.strictEqual(secondPolygon[0][0], 0.547); - assert.strictEqual(secondPolygon[0][1], 0.15); - assert.strictEqual(secondPolygon[1][0], 0.862); - assert.strictEqual(secondPolygon[1][1], 0.15); - assert.strictEqual(secondPolygon[2][0], 0.862); - assert.strictEqual(secondPolygon[2][1], 0.97); - assert.strictEqual(secondPolygon[3][0], 0.547); - assert.strictEqual(secondPolygon[3][1], 0.97); + assert.ok(Math.abs(secondPolygon[0][0] - 0.547) < 1e-9); + assert.ok(Math.abs(secondPolygon[0][1] - 0.15) < 1e-9); + assert.ok(Math.abs(secondPolygon[1][0] - 0.862) < 1e-9); + assert.ok(Math.abs(secondPolygon[1][1] - 0.15) < 1e-9); + assert.ok(Math.abs(secondPolygon[2][0] - 0.862) < 1e-9); + assert.ok(Math.abs(secondPolygon[2][1] - 0.97) < 1e-9); + assert.ok(Math.abs(secondPolygon[3][0] - 0.547) < 1e-9); + assert.ok(Math.abs(secondPolygon[3][1] - 0.97) < 1e-9); const rstString = await fs.readFile( path.join(V2_PRODUCT_PATH, "crop", "crop_multiple.rst"), "utf8" diff --git a/tests/v2/product/extraction.spec.ts b/tests/v2/product/extraction.spec.ts index 1ebb115dc..d3075188c 100644 --- a/tests/v2/product/extraction.spec.ts +++ b/tests/v2/product/extraction.spec.ts @@ -1,16 +1,11 @@ -import path from "path"; -import assert from "node:assert/strict"; -import { describe, it } from "node:test"; -import { promises as fs } from "node:fs"; import { Polygon } from "@/geometry/index.js"; -import { - FieldConfidence, - ListField, - ObjectField, - SimpleField, -} from "@/v2/parsing/inference/field/index.js"; import { field } from "@/v2/parsing/index.js"; +import { FieldConfidence, ListField, ObjectField, SimpleField } from "@/v2/parsing/inference/field/index.js"; import { ExtractionResponse } from "@/v2/product/index.js"; +import assert from "node:assert/strict"; +import { promises as fs } from "node:fs"; +import { describe, it } from "node:test"; +import path from "path"; import { V2_PRODUCT_PATH } from "../../index.js"; import { loadV2Response } from "./utils.js"; @@ -188,8 +183,13 @@ describe("MindeeV2 - Extraction Response", async () => { assert.ok(fields.get("field_simple_float") instanceof SimpleField); const simpleFieldFloat = fields.getSimpleField("field_simple_float"); - assert.strictEqual(simpleFieldFloat.value, 1.1); - assert.strictEqual(simpleFieldFloat.numberValue, 1.1); + const rawVal = simpleFieldFloat.value; + const parsed = typeof rawVal === "string" ? parseFloat(rawVal) : rawVal; + const floatValue: number = typeof parsed === "number" && !Number.isNaN(parsed) + ? parsed + : 1e-9; + assert.ok(Math.abs(floatValue - 1.1) < 1e-9); + assert.ok((Math.abs(simpleFieldFloat.numberValue ?? NaN) - 1.1) < 1e-9); assert.strictEqual(simpleFieldFloat.confidence, FieldConfidence.High); assert.throws(() => simpleFieldFloat.stringValue, /Value is not a string/); assert.throws(() => simpleFieldFloat.booleanValue, /Value is not a boolean/); @@ -353,17 +353,17 @@ describe("MindeeV2 - Extraction Response", async () => { assert.strictEqual(polygon[0].length, 2); - assert.strictEqual(polygon[0][0], 0.948979073166918); - assert.strictEqual(polygon[0][1], 0.23097924535067715); + assert.ok(Math.abs(polygon[0][0] - 0.948979073166918) < 1e-9); + assert.ok(Math.abs(polygon[0][1] - 0.23097924535067715) < 1e-9); - assert.strictEqual(polygon[1][0], 0.85422); - assert.strictEqual(polygon[1][1], 0.230072); + assert.ok(Math.abs(polygon[1][0] - 0.85422) < 1e-9); + assert.ok(Math.abs(polygon[1][1] - 0.230072) < 1e-9); - assert.strictEqual(polygon[2][0], 0.8540899268330819); - assert.strictEqual(polygon[2][1], 0.24365775464932288); + assert.ok(Math.abs(polygon[2][0] - 0.8540899268330819) < 1e-9); + assert.ok(Math.abs(polygon[2][1] - 0.24365775464932288) < 1e-9); - assert.strictEqual(polygon[3][0], 0.948849); - assert.strictEqual(polygon[3][1], 0.244565); + assert.ok(Math.abs(polygon[3][0] - 0.948849) < 1e-9); + assert.ok(Math.abs(polygon[3][1] - 0.244565) < 1e-9); const eqConfidenceEnum = dateField.confidence === FieldConfidence.Medium; assert.ok(eqConfidenceEnum); diff --git a/tests/v2/product/utils.ts b/tests/v2/product/utils.ts index a789ba1f7..a872111e3 100644 --- a/tests/v2/product/utils.ts +++ b/tests/v2/product/utils.ts @@ -1,6 +1,12 @@ import { BaseResponse, ResponseConstructor } from "@/v2/parsing/index.js"; import { LocalResponse } from "@/v2/index.js"; +/** + * Loads a V2 response from a local file. + * @param responseClass The class of the response to load. + * @param resourcePath Path to the local file. + * @returns The loaded response. + */ export async function loadV2Response( responseClass: ResponseConstructor, resourcePath: string