diff --git a/.github/workflows/favorites-quality.yml b/.github/workflows/favorites-quality.yml new file mode 100644 index 0000000000..960b2e957e --- /dev/null +++ b/.github/workflows/favorites-quality.yml @@ -0,0 +1,77 @@ +name: Favorites Contract Quality + +on: + workflow_dispatch: + pull_request: + branches: [dev, canary] + paths: + - 'packages/data-schemas/src/contracts/favorite.ts' + - 'packages/data-schemas/src/methods/favorite*.ts' + - 'packages/data-schemas/src/methods/index.ts' + - 'packages/data-schemas/src/models/favorite.ts' + - 'packages/data-schemas/src/schema/favorite.ts' + - 'packages/data-schemas/src/types/favorite.ts' + - 'packages/data-schemas/src/models/plugins/tenantIsolation.ts' + - 'packages/data-schemas/src/tenant/policy.ts' + - 'packages/data-schemas/src/config/tenantContext.ts' + - 'packages/data-schemas/src/index.ts' + - 'packages/data-schemas/package.json' + - 'packages/data-schemas/*config*' + - 'packages/data-schemas/jest.globalSetup.mjs' + - 'config/jest.*' + - 'scripts/crap*.mts' + - 'package.json' + - 'package-lock.json' + - '.github/workflows/favorites-quality.yml' + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + favorites: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-node@v5 + with: + node-version: '24.22.1' + cache: npm + - uses: actions/cache@v5 + with: + path: | + node_modules + api/node_modules + packages/api/node_modules + packages/data-provider/node_modules + packages/data-schemas/node_modules + key: node-modules-backend-${{ runner.os }}-24.22.1-${{ hashFiles('package-lock.json') }} + id: dependencies + - name: Install dependencies + if: steps.dependencies.outputs.cache-hit != 'true' + run: npm ci + - name: Build shared types + run: npm run build:data-provider + - name: Typecheck the advisory reporter + run: >- + npx --no-install tsc --noEmit --target ES2023 --module NodeNext + --moduleResolution NodeNext --allowImportingTsExtensions --skipLibCheck + --strict --types node scripts/crap.mts scripts/crap.test.mts + - name: Verify the advisory reporter + run: node --test scripts/crap.test.mts + - name: Favorites contracts, compatibility, and CRAP report + run: npm run test:quality:favorites --workspace packages/data-schemas + - name: Mutate the real favorites implementation + run: npm run test:mutations:favorites --workspace packages/data-schemas + - name: Upload advisory reports + if: always() + uses: actions/upload-artifact@v6 + with: + name: favorites-quality-${{ github.sha }} + path: packages/data-schemas/coverage/favorites/ + if-no-files-found: warn + retention-days: 14 diff --git a/.gitignore b/.gitignore index c56772c2c8..be2cd8a126 100644 --- a/.gitignore +++ b/.gitignore @@ -196,3 +196,6 @@ scripts/activity-labels/results/ # Chrome user-data directories a Lighthouse run leaves in the repository root when the browser # is handed a Windows profile path from WSL: the whole path arrives as one directory name. /*lighthouse.[0-9]*/ + +# Mutation testing sandboxes +.stryker-tmp/ diff --git a/package-lock.json b/package-lock.json index 0ed9dfa3ca..4a27c2e972 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7741,6 +7741,363 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@inquirer/ansi": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.10.tgz", + "integrity": "sha512-dwrc+1aqVZpHNkNDswXGtn1typ+Pxpo5FHZRyBw1le7onEJUkPUkYOY1FIFC1LvFMYUqqLuWBwElhAmencL7MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.2.8.tgz", + "integrity": "sha512-NY/S1EalQhlonqhwwsarrcnkH7s9ByQuvjWYdk1jB2Gb9EPQJjKkhCTWjM3yQ0UpHaLm1XbIL89b+32G8rhO5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.10", + "@inquirer/core": "^12.0.6", + "@inquirer/figures": "^2.0.10", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.3.5.tgz", + "integrity": "sha512-BJ59BOyURiZiAxdr9AuRu38/k+rV+DcTIM2fUF/c/BXrab6H8zMZTTOl3LgkSJH96Xob2vOJxDiBkHiz32Xx2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.6", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "12.0.6", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-12.0.6.tgz", + "integrity": "sha512-5yxn2LsJ8Tu4ei2a+yNkmFRhM/cb+JMEsEs/AW6CCOnBnpMy4IjYiPMFyHtYq8weOmlTyqqCx40y0DsD7OsEPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.10", + "@inquirer/figures": "^2.0.10", + "@inquirer/type": "4.2.1", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@inquirer/editor": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.3.6.tgz", + "integrity": "sha512-Z5SOeRC5VNkINfKN4rY8BSaYKb3GYi8iSEqZZRqogsE2GhBBToQ0Xw25SbI+4bcatLzU009mZ5d9EuJNiJuUCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.6", + "@inquirer/external-editor": "^3.0.7", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.1.8.tgz", + "integrity": "sha512-dMEnTkpLqzM/sbILwL7QJgx9iX5hoEM8+bh6shgwd9mYJamdOnth7y2mMaFd08Glj9AXEFKllWcGYyBqnJsHrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.6", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-3.0.7.tgz", + "integrity": "sha512-enNqTJpojgISCG+a5acjnfJ02hcRTFUBVm8uXiCY40PRPZtSzFnNLskOTU+kDUUEODYma90syxuea1rRtUFh0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.2" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.10.tgz", + "integrity": "sha512-51elrUJV3jZpvD0ygz8+U10Kc6+Lzqln+/tYmnbhy6jo5oXpE6hMCcv+OTBC0enwJXsb3JIuT3VtEinRNCSK2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + } + }, + "node_modules/@inquirer/input": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.1.9.tgz", + "integrity": "sha512-2Vd5T7lTxfgJMpAd3aOu6bQpbUnCU7tBgvTQIZkP/7juJzmRmTM2t5XfOCuirFduWLTSJI/WZp32Cq1E01TVeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.6", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.2.6.tgz", + "integrity": "sha512-22LnGGCDm1M/pKXxxF8n6ppr/1IztdsxqeqeBRUMTTNnJTkjIBiAkewiDM4u/gjtazj/FvZS+nk9AbO/vEcBFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.6", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.2.5.tgz", + "integrity": "sha512-0v0wLQA4uhWbybmdH6oPl7ooxCiymNylkvi2gCSc+mA5ZQB6QoJLVhoHq12T82lhozpp9fpMJnJkC1nJrU/A+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.10", + "@inquirer/core": "^12.0.6", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "8.7.5", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.7.5.tgz", + "integrity": "sha512-l/IWwC+QhOyXgxkZloOh+n5quGzc1m5ZawyzBmoizCZaxbJeLRdS3pStoqg/JVInJRXaG5aHMhjK6H6oYiBC9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^5.2.8", + "@inquirer/confirm": "^6.3.5", + "@inquirer/editor": "^5.3.6", + "@inquirer/expand": "^5.1.8", + "@inquirer/input": "^5.1.9", + "@inquirer/number": "^4.2.6", + "@inquirer/password": "^5.2.5", + "@inquirer/rawlist": "^5.3.8", + "@inquirer/search": "^4.3.6", + "@inquirer/select": "^5.2.8" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.3.8.tgz", + "integrity": "sha512-h+6Y4pPvYvuRbksn1kIBdD9tCYD+E468W5sIcVfLmoLP5IasnY0r33N+r+46ej/P8b2PCREjyKR+MnnfPGdTzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.6", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.3.6.tgz", + "integrity": "sha512-JZj9ttkIvuaFQIc2oxsyqC1ad05WL02ku6W/INVKTCzNaVq1KkhP3AfTRC0m7OWlKZyw+EWwPBFZfYiG9jF6Yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.6", + "@inquirer/figures": "^2.0.10", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.2.8.tgz", + "integrity": "sha512-BklkkAAOpjdCfuQw5fVGcDuhKxA7T1iBpn6b2JUvDDdkIsO3Ftlg+WnWUCVpkUBFagL2o036OFW+xL4lC9wIPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.10", + "@inquirer/core": "^12.0.6", + "@inquirer/figures": "^2.0.10", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.2.1.tgz", + "integrity": "sha512-lDkPsnv5M5CcYB2T1qTc3dobNTPkVr4DU3Ic2Q6SW8/qVRHgvzdRdmVZ6YqG9Ag1aJTeMsLU0s0WPRh3CZWDQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/@ioredis/commands": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", @@ -8591,9 +8948,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -15689,6 +16046,13 @@ "hasInstallScript": true, "license": "Apache-2.0" }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, "node_modules/@sentry/conventions": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/@sentry/conventions/-/conventions-0.16.0.tgz", @@ -16050,6 +16414,19 @@ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -16973,6 +17350,959 @@ "resolved": "https://registry.npmjs.org/@stitches/core/-/core-1.2.8.tgz", "integrity": "sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==" }, + "node_modules/@stryker-mutator/api": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@stryker-mutator/api/-/api-10.1.1.tgz", + "integrity": "sha512-3s/ECsrD1Tf1WEB7amBk0HxjN7zgkabKvmqnI2C831rTNszJ6o2HoV9PVxtElDEWxb2+t4iavK5x25G+DTUOUA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mutation-testing-metrics": "3.8.4", + "mutation-testing-report-schema": "3.8.4", + "tslib": "~2.8.0", + "typed-inject": "~5.0.0" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@stryker-mutator/core": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@stryker-mutator/core/-/core-10.1.1.tgz", + "integrity": "sha512-4xJAOCw9utJMLyrTk9gINi434tBxnyuwt8lU/InyoKagKd2/zh/2ui7ncWoD0qZeJrxClYzblg8iUh5ewBAB3Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@inquirer/prompts": "^8.0.0", + "@stryker-mutator/api": "10.1.1", + "@stryker-mutator/instrumenter": "10.1.1", + "@stryker-mutator/util": "10.1.1", + "ajv": "~8.20.0", + "chalk": "~5.6.0", + "commander": "~14.0.0", + "diff-match-patch": "1.0.5", + "emoji-regex": "~10.6.0", + "execa": "~9.6.0", + "json-rpc-2.0": "^1.7.0", + "lodash.groupby": "~4.6.0", + "minimatch": "~10.2.4", + "mutation-server-protocol": "~0.4.0", + "mutation-testing-elements": "3.8.4", + "mutation-testing-metrics": "3.8.4", + "mutation-testing-report-schema": "3.8.4", + "npm-run-path": "~6.0.0", + "progress": "~2.0.3", + "rxjs": "~7.8.1", + "semver": "^7.6.3", + "source-map": "~0.7.4", + "tree-kill": "~1.2.2", + "tslib": "2.8.1", + "typed-inject": "~5.0.0", + "typed-rest-client": "~2.3.0" + }, + "bin": { + "stryker": "bin/stryker.js" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@stryker-mutator/core/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/@stryker-mutator/core/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/@stryker-mutator/core/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@stryker-mutator/core/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/@stryker-mutator/core/node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@stryker-mutator/core/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@stryker-mutator/core/node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@stryker-mutator/core/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@stryker-mutator/core/node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@stryker-mutator/core/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@stryker-mutator/core/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@stryker-mutator/core/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@stryker-mutator/core/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@stryker-mutator/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@stryker-mutator/core/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/@stryker-mutator/core/node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@stryker-mutator/instrumenter": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@stryker-mutator/instrumenter/-/instrumenter-10.1.1.tgz", + "integrity": "sha512-Vyude2i8LhDciLV1q6J55KtdA7gZpRuMfZcBcmWV6hLHAvWocoPKnCKc5BlZt1mwGWkX3Olc0457I7Ue6F1WEg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/core": "~8.0.0", + "@babel/generator": "~8.0.0", + "@babel/parser": "~8.0.0", + "@babel/plugin-proposal-decorators": "~8.0.0", + "@babel/plugin-transform-explicit-resource-management": "^8.0.0", + "@babel/preset-react": "~8.0.0", + "@babel/preset-typescript": "~8.0.0", + "@babel/traverse": "~8.0.4", + "@stryker-mutator/api": "10.1.1", + "@stryker-mutator/util": "10.1.1", + "angular-html-parser": "~10.11.0", + "semver": "~7.8.0", + "tslib": "2.8.1", + "weapon-regex": "~2.0.0" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/code-frame": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.6.tgz", + "integrity": "sha512-uMHhvmDmwFiLuiozaoRYTZHqMFuu3fem/X9JY78lohAuLoh8LSGGC8JqUzo2cVuBsBxkjMZNk56CE7ock8Xxkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^8.0.6", + "js-tokens": "^10.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/compat-data": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-8.0.5.tgz", + "integrity": "sha512-YLsYoQMvL8l8WrGpN3Zj7O1wK5LEBN+cQtux7BcuHyxIXve724XG+zuJ1n3U1cUweRtTzQOA4IHbuQw3N34SZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/core": { + "version": "8.0.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-8.0.10.tgz", + "integrity": "sha512-T6dEcoMJ3L1hyLuNfRXcVlqQrN79kU/SB5bDygVgQNIOUgStdyEGzigtRA9K+9Coh7SZUSt543oWRBxvqYdyuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^8.0.6", + "@babel/generator": "^8.0.10", + "@babel/helper-compilation-targets": "^8.0.7", + "@babel/helpers": "^8.0.10", + "@babel/parser": "^8.0.10", + "@babel/template": "^8.0.0", + "@babel/traverse": "^8.0.10", + "@babel/types": "^8.0.10", + "@types/gensync": "^1.0.5", + "convert-source-map": "^2.0.0", + "empathic": "^2.0.1", + "gensync": "^1.0.0-beta.2", + "import-meta-resolve": "^4.2.0", + "json5": "^2.2.3", + "obug": "^3.0.0", + "verkit": "^0.5.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/generator": { + "version": "8.0.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.10.tgz", + "integrity": "sha512-iC9p3U4Y3vp0qKjRcfFeNzpj+kkK8BVYZTbQepBWVD7vOBLH6ji0drNzKPTPtumFd2w2r/hbEKYjG4pscKa4bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^8.0.10", + "@babel/types": "^8.0.10", + "@jridgewell/gen-mapping": "0.4.0-beta.0", + "@jridgewell/trace-mapping": "^0.3.31", + "@types/jsesc": "^2.5.0", + "jsesc": "^3.0.2" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-annotate-as-pure": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-8.0.0.tgz", + "integrity": "sha512-NSpMkMsvvZqzThJ0p1B02cbtA2ObEyfBvq950bmNkyxsxvcxwhvvCB036rKhlEnuBBo30bOrk13u3FzlKSoRrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-compilation-targets": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-8.0.7.tgz", + "integrity": "sha512-ilAZCdOOlOVA7dQbaFNw2f/59p6GW33NxTopc3DvEE3efp3ZXTrGX/yZScaKw8/vd9bq31Ag0EcXiLw32bAgJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^8.0.5", + "@babel/helper-validator-option": "^8.0.0", + "browserslist": "^4.24.0", + "flru": "1.0.2", + "verkit": "^0.5.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-create-class-features-plugin": { + "version": "8.0.10", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-8.0.10.tgz", + "integrity": "sha512-a97s55BatmvDeeQ9rngs/IPi1aeQ3vVUbXr0DsNGhzF/vB2w49N+FqD+D0/YEGLziU7zALhrKFlk6FGlvbM38w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^8.0.0", + "@babel/helper-member-expression-to-functions": "^8.0.5", + "@babel/helper-optimise-call-expression": "^8.0.0", + "@babel/helper-replace-supers": "^8.0.1", + "@babel/helper-skip-transparent-expression-wrappers": "^8.0.0", + "@babel/traverse": "^8.0.10", + "verkit": "^0.5.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-globals": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-8.0.6.tgz", + "integrity": "sha512-OdnA/N52LhzFpeUuVz5YGuSWjc7TPHiFmVYZbPlSgExocO2GwbxVpSni9MVs5wwW+wZ4kPuwBEBN9pIPDYz1mA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-member-expression-to-functions": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-8.0.5.tgz", + "integrity": "sha512-GLe05QD98BkNFTkjaqeqF9QSRoHLKrrB4tpoplyQuuPrQJ72rtmkM4wvqJLX/sjZPqkbK5MUYSsuUTqdJoOVjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^8.0.5", + "@babel/types": "^8.0.5" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-module-imports": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-8.0.0.tgz", + "integrity": "sha512-NZ7mSS93o4ndX4KrbD7W8Sf3QT8Qe24PrnFyUcuOPDzK6faqDFKjY9RG7he7+I7FdiQ4llpnosFqzrXa+Vy3Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^8.0.0", + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-module-transforms": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-8.0.6.tgz", + "integrity": "sha512-YUi+3X0cb0v97xKhuqkLdrwQtP6XXv1eVbhLRCFyMwG481UskbMQk4mjH7K/Jg3oJ1KLskvFwwq4kF/QnABkWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^8.0.0", + "@babel/helper-validator-identifier": "^8.0.6", + "@babel/traverse": "^8.0.6" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-optimise-call-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-8.0.0.tgz", + "integrity": "sha512-3W6satvtPuCUkUx63S2jMoW9EQNYkADgs1HTfufmL7gCmAulHMKupA/12WNz4A0GMMFn/YnWWwqOT9IZrJHQjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-plugin-utils": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-8.0.1.tgz", + "integrity": "sha512-3PKFgjTyPlhFhorfP+SjKQxLViIL++zWjFOO4hGriYU+Bsm983DxEM1JmDRJVWXV0O9npu+xXRqz7Pbd3mh70g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-replace-supers": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-8.0.1.tgz", + "integrity": "sha512-B1SZADIcy3tmH8CmWvj4SHi/oAPom4UL3uknTc2QRNsPVLFk/sPnZvQL/8kj7Y5omvjMqie0vklvs6XM4OLW5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^8.0.0", + "@babel/helper-optimise-call-expression": "^8.0.0", + "@babel/traverse": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-8.0.0.tgz", + "integrity": "sha512-xmCA9kP3IhySsqhzwIdWGlDN/1A4cCKNBO/uwZx/3YzmDoMePwno2Q5/Bq0q+tYaKbeF940YiKV/kaW8Mzvpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^8.0.0", + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-string-parser": { + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.8.tgz", + "integrity": "sha512-fVM+gSuKbdv9cpmyRNh5EvsDHUZCdxQH3FdnFKSkNYHpbsn95bbrxm+ES3MVeQjcqwi2O5ur/DjLfmTQaO60ng==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.6.tgz", + "integrity": "sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-validator-option": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-8.0.0.tgz", + "integrity": "sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helpers": { + "version": "8.0.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-8.0.10.tgz", + "integrity": "sha512-tBG9yuaR26JJWFafcl/Dk4eib6bkRJ9H5e6lENcwZqFudA9ImNCuMXLjB3/t+Ub7/DOZv2Rmi5LCOi8q2DCekQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^8.0.0", + "@babel/types": "^8.0.10" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/parser": { + "version": "8.0.10", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.10.tgz", + "integrity": "sha512-h+ah2Kg0ne1Vii892dShj9gky9I5jPzkd8x4s/3xgN/P2bMWmHS3qq59tum3mCVjCBXI8uqfYyYtrfp1Mz6+1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.10" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-proposal-decorators": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-8.0.2.tgz", + "integrity": "sha512-+C6O6KKXU7BBq1GNaIkFJxrALUVGRcr+WeWm4OcuRl3h+l/CmNfcTLMrT2Lm3uvGBimBH/8pEBRrXJFLoO67Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^8.0.1", + "@babel/helper-plugin-utils": "^8.0.1", + "@babel/plugin-syntax-decorators": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-syntax-decorators": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-8.0.1.tgz", + "integrity": "sha512-NI+0S/6MvR6GlcQFwjDZ+WIc2qvG6TXN534lYs9llNldwW4b7Dh6KTtk030FA0xWdYGs4t1lWo+OEWN8wGB+Nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-syntax-jsx": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-8.0.1.tgz", + "integrity": "sha512-n0jtCOxEovhU7METqSQjcZO9pX53nu9uNIjMS+hEt+Nt9jA7oOZoBIgbCxhhASmF6T6rPDGge5UAvh6Z4eFz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-syntax-typescript": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-8.0.3.tgz", + "integrity": "sha512-jmTPwps7oSQSZaV1SxkQ3C12UWyufGysGc5OzDpZzvPAIX4mO7dJT3hoqkWVrSImvkcMiknir1iLN1SNV/CZzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-transform-destructuring": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-8.0.5.tgz", + "integrity": "sha512-evjxc5fGvpXG2WMGSalpX8IJZVumLIDgk5r5eRhvXRDqFX/GPWq1BeRfJalPCcwJfrR5A/wvegUhx9W5Nr7/mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-8.0.5.tgz", + "integrity": "sha512-Vr20jk/ZxRGu30o0jYvcKl2Pf0tiyr8H9q2ZGxYD6yjOpv9DdP6//I85q0cSOdADZhsu/ZZoApjGVCfl4xnVJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^8.0.1", + "@babel/plugin-transform-destructuring": "^8.0.5" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-8.0.1.tgz", + "integrity": "sha512-PMuzulWrrzFNmY3lXSk/tV9NRb7y0eZZLJY4UEo2TKszroxvUZHAPPi+T9FDyrQhod+TQA+t+8/QYaaMpiEuhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^8.0.1", + "@babel/helper-plugin-utils": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-transform-react-display-name": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-8.0.1.tgz", + "integrity": "sha512-soLishXlkyu6jcICPyO3HEP7A3GCzKEnn7XfvYrImuWEOwFAz93qShmWSYPf5ww0ZkO4By0zsN2bVIDF54fSdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-transform-react-jsx": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-8.0.1.tgz", + "integrity": "sha512-NgkoF7Uq+30TmOPDdNUimT0Nta02uVjqJRFNlVWKrbOCu/CkzfHa4aMnIs0lMpkMmZmWA1e42Va+F04i/pY1zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^8.0.0", + "@babel/helper-module-imports": "^8.0.0", + "@babel/helper-plugin-utils": "^8.0.1", + "@babel/plugin-syntax-jsx": "^8.0.1", + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-8.0.1.tgz", + "integrity": "sha512-Hb+HUZpV9KFHjm+F+P3aLDMi8QXU9l3ROCQv20z18Me2sGyW5nNNR5YTevNlgHvCpFek3BnAwhDGq/BRndXViw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-8.0.1.tgz", + "integrity": "sha512-7/8UwU8hoPBurXa9tUiTTC8aACTRy5tCqLUtqikHp2eGiWoEB57AduOdbQ71OOMTEvawKrGhv3WfzkDpI+/oSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^8.0.0", + "@babel/helper-plugin-utils": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-transform-typescript": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-8.0.6.tgz", + "integrity": "sha512-WF3yRlQtClzScvQbWct00ix63+w3DPiyU0WmCBU4YqK539n3VvJFElElpevoD8wfu6953CbiNoUZBi2B7/5jXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^8.0.0", + "@babel/helper-create-class-features-plugin": "^8.0.6", + "@babel/helper-plugin-utils": "^8.0.1", + "@babel/helper-skip-transparent-expression-wrappers": "^8.0.0", + "@babel/plugin-syntax-typescript": "^8.0.3" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/preset-react": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-8.0.1.tgz", + "integrity": "sha512-jrFuPp/pTddFZbtmWhdLNAYc6UMcpboeUPnw0BBrm4nOmcAko/1TRcFi1PzWCeOFRU+VaSiKmat87W1HvR7mIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^8.0.1", + "@babel/helper-validator-option": "^8.0.0", + "@babel/plugin-transform-react-display-name": "^8.0.1", + "@babel/plugin-transform-react-jsx": "^8.0.1", + "@babel/plugin-transform-react-jsx-development": "^8.0.1", + "@babel/plugin-transform-react-pure-annotations": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/preset-typescript": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-8.0.7.tgz", + "integrity": "sha512-sUFLFphAG6YNjCnJpSKpyoNxkfyeM2KNuUjJJN0R6dZusMcK2V3Fg11MVHPFvb37AZeDNocc+xM748vdh9z82Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^8.0.1", + "@babel/helper-validator-option": "^8.0.0", + "@babel/plugin-transform-modules-commonjs": "^8.0.1", + "@babel/plugin-transform-typescript": "^8.0.6" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/template": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-8.0.0.tgz", + "integrity": "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^8.0.0", + "@babel/parser": "^8.0.0", + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/traverse": { + "version": "8.0.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-8.0.10.tgz", + "integrity": "sha512-t5DaGzMi9WlhMSFT2YavI8gl8yGihoQymSQUkIq9+MUh5tO3Vx9KHwr5gMCzeBdYkvs00xNvr18PxtQTRayucA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^8.0.6", + "@babel/generator": "^8.0.10", + "@babel/helper-globals": "^8.0.6", + "@babel/parser": "^8.0.10", + "@babel/template": "^8.0.0", + "@babel/types": "^8.0.10", + "obug": "^3.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/types": { + "version": "8.0.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.10.tgz", + "integrity": "sha512-KV8Y+teoRsmXeEPS4sCJY1kwV+hcPzHP1qQqgCxrjKOehLdBaxpsFBfoJ8MXWFV80y6Jw56+TM95wR0Per8dYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.8", + "@babel/helper-validator-identifier": "^8.0.6" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@jridgewell/gen-mapping": { + "version": "0.4.0-beta.0", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.4.0-beta.0.tgz", + "integrity": "sha512-JdGNkbE4GlNPYQhM0L95fBQr7ctLZJ276QXQLTad4t1oSdnnCI3fDq9DW3BqYAWv8Wc3+HS+4Gsii1oPMCfz1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0-beta.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/obug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/obug/-/obug-3.0.0.tgz", + "integrity": "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@stryker-mutator/jest-runner": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@stryker-mutator/jest-runner/-/jest-runner-10.1.1.tgz", + "integrity": "sha512-/Zsog+SkmuiS6ouA3WDM+ab/7lIkplCwlGslIBhSXP0L1L89mZ6JUVAyj3vAqTXhn+T6UKizzL8jCOTelpwqRQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@stryker-mutator/api": "10.1.1", + "@stryker-mutator/util": "10.1.1", + "semver": "~7.8.0", + "tslib": "~2.8.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "@stryker-mutator/core": "10.1.1" + } + }, + "node_modules/@stryker-mutator/util": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@stryker-mutator/util/-/util-10.1.1.tgz", + "integrity": "sha512-Z0zDzsmhoSykE1gT3DvD1knFxySgNMfBlpSSs1j87kWvjEMfeMqfLOk0bqEPRg/rTFIUNSEXLZapxePUhZmfZA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@swc/helpers": { "version": "0.5.17", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", @@ -18360,6 +19690,13 @@ "@types/express": "*" } }, + "node_modules/@types/gensync": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/gensync/-/gensync-1.0.5.tgz", + "integrity": "sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/geojson": { "version": "7946.0.16", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", @@ -19547,6 +20884,16 @@ } } }, + "node_modules/angular-html-parser": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/angular-html-parser/-/angular-html-parser-10.11.0.tgz", + "integrity": "sha512-3vERzJ65UFDr3C7uozLJwsNcQS3FS784dSh583oDgDTTZMgXe3/pdyXgKndxiP5R2lvYRfW6gSl145Hnyf2OFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/anser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/anser/-/anser-2.1.1.tgz", @@ -21174,6 +22521,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chardet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", + "dev": true, + "license": "MIT" + }, "node_modules/cheerio": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", @@ -21425,6 +22779,16 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -23039,6 +24403,13 @@ "node": ">=0.3.1" } }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -24775,6 +26146,23 @@ "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", "license": "Unlicense" }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, "node_modules/fast-uri": { "version": "3.1.9", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", @@ -24791,6 +26179,16 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, "node_modules/fast-xml-builder": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", @@ -24912,6 +26310,22 @@ "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", "license": "MIT" }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -25147,6 +26561,16 @@ "npm": ">=5" } }, + "node_modules/flru": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/flru/-/flru-1.0.2.tgz", + "integrity": "sha512-kWyh8ADvHBFz6ua5xYOPnUroZTT/bwWfrCeL0Wj1dzG4/YOmOcfJ99W8dOVyyynJN35rZ9aCOtHChqQovV7yog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/fn.name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", @@ -26535,6 +27959,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/import-without-cache": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/import-without-cache/-/import-without-cache-0.4.0.tgz", @@ -27090,6 +28525,18 @@ "node": ">=0.10.0" } }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-plain-object": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.1.0.tgz", @@ -27230,6 +28677,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -28744,6 +30204,13 @@ "node": ">=12" } }, + "node_modules/js-md4": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", + "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==", + "dev": true, + "license": "MIT" + }, "node_modules/js-tiktoken": { "version": "1.0.21", "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.21.tgz", @@ -28851,6 +30318,13 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, + "node_modules/json-rpc-2.0": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/json-rpc-2.0/-/json-rpc-2.0-1.8.1.tgz", + "integrity": "sha512-BmN42JKn6FI36pcMMzHCXQhpKNEfbeqgSIcUYHKnifq0/4Ey6PDinovwUOgajT7NsgUtpWdDXaCWlpI4LHXN3g==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-to-ts": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", @@ -30097,6 +31571,13 @@ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -32452,6 +33933,53 @@ "mustache": "bin/mustache" } }, + "node_modules/mutation-server-protocol": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/mutation-server-protocol/-/mutation-server-protocol-0.4.1.tgz", + "integrity": "sha512-SBGK0j8hLDne7bktgThKI8kGvGTx3rY3LAeQTmOKZ5bVnL/7TorLMvcVF7dIPJCu5RNUWhkkuF53kurygYVt3g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "zod": "^4.1.12" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/mutation-testing-elements": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/mutation-testing-elements/-/mutation-testing-elements-3.8.4.tgz", + "integrity": "sha512-5CF1SNa7at5ZH33vEr+21wNebTSrtNIVvnzaUlxortHajOrIPaSLczIWvg6sI/fsExekQ7jookwf2cHftkckqQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/mutation-testing-metrics": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/mutation-testing-metrics/-/mutation-testing-metrics-3.8.4.tgz", + "integrity": "sha512-DZcmndJBH6nrNs3tpiB3OcMVq9KkG2cHCpJSnDxSxPwi9qrafRmoec40xjhkbzOoX1n7/4UkDqg5tIj4A6nvCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mutation-testing-report-schema": "3.8.4" + } + }, + "node_modules/mutation-testing-report-schema": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/mutation-testing-report-schema/-/mutation-testing-report-schema-3.8.4.tgz", + "integrity": "sha512-s4G71R6Lt/PpZ0cqeglIcgyBdzLM8E+SeCHZAPg1wkSsPtRBa4XfPzAozYKdiJk/TLbNEEb7En9t0/bveuPuxA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/mute-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", + "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/nanoid": { "version": "3.3.18", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", @@ -33412,6 +34940,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse-srcset": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", @@ -34141,6 +35682,22 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/pretty-ms": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.1.tgz", + "integrity": "sha512-HzMy3Geq23nVALD/M2LliU+F+M+gVNsvkQWWqeBZ8HDiCgzo6YPJ/Omrmtq24EFrIsk0a3EkQGEd7bDOo+IhGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -34157,6 +35714,16 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/prom-client": { "version": "15.1.3", "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz", @@ -34722,17 +36289,6 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" }, - "node_modules/react-markdown/node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/react-markdown/node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -35332,17 +36888,6 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" }, - "node_modules/remark-directive/node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/remark-directive/node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -35422,17 +36967,6 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" }, - "node_modules/remark-gfm/node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/remark-gfm/node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -35509,17 +37043,6 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" }, - "node_modules/remark-math/node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/remark-math/node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -35596,17 +37119,6 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" }, - "node_modules/remark-parse/node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/remark-parse/node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -35693,17 +37205,6 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" }, - "node_modules/remark-rehype/node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/remark-rehype/node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -35780,17 +37281,6 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" }, - "node_modules/remark-stringify/node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/remark-stringify/node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -36345,6 +37835,16 @@ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", "license": "BSD-3-Clause" }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -38377,6 +39877,16 @@ "dev": true, "license": "MIT" }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, "node_modules/turbo": { "version": "2.10.5", "resolved": "https://registry.npmjs.org/turbo/-/turbo-2.10.5.tgz", @@ -38534,6 +40044,16 @@ "node": ">= 18" } }, + "node_modules/typed-inject": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/typed-inject/-/typed-inject-5.0.0.tgz", + "integrity": "sha512-0Ql2ORqBORLMdAW89TQKZsb1PQkFGImFfVmncXWe7a+AA3+7dh7Se9exxZowH4kbnlvKEFkMxUYdHUpjYWFJaA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/typed-query-selector": { "version": "2.12.3", "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.3.tgz", @@ -38541,6 +40061,39 @@ "dev": true, "license": "MIT" }, + "node_modules/typed-rest-client": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-2.3.1.tgz", + "integrity": "sha512-k4kX5Up6qA68D0Cby2AK+6+vM5k3qTxe+/3FqhnHRExjY5cfbOnzjQZbP/LXleF8hVoDvDqxlgk9KK83HoBZlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "des.js": "^1.1.0", + "js-md4": "^0.3.2", + "qs": "6.15.1", + "tunnel": "0.0.6", + "underscore": "^1.13.8" + }, + "engines": { + "node": ">= 16.0.0" + } + }, + "node_modules/typed-rest-client/node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -38751,6 +40304,19 @@ "node": ">=4" } }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unified": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", @@ -38791,17 +40357,6 @@ "node": ">=4" } }, - "node_modules/unified/node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", @@ -39265,6 +40820,19 @@ "extsprintf": "^1.2.0" } }, + "node_modules/verkit": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/verkit/-/verkit-0.5.0.tgz", + "integrity": "sha512-u4J1hr6Y+C/hit34GwL3eIcUf3wngT8TlYUjK7tk0jxydKLAq3sYvifjdTDA6U3KCKRcQCQDGf/srjxfZF3l7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, "node_modules/verror": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", @@ -39538,6 +41106,13 @@ "makeerror": "1.0.12" } }, + "node_modules/weapon-regex": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/weapon-regex/-/weapon-regex-2.0.6.tgz", + "integrity": "sha512-iJC2vqYp+bh3r4X6zjuP0h65XqXjgUX3nJgmhj0PQMrdhAbUwCfN76CSTEEQqV3G7468kjOJ69PmytTG8l/8Gg==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/web-features": { "version": "3.37.0", "resolved": "https://registry.npmjs.org/web-features/-/web-features-3.37.0.tgz", @@ -40665,6 +42240,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoctocolors": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz", + "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zod": { "version": "4.6.7", "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", @@ -43755,6 +45343,8 @@ "micromark-util-decode-string": "^2.0.0" }, "devDependencies": { + "@stryker-mutator/core": "10.1.1", + "@stryker-mutator/jest-runner": "10.1.1", "@types/jest": "^29.5.2", "@types/node": "^24.12.4", "jest": "^30.2.0", diff --git a/packages/data-schemas/jest.favorite.config.mjs b/packages/data-schemas/jest.favorite.config.mjs new file mode 100644 index 0000000000..0c8e319297 --- /dev/null +++ b/packages/data-schemas/jest.favorite.config.mjs @@ -0,0 +1,16 @@ +import { createRequire } from 'node:module'; +import config from './jest.config.mjs'; + +const require = createRequire(import.meta.url); + +export default { + ...config, + // Stryker must use Jest's own environment even when npm nests its dependencies. + testEnvironment: require.resolve('jest-environment-node', { paths: [require.resolve('jest')] }), + testMatch: ['/src/methods/favorite*.spec.ts'], + collectCoverageFrom: ['src/methods/favorite.ts'], + coverageDirectory: '/coverage/favorites', + coverageReporters: ['text', 'json'], + testResultsProcessor: undefined, + maxWorkers: 1, +}; diff --git a/packages/data-schemas/package.json b/packages/data-schemas/package.json index 9d4f4dd44b..4a6f8247f4 100644 --- a/packages/data-schemas/package.json +++ b/packages/data-schemas/package.json @@ -41,7 +41,10 @@ "test:ci": "jest --coverage --ci", "verify": "npm run test:ci", "b:clean": "bun run rimraf dist", - "b:build": "bun run b:clean && bun run tsdown" + "b:build": "bun run b:clean && bun run tsdown", + "test:contracts:favorites": "jest --config jest.favorite.config.mjs --coverage=false", + "test:mutations:favorites": "cd ../.. && stryker run packages/data-schemas/stryker.config.mjs", + "test:quality:favorites": "jest --config jest.favorite.config.mjs --coverage && node ../../scripts/crap.mts coverage/favorites/coverage-final.json src/methods/favorite.ts" }, "repository": { "type": "git", @@ -64,6 +67,8 @@ "micromark-util-decode-string": "^2.0.0" }, "devDependencies": { + "@stryker-mutator/core": "10.1.1", + "@stryker-mutator/jest-runner": "10.1.1", "@types/jest": "^29.5.2", "@types/node": "^24.12.4", "jest": "^30.2.0", diff --git a/packages/data-schemas/src/contracts/favorite.ts b/packages/data-schemas/src/contracts/favorite.ts new file mode 100644 index 0000000000..2749102c84 --- /dev/null +++ b/packages/data-schemas/src/contracts/favorite.ts @@ -0,0 +1,211 @@ +/* eslint-disable jest/no-export -- Shared behavioral suite, invoked by each engine's spec. */ +import type { ToolFavoriteMethods } from '~/methods/favorite'; +import type { ToolFavoriteParams } from '~/types/favorite'; + +type FavoriteParams = Omit & { userId: string }; + +/** The application-facing subset: no documents, queries, sessions, or engine IDs. */ +export interface FavoriteContract { + getToolFavorites(userId: string): ReturnType; + addToolFavorite(params: FavoriteParams): ReturnType; + removeToolFavorite(params: FavoriteParams): ReturnType; + deleteUserToolFavorites(userId: string): Promise; +} + +export interface FavoriteHarness { + readonly name: string; + readonly users: readonly [string, string]; + setup(): Promise; + teardown(): Promise; + reset(): Promise; + /** Seed timestamps without depending on the clock or the database's insertion order. */ + seed(favorites: readonly (FavoriteParams & { createdAt?: Date })[]): Promise; + inTenant(tenantId: string, operation: () => Promise): Promise; + /** Fail the next persistence operation, not the public method under test. */ + failNext(operation: 'read' | 'insert' | 'remove' | 'erase', error: Error): void; +} + +export function describeFavoriteConformance(harness: FavoriteHarness): void { + describe(`favorites contract: ${harness.name}`, () => { + let methods: FavoriteContract; + const [userA, userB] = harness.users; + const favorite: FavoriteParams = { userId: userA, itemType: 'tool', itemId: 'shared' }; + + beforeAll(async () => { + methods = await harness.setup(); + }); + afterAll(() => harness.teardown()); + beforeEach(() => harness.reset()); + + it('returns an empty list for a user without favorites', async () => { + expect(await methods.getToolFavorites(userA)).toEqual([]); + }); + + it.each(['builtin', 'tool', 'mcp', 'skill'] as const)( + 'persists a %s favorite and returns only its public fields', + async (itemType) => { + expect(await methods.addToolFavorite({ ...favorite, itemType })).toEqual({ + ok: true, + added: true, + }); + expect(await methods.getToolFavorites(userA)).toEqual([{ itemType, itemId: 'shared' }]); + }, + ); + + it('re-adding a favorite is a no-op', async () => { + await methods.addToolFavorite(favorite); + expect(await methods.addToolFavorite(favorite)).toEqual({ ok: true, added: false }); + expect(await methods.getToolFavorites(userA)).toEqual([ + { itemType: 'tool', itemId: 'shared' }, + ]); + }); + + it('identifies favorites by user, kind, and item ID', async () => { + await methods.addToolFavorite(favorite); + await methods.addToolFavorite({ ...favorite, itemType: 'skill' }); + await methods.addToolFavorite({ ...favorite, itemId: 'other' }); + await methods.addToolFavorite({ ...favorite, userId: userB }); + + expect(await methods.getToolFavorites(userB)).toEqual([ + { itemType: 'tool', itemId: 'shared' }, + ]); + const found = await methods.getToolFavorites(userA); + expect(found).toHaveLength(3); + expect(found).toEqual( + expect.arrayContaining([ + { itemType: 'tool', itemId: 'shared' }, + { itemType: 'skill', itemId: 'shared' }, + { itemType: 'tool', itemId: 'other' }, + ]), + ); + }); + + it('lists by creation time, not insertion order, without reordering a re-add', async () => { + await harness.seed([ + { ...favorite, itemId: 'newer', createdAt: new Date('2026-02-02T00:00:00Z') }, + { ...favorite, itemId: 'older', createdAt: new Date('2026-02-01T00:00:00Z') }, + ]); + await methods.addToolFavorite({ ...favorite, itemId: 'older' }); + expect(await methods.getToolFavorites(userA)).toEqual([ + { itemType: 'tool', itemId: 'older' }, + { itemType: 'tool', itemId: 'newer' }, + ]); + }); + + it('accepts an item ID at the maximum length', async () => { + const itemId = 'x'.repeat(256); + expect(await methods.addToolFavorite({ ...favorite, itemId })).toEqual({ + ok: true, + added: true, + }); + expect(await methods.getToolFavorites(userA)).toEqual([{ itemType: 'tool', itemId }]); + }); + + it.each([ + { itemType: 'agent' as never, itemId: 'invalid-kind' }, + { itemType: 'tool' as const, itemId: '' }, + { itemType: 'tool' as const, itemId: 'x'.repeat(257) }, + ])('rejects invalid input without persisting it: %j', async (input) => { + await expect(methods.addToolFavorite({ ...favorite, ...input })).rejects.toThrow(); + expect(await methods.getToolFavorites(userA)).toEqual([]); + }); + + it('allows the last slot, rejects beyond 100, and permits re-adds at the cap', async () => { + await harness.seed( + Array.from({ length: 99 }, (_, i) => ({ ...favorite, itemId: `tool-${i}` })), + ); + await harness.seed( + Array.from({ length: 100 }, (_, i) => ({ + ...favorite, + userId: userB, + itemId: `other-${i}`, + })), + ); + expect(await methods.addToolFavorite(favorite)).toEqual({ ok: true, added: true }); + await expect( + methods.addToolFavorite({ ...favorite, itemId: 'overflow' }), + ).rejects.toMatchObject({ + code: 'MAX_FAVORITES_EXCEEDED', + message: 'Maximum of 100 favorites reached', + limit: 100, + }); + expect(await methods.addToolFavorite(favorite)).toEqual({ ok: true, added: false }); + expect(await methods.getToolFavorites(userA)).toHaveLength(100); + }); + + it('concurrent duplicate adds succeed and persist a single favorite', async () => { + const results = await Promise.all( + Array.from({ length: 3 }, () => methods.addToolFavorite(favorite)), + ); + expect(results.every((result) => result.ok)).toBe(true); + // Concurrent callers need not agree on `added`; only the persisted identity is unique. + expect(await methods.getToolFavorites(userA)).toEqual([ + { itemType: 'tool', itemId: 'shared' }, + ]); + }); + + it('removes only the requested user, kind, and item, and reports a repeat as a no-op', async () => { + await harness.seed([ + favorite, + { ...favorite, itemType: 'skill' }, + { ...favorite, itemId: 'other' }, + { ...favorite, userId: userB }, + ]); + expect(await methods.removeToolFavorite(favorite)).toEqual({ ok: true, removed: true }); + expect(await methods.removeToolFavorite(favorite)).toEqual({ ok: true, removed: false }); + expect(await methods.getToolFavorites(userB)).toEqual([ + { itemType: 'tool', itemId: 'shared' }, + ]); + const remaining = await methods.getToolFavorites(userA); + expect(remaining).toHaveLength(2); + expect(remaining).toEqual( + expect.arrayContaining([ + { itemType: 'skill', itemId: 'shared' }, + { itemType: 'tool', itemId: 'other' }, + ]), + ); + }); + + it('erases all favorites for one user and returns the deletion count', async () => { + await harness.seed([ + favorite, + { ...favorite, itemType: 'skill' }, + { ...favorite, userId: userB }, + ]); + expect(await methods.deleteUserToolFavorites(userA)).toBe(2); + expect(await methods.deleteUserToolFavorites(userA)).toBe(0); + expect(await methods.getToolFavorites(userA)).toEqual([]); + expect(await methods.getToolFavorites(userB)).toEqual([ + { itemType: 'tool', itemId: 'shared' }, + ]); + }); + + it('cannot read, remove, or erase another tenant’s favorites', async () => { + await harness.inTenant('tenant-a', () => methods.addToolFavorite(favorite)); + await harness.inTenant('tenant-b', async () => { + expect(await methods.getToolFavorites(userA)).toEqual([]); + expect(await methods.removeToolFavorite(favorite)).toEqual({ ok: true, removed: false }); + expect(await methods.deleteUserToolFavorites(userA)).toBe(0); + }); + expect(await harness.inTenant('tenant-a', () => methods.getToolFavorites(userA))).toEqual([ + { itemType: 'tool', itemId: 'shared' }, + ]); + }); + + it.each(['read', 'insert', 'remove', 'erase'] as const)( + 'propagates an operational %s failure instead of reporting success or absence', + async (operation) => { + const failure = new Error('persistence unavailable'); + harness.failNext(operation, failure); + const action = { + read: () => methods.getToolFavorites(userA), + insert: () => methods.addToolFavorite(favorite), + remove: () => methods.removeToolFavorite(favorite), + erase: () => methods.deleteUserToolFavorites(userA), + }[operation]; + await expect(action()).rejects.toBe(failure); + expect(await methods.getToolFavorites(userA)).toEqual([]); + }, + ); + }); +} diff --git a/packages/data-schemas/src/methods/favorite.exports.spec.ts b/packages/data-schemas/src/methods/favorite.exports.spec.ts new file mode 100644 index 0000000000..3dd5a0ca46 --- /dev/null +++ b/packages/data-schemas/src/methods/favorite.exports.spec.ts @@ -0,0 +1,30 @@ +import mongoose from 'mongoose'; +import { MongoMemoryServer } from 'mongodb-memory-server'; +import { createMethods, createModels, FAVORITE_ITEM_TYPES, MAX_TOOL_FAVORITES } from '..'; + +it('keeps favorites available through the existing package facade', async () => { + const server = await MongoMemoryServer.create(); + const instance = new mongoose.Mongoose(); + instance.set('autoIndex', false); + instance.set('autoCreate', false); + try { + await instance.connect(server.getUri()); + const models = createModels(instance); + await models.ToolFavorite.createIndexes(); + const methods = createMethods(instance); + const userId = new mongoose.Types.ObjectId().toString(); + const params = { userId, itemType: 'tool', itemId: 'compatibility' } as const; + + expect(MAX_TOOL_FAVORITES).toBe(100); + expect(FAVORITE_ITEM_TYPES).toEqual(['builtin', 'tool', 'mcp', 'skill']); + expect(await methods.addToolFavorite(params)).toEqual({ ok: true, added: true }); + expect(await methods.getToolFavorites(userId)).toEqual([ + { itemType: 'tool', itemId: 'compatibility' }, + ]); + expect(await methods.removeToolFavorite(params)).toEqual({ ok: true, removed: true }); + expect(await methods.deleteUserToolFavorites(userId)).toBe(0); + } finally { + await instance.disconnect(); + await server.stop(); + } +}); diff --git a/packages/data-schemas/src/methods/favorite.spec.ts b/packages/data-schemas/src/methods/favorite.spec.ts index e4306ac3e4..7e1c7388b4 100644 --- a/packages/data-schemas/src/methods/favorite.spec.ts +++ b/packages/data-schemas/src/methods/favorite.spec.ts @@ -1,153 +1,86 @@ import mongoose from 'mongoose'; -import { logger, createModels } from '..'; import { MongoMemoryServer } from 'mongodb-memory-server'; -import { createToolFavoriteMethods, MAX_TOOL_FAVORITES } from './favorite'; +import type { FavoriteHarness } from '~/contracts/favorite'; +import { describeFavoriteConformance } from '~/contracts/favorite'; +import { createToolFavoriteModel } from '~/models/favorite'; +import { createToolFavoriteMethods } from './favorite'; +import { tenantStorage } from '~/config/tenantContext'; -logger.silent = true; +const instance = new mongoose.Mongoose(); +const ToolFavorite = createToolFavoriteModel(instance); +let server: MongoMemoryServer; -let ToolFavorite: mongoose.Model; -let methods: ReturnType; -let mongoServer: MongoMemoryServer; - -const userA = new mongoose.Types.ObjectId().toString(); -const userB = new mongoose.Types.ObjectId().toString(); - -beforeAll(async () => { - mongoServer = await MongoMemoryServer.create(); - await mongoose.connect(mongoServer.getUri()); - createModels(mongoose); - ToolFavorite = mongoose.models.ToolFavorite; - await ToolFavorite.syncIndexes(); - methods = createToolFavoriteMethods(mongoose); -}); - -afterAll(async () => { - await mongoose.disconnect(); - await mongoServer.stop(); -}); - -beforeEach(async () => { - await ToolFavorite.deleteMany({}); -}); - -describe('addToolFavorite', () => { - test('persists a favorite and getToolFavorites returns the lean projection', async () => { - const result = await methods.addToolFavorite({ - userId: userA, - itemType: 'mcp', - itemId: 'everything', - }); - expect(result).toEqual({ ok: true, added: true }); - - const favorites = await methods.getToolFavorites(userA); - expect(favorites).toEqual([{ itemType: 'mcp', itemId: 'everything' }]); - }); - - test('is idempotent: double-add keeps one doc and reports added: false', async () => { - await methods.addToolFavorite({ userId: userA, itemType: 'tool', itemId: 'dalle' }); - const second = await methods.addToolFavorite({ - userId: userA, - itemType: 'tool', - itemId: 'dalle', - }); - expect(second).toEqual({ ok: true, added: false }); - expect(await ToolFavorite.countDocuments({ user: userA })).toBe(1); - }); - - test('rejects an itemType outside the enum', async () => { - await expect( - methods.addToolFavorite({ - userId: userA, - itemType: 'agent' as never, - itemId: 'x', - }), - ).rejects.toThrow(); - }); - - test('rejects an itemId longer than 256 characters', async () => { - await expect( - methods.addToolFavorite({ userId: userA, itemType: 'tool', itemId: 'x'.repeat(257) }), - ).rejects.toThrow(); - }); - - test('throws a coded error at the cap, but re-adding an existing favorite still succeeds', async () => { - const docs = Array.from({ length: MAX_TOOL_FAVORITES }, (_, i) => ({ - user: userA, - itemType: 'tool', - itemId: `tool-${i}`, - })); - await ToolFavorite.insertMany(docs); - - const rejection = methods.addToolFavorite({ - userId: userA, - itemType: 'tool', - itemId: 'one-too-many', - }); - await expect(rejection).rejects.toMatchObject({ - code: 'MAX_FAVORITES_EXCEEDED', - limit: MAX_TOOL_FAVORITES, - }); - - const existing = await methods.addToolFavorite({ - userId: userA, - itemType: 'tool', - itemId: 'tool-0', - }); - expect(existing).toEqual({ ok: true, added: false }); - }); - - test('concurrent duplicate adds do not throw (unique index backstop)', async () => { - const params = { userId: userA, itemType: 'skill', itemId: 'abc123' } as const; - const results = await Promise.all([ - methods.addToolFavorite(params), - methods.addToolFavorite(params), - methods.addToolFavorite(params), - ]); - expect(results.every((r) => r.ok)).toBe(true); - expect(await ToolFavorite.countDocuments({ user: userA })).toBe(1); - }); -}); - -describe('removeToolFavorite', () => { - test('removes a favorite and is idempotent', async () => { - await methods.addToolFavorite({ userId: userA, itemType: 'builtin', itemId: 'web_search' }); - - const removed = await methods.removeToolFavorite({ - userId: userA, - itemType: 'builtin', - itemId: 'web_search', - }); - expect(removed).toEqual({ ok: true, removed: true }); - - const again = await methods.removeToolFavorite({ - userId: userA, - itemType: 'builtin', - itemId: 'web_search', - }); - expect(again).toEqual({ ok: true, removed: false }); - expect(await methods.getToolFavorites(userA)).toEqual([]); - }); -}); - -describe('getToolFavorites', () => { - test('is isolated per user', async () => { - await methods.addToolFavorite({ userId: userA, itemType: 'tool', itemId: 'dalle' }); - await methods.addToolFavorite({ userId: userB, itemType: 'mcp', itemId: 'everything' }); - - expect(await methods.getToolFavorites(userA)).toEqual([{ itemType: 'tool', itemId: 'dalle' }]); - expect(await methods.getToolFavorites(userB)).toEqual([ - { itemType: 'mcp', itemId: 'everything' }, - ]); - }); - - test('does not distinguish same itemId across kinds (compound identity)', async () => { - await methods.addToolFavorite({ userId: userA, itemType: 'tool', itemId: 'shared-id' }); - await methods.addToolFavorite({ userId: userA, itemType: 'skill', itemId: 'shared-id' }); - - const favorites = await methods.getToolFavorites(userA); - expect(favorites).toHaveLength(2); - expect(new Set(favorites.map((f) => `${f.itemType}:${f.itemId}`))).toEqual( - new Set(['tool:shared-id', 'skill:shared-id']), +const harness: FavoriteHarness = { + name: 'mongoose', + users: [new mongoose.Types.ObjectId().toString(), new mongoose.Types.ObjectId().toString()], + async setup() { + server = await MongoMemoryServer.create(); + await instance.connect(server.getUri()); + await ToolFavorite.syncIndexes(); + return createToolFavoriteMethods(instance); + }, + async teardown() { + await instance.disconnect(); + await server?.stop(); + }, + async reset() { + jest.restoreAllMocks(); + await ToolFavorite.deleteMany({}); + }, + async seed(favorites) { + await ToolFavorite.insertMany( + favorites.map(({ userId, ...fields }) => ({ user: userId, ...fields })), ); + }, + inTenant: (tenantId, operation) => tenantStorage.run({ tenantId }, operation), + failNext(operation, error) { + const method = { + read: 'find', + insert: 'updateOne', + remove: 'deleteOne', + erase: 'deleteMany', + } as const; + jest.spyOn(ToolFavorite, method[operation]).mockImplementationOnce(() => { + throw error; + }); + }, +}; + +describeFavoriteConformance(harness); + +describe('favorites Mongo adapter', () => { + beforeAll(() => harness.setup()); + afterAll(() => harness.teardown()); + beforeEach(() => harness.reset()); + + it('enforces compound uniqueness in the database, not only in application checks', async () => { + const record = { user: harness.users[0], itemType: 'tool', itemId: 'unique' }; + await ToolFavorite.create(record); + await expect(ToolFavorite.create(record)).rejects.toMatchObject({ code: 11000 }); + }); + + it('retains ObjectId input compatibility', async () => { + const methods = createToolFavoriteMethods(instance); + const params = { + userId: new mongoose.Types.ObjectId(harness.users[0]), + itemType: 'mcp', + itemId: 'server', + } as const; + expect(await methods.addToolFavorite(params)).toEqual({ ok: true, added: true }); + expect(await methods.removeToolFavorite(params)).toEqual({ ok: true, removed: true }); + }); + + it('treats a duplicate-key write race as success while keeping one record', async () => { + const methods = createToolFavoriteMethods(instance); + const params = { userId: harness.users[0], itemType: 'skill', itemId: 'race' } as const; + const update = ToolFavorite.collection.updateOne.bind(ToolFavorite.collection); + jest.spyOn(ToolFavorite.collection, 'updateOne').mockImplementationOnce(async (...args) => { + await update(...args); + throw new mongoose.mongo.MongoServerError({ code: 11000, message: 'duplicate key' }); + }); + expect(await methods.addToolFavorite(params)).toEqual({ ok: true, added: true }); + expect(await methods.getToolFavorites(harness.users[0])).toEqual([ + { itemType: 'skill', itemId: 'race' }, + ]); }); }); diff --git a/packages/data-schemas/stryker.config.mjs b/packages/data-schemas/stryker.config.mjs new file mode 100644 index 0000000000..4638b089ae --- /dev/null +++ b/packages/data-schemas/stryker.config.mjs @@ -0,0 +1,35 @@ +/** Run from the repository root so shared Jest configuration is copied into the sandbox. */ +export default { + $schema: './node_modules/@stryker-mutator/core/schema/stryker-schema.json', + plugins: ['@stryker-mutator/jest-runner'], + testRunner: 'jest', + jest: { + projectType: 'custom', + configFile: 'packages/data-schemas/jest.favorite.config.mjs', + enableFindRelatedTests: true, + config: { testMatch: ['/src/methods/favorite.spec.ts'] }, + }, + mutate: ['packages/data-schemas/src/methods/favorite.ts'], + coverageAnalysis: 'perTest', + concurrency: 2, + incremental: !process.env.CI, + incrementalFile: 'packages/data-schemas/coverage/favorites/incremental.json', + reporters: ['clear-text', 'progress', 'json', 'html'], + jsonReporter: { fileName: 'packages/data-schemas/coverage/favorites/mutation.json' }, + htmlReporter: { fileName: 'packages/data-schemas/coverage/favorites/mutation.html' }, + // Advisory during the pilot. Surviving mutants remain visible in both reports. + thresholds: { high: 90, low: 70, break: 0 }, + ignorePatterns: [ + '/*', + '!/package.json', + '!/config/', + '/config/*', + '!/config/jest.*', + '!/packages/', + '/packages/*', + '!/packages/data-schemas/', + '!/packages/data-provider/', + '**/coverage/', + '**/misc/', + ], +}; diff --git a/scripts/crap.mts b/scripts/crap.mts new file mode 100644 index 0000000000..3021e279f3 --- /dev/null +++ b/scripts/crap.mts @@ -0,0 +1,296 @@ +import { Linter } from 'eslint'; +import { parseArgs } from 'node:util'; +import { fileURLToPath } from 'node:url'; +import parser from '@typescript-eslint/parser'; +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { basename, dirname, join, relative, resolve, sep } from 'node:path'; + +import type { Rule } from 'eslint'; + +const ROOT = fileURLToPath(new URL('../', import.meta.url)); +const FAVORITES = join(ROOT, 'packages/data-schemas/src/methods/favorite.ts'); +const FUNCTION_TYPES = new Set([ + 'FunctionDeclaration', + 'FunctionExpression', + 'ArrowFunctionExpression', +]); + +export interface FunctionReport { + file: string; + function: string; + line: number; + column: number; + complexity: number; + statements: number; + coveredStatements: number; + /** Fraction in [0, 1], excluding nested functions; null means no instrumentation. */ + statementCoverage: number | null; + crap: number | null; +} + +type FunctionNode = Extract< + Rule.Node, + { type: 'FunctionDeclaration' | 'FunctionExpression' | 'ArrowFunctionExpression' } +>; + +interface MeasuredFunction { + head: number; + start: number; + end: number; + report: FunctionReport; +} + +const isRecord = (value: unknown): value is Record => + value !== null && typeof value === 'object' && !Array.isArray(value); + +export function crapScore(complexity: number, coverage: number | null): number | null { + if (!Number.isInteger(complexity) || complexity < 1) { + throw new Error('Complexity must be a positive integer'); + } + if (coverage === null) return null; + if (!Number.isFinite(coverage) || coverage < 0 || coverage > 1) { + throw new Error('Statement coverage must be a fraction in [0, 1]'); + } + return complexity ** 2 * (1 - coverage) ** 3 + complexity; +} + +function functionName(node: FunctionNode, context: Rule.RuleContext): string { + if ('id' in node && node.id?.type === 'Identifier') return node.id.name; + const parent = node.parent; + if (parent.type === 'VariableDeclarator' && parent.id.type === 'Identifier') + return parent.id.name; + if ( + parent.type === 'Property' || + parent.type === 'MethodDefinition' || + parent.type === 'PropertyDefinition' + ) { + return context.sourceCode.getText(parent.key); + } + return '(anonymous)'; +} + +/** Uses ESLint's real classic rule, not an approximation of its branch counting. */ +export function measureFunctions( + file: string, + source: string, + coverage?: unknown, +): FunctionReport[] { + const absoluteFile = resolve(file); + const functions: MeasuredFunction[] = []; + const lines = source.split(/\r\n|[\n\r\u2028\u2029]/u); + const offsets = [0]; + for (const match of source.matchAll(/\r\n|[\n\r\u2028\u2029]/gu)) { + offsets.push(match.index + match[0].length); + } + const offset = (position: unknown): number => { + if (!isRecord(position)) throw new Error(`${file}: invalid coverage location`); + const { line, column } = position; + if ( + typeof line !== 'number' || + typeof column !== 'number' || + !Number.isInteger(line) || + !Number.isInteger(column) || + line < 1 || + line > lines.length || + column < 0 || + column > lines[line - 1].length + ) { + throw new Error(`${file}: coverage location is outside the source`); + } + return offsets[line - 1] + column; + }; + const displayFile = relative(ROOT, absoluteFile); + const name = displayFile.startsWith(`..${sep}`) ? absoluteFile : displayFile.split(sep).join('/'); + const linter = new Linter(); + const messages = linter.verify( + source, + { + files: ['**/*.{js,jsx,ts,tsx,mjs,mts,cjs,cts}'], + languageOptions: { parser, ecmaVersion: 'latest', sourceType: 'module' }, + linterOptions: { noInlineConfig: true }, + plugins: { + metrics: { + rules: { + functions: { + create(context: Rule.RuleContext) { + return { + 'FunctionDeclaration, FunctionExpression, ArrowFunctionExpression'( + node: Rule.Node, + ) { + if ( + node.type !== 'FunctionDeclaration' && + node.type !== 'FunctionExpression' && + node.type !== 'ArrowFunctionExpression' + ) + return; + const parent = node.parent; + // ESLint reports methods at their property key and arrows at the => token. + const isMethod = + parent.type === 'Property' || + parent.type === 'MethodDefinition' || + parent.type === 'PropertyDefinition'; + const declaration = isMethod ? parent : node; + const head = + !isMethod && node.type === 'ArrowFunctionExpression' + ? context.sourceCode.getTokenBefore( + node.body, + (token) => token.value === '=>', + ) + : declaration; + if (!node.range || !node.loc || !head?.range) + throw new Error('Parser did not provide ranges'); + functions.push({ + head: head.range[0], + start: node.range[0], + end: node.range[1], + report: { + file: name, + function: functionName(node, context), + line: node.loc.start.line, + column: node.loc.start.column + 1, + complexity: 0, + statements: 0, + coveredStatements: 0, + statementCoverage: null, + crap: null, + }, + }); + }, + }; + }, + }, + }, + }, + }, + rules: { + 'metrics/functions': 'error', + complexity: ['error', { max: 0, variant: 'classic' }], + }, + }, + { filename: basename(absoluteFile) }, + ); + for (const message of messages) { + if (message.fatal || (message.severity === 2 && message.ruleId !== 'complexity')) { + throw new Error(`${file}:${message.line}: ${message.message}`); + } + // ESLint also measures implicit class initializers, which are not source functions. + if ( + message.ruleId !== 'complexity' || + !FUNCTION_TYPES.has(message.nodeType ?? '') || + /^Class (?:field initializer|static block)/u.test(message.message) + ) { + continue; + } + const point = offset({ line: message.line, column: message.column - 1 }); + const owner = functions.find((fn) => fn.head === point); + const complexity = /has a complexity of (\d+)\./u.exec(message.message)?.[1]; + if (!owner || !complexity || owner.report.complexity !== 0) { + throw new Error(`${file}: unrecognized ESLint complexity diagnostic: ${message.message}`); + } + owner.report.complexity = Number(complexity); + } + if (functions.some(({ report }) => report.complexity === 0)) { + throw new Error(`${file}: ESLint did not measure every function`); + } + if (coverage !== undefined) { + if ( + !isRecord(coverage) || + typeof coverage.path !== 'string' || + resolve(coverage.path) !== absoluteFile || + !isRecord(coverage.statementMap) || + !isRecord(coverage.s) + ) { + throw new Error(`${file}: invalid coverage entry or source path mismatch`); + } + const statements = Object.entries(coverage.statementMap); + if (statements.length !== Object.keys(coverage.s).length) { + throw new Error(`${file}: statement map/counter mismatch`); + } + for (const [id, location] of statements) { + const hits = coverage.s[id]; + if ( + !isRecord(location) || + typeof hits !== 'number' || + !Number.isSafeInteger(hits) || + hits < 0 + ) { + throw new Error(`${file}: invalid statement or counter ${id}`); + } + const start = offset(location.start); + const end = offset(location.end); + if (start >= end) throw new Error(`${file}: invalid statement range ${id}`); + // The declaration of a nested function belongs to its parent; its body does not. + const owner = functions.findLast((fn) => fn.start <= start && end <= fn.end); + if (!owner) continue; + owner.report.statements++; + if (hits > 0) owner.report.coveredStatements++; + } + } + return functions.map(({ report }) => { + report.statementCoverage = report.statements + ? report.coveredStatements / report.statements + : null; + report.crap = crapScore(report.complexity, report.statementCoverage); + return report; + }); +} + +export function createReport(coverageFile: string, sources: string[] = [FAVORITES]) { + const coverage: unknown = JSON.parse(readFileSync(resolve(coverageFile), 'utf8')); + if (!isRecord(coverage)) throw new Error('Expected an Istanbul coverage object'); + const entries = new Map(); + for (const [file, entry] of Object.entries(coverage)) { + const key = resolve(file); + if (entries.has(key)) throw new Error(`Duplicate coverage path: ${file}`); + entries.set(key, entry); + } + return { + complexityMetric: 'eslint/classic', + coverageMetric: 'per-function statement fraction (nested functions excluded)', + functions: [...new Set(sources.map((file) => resolve(file)))].flatMap((file) => + measureFunctions(file, readFileSync(file, 'utf8'), entries.get(file)), + ), + }; +} + +if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + try { + const { values, positionals } = parseArgs({ + allowPositionals: true, + options: { output: { type: 'string', short: 'o' }, help: { type: 'boolean', short: 'h' } }, + }); + if (values.help) { + console.log( + 'Usage: node scripts/crap.mts [--output path] [source.ts ...]', + ); + } else { + const [coverageFile, ...sources] = positionals; + if (!coverageFile) throw new Error('Expected coverage-final.json; use --help for usage'); + const files = sources.length ? sources : [FAVORITES]; + const output = resolve(values.output ?? join(dirname(resolve(coverageFile)), 'crap.json')); + if ([coverageFile, ...files].some((file) => resolve(file) === output)) { + throw new Error('Output must not overwrite coverage or source files'); + } + const report = createReport(coverageFile, files); + mkdirSync(dirname(output), { recursive: true }); + writeFileSync(output, `${JSON.stringify(report, null, 2)}\n`); + console.log( + 'Advisory CRAP baseline | CC: ESLint classic | Coverage: statements, excluding nested functions', + ); + console.log('File\tFunction\tLine:Column\tCC\tStatement coverage\tCRAP'); + for (const row of report.functions) { + const coverage = + row.statementCoverage === null + ? 'N/A' + : `${(row.statementCoverage * 100).toFixed(1)}% (${row.coveredStatements}/${row.statements})`; + console.log( + `${row.file}\t${row.function}\t${row.line}:${row.column}\t${row.complexity}\t${coverage}\t${row.crap?.toFixed(2) ?? 'N/A'}`, + ); + } + console.log(`JSON: ${output}`); + } + } catch (error) { + console.error(`crap: ${error instanceof Error ? error.message : String(error)}`); + process.exitCode = 1; + } +} diff --git a/scripts/crap.test.mts b/scripts/crap.test.mts new file mode 100644 index 0000000000..180853e959 --- /dev/null +++ b/scripts/crap.test.mts @@ -0,0 +1,236 @@ +import { test } from 'node:test'; +import { tmpdir } from 'node:os'; +import { join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { strict as assert } from 'node:assert'; +import { spawnSync } from 'node:child_process'; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; + +import { crapScore, createReport, measureFunctions } from './crap.mts'; + +const FILE = resolve('sample.ts'); +const SCRIPT = fileURLToPath(new URL('./crap.mts', import.meta.url)); + +function coverageFor(source: string, statements: [string, number][], file = FILE) { + const position = (index: number) => { + const lines = source.slice(0, index).split('\n'); + return { line: lines.length, column: lines.at(-1)!.length }; + }; + return { + path: file, + statementMap: Object.fromEntries( + statements.map(([text], id) => { + const start = source.indexOf(text); + assert.notEqual(start, -1, `Missing fixture statement: ${text}`); + return [id, { start: position(start), end: position(start + text.length) }]; + }), + ), + s: Object.fromEntries(statements.map(([, hits], id) => [id, hits])), + }; +} + +test('CRAP uses statement coverage fractions and preserves unknown coverage', () => { + assert.equal(crapScore(4, 1), 4); + assert.equal(crapScore(4, 0), 20); + assert.equal(crapScore(3, 0.5), 4.125); + assert.equal(crapScore(4, null), null); + for (const coverage of [-1, 1.1, NaN, Infinity]) { + assert.throws(() => crapScore(4, coverage), /fraction/); + } + for (const complexity of [0, -1, 1.5, Infinity]) { + assert.throws(() => crapScore(complexity, 1), /positive integer/); + } +}); + +test('real ESLint counts classic switch cases, logical and conditional branches in TypeScript', () => { + const source = `/* eslint complexity: off */ +function choose(value: number): number { + switch (value) { + case 0: return 0; + case 1: return 1; + default: return value > 2 && value < 5 ? 2 : -1; + } +}`; + const [row] = measureFunctions(FILE, source); + assert.equal(row.function, 'choose'); + assert.equal(row.line, 2); + assert.equal(row.complexity, 5); + assert.equal(row.statementCoverage, null); + assert.equal(row.crap, null); +}); + +test('nested functions own only their statements, including when sharing a line', () => { + for (const separator of [' ', '\n']) { + const source = `const outer = () => {${separator}const inner = () => 1;${separator}return inner(); };`; + const coverage = coverageFor(source, [ + ['const inner = () => 1;', 0], + ['1', 1], + ['return inner();', 0], + ]); + const [outer, inner] = measureFunctions(FILE, source, coverage); + assert.equal(outer.function, 'outer'); + assert.equal(outer.complexity, 1); + assert.equal(outer.statements, 2); + assert.equal(outer.coveredStatements, 0); + assert.equal(outer.statementCoverage, 0); + assert.equal(outer.crap, 2); + assert.equal(inner.function, 'inner'); + assert.equal(inner.statements, 1); + assert.equal(inner.statementCoverage, 1); + assert.equal(inner.crap, inner.complexity); + } +}); + +test('nested branches and implicit class initializers do not inflate enclosing complexity', () => { + const source = `function outer() { + class Box { + value = true ? 1 : 2; + handler = () => false ? 1 : 2; + } + return Box; +}`; + const rows = measureFunctions(FILE, source); + assert.deepEqual( + rows.map((row) => [row.function, row.complexity]), + [ + ['outer', 1], + ['handler', 2], + ], + ); +}); + +test('attributes method diagnostics reported before the function node itself', () => { + const source = `const box = { + read(value: number) { if (value) return 1; return 0; }, + write: (value: number) => value ? 1 : 0, +};`; + const rows = measureFunctions(FILE, source); + assert.deepEqual( + rows.map((row) => [row.function, row.complexity]), + [ + ['read', 2], + ['write', 2], + ], + ); +}); + +test('missing or empty instrumentation is N/A, not 100%; explicit zero hits stays zero', () => { + const source = 'const value = 1; function read() { return value; }'; + const missing = [ + undefined, + coverageFor(source, []), + coverageFor(source, [['const value = 1;', 1]]), + ]; + for (const coverage of missing) { + const [row] = measureFunctions(FILE, source, coverage); + assert.equal(row.statements, 0); + assert.equal(row.statementCoverage, null); + assert.equal(row.crap, null); + } + const [zero] = measureFunctions(FILE, source, coverageFor(source, [['return value;', 0]])); + assert.equal(zero.statementCoverage, 0); + assert.equal(zero.crap, 2); +}); + +test('rejects malformed coverage, counter mismatches and stale out-of-source ranges', () => { + const source = 'function read() { return 1; }'; + const valid = coverageFor(source, [['return 1;', 1]]); + const bad = [ + null, + [], + {}, + { ...valid, path: resolve('other.ts') }, + { ...valid, s: {} }, + { ...valid, s: { 1: 1 } }, + { ...valid, s: { 0: -1 } }, + { ...valid, s: { 0: 0.5 } }, + { ...valid, s: { 0: '1' } }, + ...[ + { line: 0, column: 0 }, + { line: 2, column: 0 }, + { line: 1, column: source.length + 1 }, + { line: 1, column: -1 }, + { line: 1, column: 1.5 }, + { line: 1, column: source.length }, + ].map((start) => ({ ...valid, statementMap: { 0: { ...valid.statementMap[0], start } } })), + ]; + for (const coverage of bad) { + assert.throws(() => measureFunctions(FILE, source, coverage), /invalid|mismatch|outside/i); + } + assert.throws(() => measureFunctions(FILE, 'function broken('), /Parsing error/); +}); + +test('validates CRLF source locations', () => { + const source = 'function read() {\r\n return 1;\r\n}'; + const coverage = { + path: FILE, + statementMap: { 0: { start: { line: 2, column: 2 }, end: { line: 2, column: 11 } } }, + s: { 0: 1 }, + }; + assert.equal(measureFunctions(FILE, source, coverage)[0].statementCoverage, 1); +}); + +test('CLI resolves source/output paths from package cwd and writes advisory JSON', (t) => { + const directory = mkdtempSync(join(tmpdir(), 'crap-')); + t.after(() => rmSync(directory, { recursive: true, force: true })); + const cwd = join(directory, 'package'); + const file = join(cwd, 'src', 'favorite.ts'); + const coverageFile = join(cwd, 'coverage', 'favorites', 'coverage-final.json'); + mkdirSync(join(cwd, 'src'), { recursive: true }); + mkdirSync(join(cwd, 'coverage', 'favorites'), { recursive: true }); + const source = 'export function read(): number { return 1; }'; + writeFileSync(file, source); + writeFileSync( + coverageFile, + JSON.stringify({ [file]: coverageFor(source, [['return 1;', 0]], file) }), + ); + const run = (...args: string[]) => + spawnSync(process.execPath, [SCRIPT, ...args], { cwd, encoding: 'utf8' }); + const result = run('coverage/favorites/coverage-final.json', 'src/favorite.ts'); + assert.equal(result.status, 0, result.stderr); + assert.match(result.stdout, /Advisory CRAP/); + assert.match(result.stdout, /0\.0% \(0\/1\)\s+2\.00/); + const report = JSON.parse(readFileSync(join(cwd, 'coverage', 'favorites', 'crap.json'), 'utf8')); + assert.deepEqual(report, createReport(coverageFile, [file])); + assert.equal(report.functions[0].file, file); + assert.equal(run('--output', 'custom/crap.json', coverageFile, file).status, 0); + assert.deepEqual(JSON.parse(readFileSync(join(cwd, 'custom', 'crap.json'), 'utf8')), report); + assert.equal(run('--output', file, coverageFile, file).status, 1); + assert.equal(readFileSync(file, 'utf8'), source); + assert.throws(() => createReport(join(directory, 'missing.json'), [file]), /ENOENT/); + assert.throws(() => createReport(coverageFile, [join(directory, 'missing.ts')]), /ENOENT/); + writeFileSync(coverageFile, '{}'); + const unknown = run(coverageFile, file); + assert.equal(unknown.status, 0, unknown.stderr); + assert.match(unknown.stdout, /N\/A\s+N\/A/); + const defaults = run(coverageFile); + assert.equal(defaults.status, 0, defaults.stderr); + const defaultReport = JSON.parse( + readFileSync(join(cwd, 'coverage', 'favorites', 'crap.json'), 'utf8'), + ); + assert.ok(defaultReport.functions.length > 0); + assert.ok( + defaultReport.functions.every( + (row: { file: string }) => row.file === 'packages/data-schemas/src/methods/favorite.ts', + ), + ); + writeFileSync(coverageFile, '{invalid'); + assert.equal(run(coverageFile, file).status, 1); + writeFileSync(coverageFile, '[]'); + assert.throws(() => createReport(coverageFile, [file]), /Istanbul coverage object/); +}); + +test('importing the reporter does not execute its CLI', () => { + const result = spawnSync( + process.execPath, + [ + '--input-type=module', + '-e', + `import ${JSON.stringify(new URL('./crap.mts', import.meta.url).href)}`, + ], + { encoding: 'utf8' }, + ); + assert.equal(result.status, 0, result.stderr); + assert.equal(result.stdout, ''); + assert.equal(result.stderr, ''); +});