From b8f1f4e2c3137a9e248df2f83780a0bdf1ddc842 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 29 Sep 2026 23:06:31 +0100 Subject: [PATCH 01/23] build: add the package, lint, test, mutation, release and CI tooling --- .github/workflows/ci.yml | 224 + .github/workflows/determine-runner.yml | 31 + .gitignore | 4 + .husky/commit-msg | 1 + .husky/pre-commit | 1 + .husky/pre-push | 1 + AGENTS.md | 1 + CLAUDE.md | 1 + commitlint.config.ts | 12 + eslint.config.ts | 26 + lint-staged.config.ts | 7 + package.json | 91 + pnpm-lock.yaml | 6797 ++++++++++++++++++++++++ pnpm-workspace.yaml | 6 + release.config.ts | 60 + stryker.config.ts | 18 + tsconfig.json | 33 + tsdown.config.ts | 14 + vitest.config.ts | 17 + 19 files changed, 7345 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/determine-runner.yml create mode 100755 .husky/commit-msg create mode 100755 .husky/pre-commit create mode 100755 .husky/pre-push create mode 120000 AGENTS.md create mode 120000 CLAUDE.md create mode 100644 commitlint.config.ts create mode 100644 eslint.config.ts create mode 100644 lint-staged.config.ts create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 release.config.ts create mode 100644 stryker.config.ts create mode 100644 tsconfig.json create mode 100644 tsdown.config.ts create mode 100644 vitest.config.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..710060a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,224 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +# Cancels a superseded run when a branch or PR gets pushed to again. Scoped away from main so a merge landing mid-release never kills the release job, which runs solely on a push to main. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +permissions: + contents: read + +jobs: + determine-runner: + uses: ./.github/workflows/determine-runner.yml + + commitlint: + name: Commitlint + needs: determine-runner + runs-on: ${{ fromJson(needs.determine-runner.outputs.selected-runner) }} + timeout-minutes: 10 + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v7 + with: + node-version: '22' + cache: pnpm + # Not wagoid/commitlint-github-action: it is a Docker container action, and the self-hosted fleet has no Docker daemon. + - run: pnpm install --frozen-lockfile + - name: Lint the commits of a pull request + if: github.event_name == 'pull_request' + run: pnpm exec commitlint --from "$BASE_SHA" --to "$HEAD_SHA" + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + - name: Lint the last commit of a push + if: github.event_name != 'pull_request' + run: pnpm exec commitlint --last + + lint: + name: Lint + needs: determine-runner + runs-on: ${{ fromJson(needs.determine-runner.outputs.selected-runner) }} + timeout-minutes: 10 + steps: + - uses: actions/checkout@v7 + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v7 + with: + node-version: '22' + cache: pnpm + - uses: actions/cache@v6 + with: + path: .eslintcache + # eslint.config.ts and pnpm-lock.yaml both invalidate the cache: a rule change or a dependency bump can change lint results even for files whose own content did not change. + key: eslintcache-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.ts') }} + restore-keys: eslintcache-${{ runner.os }}- + - run: pnpm install --frozen-lockfile + # The check-only script: `pnpm lint` fixes in place, so it would exit 0 on a fixable violation that never reaches the commit. + - run: pnpm lint:check + + typecheck: + name: Typecheck + needs: determine-runner + runs-on: ${{ fromJson(needs.determine-runner.outputs.selected-runner) }} + timeout-minutes: 10 + steps: + - uses: actions/checkout@v7 + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v7 + with: + node-version: '22' + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm typecheck + - run: pnpm test + - run: pnpm build + - run: pnpm exec publint + - run: pnpm exec attw --pack + + mutation: + name: Mutation + needs: determine-runner + runs-on: ${{ fromJson(needs.determine-runner.outputs.selected-runner) }} + timeout-minutes: 60 + steps: + - uses: actions/checkout@v7 + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v7 + with: + node-version: '22' + cache: pnpm + - run: pnpm install --frozen-lockfile + # Content-diff based (Stryker's IncrementalDiffer text-diffs stored against current source, never hash, mtime or git ref), so restoring from an unrelated prior run is always safe: a mismatch only means bigger diffs and more reruns, never a wrongly skipped mutant. + - name: Restore Stryker incremental result cache + uses: actions/cache/restore@v6 + with: + path: reports/stryker-incremental.json + key: stryker-incremental-${{ github.run_id }} + restore-keys: | + stryker-incremental- + # Stryker only diffs mutated and test files, so a change outside that scope (config, fixture data, an env var) is invisible to the incremental differ. --force on push to main (never on a PR) rebuilds the incremental file from a known-clean baseline so that blind spot cannot compound. + - run: pnpm test:mutation${{ github.ref == 'refs/heads/main' && ' --force' || '' }} + - name: Save Stryker incremental result cache + if: always() + uses: actions/cache/save@v6 + with: + path: reports/stryker-incremental.json + key: stryker-incremental-${{ github.run_id }} + + # The packed tarball, not the source tree, is what consumers install: this proves the published files, exports map and peer dependency work in a clean project on every Node line the package supports and on both cosmiconfig majors. The toolchain needs Node 22, so the package is built and packed on 22 and the consumer check runs on the Node under test. + package: + name: Package (Node ${{ matrix.node }}, cosmiconfig ${{ matrix.cosmiconfig }}) + needs: determine-runner + runs-on: ${{ fromJson(needs.determine-runner.outputs.selected-runner) }} + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + include: + - { node: '20', cosmiconfig: '9' } + - { node: '22', cosmiconfig: '9' } + - { node: '22', cosmiconfig: '10' } + - { node: '24', cosmiconfig: '9' } + - { node: '24', cosmiconfig: '10' } + steps: + - uses: actions/checkout@v7 + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v7 + with: + node-version: '22' + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm build + - run: pnpm pack --pack-destination "$RUNNER_TEMP/pack" + - uses: actions/setup-node@v7 + with: + node-version: ${{ matrix.node }} + - name: Install the tarball into a scratch project + run: | + mkdir "$RUNNER_TEMP/scratch" + cd "$RUNNER_TEMP/scratch" + npm init -y + npm install "$RUNNER_TEMP"/pack/*.tgz "cosmiconfig@${{ matrix.cosmiconfig }}" + - name: Load a config with extends through the installed package, as ESM and as CommonJS + working-directory: ${{ runner.temp }}/scratch + run: | + printf 'export default { a: "base", b: "base" };\n' > base.ts + printf 'export default { extends: "./base.ts", b: "config" };\n' > my-tool.config.ts + cat > check.mjs <<'EOF' + import assert from 'node:assert/strict'; + import { createExplorer } from 'cosmiconfig-extends'; + + const result = await createExplorer('my-tool').search(process.cwd()); + assert.deepEqual(result?.config, { a: 'base', b: 'config' }); + EOF + cat > check.cjs <<'EOF' + const assert = require('node:assert/strict'); + const { createExplorer } = require('cosmiconfig-extends'); + + createExplorer('my-tool') + .search(process.cwd()) + .then((result) => assert.deepEqual(result?.config, { a: 'base', b: 'config' })); + EOF + node check.mjs + node check.cjs + + required-checks: + name: Required Checks + needs: [determine-runner, commitlint, lint, typecheck, package, mutation] + # Always runs, even if a dependency failed, so it reports that failure itself rather than being skipped: a required check that is silently absent blocks a PR indefinitely instead of failing it visibly. + if: always() + runs-on: ${{ fromJson(needs.determine-runner.outputs.selected-runner) }} + timeout-minutes: 5 + steps: + - name: Fail if any required job failed or was cancelled + run: | + if [[ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" == "true" ]]; then + echo "One or more required jobs did not succeed:" + echo '${{ toJSON(needs) }}' + exit 1 + fi + + release: + name: Release + needs: [commitlint, lint, typecheck, package, mutation] + if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') + # Deliberately a GitHub-hosted runner, not the resolved one: npm trusted publishing only works from GitHub-hosted runners, so the OIDC token exchange cannot happen on the self-hosted fleet or Blacksmith. + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: write # to push the release commit and tag and create the GitHub Release + issues: write # to comment on released issues + pull-requests: write # to comment on released pull requests + id-token: write # OIDC identity for npm trusted publishing and provenance (no NPM_TOKEN) + steps: + - uses: actions/checkout@v7 + with: + # semantic-release analyses the full commit history since the last release. + fetch-depth: 0 + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v7 + with: + node-version: '22' + cache: pnpm + # registry-url is deliberately absent: setup-node would otherwise write an .npmrc _authToken line that wins over the OIDC token exchange, breaking trusted publishing. + - run: pnpm install --frozen-lockfile + - name: Upgrade npm for OIDC trusted publishing (needs npm CLI >=11.5.1) + run: npm install -g npm@latest + - name: Release + # HUSKY=0 so the commit-msg hook never fires against the automated release commit. + run: HUSKY=0 pnpm exec semantic-release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Blanked, not omitted: an inherited NPM_TOKEN or NODE_AUTH_TOKEN would otherwise be used in preference to the OIDC exchange. + NPM_TOKEN: '' + NODE_AUTH_TOKEN: '' diff --git a/.github/workflows/determine-runner.yml b/.github/workflows/determine-runner.yml new file mode 100644 index 0000000..fbe5cbf --- /dev/null +++ b/.github/workflows/determine-runner.yml @@ -0,0 +1,31 @@ +name: Determine runner + +# The one runner-selection job every job in ci.yml waits on (via workflow_call) instead of hard-coding a runner: ExaDev's self-hosted fleet first, falling back to Blacksmith if the fleet's heartbeat is stale. The decision is made by ExaDev/runner-fallback-action, which reads a secret gist refreshed by the fleet's heartbeat (exadev/github-runner); no token, no secret, no per-repo setup. The action also forces the fallback for Dependabot and fork PRs so untrusted code never runs on the fleet. +# +# The resolver itself runs on Blacksmith's smallest runner, not ubuntu-latest and not the fleet. This job decides whether the fleet can be used at all, so it must sit on infrastructure independent of both: on the fleet, a fleet-wide outage deadlocks every workflow at this job because the fallback can never engage; on ubuntu-latest, the org's recurring GitHub-hosted billing block stops the job before it starts. Blacksmith is billed outside GitHub's spending limit. +# +# The fallback is Blacksmith rather than the action's ubuntu-latest default for the same reason: routing dependent jobs to a pool that cannot start would recreate the deadlock one hop out. The preferred label is the action's default (exadev-runners, the fleet's shared runs-on label), so no `preferred-labels` override is set and routing returns to the fleet whenever the heartbeat is current. +# +# The action is pinned to a commit SHA, not a branch: b774d51 is the commit that introduced the secret-gist design. Bump deliberately when a newer commit is wanted. +on: + workflow_call: + outputs: + selected-runner: + description: "A JSON array of runner labels for fromJson at the call site." + value: ${{ jobs.determine-runner.outputs.selected-runner }} + preferred-available: + description: "Whether the preferred (fleet) runner was reachable." + value: ${{ jobs.determine-runner.outputs.preferred-available }} + +jobs: + determine-runner: + runs-on: blacksmith-2vcpu-ubuntu-2404 + timeout-minutes: 5 + outputs: + selected-runner: ${{ steps.fallback.outputs.selected-runner || '["blacksmith-2vcpu-ubuntu-2404"]' }} + preferred-available: ${{ steps.fallback.outputs.preferred-available || 'false' }} + steps: + - id: fallback + uses: ExaDev/runner-fallback-action@b774d51ff5360bf53705b2eb34ff019afd4d7f07 + with: + fallback-labels: blacksmith-2vcpu-ubuntu-2404 diff --git a/.gitignore b/.gitignore index 17d206f..8694db8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,7 @@ coverage/ .turbo/ reports/ .stryker-tmp/ + +# The instruction files are tracked symlinks to README.md; a global ignore may otherwise exclude them. +!CLAUDE.md +!AGENTS.md diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100755 index 0000000..e81b051 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1 @@ +npx commitlint --edit "$1" diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..2312dc5 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 0000000..64d4088 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1 @@ +npx pnpm run typecheck && npx pnpm run test && npx pnpm run build diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 0000000..42061c0 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +README.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..42061c0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +README.md \ No newline at end of file diff --git a/commitlint.config.ts b/commitlint.config.ts new file mode 100644 index 0000000..1da445e --- /dev/null +++ b/commitlint.config.ts @@ -0,0 +1,12 @@ +import { RuleConfigSeverity, type UserConfig } from '@commitlint/types'; + +import { commitTypes } from './release.config'; + +const config: UserConfig = { + extends: ['@commitlint/config-conventional'], + rules: { + 'type-enum': [RuleConfigSeverity.Error, 'always', commitTypes.map((t) => t.type)], + }, +}; + +export default config; diff --git a/eslint.config.ts b/eslint.config.ts new file mode 100644 index 0000000..cade828 --- /dev/null +++ b/eslint.config.ts @@ -0,0 +1,26 @@ +import { type Config, defineConfig } from 'eslint/config'; +import js from '@eslint/js'; +import exadev from '@exadev/eslint-config'; + +const config: Config[] = defineConfig( + { + ignores: [ + 'dist', + 'coverage', + 'node_modules', + 'reports', + '.stryker-tmp', + // Fixtures the type-level tests compile on purpose to observe their errors; the project's own typecheck and lint must not see them. + 'test/types/fixtures', + ], + }, + { + languageOptions: { + parserOptions: { project: './tsconfig.json', tsconfigRootDir: import.meta.dirname }, + }, + }, + { ...js.configs.recommended, files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'] }, + ...exadev, +); + +export default config; diff --git a/lint-staged.config.ts b/lint-staged.config.ts new file mode 100644 index 0000000..d667c0e --- /dev/null +++ b/lint-staged.config.ts @@ -0,0 +1,7 @@ +import type { Configuration } from 'lint-staged'; + +const config: Configuration = { + '*.ts': 'eslint --fix', +}; + +export default config; diff --git a/package.json b/package.json new file mode 100644 index 0000000..14274e0 --- /dev/null +++ b/package.json @@ -0,0 +1,91 @@ +{ + "name": "@exadev/config", + "description": "The unified exadev.config.ts: typed sections contributed by tools, resolved from one file or a standalone file per tool", + "version": "0.0.0", + "bin": { + "exadev-config": "./dist/cli.js" + }, + "dependencies": { + "@standard-schema/spec": "1.1.0", + "cosmiconfig-extends": "1.0.0" + }, + "devDependencies": { + "@arethetypeswrong/cli": "0.18.5", + "@commitlint/cli": "21.2.3", + "@commitlint/config-conventional": "21.2.3", + "@commitlint/types": "21.2.3", + "@eslint/js": "10.0.1", + "@exadev/eslint-config": "2.23.0", + "@semantic-release/changelog": "7.0.0", + "@semantic-release/git": "11.0.1", + "@stryker-mutator/api": "10.0.0", + "@stryker-mutator/core": "10.0.0", + "@stryker-mutator/typescript-checker": "10.0.0", + "@stryker-mutator/vitest-runner": "10.0.0", + "@types/node": "26.6.3", + "@vitest/coverage-v8": "4.1.11", + "cosmiconfig": "10.0.1", + "eslint": "10.11.0", + "husky": "9.1.7", + "lint-staged": "17.6.0", + "publint": "0.3.24", + "semantic-release": "25.0.9", + "tsdown": "0.23.0", + "typescript": "6.0.3", + "typescript-eslint": "8.71.0", + "vitest": "4.1.11", + "zod": "4.6.5" + }, + "engines": { + "node": ">=20" + }, + "exports": { + ".": { + "types": { + "import": "./dist/index.d.ts", + "require": "./dist/index.d.cts" + }, + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "files": [ + "dist" + ], + "keywords": [ + "config", + "cosmiconfig", + "exadev", + "standard-schema", + "typescript" + ], + "license": "MIT", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "packageManager": "pnpm@12.4.1+sha512.2e81e399d73fe8390dab25e06aa788ab7a5908248d2f5a370f82b481147a6a7a367bf8048f9a6fdb6460f21a66f0542dedb8b94ca2c8723596741920b1656d4c", + "peerDependencies": { + "cosmiconfig": "^9.0.0 || ^10.0.0" + }, + "publishConfig": { + "access": "public", + "provenance": true + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ExaDev/config.git" + }, + "scripts": { + "build": "tsdown", + "lint": "eslint . --fix --cache --max-warnings 0", + "lint:check": "eslint . --cache --max-warnings 0", + "prepare": "husky", + "prepublishOnly": "pnpm run lint:check && pnpm run typecheck && pnpm run test && tsdown && publint && attw --pack", + "release": "semantic-release", + "test": "vitest run", + "test:mutation": "stryker run stryker.config.ts", + "typecheck": "tsc -p tsconfig.json" + }, + "sideEffects": false, + "type": "module", + "types": "./dist/index.d.ts" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..8d6638e --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,6797 @@ +--- +lockfileVersion: '9.0' + +importers: + + .: + configDependencies: {} + packageManagerDependencies: + pnpm: + specifier: 12.4.1 + version: 12.4.1 + +packages: + + '@pnpm/exe.android-arm64@12.4.1': + resolution: {integrity: sha512-/HwsqXMSmlOfgtV9+O0ratzjV6Vd/8n1hh4rGHpCGmDURvt52MwZxdbhyxP4K03ZfvgSDvotFPr8O+RzE5Eu8A==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.4.1': + resolution: {integrity: sha512-+l74Qb4c2YjOzNKHXJLg+1wr8xHM1ckkUhnU5KRUK9TJqiiczt6yeTZqqzHIlJ8i6pAoj5V0OJevDRwnQKLgrQ==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.4.1': + resolution: {integrity: sha512-6rkZkT3iGfaxknUdGHraqSWFvTa6N0ajAHluv9Ax0GRWs0sIcGNiFhDopv6xSZCsJZmG483aNS/b6UEDy3blfw==} + cpu: [arm64] + os: [darwin] + + '@pnpm/exe.darwin-x64@12.4.1': + resolution: {integrity: sha512-Vb1CHlR88HghC1qUxjxjs82zQSXnXacPD+btG2CmG8Q/hBU7Q0/b2YWJKSQqZXicunV5khFtfTlAwJddV9RkYA==} + cpu: [x64] + os: [darwin] + + '@pnpm/exe.freebsd-x64@12.4.1': + resolution: {integrity: sha512-iT3iHz3Nl0Sxxj7UPOtZ/aQ81AFsQhjoeWMAlPkSRO04gsgDGAXv3UYxOFaesMWsfNxaGn0A+CITbuCHFF66FA==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.4.1': + resolution: {integrity: sha512-aBooZfNXM5f+OGUgCAMFWpE/kAhsWfvmqIyMtHy6zl3aNxyInWrcY/Saln/UElzo7lZWMC0Yktroxd/2J26lNQ==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-arm64@12.4.1': + resolution: {integrity: sha512-TlOdacTTP09BgcMvwWBFRsu8VAjfwqwnslBj+XSq1JFM3ck4f3k+1O/747EuctxZxs3/o785b6Q3s7Pd92Ptsg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-ppc64@12.4.1': + resolution: {integrity: sha512-r/ab/MlIBo75oizUP5ITiziCCrnXz4SJwfErLQ+603AshB+Yq7xTMCoMtzTSCAMu6aaymIKkAFtZvd2J75Wq0w==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.4.1': + resolution: {integrity: sha512-C/D1QWdKMiB8+wv/spl1rGITFUuqC+aI/fb6h8NB5sqxsOaGXeYc/g891oCuzggHn6SODk9p+I09hI76x/cMNw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.4.1': + resolution: {integrity: sha512-nxz5zD4yXt94uzbStDk0QTPKW+aE92hH1b5tFXK9ctB1HE9Xcq1vjTiA2LsZMFC6p4gdu5OwQeFqYNAVGa6QlA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.4.1': + resolution: {integrity: sha512-5AwgFdGhVUg2kIweYGfxzSLEHiIG77PQhZAkXC3TwofQHsu1Wr+TrV5/rNX2PopFnHRzuE581zoB8F6Wle32yg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-x64@12.4.1': + resolution: {integrity: sha512-FJOZuuuQMhp0oLzBtcKkLXknBI92hfkmSnlKc47vfin4HrmfID5khY2lGekL9tCzk1cpR+HShEw/meFl+nHtzQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.win32-arm64@12.4.1': + resolution: {integrity: sha512-OO7eKBL9S+xk5hRy+JUUZSNJknGuGe3GEUffsrlC2LbqKF02g+VX1anGIzSbJDvkkdnpJhSlxF5AUz2JzJu2Jw==} + cpu: [arm64] + os: [win32] + + '@pnpm/exe.win32-x64@12.4.1': + resolution: {integrity: sha512-x7gJHZgHo6hp354xCYA2NvoFzYJkHwovt2kVsUBK5EmXULLcP51JGMq09CX+5FRFJUZFKoXjLu3mZWmfP7o6PQ==} + cpu: [x64] + os: [win32] + + pnpm@12.4.1: + resolution: {integrity: sha512-LoHjmdc/6DkNqyXgaqeIq3pZCCSNL1o3D4K0gRR6ano2e/gEj5pv22Rg8hpm8FQt7bi5TKLIcjWWdBkgsWVtTA==} + engines: {node: '>=18.*'} + hasBin: true + +snapshots: + + '@pnpm/exe.android-arm64@12.4.1': + optional: true + + '@pnpm/exe.android-x64@12.4.1': + optional: true + + '@pnpm/exe.darwin-arm64@12.4.1': + optional: true + + '@pnpm/exe.darwin-x64@12.4.1': + optional: true + + '@pnpm/exe.freebsd-x64@12.4.1': + optional: true + + '@pnpm/exe.linux-arm64-musl@12.4.1': + optional: true + + '@pnpm/exe.linux-arm64@12.4.1': + optional: true + + '@pnpm/exe.linux-ppc64@12.4.1': + optional: true + + '@pnpm/exe.linux-riscv64@12.4.1': + optional: true + + '@pnpm/exe.linux-s390x@12.4.1': + optional: true + + '@pnpm/exe.linux-x64-musl@12.4.1': + optional: true + + '@pnpm/exe.linux-x64@12.4.1': + optional: true + + '@pnpm/exe.win32-arm64@12.4.1': + optional: true + + '@pnpm/exe.win32-x64@12.4.1': + optional: true + + pnpm@12.4.1: + optionalDependencies: + '@pnpm/exe.android-arm64': 12.4.1 + '@pnpm/exe.android-x64': 12.4.1 + '@pnpm/exe.darwin-arm64': 12.4.1 + '@pnpm/exe.darwin-x64': 12.4.1 + '@pnpm/exe.freebsd-x64': 12.4.1 + '@pnpm/exe.linux-arm64': 12.4.1 + '@pnpm/exe.linux-arm64-musl': 12.4.1 + '@pnpm/exe.linux-ppc64': 12.4.1 + '@pnpm/exe.linux-riscv64': 12.4.1 + '@pnpm/exe.linux-s390x': 12.4.1 + '@pnpm/exe.linux-x64': 12.4.1 + '@pnpm/exe.linux-x64-musl': 12.4.1 + '@pnpm/exe.win32-arm64': 12.4.1 + '@pnpm/exe.win32-x64': 12.4.1 + +--- +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@standard-schema/spec': + specifier: 1.1.0 + version: 1.1.0 + cosmiconfig-extends: + specifier: 1.0.0 + version: 1.0.0(cosmiconfig@10.0.1) + devDependencies: + '@arethetypeswrong/cli': + specifier: 0.18.5 + version: 0.18.5 + '@commitlint/cli': + specifier: 21.2.3 + version: 21.2.3(@types/node@26.6.3)(conventional-commits-parser@7.1.2)(typescript@6.0.3) + '@commitlint/config-conventional': + specifier: 21.2.3 + version: 21.2.3 + '@commitlint/types': + specifier: 21.2.3 + version: 21.2.3 + '@eslint/js': + specifier: 10.0.1 + version: 10.0.1(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0)) + '@exadev/eslint-config': + specifier: 2.23.0 + version: 2.23.0(@eslint/json@2.1.0)(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript-eslint@8.71.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(typescript@6.0.3) + '@semantic-release/changelog': + specifier: 7.0.0 + version: 7.0.0(semantic-release@25.0.9(supports-color@7.2.0)(typescript@6.0.3)) + '@semantic-release/git': + specifier: 11.0.1 + version: 11.0.1(semantic-release@25.0.9(supports-color@7.2.0)(typescript@6.0.3))(supports-color@7.2.0) + '@stryker-mutator/api': + specifier: 10.0.0 + version: 10.0.0 + '@stryker-mutator/core': + specifier: 10.0.0 + version: 10.0.0(@types/node@26.6.3) + '@stryker-mutator/typescript-checker': + specifier: 10.0.0 + version: 10.0.0(@stryker-mutator/core@10.0.0(@types/node@26.6.3))(typescript@6.0.3) + '@stryker-mutator/vitest-runner': + specifier: 10.0.0 + version: 10.0.0(@stryker-mutator/core@10.0.0(@types/node@26.6.3))(vitest@4.1.11) + '@types/node': + specifier: 26.6.3 + version: 26.6.3 + '@vitest/coverage-v8': + specifier: 4.1.11 + version: 4.1.11(vitest@4.1.11) + cosmiconfig: + specifier: 10.0.1 + version: 10.0.1 + eslint: + specifier: 10.11.0 + version: 10.11.0(jiti@2.7.0)(supports-color@7.2.0) + husky: + specifier: 9.1.7 + version: 9.1.7 + lint-staged: + specifier: 17.6.0 + version: 17.6.0 + publint: + specifier: 0.3.24 + version: 0.3.24 + semantic-release: + specifier: 25.0.9 + version: 25.0.9(supports-color@7.2.0)(typescript@6.0.3) + tsdown: + specifier: 0.23.0 + version: 0.23.0(@arethetypeswrong/core@0.18.5)(publint@0.3.24)(typescript@6.0.3) + typescript: + specifier: 6.0.3 + version: 6.0.3 + typescript-eslint: + specifier: 8.71.0 + version: 8.71.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + vitest: + specifier: 4.1.11 + version: 4.1.11(@types/node@26.6.3)(@vitest/coverage-v8@4.1.11)(vite@8.3.1(@types/node@26.6.3)(jiti@2.7.0)(yaml@2.9.1)) + zod: + specifier: 4.6.5 + version: 4.6.5 + +packages: + + '@actions/core@3.0.1': + resolution: {integrity: sha512-a6d/Nwahm9fliVGRhdhofo40HjHQasUPusmc7vBfyky+7Z+P2A1J68zyFVaNcEclc/Se+eO595oAr5nwEIoIUA==} + + '@actions/exec@3.0.0': + resolution: {integrity: sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==} + + '@actions/http-client@4.0.1': + resolution: {integrity: sha512-+Nvd1ImaOZBSoPbsUtEhv+1z99H12xzncCkz0a3RuehINE81FZSe2QTj3uvAPTcJX/SCzUQHQ0D1GrPMbrPitg==} + + '@actions/io@3.0.2': + resolution: {integrity: sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==} + + '@andrewbranch/untar.js@1.0.4': + resolution: {integrity: sha512-pVXSwPsLuw8IGLo2Di0EaOfsk+ntVvpkk942J/sHYIkwvtKUakEcPh7HBgZ6tuimgzKSEHgCvO4XgQ05DEbwDw==} + + '@arethetypeswrong/cli@0.18.5': + resolution: {integrity: sha512-gM+8vRsQOD/Uc7EnBedUhkG5OCsDWE4uoak5QvomGpMpaky0Eh41p04nIMgrWb8EOmqZUJGc6zz9hsP6E56R7g==} + engines: {node: '>=20'} + hasBin: true + + '@arethetypeswrong/core@0.18.5': + resolution: {integrity: sha512-9ytjzGwxjm9Uz7I9avfbt5vlQt6uk9uRRESzJjqrznl6WKvI6dwYTo+vJ3U02Wrq/mR3iql/PzhvHhKdJIAjDQ==} + engines: {node: '>=20'} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/code-frame@8.0.6': + resolution: {integrity: sha512-uMHhvmDmwFiLuiozaoRYTZHqMFuu3fem/X9JY78lohAuLoh8LSGGC8JqUzo2cVuBsBxkjMZNk56CE7ock8Xxkw==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/compat-data@8.0.5': + resolution: {integrity: sha512-YLsYoQMvL8l8WrGpN3Zj7O1wK5LEBN+cQtux7BcuHyxIXve724XG+zuJ1n3U1cUweRtTzQOA4IHbuQw3N34SZw==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/core@8.0.6': + resolution: {integrity: sha512-5zwYt1V4ji3mXKKRIAkohSJ29WepxNwxQi+7y7P5AcVPh1dcxnZBU2cfxHnPwS+u9+lZnXPsv30TQaF2xg+Tfg==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/generator@8.0.6': + resolution: {integrity: sha512-/pcanjdRsonCexCuxgGD8fKnBLLzk2i9uM4kmcmGyyev2R5YDs+rafMYZ89RvaGY1mW8T7C5au0TyYaEeHBGng==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-annotate-as-pure@8.0.0': + resolution: {integrity: sha512-NSpMkMsvvZqzThJ0p1B02cbtA2ObEyfBvq950bmNkyxsxvcxwhvvCB036rKhlEnuBBo30bOrk13u3FzlKSoRrw==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-compilation-targets@8.0.6': + resolution: {integrity: sha512-HTrtZ4+iA0+kzdbCgvdSk1U4pEl3BvpGnlUbualCGNbrVhhQvhgBzurTUmqpWyvyHbWYWaIfdx2mOlL9LJjsew==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-create-class-features-plugin@8.0.6': + resolution: {integrity: sha512-Hy7wG2Pk1LnAkFMq0CbBk789XMTYVzYGxR7tKgk+TOpZckQRME3Tdou4mMT/8DFjJFxFZ8R6UsS4DR/duBKlVA==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/helper-globals@8.0.6': + resolution: {integrity: sha512-OdnA/N52LhzFpeUuVz5YGuSWjc7TPHiFmVYZbPlSgExocO2GwbxVpSni9MVs5wwW+wZ4kPuwBEBN9pIPDYz1mA==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-member-expression-to-functions@8.0.5': + resolution: {integrity: sha512-GLe05QD98BkNFTkjaqeqF9QSRoHLKrrB4tpoplyQuuPrQJ72rtmkM4wvqJLX/sjZPqkbK5MUYSsuUTqdJoOVjA==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-module-imports@8.0.0': + resolution: {integrity: sha512-NZ7mSS93o4ndX4KrbD7W8Sf3QT8Qe24PrnFyUcuOPDzK6faqDFKjY9RG7he7+I7FdiQ4llpnosFqzrXa+Vy3Ew==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-module-transforms@8.0.6': + resolution: {integrity: sha512-YUi+3X0cb0v97xKhuqkLdrwQtP6XXv1eVbhLRCFyMwG481UskbMQk4mjH7K/Jg3oJ1KLskvFwwq4kF/QnABkWQ==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/helper-optimise-call-expression@8.0.0': + resolution: {integrity: sha512-3W6satvtPuCUkUx63S2jMoW9EQNYkADgs1HTfufmL7gCmAulHMKupA/12WNz4A0GMMFn/YnWWwqOT9IZrJHQjg==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-plugin-utils@8.0.1': + resolution: {integrity: sha512-3PKFgjTyPlhFhorfP+SjKQxLViIL++zWjFOO4hGriYU+Bsm983DxEM1JmDRJVWXV0O9npu+xXRqz7Pbd3mh70g==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/helper-replace-supers@8.0.1': + resolution: {integrity: sha512-B1SZADIcy3tmH8CmWvj4SHi/oAPom4UL3uknTc2QRNsPVLFk/sPnZvQL/8kj7Y5omvjMqie0vklvs6XM4OLW5Q==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@8.0.0': + resolution: {integrity: sha512-xmCA9kP3IhySsqhzwIdWGlDN/1A4cCKNBO/uwZx/3YzmDoMePwno2Q5/Bq0q+tYaKbeF940YiKV/kaW8Mzvpjw==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@8.0.6': + resolution: {integrity: sha512-+6HYEBnsDRZHJtUTAEfMQd0zMDqdFHS7C+5vAsEZqBUr3vEMWOWlHJ0Jz1gS8jJIlCvLsuA6ivmeoqzH936YcA==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@8.0.6': + resolution: {integrity: sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-validator-option@8.0.0': + resolution: {integrity: sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helpers@8.0.5': + resolution: {integrity: sha512-fQtPOXjYOYv85PIdwotp2TJGVYOycX0PQq+l844fFAxOULtBy8BVF35GyeueX0r4KvDthqPH5xAI1clQPk/2uA==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/parser@7.29.9': + resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/parser@8.0.6': + resolution: {integrity: sha512-LpGDIYJAzc3Y3PT9x+FxBrGYu2PlWxLmyN7SwPTzaX0LSwEmFJBsRnPBqmaXF2r1v+Zhz6lNiEJ+7yKtM5+Ugg==} + engines: {node: ^22.18.0 || >=24.11.0} + hasBin: true + + '@babel/plugin-proposal-decorators@8.0.2': + resolution: {integrity: sha512-+C6O6KKXU7BBq1GNaIkFJxrALUVGRcr+WeWm4OcuRl3h+l/CmNfcTLMrT2Lm3uvGBimBH/8pEBRrXJFLoO67Gg==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-syntax-decorators@8.0.1': + resolution: {integrity: sha512-NI+0S/6MvR6GlcQFwjDZ+WIc2qvG6TXN534lYs9llNldwW4b7Dh6KTtk030FA0xWdYGs4t1lWo+OEWN8wGB+Nw==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-syntax-jsx@8.0.1': + resolution: {integrity: sha512-n0jtCOxEovhU7METqSQjcZO9pX53nu9uNIjMS+hEt+Nt9jA7oOZoBIgbCxhhASmF6T6rPDGge5UAvh6Z4eFz/g==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-syntax-typescript@8.0.3': + resolution: {integrity: sha512-jmTPwps7oSQSZaV1SxkQ3C12UWyufGysGc5OzDpZzvPAIX4mO7dJT3hoqkWVrSImvkcMiknir1iLN1SNV/CZzg==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-transform-destructuring@8.0.5': + resolution: {integrity: sha512-evjxc5fGvpXG2WMGSalpX8IJZVumLIDgk5r5eRhvXRDqFX/GPWq1BeRfJalPCcwJfrR5A/wvegUhx9W5Nr7/mg==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-transform-explicit-resource-management@8.0.5': + resolution: {integrity: sha512-Vr20jk/ZxRGu30o0jYvcKl2Pf0tiyr8H9q2ZGxYD6yjOpv9DdP6//I85q0cSOdADZhsu/ZZoApjGVCfl4xnVJw==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-transform-modules-commonjs@8.0.1': + resolution: {integrity: sha512-PMuzulWrrzFNmY3lXSk/tV9NRb7y0eZZLJY4UEo2TKszroxvUZHAPPi+T9FDyrQhod+TQA+t+8/QYaaMpiEuhA==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-transform-react-display-name@8.0.1': + resolution: {integrity: sha512-soLishXlkyu6jcICPyO3HEP7A3GCzKEnn7XfvYrImuWEOwFAz93qShmWSYPf5ww0ZkO4By0zsN2bVIDF54fSdA==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-transform-react-jsx-development@8.0.1': + resolution: {integrity: sha512-Hb+HUZpV9KFHjm+F+P3aLDMi8QXU9l3ROCQv20z18Me2sGyW5nNNR5YTevNlgHvCpFek3BnAwhDGq/BRndXViw==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-transform-react-jsx@8.0.1': + resolution: {integrity: sha512-NgkoF7Uq+30TmOPDdNUimT0Nta02uVjqJRFNlVWKrbOCu/CkzfHa4aMnIs0lMpkMmZmWA1e42Va+F04i/pY1zw==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-transform-react-pure-annotations@8.0.1': + resolution: {integrity: sha512-7/8UwU8hoPBurXa9tUiTTC8aACTRy5tCqLUtqikHp2eGiWoEB57AduOdbQ71OOMTEvawKrGhv3WfzkDpI+/oSg==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-transform-typescript@8.0.6': + resolution: {integrity: sha512-WF3yRlQtClzScvQbWct00ix63+w3DPiyU0WmCBU4YqK539n3VvJFElElpevoD8wfu6953CbiNoUZBi2B7/5jXw==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/preset-react@8.0.1': + resolution: {integrity: sha512-jrFuPp/pTddFZbtmWhdLNAYc6UMcpboeUPnw0BBrm4nOmcAko/1TRcFi1PzWCeOFRU+VaSiKmat87W1HvR7mIg==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/preset-typescript@8.0.1': + resolution: {integrity: sha512-qrPhQIN1NLrPmzgazF9XKQqXrOcp/WJly+K+6ReFonn24FZqRJO7clxOJo6Ni75L+2vAqI3cHVU2OJLBxoPp5A==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/template@8.0.0': + resolution: {integrity: sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/traverse@8.0.6': + resolution: {integrity: sha512-ilYV+qLStGzSKLipJlETdvdtpK3rAuwfCmCoGKv15MU4EoeIuLkespSNoNLFGOPSciEHx9TLWtN5mv0fz5w5mQ==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@babel/types@8.0.6': + resolution: {integrity: sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@braidai/lang@1.1.2': + resolution: {integrity: sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA==} + + '@cacheable/memory@2.2.0': + resolution: {integrity: sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==} + + '@cacheable/utils@2.5.0': + resolution: {integrity: sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==} + + '@colors/colors@1.5.0': + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + + '@commitlint/cli@21.2.3': + resolution: {integrity: sha512-5yOX6IRjcrIReCxpyAd04hgI/yhUMpVBJ/ouwFV0wk9yx5+fYRDWZWJ5h55Tbedgfol+5m2mU5nqzbB8g3v0vQ==} + engines: {node: '>=22.12.0'} + hasBin: true + + '@commitlint/config-conventional@21.2.3': + resolution: {integrity: sha512-EP2n5DN2GwRWNVrLX0Jx3mpQlW5PfFi9HnD9OJ8hN/H0xFqpCFyHeGDhg+0P8/21iMMcdkcQgbNqmVklebqYsQ==} + engines: {node: '>=22.12.0'} + + '@commitlint/config-validator@21.2.3': + resolution: {integrity: sha512-JQ13v0GafSM4fBYX8UowSy++07Was20auq0NcTdnzSEq6/05Cq5GBUMIfKkqjzyBwC9sJFmPpKWgLLhH7cygkw==} + engines: {node: '>=22.12.0'} + + '@commitlint/ensure@21.2.3': + resolution: {integrity: sha512-gEjNKgRocbODI5/sMx2Qc9XOf2/zFTNV2aEK1QZ4gKDKxDC/VR7us3QtIX1C5d5xxHxx0I/tEbYbdSP1Am2LwA==} + engines: {node: '>=22.12.0'} + + '@commitlint/execute-rule@21.0.1': + resolution: {integrity: sha512-RifH+FmImozKBE6mozhF4K3r2RRKP7SMi/Q/zLCmExtp5e05lhHOUYqGBlFBAGNHaZxU/WYw1XuugYK9jQzqnA==} + engines: {node: '>=22.12.0'} + + '@commitlint/format@21.2.3': + resolution: {integrity: sha512-KKwCjXYqA8r295uhLnulDXL9yekdXmj2yp2Q0XsBPhxQ2iPuvRE8F43j7g7yexo69wTdmUQmFz15jpderluzUQ==} + engines: {node: '>=22.12.0'} + + '@commitlint/is-ignored@21.2.3': + resolution: {integrity: sha512-Ng4nRj/LBAOvc+GQu7bsFW4/fL89LvZ14OOC/ERLbUpnG6QBwWRzY/1oXGwPs3C8YHWjJObin4kLYo2YeEn70w==} + engines: {node: '>=22.12.0'} + + '@commitlint/lint@21.2.3': + resolution: {integrity: sha512-lUZcSuVLLEDkhge9ERk8Rp+FXQtrUhCK+h328BFnzKXrTuXGdqiYMEpTlNWdZaOrjyMhMJaz354ZaWEGJxcLeg==} + engines: {node: '>=22.12.0'} + + '@commitlint/load@21.2.3': + resolution: {integrity: sha512-cDrL8lOo23Lk7knGzQdjbFcq7z2JgvjwV6PhbESVvEc+Fe21MeLjPKBJi8nKjg7j+jh79CVbVSUbZc104thXRw==} + engines: {node: '>=22.12.0'} + + '@commitlint/message@21.2.0': + resolution: {integrity: sha512-YxGoiXD/HXNXLJPrQwE5poXa+XH0CBEm+mdvbHQP0g6MV/dmJyUFCzPNzZbxL93GvZ70TmtTK0Z0/IBpAqHv8g==} + engines: {node: '>=22.12.0'} + + '@commitlint/parse@21.2.3': + resolution: {integrity: sha512-Jt61QpF1xA88damCELe8I0FlDZPSpXNc8IhN7z5kZ0EcDYPjdZ+ZHPvsSXl69r2MIlLnNKeHhOn2cmVpziFE9w==} + engines: {node: '>=22.12.0'} + + '@commitlint/read@21.2.3': + resolution: {integrity: sha512-szAepuLA1vTTr7fuudEg7jYyVHudf+FH+vptU91bhxE1WnE8TKrfTAi44HVllz8XpI8Pvp4GjR/iP5Nshgh+og==} + engines: {node: '>=22.12.0'} + + '@commitlint/resolve-extends@21.2.3': + resolution: {integrity: sha512-bqJdmAtXgAQel+l02uIsF7rHgKgx1AGZdGBjam7uK4XW54ZAQtB6XAIUXezYprQiCcRMpBh4fHs5JodXK4mVZA==} + engines: {node: '>=22.12.0'} + + '@commitlint/rules@21.2.3': + resolution: {integrity: sha512-5+YnI/LuTx3JDl9GjqfP2z9x96M8KKDrd+4jOel/T0L1BB3oAmBlSN8xppf63QsudiVEgR8C0gRoAMrPVrms6g==} + engines: {node: '>=22.12.0'} + + '@commitlint/to-lines@21.0.1': + resolution: {integrity: sha512-bd1BFII7p1EQZre9Kaj+kKaMFP3cFCdt21K7DItVux9XP5WjLgJ0/Uy1pJJh9aPwVJ6SKg62PxqlZaHI8hQAXw==} + engines: {node: '>=22.12.0'} + + '@commitlint/top-level@21.2.0': + resolution: {integrity: sha512-Y5gmQ+KxzqCrBFJfLvFEPvvwD3LDiNZoTT2yeFBm96M8qhmqSzQc5DvX3rheAaAMjyIvMXOCLS/mWfdpONsjyQ==} + engines: {node: '>=22.12.0'} + + '@commitlint/types@21.2.3': + resolution: {integrity: sha512-sGEA473TlCvhCGNoTBY9OXZVWdscIXdGt0dm8EpB+2MRfmt8LNh/X56P/T3nCtEB+eH5qGEa3odphSAoUxSfSQ==} + engines: {node: '>=22.12.0'} + + '@conventional-changelog/git-client@3.1.2': + resolution: {integrity: sha512-jZqwnJwf7nboIlAcw/mkOjVa6DexCcUOgT2oOQgkoi3z9vR8tGFkcMy2BFcYwjhL9sYcDDXkRQDayiDieCoW7A==} + engines: {node: '>=22'} + peerDependencies: + conventional-commits-filter: ^6.0.1 + conventional-commits-parser: ^7.1.2 + peerDependenciesMeta: + conventional-commits-filter: + optional: true + conventional-commits-parser: + optional: true + + '@conventional-changelog/template@1.4.0': + resolution: {integrity: sha512-aalGyl7dbB5PArRebDIX43ZvBlXrYm9uWzGJ26t+4SzJVPsOuvfILGGbw5X4yX7i50YEmJ8zvbiWnqH/AAnZqg==} + engines: {node: '>=22'} + + '@es-joy/jsdoccomment@0.97.0': + resolution: {integrity: sha512-EP8uoFfh6+GsdGCduYtmWAW0h7AO+Ayik9Vh5YbA2r/3N6lmJKkCNZX+q3QBXC1K6ixjQ/9igF2b7WVvLm063g==} + engines: {node: ^22.22.2 || >=24.15.0} + + '@es-joy/resolve.exports@1.2.0': + resolution: {integrity: sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g==} + engines: {node: '>=10'} + + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.7.0': + resolution: {integrity: sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/json@2.1.0': + resolution: {integrity: sha512-Zcg+SXHmdAlo+0ZFwxOvDQye2zxhaFCu/CPdJTsMDSEYZXkX67AWvdwzhI/YdPYNW68Q1Tr1Rv2SsuUZydnjBg==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.3': + resolution: {integrity: sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@exadev/eslint-config@2.23.0': + resolution: {integrity: sha512-xcqNc3RakQj8aPIfxff+6Hp6JkXw1HkDQuWE5brEUBYncEUJkqP2Bg5kYl2SPJwSlAXCyuV7zFPOIsVNfYoxyw==} + engines: {node: '>=20'} + peerDependencies: + '@next/eslint-plugin-next': ^16.3.2 + eslint: '>=10.0.0' + eslint-plugin-jsx-a11y: ^6.10.2 + eslint-plugin-react: ^7.37.5 + eslint-plugin-react-hooks: ^7.1.1 + typescript: '>=4.8.4' + typescript-eslint: '>=8.0.0' + peerDependenciesMeta: + '@next/eslint-plugin-next': + optional: true + eslint-plugin-jsx-a11y: + optional: true + eslint-plugin-react: + optional: true + eslint-plugin-react-hooks: + optional: true + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/momoa@3.3.13': + resolution: {integrity: sha512-AbKq3EW41+HY6pNMJgAOrz68iyiTTf0Nr1JRHHfuG5zwniJ9Dw8J3U7oa1I2ptpqYwmEf1sXKOGK6lbdZ9eqmg==} + engines: {node: '>=18'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@inquirer/ansi@2.0.8': + resolution: {integrity: sha512-WpQM+Ti6Z40EFwwt+uL2p4UabT+W179zHp6HhLVOzfbwnVn05IPO/eXIZXGNqcT1jbQ15SujNLzQ39k4QPPxBQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/checkbox@5.2.5': + resolution: {integrity: sha512-bRt8J8m+Fot9CXv+zNQGXUq2ET0MggR1fPz7v6edN6MFYmsbfGnMmkmWZJEegMKqrAC8ej/o1sqisHZXZJMAfQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@6.3.2': + resolution: {integrity: sha512-Xvr/0HggjddPtGppuqVmxhTw+Hr8PvsZ/k0HmOEaAqQEt80OITNkFWnsdNmyT0/eM4Ab+iJLx2R8rctlEyfSVg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@12.0.3': + resolution: {integrity: sha512-wsSy0sznmXwkty+2PzZwx00Cazc/E0r0B7mAzdGROz2Ct+DFZXaK7WDjGZvgjRldxH5ZhFVfF2lgkYrqgOw2KA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@5.3.3': + resolution: {integrity: sha512-YsKkS2q63IiLtaDK/9nqzdComN97SDQrmKiyNggN+ceP4ty+Z6VwyTz3FpjeUWeW1Efss2xHFKCC9sx7hnrsxg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@5.1.5': + resolution: {integrity: sha512-uHuXLmXW+TtIfT/9vSBotypAkqn1n34Ul+CLGPos/xANyO4Ff5xZzkYhbKR4NEcfVK4a9mHQOpwVZzluSHFRGw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/external-editor@3.0.5': + resolution: {integrity: sha512-f3QQJRIX5ZEneBHNUIuPjmbdzHnmRFJA8r2dkcb8q+OM5Uv5KtnuAttQumnrjcBVBM3mcTX1CkmtAkU58VRZxg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@2.0.9': + resolution: {integrity: sha512-EAWgUTGQ/Umgga51dE3B2PUHbufuXarDfg86uVgoSgNHNNQnyFKcOrQLWVqYMghuSyHh8+2HUH0Js9cTC1WAdg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/input@5.1.6': + resolution: {integrity: sha512-HtcJhB2QFVXbLuJ5S3syhNbTUVxYvwqV4VRBDkQceBloC9bmTViUoRFP5PbSaDZb3HzfPmpuU/gG4ybVBz4FHA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@4.2.3': + resolution: {integrity: sha512-6Yuwh1NGSbu1Lo4N1EWjXs1jKRntLg/ZCwhmeorEHde90v1XxAozdbd4Iu30eOQLW+6h1hp2O9ujNfLSbTPJnA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@5.2.2': + resolution: {integrity: sha512-W9zYdyzogK+6110mqwaSJWCBu2yA5Q/OfnGSjjZB1bNpHlmUozXxTl0+QOZBNeVd6Qo81/qT75gW05gLAtITxw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@8.7.2': + resolution: {integrity: sha512-QoRB4wFIjgH5iOhSjoIKMkTvSHDuV+O3OITlIqAYO0oK5x364GJILXiMBvlPiE+klg7Xx9tq5XVqQHcGUDYYPA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@5.3.5': + resolution: {integrity: sha512-1oHky1ONfCOwNrnkQGDE1oaSij/3fI6HFMSf2H/WsGO2lEyDX9My82iggITSy9ddSZ8yk8j9v41OI0fVoSIoaA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@4.3.3': + resolution: {integrity: sha512-fyuIU1Nbpvwlikjg3gXwJFDI11+EFjqQ7P+iByfmivIKQ1vmaykNrD/vy5unHuUqUpsOsnvJ25//tPF7E/RBRA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@5.2.5': + resolution: {integrity: sha512-9kc15hr8r/kI+3DO/xLog5nOzTz1jqsHXa6JBFzmQKhkoJ8Slda1I1L/uD8ZSZ9tF1yp79wwXe7mclvX1rqR2Q==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@4.1.1': + resolution: {integrity: sha512-yJoHYrMnxIsJZCY+0Vb66Dy3he3kL3e2wOBKhoSwWWAzZAY82emlxwgprCtp6yRixvNRNq9ztfRWQYPNr3Go7A==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@jridgewell/gen-mapping@0.4.0-beta.0': + resolution: {integrity: sha512-JdGNkbE4GlNPYQhM0L95fBQr7ctLZJ276QXQLTad4t1oSdnnCI3fDq9DW3BqYAWv8Wc3+HS+4Gsii1oPMCfz1w==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@keyv/bigmap@1.3.1': + resolution: {integrity: sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==} + engines: {node: '>= 18'} + peerDependencies: + keyv: ^5.6.0 + + '@keyv/serialize@1.1.1': + resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} + + '@loaderkit/resolve@1.0.6': + resolution: {integrity: sha512-G8FdIoF5CypfwmD9rl8BXod5HDn8JqB0CCNBXDTaRZ+yRYhARrrSToX1zg1zy9jX3zLqigsELwhT4gNtkdQAUg==} + + '@microsoft/tsdoc-config@0.18.2': + resolution: {integrity: sha512-4ymuLTQ9lFwJXK15f41iPcNHflymjk02Ltq5f5wo3fA02O1nHIGSc1TIZwzTSQZep+jXHM644jobt4Q8O3vtqg==} + + '@microsoft/tsdoc@0.17.0': + resolution: {integrity: sha512-p68VexhnH7ojf3U27RdryUDqnKJgKaWUbCL96vJR7N0mqDuhKG5OIECk3sCPPdynxjk+yoVhRkScqtv5KQjzsw==} + + '@octokit/auth-token@6.0.0': + resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==} + engines: {node: '>= 20'} + + '@octokit/core@7.0.8': + resolution: {integrity: sha512-L7y8eYc+AwxGr2PWI4WFt1VG4TiJ66c26BD16mXpYIlXxG0SMigM1+m4aTSlYyBr5BlQsGAlz8uDCoZN4SEMcg==} + engines: {node: '>= 20'} + + '@octokit/endpoint@11.0.5': + resolution: {integrity: sha512-iXa654H3yFafF/ieHkukfbgWo2rmXD2ceD0ZOtrPhw1bc3FDch1d9N/TNs0FQ1/cIbwb7kspUX8jzIs8nzb9DQ==} + engines: {node: '>= 20'} + + '@octokit/graphql@9.0.5': + resolution: {integrity: sha512-bt/hm03LeU6Vy7FwTrkkC9p3XGT/lBwClglMqxBSe5/q0E5CdJTXeAqEI0vlw89/LF/G6tryTIH8HirZ3prMVg==} + engines: {node: '>= 20'} + + '@octokit/openapi-types@28.0.0': + resolution: {integrity: sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==} + + '@octokit/openapi-types@29.0.1': + resolution: {integrity: sha512-9qWOMFNxxLokERcms42rU0PTLqQmVs7g5E41TI4mCOxmpFayD1rfC7XxOL55cG9MBZLFlC31BrR37myMKardwg==} + + '@octokit/plugin-paginate-rest@15.0.0': + resolution: {integrity: sha512-lw9A9YL5s4VPj+VEx8uMxaxQm2YTgrPDkrLEuZuu18R8TK3oPcXF4K6t52nx6xn1RcogZC9i188sAr/4XYJaQQ==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-retry@8.1.1': + resolution: {integrity: sha512-VCVvZ/R1+u3WuiBWpNavZ0mY4aaJNAsENrpBP9aLSR2QyOpQgd7DhM5j4AW7z4MQpnJYgwBPf0XqPQoNBRdQwg==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=7' + + '@octokit/plugin-throttling@11.0.5': + resolution: {integrity: sha512-LIdrkrUv+DWbKeg/49rGuFJ3SU0d3hUS+B4MhNZLepBoNUFXms8Ic9edJjrlx+zycqJHjrMRudVpVb/bAXM2Lw==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': ^7.0.0 + + '@octokit/request-error@7.1.2': + resolution: {integrity: sha512-XZRuT3xZ84D3gYErI1DZvhJ33dCWVV6uzBtWkaBB4TvA/L6eOeTZodxLFVB44bBEEo3vEx7y00UfX1tBLrtLRg==} + engines: {node: '>= 20'} + + '@octokit/request@10.0.16': + resolution: {integrity: sha512-A0zWGjHzISIb+9ccG8s0dq7LKO5zVpJLRICjgUb+sJxEWqn8RUHB1rD3AE51+PECvXHIxqZ1VVvs4fHTSD9nUQ==} + engines: {node: '>= 20'} + + '@octokit/types@17.0.0': + resolution: {integrity: sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==} + + '@octokit/types@18.0.0': + resolution: {integrity: sha512-l6bAF43PNxkJp6g+W4PjoUSSkxHomXw2nOum5CTftJz1NlV3vu93NImgOYtLf6CbBUb5j+fiuzW0PPQ5JTSvZA==} + + '@oxc-project/types@0.151.0': + resolution: {integrity: sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA==} + + '@pnpm/config.env-replace@1.1.0': + resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} + engines: {node: '>=12.22.0'} + + '@pnpm/network.ca-file@1.0.2': + resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} + engines: {node: '>=12.22.0'} + + '@pnpm/npm-conf@3.0.3': + resolution: {integrity: sha512-//0sR/cow/s4ICQaYoAobOl4aU8cjU6x/V24V7XkKotb9+O+3zySIYp146vpaobYHnxa4pZX8NkV54Z5AwbDKA==} + engines: {node: '>=12'} + + '@publint/pack@0.1.7': + resolution: {integrity: sha512-4EDEmvxWtgsCnnVeBvtFIFZtUhPPt1+bA9JrSwU4Sa//6oKtzCSlGGXYJr44OD9aGISymbieJ4mCKHUygUDU+g==} + engines: {node: '>=18'} + + '@quansync/fs@1.1.0': + resolution: {integrity: sha512-qAPG/t3HqML1TlN7sY/pTbEjzFVAKsMjNNMGheyDosro+kT4iw2KCUoHcVdmliwWjorm4elZbgNQyU2eD97eDg==} + + '@rolldown/binding-android-arm-eabi@1.2.11': + resolution: {integrity: sha512-A5kXfGKvKWWZE0TtPrfsvT+q4Y5d1QG8gGUzpYjGydM+fARM9MuX90PrXYXe0XbsDVgyxxNzHo6giCj90bsFNw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@rolldown/binding-android-arm64@1.2.11': + resolution: {integrity: sha512-z6cTycz+iJ4PVkuL4HHW4DfTfoeU/2nqYYuSOrTmH7yHK5Y0LCOnA03V4ZNxavyVaU1oOqUgIg2klN/s+USGOA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.2.11': + resolution: {integrity: sha512-jShvqNtP6vDC6/A5JOAzbVV+DkgHqhl/ScVCJEbt+TUY6QYz7YnXcrg3sLtFBniro0f/Ld50ZwCWA6f7KYD1nQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.2.11': + resolution: {integrity: sha512-f2i2xiNWq1Z1l2++q2fuhZRdLAT3aqxD6vRNm1RAxpUoBcdqNB3C0s1Bt+K+PbEx2F5F4gQp6hqKkphCY/xF9w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.2.11': + resolution: {integrity: sha512-4Ir5FSOKIAMr4r0kExpt1s3bMgzJU3rA45AYOHtQpls0oNeqcYBKrWMlckrYH4KCfGLfkfn1tN1dmZPMVsdXow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.2.11': + resolution: {integrity: sha512-/gnRDM+39BROzAN/k1OZjDPnDMcZxB/0EUxKjONO5yVkNEvlsoMDrxGNKgZi/ttFriS2gwlDNzB65pvNbFOXIQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.2.11': + resolution: {integrity: sha512-PFaK8HwvAHbaKbBcDNQihjMKYvFnA5hiENx/l5tphTDz1E0WFp32l0A7aq7lyUwGsRw/xSrNIy/gIK4thrSCrw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.2.11': + resolution: {integrity: sha512-AskzJUIKRLPxkruR1wLKewGbOw+EYfU/9lOrBFj4AFrEA8hPpKFnODWNu2WLaNs0QNkEb9QIJufmVZZIL/bJlg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.2.11': + resolution: {integrity: sha512-qlUGAheh2yh8afH7QBgx0PrRHN85hKnNd78x8MeMhXivuevgd8vgf6/CstOzmNKY/lLTHvNTrPy98cLnAugzJw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.2.11': + resolution: {integrity: sha512-secpEad+0vCbSfn8upFySkDskv+bGPk3THSDS9Y89yc4rb4kzqHp8Dmyd9BkQW4SnhNXBZCl/6CrO//hZahNJQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.2.11': + resolution: {integrity: sha512-mOVBT3dPpkWm8XBWPmU4bf+U6dYDLeMo/9ojUmis4N0L5uu10qra5vOyngZ7/PSdoE4G9KvRt4bloRxNjLas7A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.2.11': + resolution: {integrity: sha512-Is78i9A8Ui4SqcxUwFJ9uMmjDn58IbVTjFWYdQestFEgeuEmHMLGNriXnVJKkwG2YiZjw8cP0zCTyDMdDGtOOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.2.11': + resolution: {integrity: sha512-dUCXneZ87INUMyQ0D+C0HrEBNUPNXHaPmU5GTjyKTJEiussw9Kaj5Ln8UztPe4epV/ffvgNBEadksdYhmW6xJA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-win32-arm64-msvc@1.2.11': + resolution: {integrity: sha512-jByxb6qfd+bH1xUd0qnfFnb17i9sWBPY2tOavJ0l3tdr3OTu+Kvtm8cd/JV5nFt657b1VqGltxg9olOEfofXWw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.2.11': + resolution: {integrity: sha512-/PzKqzAJ03i19oy2ItPvyvaVjOjBCNnfaJs8yvUdGBKmiESgnrJSQ2awd81QzFbbnAmu7YO9ZnJrDCb9VSJPRA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + + '@semantic-release/changelog@7.0.0': + resolution: {integrity: sha512-TNPyag5db24o7jWjre7UwKB4EcL8oJxbRhnDQ7hmZRAYqzreAc6PgdxQuU3pppp5xQinYtiumL0iG8SSKvnlzg==} + engines: {node: ^22.22.2 || >=24.15} + peerDependencies: + semantic-release: '>=20.1.0' + + '@semantic-release/commit-analyzer@13.0.1': + resolution: {integrity: sha512-wdnBPHKkr9HhNhXOhZD5a2LNl91+hs8CC2vsAVYxtZH3y0dV3wKn+uZSN61rdJQZ8EGxzWB3inWocBHV9+u/CQ==} + engines: {node: '>=20.8.1'} + peerDependencies: + semantic-release: '>=20.1.0' + + '@semantic-release/error@4.0.0': + resolution: {integrity: sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==} + engines: {node: '>=18'} + + '@semantic-release/git@11.0.1': + resolution: {integrity: sha512-Zr8BUYCTZMc8V6wDKN2dpR7nJgewd9I6THL3ydLTnp3OEdTo1/4RBLNYaeRucYMsjMv+BXoCNfXA0NADj1kwhw==} + engines: {node: ^22.22.2 || >=24.15} + peerDependencies: + semantic-release: '>=20.1.0' + + '@semantic-release/github@12.0.10': + resolution: {integrity: sha512-QskygvpX8hFx9UEbqJ+C46OIhaJjsysNQ8PYJPuRDc/ek5+dh8WYGD1ofUahCcGLAOmnnwZTcmCJCEXM8XxhRw==} + engines: {node: ^22.14.0 || >= 24.10.0} + peerDependencies: + semantic-release: '>=24.1.0' + + '@semantic-release/npm@13.2.0': + resolution: {integrity: sha512-Uz4JGafLoEGiheLKEs1orockCWP6liY9D2YnvKHYbqo9hCHAw9fuAHcU53q29SXVCtkvDhs/QVUg7Vvuyfp4Jg==} + engines: {node: ^22.14.0 || >= 24.10.0} + peerDependencies: + semantic-release: '>=20.1.0' + + '@semantic-release/release-notes-generator@14.1.1': + resolution: {integrity: sha512-Pbd2e2XRMUD0OxehHpgd5/YghsE76cddkRHSoDvKLK+OCy4Ewxn49rWR631MEUU01lgwF/uyVXvbnVuu6+Z6VA==} + engines: {node: '>=20.8.1'} + peerDependencies: + semantic-release: '>=20.1.0' + + '@simple-libs/child-process-utils@2.0.0': + resolution: {integrity: sha512-dvNoRKLijXnD0XoJAz94pbNuB5GQgDr55UhpSPhffDkTT0Cmcqh9jSCOtwfT2d4H6MI9E7c4SgtMuJXZ6F3c6A==} + engines: {node: '>=22'} + + '@simple-libs/stream-utils@1.2.0': + resolution: {integrity: sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==} + engines: {node: '>=18'} + + '@simple-libs/stream-utils@2.0.0': + resolution: {integrity: sha512-fCTuZK4QBa+39Oz9l4OGfJfz+GpwCp3AqO7Zch3to99xHPgstVsRFpeQ8LNd2o1Gv8raL2mCFwiaHh7bFSp5DQ==} + engines: {node: '>=22'} + + '@sindresorhus/base62@1.0.0': + resolution: {integrity: sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==} + engines: {node: '>=18'} + + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@stryker-mutator/api@10.0.0': + resolution: {integrity: sha512-ZtAJ0ZT3MVRCWJTBE2h90XB/6E+4lifHYtcTyNG6nU2nLekPgTo4gD5esjX6Okxo1b/JB4jJzyxYB54fwKAoJw==} + engines: {node: '>=22.0.0'} + + '@stryker-mutator/core@10.0.0': + resolution: {integrity: sha512-ZvMsRyaXQQ5e6Thcid9pkuODv6Fn9E3nrBQJUap+hcJuGJ4unm26afo3m6YKSjn8kinyxJ/3TXf0cTWRDaTxVw==} + engines: {node: '>=22.0.0'} + hasBin: true + + '@stryker-mutator/instrumenter@10.0.0': + resolution: {integrity: sha512-B7Wmn1KlEWyFeOz6D6oGvQGRfi5Xw3VemG6dEKvFQp4qLvxD9Mf4kcZghfxffgnYwXd3bFgqXsJ+ZGlhdfIOrQ==} + engines: {node: '>=22.0.0'} + + '@stryker-mutator/typescript-checker@10.0.0': + resolution: {integrity: sha512-GLvC0LVd8RJ5SxR3eFcgb7DFKmw07DYtPXs942z6Qh7l6p7YBnFyd7lPw7nvA9UKEYDd22embrFmLlyC1XJjXw==} + engines: {node: '>=22.0.0'} + peerDependencies: + '@stryker-mutator/core': 10.0.0 + typescript: '>=3.6' + + '@stryker-mutator/util@10.0.0': + resolution: {integrity: sha512-LzOpHiJaCp2ABQgnPMlrQQcsK43bd5Vo/2FGL78aN62yDoeRQ+4j3tzeuXxK5OAHdC3fUz6TDoy4IsoAuLAd3w==} + + '@stryker-mutator/vitest-runner@10.0.0': + resolution: {integrity: sha512-SHK2/vfvRUpiz7jXPnQMBnr6zLdm69DK03Mo5mPhaZWcRSygrKUqYsPqWsXsK+5ySHzlMTfCyFK5NQ/X9sJFFw==} + engines: {node: '>=22.0.0'} + peerDependencies: + '@stryker-mutator/core': 10.0.0 + vitest: '>=2.0.0' + + '@stylistic/eslint-plugin@5.10.0': + resolution: {integrity: sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^9.0.0 || ^10.0.0 + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/gensync@1.0.5': + resolution: {integrity: sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==} + + '@types/jsesc@2.5.1': + resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@26.6.3': + resolution: {integrity: sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg==} + + '@types/normalize-package-data@2.4.4': + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + + '@typescript-eslint/eslint-plugin@8.71.0': + resolution: {integrity: sha512-pqcS9c1HxZTHt7End4nXqd0s5lJrrFzrgCkKFJrsbUnaL6M3+6oBFZaslg6Gjsl3argl2DDRFROnXARaZ2e4Nw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.71.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.71.0': + resolution: {integrity: sha512-CG4nPk1f2zc8yw4pALqHsFYH2hdo+h1T9daSp21+Hnxi9LOE3GT9hAfTKJCBXVNM2GmYs1eMEP615wPoeOgk3A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.56.1': + resolution: {integrity: sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.67.0': + resolution: {integrity: sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.71.0': + resolution: {integrity: sha512-aABjw5rjBacYONVPaPiWOCjJu0vEF4a25iQuodlmQYL1trtLZ0X/y+2Vzl3BKI1odM4LnwLE1oUDXYp1wzx1TQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.56.1': + resolution: {integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/scope-manager@8.67.0': + resolution: {integrity: sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/scope-manager@8.71.0': + resolution: {integrity: sha512-gWF0BhUcnjZxSpLE8ngS/59n2SB0J3YqRxvX1+2aoRJk9hNtHSLOV+TcarFiOr5ipXm3yc1QrI4c9YZc8zyCxw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.56.1': + resolution: {integrity: sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/tsconfig-utils@8.67.0': + resolution: {integrity: sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/tsconfig-utils@8.71.0': + resolution: {integrity: sha512-Z1UlWHADEK2Mlb9NpWfDeSjqoZ5EyrOv4R3eQpbkzqn/EwaIdOpXXupEA1+0ZIOSJSZZDBHG0BrQyN8zUG6Pwg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.71.0': + resolution: {integrity: sha512-i8uO1qbdxeKgRnS5sCRt6On3/nfo2d2DwQe3Yvjx543zLy7r8ySqRuPPiIIXAhS03U0v5NfAFx+rUgxFzKKwNw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.56.1': + resolution: {integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/types@8.67.0': + resolution: {integrity: sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/types@8.71.0': + resolution: {integrity: sha512-cJ4OoxPGWvFnBTnSZyaU+qJzGTqPTGJY+gDchj6cRyLRdmIdt4rcsE4twj+zPfrNiWuVi38wijHzShL++Z9atQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.56.1': + resolution: {integrity: sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/typescript-estree@8.67.0': + resolution: {integrity: sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/typescript-estree@8.71.0': + resolution: {integrity: sha512-PEEF4G5sLLWAS5BpPrUvms4ySZkiBQQZM4z+3ReI46axK5Vqr/vXBQatJQIZZOYdGyPUAKTtsrWzpqKuU+3DEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.56.1': + resolution: {integrity: sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.67.0': + resolution: {integrity: sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.71.0': + resolution: {integrity: sha512-pKR/tEMVrXZG23UFKUn5BQf3zfmfk7KQceI2cGzywZ5nxM5Eu3hEJU1utjWzydtzBbcJAQhHN8iPCxobHpPcZQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.56.1': + resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/visitor-keys@8.67.0': + resolution: {integrity: sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/visitor-keys@8.71.0': + resolution: {integrity: sha512-8eQ9R218XORK+KLosnf4bu/QsUXvUyVwTbArg7/0NMB1Pu87OJKvj4nhFblkYE8gQV73mW1dx1ptlPCkwRGa7A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitest/coverage-v8@4.1.11': + resolution: {integrity: sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw==} + peerDependencies: + '@vitest/browser': 4.1.11 + vitest: 4.1.11 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@4.1.11': + resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} + + '@vitest/mocker@4.1.11': + resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.11': + resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} + + '@vitest/runner@4.1.11': + resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} + + '@vitest/snapshot@4.1.11': + resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} + + '@vitest/spy@4.1.11': + resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} + + '@vitest/utils@4.1.11': + resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} + + '@yuku-codegen/binding-android-arm64@0.10.2': + resolution: {integrity: sha512-Oc2KInVkPfUjEB4PeV6X0NvIoyYTzDe/WmqFrTwBqKs6YKnz4A7XwWQvJTb7M0rLz9a6WgpUUiiS5QKaCi4O/g==} + cpu: [arm64] + os: [android] + + '@yuku-codegen/binding-darwin-arm64@0.10.2': + resolution: {integrity: sha512-3H7eNPIHJndUJXZ4QUGBPq1zC6RGcUZfm7bymJes+/N7wTVWUn1bxZ9JcozYHpkWQNm9f23G8YWbaHD9aYH72A==} + cpu: [arm64] + os: [darwin] + + '@yuku-codegen/binding-darwin-x64@0.10.2': + resolution: {integrity: sha512-AJOUpR2s3LF9AWiiub/Uyc/UoXNTWcq8hK3cVI6fUSXtwh34dG21J6hRuNlO6JVJFTo4o3ScVvZOrh/YFfUEAA==} + cpu: [x64] + os: [darwin] + + '@yuku-codegen/binding-freebsd-x64@0.10.2': + resolution: {integrity: sha512-ms7DcZu87u5CiK/wMwvpKAvAmtaqKjCQIXNweMLypG6qbuiaOMQf1jpbB5+j46xBcgCovo8TQMcv0bCQLKIL9w==} + cpu: [x64] + os: [freebsd] + + '@yuku-codegen/binding-linux-arm-gnu@0.10.2': + resolution: {integrity: sha512-xJDpYAsKV5+eaiBhTYl05fvT6sst4PsCeuIFMRu9b0/WCSrNQPfCDtAQ5/MS6xNpsdujdZEPR+gmfWk3ZG5i3A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@yuku-codegen/binding-linux-arm-musl@0.10.2': + resolution: {integrity: sha512-qfTkgd7AEx61l4K9VtXWCGTxc/fmXJ4ZheDz3i+/AAJUtg4sa0bPrc0VBxPggB/rayR8Z3+JfrBItuyanBJJ9Q==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@yuku-codegen/binding-linux-arm64-gnu@0.10.2': + resolution: {integrity: sha512-4e6Mifm/4UdjtU3D8mATIrvgP+xEiH8xtQOjd0zpd72XKWT7ug3sdyhq2utkkZFP6BvYp7SgZrI+aR4VeIOHdw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@yuku-codegen/binding-linux-arm64-musl@0.10.2': + resolution: {integrity: sha512-RdsJrUfDYFVV3JOmWFUWwSu31fa1APn12xDeIKxgl/YWxrabwtxsDBNjF2561c7tmcbiewdCUvngqRs8AyGVLA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@yuku-codegen/binding-linux-x64-gnu@0.10.2': + resolution: {integrity: sha512-mVzWimEPPreaPyXIUvxsHoJzvO7ckZ5j0LgQFHz04zQIRwt9A9T2hA7K5/jYjJKkMxWG/U2VjhGNwVhtyU+uqg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@yuku-codegen/binding-linux-x64-musl@0.10.2': + resolution: {integrity: sha512-Y77fyISurmr2mvO1yeq3u+x/WJbACgPR4w+lzEVx30ViCxZzIGrZPRN1yEdZ9xUNPNsIO5thBHIdRNG+UGUG+Q==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@yuku-codegen/binding-win32-arm64@0.10.2': + resolution: {integrity: sha512-1+tGLyG0u5YYy0lev4ck7/0sd8xJ9SZde0dLut7qLDPZV5WVTV8x6OxNXUne/PGuHZyO1vK+txPQzzHOyXHGSw==} + cpu: [arm64] + os: [win32] + + '@yuku-codegen/binding-win32-x64@0.10.2': + resolution: {integrity: sha512-8a2SExRohRbwC0MY4VpOF0/RSckrJ2ASZqAor5/RYSJJaeadR93n10gZzcKT6pY9ukiSOIZCRKVRD2tH73T0qA==} + cpu: [x64] + os: [win32] + + '@yuku-parser/binding-android-arm64@0.10.2': + resolution: {integrity: sha512-2VPBU9fRGRAQ2xPAvghnec3oou5Nrxm2Bezhf+13UMspAxQSkF/32r+ySKXSwIPA5/RivumDJDxAxc301Sgicw==} + cpu: [arm64] + os: [android] + + '@yuku-parser/binding-darwin-arm64@0.10.2': + resolution: {integrity: sha512-LD+PMZE51tCYTOss5HBkm3/AE39MvcMDBfWJx7A4yDcjfNbAQDnHZKtzSOuqrswx+TQHY0ws5xn6+fWOwtmfBA==} + cpu: [arm64] + os: [darwin] + + '@yuku-parser/binding-darwin-x64@0.10.2': + resolution: {integrity: sha512-mmZ8cND+AoIIMRERyMinlg5ApHxP23B9jH2B5wT7T+dliPa9rubLxneB/SUjFwyUjGaFnB5G7t4YvpfbO5zbkQ==} + cpu: [x64] + os: [darwin] + + '@yuku-parser/binding-freebsd-x64@0.10.2': + resolution: {integrity: sha512-gVIjaaIddbRfAhHlC8N809wQWml7mxfSVnzaLtzGXObTeFTPAg/YVnQXt1UOhPM1ah5eG/8Y0RUlNpB4GVe7eQ==} + cpu: [x64] + os: [freebsd] + + '@yuku-parser/binding-linux-arm-gnu@0.10.2': + resolution: {integrity: sha512-q/XPPQQAPdlw05aPj30ygBhekmQryGOwxVraBgApjKK8yY1kNQgqq6XCYLF1WHSee+lhxclrTO7W0/bt7dR1GA==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-arm-musl@0.10.2': + resolution: {integrity: sha512-A+Cb0I1hFF4wilTQpWs79k1aNnj4B2tkrHx3zsuUNF9BtVY2zPZ4yeQEM/zjXrS/qJlNMZVK9NrWJjwdpnTrfg==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-linux-arm64-gnu@0.10.2': + resolution: {integrity: sha512-aGNSzqIqqphFwAdIFwVvKIyXD1Iy3CxrEJVIZAT87Ecyi4vCmmDD2v2l9h+Gd3/wSy3JZlOI792LJbKAjyy9rQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-arm64-musl@0.10.2': + resolution: {integrity: sha512-Ddl1sF0rtuCXyHGSOV6dcmdx+ETv1iD+IVFqQuOjzkJZWclxJxdBWX6ZJMRtTiGqGUTbqLEKiTXxpR/Bvqk+2A==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-linux-x64-gnu@0.10.2': + resolution: {integrity: sha512-/nlcpR6IF5U+0m5L9wvAIXeQa2DT+BXuvqKDsTcOTlcc0B4dHNyqE5hTXsS4hAyimpy2ZK8A1zMNF5hTrjg2cg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-x64-musl@0.10.2': + resolution: {integrity: sha512-GX80dxTQD/M/OryyuxJcFzFENzry3cDFPvCFTyWogNWQH3fSHfMrNfpwpl1YzMos1eV9NygK5GSDG7VArQlb4w==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-win32-arm64@0.10.2': + resolution: {integrity: sha512-agePQBV4VHewiGU0ACSjscZ/hJd283f9JfAF19Gf1rI5+wy5tTgx4GS36i0b3xim15AQ4RCpDRosEvhLZ4zAOw==} + cpu: [arm64] + os: [win32] + + '@yuku-parser/binding-win32-x64@0.10.2': + resolution: {integrity: sha512-s8//CMpgL5+y1lvDCdyh1rwGI5+ytDJywFJRe1vnhI7n0j+caxshNucurikYLLVeQ2SYFex6aPrzgQxkabBQRA==} + cpu: [x64] + os: [win32] + + '@yuku-toolchain/types@0.10.2': + resolution: {integrity: sha512-sSeo4SSSToiS+sSD+bwn/s94EEcaLJ7tG5LCp8gFYC1G5VzxzX7fqB6m9RU1yMD8KTpu6zdU/I1NlQf8hVBJ9Q==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@9.0.0: + resolution: {integrity: sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==} + engines: {node: '>= 20'} + + aggregate-error@5.0.0: + resolution: {integrity: sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==} + engines: {node: '>=18'} + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + angular-html-parser@10.11.0: + resolution: {integrity: sha512-3vERzJ65UFDr3C7uozLJwsNcQS3FS784dSh583oDgDTTZMgXe3/pdyXgKndxiP5R2lvYRfW6gSl145Hnyf2OFA==} + engines: {node: '>= 14'} + + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.4.0: + resolution: {integrity: sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==} + engines: {node: '>=12'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + are-docs-informative@0.1.1: + resolution: {integrity: sha512-sqRsNQBwbKLRX0jV5Cu5uzmtflf892n4Vukz7T659ebL4pz3mpOqCMU7lxMoBTFwnp10E3YB5ZcyHM41W5bcDA==} + engines: {node: '>=18'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + argue-cli@3.2.0: + resolution: {integrity: sha512-VipTB0gXgGIFO2Rg9yEVN5wLt2AurJcZqDbgmYSwPwsykhLrQhs240/bfceev4w68lI8JshoOJIk9uW7tFzROw==} + engines: {node: '>=22'} + + argv-formatter@1.0.0: + resolution: {integrity: sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==} + + array-ify@1.0.0: + resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@1.0.7: + resolution: {integrity: sha512-kFL68AG6ajd8fg248zwM9GQrUWEp79gsmjum34OEXjs4yHuUMZfYKwOLW9GMmB4oNvVrj+EAGxsP7ye2UR9UlA==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.11.26: + resolution: {integrity: sha512-GLQdD3y6UF8iVuMJl5fHgE4jdn/ua7n+toKfLgNlg3BqQtOZjpy68T8Tup8/wGWZCDlm7KMg7tPb4MPn7oN0TQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + before-after-hook@4.0.0: + resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} + + bottleneck@2.19.5: + resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} + + brace-expansion@5.0.12: + resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} + engines: {node: 20 || >=22} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.29.2: + resolution: {integrity: sha512-/u9r8k8ue4ZhHCw9ovDtltpWdXeXzjhdxCGj6vm1RGLPXb8lu33EMdoCHF5Sx8oYBS+Q/FYYNmAqlHncxR3RmA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + cac@7.0.0: + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} + engines: {node: '>=20.19.0'} + + cacheable@2.5.0: + resolution: {integrity: sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001813: + resolution: {integrity: sha512-zfjJo4rM0+fUomGDBW/xcDjhIwz/210DGvip2MAMDZ8KHcRPnOHmEgHPZP0UHlZoxr8fYKVJOqcORhYQcG4FKQ==} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + chardet@2.2.0: + resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==} + + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + + clean-stack@5.3.0: + resolution: {integrity: sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==} + engines: {node: '>=14.16'} + + cli-highlight@2.1.11: + resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + + comment-parser@1.4.8: + resolution: {integrity: sha512-rKZTGo4fzKYna8UcL0isTg5wkBNla7bxTypLwZQXjIdi++IdP1OJ41rI5Mti3/jltkPujbu4i9LIARYA+zpotQ==} + engines: {node: '>= 12.0.0'} + + compare-func@2.0.0: + resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + + content-type@3.1.1: + resolution: {integrity: sha512-GW4qUsfFo59d0HbUibDlWv5wPz+vAAcaTWbKIuKCf0JkC7wWkSyf8f13IpXn5JkeMlB2P8iTSSIjwXljorg2vA==} + engines: {node: '>=22'} + + conventional-changelog-angular@8.3.1: + resolution: {integrity: sha512-6gfI3otXK5Ph5DfCOI1dblr+kN3FAm5a97hYoQkqNZxOaYa5WKfXH+AnpsmS+iUH2mgVC2Cg2Qw9m5OKcmNrIg==} + engines: {node: '>=18'} + + conventional-changelog-angular@9.4.0: + resolution: {integrity: sha512-HdxRxuS8bBXVIuo4V82gvSwAXT0vYQUizrjs/izmPg5JdDstr8v8I5hduGL3iQbG+o310dUDxC4+LetuS5hu9w==} + engines: {node: '>=22'} + + conventional-changelog-conventionalcommits@10.4.0: + resolution: {integrity: sha512-Rriac6ZrAlVm6cy9Bz4NSp+WMHpwNXoPIYex+HjCgduAVUSbnew29DQjQw0C4g9u3HtSYzGiGY+pdBXAZo+4aA==} + engines: {node: '>=22'} + + conventional-changelog-writer@8.4.0: + resolution: {integrity: sha512-HHBFkk1EECxxmCi4CTu091iuDpQv5/OavuCUAuZmrkWpmYfyD816nom1CvtfXJ/uYfAAjavgHvXHX291tSLK8g==} + engines: {node: '>=18'} + hasBin: true + + conventional-commits-filter@5.0.0: + resolution: {integrity: sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==} + engines: {node: '>=18'} + + conventional-commits-parser@6.4.0: + resolution: {integrity: sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==} + engines: {node: '>=18'} + hasBin: true + + conventional-commits-parser@7.1.2: + resolution: {integrity: sha512-O+x4N2yH+ijvqWlIyTHsXTAP+algNWgGbjY2duCe8w2vUMvUB95cLRslCPfTMQyLAKlet3bhZTdu6ozn4M+QJQ==} + engines: {node: '>=22'} + hasBin: true + + convert-hrtime@5.0.0: + resolution: {integrity: sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==} + engines: {node: '>=12'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cosmiconfig-extends@1.0.0: + resolution: {integrity: sha512-vwqRLc9XoQ61TMWO/oGnCj/osbbvIWfYXAlyVMQpBD9/9skbjMbMLFNPE3UiTU1GKz1yJ8brnps10PM2UagS1w==} + engines: {node: '>=20'} + peerDependencies: + cosmiconfig: ^9.0.0 || ^10.0.0 + + cosmiconfig-typescript-loader@6.3.0: + resolution: {integrity: sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==} + engines: {node: '>=v18'} + peerDependencies: + '@types/node': '*' + cosmiconfig: '>=9' + typescript: '>=5' + + cosmiconfig@10.0.1: + resolution: {integrity: sha512-/zvGoYn0y27QTBmK+0B+oPdEPC4fKGUmJsMTQbDjqph00TrkqfVFW/M+C6ya54sYCgHC6QCwK/mI6djt8cEGHQ==} + engines: {node: ^22.18 || >= 24} + + cosmiconfig@9.0.2: + resolution: {integrity: sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + crypto-random-string@4.0.0: + resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} + engines: {node: '>=12'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + + des.js@1.1.0: + resolution: {integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + diff-match-patch@1.0.5: + resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dot-prop@5.3.0: + resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} + engines: {node: '>=8'} + + dts-resolver@3.0.0: + resolution: {integrity: sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==} + engines: {node: ^22.18.0 || >=24.0.0} + peerDependencies: + oxc-resolver: '>=11.0.0' + peerDependenciesMeta: + oxc-resolver: + optional: true + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + duplexer2@0.1.4: + resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + + electron-to-chromium@1.5.439: + resolution: {integrity: sha512-qu6QIPXhsb+CRcAiTMNjR4A1y/7tCYKkKjr5CZXRVih6qkDf79peZ2BpEU3qoDBNCRrcy3Mra3X9nG5oruuA7Q==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emojilib@2.4.0: + resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} + + empathic@2.1.0: + resolution: {integrity: sha512-AnfC1ATldl49/cvZdLPDjBfrRNwbDO05aibiOtzQu3qtlbJtomNLhF30HEtn/7iBz50dlMECqATo3fG0LrdEgw==} + engines: {node: '>=14'} + + env-ci@11.2.0: + resolution: {integrity: sha512-D5kWfzkmaOQDioPmiviWAVtKmpPT4/iJmMVQxWxMPJTFyTkdc5JQUfc5iXEeWxcOdsYTKSAiA/Age4NUOqKsRA==} + engines: {node: ^18.17 || >=20.6.1} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-toolkit@1.52.0: + resolution: {integrity: sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + eslint-plugin-jsdoc@64.3.6: + resolution: {integrity: sha512-lo7IXmgUUNy88SxW7KnJmmD2iPQBIRMomfCFPHidW1M3zNNVuzxlB2uoNrNyE1g4v2/L+RtYmiROPwQhSNzL8Q==} + engines: {node: ^22.22.2 || >=24.15.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + + eslint-plugin-json-canonical@2.1.0: + resolution: {integrity: sha512-sjT8FdqSvpEiB0yzCOGBozbEJ64SLxDp8CYlV9wHEgL0HFLj3kJdfWl5UuzMe72OoOXbwzo418VeUlxX5iNfgg==} + engines: {node: '>=20'} + peerDependencies: + '@eslint/json': ^2.1.0 + eslint: '>=10.0.0' + + eslint-plugin-tsdoc@0.5.3: + resolution: {integrity: sha512-WVG/kTgaJuTaAyHqTFmJ6W6rD6vV5UTHjw9ipMPA0fcmHLbJSLPvnY645p0N5bidRGTkboNEYrkOufnhC6RYXw==} + peerDependencies: + eslint: '>=7' + peerDependenciesMeta: + eslint: + optional: true + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.11.0: + resolution: {integrity: sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + execa@10.0.1: + resolution: {integrity: sha512-ge98qjkRK4IB7tL7Ju/6qmm5LHoH1eEMt5FNZrz3f4UIYhF28lggX20z3FaX1sgc67msLEn0N0BscOs29iuwyw==} + engines: {node: '>=22'} + + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + + execa@9.6.1: + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} + engines: {node: ^18.19.0 || >=20.5.0} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + + fast-uri@3.1.8: + resolution: {integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==} + + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} + + figures@2.0.0: + resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==} + engines: {node: '>=4'} + + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + + file-entry-cache@11.1.5: + resolution: {integrity: sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up-simple@1.0.1: + resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} + engines: {node: '>=18'} + + find-up@2.1.0: + resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} + engines: {node: '>=4'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + find-versions@6.0.0: + resolution: {integrity: sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==} + engines: {node: '>=18'} + + flat-cache@6.1.23: + resolution: {integrity: sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==} + + flatted@3.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} + + flru@1.0.2: + resolution: {integrity: sha512-kWyh8ADvHBFz6ua5xYOPnUroZTT/bwWfrCeL0Wj1dzG4/YOmOcfJ99W8dOVyyynJN35rZ9aCOtHChqQovV7yog==} + engines: {node: '>=6'} + + fs-extra@11.4.1: + resolution: {integrity: sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==} + engines: {node: '>=14.14'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function-timeout@1.0.2: + resolution: {integrity: sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==} + engines: {node: '>=18'} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.7.0: + resolution: {integrity: sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==} + engines: {node: '>=18'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + + get-tsconfig@5.0.0-beta.6: + resolution: {integrity: sha512-X6fBC0pmImC70gvX2zm56go9hx0MyoGVdG0tUCkg/D+Xnh5TJsOZ7iDbOdI3PvmtrDxnu1YdDufpK2QJX1Meqw==} + engines: {node: '>=20.20.0'} + + git-log-parser@1.2.1: + resolution: {integrity: sha512-PI+sPDvHXNPl5WNOErAK05s3j0lgwUzMN6o8cyQrDaKfT3qd7TmNJKeXX+SknI5I0QhG5fVPAEwSY4tRGDtYoQ==} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + global-directory@5.0.0: + resolution: {integrity: sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==} + engines: {node: '>=20'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + handlebars@4.7.9: + resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==} + engines: {node: '>=0.4.7'} + hasBin: true + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hashery@1.5.1: + resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==} + engines: {node: '>=20'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + + hook-std@4.0.0: + resolution: {integrity: sha512-IHI4bEVOt3vRUDJ+bFA9VUJlo7SzvFARPNLw75pqSmAOP2HmTWfFJtPvLBrDrlgjEYXY9zs7SFdHPQaJShkSCQ==} + engines: {node: '>=20'} + + hookable@6.1.2: + resolution: {integrity: sha512-+abwxtiEA52GCVIsQqut3S/uKTbUwYIp4Pe/vv+6py5XiXBCqMZHg6pA6Y5qhgLSEys0/cYuPbO0z1QFj5ZCmg==} + + hookified@1.15.1: + resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==} + + hookified@2.2.0: + resolution: {integrity: sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==} + + hosted-git-info@7.0.2: + resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} + engines: {node: ^16.14.0 || >=18.0.0} + + hosted-git-info@9.0.3: + resolution: {integrity: sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==} + engines: {node: ^20.17.0 || >=22.9.0} + + html-entities@2.6.0: + resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-proxy-agent@9.1.0: + resolution: {integrity: sha512-2NxoveTT58mjYT4n3RPTEfCZGLMbidoO8XEieXfpSYxu+PQJ1qpx4ypwH6N+uF9twBPIvRRgvkvW5HUTYWENig==} + engines: {node: '>= 20'} + + https-proxy-agent@9.1.0: + resolution: {integrity: sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==} + engines: {node: '>= 20'} + + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + human-signals@8.0.1: + resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} + engines: {node: '>=18.18.0'} + + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.10: + resolution: {integrity: sha512-HpbUakT7xp5miBUywCHf36ZEuAJNklBJDDsGpUIjMzOSmM8ELSfA9Sa/QDPeNeqeoN31u+UTCkL4klCOVvRm4Q==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-from-esm@2.0.0: + resolution: {integrity: sha512-YVt14UZCgsX1vZQ3gKjkWVdBdHQ6eu3MPU1TBgL1H5orXe2+jWD006WCPPtOuwlQm10NuzOW5WawiF1Q9veW8g==} + engines: {node: '>=18.20'} + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + + import-without-cache@0.4.1: + resolution: {integrity: sha512-vXoV9PjKHEednCUu01e98TkImxy67e3BJXbTIOmIq4Hyzw3IAwYRcuPkfeTzJzwyyts4kjuA73x+pWJLc4f86A==} + engines: {node: ^22.18.0 || >=24.0.0} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + + index-to-position@1.2.0: + resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} + engines: {node: '>=18'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + ini@6.0.0: + resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-core-module@2.17.0: + resolution: {integrity: sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + issue-parser@7.0.2: + resolution: {integrity: sha512-7atWPjhGEIX3JEtMrOYd8TKzboYlq+5sNbdl9POiLYOI14G5HZiQbZP0Xj5EZdrufQVXfJlpTV0hys0CuxwxZw==} + engines: {node: ^18.17 || >=20.6.1} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + java-properties@1.0.2: + resolution: {integrity: sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==} + engines: {node: '>= 0.6.0'} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + jju@1.4.0: + resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + + js-md4@0.3.2: + resolution: {integrity: sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==} + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.3.2: + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} + hasBin: true + + js-yaml@5.4.2: + resolution: {integrity: sha512-m+aqu+LwO1O6sIopafj8HUVl5aawITwZQe/yHpMCKjaWBaA/d07B/QdMb3529REftiU+RMMHL3Vlsw3hON7vWg==} + hasBin: true + + jsdoc-type-pratt-parser@9.2.2: + resolution: {integrity: sha512-eFY7Rlscvv/BpY6UsVsjKwMhu1tJO/F4U/vC9g3yeRy/JqszuO1UP6Vdq+85G+3FP6fEw/sWR8a5ftPFY86csw==} + engines: {node: ^22.22.2 || >=24.15.0} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-parse-better-errors@1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-rpc-2.0@1.8.1: + resolution: {integrity: sha512-BmN42JKn6FI36pcMMzHCXQhpKNEfbeqgSIcUYHKnifq0/4Ey6PDinovwUOgajT7NsgUtpWdDXaCWlpI4LHXN3g==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json-with-bigint@3.5.12: + resolution: {integrity: sha512-uwbF/wSSuOgC7qqlq27Xp5B6a2MHVug3t0idZdTqu0JnlFvgJuH7ju+KAk/J06C7GfhoYy2gnb9wz2INqcne7w==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + + keyv@5.6.0: + resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lint-staged@17.6.0: + resolution: {integrity: sha512-nOhBfYkZEwkFtNzD5pK9s9LkcWoxJ+whLzEnhqi9nl7zTB5n7tg8+9iYgMGx2Wm3VlLu8cbNj0RACzY+nWi5iQ==} + engines: {node: '>=22.22.1'} + hasBin: true + + load-json-file@4.0.0: + resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} + engines: {node: '>=4'} + + locate-path@2.0.0: + resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} + engines: {node: '>=4'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + + lodash.capitalize@4.2.1: + resolution: {integrity: sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==} + + lodash.escaperegexp@4.1.2: + resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + + lodash.groupby@4.6.0: + resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.uniqby@4.7.0: + resolution: {integrity: sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@11.5.3: + resolution: {integrity: sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==} + engines: {node: 20 || >=22} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.5: + resolution: {integrity: sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==} + + make-asynchronous@1.1.0: + resolution: {integrity: sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg==} + engines: {node: '>=18'} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + marked-terminal@7.3.0: + resolution: {integrity: sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==} + engines: {node: '>=16.0.0'} + peerDependencies: + marked: '>=1 <16' + + marked@15.0.12: + resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==} + engines: {node: '>= 18'} + hasBin: true + + marked@9.1.6: + resolution: {integrity: sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==} + engines: {node: '>= 16'} + hasBin: true + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + meow@13.2.0: + resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} + engines: {node: '>=18'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime@4.1.0: + resolution: {integrity: sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==} + engines: {node: '>=16'} + hasBin: true + + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + + minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} + engines: {node: 18 || 20 || >=22} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mutation-server-protocol@0.4.1: + resolution: {integrity: sha512-SBGK0j8hLDne7bktgThKI8kGvGTx3rY3LAeQTmOKZ5bVnL/7TorLMvcVF7dIPJCu5RNUWhkkuF53kurygYVt3g==} + engines: {node: '>=18'} + + mutation-testing-elements@3.8.4: + resolution: {integrity: sha512-5CF1SNa7at5ZH33vEr+21wNebTSrtNIVvnzaUlxortHajOrIPaSLczIWvg6sI/fsExekQ7jookwf2cHftkckqQ==} + + mutation-testing-metrics@3.8.4: + resolution: {integrity: sha512-DZcmndJBH6nrNs3tpiB3OcMVq9KkG2cHCpJSnDxSxPwi9qrafRmoec40xjhkbzOoX1n7/4UkDqg5tIj4A6nvCw==} + + mutation-testing-report-schema@3.8.4: + resolution: {integrity: sha512-s4G71R6Lt/PpZ0cqeglIcgyBdzLM8E+SeCHZAPg1wkSsPtRBa4XfPzAozYKdiJk/TLbNEEb7En9t0/bveuPuxA==} + + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.19: + resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + nerf-dart@1.0.0: + resolution: {integrity: sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==} + + node-emoji@2.2.0: + resolution: {integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==} + engines: {node: '>=18'} + + node-releases@2.0.57: + resolution: {integrity: sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==} + engines: {node: '>=18'} + + normalize-package-data@6.0.2: + resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} + engines: {node: ^16.14.0 || >=18.0.0} + + normalize-package-data@8.0.0: + resolution: {integrity: sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + normalize-url@9.0.1: + resolution: {integrity: sha512-ARftfC5HdUNu9jJeL8pHj8debUIHA2b91FizCoMzY4lG6dDX13jdvTK0TBe24IBDRf2HvJSzzwEPvmbkQWHRSg==} + engines: {node: '>=20'} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + + npm@11.20.0: + resolution: {integrity: sha512-dF3EDFwbYN+N5RUip+ZYDe0NeURK5BgqKOcvT1iNtUYhTMTl0FwWhBuXrS7KtXyduqyTMS5aaQaregnHDAxNgw==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + bundledDependencies: + - '@isaacs/string-locale-compare' + - '@npmcli/arborist' + - '@npmcli/config' + - '@npmcli/fs' + - '@npmcli/map-workspaces' + - '@npmcli/metavuln-calculator' + - '@npmcli/package-json' + - '@npmcli/promise-spawn' + - '@npmcli/redact' + - '@npmcli/run-script' + - '@sigstore/tuf' + - abbrev + - archy + - cacache + - chalk + - ci-info + - fastest-levenshtein + - fs-minipass + - glob + - graceful-fs + - hosted-git-info + - ini + - init-package-json + - is-cidr + - json-parse-even-better-errors + - libnpmaccess + - libnpmdiff + - libnpmexec + - libnpmfund + - libnpmorg + - libnpmpack + - libnpmpublish + - libnpmsearch + - libnpmteam + - libnpmversion + - make-fetch-happen + - minimatch + - minipass + - minipass-pipeline + - ms + - node-gyp + - nopt + - npm-audit-report + - npm-install-checks + - npm-package-arg + - npm-pick-manifest + - npm-profile + - npm-registry-fetch + - npm-user-validate + - p-map + - pacote + - parse-conflict-json + - proc-log + - qrcode-terminal + - read + - semver + - spdx-expression-parse + - ssri + - supports-color + - tar + - text-table + - tiny-relative-date + - treeverse + - validate-npm-package-name + - which + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-deep-merge@2.0.1: + resolution: {integrity: sha512-aKttDKcU3pyZqKcCkDhsMn70WmZFG2JGDQLP9EcLyTSIFQRCPWLAmBZRLJnrVUrhPG1jETEEbfdgbNtJf1LyMg==} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + obug@2.2.1: + resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} + engines: {node: '>=12.20.0'} + + obug@3.0.0: + resolution: {integrity: sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==} + engines: {node: '>=12.20.0'} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-each-series@3.0.0: + resolution: {integrity: sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==} + engines: {node: '>=12'} + + p-event@6.0.1: + resolution: {integrity: sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==} + engines: {node: '>=16.17'} + + p-filter@4.1.0: + resolution: {integrity: sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==} + engines: {node: '>=18'} + + p-limit@1.3.0: + resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} + engines: {node: '>=4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@2.0.0: + resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} + engines: {node: '>=4'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-map@7.0.8: + resolution: {integrity: sha512-MitaVsCuCFIvOLLPIU7NnfrZvS9H9h7kwMUkDo+T2pEISaJD48IV9S8iIdXB7PsvvdxyYcsSTTrr90XKsbulNw==} + engines: {node: '>=18'} + + p-reduce@3.0.0: + resolution: {integrity: sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==} + engines: {node: '>=12'} + + p-timeout@6.1.4: + resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==} + engines: {node: '>=14.16'} + + p-try@1.0.0: + resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} + engines: {node: '>=4'} + + package-manager-detector@1.8.0: + resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-imports-exports@0.2.4: + resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==} + + parse-json@4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-json@8.3.0: + resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} + engines: {node: '>=18'} + + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + + parse-statements@1.0.11: + resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==} + + parse5-htmlparser2-tree-adapter@6.0.1: + resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} + + parse5@5.1.1: + resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} + + parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + + path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + pify@3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + + pkg-conf@2.1.0: + resolution: {integrity: sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==} + engines: {node: '>=4'} + + postcss@8.5.28: + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + pretty-ms@9.3.1: + resolution: {integrity: sha512-HzMy3Geq23nVALD/M2LliU+F+M+gVNsvkQWWqeBZ8HDiCgzo6YPJ/Omrmtq24EFrIsk0a3EkQGEd7bDOo+IhGA==} + engines: {node: '>=18'} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + + proxy-agent-negotiate@1.1.0: + resolution: {integrity: sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ==} + engines: {node: '>= 20'} + peerDependencies: + kerberos: ^2.0.0 + peerDependenciesMeta: + kerberos: + optional: true + + publint@0.3.24: + resolution: {integrity: sha512-9zS56KrKBoqi5Qt8h92uMP8TTM9AYZSgnmCo4u2priMqkOZvQnTsziZ2p5LJ2ywbYkAjoCDp2jda9u4cgFefIw==} + engines: {node: '>=18'} + hasBin: true + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qified@0.10.1: + resolution: {integrity: sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==} + engines: {node: '>=20'} + + qs@6.15.1: + resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} + engines: {node: '>=0.6'} + + quansync@1.0.0: + resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + read-package-up@11.0.0: + resolution: {integrity: sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==} + engines: {node: '>=18'} + + read-package-up@12.0.0: + resolution: {integrity: sha512-Q5hMVBYur/eQNWDdbF4/Wqqr9Bjvtrw2kjGxxBbKLbx8bVCL8gcArjTy8zDUuLGQicftpMuU0riQNcAsbtOVsw==} + engines: {node: '>=20'} + + read-pkg@10.1.0: + resolution: {integrity: sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg==} + engines: {node: '>=20'} + + read-pkg@9.0.1: + resolution: {integrity: sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==} + engines: {node: '>=18'} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + registry-auth-token@5.1.1: + resolution: {integrity: sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==} + engines: {node: '>=14'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + reserved-identifiers@1.2.0: + resolution: {integrity: sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==} + engines: {node: '>=18'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + + rolldown-plugin-dts@0.28.6: + resolution: {integrity: sha512-qKrFtBfRfR2hP233m7Ic9zf3wv6MSYdZghWKMdEO6dRYZGlNfINJAa1P5ZVvyHh3mrcd7IJQvIY8L9vnm+v5rQ==} + engines: {node: ^22.18.0 || ^24.11.0 || >=26.0.0} + peerDependencies: + '@typescript/native-preview': '*' + '@volar/typescript': ~2.4.0 + '@vue/language-core': ~3.2.0 || ~3.3.0 + rolldown: ^1.2.0 + typescript: ^5.0.0 || ^6.0.0 || ~7.0.0 + vue-tsc: ~3.2.0 || ~3.3.0 + peerDependenciesMeta: + '@typescript/native-preview': + optional: true + '@volar/typescript': + optional: true + '@vue/language-core': + optional: true + typescript: + optional: true + vue-tsc: + optional: true + + rolldown@1.2.11: + resolution: {integrity: sha512-qpSwIyz0jHQq5qXBTNxFmE6664rJ7O+4TvPFOiOaBSrz8IOHc1koKKSqTM2H6u1UG1+TveuC6vaDHKXFOvb1Kw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semantic-release@25.0.9: + resolution: {integrity: sha512-bxve7csK0/Txr++CkfrmV+X1r4jqiSOw2WsSad9E2S68R+ZfLBwDn8IceM8WfiOmKQIHgsQc1cNA8Dzg7U75pg==} + engines: {node: ^22.14.0 || >= 24.10.0} + hasBin: true + + semver-regex@4.0.5: + resolution: {integrity: sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==} + engines: {node: '>=12'} + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + signale@1.4.0: + resolution: {integrity: sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==} + engines: {node: '>=6'} + + skin-tone@2.0.0: + resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} + engines: {node: '>=8'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + spawn-error-forwarder@1.0.0: + resolution: {integrity: sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==} + + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-expression-parse@5.0.0: + resolution: {integrity: sha512-vngmw3Rgn+o2arXNbnZaj5UtOEBuWBfvaI+Wc8GFfykIhA5/vdK9/Sp/XkLv63dykz2rxKDvKEHupF5P0FORcQ==} + + spdx-license-ids@3.0.24: + resolution: {integrity: sha512-cLS9TtWkIQFyLkJ3/5aFQAOHOSKTlOs/7WDut/XPSdjom1fJhUdkepeJAKXa9Y05+FabHd0sti+Et559vDtkpQ==} + + split2@1.0.0: + resolution: {integrity: sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + + stream-combiner2@1.1.1: + resolution: {integrity: sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string-width@8.3.0: + resolution: {integrity: sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==} + engines: {node: '>=20'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + super-regex@1.1.0: + resolution: {integrity: sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==} + engines: {node: '>=18'} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-hyperlinks@3.2.0: + resolution: {integrity: sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==} + engines: {node: '>=14.18'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + + temp-dir@3.0.0: + resolution: {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==} + engines: {node: '>=14.16'} + + tempy@3.2.0: + resolution: {integrity: sha512-d79HhZya5Djd7am0q+W4RTsSU+D/aJzM+4Y4AGJGuGlgM2L6sx5ZvOYTmZjqPhrDrV6xJTtRSm1JCLj6V6LHLQ==} + engines: {node: '>=14.16'} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + through2@2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + + time-span@5.1.0: + resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==} + engines: {node: '>=12'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.1: + resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} + engines: {node: '>=14.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + to-valid-identifier@1.0.0: + resolution: {integrity: sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==} + engines: {node: '>=20'} + + traverse@0.6.8: + resolution: {integrity: sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==} + engines: {node: '>= 0.4'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tsdown@0.23.0: + resolution: {integrity: sha512-BaT+ep1xnj5hdyICLd5r5SYYE+TXnI1ATePLykv9vcKpHpFTWUWRH+x1AF/E2qcu3YB6+vI8IdyxIIIffEqw5Q==} + engines: {node: ^22.18.0 || ^24.11.0 || >=26.0.0} + hasBin: true + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + '@tsdown/css': 0.23.0 + '@tsdown/exe': 0.23.0 + '@vitejs/devtools': '*' + publint: ^0.3.8 + tsx: '*' + typescript: ^5.0.0 || ^6.0.0 || ^7.0.0 + unplugin-unused: '>=0.5.0' + unrun: '*' + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + '@tsdown/css': + optional: true + '@tsdown/exe': + optional: true + '@vitejs/devtools': + optional: true + publint: + optional: true + tsx: + optional: true + typescript: + optional: true + unplugin-unused: + optional: true + unrun: + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tunnel@0.0.6: + resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} + engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@1.4.0: + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} + + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + type-fest@5.10.0: + resolution: {integrity: sha512-NoSdpq/WEiAg5sjmBkmV/hfxv6HJH4NqPNrqjtSO5CwRmpsDfaf4begxW34KdJykH/l1yHtwBWQkCRdoXO8mPA==} + engines: {node: '>=20'} + + typed-inject@5.0.0: + resolution: {integrity: sha512-0Ql2ORqBORLMdAW89TQKZsb1PQkFGImFfVmncXWe7a+AA3+7dh7Se9exxZowH4kbnlvKEFkMxUYdHUpjYWFJaA==} + engines: {node: '>=18'} + + typed-rest-client@2.3.1: + resolution: {integrity: sha512-k4kX5Up6qA68D0Cby2AK+6+vM5k3qTxe+/3FqhnHRExjY5cfbOnzjQZbP/LXleF8hVoDvDqxlgk9KK83HoBZlQ==} + engines: {node: '>= 16.0.0'} + + typescript-eslint@8.71.0: + resolution: {integrity: sha512-fBdHYiqQ14RW6mOMXD14Svn82ZsCYAoQSzGRzyEjR59S5A2Krh/l7fGTOQ7iCr8gGy/mHVXtEF7s5fgjEdV0Pw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + typescript@5.6.1-rc: + resolution: {integrity: sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==} + engines: {node: '>=14.17'} + hasBin: true + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + + unconfig-core@7.5.0: + resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} + + underscore@1.13.8: + resolution: {integrity: sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==} + + undici-types@8.9.0: + resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} + + undici@6.29.0: + resolution: {integrity: sha512-R+RODBqp6i2pPflGdq+xIOUkl+RNfGgHwoinecKu/JCuf2uO06cOKoDbI2P7Dn6KcswdKwrczbU6IYJ6K8X+wg==} + engines: {node: '>=18.17'} + + undici@7.30.0: + resolution: {integrity: sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==} + engines: {node: '>=20.18.1'} + + unicode-emoji-modifier-base@1.0.0: + resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} + engines: {node: '>=4'} + + unicorn-magic@0.1.0: + resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + engines: {node: '>=18'} + + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + + unicorn-magic@0.4.1: + resolution: {integrity: sha512-lzlXPoVB0Uy/FvTaUgX39RbixYiSKoc3JSoSf01+0c2B6FR3qdYIPN7Qjo/AV7n6sqLCk0509cT3LRj1oSZ1+A==} + engines: {node: '>=20'} + + unique-string@3.0.0: + resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} + engines: {node: '>=12'} + + universal-user-agent@7.0.3: + resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + update-browserslist-db@1.3.3: + resolution: {integrity: sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + url-join@5.0.0: + resolution: {integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + + validate-npm-package-name@5.0.1: + resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + verkit@0.3.2: + resolution: {integrity: sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg==} + engines: {node: '>=18.12.0'} + + verkit@0.4.1: + resolution: {integrity: sha512-mMVzj0TXExtVdlDEq+Mzp0eyOuyznNpFobNM3uAqe3HVm/Ps2c+u17BligMkZjZCA6EiXpoon8iRTer3iu40SQ==} + engines: {node: '>=18.12.0'} + + vite@8.3.1: + resolution: {integrity: sha512-/bvH9E9tmCXRGp2uXY3WbOldqpTwFkbha/8ANaEQ6VkxhH60KyqLwgZq6lG2y+4uT55x9+9eUHMpQ7uGnOCKjA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.7.1 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.11: + resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.11 + '@vitest/browser-preview': 4.1.11 + '@vitest/browser-webdriverio': 4.1.11 + '@vitest/coverage-istanbul': 4.1.11 + '@vitest/coverage-v8': 4.1.11 + '@vitest/ui': 4.1.11 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + weapon-regex@2.0.5: + resolution: {integrity: sha512-BJZkSdtcae9Wb8+hKNtAqR+Q61EMOAuQnpCCHKwT50K7fIWg7/2/RnbKR27YB+sMcYxYLUVj+Asm5Y2XhD8mZg==} + + web-worker@1.5.0: + resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==} + + which-command@0.1.0: + resolution: {integrity: sha512-XZyoF5/5hZtXitIwzrU4NKK+Wtbb9aB9CezUEw2Q0wlYK8NUYQxC1rRXgNueYLtBAJwXIb+/tFVk4dozciNJMA==} + engines: {node: '>=22'} + hasBin: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yaml@2.9.1: + resolution: {integrity: sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yargs@16.2.2: + resolution: {integrity: sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==} + engines: {node: '>=10'} + + yargs@18.2.0: + resolution: {integrity: sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yoctocolors@2.2.0: + resolution: {integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==} + engines: {node: '>=18'} + + yuku-ast@0.10.2: + resolution: {integrity: sha512-UnG9mA6giglCvSErft2/40TVFX750Sj1xgwddPLpG7J7rlr/P1wPADg9G2RK+d/1tNLtRVoLdMMOMVBB9561TQ==} + + yuku-codegen@0.10.2: + resolution: {integrity: sha512-hentl2dtrF6cPjiAirtkfxfFZBA6Hdm61UqRJ7cA0qrlDNMk9PZGOjw5w1mx3E0hu/6pHcJF4dJW+D0SXHPZOA==} + + yuku-parser@0.10.2: + resolution: {integrity: sha512-CgaU0/PPjCAIEZ3WQroosOxTY3eeKldAN3h+vk8pMNz4+jl1CZzBR4pW+K/rRPF112VooCL5FdjJoiMNjDrL2A==} + + zod@4.6.5: + resolution: {integrity: sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==} + +snapshots: + + '@actions/core@3.0.1': + dependencies: + '@actions/exec': 3.0.0 + '@actions/http-client': 4.0.1 + + '@actions/exec@3.0.0': + dependencies: + '@actions/io': 3.0.2 + + '@actions/http-client@4.0.1': + dependencies: + tunnel: 0.0.6 + undici: 6.29.0 + + '@actions/io@3.0.2': {} + + '@andrewbranch/untar.js@1.0.4': {} + + '@arethetypeswrong/cli@0.18.5': + dependencies: + '@arethetypeswrong/core': 0.18.5 + chalk: 4.1.2 + cli-table3: 0.6.5 + commander: 10.0.1 + marked: 9.1.6 + marked-terminal: 7.3.0(marked@9.1.6) + semver: 7.8.5 + + '@arethetypeswrong/core@0.18.5': + dependencies: + '@andrewbranch/untar.js': 1.0.4 + '@loaderkit/resolve': 1.0.6 + cjs-module-lexer: 1.4.3 + fflate: 0.8.3 + lru-cache: 11.5.3 + semver: 7.8.5 + typescript: 5.6.1-rc + validate-npm-package-name: 5.0.1 + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/code-frame@8.0.6': + dependencies: + '@babel/helper-validator-identifier': 8.0.6 + js-tokens: 10.0.0 + + '@babel/compat-data@8.0.5': {} + + '@babel/core@8.0.6': + dependencies: + '@babel/code-frame': 8.0.6 + '@babel/generator': 8.0.6 + '@babel/helper-compilation-targets': 8.0.6 + '@babel/helpers': 8.0.5 + '@babel/parser': 8.0.6 + '@babel/template': 8.0.0 + '@babel/traverse': 8.0.6 + '@babel/types': 8.0.6 + '@types/gensync': 1.0.5 + convert-source-map: 2.0.0 + empathic: 2.1.0 + gensync: 1.0.0-beta.2 + import-meta-resolve: 4.2.0 + json5: 2.2.3 + obug: 2.2.1 + verkit: 0.3.2 + + '@babel/generator@8.0.6': + dependencies: + '@babel/parser': 8.0.6 + '@babel/types': 8.0.6 + '@jridgewell/gen-mapping': 0.4.0-beta.0 + '@jridgewell/trace-mapping': 0.3.31 + '@types/jsesc': 2.5.1 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@8.0.0': + dependencies: + '@babel/types': 8.0.6 + + '@babel/helper-compilation-targets@8.0.6': + dependencies: + '@babel/compat-data': 8.0.5 + '@babel/helper-validator-option': 8.0.0 + browserslist: 4.29.2 + flru: 1.0.2 + verkit: 0.3.2 + + '@babel/helper-create-class-features-plugin@8.0.6(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@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/core@8.0.6) + '@babel/helper-skip-transparent-expression-wrappers': 8.0.0 + '@babel/traverse': 8.0.6 + verkit: 0.3.2 + + '@babel/helper-globals@8.0.6': {} + + '@babel/helper-member-expression-to-functions@8.0.5': + dependencies: + '@babel/traverse': 8.0.6 + '@babel/types': 8.0.6 + + '@babel/helper-module-imports@8.0.0': + dependencies: + '@babel/traverse': 8.0.6 + '@babel/types': 8.0.6 + + '@babel/helper-module-transforms@8.0.6(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-module-imports': 8.0.0 + '@babel/helper-validator-identifier': 8.0.6 + '@babel/traverse': 8.0.6 + + '@babel/helper-optimise-call-expression@8.0.0': + dependencies: + '@babel/types': 8.0.6 + + '@babel/helper-plugin-utils@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + + '@babel/helper-replace-supers@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-member-expression-to-functions': 8.0.5 + '@babel/helper-optimise-call-expression': 8.0.0 + '@babel/traverse': 8.0.6 + + '@babel/helper-skip-transparent-expression-wrappers@8.0.0': + dependencies: + '@babel/traverse': 8.0.6 + '@babel/types': 8.0.6 + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-string-parser@8.0.6': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-identifier@8.0.6': {} + + '@babel/helper-validator-option@8.0.0': {} + + '@babel/helpers@8.0.5': + dependencies: + '@babel/template': 8.0.0 + '@babel/types': 8.0.6 + + '@babel/parser@7.29.9': + dependencies: + '@babel/types': 7.29.8 + + '@babel/parser@8.0.6': + dependencies: + '@babel/types': 8.0.6 + + '@babel/plugin-proposal-decorators@8.0.2(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-create-class-features-plugin': 8.0.6(@babel/core@8.0.6) + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + '@babel/plugin-syntax-decorators': 8.0.1(@babel/core@8.0.6) + + '@babel/plugin-syntax-decorators@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + + '@babel/plugin-syntax-jsx@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + + '@babel/plugin-syntax-typescript@8.0.3(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + + '@babel/plugin-transform-destructuring@8.0.5(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + + '@babel/plugin-transform-explicit-resource-management@8.0.5(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + '@babel/plugin-transform-destructuring': 8.0.5(@babel/core@8.0.6) + + '@babel/plugin-transform-modules-commonjs@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-module-transforms': 8.0.6(@babel/core@8.0.6) + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + + '@babel/plugin-transform-react-display-name@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + + '@babel/plugin-transform-react-jsx-development@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/plugin-transform-react-jsx': 8.0.1(@babel/core@8.0.6) + + '@babel/plugin-transform-react-jsx@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-annotate-as-pure': 8.0.0 + '@babel/helper-module-imports': 8.0.0 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + '@babel/plugin-syntax-jsx': 8.0.1(@babel/core@8.0.6) + '@babel/types': 8.0.6 + + '@babel/plugin-transform-react-pure-annotations@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-annotate-as-pure': 8.0.0 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + + '@babel/plugin-transform-typescript@8.0.6(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-annotate-as-pure': 8.0.0 + '@babel/helper-create-class-features-plugin': 8.0.6(@babel/core@8.0.6) + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + '@babel/helper-skip-transparent-expression-wrappers': 8.0.0 + '@babel/plugin-syntax-typescript': 8.0.3(@babel/core@8.0.6) + + '@babel/preset-react@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + '@babel/helper-validator-option': 8.0.0 + '@babel/plugin-transform-react-display-name': 8.0.1(@babel/core@8.0.6) + '@babel/plugin-transform-react-jsx': 8.0.1(@babel/core@8.0.6) + '@babel/plugin-transform-react-jsx-development': 8.0.1(@babel/core@8.0.6) + '@babel/plugin-transform-react-pure-annotations': 8.0.1(@babel/core@8.0.6) + + '@babel/preset-typescript@8.0.1(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) + '@babel/helper-validator-option': 8.0.0 + '@babel/plugin-transform-modules-commonjs': 8.0.1(@babel/core@8.0.6) + '@babel/plugin-transform-typescript': 8.0.6(@babel/core@8.0.6) + + '@babel/template@8.0.0': + dependencies: + '@babel/code-frame': 8.0.6 + '@babel/parser': 8.0.6 + '@babel/types': 8.0.6 + + '@babel/traverse@8.0.6': + dependencies: + '@babel/code-frame': 8.0.6 + '@babel/generator': 8.0.6 + '@babel/helper-globals': 8.0.6 + '@babel/parser': 8.0.6 + '@babel/template': 8.0.0 + '@babel/types': 8.0.6 + obug: 2.2.1 + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@babel/types@8.0.6': + dependencies: + '@babel/helper-string-parser': 8.0.6 + '@babel/helper-validator-identifier': 8.0.6 + + '@bcoe/v8-coverage@1.0.2': {} + + '@braidai/lang@1.1.2': {} + + '@cacheable/memory@2.2.0': + dependencies: + '@cacheable/utils': 2.5.0 + '@keyv/bigmap': 1.3.1(keyv@5.6.0) + hookified: 1.15.1 + keyv: 5.6.0 + + '@cacheable/utils@2.5.0': + dependencies: + hashery: 1.5.1 + keyv: 5.6.0 + + '@colors/colors@1.5.0': + optional: true + + '@commitlint/cli@21.2.3(@types/node@26.6.3)(conventional-commits-parser@7.1.2)(typescript@6.0.3)': + dependencies: + '@commitlint/config-conventional': 21.2.3 + '@commitlint/format': 21.2.3 + '@commitlint/lint': 21.2.3 + '@commitlint/load': 21.2.3(@types/node@26.6.3)(typescript@6.0.3) + '@commitlint/read': 21.2.3(conventional-commits-parser@7.1.2) + '@commitlint/types': 21.2.3 + tinyexec: 1.3.1 + yargs: 18.2.0 + transitivePeerDependencies: + - '@types/node' + - conventional-commits-filter + - conventional-commits-parser + - typescript + + '@commitlint/config-conventional@21.2.3': + dependencies: + '@commitlint/types': 21.2.3 + conventional-changelog-conventionalcommits: 10.4.0 + + '@commitlint/config-validator@21.2.3': + dependencies: + '@commitlint/types': 21.2.3 + ajv: 8.20.0 + + '@commitlint/ensure@21.2.3': + dependencies: + '@commitlint/types': 21.2.3 + es-toolkit: 1.52.0 + + '@commitlint/execute-rule@21.0.1': {} + + '@commitlint/format@21.2.3': + dependencies: + '@commitlint/types': 21.2.3 + picocolors: 1.1.1 + + '@commitlint/is-ignored@21.2.3': + dependencies: + '@commitlint/types': 21.2.3 + semver: 7.8.5 + + '@commitlint/lint@21.2.3': + dependencies: + '@commitlint/is-ignored': 21.2.3 + '@commitlint/parse': 21.2.3 + '@commitlint/rules': 21.2.3 + '@commitlint/types': 21.2.3 + + '@commitlint/load@21.2.3(@types/node@26.6.3)(typescript@6.0.3)': + dependencies: + '@commitlint/config-validator': 21.2.3 + '@commitlint/execute-rule': 21.0.1 + '@commitlint/resolve-extends': 21.2.3 + '@commitlint/types': 21.2.3 + cosmiconfig: 9.0.2(typescript@6.0.3) + cosmiconfig-typescript-loader: 6.3.0(@types/node@26.6.3)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3) + es-toolkit: 1.52.0 + is-plain-obj: 4.1.0 + picocolors: 1.1.1 + transitivePeerDependencies: + - '@types/node' + - typescript + + '@commitlint/message@21.2.0': {} + + '@commitlint/parse@21.2.3': + dependencies: + '@commitlint/types': 21.2.3 + conventional-changelog-angular: 9.4.0 + conventional-commits-parser: 7.1.2 + + '@commitlint/read@21.2.3(conventional-commits-parser@7.1.2)': + dependencies: + '@commitlint/top-level': 21.2.0 + '@commitlint/types': 21.2.3 + '@conventional-changelog/git-client': 3.1.2(conventional-commits-parser@7.1.2) + tinyexec: 1.3.1 + transitivePeerDependencies: + - conventional-commits-filter + - conventional-commits-parser + + '@commitlint/resolve-extends@21.2.3': + dependencies: + '@commitlint/config-validator': 21.2.3 + '@commitlint/types': 21.2.3 + es-toolkit: 1.52.0 + global-directory: 5.0.0 + resolve-from: 5.0.0 + + '@commitlint/rules@21.2.3': + dependencies: + '@commitlint/ensure': 21.2.3 + '@commitlint/message': 21.2.0 + '@commitlint/to-lines': 21.0.1 + '@commitlint/types': 21.2.3 + + '@commitlint/to-lines@21.0.1': {} + + '@commitlint/top-level@21.2.0': + dependencies: + escalade: 3.2.0 + + '@commitlint/types@21.2.3': + dependencies: + conventional-commits-parser: 7.1.2 + picocolors: 1.1.1 + + '@conventional-changelog/git-client@3.1.2(conventional-commits-parser@7.1.2)': + dependencies: + '@simple-libs/child-process-utils': 2.0.0 + '@simple-libs/stream-utils': 2.0.0 + semver: 7.8.5 + optionalDependencies: + conventional-commits-parser: 7.1.2 + + '@conventional-changelog/template@1.4.0': {} + + '@es-joy/jsdoccomment@0.97.0': + dependencies: + '@types/estree': 1.0.9 + '@typescript-eslint/types': 8.71.0 + comment-parser: 1.4.8 + esquery: 1.7.0 + jsdoc-type-pratt-parser: 9.2.2 + + '@es-joy/resolve.exports@1.2.0': {} + + '@eslint-community/eslint-utils@4.10.1(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))': + dependencies: + eslint: 10.11.0(jiti@2.7.0)(supports-color@7.2.0) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.5(supports-color@7.2.0)': + dependencies: + '@eslint/object-schema': 3.0.5 + debug: 4.4.3(supports-color@7.2.0) + minimatch: 10.2.6 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.7.0': + dependencies: + '@eslint/core': 1.2.1 + + '@eslint/core@1.2.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/js@10.0.1(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))': + optionalDependencies: + eslint: 10.11.0(jiti@2.7.0)(supports-color@7.2.0) + + '@eslint/json@2.1.0': + dependencies: + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.3 + '@humanwhocodes/momoa': 3.3.13 + natural-compare: 1.4.0 + + '@eslint/object-schema@3.0.5': {} + + '@eslint/plugin-kit@0.7.3': + dependencies: + '@eslint/core': 1.2.1 + levn: 0.4.1 + + '@exadev/eslint-config@2.23.0(@eslint/json@2.1.0)(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript-eslint@8.71.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(typescript@6.0.3)': + dependencies: + '@eslint/js': 10.0.1(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0)) + '@microsoft/tsdoc': 0.17.0 + '@stylistic/eslint-plugin': 5.10.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0)) + '@typescript-eslint/utils': 8.67.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + eslint: 10.11.0(jiti@2.7.0)(supports-color@7.2.0) + eslint-plugin-jsdoc: 64.3.6(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + eslint-plugin-json-canonical: 2.1.0(@eslint/json@2.1.0)(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0)) + eslint-plugin-tsdoc: 0.5.3(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + typescript-eslint: 8.71.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + transitivePeerDependencies: + - '@eslint/json' + - supports-color + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/momoa@3.3.13': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@inquirer/ansi@2.0.8': {} + + '@inquirer/checkbox@5.2.5(@types/node@26.6.3)': + dependencies: + '@inquirer/ansi': 2.0.8 + '@inquirer/core': 12.0.3(@types/node@26.6.3) + '@inquirer/figures': 2.0.9 + '@inquirer/type': 4.1.1(@types/node@26.6.3) + optionalDependencies: + '@types/node': 26.6.3 + + '@inquirer/confirm@6.3.2(@types/node@26.6.3)': + dependencies: + '@inquirer/core': 12.0.3(@types/node@26.6.3) + '@inquirer/type': 4.1.1(@types/node@26.6.3) + optionalDependencies: + '@types/node': 26.6.3 + + '@inquirer/core@12.0.3(@types/node@26.6.3)': + dependencies: + '@inquirer/ansi': 2.0.8 + '@inquirer/figures': 2.0.9 + '@inquirer/type': 4.1.1(@types/node@26.6.3) + cli-width: 4.1.0 + fast-wrap-ansi: 0.2.2 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + optionalDependencies: + '@types/node': 26.6.3 + + '@inquirer/editor@5.3.3(@types/node@26.6.3)': + dependencies: + '@inquirer/core': 12.0.3(@types/node@26.6.3) + '@inquirer/external-editor': 3.0.5(@types/node@26.6.3) + '@inquirer/type': 4.1.1(@types/node@26.6.3) + optionalDependencies: + '@types/node': 26.6.3 + + '@inquirer/expand@5.1.5(@types/node@26.6.3)': + dependencies: + '@inquirer/core': 12.0.3(@types/node@26.6.3) + '@inquirer/type': 4.1.1(@types/node@26.6.3) + optionalDependencies: + '@types/node': 26.6.3 + + '@inquirer/external-editor@3.0.5(@types/node@26.6.3)': + dependencies: + chardet: 2.2.0 + iconv-lite: 0.7.3 + optionalDependencies: + '@types/node': 26.6.3 + + '@inquirer/figures@2.0.9': {} + + '@inquirer/input@5.1.6(@types/node@26.6.3)': + dependencies: + '@inquirer/core': 12.0.3(@types/node@26.6.3) + '@inquirer/type': 4.1.1(@types/node@26.6.3) + optionalDependencies: + '@types/node': 26.6.3 + + '@inquirer/number@4.2.3(@types/node@26.6.3)': + dependencies: + '@inquirer/core': 12.0.3(@types/node@26.6.3) + '@inquirer/type': 4.1.1(@types/node@26.6.3) + optionalDependencies: + '@types/node': 26.6.3 + + '@inquirer/password@5.2.2(@types/node@26.6.3)': + dependencies: + '@inquirer/ansi': 2.0.8 + '@inquirer/core': 12.0.3(@types/node@26.6.3) + '@inquirer/type': 4.1.1(@types/node@26.6.3) + optionalDependencies: + '@types/node': 26.6.3 + + '@inquirer/prompts@8.7.2(@types/node@26.6.3)': + dependencies: + '@inquirer/checkbox': 5.2.5(@types/node@26.6.3) + '@inquirer/confirm': 6.3.2(@types/node@26.6.3) + '@inquirer/editor': 5.3.3(@types/node@26.6.3) + '@inquirer/expand': 5.1.5(@types/node@26.6.3) + '@inquirer/input': 5.1.6(@types/node@26.6.3) + '@inquirer/number': 4.2.3(@types/node@26.6.3) + '@inquirer/password': 5.2.2(@types/node@26.6.3) + '@inquirer/rawlist': 5.3.5(@types/node@26.6.3) + '@inquirer/search': 4.3.3(@types/node@26.6.3) + '@inquirer/select': 5.2.5(@types/node@26.6.3) + optionalDependencies: + '@types/node': 26.6.3 + + '@inquirer/rawlist@5.3.5(@types/node@26.6.3)': + dependencies: + '@inquirer/core': 12.0.3(@types/node@26.6.3) + '@inquirer/type': 4.1.1(@types/node@26.6.3) + optionalDependencies: + '@types/node': 26.6.3 + + '@inquirer/search@4.3.3(@types/node@26.6.3)': + dependencies: + '@inquirer/core': 12.0.3(@types/node@26.6.3) + '@inquirer/figures': 2.0.9 + '@inquirer/type': 4.1.1(@types/node@26.6.3) + optionalDependencies: + '@types/node': 26.6.3 + + '@inquirer/select@5.2.5(@types/node@26.6.3)': + dependencies: + '@inquirer/ansi': 2.0.8 + '@inquirer/core': 12.0.3(@types/node@26.6.3) + '@inquirer/figures': 2.0.9 + '@inquirer/type': 4.1.1(@types/node@26.6.3) + optionalDependencies: + '@types/node': 26.6.3 + + '@inquirer/type@4.1.1(@types/node@26.6.3)': + optionalDependencies: + '@types/node': 26.6.3 + + '@jridgewell/gen-mapping@0.4.0-beta.0': + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 + + '@keyv/bigmap@1.3.1(keyv@5.6.0)': + dependencies: + hashery: 1.5.1 + hookified: 1.15.1 + keyv: 5.6.0 + + '@keyv/serialize@1.1.1': {} + + '@loaderkit/resolve@1.0.6': + dependencies: + '@braidai/lang': 1.1.2 + + '@microsoft/tsdoc-config@0.18.2': + dependencies: + '@microsoft/tsdoc': 0.17.0 + ajv: 8.18.0 + jju: 1.4.0 + resolve: 1.22.12 + + '@microsoft/tsdoc@0.17.0': {} + + '@octokit/auth-token@6.0.0': {} + + '@octokit/core@7.0.8': + dependencies: + '@octokit/auth-token': 6.0.0 + '@octokit/graphql': 9.0.5 + '@octokit/request': 10.0.16 + '@octokit/request-error': 7.1.2 + '@octokit/types': 18.0.0 + before-after-hook: 4.0.0 + universal-user-agent: 7.0.3 + + '@octokit/endpoint@11.0.5': + dependencies: + '@octokit/types': 18.0.0 + universal-user-agent: 7.0.3 + + '@octokit/graphql@9.0.5': + dependencies: + '@octokit/request': 10.0.16 + '@octokit/types': 18.0.0 + universal-user-agent: 7.0.3 + + '@octokit/openapi-types@28.0.0': {} + + '@octokit/openapi-types@29.0.1': {} + + '@octokit/plugin-paginate-rest@15.0.0(@octokit/core@7.0.8)': + dependencies: + '@octokit/core': 7.0.8 + '@octokit/types': 17.0.0 + + '@octokit/plugin-retry@8.1.1(@octokit/core@7.0.8)': + dependencies: + '@octokit/core': 7.0.8 + '@octokit/request-error': 7.1.2 + '@octokit/types': 17.0.0 + bottleneck: 2.19.5 + + '@octokit/plugin-throttling@11.0.5(@octokit/core@7.0.8)': + dependencies: + '@octokit/core': 7.0.8 + '@octokit/types': 17.0.0 + bottleneck: 2.19.5 + + '@octokit/request-error@7.1.2': + dependencies: + '@octokit/types': 18.0.0 + + '@octokit/request@10.0.16': + dependencies: + '@octokit/endpoint': 11.0.5 + '@octokit/request-error': 7.1.2 + '@octokit/types': 18.0.0 + content-type: 3.1.1 + json-with-bigint: 3.5.12 + universal-user-agent: 7.0.3 + + '@octokit/types@17.0.0': + dependencies: + '@octokit/openapi-types': 28.0.0 + + '@octokit/types@18.0.0': + dependencies: + '@octokit/openapi-types': 29.0.1 + + '@oxc-project/types@0.151.0': {} + + '@pnpm/config.env-replace@1.1.0': {} + + '@pnpm/network.ca-file@1.0.2': + dependencies: + graceful-fs: 4.2.10 + + '@pnpm/npm-conf@3.0.3': + dependencies: + '@pnpm/config.env-replace': 1.1.0 + '@pnpm/network.ca-file': 1.0.2 + config-chain: 1.1.13 + + '@publint/pack@0.1.7': + dependencies: + tinyexec: 1.3.1 + + '@quansync/fs@1.1.0': + dependencies: + quansync: 1.0.0 + + '@rolldown/binding-android-arm-eabi@1.2.11': + optional: true + + '@rolldown/binding-android-arm64@1.2.11': + optional: true + + '@rolldown/binding-darwin-arm64@1.2.11': + optional: true + + '@rolldown/binding-darwin-x64@1.2.11': + optional: true + + '@rolldown/binding-freebsd-x64@1.2.11': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.2.11': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.2.11': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.2.11': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.2.11': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.2.11': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.2.11': + optional: true + + '@rolldown/binding-linux-x64-musl@1.2.11': + optional: true + + '@rolldown/binding-openharmony-arm64@1.2.11': + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.2.11': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.2.11': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@sec-ant/readable-stream@0.4.1': {} + + '@semantic-release/changelog@7.0.0(semantic-release@25.0.9(supports-color@7.2.0)(typescript@6.0.3))': + dependencies: + '@semantic-release/error': 4.0.0 + aggregate-error: 5.0.0 + lodash-es: 4.18.1 + semantic-release: 25.0.9(supports-color@7.2.0)(typescript@6.0.3) + + '@semantic-release/commit-analyzer@13.0.1(semantic-release@25.0.9(supports-color@7.2.0)(typescript@6.0.3))(supports-color@7.2.0)': + dependencies: + conventional-changelog-angular: 8.3.1 + conventional-changelog-writer: 8.4.0 + conventional-commits-filter: 5.0.0 + conventional-commits-parser: 6.4.0 + debug: 4.4.3(supports-color@7.2.0) + import-from-esm: 2.0.0(supports-color@7.2.0) + lodash-es: 4.18.1 + micromatch: 4.0.8 + semantic-release: 25.0.9(supports-color@7.2.0)(typescript@6.0.3) + transitivePeerDependencies: + - supports-color + + '@semantic-release/error@4.0.0': {} + + '@semantic-release/git@11.0.1(semantic-release@25.0.9(supports-color@7.2.0)(typescript@6.0.3))(supports-color@7.2.0)': + dependencies: + '@semantic-release/error': 4.0.0 + aggregate-error: 5.0.0 + debug: 4.4.3(supports-color@7.2.0) + dir-glob: 3.0.1 + execa: 10.0.1 + lodash-es: 4.18.1 + micromatch: 4.0.8 + p-reduce: 3.0.0 + semantic-release: 25.0.9(supports-color@7.2.0)(typescript@6.0.3) + transitivePeerDependencies: + - supports-color + + '@semantic-release/github@12.0.10(semantic-release@25.0.9(supports-color@7.2.0)(typescript@6.0.3))(supports-color@7.2.0)': + dependencies: + '@octokit/core': 7.0.8 + '@octokit/plugin-paginate-rest': 15.0.0(@octokit/core@7.0.8) + '@octokit/plugin-retry': 8.1.1(@octokit/core@7.0.8) + '@octokit/plugin-throttling': 11.0.5(@octokit/core@7.0.8) + '@semantic-release/error': 4.0.0 + aggregate-error: 5.0.0 + debug: 4.4.3(supports-color@7.2.0) + dir-glob: 3.0.1 + http-proxy-agent: 9.1.0(supports-color@7.2.0) + https-proxy-agent: 9.1.0(supports-color@7.2.0) + issue-parser: 7.0.2 + lodash-es: 4.18.1 + mime: 4.1.0 + p-filter: 4.1.0 + semantic-release: 25.0.9(supports-color@7.2.0)(typescript@6.0.3) + tinyglobby: 0.2.17 + undici: 7.30.0 + url-join: 5.0.0 + transitivePeerDependencies: + - kerberos + - supports-color + + '@semantic-release/npm@13.2.0(semantic-release@25.0.9(supports-color@7.2.0)(typescript@6.0.3))': + dependencies: + '@actions/core': 3.0.1 + '@semantic-release/error': 4.0.0 + aggregate-error: 5.0.0 + env-ci: 11.2.0 + execa: 9.6.1 + fs-extra: 11.4.1 + lodash-es: 4.18.1 + nerf-dart: 1.0.0 + normalize-url: 9.0.1 + npm: 11.20.0 + rc: 1.2.8 + read-pkg: 10.1.0 + registry-auth-token: 5.1.1 + semantic-release: 25.0.9(supports-color@7.2.0)(typescript@6.0.3) + semver: 7.8.5 + tempy: 3.2.0 + + '@semantic-release/release-notes-generator@14.1.1(semantic-release@25.0.9(supports-color@7.2.0)(typescript@6.0.3))(supports-color@7.2.0)': + dependencies: + conventional-changelog-angular: 8.3.1 + conventional-changelog-writer: 8.4.0 + conventional-commits-filter: 5.0.0 + conventional-commits-parser: 6.4.0 + debug: 4.4.3(supports-color@7.2.0) + import-from-esm: 2.0.0(supports-color@7.2.0) + lodash-es: 4.18.1 + read-package-up: 11.0.0 + semantic-release: 25.0.9(supports-color@7.2.0)(typescript@6.0.3) + transitivePeerDependencies: + - supports-color + + '@simple-libs/child-process-utils@2.0.0': + dependencies: + '@simple-libs/stream-utils': 2.0.0 + + '@simple-libs/stream-utils@1.2.0': {} + + '@simple-libs/stream-utils@2.0.0': {} + + '@sindresorhus/base62@1.0.0': {} + + '@sindresorhus/is@4.6.0': {} + + '@sindresorhus/merge-streams@4.0.0': {} + + '@standard-schema/spec@1.1.0': {} + + '@stryker-mutator/api@10.0.0': + dependencies: + mutation-testing-metrics: 3.8.4 + mutation-testing-report-schema: 3.8.4 + tslib: 2.8.1 + typed-inject: 5.0.0 + + '@stryker-mutator/core@10.0.0(@types/node@26.6.3)': + dependencies: + '@inquirer/prompts': 8.7.2(@types/node@26.6.3) + '@stryker-mutator/api': 10.0.0 + '@stryker-mutator/instrumenter': 10.0.0 + '@stryker-mutator/util': 10.0.0 + ajv: 8.20.0 + chalk: 5.6.2 + commander: 14.0.3 + diff-match-patch: 1.0.5 + emoji-regex: 10.6.0 + execa: 9.6.1 + json-rpc-2.0: 1.8.1 + lodash.groupby: 4.6.0 + minimatch: 10.2.6 + mutation-server-protocol: 0.4.1 + 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.2 + semver: 7.8.5 + source-map: 0.7.6 + tree-kill: 1.2.2 + tslib: 2.8.1 + typed-inject: 5.0.0 + typed-rest-client: 2.3.1 + transitivePeerDependencies: + - '@types/node' + + '@stryker-mutator/instrumenter@10.0.0': + dependencies: + '@babel/core': 8.0.6 + '@babel/generator': 8.0.6 + '@babel/parser': 8.0.6 + '@babel/plugin-proposal-decorators': 8.0.2(@babel/core@8.0.6) + '@babel/plugin-transform-explicit-resource-management': 8.0.5(@babel/core@8.0.6) + '@babel/preset-react': 8.0.1(@babel/core@8.0.6) + '@babel/preset-typescript': 8.0.1(@babel/core@8.0.6) + '@babel/traverse': 8.0.6 + '@stryker-mutator/api': 10.0.0 + '@stryker-mutator/util': 10.0.0 + angular-html-parser: 10.11.0 + semver: 7.8.5 + tslib: 2.8.1 + weapon-regex: 2.0.5 + + '@stryker-mutator/typescript-checker@10.0.0(@stryker-mutator/core@10.0.0(@types/node@26.6.3))(typescript@6.0.3)': + dependencies: + '@stryker-mutator/api': 10.0.0 + '@stryker-mutator/core': 10.0.0(@types/node@26.6.3) + '@stryker-mutator/util': 10.0.0 + semver: 7.8.5 + typescript: 6.0.3 + + '@stryker-mutator/util@10.0.0': {} + + '@stryker-mutator/vitest-runner@10.0.0(@stryker-mutator/core@10.0.0(@types/node@26.6.3))(vitest@4.1.11)': + dependencies: + '@stryker-mutator/api': 10.0.0 + '@stryker-mutator/core': 10.0.0(@types/node@26.6.3) + '@stryker-mutator/util': 10.0.0 + semver: 7.8.5 + tslib: 2.8.1 + vitest: 4.1.11(@types/node@26.6.3)(@vitest/coverage-v8@4.1.11)(vite@8.3.1(@types/node@26.6.3)(jiti@2.7.0)(yaml@2.9.1)) + + '@stylistic/eslint-plugin@5.10.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))': + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0)) + '@typescript-eslint/types': 8.71.0 + eslint: 10.11.0(jiti@2.7.0)(supports-color@7.2.0) + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + estraverse: 5.3.0 + picomatch: 4.0.7 + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/esrecurse@4.3.1': {} + + '@types/estree@1.0.9': {} + + '@types/gensync@1.0.5': {} + + '@types/jsesc@2.5.1': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@26.6.3': + dependencies: + undici-types: 8.9.0 + + '@types/normalize-package-data@2.4.4': {} + + '@typescript-eslint/eslint-plugin@8.71.0(@typescript-eslint/parser@8.71.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.71.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/type-utils': 8.71.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.71.0 + eslint: 10.11.0(jiti@2.7.0)(supports-color@7.2.0) + ignore: 7.0.10 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.71.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.71.0 + debug: 4.4.3(supports-color@7.2.0) + eslint: 10.11.0(jiti@2.7.0)(supports-color@7.2.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.56.1(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@6.0.3) + '@typescript-eslint/types': 8.56.1 + debug: 4.4.3(supports-color@7.2.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.67.0(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@6.0.3) + '@typescript-eslint/types': 8.67.0 + debug: 4.4.3(supports-color@7.2.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.71.0(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 + debug: 4.4.3(supports-color@7.2.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + + '@typescript-eslint/scope-manager@8.67.0': + dependencies: + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/visitor-keys': 8.67.0 + + '@typescript-eslint/scope-manager@8.71.0': + dependencies: + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/visitor-keys': 8.71.0 + + '@typescript-eslint/tsconfig-utils@8.56.1(typescript@6.0.3)': + dependencies: + typescript: 6.0.3 + + '@typescript-eslint/tsconfig-utils@8.67.0(typescript@6.0.3)': + dependencies: + typescript: 6.0.3 + + '@typescript-eslint/tsconfig-utils@8.71.0(typescript@6.0.3)': + dependencies: + typescript: 6.0.3 + + '@typescript-eslint/type-utils@8.71.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + debug: 4.4.3(supports-color@7.2.0) + eslint: 10.11.0(jiti@2.7.0)(supports-color@7.2.0) + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.56.1': {} + + '@typescript-eslint/types@8.67.0': {} + + '@typescript-eslint/types@8.71.0': {} + + '@typescript-eslint/typescript-estree@8.56.1(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + '@typescript-eslint/project-service': 8.56.1(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@6.0.3) + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + debug: 4.4.3(supports-color@7.2.0) + minimatch: 10.2.6 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@8.67.0(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + '@typescript-eslint/project-service': 8.67.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@6.0.3) + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/visitor-keys': 8.67.0 + debug: 4.4.3(supports-color@7.2.0) + minimatch: 10.2.6 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@8.71.0(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + '@typescript-eslint/project-service': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/visitor-keys': 8.71.0 + debug: 4.4.3(supports-color@7.2.0) + minimatch: 10.2.6 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.56.1(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0)) + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(supports-color@7.2.0)(typescript@6.0.3) + eslint: 10.11.0(jiti@2.7.0)(supports-color@7.2.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.67.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0)) + '@typescript-eslint/scope-manager': 8.67.0 + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@6.0.3) + eslint: 10.11.0(jiti@2.7.0)(supports-color@7.2.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.71.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0)) + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + eslint: 10.11.0(jiti@2.7.0)(supports-color@7.2.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + eslint-visitor-keys: 5.0.1 + + '@typescript-eslint/visitor-keys@8.67.0': + dependencies: + '@typescript-eslint/types': 8.67.0 + eslint-visitor-keys: 5.0.1 + + '@typescript-eslint/visitor-keys@8.71.0': + dependencies: + '@typescript-eslint/types': 8.71.0 + eslint-visitor-keys: 5.0.1 + + '@vitest/coverage-v8@4.1.11(vitest@4.1.11)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.11 + ast-v8-to-istanbul: 1.0.7 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.5 + obug: 2.2.1 + std-env: 4.2.0 + tinyrainbow: 3.1.1 + vitest: 4.1.11(@types/node@26.6.3)(@vitest/coverage-v8@4.1.11)(vite@8.3.1(@types/node@26.6.3)(jiti@2.7.0)(yaml@2.9.1)) + + '@vitest/expect@4.1.11': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 + chai: 6.2.2 + tinyrainbow: 3.1.1 + + '@vitest/mocker@4.1.11(vite@8.3.1(@types/node@26.6.3)(jiti@2.7.0)(yaml@2.9.1))': + dependencies: + '@vitest/spy': 4.1.11 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.3.1(@types/node@26.6.3)(jiti@2.7.0)(yaml@2.9.1) + + '@vitest/pretty-format@4.1.11': + dependencies: + tinyrainbow: 3.1.1 + + '@vitest/runner@4.1.11': + dependencies: + '@vitest/utils': 4.1.11 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.11': + dependencies: + '@vitest/pretty-format': 4.1.11 + '@vitest/utils': 4.1.11 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.11': {} + + '@vitest/utils@4.1.11': + dependencies: + '@vitest/pretty-format': 4.1.11 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.1 + + '@yuku-codegen/binding-android-arm64@0.10.2': + optional: true + + '@yuku-codegen/binding-darwin-arm64@0.10.2': + optional: true + + '@yuku-codegen/binding-darwin-x64@0.10.2': + optional: true + + '@yuku-codegen/binding-freebsd-x64@0.10.2': + optional: true + + '@yuku-codegen/binding-linux-arm-gnu@0.10.2': + optional: true + + '@yuku-codegen/binding-linux-arm-musl@0.10.2': + optional: true + + '@yuku-codegen/binding-linux-arm64-gnu@0.10.2': + optional: true + + '@yuku-codegen/binding-linux-arm64-musl@0.10.2': + optional: true + + '@yuku-codegen/binding-linux-x64-gnu@0.10.2': + optional: true + + '@yuku-codegen/binding-linux-x64-musl@0.10.2': + optional: true + + '@yuku-codegen/binding-win32-arm64@0.10.2': + optional: true + + '@yuku-codegen/binding-win32-x64@0.10.2': + optional: true + + '@yuku-parser/binding-android-arm64@0.10.2': + optional: true + + '@yuku-parser/binding-darwin-arm64@0.10.2': + optional: true + + '@yuku-parser/binding-darwin-x64@0.10.2': + optional: true + + '@yuku-parser/binding-freebsd-x64@0.10.2': + optional: true + + '@yuku-parser/binding-linux-arm-gnu@0.10.2': + optional: true + + '@yuku-parser/binding-linux-arm-musl@0.10.2': + optional: true + + '@yuku-parser/binding-linux-arm64-gnu@0.10.2': + optional: true + + '@yuku-parser/binding-linux-arm64-musl@0.10.2': + optional: true + + '@yuku-parser/binding-linux-x64-gnu@0.10.2': + optional: true + + '@yuku-parser/binding-linux-x64-musl@0.10.2': + optional: true + + '@yuku-parser/binding-win32-arm64@0.10.2': + optional: true + + '@yuku-parser/binding-win32-x64@0.10.2': + optional: true + + '@yuku-toolchain/types@0.10.2': {} + + acorn-jsx@5.3.2(acorn@8.18.0): + dependencies: + acorn: 8.18.0 + + acorn@8.18.0: {} + + agent-base@9.0.0: {} + + aggregate-error@5.0.0: + dependencies: + clean-stack: 5.3.0 + indent-string: 5.0.0 + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.8 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.8 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + angular-html-parser@10.11.0: {} + + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.4.0: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + any-promise@1.3.0: {} + + are-docs-informative@0.1.1: {} + + argparse@2.0.1: {} + + argue-cli@3.2.0: {} + + argv-formatter@1.0.0: {} + + array-ify@1.0.0: {} + + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@1.0.7: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.11.26: {} + + before-after-hook@4.0.0: {} + + bottleneck@2.19.5: {} + + brace-expansion@5.0.12: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.29.2: + dependencies: + baseline-browser-mapping: 2.11.26 + caniuse-lite: 1.0.30001813 + electron-to-chromium: 1.5.439 + node-releases: 2.0.57 + update-browserslist-db: 1.3.3(browserslist@4.29.2) + + cac@7.0.0: {} + + cacheable@2.5.0: + dependencies: + '@cacheable/memory': 2.2.0 + '@cacheable/utils': 2.5.0 + hookified: 1.15.1 + keyv: 5.6.0 + qified: 0.10.1 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001813: {} + + chai@6.2.2: {} + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + char-regex@1.0.2: {} + + chardet@2.2.0: {} + + cjs-module-lexer@1.4.3: {} + + clean-stack@5.3.0: + dependencies: + escape-string-regexp: 5.0.0 + + cli-highlight@2.1.11: + dependencies: + chalk: 4.1.2 + highlight.js: 10.7.3 + mz: 2.7.0 + parse5: 5.1.1 + parse5-htmlparser2-tree-adapter: 6.0.1 + yargs: 16.2.2 + + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + + cli-width@4.1.0: {} + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + commander@10.0.1: {} + + commander@14.0.3: {} + + comment-parser@1.4.8: {} + + compare-func@2.0.0: + dependencies: + array-ify: 1.0.0 + dot-prop: 5.3.0 + + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + + content-type@3.1.1: {} + + conventional-changelog-angular@8.3.1: + dependencies: + compare-func: 2.0.0 + + conventional-changelog-angular@9.4.0: + dependencies: + '@conventional-changelog/template': 1.4.0 + + conventional-changelog-conventionalcommits@10.4.0: + dependencies: + '@conventional-changelog/template': 1.4.0 + + conventional-changelog-writer@8.4.0: + dependencies: + '@simple-libs/stream-utils': 1.2.0 + conventional-commits-filter: 5.0.0 + handlebars: 4.7.9 + meow: 13.2.0 + semver: 7.8.5 + + conventional-commits-filter@5.0.0: {} + + conventional-commits-parser@6.4.0: + dependencies: + '@simple-libs/stream-utils': 1.2.0 + meow: 13.2.0 + + conventional-commits-parser@7.1.2: + dependencies: + '@simple-libs/stream-utils': 2.0.0 + argue-cli: 3.2.0 + + convert-hrtime@5.0.0: {} + + convert-source-map@2.0.0: {} + + core-util-is@1.0.3: {} + + cosmiconfig-extends@1.0.0(cosmiconfig@10.0.1): + dependencies: + '@standard-schema/spec': 1.1.0 + cosmiconfig: 10.0.1 + jiti: 2.7.0 + + cosmiconfig-typescript-loader@6.3.0(@types/node@26.6.3)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3): + dependencies: + '@types/node': 26.6.3 + cosmiconfig: 9.0.2(typescript@6.0.3) + jiti: 2.6.1 + typescript: 6.0.3 + + cosmiconfig@10.0.1: + dependencies: + env-paths: 2.2.1 + js-yaml: 5.4.2 + + cosmiconfig@9.0.2(typescript@6.0.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.3.2 + parse-json: 5.2.0 + optionalDependencies: + typescript: 6.0.3 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crypto-random-string@4.0.0: + dependencies: + type-fest: 1.4.0 + + debug@4.4.3(supports-color@7.2.0): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 7.2.0 + + deep-extend@0.6.0: {} + + deep-is@0.1.4: {} + + defu@6.1.7: {} + + des.js@1.1.0: + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + + detect-libc@2.1.2: {} + + diff-match-patch@1.0.5: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dot-prop@5.3.0: + dependencies: + is-obj: 2.0.0 + + dts-resolver@3.0.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + duplexer2@0.1.4: + dependencies: + readable-stream: 2.3.8 + + electron-to-chromium@1.5.439: {} + + emoji-regex@10.6.0: {} + + emoji-regex@8.0.0: {} + + emojilib@2.4.0: {} + + empathic@2.1.0: {} + + env-ci@11.2.0: + dependencies: + execa: 8.0.1 + java-properties: 1.0.2 + + env-paths@2.2.1: {} + + environment@1.1.0: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@2.3.2: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-toolkit@1.52.0: {} + + escalade@3.2.0: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + escape-string-regexp@5.0.0: {} + + eslint-plugin-jsdoc@64.3.6(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + dependencies: + '@es-joy/jsdoccomment': 0.97.0 + '@es-joy/resolve.exports': 1.2.0 + '@typescript-eslint/utils': 8.71.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + are-docs-informative: 0.1.1 + comment-parser: 1.4.8 + debug: 4.4.3(supports-color@7.2.0) + escape-string-regexp: 5.0.0 + eslint: 10.11.0(jiti@2.7.0)(supports-color@7.2.0) + espree: 11.2.0 + esquery: 1.7.0 + html-entities: 2.6.0 + object-deep-merge: 2.0.1 + parse-imports-exports: 0.2.4 + semver: 7.8.5 + spdx-expression-parse: 5.0.0 + to-valid-identifier: 1.0.0 + transitivePeerDependencies: + - supports-color + - typescript + + eslint-plugin-json-canonical@2.1.0(@eslint/json@2.1.0)(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0)): + dependencies: + '@eslint/json': 2.1.0 + eslint: 10.11.0(jiti@2.7.0)(supports-color@7.2.0) + + eslint-plugin-tsdoc@0.5.3(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + dependencies: + '@microsoft/tsdoc': 0.17.0 + '@microsoft/tsdoc-config': 0.18.2 + '@typescript-eslint/utils': 8.56.1(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + optionalDependencies: + eslint: 10.11.0(jiti@2.7.0)(supports-color@7.2.0) + transitivePeerDependencies: + - supports-color + - typescript + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.9 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0): + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.5(supports-color@7.2.0) + '@eslint/config-helpers': 0.7.0 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.3 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + cross-spawn: 7.0.6 + debug: 4.4.3(supports-color@7.2.0) + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 11.1.5 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.6 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.7.0 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) + eslint-visitor-keys: 4.2.1 + + espree@11.2.0: + dependencies: + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + esutils@2.0.3: {} + + execa@10.0.1: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + figures: 6.1.0 + get-stream: 9.0.1 + 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.3.1 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + which-command: 0.1.0 + yoctocolors: 2.2.0 + + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + + execa@9.6.1: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + 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.3.1 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.2.0 + + expect-type@1.4.0: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + + fast-uri@3.1.8: {} + + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + fflate@0.8.3: {} + + figures@2.0.0: + dependencies: + escape-string-regexp: 1.0.5 + + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + + file-entry-cache@11.1.5: + dependencies: + flat-cache: 6.1.23 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up-simple@1.0.1: {} + + find-up@2.1.0: + dependencies: + locate-path: 2.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + find-versions@6.0.0: + dependencies: + semver-regex: 4.0.5 + super-regex: 1.1.0 + + flat-cache@6.1.23: + dependencies: + cacheable: 2.5.0 + flatted: 3.4.4 + hookified: 1.15.1 + + flatted@3.4.4: {} + + flru@1.0.2: {} + + fs-extra@11.4.1: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function-timeout@1.0.2: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.7.0: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + get-stream@6.0.1: {} + + get-stream@8.0.1: {} + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + + get-tsconfig@5.0.0-beta.6: + dependencies: + resolve-pkg-maps: 1.0.0 + + git-log-parser@1.2.1: + dependencies: + argv-formatter: 1.0.0 + spawn-error-forwarder: 1.0.0 + split2: 1.0.0 + stream-combiner2: 1.1.1 + through2: 2.0.5 + traverse: 0.6.8 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + global-directory@5.0.0: + dependencies: + ini: 6.0.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.10: {} + + graceful-fs@4.2.11: {} + + handlebars@4.7.9: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + hashery@1.5.1: + dependencies: + hookified: 1.15.1 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + highlight.js@10.7.3: {} + + hook-std@4.0.0: {} + + hookable@6.1.2: {} + + hookified@1.15.1: {} + + hookified@2.2.0: {} + + hosted-git-info@7.0.2: + dependencies: + lru-cache: 10.4.3 + + hosted-git-info@9.0.3: + dependencies: + lru-cache: 11.5.3 + + html-entities@2.6.0: {} + + html-escaper@2.0.2: {} + + http-proxy-agent@9.1.0(supports-color@7.2.0): + dependencies: + agent-base: 9.0.0 + debug: 4.4.3(supports-color@7.2.0) + proxy-agent-negotiate: 1.1.0 + transitivePeerDependencies: + - kerberos + - supports-color + + https-proxy-agent@9.1.0(supports-color@7.2.0): + dependencies: + agent-base: 9.0.0 + debug: 4.4.3(supports-color@7.2.0) + proxy-agent-negotiate: 1.1.0 + transitivePeerDependencies: + - kerberos + - supports-color + + human-signals@5.0.0: {} + + human-signals@8.0.1: {} + + husky@9.1.7: {} + + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + ignore@7.0.10: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-from-esm@2.0.0(supports-color@7.2.0): + dependencies: + debug: 4.4.3(supports-color@7.2.0) + import-meta-resolve: 4.2.0 + transitivePeerDependencies: + - supports-color + + import-meta-resolve@4.2.0: {} + + import-without-cache@0.4.1: {} + + imurmurhash@0.1.4: {} + + indent-string@5.0.0: {} + + index-to-position@1.2.0: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + ini@6.0.0: {} + + is-arrayish@0.2.1: {} + + is-core-module@2.17.0: + dependencies: + hasown: 2.0.4 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-obj@2.0.0: {} + + is-plain-obj@4.1.0: {} + + is-stream@3.0.0: {} + + is-stream@4.0.1: {} + + is-unicode-supported@2.1.0: {} + + isarray@1.0.0: {} + + isexe@2.0.0: {} + + issue-parser@7.0.2: + dependencies: + lodash.capitalize: 4.2.1 + lodash.escaperegexp: 4.1.2 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.uniqby: 4.7.0 + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + java-properties@1.0.2: {} + + jiti@2.6.1: {} + + jiti@2.7.0: {} + + jju@1.4.0: {} + + js-md4@0.3.2: {} + + js-tokens@10.0.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.3.2: + dependencies: + argparse: 2.0.1 + + js-yaml@5.4.2: + dependencies: + argparse: 2.0.1 + + jsdoc-type-pratt-parser@9.2.2: + dependencies: + '@types/estree': 1.0.9 + '@types/node': 26.6.3 + + jsesc@3.1.0: {} + + json-parse-better-errors@1.0.2: {} + + json-parse-even-better-errors@2.3.1: {} + + json-rpc-2.0@1.8.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json-with-bigint@3.5.12: {} + + json5@2.2.3: {} + + jsonfile@6.2.1: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + keyv@5.6.0: + dependencies: + '@keyv/serialize': 1.1.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + lines-and-columns@1.2.4: {} + + lint-staged@17.6.0: + dependencies: + picomatch: 4.0.7 + string-argv: 0.3.2 + tinyexec: 1.3.1 + optionalDependencies: + yaml: 2.9.1 + + load-json-file@4.0.0: + dependencies: + graceful-fs: 4.2.11 + parse-json: 4.0.0 + pify: 3.0.0 + strip-bom: 3.0.0 + + locate-path@2.0.0: + dependencies: + p-locate: 2.0.0 + path-exists: 3.0.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash-es@4.18.1: {} + + lodash.capitalize@4.2.1: {} + + lodash.escaperegexp@4.1.2: {} + + lodash.groupby@4.6.0: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.uniqby@4.7.0: {} + + lru-cache@10.4.3: {} + + lru-cache@11.5.3: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + + magicast@0.5.5: + dependencies: + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 + source-map-js: 1.2.1 + + make-asynchronous@1.1.0: + dependencies: + p-event: 6.0.1 + type-fest: 4.41.0 + web-worker: 1.5.0 + + make-dir@4.0.0: + dependencies: + semver: 7.8.5 + + marked-terminal@7.3.0(marked@15.0.12): + dependencies: + ansi-escapes: 7.3.0 + ansi-regex: 6.4.0 + chalk: 5.6.2 + cli-highlight: 2.1.11 + cli-table3: 0.6.5 + marked: 15.0.12 + node-emoji: 2.2.0 + supports-hyperlinks: 3.2.0 + + marked-terminal@7.3.0(marked@9.1.6): + dependencies: + ansi-escapes: 7.3.0 + ansi-regex: 6.4.0 + chalk: 5.6.2 + cli-highlight: 2.1.11 + cli-table3: 0.6.5 + marked: 9.1.6 + node-emoji: 2.2.0 + supports-hyperlinks: 3.2.0 + + marked@15.0.12: {} + + marked@9.1.6: {} + + math-intrinsics@1.1.0: {} + + meow@13.2.0: {} + + merge-stream@2.0.0: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime@4.1.0: {} + + mimic-fn@4.0.0: {} + + minimalistic-assert@1.0.1: {} + + minimatch@10.2.6: + dependencies: + brace-expansion: 5.0.12 + + minimist@1.2.8: {} + + mri@1.2.0: {} + + ms@2.1.3: {} + + mutation-server-protocol@0.4.1: + dependencies: + zod: 4.6.5 + + mutation-testing-elements@3.8.4: {} + + mutation-testing-metrics@3.8.4: + dependencies: + mutation-testing-report-schema: 3.8.4 + + mutation-testing-report-schema@3.8.4: {} + + mute-stream@3.0.0: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.19: {} + + natural-compare@1.4.0: {} + + neo-async@2.6.2: {} + + nerf-dart@1.0.0: {} + + node-emoji@2.2.0: + dependencies: + '@sindresorhus/is': 4.6.0 + char-regex: 1.0.2 + emojilib: 2.4.0 + skin-tone: 2.0.0 + + node-releases@2.0.57: {} + + normalize-package-data@6.0.2: + dependencies: + hosted-git-info: 7.0.2 + semver: 7.8.5 + validate-npm-package-license: 3.0.4 + + normalize-package-data@8.0.0: + dependencies: + hosted-git-info: 9.0.3 + semver: 7.8.5 + validate-npm-package-license: 3.0.4 + + normalize-url@9.0.1: {} + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + + npm@11.20.0: {} + + object-assign@4.1.1: {} + + object-deep-merge@2.0.1: {} + + object-inspect@1.13.4: {} + + obug@2.2.1: {} + + obug@3.0.0: {} + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-each-series@3.0.0: {} + + p-event@6.0.1: + dependencies: + p-timeout: 6.1.4 + + p-filter@4.1.0: + dependencies: + p-map: 7.0.8 + + p-limit@1.3.0: + dependencies: + p-try: 1.0.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@2.0.0: + dependencies: + p-limit: 1.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-map@7.0.8: {} + + p-reduce@3.0.0: {} + + p-timeout@6.1.4: {} + + p-try@1.0.0: {} + + package-manager-detector@1.8.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-imports-exports@0.2.4: + dependencies: + parse-statements: 1.0.11 + + parse-json@4.0.0: + dependencies: + error-ex: 1.3.4 + json-parse-better-errors: 1.0.2 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.7 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-json@8.3.0: + dependencies: + '@babel/code-frame': 7.29.7 + index-to-position: 1.2.0 + type-fest: 4.41.0 + + parse-ms@4.0.0: {} + + parse-statements@1.0.11: {} + + parse5-htmlparser2-tree-adapter@6.0.1: + dependencies: + parse5: 6.0.1 + + parse5@5.1.1: {} + + parse5@6.0.1: {} + + path-exists@3.0.0: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-parse@1.0.7: {} + + path-type@4.0.0: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.7: {} + + pify@3.0.0: {} + + pkg-conf@2.1.0: + dependencies: + find-up: 2.1.0 + load-json-file: 4.0.0 + + postcss@8.5.28: + dependencies: + nanoid: 3.3.19 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + pretty-ms@9.3.1: + dependencies: + parse-ms: 4.0.0 + + process-nextick-args@2.0.1: {} + + progress@2.0.3: {} + + proto-list@1.2.4: {} + + proxy-agent-negotiate@1.1.0: {} + + publint@0.3.24: + dependencies: + '@publint/pack': 0.1.7 + package-manager-detector: 1.8.0 + picocolors: 1.1.1 + sade: 1.8.1 + + punycode@2.3.1: {} + + qified@0.10.1: + dependencies: + hookified: 2.2.0 + + qs@6.15.1: + dependencies: + side-channel: 1.1.1 + + quansync@1.0.0: {} + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + read-package-up@11.0.0: + dependencies: + find-up-simple: 1.0.1 + read-pkg: 9.0.1 + type-fest: 4.41.0 + + read-package-up@12.0.0: + dependencies: + find-up-simple: 1.0.1 + read-pkg: 10.1.0 + type-fest: 5.10.0 + + read-pkg@10.1.0: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 8.0.0 + parse-json: 8.3.0 + type-fest: 5.10.0 + unicorn-magic: 0.4.1 + + read-pkg@9.0.1: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 6.0.2 + parse-json: 8.3.0 + type-fest: 4.41.0 + unicorn-magic: 0.1.0 + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + registry-auth-token@5.1.1: + dependencies: + '@pnpm/npm-conf': 3.0.3 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + reserved-identifiers@1.2.0: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.17.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + rolldown-plugin-dts@0.28.6(rolldown@1.2.11)(typescript@6.0.3): + dependencies: + dts-resolver: 3.0.0 + get-tsconfig: 5.0.0-beta.6 + obug: 3.0.0 + rolldown: 1.2.11 + yuku-ast: 0.10.2 + yuku-codegen: 0.10.2 + yuku-parser: 0.10.2 + optionalDependencies: + typescript: 6.0.3 + transitivePeerDependencies: + - oxc-resolver + + rolldown@1.2.11: + dependencies: + '@oxc-project/types': 0.151.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm-eabi': 1.2.11 + '@rolldown/binding-android-arm64': 1.2.11 + '@rolldown/binding-darwin-arm64': 1.2.11 + '@rolldown/binding-darwin-x64': 1.2.11 + '@rolldown/binding-freebsd-x64': 1.2.11 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.11 + '@rolldown/binding-linux-arm64-gnu': 1.2.11 + '@rolldown/binding-linux-arm64-musl': 1.2.11 + '@rolldown/binding-linux-ppc64-gnu': 1.2.11 + '@rolldown/binding-linux-s390x-gnu': 1.2.11 + '@rolldown/binding-linux-x64-gnu': 1.2.11 + '@rolldown/binding-linux-x64-musl': 1.2.11 + '@rolldown/binding-openharmony-arm64': 1.2.11 + '@rolldown/binding-win32-arm64-msvc': 1.2.11 + '@rolldown/binding-win32-x64-msvc': 1.2.11 + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + sade@1.8.1: + dependencies: + mri: 1.2.0 + + safe-buffer@5.1.2: {} + + safer-buffer@2.1.2: {} + + semantic-release@25.0.9(supports-color@7.2.0)(typescript@6.0.3): + dependencies: + '@semantic-release/commit-analyzer': 13.0.1(semantic-release@25.0.9(supports-color@7.2.0)(typescript@6.0.3))(supports-color@7.2.0) + '@semantic-release/error': 4.0.0 + '@semantic-release/github': 12.0.10(semantic-release@25.0.9(supports-color@7.2.0)(typescript@6.0.3))(supports-color@7.2.0) + '@semantic-release/npm': 13.2.0(semantic-release@25.0.9(supports-color@7.2.0)(typescript@6.0.3)) + '@semantic-release/release-notes-generator': 14.1.1(semantic-release@25.0.9(supports-color@7.2.0)(typescript@6.0.3))(supports-color@7.2.0) + aggregate-error: 5.0.0 + cosmiconfig: 9.0.2(typescript@6.0.3) + debug: 4.4.3(supports-color@7.2.0) + env-ci: 11.2.0 + execa: 9.6.1 + figures: 6.1.0 + find-versions: 6.0.0 + get-stream: 6.0.1 + git-log-parser: 1.2.1 + hook-std: 4.0.0 + hosted-git-info: 9.0.3 + import-from-esm: 2.0.0(supports-color@7.2.0) + lodash-es: 4.18.1 + marked: 15.0.12 + marked-terminal: 7.3.0(marked@15.0.12) + micromatch: 4.0.8 + p-each-series: 3.0.0 + p-reduce: 3.0.0 + read-package-up: 12.0.0 + resolve-from: 5.0.0 + semver: 7.8.5 + signale: 1.4.0 + yargs: 18.2.0 + transitivePeerDependencies: + - kerberos + - supports-color + - typescript + + semver-regex@4.0.5: {} + + semver@7.8.5: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + signale@1.4.0: + dependencies: + chalk: 2.4.2 + figures: 2.0.0 + pkg-conf: 2.1.0 + + skin-tone@2.0.0: + dependencies: + unicode-emoji-modifier-base: 1.0.0 + + source-map-js@1.2.1: {} + + source-map@0.6.1: {} + + source-map@0.7.6: {} + + spawn-error-forwarder@1.0.0: {} + + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.24 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.24 + + spdx-expression-parse@5.0.0: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.24 + + spdx-license-ids@3.0.24: {} + + split2@1.0.0: + dependencies: + through2: 2.0.5 + + stackback@0.0.2: {} + + std-env@4.2.0: {} + + stream-combiner2@1.1.1: + dependencies: + duplexer2: 0.1.4 + readable-stream: 2.3.8 + + string-argv@0.3.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.7.0 + strip-ansi: 7.2.0 + + string-width@8.3.0: + dependencies: + get-east-asian-width: 1.7.0 + strip-ansi: 7.2.0 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.4.0 + + strip-bom@3.0.0: {} + + strip-final-newline@3.0.0: {} + + strip-final-newline@4.0.0: {} + + strip-json-comments@2.0.1: {} + + super-regex@1.1.0: + dependencies: + function-timeout: 1.0.2 + make-asynchronous: 1.1.0 + time-span: 5.1.0 + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-hyperlinks@3.2.0: + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + tagged-tag@1.0.0: {} + + temp-dir@3.0.0: {} + + tempy@3.2.0: + dependencies: + is-stream: 3.0.0 + temp-dir: 3.0.0 + type-fest: 2.19.0 + unique-string: 3.0.0 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + through2@2.0.5: + dependencies: + readable-stream: 2.3.8 + xtend: 4.0.2 + + time-span@5.1.0: + dependencies: + convert-hrtime: 5.0.0 + + tinybench@2.9.0: {} + + tinyexec@1.3.1: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + tinyrainbow@3.1.1: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + to-valid-identifier@1.0.0: + dependencies: + '@sindresorhus/base62': 1.0.0 + reserved-identifiers: 1.2.0 + + traverse@0.6.8: {} + + tree-kill@1.2.2: {} + + ts-api-utils@2.5.0(typescript@6.0.3): + dependencies: + typescript: 6.0.3 + + tsdown@0.23.0(@arethetypeswrong/core@0.18.5)(publint@0.3.24)(typescript@6.0.3): + dependencies: + cac: 7.0.0 + defu: 6.1.7 + empathic: 2.1.0 + hookable: 6.1.2 + import-without-cache: 0.4.1 + obug: 2.2.1 + picomatch: 4.0.7 + rolldown: 1.2.11 + rolldown-plugin-dts: 0.28.6(rolldown@1.2.11)(typescript@6.0.3) + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + tree-kill: 1.2.2 + unconfig-core: 7.5.0 + verkit: 0.4.1 + optionalDependencies: + '@arethetypeswrong/core': 0.18.5 + publint: 0.3.24 + typescript: 6.0.3 + transitivePeerDependencies: + - '@typescript/native-preview' + - '@volar/typescript' + - '@vue/language-core' + - oxc-resolver + - vue-tsc + + tslib@2.8.1: {} + + tunnel@0.0.6: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@1.4.0: {} + + type-fest@2.19.0: {} + + type-fest@4.41.0: {} + + type-fest@5.10.0: + dependencies: + tagged-tag: 1.0.0 + + typed-inject@5.0.0: {} + + typed-rest-client@2.3.1: + dependencies: + des.js: 1.1.0 + js-md4: 0.3.2 + qs: 6.15.1 + tunnel: 0.0.6 + underscore: 1.13.8 + + typescript-eslint@8.71.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.71.0(@typescript-eslint/parser@8.71.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.71.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + eslint: 10.11.0(jiti@2.7.0)(supports-color@7.2.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + typescript@5.6.1-rc: {} + + typescript@6.0.3: {} + + uglify-js@3.19.3: + optional: true + + unconfig-core@7.5.0: + dependencies: + '@quansync/fs': 1.1.0 + quansync: 1.0.0 + + underscore@1.13.8: {} + + undici-types@8.9.0: {} + + undici@6.29.0: {} + + undici@7.30.0: {} + + unicode-emoji-modifier-base@1.0.0: {} + + unicorn-magic@0.1.0: {} + + unicorn-magic@0.3.0: {} + + unicorn-magic@0.4.1: {} + + unique-string@3.0.0: + dependencies: + crypto-random-string: 4.0.0 + + universal-user-agent@7.0.3: {} + + universalify@2.0.1: {} + + update-browserslist-db@1.3.3(browserslist@4.29.2): + dependencies: + browserslist: 4.29.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + url-join@5.0.0: {} + + util-deprecate@1.0.2: {} + + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + validate-npm-package-name@5.0.1: {} + + verkit@0.3.2: {} + + verkit@0.4.1: {} + + vite@8.3.1(@types/node@26.6.3)(jiti@2.7.0)(yaml@2.9.1): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.28 + rolldown: 1.2.11 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 26.6.3 + fsevents: 2.3.3 + jiti: 2.7.0 + yaml: 2.9.1 + + vitest@4.1.11(@types/node@26.6.3)(@vitest/coverage-v8@4.1.11)(vite@8.3.1(@types/node@26.6.3)(jiti@2.7.0)(yaml@2.9.1)): + dependencies: + '@vitest/expect': 4.1.11 + '@vitest/mocker': 4.1.11(vite@8.3.1(@types/node@26.6.3)(jiti@2.7.0)(yaml@2.9.1)) + '@vitest/pretty-format': 4.1.11 + '@vitest/runner': 4.1.11 + '@vitest/snapshot': 4.1.11 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.2.1 + pathe: 2.0.3 + picomatch: 4.0.7 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.1 + vite: 8.3.1(@types/node@26.6.3)(jiti@2.7.0)(yaml@2.9.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 26.6.3 + '@vitest/coverage-v8': 4.1.11(vitest@4.1.11) + transitivePeerDependencies: + - msw + + weapon-regex@2.0.5: {} + + web-worker@1.5.0: {} + + which-command@0.1.0: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + wordwrap@1.0.0: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + xtend@4.0.2: {} + + y18n@5.0.8: {} + + yaml@2.9.1: + optional: true + + yargs-parser@20.2.9: {} + + yargs-parser@22.0.0: {} + + yargs@16.2.2: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + + yargs@18.2.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 8.3.0 + y18n: 5.0.8 + yargs-parser: 22.0.0 + + yocto-queue@0.1.0: {} + + yoctocolors@2.2.0: {} + + yuku-ast@0.10.2: + dependencies: + '@yuku-toolchain/types': 0.10.2 + + yuku-codegen@0.10.2: + dependencies: + '@yuku-toolchain/types': 0.10.2 + optionalDependencies: + '@yuku-codegen/binding-android-arm64': 0.10.2 + '@yuku-codegen/binding-darwin-arm64': 0.10.2 + '@yuku-codegen/binding-darwin-x64': 0.10.2 + '@yuku-codegen/binding-freebsd-x64': 0.10.2 + '@yuku-codegen/binding-linux-arm-gnu': 0.10.2 + '@yuku-codegen/binding-linux-arm-musl': 0.10.2 + '@yuku-codegen/binding-linux-arm64-gnu': 0.10.2 + '@yuku-codegen/binding-linux-arm64-musl': 0.10.2 + '@yuku-codegen/binding-linux-x64-gnu': 0.10.2 + '@yuku-codegen/binding-linux-x64-musl': 0.10.2 + '@yuku-codegen/binding-win32-arm64': 0.10.2 + '@yuku-codegen/binding-win32-x64': 0.10.2 + + yuku-parser@0.10.2: + dependencies: + '@yuku-toolchain/types': 0.10.2 + yuku-ast: 0.10.2 + optionalDependencies: + '@yuku-parser/binding-android-arm64': 0.10.2 + '@yuku-parser/binding-darwin-arm64': 0.10.2 + '@yuku-parser/binding-darwin-x64': 0.10.2 + '@yuku-parser/binding-freebsd-x64': 0.10.2 + '@yuku-parser/binding-linux-arm-gnu': 0.10.2 + '@yuku-parser/binding-linux-arm-musl': 0.10.2 + '@yuku-parser/binding-linux-arm64-gnu': 0.10.2 + '@yuku-parser/binding-linux-arm64-musl': 0.10.2 + '@yuku-parser/binding-linux-x64-gnu': 0.10.2 + '@yuku-parser/binding-linux-x64-musl': 0.10.2 + '@yuku-parser/binding-win32-arm64': 0.10.2 + '@yuku-parser/binding-win32-x64': 0.10.2 + + zod@4.6.5: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..efab338 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +# This is a single-package repo, not a real pnpm workspace: packages is deliberately empty. The file exists to carry pnpm settings. +packages: [] +# Every dependency add or update writes an exact version, never a range: a floating range lets a routine `pnpm install` silently resolve to a version nobody has reviewed. +minimumReleaseAgeExclude: + - cosmiconfig-extends@1.0.0 +saveExact: true diff --git a/release.config.ts b/release.config.ts new file mode 100644 index 0000000..004a961 --- /dev/null +++ b/release.config.ts @@ -0,0 +1,60 @@ +import type { Options } from 'semantic-release'; + +type ReleaseLevel = 'major' | 'minor' | 'patch' | false; + +interface CommitType { + readonly type: string; + readonly release: ReleaseLevel; +} + +/** + * Single source of truth for the conventional-commit types this project uses. commitlint's allowed type-enum (commitlint.config.ts imports this) and commit-analyzer's releaseRules below both derive from it, so a type cannot trigger a release without also being accepted by commit-msg validation, or the reverse. + */ +export const commitTypes: readonly CommitType[] = [ + { type: 'feat', release: 'minor' }, + { type: 'fix', release: 'patch' }, + { type: 'perf', release: 'patch' }, + { type: 'revert', release: 'patch' }, + { type: 'refactor', release: 'patch' }, + { type: 'docs', release: 'patch' }, + { type: 'style', release: 'patch' }, + { type: 'test', release: 'patch' }, + { type: 'build', release: 'patch' }, + { type: 'ci', release: 'patch' }, + { type: 'chore', release: 'patch' }, +]; + +/** + * Runs on `main`. Analyses commits since the last tag, bumps the version, publishes to npmjs.org through trusted OIDC publishing (no stored token, see .github/workflows/ci.yml), creates a versioned tag and GitHub Release with generated notes, and commits CHANGELOG.md and package.json back to main. + */ +const config: Options = { + branches: ['main'], + plugins: [ + [ + '@semantic-release/commit-analyzer', + { + preset: 'conventionalcommits', + releaseRules: [{ breaking: true, release: 'major' }, ...commitTypes.map((t) => ({ type: t.type, release: t.release }))], + }, + ], + [ + '@semantic-release/release-notes-generator', + { + // Deliberately angular, not conventionalcommits: conventional-changelog-writer's bundled commit partial does not match the conventionalcommits preset's function-based partial signature, producing a changelog with a version header and nothing under it. + preset: 'angular', + }, + ], + '@semantic-release/changelog', + ['@semantic-release/npm', { npmPublish: true }], + '@semantic-release/github', + [ + '@semantic-release/git', + { + assets: ['CHANGELOG.md', 'package.json'], + message: 'chore(release): ${nextRelease.version} [skip ci]', + }, + ], + ], +}; + +export default config; diff --git a/stryker.config.ts b/stryker.config.ts new file mode 100644 index 0000000..437020f --- /dev/null +++ b/stryker.config.ts @@ -0,0 +1,18 @@ +import type { StrykerOptions } from '@stryker-mutator/api/core'; + +const config: Partial = { + packageManager: 'pnpm', + testRunner: 'vitest', + // Explicit, not Stryker's auto-discovery, which resolves `@stryker-mutator/*` plugins relative to where `@stryker-mutator/core` physically lives; under pnpm's isolated node_modules that is a different resolution root than this config's own directory. + plugins: ['@stryker-mutator/typescript-checker', '@stryker-mutator/vitest-runner'], + checkers: ['typescript'], + coverageAnalysis: 'perTest', + mutate: ['src/**/*.ts', '!src/**/*.test.ts'], + incremental: true, + incrementalFile: 'reports/stryker-incremental.json', + reporters: ['html', 'clear-text', 'progress'], + htmlReporter: { fileName: 'reports/mutation/mutation.html' }, + thresholds: { high: 100, low: 100, break: 100 }, +}; + +export default config; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..614d4bd --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "declaration": true, + "esModuleInterop": true, + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "isolatedDeclarations": true, + "isolatedModules": true, + "lib": ["ES2024"], + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noPropertyAccessFromIndexSignature": true, + "noUncheckedIndexedAccess": true, + "noUncheckedSideEffectImports": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "strictBuiltinIteratorReturn": true, + "target": "ES2024", + "types": ["node"], + "verbatimModuleSyntax": true + }, + "exclude": ["test/types/fixtures"], + "include": ["src", "test", "*.config.ts"] +} diff --git a/tsdown.config.ts b/tsdown.config.ts new file mode 100644 index 0000000..00eb1b9 --- /dev/null +++ b/tsdown.config.ts @@ -0,0 +1,14 @@ +import { defineConfig, type UserConfig } from 'tsdown'; + +const config: UserConfig = defineConfig({ + entry: ['src/index.ts', 'src/cli.ts'], + root: 'src', + format: ['esm', 'cjs'], + dts: true, + platform: 'node', + // Keeps the .js (ESM) and .cjs (CJS) extensions package.json's exports map names; on the node platform tsdown would otherwise emit .mjs. + fixedExtension: false, + clean: true, +}); + +export default config; diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..9f4db4f --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,17 @@ +import { defineConfig, type ViteUserConfig } from 'vitest/config'; + +const config: ViteUserConfig = defineConfig({ + test: { + // Every case that loads a TypeScript file pays jiti's first-transpile cost, and the interop cases spawn a second cosmiconfig major. + testTimeout: 30_000, + coverage: { + enabled: true, + provider: 'v8', + reporter: ['text', 'html', 'lcov'], + include: ['src/**/*.ts'], + exclude: ['src/**/*.test.ts'], + }, + }, +}); + +export default config; From 21974348ab7fb8b0569e54d0b2837c39771a1060 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 29 Sep 2026 23:12:16 +0100 Subject: [PATCH 02/23] feat: add typed section descriptors and the shared layout section --- src/index.ts | 14 + src/layout.ts | 242 ++++++++++++++++++ src/layout.unit.test.ts | 214 ++++++++++++++++ src/section.ts | 95 +++++++ src/section.unit.test.ts | 99 +++++++ src/validation.ts | 195 ++++++++++++++ src/validation.unit.test.ts | 215 ++++++++++++++++ stryker.config.ts | 3 +- test/support/project.ts | 26 ++ .../types/authoring-types.integration.test.ts | 150 +++++++++++ test/types/fixtures/cases/bad-enum.ts | 6 + test/types/fixtures/cases/bad-extends.ts | 3 + .../fixtures/cases/default-export-inferred.ts | 5 + .../fixtures/cases/empty-map-define-config.ts | 6 + .../fixtures/cases/empty-map-with-sections.ts | 3 + test/types/fixtures/cases/exact-optional.ts | 6 + .../fixtures/cases/layout-missing-groups.ts | 5 + .../fixtures/cases/layout-unknown-key.ts | 5 + test/types/fixtures/cases/missing-required.ts | 6 + test/types/fixtures/cases/misspelt-section.ts | 6 + test/types/fixtures/cases/ok-envelope-only.ts | 3 + .../cases/ok-isolated-declarations.ts | 9 + test/types/fixtures/cases/ok-layout.ts | 7 + .../fixtures/cases/ok-sections-optional.ts | 6 + test/types/fixtures/cases/ok-two-tools.ts | 9 + test/types/fixtures/cases/uninstalled-tool.ts | 5 + test/types/fixtures/cases/unknown-key.ts | 6 + test/types/fixtures/cases/unlisted-tool.ts | 11 + test/types/fixtures/cases/wrong-type.ts | 6 + test/types/fixtures/tool-a.ts | 14 + test/types/fixtures/tool-b.ts | 10 + vitest.config.ts | 3 +- 32 files changed, 1391 insertions(+), 2 deletions(-) create mode 100644 src/index.ts create mode 100644 src/layout.ts create mode 100644 src/layout.unit.test.ts create mode 100644 src/section.ts create mode 100644 src/section.unit.test.ts create mode 100644 src/validation.ts create mode 100644 src/validation.unit.test.ts create mode 100644 test/support/project.ts create mode 100644 test/types/authoring-types.integration.test.ts create mode 100644 test/types/fixtures/cases/bad-enum.ts create mode 100644 test/types/fixtures/cases/bad-extends.ts create mode 100644 test/types/fixtures/cases/default-export-inferred.ts create mode 100644 test/types/fixtures/cases/empty-map-define-config.ts create mode 100644 test/types/fixtures/cases/empty-map-with-sections.ts create mode 100644 test/types/fixtures/cases/exact-optional.ts create mode 100644 test/types/fixtures/cases/layout-missing-groups.ts create mode 100644 test/types/fixtures/cases/layout-unknown-key.ts create mode 100644 test/types/fixtures/cases/missing-required.ts create mode 100644 test/types/fixtures/cases/misspelt-section.ts create mode 100644 test/types/fixtures/cases/ok-envelope-only.ts create mode 100644 test/types/fixtures/cases/ok-isolated-declarations.ts create mode 100644 test/types/fixtures/cases/ok-layout.ts create mode 100644 test/types/fixtures/cases/ok-sections-optional.ts create mode 100644 test/types/fixtures/cases/ok-two-tools.ts create mode 100644 test/types/fixtures/cases/uninstalled-tool.ts create mode 100644 test/types/fixtures/cases/unknown-key.ts create mode 100644 test/types/fixtures/cases/unlisted-tool.ts create mode 100644 test/types/fixtures/cases/wrong-type.ts create mode 100644 test/types/fixtures/tool-a.ts create mode 100644 test/types/fixtures/tool-b.ts diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..3a08e6e --- /dev/null +++ b/src/index.ts @@ -0,0 +1,14 @@ +export { + type GroupSpec, + type LayoutConfig, + layoutSchema, + layoutSection, + type NamingOptions, + type NamingStrategy, + type RankRule, + type RankSkipOptions, + type SliceByNamePrefix, + type SliceBySegment, + type SliceSpec, +} from './layout'; +export { type Config, type ConfigOf, defineConfig, defineSection, type Envelope, type Section, type SectionMap, type SectionsOf, withSections } from './section'; diff --git a/src/layout.ts b/src/layout.ts new file mode 100644 index 0000000..289b32b --- /dev/null +++ b/src/layout.ts @@ -0,0 +1,242 @@ +import type { StandardSchemaV1 } from '@standard-schema/spec'; + +import { defineSection, type Section } from './section'; +import { + array, + type Check, + integer, + isRecord, + isString, + literal, + optional, + pair, + type Path, + type Report, + required, + standardSchema, + strictObject, +} from './validation'; + +/** + * A group whose packages take their slice from the Nth path segment after the group's own root. + */ +export interface SliceBySegment { + readonly segment: number; +} + +/** + * A group with no path structure of its own (a flat directory) whose packages take their slice from whichever other group's already-observed slice value prefixes their declared name. + */ +export interface SliceByNamePrefix { + readonly namePrefix: true; +} + +/** + * A group's own sub-partition: packages in different slices are isolated from each other even when their groups' ranks would allow the dependency. + */ +export type SliceSpec = SliceBySegment | SliceByNamePrefix; + +/** + * How a group derives a package's expected name from its path: `drop-group` omits the group's own segment, `keep-group` keeps it, and `basename` uses only the last path segment. + */ +export type NamingStrategy = 'drop-group' | 'keep-group' | 'basename'; + +/** + * One top-level directory of the workspace that groups packages. + */ +export interface GroupSpec { + readonly name: string; + /** + * The group's root relative to the workspace root; the group name when omitted. + */ + readonly path?: string; + /** + * The group's rank in a group-rank model. Omit it in a name-role model, where {@link LayoutConfig.nameRanks} and {@link LayoutConfig.defaultRank} decide. + */ + readonly rank?: number; + readonly slice?: SliceSpec; + /** + * How the group's packages are expected to be named from their paths; `drop-group` when omitted. + */ + readonly naming?: NamingStrategy; +} + +/** + * Assigns a rank to every package whose declared name matches `pattern`. + */ +export interface RankRule { + /** + * The source text of a regular expression, compiled with the `u` flag and tested against the package's declared name. Rules are checked in array order and the first match wins. + */ + readonly pattern: string; + readonly rank: number; +} + +/** + * Limits how far below itself a package may depend. + */ +export interface RankSkipOptions { + /** + * A dependency may sit at most this many ranks below its dependant: 1 allows only the next rank down, 0 only the same rank. + */ + readonly maxDistance: number; + /** + * Ranks that may be depended on from any distance, typically a dependency-light contract layer. + */ + readonly exemptRanks: readonly number[]; +} + +/** + * The scoped name convention packages are expected to follow. + */ +export interface NamingOptions { + /** + * The package name prefix, such as `@acme`, joined to the derived path segments with `/`. Names are unscoped when omitted. + */ + readonly scope?: string; + /** + * Joins a package's derived path segments; `-` when omitted. + */ + readonly separator?: string; +} + +/** + * The shape of a workspace: which directories group its packages, how those groups rank, and which dependencies between them are allowed. It describes structure only. What a tool does about a violation (allow lists, required files or scripts) is that tool's own section. + */ +export interface LayoutConfig { + /** + * The workspace root directory. When omitted a tool finds the nearest ancestor of the file it is looking at that owns a `pnpm-workspace.yaml`. + */ + readonly root?: string; + /** + * Workspace package globs in the `pnpm-workspace.yaml` dialect (`*`, `**`, `!`-prefixed excludes). When omitted a tool reads the workspace file's own `packages` list. + */ + readonly packages?: readonly string[]; + /** + * The `package.json` fields read as a package's declared dependencies; `dependencies` alone when omitted. + */ + readonly dependencyFields?: readonly string[]; + /** + * The workspace's groups. Names are unique. + */ + readonly groups: readonly GroupSpec[]; + /** + * The name-role model: patterns assigning a rank by declared package name, consulted before a package's group rank. + */ + readonly nameRanks?: readonly RankRule[]; + /** + * The rank of a package that neither `nameRanks` nor its group ranks. + */ + readonly defaultRank?: number; + readonly rankSkip?: RankSkipOptions; + /** + * Pairs of group names that may not depend on each other in either direction, on top of the rank and slice checks. Each name is a declared group, and a pair names two different groups. + */ + readonly isolatedGroups?: readonly (readonly [string, string])[]; + readonly naming?: NamingOptions; +} + +const isSliceBySegment: Check = strictObject({ segment: required(integer(0)) }); +const isSliceByNamePrefix: Check = strictObject({ namePrefix: required(literal(true)) }); + +const isSliceSpec: Check = (value, path, report): value is SliceSpec => { + if (isRecord(value) && 'segment' in value) { + return isSliceBySegment(value, path, report); + } + if (isRecord(value) && 'namePrefix' in value) { + return isSliceByNamePrefix(value, path, report); + } + report(path, 'expected { segment: } or { namePrefix: true }'); + + return false; +}; + +const isGroupSpec: Check = strictObject({ + name: required(isString), + path: optional(isString), + rank: optional(integer()), + slice: optional(isSliceSpec), + naming: optional(literal('drop-group', 'keep-group', 'basename')), +}); + +const isRankRule: Check = strictObject({ pattern: required(isString), rank: required(integer()) }); + +const isRankSkipOptions: Check = strictObject({ + maxDistance: required(integer(0)), + exemptRanks: required(array(integer())), +}); + +const isNamingOptions: Check = strictObject({ + scope: optional(isString), + separator: optional(isString), +}); + +const hasLayoutShape: Check = strictObject({ + root: optional(isString), + packages: optional(array(isString)), + dependencyFields: optional(array(isString)), + groups: required(array(isGroupSpec)), + nameRanks: optional(array(isRankRule)), + defaultRank: optional(integer()), + rankSkip: optional(isRankSkipOptions), + isolatedGroups: optional(array(pair(isString))), + naming: optional(isNamingOptions), +}); + +function isValidRegularExpression(source: string): boolean { + try { + new RegExp(source, 'u'); + + return true; + } catch { + return false; + } +} + +/** + * The rules that relate one part of a structurally valid layout to another: unique group names, compilable rank patterns, and isolated pairs that name two different declared groups. + */ +function isCoherent(layout: LayoutConfig, path: Path, report: Report): boolean { + let coherent = true; + const declared = new Set(); + layout.groups.forEach((group, index) => { + if (declared.has(group.name)) { + report([...path, 'groups', index, 'name'], `group '${group.name}' is declared more than once`); + coherent = false; + } + declared.add(group.name); + }); + layout.nameRanks?.forEach((rule, index) => { + if (!isValidRegularExpression(rule.pattern)) { + report([...path, 'nameRanks', index, 'pattern'], `'${rule.pattern}' is not a valid regular expression`); + coherent = false; + } + }); + layout.isolatedGroups?.forEach((groups, index) => { + groups.forEach((name, position) => { + if (!declared.has(name)) { + report([...path, 'isolatedGroups', index, position], `'${name}' is not a declared group`); + coherent = false; + } + }); + if (groups[0] === groups[1]) { + report([...path, 'isolatedGroups', index], `a group cannot be isolated from itself ('${groups[0]}')`); + coherent = false; + } + }); + + return coherent; +} + +const isLayoutConfig: Check = (value, path, report): value is LayoutConfig => + hasLayoutShape(value, path, report) && isCoherent(value, path, report); + +/** + * The Standard Schema for {@link LayoutConfig}. It rejects unknown keys at every level, and checks the rules that relate one part of the layout to another. It applies no defaults and returns a valid value as it is. + */ +export const layoutSchema: StandardSchemaV1 = standardSchema('@exadev/config', isLayoutConfig); + +/** + * The shared `layout` section: the workspace's shape, read by every tool that reasons about it, so the ESLint architecture rules and the conformance tool read one description. Its fields match the options of `@exadev/eslint-config`'s workspace architecture rules. + */ +export const layoutSection: Section<'layout', StandardSchemaV1> = defineSection('layout', layoutSchema); diff --git a/src/layout.unit.test.ts b/src/layout.unit.test.ts new file mode 100644 index 0000000..5a797c8 --- /dev/null +++ b/src/layout.unit.test.ts @@ -0,0 +1,214 @@ +import type { WorkspaceArchitectureOptions } from '@exadev/eslint-config'; +import { describe, expect, expectTypeOf, it } from 'vitest'; + +import { type LayoutConfig, layoutSchema, layoutSection } from './layout'; + +interface Problem { + readonly path: readonly (string | number | symbol | { readonly key: PropertyKey })[] | undefined; + readonly message: string; +} + +async function validate(value: unknown): Promise<{ readonly valid: boolean; readonly problems: readonly Problem[] }> { + const result = await layoutSchema['~standard'].validate(value); + + return result.issues === undefined + ? { valid: true, problems: [] } + : { valid: false, problems: result.issues.map(({ path, message }) => ({ path, message })) }; +} + +async function problemsOf(value: unknown): Promise { + return (await validate(value)).problems; +} + +const groupRankLayout: LayoutConfig = { + root: '.', + packages: ['core/*', 'features/*', 'targets/*', '!**/test/**'], + dependencyFields: ['dependencies', 'peerDependencies'], + groups: [ + { name: 'contracts', rank: 0 }, + { name: 'core', rank: 1, naming: 'drop-group' }, + { name: 'features', path: 'packages/features', rank: 2, naming: 'keep-group', slice: { segment: 0 } }, + { name: 'targets', rank: 3, naming: 'basename', slice: { namePrefix: true } }, + ], + defaultRank: 1, + rankSkip: { maxDistance: 1, exemptRanks: [0] }, + isolatedGroups: [['features', 'targets']], + naming: { scope: '@acme', separator: '-' }, +}; + +const nameRoleLayout: LayoutConfig = { + groups: [{ name: 'libs' }, { name: 'apps' }], + nameRanks: [ + { pattern: '-contract$', rank: 0 }, + { pattern: '^@acme/app-', rank: 3 }, + ], + defaultRank: 1, + rankSkip: { maxDistance: 0, exemptRanks: [] }, +}; + +describe('layoutSection', () => { + it('is the section named layout, validated by the layout schema', () => { + expect(layoutSection.name).toBe('layout'); + expect(layoutSection.schema).toBe(layoutSchema); + }); + + it('describes the same options as the workspace architecture rules of @exadev/eslint-config', () => { + expectTypeOf().toEqualTypeOf(); + }); +}); + +describe('layoutSchema', () => { + it.each([ + ['the smallest layout', { groups: [] }], + ['a group-rank model with slices, naming and isolation', groupRankLayout], + ['a name-role model with rank patterns', nameRoleLayout], + ['a negative rank and a zero slice segment', { groups: [{ name: 'a', rank: -1, slice: { segment: 0 } }], defaultRank: -2 }], + ['every naming strategy', { groups: [{ name: 'a', naming: 'drop-group' }, { name: 'b', naming: 'keep-group' }, { name: 'c', naming: 'basename' }] }], + ])('accepts %s', async (_name, layout) => { + expect(await validate(layout)).toEqual({ valid: true, problems: [] }); + }); + + it('returns a valid layout as it is, applying no defaults', async () => { + const result = await layoutSchema['~standard'].validate(groupRankLayout); + + expect(result).toEqual({ value: groupRankLayout }); + expect(result).toHaveProperty('value', groupRankLayout); + }); + + it.each([[null], [[]], ['layout'], [undefined]])('rejects %j as not an object', async (value) => { + expect(await problemsOf(value)).toEqual([{ path: [], message: 'expected an object' }]); + }); + + it('requires groups', async () => { + expect(await problemsOf({})).toEqual([{ path: ['groups'], message: 'required' }]); + }); + + it('rejects an unknown key at every level, naming where it is', async () => { + const layout = { + groups: [{ name: 'a', extra: 1, slice: { segment: 0, namePrefix: true } }], + rankskip: { maxDistance: 1, exemptRanks: [] }, + nameRanks: [{ pattern: 'x', rank: 1, extra: 1 }], + rankSkip: { maxDistance: 1, exemptRanks: [], extra: 1 }, + naming: { extra: 1 }, + }; + + expect(await problemsOf(layout)).toEqual([ + { path: ['rankskip'], message: 'unknown key' }, + { path: ['groups', 0, 'extra'], message: 'unknown key' }, + { path: ['groups', 0, 'slice', 'namePrefix'], message: 'unknown key' }, + { path: ['nameRanks', 0, 'extra'], message: 'unknown key' }, + { path: ['rankSkip', 'extra'], message: 'unknown key' }, + { path: ['naming', 'extra'], message: 'unknown key' }, + ]); + }); + + it('rejects a field of the wrong type, at its path', async () => { + expect( + await problemsOf({ + root: 1, + packages: 'core/*', + dependencyFields: ['dependencies', 2], + groups: [{ name: 1, path: 2, rank: 1.5, naming: 'flat' }], + nameRanks: [{ pattern: 1, rank: 'x' }], + defaultRank: '1', + rankSkip: { maxDistance: -1, exemptRanks: ['0'] }, + naming: { scope: 1, separator: 2 }, + }), + ).toEqual([ + { path: ['root'], message: 'expected a string' }, + { path: ['packages'], message: 'expected an array' }, + { path: ['dependencyFields', 1], message: 'expected a string' }, + { path: ['groups', 0, 'name'], message: 'expected a string' }, + { path: ['groups', 0, 'path'], message: 'expected a string' }, + { path: ['groups', 0, 'rank'], message: 'expected an integer' }, + { path: ['groups', 0, 'naming'], message: 'expected "drop-group" or "keep-group" or "basename"' }, + { path: ['nameRanks', 0, 'pattern'], message: 'expected a string' }, + { path: ['nameRanks', 0, 'rank'], message: 'expected an integer' }, + { path: ['defaultRank'], message: 'expected an integer' }, + { path: ['rankSkip', 'maxDistance'], message: 'expected an integer of at least 0' }, + { path: ['rankSkip', 'exemptRanks', 0], message: 'expected an integer' }, + { path: ['naming', 'scope'], message: 'expected a string' }, + { path: ['naming', 'separator'], message: 'expected a string' }, + ]); + }); + + it('requires the members of rankSkip and of a group and rank rule', async () => { + expect(await problemsOf({ groups: [{}], nameRanks: [{}], rankSkip: {} })).toEqual([ + { path: ['groups', 0, 'name'], message: 'required' }, + { path: ['nameRanks', 0, 'pattern'], message: 'required' }, + { path: ['nameRanks', 0, 'rank'], message: 'required' }, + { path: ['rankSkip', 'maxDistance'], message: 'required' }, + { path: ['rankSkip', 'exemptRanks'], message: 'required' }, + ]); + }); + + describe('slice', () => { + it.each([[{ segment: -1 }], [{ segment: 1.5 }], [{ segment: '0' }]])('rejects the segment %j', async (slice) => { + expect((await problemsOf({ groups: [{ name: 'a', slice }] })).map((problem) => problem.path)).toEqual([['groups', 0, 'slice', 'segment']]); + }); + + it.each([[{ namePrefix: false }], [{ namePrefix: 'true' }]])('rejects %j, since namePrefix can only be true', async (slice) => { + expect(await problemsOf({ groups: [{ name: 'a', slice }] })).toEqual([{ path: ['groups', 0, 'slice', 'namePrefix'], message: 'expected true' }]); + }); + + it.each([[{}], [{ other: 1 }], [null], [[]], ['segment'], [1]])('rejects %j, which is neither variant', async (slice) => { + expect(await problemsOf({ groups: [{ name: 'a', slice }] })).toEqual([ + { path: ['groups', 0, 'slice'], message: 'expected { segment: } or { namePrefix: true }' }, + ]); + }); + }); + + describe('isolatedGroups', () => { + const groups = [{ name: 'a' }, { name: 'b' }]; + + it('accepts a pair of two different declared groups', async () => { + expect((await validate({ groups, isolatedGroups: [['a', 'b']] })).valid).toBe(true); + }); + + it('rejects a pair with the wrong number of members', async () => { + expect(await problemsOf({ groups, isolatedGroups: [['a'], ['a', 'b', 'a']] })).toEqual([ + { path: ['isolatedGroups', 0], message: 'expected an array of exactly two items' }, + { path: ['isolatedGroups', 1], message: 'expected an array of exactly two items' }, + ]); + }); + + it('rejects a member that is not a declared group, at its position', async () => { + expect(await problemsOf({ groups, isolatedGroups: [['a', 'missing'], ['other', 'b']] })).toEqual([ + { path: ['isolatedGroups', 0, 1], message: "'missing' is not a declared group" }, + { path: ['isolatedGroups', 1, 0], message: "'other' is not a declared group" }, + ]); + }); + + it('rejects a group isolated from itself', async () => { + expect(await problemsOf({ groups, isolatedGroups: [['a', 'a']] })).toEqual([ + { path: ['isolatedGroups', 0], message: "a group cannot be isolated from itself ('a')" }, + ]); + }); + + it('reports an undeclared group isolated from itself as both problems', async () => { + expect((await problemsOf({ groups, isolatedGroups: [['z', 'z']] })).map((problem) => problem.path)).toEqual([ + ['isolatedGroups', 0, 0], + ['isolatedGroups', 0, 1], + ['isolatedGroups', 0], + ]); + }); + }); + + it('rejects a group name declared more than once, at every declaration after the first', async () => { + expect(await problemsOf({ groups: [{ name: 'a' }, { name: 'a' }, { name: 'a' }] })).toEqual([ + { path: ['groups', 1, 'name'], message: "group 'a' is declared more than once" }, + { path: ['groups', 2, 'name'], message: "group 'a' is declared more than once" }, + ]); + }); + + it('rejects a rank pattern that is not a regular expression under the u flag', async () => { + expect(await problemsOf({ groups: [], nameRanks: [{ pattern: '^ok$', rank: 1 }, { pattern: '(', rank: 1 }, { pattern: '\\-', rank: 1 }] })).toEqual([ + { path: ['nameRanks', 1, 'pattern'], message: "'(' is not a valid regular expression" }, + { path: ['nameRanks', 2, 'pattern'], message: "'\\-' is not a valid regular expression" }, + ]); + }); + + it('does not run the relational rules on a layout whose shape is wrong', async () => { + expect(await problemsOf({ groups: [{ name: 'a' }, { name: 'a', rank: 'x' }] })).toEqual([{ path: ['groups', 1, 'rank'], message: 'expected an integer' }]); + }); +}); diff --git a/src/section.ts b/src/section.ts new file mode 100644 index 0000000..8ffcb0f --- /dev/null +++ b/src/section.ts @@ -0,0 +1,95 @@ +import type { StandardSchemaV1 } from '@standard-schema/spec'; + +/** + * A tool's claim on one top-level key of the unified config: the key's name, and the Standard Schema that owns the shape of its value. The tool exports one; the authoring file passes it to {@link withSections} and the tool passes it to `loadSection`, so name and schema cannot drift apart. + */ +export interface Section { + readonly name: Name; + readonly schema: Schema; +} + +/** + * The keys every config file may carry regardless of which sections it lists. + */ +export interface Envelope { + /** + * Presets to extend, resolved and merged by `cosmiconfig-extends`. For a unified file the presets are whole config files, so a preset can supply any section. + */ + readonly extends?: string | readonly string[]; +} + +/** + * Section name to the value type the authoring file may write for it. + */ +export type SectionMap = Readonly>; + +/** + * The value an authoring file exports: the {@link Envelope} plus one optional key per listed section. + * + * With no sections the mapped part would be `{}`, which TypeScript does not excess-property check, so any key would be accepted. Without sections the type is therefore the envelope alone, and every section key is an unknown-property error. + */ +export type Config = keyof Sections extends never + ? Envelope + : Envelope & { readonly [Name in keyof Sections]?: Sections[Name] }; + +/** + * The section map for a list of section descriptors: each name to what its schema accepts as input. + */ +export type SectionsOf = { + [Descriptor in Descriptors[number] as Descriptor['name']]: StandardSchemaV1.InferInput; +}; + +/** + * The type of an authoring file's export for a list of section descriptors. Under `isolatedDeclarations` a default export must be a named, annotated value, and this is the annotation: `const config: ConfigOf<[typeof toolA]> = withSections(toolA)({ ... }); export default config;`. + */ +export type ConfigOf = Config>; + +const SECTION_NAME = /^[A-Za-z][A-Za-z0-9_-]*$/; +const ENVELOPE_KEYS: readonly string[] = ['extends']; + +/** + * Describe a tool's section. `name` is both the key in the unified config and the base of the standalone file name `.config.ts`, so it must start with a letter and contain only letters, digits, `-` and `_`, and it must not be a key of the {@link Envelope}. Throws a `TypeError` otherwise. + */ +export function defineSection( + name: Name, + schema: Schema, +): Section { + if (!SECTION_NAME.test(name)) { + throw new TypeError(`invalid section name '${name}': it must start with a letter and contain only letters, digits, '-' and '_'`); + } + if (ENVELOPE_KEYS.includes(name)) { + throw new TypeError(`invalid section name '${name}': it is a key of the config envelope`); + } + + return { name, schema }; +} + +/** + * Bind the sections a config file may contain. The authoring file passes the descriptors its tools export, and gets back a `defineConfig` for exactly those sections: + * + * ```ts + * export default withSections(eslint, layoutSection)({ layout: { groups: [{ name: 'core' }] } }); + * ``` + * + * A section is accepted when its descriptor is passed, so a tool whose package is not installed is a module-not-found error on its import, and a tool that is installed but not passed is an unknown-property error on its key. At least one descriptor is required, and each name may be listed once: a repeated name throws a `TypeError`. + */ +export function withSections( + ...sections: Descriptors +): (config: ConfigOf) => ConfigOf { + const names = new Set(); + for (const { name } of sections) { + if (names.has(name)) { + throw new TypeError(`section '${name}' is listed more than once`); + } + names.add(name); + } + + return (config) => config; +} + +/** + * Type-check a config that lists no sections, so only the {@link Envelope} keys are accepted. The value is returned unchanged: schema defaults are never applied while authoring, because `extends` merges what a file exports and a default would look like a value the author wrote. + */ +export function defineConfig(config: Envelope): Envelope { + return config; +} diff --git a/src/section.unit.test.ts b/src/section.unit.test.ts new file mode 100644 index 0000000..ebd4d32 --- /dev/null +++ b/src/section.unit.test.ts @@ -0,0 +1,99 @@ +import { describe, expect, expectTypeOf, it } from 'vitest'; +import { z } from 'zod'; + +import { type Config, type ConfigOf, defineConfig, defineSection, type Envelope, type Section, type SectionsOf, withSections } from './section'; + +interface ToolAConfig { + include: string[]; + level?: 'low' | 'high'; +} + +const toolAConfigSchema: z.ZodType = z.strictObject({ + include: z.array(z.string()), + level: z.exactOptional(z.enum(['low', 'high'])), +}); + +const toolA: Section<'toolA', z.ZodType> = defineSection('toolA', toolAConfigSchema); +const toolB = defineSection('toolB', z.strictObject({ rules: z.record(z.string(), z.string()) })); + +describe('defineSection', () => { + it('pairs the name with the schema it was given', () => { + expect(toolA.name).toBe('toolA'); + expect(toolA.schema).toBe(toolAConfigSchema); + }); + + it('keeps the name as a literal type', () => { + expectTypeOf(toolA.name).toEqualTypeOf<'toolA'>(); + expectTypeOf(toolB.name).toEqualTypeOf<'toolB'>(); + }); + + it.each(['eslint', 'my-tool', 'my_tool', 'A1', 'a', 'a-', 'Z9_-'])('accepts the name %s', (name) => { + expect(defineSection(name, toolAConfigSchema).name).toBe(name); + }); + + it.each(['', '1tool', '-tool', '_tool', 'my tool', 'my.tool', 'a/b', '../x', 'a\\b', 'tool\n', '\ntool', ' tool', 'tool ', '__proto__', 'é'])( + 'rejects the name %j, which cannot be a key and a file name', + (name) => { + expect(() => defineSection(name, toolAConfigSchema)).toThrow(TypeError); + expect(() => defineSection(name, toolAConfigSchema)).toThrow(/^invalid section name '.*': it must start with a letter and contain only letters, digits, '-' and '_'$/s); + }, + ); + + it('rejects the name of an envelope key', () => { + expect(() => defineSection('extends', toolAConfigSchema)).toThrow(new TypeError("invalid section name 'extends': it is a key of the config envelope")); + }); +}); + +describe('withSections', () => { + it('returns a defineConfig that hands back the config it was given', () => { + const config = { toolA: { include: ['src'] } }; + + expect(withSections(toolA)(config)).toBe(config); + }); + + it('accepts several sections', () => { + const config = { toolA: { include: ['src'] }, toolB: { rules: { x: 'warn' } } }; + + expect(withSections(toolA, toolB)(config)).toBe(config); + }); + + it('rejects a section listed twice, naming it', () => { + expect(() => withSections(toolA, toolB, defineSection('toolA', toolAConfigSchema))).toThrow(new TypeError("section 'toolA' is listed more than once")); + }); + + it('types the config from the listed descriptors', () => { + const defineConfigForA = withSections(toolA); + + expectTypeOf(defineConfigForA).parameter(0).toEqualTypeOf(); + expectTypeOf(defineConfigForA).returns.toEqualTypeOf>(); + }); + + it('derives the section map from the descriptors', () => { + expectTypeOf>().toEqualTypeOf<{ + toolA: ToolAConfig; + toolB: { rules: Record }; + }>(); + }); +}); + +describe('defineConfig', () => { + it('returns its argument unchanged', () => { + const config = { extends: './preset.ts' }; + + expect(defineConfig(config)).toBe(config); + }); + + it('accepts only the envelope', () => { + expectTypeOf(defineConfig).parameter(0).toEqualTypeOf(); + }); +}); + +describe('Config', () => { + it('adds one optional key per section', () => { + expectTypeOf>().toEqualTypeOf(); + }); + + it('allows extends as a string or a list of strings', () => { + expectTypeOf().toEqualTypeOf(); + }); +}); diff --git a/src/validation.ts b/src/validation.ts new file mode 100644 index 0000000..d15af93 --- /dev/null +++ b/src/validation.ts @@ -0,0 +1,195 @@ +import type { StandardSchemaV1 } from '@standard-schema/spec'; + +/** + * Location of a value inside the validated root, outermost key first; empty for the root itself. + */ +export type Path = readonly (string | number)[]; + +/** + * Records one problem at `path`. + */ +export type Report = (path: Path, message: string) => void; + +/** + * A type guard that also explains itself. It reports every problem it finds, without stopping at the first, and returns `true` exactly when it reported none. + */ +export type Check = (value: unknown, path: Path, report: Report) => value is T; + +/** + * One property of an object shape. `optional` is the property's presence rule, and the type parameter carries it so that a shape can be checked against the interface it describes. + */ +export interface Field { + readonly check: Check; + readonly optional: Optional; +} + +/** + * The fields an object guard needs for `T`: one per property, optional exactly where `T`'s property is optional. + */ +export type Shape = { + readonly [K in keyof T]-?: object extends Pick ? Field[K], true> : Field; +}; + +/** + * Whether `value` is a non-null, non-array object. + */ +export function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +function isInteger(value: unknown): value is number { + return Number.isInteger(value); +} + +/** + * Check every element of `items` against `item`, reporting each failure at its index, and return whether all passed. + */ +function checkItems(items: readonly unknown[], item: Check, path: Path, report: Report): boolean { + let valid = true; + items.forEach((element, index) => { + valid = item(element, [...path, index], report) && valid; + }); + + return valid; +} + +/** + * A property that must be present. + */ +export function required(check: Check): Field { + return { check, optional: false }; +} + +/** + * A property that may be absent. A key that is present must still pass `check`, so `undefined` is not a way to omit it. + */ +export function optional(check: Check): Field { + return { check, optional: true }; +} + +/** + * Accepts any string. + */ +export const isString: Check = (value, path, report): value is string => { + if (typeof value === 'string') { + return true; + } + report(path, 'expected a string'); + + return false; +}; + +/** + * Accepts an integer no smaller than `minimum`, or any integer when `minimum` is omitted. + */ +export function integer(minimum?: number): Check { + return (value, path, report): value is number => { + if (!isInteger(value)) { + report(path, 'expected an integer'); + + return false; + } + if (minimum !== undefined && value < minimum) { + report(path, `expected an integer of at least ${String(minimum)}`); + + return false; + } + + return true; + }; +} + +/** + * Accepts exactly one of `allowed`. + */ +export function literal(...allowed: readonly V[]): Check { + return (value, path, report): value is V => { + if (allowed.some((candidate) => candidate === value)) { + return true; + } + report(path, `expected ${allowed.map((candidate) => JSON.stringify(candidate)).join(' or ')}`); + + return false; + }; +} + +/** + * Accepts an array whose every item passes `item`. All items are checked, so every bad item is reported. + */ +export function array(item: Check): Check { + return (value, path, report): value is readonly T[] => { + if (!Array.isArray(value)) { + report(path, 'expected an array'); + + return false; + } + + return checkItems(value, item, path, report); + }; +} + +/** + * Accepts an array of exactly two items that each pass `item`. + */ +export function pair(item: Check): Check { + return (value, path, report): value is readonly [T, T] => { + if (!Array.isArray(value) || value.length !== 2) { + report(path, 'expected an array of exactly two items'); + + return false; + } + + return checkItems(value, item, path, report); + }; +} + +/** + * Accepts a plain object with exactly the keys of `shape`: a key outside it is reported as unknown, and a required key that is absent is reported as missing. + */ +export function strictObject(shape: Readonly>): Check; +export function strictObject(shape: Readonly>>): Check { + return (value, path, report): value is object => { + if (!isRecord(value)) { + report(path, 'expected an object'); + + return false; + } + let valid = true; + for (const key of Object.keys(value)) { + if (!Object.hasOwn(shape, key)) { + report([...path, key], 'unknown key'); + valid = false; + } + } + for (const [key, field] of Object.entries(shape)) { + if (Object.hasOwn(value, key)) { + valid = field.check(value[key], [...path, key], report) && valid; + } else if (!field.optional) { + report([...path, key], 'required'); + valid = false; + } + } + + return valid; + }; +} + +/** + * Wrap a guard as a Standard Schema whose input and output are both `T`: a value that passes is returned as it is, with nothing defaulted or stripped. + */ +export function standardSchema(vendor: string, check: Check): StandardSchemaV1 { + return { + '~standard': { + version: 1, + vendor, + validate: (value) => { + const issues: StandardSchemaV1.Issue[] = []; + const valid = check(value, [], (path, message) => { + issues.push({ path, message }); + }); + + return valid ? { value } : { issues }; + }, + }, + }; +} diff --git a/src/validation.unit.test.ts b/src/validation.unit.test.ts new file mode 100644 index 0000000..2566f5c --- /dev/null +++ b/src/validation.unit.test.ts @@ -0,0 +1,215 @@ +import { describe, expect, it } from 'vitest'; + +import { + array, + type Check, + integer, + isRecord, + isString, + literal, + optional, + pair, + type Path, + required, + standardSchema, + strictObject, +} from './validation'; + +const NEGATIVE = -1; +const FRACTION = 1.5; + +interface Problem { + readonly path: Path; + readonly message: string; +} + +function run(check: Check, value: unknown): { readonly valid: boolean; readonly problems: readonly Problem[] } { + const problems: Problem[] = []; + const valid = check(value, [], (path, message) => { + problems.push({ path, message }); + }); + + return { valid, problems }; +} + +describe('isRecord', () => { + it.each([[{}], [{ a: 1 }], [Object.create(null)]])('accepts the object %j', (value) => { + expect(isRecord(value)).toBe(true); + }); + + it.each([[null], [[]], ['a'], [1], [undefined]])('rejects %j', (value) => { + expect(isRecord(value)).toBe(false); + }); +}); + +describe('isString', () => { + it('accepts a string and reports nothing', () => { + expect(run(isString, 'a')).toEqual({ valid: true, problems: [] }); + }); + + it('reports anything else at the path it was given', () => { + const problems: Problem[] = []; + + expect( + isString(1, ['x', 2], (path, message) => { + problems.push({ path, message }); + }), + ).toBe(false); + expect(problems).toEqual([{ path: ['x', 2], message: 'expected a string' }]); + }); +}); + +describe('integer', () => { + it('accepts an integer, including zero and a negative one when there is no minimum', () => { + expect(run(integer(), 0).valid).toBe(true); + expect(run(integer(), NEGATIVE).valid).toBe(true); + }); + + it.each([[FRACTION], ['1'], [Number.NaN], [null]])('rejects %j as not an integer', (value) => { + expect(run(integer(), value)).toEqual({ valid: false, problems: [{ path: [], message: 'expected an integer' }] }); + }); + + it('accepts a value equal to the minimum and rejects one below it', () => { + expect(run(integer(2), 2).valid).toBe(true); + expect(run(integer(2), 1)).toEqual({ valid: false, problems: [{ path: [], message: 'expected an integer of at least 2' }] }); + }); + + it('accepts a minimum of zero', () => { + expect(run(integer(0), 0).valid).toBe(true); + expect(run(integer(0), -1).valid).toBe(false); + }); +}); + +describe('literal', () => { + it('accepts each allowed value', () => { + expect(run(literal('a', 'b'), 'a').valid).toBe(true); + expect(run(literal('a', 'b'), 'b').valid).toBe(true); + expect(run(literal(true), true).valid).toBe(true); + }); + + it('names every allowed value when rejecting', () => { + expect(run(literal('a', 'b'), 'c')).toEqual({ valid: false, problems: [{ path: [], message: 'expected "a" or "b"' }] }); + }); + + it('does not coerce', () => { + expect(run(literal(1), '1').valid).toBe(false); + }); +}); + +describe('array', () => { + it('accepts an empty array and an array of valid items', () => { + expect(run(array(isString), []).valid).toBe(true); + expect(run(array(isString), ['a', 'b']).valid).toBe(true); + }); + + it.each([['a'], [{}], [null]])('rejects %j as not an array', (value) => { + expect(run(array(isString), value)).toEqual({ valid: false, problems: [{ path: [], message: 'expected an array' }] }); + }); + + it('reports every bad item at its index, not only the first', () => { + expect(run(array(isString), [1, 'a', 2])).toEqual({ + valid: false, + problems: [ + { path: [0], message: 'expected a string' }, + { path: [2], message: 'expected a string' }, + ], + }); + }); +}); + +describe('pair', () => { + it('accepts exactly two valid items', () => { + expect(run(pair(isString), ['a', 'b']).valid).toBe(true); + }); + + it.each([[[]], [['a']], [['a', 'b', 'c']], ['ab'], [null]])('rejects %j', (value) => { + expect(run(pair(isString), value)).toEqual({ + valid: false, + problems: [{ path: [], message: 'expected an array of exactly two items' }], + }); + }); + + it('reports each bad member at its index', () => { + expect(run(pair(isString), [1, 2])).toEqual({ + valid: false, + problems: [ + { path: [0], message: 'expected a string' }, + { path: [1], message: 'expected a string' }, + ], + }); + }); +}); + +describe('strictObject', () => { + interface Sample { + readonly a: string; + readonly b?: number; + } + const isSample = strictObject({ a: required(isString), b: optional(integer()) }); + + it('accepts an object with the required key, with or without the optional one', () => { + expect(run(isSample, { a: 'x' }).valid).toBe(true); + expect(run(isSample, { a: 'x', b: 1 }).valid).toBe(true); + }); + + it.each([[null], [[]], ['a']])('rejects %j as not an object', (value) => { + expect(run(isSample, value)).toEqual({ valid: false, problems: [{ path: [], message: 'expected an object' }] }); + }); + + it('reports a missing required key', () => { + expect(run(isSample, {})).toEqual({ valid: false, problems: [{ path: ['a'], message: 'required' }] }); + }); + + it('reports an unknown key', () => { + expect(run(isSample, { a: 'x', c: 1 })).toEqual({ valid: false, problems: [{ path: ['c'], message: 'unknown key' }] }); + }); + + it('checks a present optional key, and treats undefined as a value rather than as absence', () => { + expect(run(isSample, { a: 'x', b: 'y' }).problems).toEqual([{ path: ['b'], message: 'expected an integer' }]); + expect(run(isSample, { a: 'x', b: undefined }).valid).toBe(false); + }); + + it('reports every problem in one pass and nests paths', () => { + const nested = strictObject<{ readonly inner: Sample }>({ inner: required(isSample) }); + + expect(run(nested, { inner: { b: 'y', c: 1 }, extra: 1 }).problems).toEqual([ + { path: ['extra'], message: 'unknown key' }, + { path: ['inner', 'c'], message: 'unknown key' }, + { path: ['inner', 'a'], message: 'required' }, + { path: ['inner', 'b'], message: 'expected an integer' }, + ]); + }); + + it('does not take a key from the prototype as present', () => { + expect(run(isSample, Object.create({ a: 'x' })).problems).toEqual([{ path: ['a'], message: 'required' }]); + }); + + it('treats a key named after an Object.prototype member as unknown', () => { + expect(run(isSample, { a: 'x', toString: 1 }).problems).toEqual([{ path: ['toString'], message: 'unknown key' }]); + }); +}); + +describe('standardSchema', () => { + const schema = standardSchema('test-vendor', array(isString)); + + it('declares Standard Schema version 1 and its vendor', () => { + expect(schema['~standard'].version).toBe(1); + expect(schema['~standard'].vendor).toBe('test-vendor'); + }); + + it('returns a valid value as it is', () => { + const value = ['a']; + + expect(schema['~standard'].validate(value)).toEqual({ value }); + expect(schema['~standard'].validate(value)).toHaveProperty('value', value); + }); + + it('returns every issue with its path when the value is invalid', () => { + expect(schema['~standard'].validate(['a', 1, 2])).toEqual({ + issues: [ + { path: [1], message: 'expected a string' }, + { path: [2], message: 'expected a string' }, + ], + }); + }); +}); diff --git a/stryker.config.ts b/stryker.config.ts index 437020f..47b7816 100644 --- a/stryker.config.ts +++ b/stryker.config.ts @@ -7,7 +7,8 @@ const config: Partial = { plugins: ['@stryker-mutator/typescript-checker', '@stryker-mutator/vitest-runner'], checkers: ['typescript'], coverageAnalysis: 'perTest', - mutate: ['src/**/*.ts', '!src/**/*.test.ts'], + // cli.ts only forwards process arguments and streams to runCommand, so no test in this project can observe a mutant in it; the packaged-install check runs it. + mutate: ['src/**/*.ts', '!src/**/*.test.ts', '!src/cli.ts'], incremental: true, incrementalFile: 'reports/stryker-incremental.json', reporters: ['html', 'clear-text', 'progress'], diff --git a/test/support/project.ts b/test/support/project.ts new file mode 100644 index 0000000..38e452b --- /dev/null +++ b/test/support/project.ts @@ -0,0 +1,26 @@ +import { mkdirSync, mkdtempSync, realpathSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; + +/** + * Create a fresh directory containing `files` (relative path to content) and return its real path, so that paths compared against jiti's resolved output are not affected by a symlinked temporary directory. + */ +export function makeProject(files: Readonly> = {}): string { + const root = realpathSync(mkdtempSync(join(tmpdir(), 'exadev-config-'))); + for (const [relativePath, content] of Object.entries(files)) { + writeProjectFile(root, relativePath, content); + } + + return root; +} + +/** + * Write (or overwrite) one file below `root`, creating parent directories. + */ +export function writeProjectFile(root: string, relativePath: string, content: string): string { + const fullPath = join(root, relativePath); + mkdirSync(dirname(fullPath), { recursive: true }); + writeFileSync(fullPath, content); + + return fullPath; +} diff --git a/test/types/authoring-types.integration.test.ts b/test/types/authoring-types.integration.test.ts new file mode 100644 index 0000000..eb6b58c --- /dev/null +++ b/test/types/authoring-types.integration.test.ts @@ -0,0 +1,150 @@ +import { readdirSync } from 'node:fs'; +import { join, relative } from 'node:path'; + +import ts from 'typescript'; +import { beforeAll, describe, expect, it } from 'vitest'; + +interface Finding { + readonly line: number; + readonly code: number; + readonly message: string; +} + +const FIXTURES = join(import.meta.dirname, 'fixtures'); +const CASES = join(FIXTURES, 'cases'); + +/** + * The compiler options of the project's own tsconfig that decide how strictly an authoring file is checked. `isolatedDeclarations` is added only for the program that emits declarations. + */ +const BASE_OPTIONS: ts.CompilerOptions = { + exactOptionalPropertyTypes: true, + lib: ['lib.es2024.d.ts'], + module: ts.ModuleKind.ESNext, + moduleResolution: ts.ModuleResolutionKind.Bundler, + noEmit: true, + noUncheckedIndexedAccess: true, + skipLibCheck: true, + strict: true, + target: ts.ScriptTarget.ES2024, + types: [], + verbatimModuleSyntax: true, +}; + +function compile(options: ts.CompilerOptions): ReadonlyMap { + const files = [...readdirSync(CASES).map((name) => join(CASES, name)), join(FIXTURES, 'tool-a.ts'), join(FIXTURES, 'tool-b.ts')]; + const program = ts.createProgram(files, { ...BASE_OPTIONS, ...options }); + const byFile = new Map(); + for (const file of files) { + const findings = ts.getPreEmitDiagnostics(program, program.getSourceFile(file)).map((diagnostic) => ({ + line: diagnostic.file === undefined || diagnostic.start === undefined ? 0 : diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start).line + 1, + code: diagnostic.code, + message: ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'), + })); + byFile.set(relative(FIXTURES, file), findings); + } + + return byFile; +} + +let plain: ReadonlyMap; +let isolated: ReadonlyMap; + +beforeAll(() => { + plain = compile({}); + isolated = compile({ declaration: true, isolatedDeclarations: true }); +}); + +function findingsIn(program: ReadonlyMap, name: string): readonly Finding[] { + const findings = program.get(`${name}.ts`); + if (findings === undefined) { + throw new Error(`no fixture named ${name}`); + } + + return findings; +} + +interface Expected { + readonly line: number; + readonly code: number; + /** + * A fragment of the compiler's message, so a wording change in a later TypeScript release does not fail a test about which error is reported. + */ + readonly message: string; +} + +/** + * Asserts the compiler reported exactly the errors in `expected`, in order, by line and code, and that each message contains its fragment. + */ +function expectErrors(actual: readonly Finding[], expected: readonly Expected[]): void { + expect(actual.map(({ line, code }) => ({ line, code }))).toEqual(expected.map(({ line, code }) => ({ line, code }))); + for (const [index, { message }] of expected.entries()) { + expect(actual[index]?.message).toContain(message); + } +} + +describe('an authoring file', () => { + it.each(['ok-two-tools', 'ok-layout', 'ok-sections-optional', 'ok-envelope-only', 'ok-isolated-declarations'])('%s type-checks', (name) => { + expect(findingsIn(plain, `cases/${name}`)).toEqual([]); + }); + + it('rejects an unknown key inside a section, suggesting the near miss', () => { + expectErrors(findingsIn(plain, 'cases/unknown-key'), [{ line: 5, code: 2561, message: "'inclde' does not exist in type 'ToolAConfig'. Did you mean to write 'include'?" }]); + }); + + it('rejects a field of the wrong type', () => { + expectErrors(findingsIn(plain, 'cases/wrong-type'), [{ line: 5, code: 2322, message: "Type 'string' is not assignable to type 'string[]'." }]); + }); + + it('rejects a value outside a field\'s union', () => { + expectErrors(findingsIn(plain, 'cases/bad-enum'), [{ line: 5, code: 2322, message: '"medium"' }]); + }); + + it('rejects a missing required field', () => { + expectErrors(findingsIn(plain, 'cases/missing-required'), [{ line: 5, code: 2741, message: "Property 'include' is missing" }]); + }); + + it('rejects an explicit undefined for an optional field under exactOptionalPropertyTypes', () => { + expectErrors(findingsIn(plain, 'cases/exact-optional'), [{ line: 5, code: 2375, message: 'exactOptionalPropertyTypes' }]); + }); + + it('rejects the section of a tool that is installed but not listed', () => { + expectErrors(findingsIn(plain, 'cases/unlisted-tool'), [{ line: 8, code: 2561, message: "'toolB' does not exist in type" }]); + }); + + it('rejects a misspelt section name', () => { + expectErrors(findingsIn(plain, 'cases/misspelt-section'), [{ line: 5, code: 2561, message: "Did you mean to write 'toolA'?" }]); + }); + + it('rejects any section key when no section is listed, instead of accepting {}', () => { + expectErrors(findingsIn(plain, 'cases/empty-map-define-config'), [{ line: 5, code: 2353, message: "'toolA' does not exist in type 'Envelope'" }]); + }); + + it('rejects listing no descriptors at all', () => { + expectErrors(findingsIn(plain, 'cases/empty-map-with-sections'), [{ line: 3, code: 2555, message: 'Expected at least 1 arguments' }]); + }); + + it('reports a tool whose package is not installed as a module that cannot be found', () => { + expectErrors(findingsIn(plain, 'cases/uninstalled-tool'), [{ line: 3, code: 2307, message: "Cannot find module '@acme/absent'" }]); + }); + + it('rejects an extends that is not a string or a list of strings', () => { + expectErrors(findingsIn(plain, 'cases/bad-extends'), [{ line: 3, code: 2322, message: "Type 'number' is not assignable" }]); + }); + + it('checks the layout section like any other', () => { + expectErrors(findingsIn(plain, 'cases/layout-unknown-key'), [{ line: 4, code: 2561, message: "'rankskip' does not exist in type 'LayoutConfig'. Did you mean to write 'rankSkip'?" }]); + expectErrors(findingsIn(plain, 'cases/layout-missing-groups'), [{ line: 4, code: 2741, message: "Property 'groups' is missing" }]); + }); +}); + +describe('with isolatedDeclarations', () => { + it('accepts an annotated default export and the descriptors a tool package writes', () => { + expect(findingsIn(isolated, 'cases/ok-isolated-declarations')).toEqual([]); + expect(findingsIn(isolated, 'tool-a')).toEqual([]); + expect(findingsIn(isolated, 'tool-b')).toEqual([]); + }); + + it('requires a default export to be a named, annotated value, which ConfigOf makes short to write', () => { + expectErrors(findingsIn(isolated, 'cases/default-export-inferred'), [{ line: 5, code: 9037, message: "Default exports can't be inferred" }]); + }); +}); diff --git a/test/types/fixtures/cases/bad-enum.ts b/test/types/fixtures/cases/bad-enum.ts new file mode 100644 index 0000000..c72da52 --- /dev/null +++ b/test/types/fixtures/cases/bad-enum.ts @@ -0,0 +1,6 @@ +import { withSections } from '../../../../src/index'; +import { toolA } from '../tool-a'; + +export const config = withSections(toolA)({ + toolA: { include: ['src'], level: 'medium' }, +}); diff --git a/test/types/fixtures/cases/bad-extends.ts b/test/types/fixtures/cases/bad-extends.ts new file mode 100644 index 0000000..7a4426c --- /dev/null +++ b/test/types/fixtures/cases/bad-extends.ts @@ -0,0 +1,3 @@ +import { defineConfig } from '../../../../src/index'; + +export const config = defineConfig({ extends: 5 }); diff --git a/test/types/fixtures/cases/default-export-inferred.ts b/test/types/fixtures/cases/default-export-inferred.ts new file mode 100644 index 0000000..37fc29a --- /dev/null +++ b/test/types/fixtures/cases/default-export-inferred.ts @@ -0,0 +1,5 @@ +import { withSections } from '../../../../src/index'; +import { toolA } from '../tool-a'; + +// The pattern the README warns against under isolatedDeclarations: the default export needs a named, annotated value. +export default withSections(toolA)({ toolA: { include: ['src'] } }); diff --git a/test/types/fixtures/cases/empty-map-define-config.ts b/test/types/fixtures/cases/empty-map-define-config.ts new file mode 100644 index 0000000..24a9d11 --- /dev/null +++ b/test/types/fixtures/cases/empty-map-define-config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from '../../../../src/index'; + +// No sections are listed, so no section key is accepted (an empty mapped type would be {} and accept it). +export const config = defineConfig({ + toolA: { include: ['src'] }, +}); diff --git a/test/types/fixtures/cases/empty-map-with-sections.ts b/test/types/fixtures/cases/empty-map-with-sections.ts new file mode 100644 index 0000000..f6ef309 --- /dev/null +++ b/test/types/fixtures/cases/empty-map-with-sections.ts @@ -0,0 +1,3 @@ +import { withSections } from '../../../../src/index'; + +export const define = withSections(); diff --git a/test/types/fixtures/cases/exact-optional.ts b/test/types/fixtures/cases/exact-optional.ts new file mode 100644 index 0000000..ddaedf4 --- /dev/null +++ b/test/types/fixtures/cases/exact-optional.ts @@ -0,0 +1,6 @@ +import { withSections } from '../../../../src/index'; +import { toolA } from '../tool-a'; + +export const config = withSections(toolA)({ + toolA: { include: ['src'], level: undefined }, +}); diff --git a/test/types/fixtures/cases/layout-missing-groups.ts b/test/types/fixtures/cases/layout-missing-groups.ts new file mode 100644 index 0000000..0f2357a --- /dev/null +++ b/test/types/fixtures/cases/layout-missing-groups.ts @@ -0,0 +1,5 @@ +import { layoutSection, withSections } from '../../../../src/index'; + +export const config = withSections(layoutSection)({ + layout: { defaultRank: 1 }, +}); diff --git a/test/types/fixtures/cases/layout-unknown-key.ts b/test/types/fixtures/cases/layout-unknown-key.ts new file mode 100644 index 0000000..6dd5e1a --- /dev/null +++ b/test/types/fixtures/cases/layout-unknown-key.ts @@ -0,0 +1,5 @@ +import { layoutSection, withSections } from '../../../../src/index'; + +export const config = withSections(layoutSection)({ + layout: { groups: [], rankskip: { maxDistance: 1, exemptRanks: [] } }, +}); diff --git a/test/types/fixtures/cases/missing-required.ts b/test/types/fixtures/cases/missing-required.ts new file mode 100644 index 0000000..008510b --- /dev/null +++ b/test/types/fixtures/cases/missing-required.ts @@ -0,0 +1,6 @@ +import { withSections } from '../../../../src/index'; +import { toolA } from '../tool-a'; + +export const config = withSections(toolA)({ + toolA: {}, +}); diff --git a/test/types/fixtures/cases/misspelt-section.ts b/test/types/fixtures/cases/misspelt-section.ts new file mode 100644 index 0000000..2e8d24d --- /dev/null +++ b/test/types/fixtures/cases/misspelt-section.ts @@ -0,0 +1,6 @@ +import { withSections } from '../../../../src/index'; +import { toolA } from '../tool-a'; + +export const config = withSections(toolA)({ + toola: { include: ['src'] }, +}); diff --git a/test/types/fixtures/cases/ok-envelope-only.ts b/test/types/fixtures/cases/ok-envelope-only.ts new file mode 100644 index 0000000..ee4141e --- /dev/null +++ b/test/types/fixtures/cases/ok-envelope-only.ts @@ -0,0 +1,3 @@ +import { defineConfig } from '../../../../src/index'; + +export const config = defineConfig({ extends: './preset.ts' }); diff --git a/test/types/fixtures/cases/ok-isolated-declarations.ts b/test/types/fixtures/cases/ok-isolated-declarations.ts new file mode 100644 index 0000000..7a9914b --- /dev/null +++ b/test/types/fixtures/cases/ok-isolated-declarations.ts @@ -0,0 +1,9 @@ +import { type ConfigOf, withSections } from '../../../../src/index'; +import { toolA } from '../tool-a'; +import { toolB } from '../tool-b'; + +const config: ConfigOf<[typeof toolA, typeof toolB]> = withSections(toolA, toolB)({ + toolA: { include: ['src'] }, +}); + +export default config; diff --git a/test/types/fixtures/cases/ok-layout.ts b/test/types/fixtures/cases/ok-layout.ts new file mode 100644 index 0000000..fcdfab2 --- /dev/null +++ b/test/types/fixtures/cases/ok-layout.ts @@ -0,0 +1,7 @@ +import { layoutSection, withSections } from '../../../../src/index'; +import { toolA } from '../tool-a'; + +export const config = withSections(layoutSection, toolA)({ + layout: { groups: [{ name: 'core', rank: 0, slice: { segment: 0 } }], isolatedGroups: [['core', 'features']] }, + toolA: { include: ['src'] }, +}); diff --git a/test/types/fixtures/cases/ok-sections-optional.ts b/test/types/fixtures/cases/ok-sections-optional.ts new file mode 100644 index 0000000..4ed203f --- /dev/null +++ b/test/types/fixtures/cases/ok-sections-optional.ts @@ -0,0 +1,6 @@ +import { withSections } from '../../../../src/index'; +import { toolA } from '../tool-a'; +import { toolB } from '../tool-b'; + +export const onlyOne = withSections(toolA, toolB)({ toolB: { rules: {} } }); +export const none = withSections(toolA)({}); diff --git a/test/types/fixtures/cases/ok-two-tools.ts b/test/types/fixtures/cases/ok-two-tools.ts new file mode 100644 index 0000000..9b6485b --- /dev/null +++ b/test/types/fixtures/cases/ok-two-tools.ts @@ -0,0 +1,9 @@ +import { withSections } from '../../../../src/index'; +import { toolA } from '../tool-a'; +import { toolB } from '../tool-b'; + +export const config = withSections(toolA, toolB)({ + extends: ['./preset.ts'], + toolA: { include: ['src'], level: 'high' }, + toolB: { rules: { x: 'warn' } }, +}); diff --git a/test/types/fixtures/cases/uninstalled-tool.ts b/test/types/fixtures/cases/uninstalled-tool.ts new file mode 100644 index 0000000..126e854 --- /dev/null +++ b/test/types/fixtures/cases/uninstalled-tool.ts @@ -0,0 +1,5 @@ +import { withSections } from '../../../../src/index'; +// The package is not installed, so the import itself reports the error. +import { absent } from '@acme/absent'; + +export const config = withSections(absent)({}); diff --git a/test/types/fixtures/cases/unknown-key.ts b/test/types/fixtures/cases/unknown-key.ts new file mode 100644 index 0000000..25d4109 --- /dev/null +++ b/test/types/fixtures/cases/unknown-key.ts @@ -0,0 +1,6 @@ +import { withSections } from '../../../../src/index'; +import { toolA } from '../tool-a'; + +export const config = withSections(toolA)({ + toolA: { include: ['src'], inclde: ['x'] }, +}); diff --git a/test/types/fixtures/cases/unlisted-tool.ts b/test/types/fixtures/cases/unlisted-tool.ts new file mode 100644 index 0000000..ba425e2 --- /dev/null +++ b/test/types/fixtures/cases/unlisted-tool.ts @@ -0,0 +1,11 @@ +import { withSections } from '../../../../src/index'; +import { toolA } from '../tool-a'; +import { toolB } from '../tool-b'; + +// toolB is installed and imported, but not passed to withSections. +export const config = withSections(toolA)({ + toolA: { include: ['src'] }, + toolB: { rules: { x: 'warn' } }, +}); + +export const unused = toolB; diff --git a/test/types/fixtures/cases/wrong-type.ts b/test/types/fixtures/cases/wrong-type.ts new file mode 100644 index 0000000..7f52dee --- /dev/null +++ b/test/types/fixtures/cases/wrong-type.ts @@ -0,0 +1,6 @@ +import { withSections } from '../../../../src/index'; +import { toolA } from '../tool-a'; + +export const config = withSections(toolA)({ + toolA: { include: 'src' }, +}); diff --git a/test/types/fixtures/tool-a.ts b/test/types/fixtures/tool-a.ts new file mode 100644 index 0000000..e9d611f --- /dev/null +++ b/test/types/fixtures/tool-a.ts @@ -0,0 +1,14 @@ +import { defineSection, type Section } from '../../../src/index'; +import { z } from 'zod'; + +export interface ToolAConfig { + include: string[]; + level?: 'low' | 'high'; +} + +export const toolAConfigSchema: z.ZodType = z.strictObject({ + include: z.array(z.string()), + level: z.exactOptional(z.enum(['low', 'high'])), +}); + +export const toolA: Section<'toolA', z.ZodType> = defineSection('toolA', toolAConfigSchema); diff --git a/test/types/fixtures/tool-b.ts b/test/types/fixtures/tool-b.ts new file mode 100644 index 0000000..152ec47 --- /dev/null +++ b/test/types/fixtures/tool-b.ts @@ -0,0 +1,10 @@ +import { defineSection, type Section } from '../../../src/index'; +import { z } from 'zod'; + +export interface ToolBConfig { + rules: Record; +} + +export const toolBConfigSchema: z.ZodType = z.strictObject({ rules: z.record(z.string(), z.string()) }); + +export const toolB: Section<'toolB', z.ZodType> = defineSection('toolB', toolBConfigSchema); diff --git a/vitest.config.ts b/vitest.config.ts index 9f4db4f..89fc222 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -9,7 +9,8 @@ const config: ViteUserConfig = defineConfig({ provider: 'v8', reporter: ['text', 'html', 'lcov'], include: ['src/**/*.ts'], - exclude: ['src/**/*.test.ts'], + // cli.ts only forwards process arguments and streams to runCommand; the packaged-install check runs it. + exclude: ['src/**/*.test.ts', 'src/cli.ts'], }, }, }); From 3f57b513a0d7d16a2c853c8b6bf68da023efc155 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 29 Sep 2026 23:12:24 +0100 Subject: [PATCH 03/23] feat: resolve a tool's section from the unified file or a standalone file --- src/config-file.ts | 51 ++++++ src/index.ts | 2 + src/load-section.integration.test.ts | 224 +++++++++++++++++++++++++++ src/load-section.ts | 47 ++++++ 4 files changed, 324 insertions(+) create mode 100644 src/config-file.ts create mode 100644 src/load-section.integration.test.ts create mode 100644 src/load-section.ts diff --git a/src/config-file.ts b/src/config-file.ts new file mode 100644 index 0000000..27d7bec --- /dev/null +++ b/src/config-file.ts @@ -0,0 +1,51 @@ +import { statSync } from 'node:fs'; +import { join } from 'node:path'; + +import { createExplorer, type ExplorerOptions } from 'cosmiconfig-extends'; + +import { isRecord } from './validation'; + +/** + * The name of the unified config file, looked for in the directory a tool is asked about. + */ +export const UNIFIED_FILE = 'exadev.config.ts'; + +/** + * How config files are loaded, passed to `cosmiconfig-extends`: the authoring-import `alias`, jiti's `fsCache`, the `trust` policy for `extends` references (local paths only by default), and the `merge` that combines a file with the presets it extends (arrays replace by default). + */ +export type ConfigFileOptions = Pick; + +/** + * The path of the standalone config file for the tool that owns section `name`. + */ +export function standaloneFile(cwd: string, name: string): string { + return join(cwd, `${name}.config.ts`); +} + +/** + * The evaluated content of the config file at `file` after `extends` is applied, or `undefined` when the file does not exist or is empty. Every call reads the file afresh. + */ +export async function readConfigFile(file: string, options: ConfigFileOptions): Promise { + if (statSync(file, { throwIfNoEntry: false })?.isFile() !== true) { + return undefined; + } + const explorer = createExplorer('exadev', { ...options, cosmiconfig: { cache: false } }); + const result = await explorer.load(file); + + return result?.config; +} + +/** + * The evaluated unified config at `file`: a record, or `undefined` when the file is absent or empty. Throws when the file exports something other than an object. + */ +export async function readUnifiedFile(file: string, options: ConfigFileOptions): Promise> | undefined> { + const config = await readConfigFile(file, options); + if (config === undefined) { + return undefined; + } + if (!isRecord(config)) { + throw new TypeError(`${file}: the default export must be an object`); + } + + return config; +} diff --git a/src/index.ts b/src/index.ts index 3a08e6e..c8ec801 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +export type { ConfigFileOptions } from './config-file'; export { type GroupSpec, type LayoutConfig, @@ -11,4 +12,5 @@ export { type SliceBySegment, type SliceSpec, } from './layout'; +export { loadSection, type LoadSectionOptions } from './load-section'; export { type Config, type ConfigOf, defineConfig, defineSection, type Envelope, type Section, type SectionMap, type SectionsOf, withSections } from './section'; diff --git a/src/load-section.integration.test.ts b/src/load-section.integration.test.ts new file mode 100644 index 0000000..defe948 --- /dev/null +++ b/src/load-section.integration.test.ts @@ -0,0 +1,224 @@ +import { join, resolve } from 'node:path'; + +import { ConfigValidationError, type Merge } from 'cosmiconfig-extends'; +import { describe, expect, it } from 'vitest'; +import { z } from 'zod'; + +import { makeProject, writeProjectFile } from '../test/support/project'; + +import { layoutSection } from './layout'; +import { loadSection } from './load-section'; +import { defineSection } from './section'; +import { isRecord } from './validation'; + +const toolA = defineSection('toolA', z.strictObject({ include: z.array(z.string()), level: z.enum(['low', 'high']).default('low') })); + +function isArray(value: unknown): value is readonly unknown[] { + return Array.isArray(value); +} + +const SECTION = "{ include: ['src'] }"; + +function unified(section: string = SECTION): string { + return `export default { toolA: ${section}, other: { anything: true } };\n`; +} + +describe('loadSection', () => { + it('reads a section from the unified file', async () => { + const cwd = makeProject({ 'exadev.config.ts': unified() }); + + expect(await loadSection(toolA, { cwd })).toEqual({ include: ['src'], level: 'low' }); + }); + + it('reads a section from its standalone file', async () => { + const cwd = makeProject({ 'toolA.config.ts': `export default ${SECTION};\n` }); + + expect(await loadSection(toolA, { cwd })).toEqual({ include: ['src'], level: 'low' }); + }); + + it('produces the same section from the unified file and from the standalone file', async () => { + const fromUnified = await loadSection(toolA, { cwd: makeProject({ 'exadev.config.ts': unified("{ include: ['a', 'b'], level: 'high' }") }) }); + const fromStandalone = await loadSection(toolA, { cwd: makeProject({ 'toolA.config.ts': "export default { include: ['a', 'b'], level: 'high' };\n" }) }); + + expect(fromUnified).toEqual({ include: ['a', 'b'], level: 'high' }); + expect(fromStandalone).toEqual(fromUnified); + }); + + it('ignores the sections of other tools in the unified file, and their standalone files', async () => { + const cwd = makeProject({ 'exadev.config.ts': unified(), 'other.config.ts': 'export default 1;\n' }); + + expect(await loadSection(toolA, { cwd })).toEqual({ include: ['src'], level: 'low' }); + }); + + it('fails when the section is defined in both files, naming both', async () => { + const cwd = makeProject({ 'exadev.config.ts': unified(), 'toolA.config.ts': `export default ${SECTION};\n` }); + + await expect(loadSection(toolA, { cwd })).rejects.toThrow( + new Error(`section 'toolA' is defined in both ${join(cwd, 'exadev.config.ts')} and ${join(cwd, 'toolA.config.ts')}; keep exactly one`), + ); + }); + + it('is not a conflict when the unified file defines only other sections', async () => { + const cwd = makeProject({ 'exadev.config.ts': "export default { other: {} };\n", 'toolA.config.ts': `export default ${SECTION};\n` }); + + expect(await loadSection(toolA, { cwd })).toEqual({ include: ['src'], level: 'low' }); + }); + + it('returns undefined when no file defines the section', async () => { + expect(await loadSection(toolA, { cwd: makeProject() })).toBeUndefined(); + expect(await loadSection(toolA, { cwd: makeProject({ 'exadev.config.ts': 'export default { other: {} };\n' }) })).toBeUndefined(); + }); + + it('returns undefined for an empty unified file and for a section exported as undefined', async () => { + expect(await loadSection(toolA, { cwd: makeProject({ 'exadev.config.ts': 'export default undefined;\n' }) })).toBeUndefined(); + expect(await loadSection(toolA, { cwd: makeProject({ 'exadev.config.ts': 'export default { toolA: undefined };\n' }) })).toBeUndefined(); + expect(await loadSection(toolA, { cwd: makeProject({ 'toolA.config.ts': 'export default undefined;\n' }) })).toBeUndefined(); + }); + + it('does not take a section from the prototype of the unified config', async () => { + const constructorSection = defineSection('constructor', z.unknown()); + const cwd = makeProject({ 'exadev.config.ts': 'export default {};\n' }); + + expect(await loadSection(constructorSection, { cwd })).toBeUndefined(); + }); + + it('does not search parent directories', async () => { + const parent = makeProject({ 'exadev.config.ts': unified() }); + writeProjectFile(parent, 'child/package.json', '{}'); + + expect(await loadSection(toolA, { cwd: join(parent, 'child') })).toBeUndefined(); + }); + + it('applies the schema and returns its output, defaults included', async () => { + const cwd = makeProject({ 'exadev.config.ts': unified() }); + + expect(await loadSection(toolA, { cwd })).toHaveProperty('level', 'low'); + }); + + describe('validation', () => { + it('rejects an invalid section in the unified file with the file and the path of every problem', async () => { + const cwd = makeProject({ 'exadev.config.ts': unified("{ include: 'src', bogus: 1 }") }); + const failure: unknown = await loadSection(toolA, { cwd }).catch((error: unknown) => error); + + expect(failure).toBeInstanceOf(ConfigValidationError); + expect(failure).toHaveProperty('message', expect.stringContaining(`invalid 'toolA' section in ${join(cwd, 'exadev.config.ts')}:`)); + expect(failure).toHaveProperty('issues', expect.arrayContaining([expect.objectContaining({ path: ['include'] }), expect.objectContaining({ path: [] })])); + }); + + it('rejects an invalid standalone section with the standalone file', async () => { + const cwd = makeProject({ 'toolA.config.ts': "export default { include: 1 };\n" }); + + await expect(loadSection(toolA, { cwd })).rejects.toThrow(`invalid 'toolA' section in ${join(cwd, 'toolA.config.ts')}:`); + }); + + it('rejects a unified file that does not export an object', async () => { + for (const value of ['5', "'text'", '[]']) { + const cwd = makeProject({ 'exadev.config.ts': `export default ${value};\n` }); + + await expect(loadSection(toolA, { cwd })).rejects.toThrow(new TypeError(`${join(cwd, 'exadev.config.ts')}: the default export must be an object`)); + } + }); + + it('validates through a Standard Schema that is not zod', async () => { + const cwd = makeProject({ 'exadev.config.ts': "export default { layout: { groups: [{ name: 'core' }, { name: 'core' }] } };\n" }); + + await expect(loadSection(layoutSection, { cwd })).rejects.toThrow("groups.1.name: group 'core' is declared more than once"); + }); + }); + + describe('layout', () => { + it('loads the shared layout section from either file', async () => { + const layout = "{ groups: [{ name: 'core', rank: 0 }], naming: { scope: '@acme' } }"; + const expected = { groups: [{ name: 'core', rank: 0 }], naming: { scope: '@acme' } }; + + expect(await loadSection(layoutSection, { cwd: makeProject({ 'exadev.config.ts': `export default { layout: ${layout} };\n` }) })).toEqual(expected); + expect(await loadSection(layoutSection, { cwd: makeProject({ 'layout.config.ts': `export default ${layout};\n` }) })).toEqual(expected); + }); + }); + + describe('extends', () => { + it('merges a preset into the unified file, so a preset can supply a section', async () => { + const cwd = makeProject({ + 'preset.ts': "export default { toolA: { include: ['preset'], level: 'high' } };\n", + 'exadev.config.ts': "export default { extends: './preset.ts', toolA: { include: ['local'] } };\n", + }); + + expect(await loadSection(toolA, { cwd })).toEqual({ include: ['local'], level: 'high' }); + }); + + it('applies extends inside a standalone file', async () => { + const cwd = makeProject({ + 'base.ts': "export default { include: ['base'], level: 'high' };\n", + 'toolA.config.ts': "export default { extends: './base.ts', level: 'low' };\n", + }); + + expect(await loadSection(toolA, { cwd })).toEqual({ include: ['base'], level: 'low' }); + }); + + it('refuses a package preset by default and admits it when trust allows it', async () => { + const files = { + 'node_modules/shared/package.json': JSON.stringify({ name: 'shared', type: 'module', main: 'index.js' }), + 'node_modules/shared/index.js': "export default { toolA: { include: ['shared'] } };\n", + 'exadev.config.ts': "export default { extends: 'shared' };\n", + }; + + await expect(loadSection(toolA, { cwd: makeProject(files) })).rejects.toThrow(/refusing to load untrusted preset 'shared'/); + expect(await loadSection(toolA, { cwd: makeProject(files), trust: ({ ref }) => ref === 'shared' })).toEqual({ include: ['shared'], level: 'low' }); + }); + + it('combines layers with the merge it is given', async () => { + const cwd = makeProject({ + 'preset.ts': "export default { toolA: { include: ['preset'] } };\n", + 'exadev.config.ts': "export default { extends: './preset.ts', toolA: { include: ['local'] } };\n", + }); + const merge: Merge = (base, override) => { + if (isArray(base) && isArray(override)) { + return [...new Set([...base, ...override])]; + } + if (isRecord(base) && isRecord(override)) { + const keys = new Set([...Object.keys(base), ...Object.keys(override)]); + + return Object.fromEntries([...keys].map((key) => [key, key in override ? merge(base[key], override[key]) : base[key]])); + } + + return override ?? base; + }; + + expect(await loadSection(toolA, { cwd, merge })).toEqual({ include: ['preset', 'local'], level: 'low' }); + }); + }); + + describe('authoring files', () => { + it('loads a file that imports @exadev/config through the alias, as an authoring file does', async () => { + const cwd = makeProject({ + 'shim/index.ts': `export * from ${JSON.stringify(resolve(import.meta.dirname, 'index.ts'))};\n`, + 'node_modules/@acme/tool-a/index.js': "export const toolA = { name: 'toolA', schema: {} };\n", + 'node_modules/@acme/tool-a/package.json': JSON.stringify({ name: '@acme/tool-a', type: 'module', main: 'index.js' }), + 'exadev.config.ts': [ + "import { toolA } from '@acme/tool-a';", + "import { layoutSection, withSections } from '@exadev/config';", + '', + 'export default withSections(toolA, layoutSection)({', + " toolA: { include: ['src'] },", + " layout: { groups: [{ name: 'core' }] },", + '});', + '', + ].join('\n'), + }); + const options = { cwd, alias: { '@exadev/config': join(cwd, 'shim') } }; + + expect(await loadSection(toolA, options)).toEqual({ include: ['src'], level: 'low' }); + expect(await loadSection(layoutSection, options)).toEqual({ groups: [{ name: 'core' }] }); + }); + }); + + it('reads the files afresh on every call', async () => { + const cwd = makeProject({ 'exadev.config.ts': unified("{ include: ['one'] }") }); + + expect(await loadSection(toolA, { cwd })).toHaveProperty('include', ['one']); + + writeProjectFile(cwd, 'exadev.config.ts', unified("{ include: ['two'] }")); + + expect(await loadSection(toolA, { cwd })).toHaveProperty('include', ['two']); + }); +}); diff --git a/src/load-section.ts b/src/load-section.ts new file mode 100644 index 0000000..c5966bd --- /dev/null +++ b/src/load-section.ts @@ -0,0 +1,47 @@ +import { join } from 'node:path'; + +import type { StandardSchemaV1 } from '@standard-schema/spec'; +import { validateStandard } from 'cosmiconfig-extends'; + +import { type ConfigFileOptions, readConfigFile, readUnifiedFile, standaloneFile, UNIFIED_FILE } from './config-file'; +import type { Section } from './section'; + +/** + * Options for {@link loadSection}. + */ +export interface LoadSectionOptions extends ConfigFileOptions { + /** + * The directory that holds the config files. + */ + readonly cwd: string; +} + +/** + * Load one tool's section, validated by the descriptor's schema. + * + * The section comes from `exadev.config.ts` (its key `section.name`) or from `.config.ts`, both in `cwd`; neither is searched for in a parent directory. Both files are evaluated with `extends` applied. A section defined in both files throws an error naming both, since choosing one silently would hide a configuration that never takes effect. The result is the schema's output, and is `undefined` when neither file defines the section. + * + * Throws `ConfigValidationError` when the section fails the schema, and a `TypeError` when the unified file exports something other than an object. + */ +export async function loadSection( + section: Section, + options: LoadSectionOptions, +): Promise | undefined> { + const unifiedFile = join(options.cwd, UNIFIED_FILE); + const standalone = standaloneFile(options.cwd, section.name); + const unified = await readUnifiedFile(unifiedFile, options); + const fromUnified: unknown = unified !== undefined && Object.hasOwn(unified, section.name) ? unified[section.name] : undefined; + const fromStandalone = await readConfigFile(standalone, options); + + if (fromUnified !== undefined && fromStandalone !== undefined) { + throw new Error(`section '${section.name}' is defined in both ${unifiedFile} and ${standalone}; keep exactly one`); + } + if (fromUnified !== undefined) { + return validateStandard(section.schema, fromUnified, `'${section.name}' section in ${unifiedFile}`); + } + if (fromStandalone !== undefined) { + return validateStandard(section.schema, fromStandalone, `'${section.name}' section in ${standalone}`); + } + + return undefined; +} From be56a284c9b3486874918e4cfcf16a2170eb55df Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 29 Sep 2026 23:12:30 +0100 Subject: [PATCH 04/23] feat: report config sections that no installed tool owns, with a command --- src/cli.ts | 11 ++ src/doctor-command.integration.test.ts | 119 ++++++++++++++++++++ src/doctor-command.ts | 72 ++++++++++++ src/doctor-command.unit.test.ts | 20 ++++ src/doctor.integration.test.ts | 150 +++++++++++++++++++++++++ src/doctor.ts | 134 ++++++++++++++++++++++ src/index.ts | 1 + 7 files changed, 507 insertions(+) create mode 100644 src/cli.ts create mode 100644 src/doctor-command.integration.test.ts create mode 100644 src/doctor-command.ts create mode 100644 src/doctor-command.unit.test.ts create mode 100644 src/doctor.integration.test.ts create mode 100644 src/doctor.ts diff --git a/src/cli.ts b/src/cli.ts new file mode 100644 index 0000000..0bef846 --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,11 @@ +#!/usr/bin/env node +import { runCommand } from './doctor-command'; + +process.exitCode = await runCommand(process.argv.slice(2), { + stdout: (text) => { + process.stdout.write(text); + }, + stderr: (text) => { + process.stderr.write(text); + }, +}); diff --git a/src/doctor-command.integration.test.ts b/src/doctor-command.integration.test.ts new file mode 100644 index 0000000..003ba9f --- /dev/null +++ b/src/doctor-command.integration.test.ts @@ -0,0 +1,119 @@ +import { join } from 'node:path'; + +import { describe, expect, it, vi } from 'vitest'; + +import { makeProject } from '../test/support/project'; + +import { EXIT_CODES, runCommand } from './doctor-command'; + +interface Run { + readonly code: number; + readonly stdout: string; + readonly stderr: string; +} + +async function run(...args: readonly string[]): Promise { + let stdout = ''; + let stderr = ''; + const code = await runCommand(args, { + stdout: (text) => { + stdout += text; + }, + stderr: (text) => { + stderr += text; + }, + }); + + return { code, stdout, stderr }; +} + +describe('runCommand', () => { + it.each([[[]], [['--help']]])('prints the usage and succeeds for %j', async (args) => { + const { code, stdout, stderr } = await run(...args); + + expect(code).toBe(EXIT_CODES.clean); + expect(stdout).toContain('Usage: exadev-config doctor [--cwd ] [--section ]...'); + expect(stderr).toBe(''); + }); + + it('fails on an unknown command, printing the usage with the error', async () => { + const { code, stdout, stderr } = await run('frobnicate'); + + expect(code).toBe(EXIT_CODES.failed); + expect(stdout).toBe(''); + expect(stderr).toMatch(/^unknown command 'frobnicate'\n\nUsage: exadev-config doctor/); + }); + + describe('doctor', () => { + it('succeeds silently when every section is owned', async () => { + const cwd = makeProject({ 'exadev.config.ts': 'export default { layout: {} };\n' }); + + expect(await run('doctor', '--cwd', cwd)).toEqual({ code: EXIT_CODES.clean, stdout: '', stderr: '' }); + }); + + it('reports each unowned section on stderr with the known ones, and exits 1', async () => { + const cwd = makeProject({ 'exadev.config.ts': 'export default { elint: {}, layout: {}, tsc: {} };\n' }); + const file = join(cwd, 'exadev.config.ts'); + + expect(await run('doctor', '--cwd', cwd)).toEqual({ + code: EXIT_CODES.unowned, + stdout: '', + stderr: [ + `${file}: section 'elint' is not owned by any installed or listed tool. Known sections: layout.`, + `${file}: section 'tsc' is not owned by any installed or listed tool. Known sections: layout.`, + '', + ].join('\n'), + }); + }); + + it('treats sections named by repeated --section as owned', async () => { + const cwd = makeProject({ 'exadev.config.ts': 'export default { elint: {}, tsc: {} };\n' }); + + expect(await run('doctor', '--cwd', cwd, '--section', 'elint', '--section', 'tsc')).toEqual({ code: EXIT_CODES.clean, stdout: '', stderr: '' }); + }); + + it('lists a section that was named to it among the known ones', async () => { + const cwd = makeProject({ 'exadev.config.ts': 'export default { elint: {}, tsc: {} };\n' }); + + expect((await run('doctor', '--cwd', cwd, '--section', 'tsc')).stderr).toContain('Known sections: layout, tsc.'); + }); + + it('resolves the current directory when --cwd is omitted', async () => { + const cwd = makeProject({ 'exadev.config.ts': 'export default { elint: {} };\n' }); + const spy = vi.spyOn(process, 'cwd').mockReturnValue(cwd); + + try { + expect((await run('doctor')).code).toBe(EXIT_CODES.unowned); + } finally { + spy.mockRestore(); + } + }); + + it('resolves a relative --cwd against the current directory', async () => { + const parent = makeProject({ 'child/exadev.config.ts': 'export default { elint: {} };\n' }); + const spy = vi.spyOn(process, 'cwd').mockReturnValue(parent); + + try { + expect((await run('doctor', '--cwd', 'child')).stderr).toContain(join(parent, 'child', 'exadev.config.ts')); + } finally { + spy.mockRestore(); + } + }); + + it.each([[['--nope']], [['stray']], [['--cwd']]])('fails on the invalid arguments %j, reporting why', async (args) => { + const { code, stdout, stderr } = await run('doctor', ...args); + + expect(code).toBe(EXIT_CODES.failed); + expect(stdout).toBe(''); + expect(stderr).not.toBe(''); + }); + + it('fails, reporting the message, when the config cannot be loaded', async () => { + const cwd = makeProject({ 'exadev.config.ts': "throw new Error('broken config');\n" }); + const { code, stderr } = await run('doctor', '--cwd', cwd); + + expect(code).toBe(EXIT_CODES.failed); + expect(stderr).toBe('broken config\n'); + }); + }); +}); diff --git a/src/doctor-command.ts b/src/doctor-command.ts new file mode 100644 index 0000000..9fc8500 --- /dev/null +++ b/src/doctor-command.ts @@ -0,0 +1,72 @@ +import { resolve } from 'node:path'; +import { parseArgs } from 'node:util'; + +import { doctor } from './doctor'; + +/** + * Process exit codes of the `exadev-config` command. + */ +export const EXIT_CODES = { clean: 0, unowned: 1, failed: 2 } as const; + +/** + * Where the command writes, so a caller can capture the output. + */ +export interface CommandOutput { + readonly stdout: (text: string) => void; + readonly stderr: (text: string) => void; +} + +const USAGE = `Usage: exadev-config doctor [--cwd ] [--section ]... + +Reports the sections in exadev.config.ts that no installed or listed tool owns. + + --cwd Directory that holds exadev.config.ts and package.json. Defaults to the current directory. + --section Treat as owned. Repeatable. + --help Show this message. + +Exit status: ${String(EXIT_CODES.clean)} when every section is owned, ${String(EXIT_CODES.unowned)} when some is not, ${String(EXIT_CODES.failed)} when the command could not run. +`; + +function messageOf(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} + +async function runDoctor(args: readonly string[], output: CommandOutput): Promise { + const { values } = parseArgs({ + args: [...args], + options: { cwd: { type: 'string' }, section: { type: 'string', multiple: true } }, + allowPositionals: false, + }); + const cwd = resolve(values.cwd ?? '.'); + const report = await doctor({ cwd, listed: values.section ?? [] }); + if (report.unowned.length === 0) { + return EXIT_CODES.clean; + } + for (const name of report.unowned) { + output.stderr(`${report.file ?? cwd}: section '${name}' is not owned by any installed or listed tool. Known sections: ${report.known.join(', ')}.\n`); + } + + return EXIT_CODES.unowned; +} + +/** + * Run the `exadev-config` command with the arguments after the program name and return the exit code. A failure to run (unknown option, unloadable config) is reported on `stderr` and returns {@link EXIT_CODES}`.failed`. + */ +export async function runCommand(args: readonly string[], output: CommandOutput): Promise { + try { + const [command, ...rest] = args; + if (command === 'doctor') { + return await runDoctor(rest, output); + } + if (command === undefined || command === '--help') { + output.stdout(USAGE); + + return EXIT_CODES.clean; + } + throw new TypeError(`unknown command '${command}'\n\n${USAGE}`); + } catch (error) { + output.stderr(`${messageOf(error)}\n`); + + return EXIT_CODES.failed; + } +} diff --git a/src/doctor-command.unit.test.ts b/src/doctor-command.unit.test.ts new file mode 100644 index 0000000..fc2da95 --- /dev/null +++ b/src/doctor-command.unit.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { EXIT_CODES, runCommand } from './doctor-command'; + +vi.mock('./doctor', () => ({ doctor: vi.fn().mockRejectedValue('plain text') })); + +describe('runCommand', () => { + it('reports the text of a thrown value that is not an error, and fails', async () => { + let stderr = ''; + const code = await runCommand(['doctor'], { + stdout: () => undefined, + stderr: (text) => { + stderr += text; + }, + }); + + expect(code).toBe(EXIT_CODES.failed); + expect(stderr).toBe('plain text\n'); + }); +}); diff --git a/src/doctor.integration.test.ts b/src/doctor.integration.test.ts new file mode 100644 index 0000000..1013195 --- /dev/null +++ b/src/doctor.integration.test.ts @@ -0,0 +1,150 @@ +import { join } from 'node:path'; + +import { describe, expect, it } from 'vitest'; + +import { makeProject } from '../test/support/project'; + +import { doctor, MANIFEST_FIELD } from './doctor'; + +function installed(name: string, manifest: Record = {}): Record { + return { [`node_modules/${name}/package.json`]: JSON.stringify({ name, version: '1.0.0', ...manifest }) }; +} + +function project(dependencies: Record, config = 'export default { layout: {} };\n'): Record { + return { 'package.json': JSON.stringify(dependencies), 'exadev.config.ts': config }; +} + +const declares = (...sections: readonly string[]): Record => ({ [MANIFEST_FIELD]: { sections } }); + +describe('doctor', () => { + it('reports nothing for a directory with no unified config, and still lists the built-in section', async () => { + expect(await doctor({ cwd: makeProject() })).toEqual({ file: undefined, defined: [], unowned: [], known: ['layout'] }); + }); + + it('reports nothing for an empty unified config', async () => { + const cwd = makeProject({ 'exadev.config.ts': 'export default undefined;\n' }); + + expect(await doctor({ cwd })).toEqual({ file: undefined, defined: [], unowned: [], known: ['layout'] }); + }); + + it('owns the layout section without any package', async () => { + const cwd = makeProject({ 'exadev.config.ts': 'export default { layout: {} };\n' }); + + expect(await doctor({ cwd })).toEqual({ file: join(cwd, 'exadev.config.ts'), defined: ['layout'], unowned: [], known: ['layout'] }); + }); + + it('reports a section no installed or listed tool owns, keeping file order', async () => { + const cwd = makeProject({ 'exadev.config.ts': 'export default { zed: 1, layout: {}, elint: {}, alpha: 2 };\n' }); + + const report = await doctor({ cwd }); + + expect(report.defined).toEqual(['zed', 'layout', 'elint', 'alpha']); + expect(report.unowned).toEqual(['zed', 'elint', 'alpha']); + }); + + it('owns a section declared by an installed dependency', async () => { + const cwd = makeProject({ + ...project({ dependencies: { 'tool-a': '1.0.0' } }, 'export default { toolA: {}, elint: {} };\n'), + ...installed('tool-a', declares('toolA', 'toolAExtra')), + }); + + const report = await doctor({ cwd }); + + expect(report.unowned).toEqual(['elint']); + expect(report.known).toEqual(['layout', 'toolA', 'toolAExtra']); + }); + + it.each(['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'])('reads the tools listed under %s', async (field) => { + const cwd = makeProject({ ...project({ [field]: { '@acme/tool': '1.0.0' } }, 'export default { tool: {} };\n'), ...installed('@acme/tool', declares('tool')) }); + + expect((await doctor({ cwd })).unowned).toEqual([]); + }); + + it('reads the tools listed under every dependency field together', async () => { + const cwd = makeProject({ + ...project({ dependencies: { a: '1' }, devDependencies: { b: '1' } }, 'export default { a: {}, b: {} };\n'), + ...installed('a', declares('a')), + ...installed('b', declares('b')), + }); + + expect((await doctor({ cwd })).unowned).toEqual([]); + }); + + it('finds a dependency installed in a parent directory', async () => { + const root = makeProject({ ...installed('tool-a', declares('toolA')), 'inner/package.json': JSON.stringify({ dependencies: { 'tool-a': '1' } }), 'inner/exadev.config.ts': 'export default { toolA: {} };\n' }); + + expect((await doctor({ cwd: join(root, 'inner') })).unowned).toEqual([]); + }); + + it('reads a manifest that the package exports map hides from a resolver', async () => { + const cwd = makeProject({ + ...project({ dependencies: { 'tool-a': '1' } }, 'export default { toolA: {} };\n'), + ...installed('tool-a', { ...declares('toolA'), exports: { '.': './index.js' } }), + }); + + expect((await doctor({ cwd })).unowned).toEqual([]); + }); + + it('ignores a dependency that is not installed and one that declares nothing', async () => { + const cwd = makeProject({ ...project({ dependencies: { missing: '1', plain: '1' } }, 'export default { x: {} };\n'), ...installed('plain') }); + + expect(await doctor({ cwd })).toMatchObject({ unowned: ['x'], known: ['layout'] }); + }); + + it('ignores a package.json whose dependency fields are not objects, and one that is not an object', async () => { + expect((await doctor({ cwd: makeProject(project({ dependencies: ['a'], devDependencies: 'b' })) })).known).toEqual(['layout']); + expect((await doctor({ cwd: makeProject({ 'package.json': '[]', 'exadev.config.ts': 'export default {};\n' }) })).known).toEqual(['layout']); + }); + + it('treats a directory with no package.json as having no installed tools', async () => { + const cwd = makeProject({ 'exadev.config.ts': 'export default { x: {} };\n', ...installed('tool-a', declares('x')) }); + + expect((await doctor({ cwd })).unowned).toEqual(['x']); + }); + + it('owns the sections it is told about', async () => { + const cwd = makeProject({ 'exadev.config.ts': 'export default { a: {}, b: {}, layout: {} };\n' }); + + const report = await doctor({ cwd, listed: ['b', 'a', 'layout'] }); + + expect(report.unowned).toEqual([]); + expect(report.known).toEqual(['a', 'b', 'layout']); + }); + + it('counts a section inherited through extends', async () => { + const cwd = makeProject({ 'preset.ts': 'export default { inherited: {} };\n', 'exadev.config.ts': "export default { extends: './preset.ts', layout: {} };\n" }); + + expect(await doctor({ cwd })).toMatchObject({ defined: ['inherited', 'layout'], unowned: ['inherited'] }); + }); + + it('rejects a unified file that does not export an object', async () => { + const cwd = makeProject({ 'exadev.config.ts': 'export default 5;\n' }); + + await expect(doctor({ cwd })).rejects.toThrow(TypeError); + }); + + it('applies the trust policy it is given to extends', async () => { + const cwd = makeProject({ + ...installed('shared'), + 'node_modules/shared/index.js': 'export default { fromShared: {} };\n', + 'node_modules/shared/package.json': JSON.stringify({ name: 'shared', type: 'module', main: 'index.js' }), + 'exadev.config.ts': "export default { extends: 'shared' };\n", + }); + + await expect(doctor({ cwd })).rejects.toThrow(/untrusted/); + expect((await doctor({ cwd, trust: () => true })).defined).toEqual(['fromShared']); + }); + + describe('a malformed manifest', () => { + it.each([ + ['a string', 'sections', '(root): expected an object'], + ['no sections key', {}, 'sections: required'], + ['an unknown key', { sections: [], other: 1 }, 'other: unknown key'], + ['a non-string section', { sections: ['a', 2] }, 'sections.1: expected a string'], + ])('with %s is an error naming the package and the problem', async (_name, manifest, problem) => { + const cwd = makeProject({ ...project({ dependencies: { 'tool-a': '1' } }), ...installed('tool-a', { [MANIFEST_FIELD]: manifest }) }); + + await expect(doctor({ cwd })).rejects.toThrow(new TypeError(`invalid '${MANIFEST_FIELD}' in the package.json of 'tool-a':\n ${problem}`)); + }); + }); +}); diff --git a/src/doctor.ts b/src/doctor.ts new file mode 100644 index 0000000..6916787 --- /dev/null +++ b/src/doctor.ts @@ -0,0 +1,134 @@ +import { readFileSync, statSync } from 'node:fs'; +import { dirname, join } from 'node:path'; + +import { type ConfigFileOptions, readUnifiedFile, UNIFIED_FILE } from './config-file'; +import { layoutSection } from './layout'; +import { array, type Check, isRecord, isString, required, strictObject } from './validation'; + +/** + * The `package.json` field through which a tool package declares the section names it owns: `"exadevConfig": { "sections": ["eslint"] }`. It is how `doctor` learns which sections an installed tool reads, without importing the tool. + */ +export const MANIFEST_FIELD = 'exadevConfig'; + +interface Manifest { + readonly sections: readonly string[]; +} + +const isManifest: Check = strictObject({ sections: required(array(isString)) }); + +const DEPENDENCY_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'] as const; + +/** + * Options for {@link doctor}. + */ +export interface DoctorOptions extends ConfigFileOptions { + /** + * The directory that holds `exadev.config.ts` and the `package.json` whose dependencies are the installed tools. + */ + readonly cwd: string; + /** + * Section names to treat as owned in addition to the built-in `layout` and those declared by installed packages, for a tool that cannot declare them itself. + */ + readonly listed?: readonly string[]; +} + +/** + * What {@link doctor} found. + */ +export interface DoctorReport { + /** + * The unified config file that was read, or `undefined` when the directory has none or the file is empty. + */ + readonly file: string | undefined; + /** + * The top-level keys the file defines after `extends` is applied, in file order. + */ + readonly defined: readonly string[]; + /** + * The keys in {@link DoctorReport.defined} that no installed or listed tool owns. Every tool ignores such a key, so it is a misspelt name or a tool that is not installed. + */ + readonly unowned: readonly string[]; + /** + * Every section name that is owned, sorted. + */ + readonly known: readonly string[]; +} + +function readJson(file: string): unknown { + const parsed: unknown = JSON.parse(readFileSync(file, 'utf8')); + + return parsed; +} + +/** + * The `package.json` of dependency `name`, found by walking up from `cwd` through `node_modules` directories, or `undefined` when it is not installed. The file is read directly, because a package's `exports` map may hide `package.json` from a resolver. + */ +function findInstalledManifest(cwd: string, name: string): unknown { + for (let directory = cwd; ; directory = dirname(directory)) { + const file = join(directory, 'node_modules', name, 'package.json'); + if (statSync(file, { throwIfNoEntry: false })?.isFile() === true) { + return readJson(file); + } + if (dirname(directory) === directory) { + return undefined; + } + } +} + +function dependencyNames(packageJson: unknown): readonly string[] { + if (!isRecord(packageJson)) { + return []; + } + + return DEPENDENCY_FIELDS.flatMap((field) => { + const dependencies = packageJson[field]; + + return isRecord(dependencies) ? Object.keys(dependencies) : []; + }); +} + +function declaredSections(dependency: string, packageJson: unknown): readonly string[] { + if (!isRecord(packageJson) || !Object.hasOwn(packageJson, MANIFEST_FIELD)) { + return []; + } + const manifest = packageJson[MANIFEST_FIELD]; + const problems: string[] = []; + if (!isManifest(manifest, [], (path, message) => { + problems.push(`${path.join('.') || '(root)'}: ${message}`); + })) { + throw new TypeError(`invalid '${MANIFEST_FIELD}' in the package.json of '${dependency}':\n${problems.map((problem) => ` ${problem}`).join('\n')}`); + } + + return manifest.sections; +} + +/** + * The section names the packages installed for the project in `cwd` declare through {@link MANIFEST_FIELD}. A dependency that is not installed, or declares none, contributes nothing. + */ +function installedSections(cwd: string): readonly string[] { + const projectFile = join(cwd, 'package.json'); + if (statSync(projectFile, { throwIfNoEntry: false })?.isFile() !== true) { + return []; + } + + return dependencyNames(readJson(projectFile)).flatMap((name) => declaredSections(name, findInstalledManifest(cwd, name))); +} + +/** + * Report the sections in the unified config file of `cwd` that no installed or listed tool owns. + * + * Typing cannot catch these: a section for a tool that is not installed, or under a misspelt name, is simply ignored by every tool. A section is owned when it is `layout`, when a dependency of the project in `cwd` declares it through {@link MANIFEST_FIELD}, or when it is in `options.listed`. + * + * Throws when the config file cannot be loaded, when it does not export an object, or when an installed dependency's {@link MANIFEST_FIELD} is malformed. + */ +export async function doctor(options: DoctorOptions): Promise { + const file = join(options.cwd, UNIFIED_FILE); + const config = await readUnifiedFile(file, options); + const known = [...new Set([layoutSection.name, ...installedSections(options.cwd), ...(options.listed ?? [])])].sort(); + if (config === undefined) { + return { file: undefined, defined: [], unowned: [], known }; + } + const defined = Object.keys(config); + + return { file, defined, unowned: defined.filter((name) => !known.includes(name)), known }; +} diff --git a/src/index.ts b/src/index.ts index c8ec801..ee1bfc1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ export type { ConfigFileOptions } from './config-file'; +export { doctor, type DoctorOptions, type DoctorReport, MANIFEST_FIELD } from './doctor'; export { type GroupSpec, type LayoutConfig, From f388a6b23984509d3ffc833d8f046a6b3642b885 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 29 Sep 2026 23:13:14 +0100 Subject: [PATCH 05/23] build: emit the command line entry as an ES module only --- src/cli.ts | 5 ++++- tsdown.config.ts | 14 ++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 0bef846..05b6eaa 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,11 +1,14 @@ #!/usr/bin/env node import { runCommand } from './doctor-command'; -process.exitCode = await runCommand(process.argv.slice(2), { +// runCommand reports its own failures through the exit code and never rejects. +void runCommand(process.argv.slice(2), { stdout: (text) => { process.stdout.write(text); }, stderr: (text) => { process.stderr.write(text); }, +}).then((code) => { + process.exitCode = code; }); diff --git a/tsdown.config.ts b/tsdown.config.ts index 00eb1b9..0b87da9 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -1,14 +1,16 @@ import { defineConfig, type UserConfig } from 'tsdown'; -const config: UserConfig = defineConfig({ - entry: ['src/index.ts', 'src/cli.ts'], +const shared: UserConfig = { root: 'src', - format: ['esm', 'cjs'], - dts: true, platform: 'node', // Keeps the .js (ESM) and .cjs (CJS) extensions package.json's exports map names; on the node platform tsdown would otherwise emit .mjs. fixedExtension: false, - clean: true, -}); +}; + +const config: UserConfig[] = defineConfig([ + { ...shared, entry: ['src/index.ts'], format: ['esm', 'cjs'], dts: true, clean: true }, + // The command line entry is only ever run as the bin, which is an ES module because the package is `type: module`, so it needs neither a CommonJS build nor declarations. + { ...shared, entry: ['src/cli.ts'], format: ['esm'], dts: false, clean: false }, +]); export default config; From fb7245b63bf59a8b985344a3dec053806918d8a8 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 29 Sep 2026 23:17:00 +0100 Subject: [PATCH 06/23] docs: describe authoring, resolution, section authoring and the doctor command --- README.md | 158 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 156 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3a10812..98c5353 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,157 @@ -# config +# @exadev/config -`@exadev/config` is the unified `exadev.config.ts` for ExaDev tools: each tool contributes a typed, schema-validated section, and the package resolves a tool's section from either the unified file or a standalone `.config.ts`, without importing any tool. +The unified `exadev.config.ts`. Each ExaDev tool contributes a typed, schema-validated section to one config file, or reads the same section from a standalone `.config.ts`, and this package imports no tool to make that work. It builds on [cosmiconfig-extends](https://github.com/ExaDev/cosmiconfig-extends), so a config file is TypeScript evaluated through jiti and can `extends` presets, under that package's trust policy. + +## Getting started + +```sh +pnpm add @exadev/config cosmiconfig +``` + +`cosmiconfig` is a peer dependency (`^9.0.0 || ^10.0.0`). The package supports Node 20 and later; cosmiconfig 10 itself declares Node `^22.18 || >=24`, so a consumer on an older Node sees an engines warning from cosmiconfig, not from this package. The package replaces the only part of cosmiconfig 10 that depends on that Node version, its `.ts` loading. + +## Authoring a config + +Each tool package exports the section it owns. The authoring file passes the sections it uses to `withSections`, which returns a `defineConfig` typed for exactly those sections: + +```ts +// exadev.config.ts +import { layoutSection, withSections } from '@exadev/config'; +import { eslint } from '@exadev/eslint-config'; + +export default withSections(layoutSection, eslint)({ + layout: { + groups: [ + { name: 'core', rank: 0 }, + { name: 'features', rank: 1, slice: { segment: 0 } }, + ], + }, + eslint: { /* the tool's own options */ }, +}); +``` + +A section is accepted exactly when its descriptor is passed, so the type system reports the mistakes a config file is likely to contain: + +- A key or value the section's schema does not allow is an error on that key, with a near-miss suggestion for a misspelt one. +- A section whose tool is installed but not passed to `withSections` is an unknown-property error on its key. +- A section whose tool is not installed is a module-not-found error on the import. +- A config that lists no sections accepts none. Without a section an empty mapped type would be `{}`, which TypeScript does not check for excess properties, so `defineConfig` accepts only the `extends` key and `withSections` needs at least one descriptor. + +`extends` names presets, resolved and merged by cosmiconfig-extends. For the unified file a preset is a whole config file, so it can supply any section. + +`defineConfig` and the function `withSections` returns hand back their argument untouched. They apply no schema defaults, because `extends` merges what a file exports and a default applied while authoring would look like a value the author wrote and override the presets it extends. Defaults belong to a section's schema, applied when a tool loads it. + +### Emitting declarations + +Under `isolatedDeclarations` a default export cannot be inferred. Name the value and annotate it with `ConfigOf`: + +```ts +import { type ConfigOf, layoutSection, withSections } from '@exadev/config'; + +const config: ConfigOf<[typeof layoutSection]> = withSections(layoutSection)({ layout: { groups: [] } }); + +export default config; +``` + +## Standalone and unified files + +A tool reads its section with `loadSection(section, { cwd })`. The section comes from either of two files in `cwd`, and neither is searched for in a parent directory: + +- `exadev.config.ts`, under the key `section.name`. +- `.config.ts`, whose default export is the section itself. + +Both are evaluated with `extends` applied, and both produce the same section for the same content. A section defined in both files throws an error naming both, since choosing one silently would hide a configuration that never takes effect. When neither file defines it the result is `undefined`. + +The result is the schema's output, so defaults the schema applies are included. A section that fails its schema throws `ConfigValidationError` from cosmiconfig-extends, naming the file, with one normalised `{ path, message }` entry per problem. A unified file that exports anything but an object throws a `TypeError`. + +`loadSection` also takes the options cosmiconfig-extends passes through: `alias` (the directory an authoring import such as `@exadev/config` resolves to, whatever the install layout), `fsCache`, `trust` (which `extends` references may load; local paths only by default) and `merge` (how a file and its presets combine; arrays replace by default). + +## Writing a section for a tool + +A tool exports a descriptor made from a name and any [Standard Schema](https://standardschema.dev) (zod, valibot, ArkType and others): + +```ts +import { defineSection, loadSection, type Section } from '@exadev/config'; +import { z } from 'zod'; + +export interface MyToolConfig { + include: string[]; + level?: 'low' | 'high'; +} + +const schema: z.ZodType = z.strictObject({ + include: z.array(z.string()), + level: z.exactOptional(z.enum(['low', 'high'])), +}); + +export const myTool: Section<'myTool', z.ZodType> = defineSection('myTool', schema); + +export function loadMyToolConfig(cwd: string): Promise { + return loadSection(myTool, { cwd }); +} +``` + +Notes for the tool author: + +- The name is both the key in the unified file and the base of the standalone file name, so it starts with a letter and contains only letters, digits, `-` and `_`. `extends` is reserved. `defineSection` throws a `TypeError` otherwise. +- Under `isolatedDeclarations` an exported schema needs a written type. Write the interface by hand and annotate the schema `z.ZodType` (output first, then input), as above, and use `z.exactOptional` for optional fields under `exactOptionalPropertyTypes`. +- Use `.strict()` schemas (`z.strictObject`) so a misspelt key in a config file fails at load time as well as in the type checker. +- To let `doctor` know which sections an installed package owns, declare them in the package's `package.json`: + + ```json + { "exadevConfig": { "sections": ["myTool"] } } + ``` + +## The layout section + +`layoutSection` is the shared `layout` section: the shape of a workspace, so the ESLint architecture rules and the conformance tool read one description. Its fields match `WorkspaceArchitectureOptions` in `@exadev/eslint-config`: + +- `root`, `packages` and `dependencyFields` say where packages are and which `package.json` fields count as dependencies. +- `groups` are the top-level directories that group packages. Each has a `name` and optionally a `path`, a `rank`, a `slice` (`{ segment: n }` or `{ namePrefix: true }`) and a `naming` strategy (`drop-group`, `keep-group` or `basename`). +- `nameRanks` and `defaultRank` rank packages by declared name, for a workspace whose layers are roles rather than directories. +- `rankSkip` limits how many ranks below itself a package may depend, with `exemptRanks` reachable from anywhere. +- `isolatedGroups` lists pairs of groups that may not depend on each other in either direction. +- `naming` gives the `scope` and `separator` of the package name convention. + +The schema rejects an unknown key at every level and checks the rules that relate one part of the layout to another: group names are unique, a rank pattern compiles as a regular expression, and an isolated pair names two different declared groups. Per-rule policy (allow lists, required files and scripts) is not part of the layout; it belongs to the section of the tool that owns the rule. + +## Finding sections nobody reads + +A section for a tool that is not installed, or under a misspelt name, is ignored by every tool, and the type system cannot see it from the tool's side. `doctor` reports such sections: + +```ts +import { doctor } from '@exadev/config'; + +const report = await doctor({ cwd: process.cwd(), listed: ['myTool'] }); +// report.unowned lists the keys of exadev.config.ts that no installed or listed tool owns. +``` + +A key is owned when it is `layout`, when a dependency of the project's `package.json` declares it in its `exadevConfig.sections`, or when it appears in `listed`. A dependency is looked for in the `node_modules` directories from `cwd` upwards. The command wraps the same function: + +```sh +exadev-config doctor [--cwd ] [--section ]... +``` + +It exits 0 when every section is owned, 1 when some is not, and 2 when it could not run. + +## Development + +Requires Node 22 or later for the toolchain and pnpm. The published package supports Node 20 and later. + +```sh +pnpm install +pnpm lint # eslint, with --fix +pnpm lint:check # eslint, check only; what CI runs +pnpm typecheck +pnpm test # vitest, with coverage +pnpm build # tsdown: ESM, CJS and declarations +pnpm test:mutation # Stryker, with a 100% break threshold +``` + +CI also runs `pnpm exec publint` and `pnpm exec attw --pack` after the build, and installs the packed tarball into a scratch project on each supported Node line and both cosmiconfig majors (`test/package/install-check.sh`), where it loads sections and runs the command as ESM and as CommonJS. + +The authoring type checks are tests: `test/types/authoring-types.integration.test.ts` compiles the files in `test/types/fixtures/cases` with the TypeScript compiler API and asserts the exact errors each one produces, so a change that loosens the types fails a test instead of passing silently. + +CI selects its runner with `ExaDev/runner-fallback-action` (self-hosted fleet first, Blacksmith as fallback). The release job stays on a GitHub-hosted runner because npm trusted publishing needs one, and publishes with provenance through OIDC, with no stored token. + +Commits follow [Conventional Commits](https://www.conventionalcommits.org); semantic-release derives the version from them. From 3e692e9aa3aa42b883e5a73bbf45e1e4027d73cd Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 29 Sep 2026 23:17:03 +0100 Subject: [PATCH 07/23] ci: run the packaged-install check from a script that also runs the command --- .github/workflows/ci.yml | 32 +++----------------------------- eslint.config.ts | 2 ++ test/package/check.cjs | 12 ++++++++++++ test/package/check.mjs | 27 +++++++++++++++++++++++++++ test/package/install-check.sh | 27 +++++++++++++++++++++++++++ 5 files changed, 71 insertions(+), 29 deletions(-) create mode 100644 test/package/check.cjs create mode 100644 test/package/check.mjs create mode 100644 test/package/install-check.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 710060a..aaebfff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -115,7 +115,7 @@ jobs: path: reports/stryker-incremental.json key: stryker-incremental-${{ github.run_id }} - # The packed tarball, not the source tree, is what consumers install: this proves the published files, exports map and peer dependency work in a clean project on every Node line the package supports and on both cosmiconfig majors. The toolchain needs Node 22, so the package is built and packed on 22 and the consumer check runs on the Node under test. + # The packed tarball, not the source tree, is what consumers install: this proves the published files, exports map, command line entry and peer dependency work in a clean project on every Node line the package supports and on both cosmiconfig majors. The toolchain needs Node 22, so the package is built and packed on 22 and the consumer check runs on the Node under test. package: name: Package (Node ${{ matrix.node }}, cosmiconfig ${{ matrix.cosmiconfig }}) needs: determine-runner @@ -143,34 +143,8 @@ jobs: - uses: actions/setup-node@v7 with: node-version: ${{ matrix.node }} - - name: Install the tarball into a scratch project - run: | - mkdir "$RUNNER_TEMP/scratch" - cd "$RUNNER_TEMP/scratch" - npm init -y - npm install "$RUNNER_TEMP"/pack/*.tgz "cosmiconfig@${{ matrix.cosmiconfig }}" - - name: Load a config with extends through the installed package, as ESM and as CommonJS - working-directory: ${{ runner.temp }}/scratch - run: | - printf 'export default { a: "base", b: "base" };\n' > base.ts - printf 'export default { extends: "./base.ts", b: "config" };\n' > my-tool.config.ts - cat > check.mjs <<'EOF' - import assert from 'node:assert/strict'; - import { createExplorer } from 'cosmiconfig-extends'; - - const result = await createExplorer('my-tool').search(process.cwd()); - assert.deepEqual(result?.config, { a: 'base', b: 'config' }); - EOF - cat > check.cjs <<'EOF' - const assert = require('node:assert/strict'); - const { createExplorer } = require('cosmiconfig-extends'); - - createExplorer('my-tool') - .search(process.cwd()) - .then((result) => assert.deepEqual(result?.config, { a: 'base', b: 'config' })); - EOF - node check.mjs - node check.cjs + - name: Install the tarball into a scratch project and load sections through it, as ESM and as CommonJS + run: bash test/package/install-check.sh "$RUNNER_TEMP/pack" "${{ matrix.cosmiconfig }}" "$RUNNER_TEMP/scratch" required-checks: name: Required Checks diff --git a/eslint.config.ts b/eslint.config.ts index cade828..88a8109 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -12,6 +12,8 @@ const config: Config[] = defineConfig( '.stryker-tmp', // Fixtures the type-level tests compile on purpose to observe their errors; the project's own typecheck and lint must not see them. 'test/types/fixtures', + // Plain Node scripts that run in a scratch project against the installed tarball, outside this project's TypeScript program. + 'test/package', ], }, { diff --git a/test/package/check.cjs b/test/package/check.cjs new file mode 100644 index 0000000..65d3ff2 --- /dev/null +++ b/test/package/check.cjs @@ -0,0 +1,12 @@ +// Runs in the same scratch project as check.mjs and loads sections through the installed package as CommonJS. +const assert = require('node:assert/strict'); +const { defineSection, layoutSection, loadSection, withSections } = require('@exadev/config'); +const { z } = require('zod'); + +const toolA = defineSection('toolA', z.strictObject({ include: z.array(z.string()) })); + +assert.equal(withSections(toolA).length, 1); +Promise.all([loadSection(layoutSection, { cwd: process.cwd() }), loadSection(toolA, { cwd: process.cwd() })]).then(([layout, section]) => { + assert.deepEqual(layout, { groups: [{ name: 'core', rank: 0 }] }); + assert.deepEqual(section, { include: ['src'] }); +}); diff --git a/test/package/check.mjs b/test/package/check.mjs new file mode 100644 index 0000000..83de1a5 --- /dev/null +++ b/test/package/check.mjs @@ -0,0 +1,27 @@ +// Runs in a scratch project that has the packed tarball, cosmiconfig and zod installed. It loads sections through the installed package as an ES module. +import assert from 'node:assert/strict'; +import { execFileSync } from 'node:child_process'; +import { defineSection, doctor, layoutSection, loadSection } from '@exadev/config'; +import { z } from 'zod'; + +const toolA = defineSection('toolA', z.strictObject({ include: z.array(z.string()) })); +const cwd = process.cwd(); + +assert.deepEqual(await loadSection(layoutSection, { cwd }), { groups: [{ name: 'core', rank: 0 }] }); +assert.deepEqual(await loadSection(toolA, { cwd }), { include: ['src'] }); + +const report = await doctor({ cwd, listed: ['toolA'] }); +assert.deepEqual(report.unowned, ['typo']); + +const run = (args) => { + try { + execFileSync('node_modules/.bin/exadev-config', args, { stdio: 'pipe' }); + + return 0; + } catch (error) { + return error.status; + } +}; +assert.equal(run(['doctor', '--section', 'toolA', '--section', 'typo']), 0); +assert.equal(run(['doctor', '--section', 'toolA']), 1); +assert.equal(run(['unknown']), 2); diff --git a/test/package/install-check.sh b/test/package/install-check.sh new file mode 100644 index 0000000..a1650da --- /dev/null +++ b/test/package/install-check.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Installs the packed tarball into a scratch project and loads sections through it as ESM and as CommonJS. +# Usage: install-check.sh +set -euo pipefail + +pack_dir=$1 +cosmiconfig_major=$2 +scratch=$3 +here=$(cd "$(dirname "$0")" && pwd) + +mkdir -p "$scratch" +cd "$scratch" +npm init -y > /dev/null +npm install "$pack_dir"/*.tgz "cosmiconfig@$cosmiconfig_major" zod + +cp "$here/check.mjs" "$here/check.cjs" . +# The file is evaluated, not type-checked: it imports the installed package as an authoring file does, and defines a section no tool owns. +cat > exadev.config.ts <<'CONFIG' +import { layoutSection, withSections } from '@exadev/config'; + +const define = withSections(layoutSection); + +export default { ...define({ layout: { groups: [{ name: 'core', rank: 0 }] } }), toolA: { include: ['src'] }, typo: {} }; +CONFIG + +node check.mjs +node check.cjs From f183635eff6b43a859b70cb0bb97494d35916aac Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 29 Sep 2026 23:17:14 +0100 Subject: [PATCH 08/23] docs: use a neutral tool in the authoring example --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 98c5353..68ce2d9 100644 --- a/README.md +++ b/README.md @@ -17,16 +17,16 @@ Each tool package exports the section it owns. The authoring file passes the sec ```ts // exadev.config.ts import { layoutSection, withSections } from '@exadev/config'; -import { eslint } from '@exadev/eslint-config'; +import { myTool } from 'my-tool'; -export default withSections(layoutSection, eslint)({ +export default withSections(layoutSection, myTool)({ layout: { groups: [ { name: 'core', rank: 0 }, { name: 'features', rank: 1, slice: { segment: 0 } }, ], }, - eslint: { /* the tool's own options */ }, + myTool: { include: ['src'] }, }); ``` From 03bbb5372b21215d341ac24430ec972540cc2cee Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 29 Sep 2026 23:31:55 +0100 Subject: [PATCH 09/23] fix: refuse section descriptors that widen or collide at the type level A descriptor whose name type is string (a bare Section annotation) gave the section map a string index signature, which switched off the excess-property check for every key of the config. withSections now rejects such a descriptor, one named after an envelope key, two that share a name, and an empty list, each with a message stating the reason. defineSection rejects an envelope key name in the tool's own build. --- src/section.ts | 48 +++++++++++++++++-- src/section.unit.test.ts | 16 ++++++- .../types/authoring-types.integration.test.ts | 15 +++++- .../fixtures/cases/bare-section-descriptor.ts | 8 ++++ .../fixtures/cases/duplicate-section-name.ts | 8 ++++ .../fixtures/cases/envelope-named-section.ts | 5 ++ .../fixtures/cases/wide-name-descriptor.ts | 9 ++++ 7 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 test/types/fixtures/cases/bare-section-descriptor.ts create mode 100644 test/types/fixtures/cases/duplicate-section-name.ts create mode 100644 test/types/fixtures/cases/envelope-named-section.ts create mode 100644 test/types/fixtures/cases/wide-name-descriptor.ts diff --git a/src/section.ts b/src/section.ts index 8ffcb0f..74c3626 100644 --- a/src/section.ts +++ b/src/section.ts @@ -44,6 +44,48 @@ export type SectionsOf = { */ export type ConfigOf = Config>; +/** + * A type that no value has, used where an argument must be refused: the compiler quotes `Reason` in its error message, so the reason is what the author reads. + */ +export interface Rejected { + readonly rejected: Reason; +} + +/** + * The names of the {@link Envelope} keys, which a section may not take. + */ +export type EnvelopeKey = keyof Envelope; + +/** + * What a section name must be besides a string: `unknown` (no further constraint) unless it is an {@link Envelope} key, which a section may not take because the key already means something else. + */ +type Nameable = Name extends EnvelopeKey ? Rejected<'a section may not be named after a key of the config envelope'> : unknown; + +/** + * The literal names of every descriptor in `Descriptors` except the one at `Index`. A name that is not a literal is left out, because `string` would otherwise be reported as a duplicate of every other name; it is rejected on its own. + */ +type OtherNames = { + [Position in keyof Descriptors]: Position extends Index ? never : string extends Descriptors[Position]['name'] ? never : Descriptors[Position]['name']; +}[number]; + +/** + * `unknown` (no further constraint) unless `Descriptors` is empty. With no sections the mapped part of {@link Config} would be `{}`, so {@link withSections} refuses an empty list. + */ +type NonEmpty = Descriptors extends readonly [] ? Rejected<'list at least one section, or use defineConfig for a config with none'> : unknown; + +/** + * What an argument of {@link withSections} must be besides a descriptor: `unknown` (no further constraint) when the descriptor can be listed, otherwise a {@link Rejected} that says why not. + * + * A name that is not a literal (`string`, from a descriptor annotated with a wide {@link Section} type) would give {@link SectionsOf} a string index signature, which turns off the excess-property check for the whole config. A name that is an {@link Envelope} key, or is shared by two descriptors, would give one key two meanings. + */ +export type Listable = string extends Descriptor['name'] + ? Rejected<'a listed section needs a literal name type: annotate the descriptor as Section<"name", Schema> or leave it inferred'> + : Descriptor['name'] extends EnvelopeKey + ? Rejected<'a section may not be named after a key of the config envelope'> + : Descriptor['name'] extends OtherNames + ? Rejected<'a section name may be listed only once'> + : unknown; + const SECTION_NAME = /^[A-Za-z][A-Za-z0-9_-]*$/; const ENVELOPE_KEYS: readonly string[] = ['extends']; @@ -51,7 +93,7 @@ const ENVELOPE_KEYS: readonly string[] = ['extends']; * Describe a tool's section. `name` is both the key in the unified config and the base of the standalone file name `.config.ts`, so it must start with a letter and contain only letters, digits, `-` and `_`, and it must not be a key of the {@link Envelope}. Throws a `TypeError` otherwise. */ export function defineSection( - name: Name, + name: Name & Nameable, schema: Schema, ): Section { if (!SECTION_NAME.test(name)) { @@ -73,8 +115,8 @@ export function defineSection( - ...sections: Descriptors +export function withSections( + ...sections: Descriptors & NonEmpty & { readonly [Index in keyof Descriptors]: Listable } ): (config: ConfigOf) => ConfigOf { const names = new Set(); for (const { name } of sections) { diff --git a/src/section.unit.test.ts b/src/section.unit.test.ts index ebd4d32..ff6a43e 100644 --- a/src/section.unit.test.ts +++ b/src/section.unit.test.ts @@ -16,6 +16,13 @@ const toolAConfigSchema: z.ZodType = z.strictObject({ const toolA: Section<'toolA', z.ZodType> = defineSection('toolA', toolAConfigSchema); const toolB = defineSection('toolB', z.strictObject({ rules: z.record(z.string(), z.string()) })); +/** + * The same string with its literal type widened, so a call the compiler refuses for the literal reaches the runtime check. + */ +function widened(name: string): string { + return name; +} + describe('defineSection', () => { it('pairs the name with the schema it was given', () => { expect(toolA.name).toBe('toolA'); @@ -40,7 +47,7 @@ describe('defineSection', () => { ); it('rejects the name of an envelope key', () => { - expect(() => defineSection('extends', toolAConfigSchema)).toThrow(new TypeError("invalid section name 'extends': it is a key of the config envelope")); + expect(() => defineSection(widened('extends'), toolAConfigSchema)).toThrow(new TypeError("invalid section name 'extends': it is a key of the config envelope")); }); }); @@ -58,7 +65,12 @@ describe('withSections', () => { }); it('rejects a section listed twice, naming it', () => { - expect(() => withSections(toolA, toolB, defineSection('toolA', toolAConfigSchema))).toThrow(new TypeError("section 'toolA' is listed more than once")); + // Called through Reflect.apply because the compiler already refuses this call; the runtime check is for callers it does not protect. + const listed: readonly Section[] = [toolA, toolB, defineSection('toolA', toolAConfigSchema)]; + + expect(() => { + Reflect.apply(withSections, undefined, listed); + }).toThrow(new TypeError("section 'toolA' is listed more than once")); }); it('types the config from the listed descriptors', () => { diff --git a/test/types/authoring-types.integration.test.ts b/test/types/authoring-types.integration.test.ts index eb6b58c..785cede 100644 --- a/test/types/authoring-types.integration.test.ts +++ b/test/types/authoring-types.integration.test.ts @@ -120,7 +120,20 @@ describe('an authoring file', () => { }); it('rejects listing no descriptors at all', () => { - expectErrors(findingsIn(plain, 'cases/empty-map-with-sections'), [{ line: 3, code: 2555, message: 'Expected at least 1 arguments' }]); + expectErrors(findingsIn(plain, 'cases/empty-map-with-sections'), [{ line: 3, code: 2345, message: 'list at least one section' }]); + }); + + it('rejects a descriptor whose name is not a literal, which would switch off the unknown-key check for every section', () => { + expectErrors(findingsIn(plain, 'cases/wide-name-descriptor'), [{ line: 6, code: 2345, message: 'a listed section needs a literal name type' }]); + expectErrors(findingsIn(plain, 'cases/bare-section-descriptor'), [{ line: 6, code: 2345, message: 'a listed section needs a literal name type' }]); + }); + + it('rejects a section named after an envelope key', () => { + expectErrors(findingsIn(plain, 'cases/envelope-named-section'), [{ line: 5, code: 2345, message: 'a section may not be named after a key of the config envelope' }]); + }); + + it('rejects two descriptors that share a name', () => { + expectErrors(findingsIn(plain, 'cases/duplicate-section-name'), [{ line: 6, code: 2345, message: 'a section name may be listed only once' }]); }); it('reports a tool whose package is not installed as a module that cannot be found', () => { diff --git a/test/types/fixtures/cases/bare-section-descriptor.ts b/test/types/fixtures/cases/bare-section-descriptor.ts new file mode 100644 index 0000000..9c1f2b9 --- /dev/null +++ b/test/types/fixtures/cases/bare-section-descriptor.ts @@ -0,0 +1,8 @@ +import { defineSection, type Section, withSections } from '../../../../src/index'; +import { toolA, toolAConfigSchema } from '../tool-a'; + +const bare: Section = defineSection('bareTool', toolAConfigSchema); + +export const config = withSections(toolA, bare)({ + toolA: { include: ['src'] }, +}); diff --git a/test/types/fixtures/cases/duplicate-section-name.ts b/test/types/fixtures/cases/duplicate-section-name.ts new file mode 100644 index 0000000..dfb9cbc --- /dev/null +++ b/test/types/fixtures/cases/duplicate-section-name.ts @@ -0,0 +1,8 @@ +import { defineSection, withSections } from '../../../../src/index'; +import { toolA, toolAConfigSchema } from '../tool-a'; + +const duplicate = defineSection('toolA', toolAConfigSchema); + +export const config = withSections(toolA, duplicate)({ + toolA: { include: ['src'] }, +}); diff --git a/test/types/fixtures/cases/envelope-named-section.ts b/test/types/fixtures/cases/envelope-named-section.ts new file mode 100644 index 0000000..4028283 --- /dev/null +++ b/test/types/fixtures/cases/envelope-named-section.ts @@ -0,0 +1,5 @@ +import { defineSection } from '../../../../src/index'; +import { toolAConfigSchema } from '../tool-a'; + +// The runtime refuses this name too; the compiler refuses it in the tool's own build. +export const reserved = defineSection('extends', toolAConfigSchema); diff --git a/test/types/fixtures/cases/wide-name-descriptor.ts b/test/types/fixtures/cases/wide-name-descriptor.ts new file mode 100644 index 0000000..f704d71 --- /dev/null +++ b/test/types/fixtures/cases/wide-name-descriptor.ts @@ -0,0 +1,9 @@ +import { defineSection, type Section, withSections } from '../../../../src/index'; +import { toolA, toolAConfigSchema } from '../tool-a'; + +const wide: Section = defineSection('wideTool', toolAConfigSchema); + +export const config = withSections(toolA, wide)({ + toolA: { include: ['src'] }, + toola: { include: ['src'] }, +}); From 05151190b7ec59fe873567b2a7b984cf5a8aabe4 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 29 Sep 2026 23:33:50 +0100 Subject: [PATCH 10/23] fix: fail on a config file that exports no default or a null default cosmiconfig-extends hands back the whole module namespace when a module has no default export, and null for a null default. Both then read as a config that defines nothing, so loadSection returned undefined and doctor reported a clean directory for a configuration that was never applied. The explorer's TypeScript loaders now refuse both; an empty file or a default export of undefined remains an empty config. --- README.md | 2 +- src/config-file.ts | 37 +++++++++++++++++++++++++--- src/doctor.integration.test.ts | 4 +++ src/load-section.integration.test.ts | 20 +++++++++++++++ 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 68ce2d9..c756fe5 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ A tool reads its section with `loadSection(section, { cwd })`. The section comes Both are evaluated with `extends` applied, and both produce the same section for the same content. A section defined in both files throws an error naming both, since choosing one silently would hide a configuration that never takes effect. When neither file defines it the result is `undefined`. -The result is the schema's output, so defaults the schema applies are included. A section that fails its schema throws `ConfigValidationError` from cosmiconfig-extends, naming the file, with one normalised `{ path, message }` entry per problem. A unified file that exports anything but an object throws a `TypeError`. +The result is the schema's output, so defaults the schema applies are included. A section that fails its schema throws `ConfigValidationError` from cosmiconfig-extends, naming the file, with one normalised `{ path, message }` entry per problem. A unified file that exports anything but an object throws a `TypeError`, and so does any config file with no default export or a `null` one, since a configuration that never takes effect must not read as an absent file. Only an empty file, or one whose default export is `undefined`, is an empty config. `loadSection` also takes the options cosmiconfig-extends passes through: `alias` (the directory an authoring import such as `@exadev/config` resolves to, whatever the install layout), `fsCache`, `trust` (which `extends` references may load; local paths only by default) and `merge` (how a file and its presets combine; arrays replace by default). diff --git a/src/config-file.ts b/src/config-file.ts index 27d7bec..c267fc4 100644 --- a/src/config-file.ts +++ b/src/config-file.ts @@ -1,7 +1,8 @@ import { statSync } from 'node:fs'; import { join } from 'node:path'; -import { createExplorer, type ExplorerOptions } from 'cosmiconfig-extends'; +import type { Loader } from 'cosmiconfig'; +import { createExplorer, createJitiLoader, type ExplorerOptions } from 'cosmiconfig-extends'; import { isRecord } from './validation'; @@ -23,13 +24,43 @@ export function standaloneFile(cwd: string, name: string): string { } /** - * The evaluated content of the config file at `file` after `extends` is applied, or `undefined` when the file does not exist or is empty. Every call reads the file afresh. + * The own, non-enumerable property jiti defines on the namespace of a module transpiled from ES module syntax. A namespace that has it and no `default` key is a module that exports nothing as its default; a CommonJS `module.exports` object does not have it. + */ +const ES_MODULE_MARKER = '__esModule'; + +function isNamespaceWithoutDefault(exported: unknown): boolean { + return isRecord(exported) && Object.getOwnPropertyDescriptor(exported, ES_MODULE_MARKER)?.value === true; +} + +/** + * Wrap a loader so a config file that exports no configuration fails, instead of reading as an absent file. cosmiconfig-extends hands back the whole module namespace when a module has no default export, and `null` when the default export is `null`; either would otherwise reach the caller as a config that defines nothing, so a configuration that never takes effect would go unnoticed. A default export of `undefined` is deliberately an empty config. + */ +function requireDefaultExport(loader: Loader): Loader { + return async (filepath, content) => { + const exported: unknown = await loader(filepath, content); + if (exported === null) { + throw new TypeError(`${filepath}: the default export is null; export an object, or undefined for an empty config`); + } + if (isNamespaceWithoutDefault(exported)) { + throw new TypeError(`${filepath}: the file has no default export; write the config as \`export default\``); + } + + return exported; + }; +} + +/** + * The evaluated content of the config file at `file` after `extends` is applied, or `undefined` when the file does not exist, is empty, or has `undefined` as its default export. Throws when the file does not export a default value. Every call reads the file afresh. */ export async function readConfigFile(file: string, options: ConfigFileOptions): Promise { if (statSync(file, { throwIfNoEntry: false })?.isFile() !== true) { return undefined; } - const explorer = createExplorer('exadev', { ...options, cosmiconfig: { cache: false } }); + const checked = requireDefaultExport(createJitiLoader(options).loader); + const explorer = createExplorer('exadev', { + ...options, + cosmiconfig: { cache: false, loaders: { '.ts': checked, '.mts': checked, '.cts': checked } }, + }); const result = await explorer.load(file); return result?.config; diff --git a/src/doctor.integration.test.ts b/src/doctor.integration.test.ts index 1013195..0938b38 100644 --- a/src/doctor.integration.test.ts +++ b/src/doctor.integration.test.ts @@ -123,6 +123,10 @@ describe('doctor', () => { await expect(doctor({ cwd })).rejects.toThrow(TypeError); }); + it.each(['export default null;\n', 'export const config = { layout: {} };\n'])('rejects a unified file that exports no configuration: %j', async (content) => { + await expect(doctor({ cwd: makeProject({ 'exadev.config.ts': content }) })).rejects.toThrow(TypeError); + }); + it('applies the trust policy it is given to extends', async () => { const cwd = makeProject({ ...installed('shared'), diff --git a/src/load-section.integration.test.ts b/src/load-section.integration.test.ts index defe948..e5d7980 100644 --- a/src/load-section.integration.test.ts +++ b/src/load-section.integration.test.ts @@ -75,6 +75,26 @@ describe('loadSection', () => { expect(await loadSection(toolA, { cwd: makeProject({ 'toolA.config.ts': 'export default undefined;\n' }) })).toBeUndefined(); }); + it('returns undefined for a file with no content', async () => { + expect(await loadSection(toolA, { cwd: makeProject({ 'exadev.config.ts': '' }) })).toBeUndefined(); + expect(await loadSection(toolA, { cwd: makeProject({ 'toolA.config.ts': '' }) })).toBeUndefined(); + }); + + const NULL_EXPORT = 'the default export is null; export an object, or undefined for an empty config'; + const NO_DEFAULT = 'the file has no default export; write the config as `export default`'; + + it.each([ + ['exadev.config.ts', 'export default null;\n', NULL_EXPORT], + ['exadev.config.ts', 'export const config = { toolA: { include: [] } };\n', NO_DEFAULT], + ['exadev.config.ts', 'export {};\n', NO_DEFAULT], + ['toolA.config.ts', 'export default null;\n', NULL_EXPORT], + ['toolA.config.ts', 'export const config = { include: [] };\n', NO_DEFAULT], + ])('fails on %s that exports no configuration: %j', async (file, content, message) => { + const cwd = makeProject({ [file]: content }); + + await expect(loadSection(toolA, { cwd })).rejects.toMatchObject({ name: 'TypeError', message: `${join(cwd, file)}: ${message}` }); + }); + it('does not take a section from the prototype of the unified config', async () => { const constructorSection = defineSection('constructor', z.unknown()); const cwd = makeProject({ 'exadev.config.ts': 'export default {};\n' }); From 6a16cb4358d18d5355f60d4375b7de653f226b28 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 29 Sep 2026 23:34:32 +0100 Subject: [PATCH 11/23] fix: fail when the directory to read config from does not exist loadSection returned undefined for a nonexistent directory or one that is a file, and doctor and the exadev-config command reported success for a mistyped --cwd, so the check could never go red for that input. Both now throw, and the command exits with the failure status. --- src/config-file.ts | 9 +++++++++ src/doctor-command.integration.test.ts | 6 ++++++ src/doctor.integration.test.ts | 6 ++++++ src/doctor.ts | 5 +++-- src/load-section.integration.test.ts | 8 ++++++++ src/load-section.ts | 5 +++-- 6 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/config-file.ts b/src/config-file.ts index c267fc4..964da0c 100644 --- a/src/config-file.ts +++ b/src/config-file.ts @@ -16,6 +16,15 @@ export const UNIFIED_FILE = 'exadev.config.ts'; */ export type ConfigFileOptions = Pick; +/** + * Throw unless `directory` is an existing directory, so a mistyped directory is an error rather than a directory with no config. + */ +export function assertDirectory(directory: string): void { + if (statSync(directory, { throwIfNoEntry: false })?.isDirectory() !== true) { + throw new Error(`${directory} is not an existing directory`); + } +} + /** * The path of the standalone config file for the tool that owns section `name`. */ diff --git a/src/doctor-command.integration.test.ts b/src/doctor-command.integration.test.ts index 003ba9f..f24a124 100644 --- a/src/doctor-command.integration.test.ts +++ b/src/doctor-command.integration.test.ts @@ -108,6 +108,12 @@ describe('runCommand', () => { expect(stderr).not.toBe(''); }); + it('fails, naming the directory, when --cwd is not an existing directory', async () => { + const missing = join(makeProject(), 'nope-typo'); + + expect(await run('doctor', '--cwd', missing)).toEqual({ code: EXIT_CODES.failed, stdout: '', stderr: `${missing} is not an existing directory\n` }); + }); + it('fails, reporting the message, when the config cannot be loaded', async () => { const cwd = makeProject({ 'exadev.config.ts': "throw new Error('broken config');\n" }); const { code, stderr } = await run('doctor', '--cwd', cwd); diff --git a/src/doctor.integration.test.ts b/src/doctor.integration.test.ts index 0938b38..bc26c57 100644 --- a/src/doctor.integration.test.ts +++ b/src/doctor.integration.test.ts @@ -21,6 +21,12 @@ describe('doctor', () => { expect(await doctor({ cwd: makeProject() })).toEqual({ file: undefined, defined: [], unowned: [], known: ['layout'] }); }); + it('fails when cwd is not an existing directory, instead of reporting a clean one', async () => { + const missing = join(makeProject(), 'nope'); + + await expect(doctor({ cwd: missing })).rejects.toThrow(new Error(`${missing} is not an existing directory`)); + }); + it('reports nothing for an empty unified config', async () => { const cwd = makeProject({ 'exadev.config.ts': 'export default undefined;\n' }); diff --git a/src/doctor.ts b/src/doctor.ts index 6916787..247bad7 100644 --- a/src/doctor.ts +++ b/src/doctor.ts @@ -1,7 +1,7 @@ import { readFileSync, statSync } from 'node:fs'; import { dirname, join } from 'node:path'; -import { type ConfigFileOptions, readUnifiedFile, UNIFIED_FILE } from './config-file'; +import { assertDirectory, type ConfigFileOptions, readUnifiedFile, UNIFIED_FILE } from './config-file'; import { layoutSection } from './layout'; import { array, type Check, isRecord, isString, required, strictObject } from './validation'; @@ -119,9 +119,10 @@ function installedSections(cwd: string): readonly string[] { * * Typing cannot catch these: a section for a tool that is not installed, or under a misspelt name, is simply ignored by every tool. A section is owned when it is `layout`, when a dependency of the project in `cwd` declares it through {@link MANIFEST_FIELD}, or when it is in `options.listed`. * - * Throws when the config file cannot be loaded, when it does not export an object, or when an installed dependency's {@link MANIFEST_FIELD} is malformed. + * Throws when `cwd` is not an existing directory, when the config file cannot be loaded, when it does not export an object, or when an installed dependency's {@link MANIFEST_FIELD} is malformed. */ export async function doctor(options: DoctorOptions): Promise { + assertDirectory(options.cwd); const file = join(options.cwd, UNIFIED_FILE); const config = await readUnifiedFile(file, options); const known = [...new Set([layoutSection.name, ...installedSections(options.cwd), ...(options.listed ?? [])])].sort(); diff --git a/src/load-section.integration.test.ts b/src/load-section.integration.test.ts index e5d7980..228bdc7 100644 --- a/src/load-section.integration.test.ts +++ b/src/load-section.integration.test.ts @@ -102,6 +102,14 @@ describe('loadSection', () => { expect(await loadSection(constructorSection, { cwd })).toBeUndefined(); }); + it('fails when cwd is not an existing directory, instead of reporting an absent section', async () => { + const cwd = makeProject({ 'exadev.config.ts': unified() }); + const missing = join(cwd, 'does-not-exist'); + + await expect(loadSection(toolA, { cwd: missing })).rejects.toThrow(new Error(`${missing} is not an existing directory`)); + await expect(loadSection(toolA, { cwd: join(cwd, 'exadev.config.ts') })).rejects.toThrow('is not an existing directory'); + }); + it('does not search parent directories', async () => { const parent = makeProject({ 'exadev.config.ts': unified() }); writeProjectFile(parent, 'child/package.json', '{}'); diff --git a/src/load-section.ts b/src/load-section.ts index c5966bd..d192788 100644 --- a/src/load-section.ts +++ b/src/load-section.ts @@ -3,7 +3,7 @@ import { join } from 'node:path'; import type { StandardSchemaV1 } from '@standard-schema/spec'; import { validateStandard } from 'cosmiconfig-extends'; -import { type ConfigFileOptions, readConfigFile, readUnifiedFile, standaloneFile, UNIFIED_FILE } from './config-file'; +import { assertDirectory, type ConfigFileOptions, readConfigFile, readUnifiedFile, standaloneFile, UNIFIED_FILE } from './config-file'; import type { Section } from './section'; /** @@ -21,12 +21,13 @@ export interface LoadSectionOptions extends ConfigFileOptions { * * The section comes from `exadev.config.ts` (its key `section.name`) or from `.config.ts`, both in `cwd`; neither is searched for in a parent directory. Both files are evaluated with `extends` applied. A section defined in both files throws an error naming both, since choosing one silently would hide a configuration that never takes effect. The result is the schema's output, and is `undefined` when neither file defines the section. * - * Throws `ConfigValidationError` when the section fails the schema, and a `TypeError` when the unified file exports something other than an object. + * Throws `ConfigValidationError` when the section fails the schema, a `TypeError` when the unified file exports something other than an object, and an `Error` when `cwd` is not an existing directory. */ export async function loadSection( section: Section, options: LoadSectionOptions, ): Promise | undefined> { + assertDirectory(options.cwd); const unifiedFile = join(options.cwd, UNIFIED_FILE); const standalone = standaloneFile(options.cwd, section.name); const unified = await readUnifiedFile(unifiedFile, options); From 1b5e031cbbb44503a83628c13f54a45a1b0bee29 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 29 Sep 2026 23:35:59 +0100 Subject: [PATCH 12/23] fix: namespace the standalone file and read .mts and .cts config files The standalone file .config.ts is the native config file of many tools, so a section called eslint or vitest read the tool's own config as its section, or failed as defined in both files. The standalone file is now exadev..config.ts, which no tool owns. The .mts and .cts extensions that cosmiconfig-extends loads were silently ignored. Both file kinds now accept .ts, .mts and .cts, and a file present under more than one extension throws. The defined-in-both error says the unified side may come through extends, since the preset is not known at that point. --- README.md | 10 ++-- src/config-file.ts | 36 ++++++++---- src/doctor.integration.test.ts | 12 ++++ src/doctor.ts | 12 ++-- src/load-section.integration.test.ts | 82 +++++++++++++++++++++++----- src/load-section.ts | 62 +++++++++++++++------ src/section.ts | 2 +- 7 files changed, 163 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index c756fe5..ca639bf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # @exadev/config -The unified `exadev.config.ts`. Each ExaDev tool contributes a typed, schema-validated section to one config file, or reads the same section from a standalone `.config.ts`, and this package imports no tool to make that work. It builds on [cosmiconfig-extends](https://github.com/ExaDev/cosmiconfig-extends), so a config file is TypeScript evaluated through jiti and can `extends` presets, under that package's trust policy. +The unified `exadev.config.ts`. Each ExaDev tool contributes a typed, schema-validated section to one config file, or reads the same section from a standalone `exadev..config.ts`, and this package imports no tool to make that work. It builds on [cosmiconfig-extends](https://github.com/ExaDev/cosmiconfig-extends), so a config file is TypeScript evaluated through jiti and can `extends` presets, under that package's trust policy. ## Getting started @@ -55,12 +55,14 @@ export default config; ## Standalone and unified files -A tool reads its section with `loadSection(section, { cwd })`. The section comes from either of two files in `cwd`, and neither is searched for in a parent directory: +A tool reads its section with `loadSection(section, { cwd })`. `cwd` must be an existing directory, and a mistyped one throws instead of reading as a directory with no config. The section comes from either of two files in `cwd`, and neither is searched for in a parent directory: - `exadev.config.ts`, under the key `section.name`. -- `.config.ts`, whose default export is the section itself. +- `exadev..config.ts`, whose default export is the section itself. -Both are evaluated with `extends` applied, and both produce the same section for the same content. A section defined in both files throws an error naming both, since choosing one silently would hide a configuration that never takes effect. When neither file defines it the result is `undefined`. +The standalone name is namespaced because the plain `.config.ts` is the native config file of many tools (`eslint.config.ts`, `vitest.config.ts`), so a section called `eslint` would otherwise read the ESLint flat config as its own. Either file may use the extension `.mts` or `.cts` instead of `.ts`; a file that exists under more than one extension throws, since choosing one silently would hide a configuration that never takes effect. Other extensions, and the plain `.config.ts`, are never read. + +Both are evaluated with `extends` applied, and both produce the same section for the same content. A section defined in both files (the unified file counts a section a preset supplies) throws an error naming both, since choosing one silently would hide a configuration that never takes effect. When neither file defines it the result is `undefined`. The result is the schema's output, so defaults the schema applies are included. A section that fails its schema throws `ConfigValidationError` from cosmiconfig-extends, naming the file, with one normalised `{ path, message }` entry per problem. A unified file that exports anything but an object throws a `TypeError`, and so does any config file with no default export or a `null` one, since a configuration that never takes effect must not read as an absent file. Only an empty file, or one whose default export is `undefined`, is an empty config. diff --git a/src/config-file.ts b/src/config-file.ts index 964da0c..f928a9f 100644 --- a/src/config-file.ts +++ b/src/config-file.ts @@ -7,9 +7,14 @@ import { createExplorer, createJitiLoader, type ExplorerOptions } from 'cosmicon import { isRecord } from './validation'; /** - * The name of the unified config file, looked for in the directory a tool is asked about. + * The base name of the unified config file, `exadev.config.`, looked for in the directory a tool is asked about. */ -export const UNIFIED_FILE = 'exadev.config.ts'; +export const UNIFIED_BASE = 'exadev'; + +/** + * The extensions a config file may have: the ones cosmiconfig-extends registers its TypeScript loader for. + */ +export const CONFIG_EXTENSIONS = ['.ts', '.mts', '.cts'] as const; /** * How config files are loaded, passed to `cosmiconfig-extends`: the authoring-import `alias`, jiti's `fsCache`, the `trust` policy for `extends` references (local paths only by default), and the `merge` that combines a file with the presets it extends (arrays replace by default). @@ -26,10 +31,24 @@ export function assertDirectory(directory: string): void { } /** - * The path of the standalone config file for the tool that owns section `name`. + * The base name of the standalone config file for the tool that owns section `name`: `exadev..config.`. It is namespaced because the plain `.config.` is the native config file of many tools (`eslint.config.ts`, `vitest.config.ts`), which a section of the same name would otherwise read as its own. */ -export function standaloneFile(cwd: string, name: string): string { - return join(cwd, `${name}.config.ts`); +export function standaloneBase(name: string): string { + return `${UNIFIED_BASE}.${name}`; +} + +/** + * The config file `.config.` in `directory`, or `undefined` when there is none. Throws when the file exists under more than one extension, since choosing one silently would hide a configuration that never takes effect. + */ +export function findConfigFile(directory: string, base: string): string | undefined { + const found = CONFIG_EXTENSIONS.map((extension) => join(directory, `${base}.config${extension}`)).filter( + (file) => statSync(file, { throwIfNoEntry: false })?.isFile() === true, + ); + if (found.length > 1) { + throw new Error(`${base}.config exists under more than one extension (${found.join(', ')}); keep exactly one`); + } + + return found[0]; } /** @@ -59,12 +78,9 @@ function requireDefaultExport(loader: Loader): Loader { } /** - * The evaluated content of the config file at `file` after `extends` is applied, or `undefined` when the file does not exist, is empty, or has `undefined` as its default export. Throws when the file does not export a default value. Every call reads the file afresh. + * The evaluated content of the config file at `file` after `extends` is applied, or `undefined` when the file is empty or has `undefined` as its default export. Throws when the file does not export a default value. Every call reads the file afresh. */ export async function readConfigFile(file: string, options: ConfigFileOptions): Promise { - if (statSync(file, { throwIfNoEntry: false })?.isFile() !== true) { - return undefined; - } const checked = requireDefaultExport(createJitiLoader(options).loader); const explorer = createExplorer('exadev', { ...options, @@ -76,7 +92,7 @@ export async function readConfigFile(file: string, options: ConfigFileOptions): } /** - * The evaluated unified config at `file`: a record, or `undefined` when the file is absent or empty. Throws when the file exports something other than an object. + * The evaluated unified config at `file`: a record, or `undefined` when the file is empty. Throws when the file exports something other than an object. */ export async function readUnifiedFile(file: string, options: ConfigFileOptions): Promise> | undefined> { const config = await readConfigFile(file, options); diff --git a/src/doctor.integration.test.ts b/src/doctor.integration.test.ts index bc26c57..ec3b599 100644 --- a/src/doctor.integration.test.ts +++ b/src/doctor.integration.test.ts @@ -123,6 +123,18 @@ describe('doctor', () => { expect(await doctor({ cwd })).toMatchObject({ defined: ['inherited', 'layout'], unowned: ['inherited'] }); }); + it.each(['.mts', '.cts'])('reads a unified file with the extension %s', async (extension) => { + const cwd = makeProject({ [`exadev.config${extension}`]: 'export default { elint: {} };\n' }); + + expect(await doctor({ cwd })).toMatchObject({ file: join(cwd, `exadev.config${extension}`), unowned: ['elint'] }); + }); + + it('rejects a unified file that exists under two extensions', async () => { + const cwd = makeProject({ 'exadev.config.ts': 'export default {};\n', 'exadev.config.cts': 'export default {};\n' }); + + await expect(doctor({ cwd })).rejects.toThrow('exadev.config exists under more than one extension'); + }); + it('rejects a unified file that does not export an object', async () => { const cwd = makeProject({ 'exadev.config.ts': 'export default 5;\n' }); diff --git a/src/doctor.ts b/src/doctor.ts index 247bad7..3e247a5 100644 --- a/src/doctor.ts +++ b/src/doctor.ts @@ -1,7 +1,7 @@ import { readFileSync, statSync } from 'node:fs'; import { dirname, join } from 'node:path'; -import { assertDirectory, type ConfigFileOptions, readUnifiedFile, UNIFIED_FILE } from './config-file'; +import { assertDirectory, type ConfigFileOptions, findConfigFile, readUnifiedFile, UNIFIED_BASE } from './config-file'; import { layoutSection } from './layout'; import { array, type Check, isRecord, isString, required, strictObject } from './validation'; @@ -37,11 +37,11 @@ export interface DoctorOptions extends ConfigFileOptions { */ export interface DoctorReport { /** - * The unified config file that was read, or `undefined` when the directory has none or the file is empty. + * The unified config file that was read (`exadev.config.ts`, or the same with the extension `.mts` or `.cts`), or `undefined` when the directory has none or the file is empty. */ readonly file: string | undefined; /** - * The top-level keys the file defines after `extends` is applied, in file order. + * The top-level keys the file defines after `extends` is applied, in merge order: the keys of a preset come before the keys only the file itself defines. */ readonly defined: readonly string[]; /** @@ -123,10 +123,10 @@ function installedSections(cwd: string): readonly string[] { */ export async function doctor(options: DoctorOptions): Promise { assertDirectory(options.cwd); - const file = join(options.cwd, UNIFIED_FILE); - const config = await readUnifiedFile(file, options); + const file = findConfigFile(options.cwd, UNIFIED_BASE); + const config = file === undefined ? undefined : await readUnifiedFile(file, options); const known = [...new Set([layoutSection.name, ...installedSections(options.cwd), ...(options.listed ?? [])])].sort(); - if (config === undefined) { + if (file === undefined || config === undefined) { return { file: undefined, defined: [], unowned: [], known }; } const defined = Object.keys(config); diff --git a/src/load-section.integration.test.ts b/src/load-section.integration.test.ts index 228bdc7..4ba07e0 100644 --- a/src/load-section.integration.test.ts +++ b/src/load-section.integration.test.ts @@ -31,35 +31,45 @@ describe('loadSection', () => { }); it('reads a section from its standalone file', async () => { - const cwd = makeProject({ 'toolA.config.ts': `export default ${SECTION};\n` }); + const cwd = makeProject({ 'exadev.toolA.config.ts': `export default ${SECTION};\n` }); expect(await loadSection(toolA, { cwd })).toEqual({ include: ['src'], level: 'low' }); }); it('produces the same section from the unified file and from the standalone file', async () => { const fromUnified = await loadSection(toolA, { cwd: makeProject({ 'exadev.config.ts': unified("{ include: ['a', 'b'], level: 'high' }") }) }); - const fromStandalone = await loadSection(toolA, { cwd: makeProject({ 'toolA.config.ts': "export default { include: ['a', 'b'], level: 'high' };\n" }) }); + const fromStandalone = await loadSection(toolA, { cwd: makeProject({ 'exadev.toolA.config.ts': "export default { include: ['a', 'b'], level: 'high' };\n" }) }); expect(fromUnified).toEqual({ include: ['a', 'b'], level: 'high' }); expect(fromStandalone).toEqual(fromUnified); }); it('ignores the sections of other tools in the unified file, and their standalone files', async () => { - const cwd = makeProject({ 'exadev.config.ts': unified(), 'other.config.ts': 'export default 1;\n' }); + const cwd = makeProject({ 'exadev.config.ts': unified(), 'exadev.other.config.ts': 'export default 1;\n' }); expect(await loadSection(toolA, { cwd })).toEqual({ include: ['src'], level: 'low' }); }); it('fails when the section is defined in both files, naming both', async () => { - const cwd = makeProject({ 'exadev.config.ts': unified(), 'toolA.config.ts': `export default ${SECTION};\n` }); + const cwd = makeProject({ 'exadev.config.ts': unified(), 'exadev.toolA.config.ts': `export default ${SECTION};\n` }); await expect(loadSection(toolA, { cwd })).rejects.toThrow( - new Error(`section 'toolA' is defined in both ${join(cwd, 'exadev.config.ts')} and ${join(cwd, 'toolA.config.ts')}; keep exactly one`), + new Error(`section 'toolA' is defined in both ${join(cwd, 'exadev.config.ts')} (directly or through extends) and ${join(cwd, 'exadev.toolA.config.ts')}; keep exactly one`), ); }); + it('fails when a preset of the unified file supplies a section the standalone file also defines', async () => { + const cwd = makeProject({ + 'preset.ts': `export default { toolA: ${SECTION} };\n`, + 'exadev.config.ts': "export default { extends: './preset.ts' };\n", + 'exadev.toolA.config.ts': `export default ${SECTION};\n`, + }); + + await expect(loadSection(toolA, { cwd })).rejects.toThrow(/defined in both .*exadev\.config\.ts \(directly or through extends\) and .*exadev\.toolA\.config\.ts/); + }); + it('is not a conflict when the unified file defines only other sections', async () => { - const cwd = makeProject({ 'exadev.config.ts': "export default { other: {} };\n", 'toolA.config.ts': `export default ${SECTION};\n` }); + const cwd = makeProject({ 'exadev.config.ts': "export default { other: {} };\n", 'exadev.toolA.config.ts': `export default ${SECTION};\n` }); expect(await loadSection(toolA, { cwd })).toEqual({ include: ['src'], level: 'low' }); }); @@ -72,12 +82,12 @@ describe('loadSection', () => { it('returns undefined for an empty unified file and for a section exported as undefined', async () => { expect(await loadSection(toolA, { cwd: makeProject({ 'exadev.config.ts': 'export default undefined;\n' }) })).toBeUndefined(); expect(await loadSection(toolA, { cwd: makeProject({ 'exadev.config.ts': 'export default { toolA: undefined };\n' }) })).toBeUndefined(); - expect(await loadSection(toolA, { cwd: makeProject({ 'toolA.config.ts': 'export default undefined;\n' }) })).toBeUndefined(); + expect(await loadSection(toolA, { cwd: makeProject({ 'exadev.toolA.config.ts': 'export default undefined;\n' }) })).toBeUndefined(); }); it('returns undefined for a file with no content', async () => { expect(await loadSection(toolA, { cwd: makeProject({ 'exadev.config.ts': '' }) })).toBeUndefined(); - expect(await loadSection(toolA, { cwd: makeProject({ 'toolA.config.ts': '' }) })).toBeUndefined(); + expect(await loadSection(toolA, { cwd: makeProject({ 'exadev.toolA.config.ts': '' }) })).toBeUndefined(); }); const NULL_EXPORT = 'the default export is null; export an object, or undefined for an empty config'; @@ -87,8 +97,8 @@ describe('loadSection', () => { ['exadev.config.ts', 'export default null;\n', NULL_EXPORT], ['exadev.config.ts', 'export const config = { toolA: { include: [] } };\n', NO_DEFAULT], ['exadev.config.ts', 'export {};\n', NO_DEFAULT], - ['toolA.config.ts', 'export default null;\n', NULL_EXPORT], - ['toolA.config.ts', 'export const config = { include: [] };\n', NO_DEFAULT], + ['exadev.toolA.config.ts', 'export default null;\n', NULL_EXPORT], + ['exadev.toolA.config.ts', 'export const config = { include: [] };\n', NO_DEFAULT], ])('fails on %s that exports no configuration: %j', async (file, content, message) => { const cwd = makeProject({ [file]: content }); @@ -123,6 +133,50 @@ describe('loadSection', () => { expect(await loadSection(toolA, { cwd })).toHaveProperty('level', 'low'); }); + describe('file names', () => { + const eslintSection = defineSection('eslint', z.strictObject({ strict: z.boolean() })); + + it('does not read the native config file of a tool that shares the section name', async () => { + const native = makeProject({ 'eslint.config.ts': 'export default [{ rules: {} }];\n', 'vitest.config.ts': 'export default {};\n' }); + + expect(await loadSection(eslintSection, { cwd: native })).toBeUndefined(); + + const both = makeProject({ 'eslint.config.ts': 'export default [{ rules: {} }];\n', 'exadev.config.ts': 'export default { eslint: { strict: true } };\n' }); + + expect(await loadSection(eslintSection, { cwd: both })).toEqual({ strict: true }); + }); + + it.each(['.mts', '.cts'])('reads the unified file and a standalone file with the extension %s', async (extension) => { + const unifiedProject = makeProject({ [`exadev.config${extension}`]: unified() }); + const standaloneProject = makeProject({ [`exadev.toolA.config${extension}`]: `export default ${SECTION};\n` }); + + expect(await loadSection(toolA, { cwd: unifiedProject })).toEqual({ include: ['src'], level: 'low' }); + expect(await loadSection(toolA, { cwd: standaloneProject })).toEqual({ include: ['src'], level: 'low' }); + }); + + it('fails when the unified file exists under two extensions, naming both', async () => { + const cwd = makeProject({ 'exadev.config.ts': unified(), 'exadev.config.mts': unified() }); + + await expect(loadSection(toolA, { cwd })).rejects.toThrow( + new Error(`exadev.config exists under more than one extension (${join(cwd, 'exadev.config.ts')}, ${join(cwd, 'exadev.config.mts')}); keep exactly one`), + ); + }); + + it('fails when a standalone file exists under two extensions, naming both', async () => { + const cwd = makeProject({ 'exadev.toolA.config.ts': `export default ${SECTION};\n`, 'exadev.toolA.config.cts': `export default ${SECTION};\n` }); + + await expect(loadSection(toolA, { cwd })).rejects.toThrow( + new Error(`exadev.toolA.config exists under more than one extension (${join(cwd, 'exadev.toolA.config.ts')}, ${join(cwd, 'exadev.toolA.config.cts')}); keep exactly one`), + ); + }); + + it('fails when the section is defined in files of different extensions', async () => { + const cwd = makeProject({ 'exadev.config.mts': unified(), 'exadev.toolA.config.cts': `export default ${SECTION};\n` }); + + await expect(loadSection(toolA, { cwd })).rejects.toThrow(new Error(`section 'toolA' is defined in both ${join(cwd, 'exadev.config.mts')} (directly or through extends) and ${join(cwd, 'exadev.toolA.config.cts')}; keep exactly one`)); + }); + }); + describe('validation', () => { it('rejects an invalid section in the unified file with the file and the path of every problem', async () => { const cwd = makeProject({ 'exadev.config.ts': unified("{ include: 'src', bogus: 1 }") }); @@ -134,9 +188,9 @@ describe('loadSection', () => { }); it('rejects an invalid standalone section with the standalone file', async () => { - const cwd = makeProject({ 'toolA.config.ts': "export default { include: 1 };\n" }); + const cwd = makeProject({ 'exadev.toolA.config.ts': "export default { include: 1 };\n" }); - await expect(loadSection(toolA, { cwd })).rejects.toThrow(`invalid 'toolA' section in ${join(cwd, 'toolA.config.ts')}:`); + await expect(loadSection(toolA, { cwd })).rejects.toThrow(`invalid 'toolA' section in ${join(cwd, 'exadev.toolA.config.ts')}:`); }); it('rejects a unified file that does not export an object', async () => { @@ -160,7 +214,7 @@ describe('loadSection', () => { const expected = { groups: [{ name: 'core', rank: 0 }], naming: { scope: '@acme' } }; expect(await loadSection(layoutSection, { cwd: makeProject({ 'exadev.config.ts': `export default { layout: ${layout} };\n` }) })).toEqual(expected); - expect(await loadSection(layoutSection, { cwd: makeProject({ 'layout.config.ts': `export default ${layout};\n` }) })).toEqual(expected); + expect(await loadSection(layoutSection, { cwd: makeProject({ 'exadev.layout.config.ts': `export default ${layout};\n` }) })).toEqual(expected); }); }); @@ -177,7 +231,7 @@ describe('loadSection', () => { it('applies extends inside a standalone file', async () => { const cwd = makeProject({ 'base.ts': "export default { include: ['base'], level: 'high' };\n", - 'toolA.config.ts': "export default { extends: './base.ts', level: 'low' };\n", + 'exadev.toolA.config.ts': "export default { extends: './base.ts', level: 'low' };\n", }); expect(await loadSection(toolA, { cwd })).toEqual({ include: ['base'], level: 'low' }); diff --git a/src/load-section.ts b/src/load-section.ts index d192788..8614345 100644 --- a/src/load-section.ts +++ b/src/load-section.ts @@ -1,9 +1,15 @@ -import { join } from 'node:path'; - import type { StandardSchemaV1 } from '@standard-schema/spec'; import { validateStandard } from 'cosmiconfig-extends'; -import { assertDirectory, type ConfigFileOptions, readConfigFile, readUnifiedFile, standaloneFile, UNIFIED_FILE } from './config-file'; +import { + assertDirectory, + type ConfigFileOptions, + findConfigFile, + readConfigFile, + readUnifiedFile, + standaloneBase, + UNIFIED_BASE, +} from './config-file'; import type { Section } from './section'; /** @@ -16,10 +22,35 @@ export interface LoadSectionOptions extends ConfigFileOptions { readonly cwd: string; } +/** + * Where a section's value was found. + */ +interface Found { + readonly file: string; + readonly value: unknown; +} + +async function fromUnified(name: string, cwd: string, options: ConfigFileOptions): Promise { + const file = findConfigFile(cwd, UNIFIED_BASE); + const config = file === undefined ? undefined : await readUnifiedFile(file, options); + if (file === undefined || config === undefined || !Object.hasOwn(config, name) || config[name] === undefined) { + return undefined; + } + + return { file, value: config[name] }; +} + +async function fromStandalone(name: string, cwd: string, options: ConfigFileOptions): Promise { + const file = findConfigFile(cwd, standaloneBase(name)); + const value = file === undefined ? undefined : await readConfigFile(file, options); + + return file === undefined || value === undefined ? undefined : { file, value }; +} + /** * Load one tool's section, validated by the descriptor's schema. * - * The section comes from `exadev.config.ts` (its key `section.name`) or from `.config.ts`, both in `cwd`; neither is searched for in a parent directory. Both files are evaluated with `extends` applied. A section defined in both files throws an error naming both, since choosing one silently would hide a configuration that never takes effect. The result is the schema's output, and is `undefined` when neither file defines the section. + * The section comes from `exadev.config.ts` (its key `section.name`) or from `exadev..config.ts`, both in `cwd`; either may instead use the extension `.mts` or `.cts`, and a file that exists under more than one extension throws. Neither file is searched for in a parent directory. Both are evaluated with `extends` applied, so a section that reaches the unified file through a preset counts as defined there. A section defined in both files throws an error naming both, since choosing one silently would hide a configuration that never takes effect. The result is the schema's output, and is `undefined` when neither file defines the section. * * Throws `ConfigValidationError` when the section fails the schema, a `TypeError` when the unified file exports something other than an object, and an `Error` when `cwd` is not an existing directory. */ @@ -28,21 +59,16 @@ export async function loadSection | undefined> { assertDirectory(options.cwd); - const unifiedFile = join(options.cwd, UNIFIED_FILE); - const standalone = standaloneFile(options.cwd, section.name); - const unified = await readUnifiedFile(unifiedFile, options); - const fromUnified: unknown = unified !== undefined && Object.hasOwn(unified, section.name) ? unified[section.name] : undefined; - const fromStandalone = await readConfigFile(standalone, options); - - if (fromUnified !== undefined && fromStandalone !== undefined) { - throw new Error(`section '${section.name}' is defined in both ${unifiedFile} and ${standalone}; keep exactly one`); - } - if (fromUnified !== undefined) { - return validateStandard(section.schema, fromUnified, `'${section.name}' section in ${unifiedFile}`); + const unified = await fromUnified(section.name, options.cwd, options); + const standalone = await fromStandalone(section.name, options.cwd, options); + + if (unified !== undefined && standalone !== undefined) { + throw new Error(`section '${section.name}' is defined in both ${unified.file} (directly or through extends) and ${standalone.file}; keep exactly one`); } - if (fromStandalone !== undefined) { - return validateStandard(section.schema, fromStandalone, `'${section.name}' section in ${standalone}`); + const found = unified ?? standalone; + if (found === undefined) { + return undefined; } - return undefined; + return validateStandard(section.schema, found.value, `'${section.name}' section in ${found.file}`); } diff --git a/src/section.ts b/src/section.ts index 74c3626..112e60d 100644 --- a/src/section.ts +++ b/src/section.ts @@ -90,7 +90,7 @@ const SECTION_NAME = /^[A-Za-z][A-Za-z0-9_-]*$/; const ENVELOPE_KEYS: readonly string[] = ['extends']; /** - * Describe a tool's section. `name` is both the key in the unified config and the base of the standalone file name `.config.ts`, so it must start with a letter and contain only letters, digits, `-` and `_`, and it must not be a key of the {@link Envelope}. Throws a `TypeError` otherwise. + * Describe a tool's section. `name` is both the key in the unified config and the base of the standalone file name `exadev..config.ts`, so it must start with a letter and contain only letters, digits, `-` and `_`, and it must not be a key of the {@link Envelope}. Throws a `TypeError` otherwise. */ export function defineSection( name: Name & Nameable, From 5f84329a9545fd04893d47b1ad5d7b103277de49 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 29 Sep 2026 23:38:54 +0100 Subject: [PATCH 13/23] fix: accept doctor --help, fail on a missing command, and say when there is no config The usage text advertises --help for the doctor command, but parseArgs rejected it as an unknown option. Running the command with no arguments printed the usage and exited 0. It now exits with the failure status. A directory with no unified config exited 0 in silence, which made a clean exit weak evidence; the command now says nothing was checked. --- README.md | 2 +- src/doctor-command.integration.test.ts | 19 ++++++++++++++++++- src/doctor-command.ts | 23 ++++++++++++++++++----- test/package/check.mjs | 2 ++ 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ca639bf..d3eebbf 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ A key is owned when it is `layout`, when a dependency of the project's `package. exadev-config doctor [--cwd ] [--section ]... ``` -It exits 0 when every section is owned, 1 when some is not, and 2 when it could not run. +It exits 0 when every section is owned, 1 when some is not, and 2 when it could not run, which includes a missing command, a `--cwd` that is not a directory and a config that fails to load. A directory with no `exadev.config.ts` (or an empty one) is not searched upward: the command says there is nothing to check and exits 0, so run it where the file is. `exadev-config --help` and `exadev-config doctor --help` print the usage. ## Development diff --git a/src/doctor-command.integration.test.ts b/src/doctor-command.integration.test.ts index f24a124..65810e7 100644 --- a/src/doctor-command.integration.test.ts +++ b/src/doctor-command.integration.test.ts @@ -28,7 +28,7 @@ async function run(...args: readonly string[]): Promise { } describe('runCommand', () => { - it.each([[[]], [['--help']]])('prints the usage and succeeds for %j', async (args) => { + it.each([[['--help']], [['doctor', '--help']]])('prints the usage and succeeds for %j', async (args) => { const { code, stdout, stderr } = await run(...args); expect(code).toBe(EXIT_CODES.clean); @@ -36,6 +36,14 @@ describe('runCommand', () => { expect(stderr).toBe(''); }); + it('fails on a missing command, printing the usage with the error', async () => { + const { code, stdout, stderr } = await run(); + + expect(code).toBe(EXIT_CODES.failed); + expect(stdout).toBe(''); + expect(stderr).toMatch(/^missing command\n\nUsage: exadev-config doctor/); + }); + it('fails on an unknown command, printing the usage with the error', async () => { const { code, stdout, stderr } = await run('frobnicate'); @@ -51,6 +59,15 @@ describe('runCommand', () => { expect(await run('doctor', '--cwd', cwd)).toEqual({ code: EXIT_CODES.clean, stdout: '', stderr: '' }); }); + it.each([[{}], [{ 'exadev.config.ts': 'export default undefined;\n' }]])('says there is nothing to check, and succeeds, when there is no unified config: %j', async (files) => { + const cwd = makeProject(files); + const { code, stdout, stderr } = await run('doctor', '--cwd', cwd); + + expect(code).toBe(EXIT_CODES.clean); + expect(stdout).toBe(`${cwd}: nothing to check, since exadev.config.ts is absent or empty there. The command does not search parent directories.\n`); + expect(stderr).toBe(''); + }); + it('reports each unowned section on stderr with the known ones, and exits 1', async () => { const cwd = makeProject({ 'exadev.config.ts': 'export default { elint: {}, layout: {}, tsc: {} };\n' }); const file = join(cwd, 'exadev.config.ts'); diff --git a/src/doctor-command.ts b/src/doctor-command.ts index 9fc8500..4d12f02 100644 --- a/src/doctor-command.ts +++ b/src/doctor-command.ts @@ -24,7 +24,7 @@ Reports the sections in exadev.config.ts that no installed or listed tool owns. --section Treat as owned. Repeatable. --help Show this message. -Exit status: ${String(EXIT_CODES.clean)} when every section is owned, ${String(EXIT_CODES.unowned)} when some is not, ${String(EXIT_CODES.failed)} when the command could not run. +Exit status: ${String(EXIT_CODES.clean)} when every section is owned (or there is no exadev.config.ts to check), ${String(EXIT_CODES.unowned)} when some is not, ${String(EXIT_CODES.failed)} when the command could not run. `; function messageOf(error: unknown): string { @@ -34,23 +34,33 @@ function messageOf(error: unknown): string { async function runDoctor(args: readonly string[], output: CommandOutput): Promise { const { values } = parseArgs({ args: [...args], - options: { cwd: { type: 'string' }, section: { type: 'string', multiple: true } }, + options: { cwd: { type: 'string' }, section: { type: 'string', multiple: true }, help: { type: 'boolean' } }, allowPositionals: false, }); + if (values.help === true) { + output.stdout(USAGE); + + return EXIT_CODES.clean; + } const cwd = resolve(values.cwd ?? '.'); const report = await doctor({ cwd, listed: values.section ?? [] }); + if (report.file === undefined) { + output.stdout(`${cwd}: nothing to check, since exadev.config.ts is absent or empty there. The command does not search parent directories.\n`); + + return EXIT_CODES.clean; + } if (report.unowned.length === 0) { return EXIT_CODES.clean; } for (const name of report.unowned) { - output.stderr(`${report.file ?? cwd}: section '${name}' is not owned by any installed or listed tool. Known sections: ${report.known.join(', ')}.\n`); + output.stderr(`${report.file}: section '${name}' is not owned by any installed or listed tool. Known sections: ${report.known.join(', ')}.\n`); } return EXIT_CODES.unowned; } /** - * Run the `exadev-config` command with the arguments after the program name and return the exit code. A failure to run (unknown option, unloadable config) is reported on `stderr` and returns {@link EXIT_CODES}`.failed`. + * Run the `exadev-config` command with the arguments after the program name and return the exit code. A failure to run (missing or unknown command, unknown option, unloadable config) is reported on `stderr` and returns {@link EXIT_CODES}`.failed`. */ export async function runCommand(args: readonly string[], output: CommandOutput): Promise { try { @@ -58,11 +68,14 @@ export async function runCommand(args: readonly string[], output: CommandOutput) if (command === 'doctor') { return await runDoctor(rest, output); } - if (command === undefined || command === '--help') { + if (command === '--help') { output.stdout(USAGE); return EXIT_CODES.clean; } + if (command === undefined) { + throw new TypeError(`missing command\n\n${USAGE}`); + } throw new TypeError(`unknown command '${command}'\n\n${USAGE}`); } catch (error) { output.stderr(`${messageOf(error)}\n`); diff --git a/test/package/check.mjs b/test/package/check.mjs index 83de1a5..514e33a 100644 --- a/test/package/check.mjs +++ b/test/package/check.mjs @@ -25,3 +25,5 @@ const run = (args) => { assert.equal(run(['doctor', '--section', 'toolA', '--section', 'typo']), 0); assert.equal(run(['doctor', '--section', 'toolA']), 1); assert.equal(run(['unknown']), 2); +assert.equal(run([]), 2); +assert.equal(run(['doctor', '--help']), 0); From 8b42b21e200106413541442ec47fa193ce0eb7f7 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 29 Sep 2026 23:39:45 +0100 Subject: [PATCH 14/23] fix: let the project's own manifest own sections and tolerate unknown manifest keys doctor ignored the exadevConfig field of the project's own package.json, so a tool repository dogfooding its section saw it reported as unowned. The manifest check also rejected any key besides sections, which would make every consumer on an older version fail as soon as a tool adds one; it now checks sections and ignores the rest. --- README.md | 2 +- src/doctor.integration.test.ts | 25 ++++++++++++++++++++++++- src/doctor.ts | 18 ++++++++++++------ src/validation.ts | 32 +++++++++++++++++++++++--------- src/validation.unit.test.ts | 23 +++++++++++++++++++++++ 5 files changed, 83 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index d3eebbf..a03ab37 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ const report = await doctor({ cwd: process.cwd(), listed: ['myTool'] }); // report.unowned lists the keys of exadev.config.ts that no installed or listed tool owns. ``` -A key is owned when it is `layout`, when a dependency of the project's `package.json` declares it in its `exadevConfig.sections`, or when it appears in `listed`. A dependency is looked for in the `node_modules` directories from `cwd` upwards. The command wraps the same function: +A key is owned when it is `layout`, when a dependency of the project's `package.json` declares it in its `exadevConfig.sections`, or when the project's own `package.json` declares it (a tool repository dogfooding its section), or when it appears in `listed`. Keys of `exadevConfig` other than `sections` are ignored, so a later manifest version does not break an older `doctor`. A dependency is looked for in the `node_modules` directories from `cwd` upwards. The command wraps the same function: ```sh exadev-config doctor [--cwd ] [--section ]... diff --git a/src/doctor.integration.test.ts b/src/doctor.integration.test.ts index ec3b599..793dc3f 100644 --- a/src/doctor.integration.test.ts +++ b/src/doctor.integration.test.ts @@ -157,11 +157,34 @@ describe('doctor', () => { expect((await doctor({ cwd, trust: () => true })).defined).toEqual(['fromShared']); }); + it('owns a section the project itself declares, as a tool repository does for the section it dogfoods', async () => { + const cwd = makeProject(project({ name: 'tool-a', ...declares('toolA') }, 'export default { toolA: {}, elint: {} };\n')); + + const report = await doctor({ cwd }); + + expect(report.unowned).toEqual(['elint']); + expect(report.known).toEqual(['layout', 'toolA']); + }); + + it('names the project when its own manifest is malformed', async () => { + const cwd = makeProject(project({ [MANIFEST_FIELD]: {} })); + + await expect(doctor({ cwd })).rejects.toThrow(new TypeError(`invalid '${MANIFEST_FIELD}' in the project's package.json:\n sections: required`)); + }); + + it('ignores a key of the manifest that it does not know, so a later manifest version does not break an older doctor', async () => { + const cwd = makeProject({ + ...project({ dependencies: { 'tool-a': '1' } }, 'export default { toolA: {} };\n'), + ...installed('tool-a', { [MANIFEST_FIELD]: { sections: ['toolA'], version: 2 } }), + }); + + expect((await doctor({ cwd })).unowned).toEqual([]); + }); + describe('a malformed manifest', () => { it.each([ ['a string', 'sections', '(root): expected an object'], ['no sections key', {}, 'sections: required'], - ['an unknown key', { sections: [], other: 1 }, 'other: unknown key'], ['a non-string section', { sections: ['a', 2] }, 'sections.1: expected a string'], ])('with %s is an error naming the package and the problem', async (_name, manifest, problem) => { const cwd = makeProject({ ...project({ dependencies: { 'tool-a': '1' } }), ...installed('tool-a', { [MANIFEST_FIELD]: manifest }) }); diff --git a/src/doctor.ts b/src/doctor.ts index 3e247a5..d84c4c1 100644 --- a/src/doctor.ts +++ b/src/doctor.ts @@ -3,7 +3,7 @@ import { dirname, join } from 'node:path'; import { assertDirectory, type ConfigFileOptions, findConfigFile, readUnifiedFile, UNIFIED_BASE } from './config-file'; import { layoutSection } from './layout'; -import { array, type Check, isRecord, isString, required, strictObject } from './validation'; +import { array, type Check, isRecord, isString, looseObject, required } from './validation'; /** * The `package.json` field through which a tool package declares the section names it owns: `"exadevConfig": { "sections": ["eslint"] }`. It is how `doctor` learns which sections an installed tool reads, without importing the tool. @@ -14,7 +14,8 @@ interface Manifest { readonly sections: readonly string[]; } -const isManifest: Check = strictObject({ sections: required(array(isString)) }); +// Loose, not strict: a future version of this package may add a key that an older doctor must not reject in every consumer that installs the tool. +const isManifest: Check = looseObject({ sections: required(array(isString)) }); const DEPENDENCY_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'] as const; @@ -87,7 +88,7 @@ function dependencyNames(packageJson: unknown): readonly string[] { }); } -function declaredSections(dependency: string, packageJson: unknown): readonly string[] { +function declaredSections(owner: string, packageJson: unknown): readonly string[] { if (!isRecord(packageJson) || !Object.hasOwn(packageJson, MANIFEST_FIELD)) { return []; } @@ -96,14 +97,14 @@ function declaredSections(dependency: string, packageJson: unknown): readonly st if (!isManifest(manifest, [], (path, message) => { problems.push(`${path.join('.') || '(root)'}: ${message}`); })) { - throw new TypeError(`invalid '${MANIFEST_FIELD}' in the package.json of '${dependency}':\n${problems.map((problem) => ` ${problem}`).join('\n')}`); + throw new TypeError(`invalid '${MANIFEST_FIELD}' in ${owner}:\n${problems.map((problem) => ` ${problem}`).join('\n')}`); } return manifest.sections; } /** - * The section names the packages installed for the project in `cwd` declare through {@link MANIFEST_FIELD}. A dependency that is not installed, or declares none, contributes nothing. + * The section names that the project in `cwd` and the packages installed for it declare through {@link MANIFEST_FIELD}; the project's own declaration is how a tool repository owns the section it dogfoods. A dependency that is not installed, or declares none, contributes nothing. */ function installedSections(cwd: string): readonly string[] { const projectFile = join(cwd, 'package.json'); @@ -111,7 +112,12 @@ function installedSections(cwd: string): readonly string[] { return []; } - return dependencyNames(readJson(projectFile)).flatMap((name) => declaredSections(name, findInstalledManifest(cwd, name))); + const project = readJson(projectFile); + + return [ + ...declaredSections("the project's package.json", project), + ...dependencyNames(project).flatMap((name) => declaredSections(`the package.json of '${name}'`, findInstalledManifest(cwd, name))), + ]; } /** diff --git a/src/validation.ts b/src/validation.ts index d15af93..70db639 100644 --- a/src/validation.ts +++ b/src/validation.ts @@ -143,11 +143,7 @@ export function pair(item: Check): Check { }; } -/** - * Accepts a plain object with exactly the keys of `shape`: a key outside it is reported as unknown, and a required key that is absent is reported as missing. - */ -export function strictObject(shape: Readonly>): Check; -export function strictObject(shape: Readonly>>): Check { +function objectOf(shape: Readonly>>, rejectUnknownKeys: boolean): Check { return (value, path, report): value is object => { if (!isRecord(value)) { report(path, 'expected an object'); @@ -155,10 +151,12 @@ export function strictObject(shape: Readonly(shape: Readonly>): Check; +export function strictObject(shape: Readonly>>): Check { + return objectOf(shape, true); +} + +/** + * Accepts a plain object that has the keys of `shape` and checks them, but ignores any other key. It is for a document that other parties extend, where a key this version does not know about is not an error. + */ +export function looseObject(shape: Readonly>): Check; +export function looseObject(shape: Readonly>>): Check { + return objectOf(shape, false); +} + /** * Wrap a guard as a Standard Schema whose input and output are both `T`: a value that passes is returned as it is, with nothing defaulted or stripped. */ diff --git a/src/validation.unit.test.ts b/src/validation.unit.test.ts index 2566f5c..25b6c7b 100644 --- a/src/validation.unit.test.ts +++ b/src/validation.unit.test.ts @@ -7,6 +7,7 @@ import { isRecord, isString, literal, + looseObject, optional, pair, type Path, @@ -189,6 +190,28 @@ describe('strictObject', () => { }); }); +describe('looseObject', () => { + interface Sample { + readonly a: string; + readonly b?: number; + } + const isSample = looseObject({ a: required(isString), b: optional(integer()) }); + + it('accepts an object with the required key, whatever other keys it has', () => { + expect(run(isSample, { a: 'x' }).valid).toBe(true); + expect(run(isSample, { a: 'x', b: 1, c: 'anything' }).valid).toBe(true); + }); + + it.each([[null], [[]], ['a']])('rejects %j as not an object', (value) => { + expect(run(isSample, value)).toEqual({ valid: false, problems: [{ path: [], message: 'expected an object' }] }); + }); + + it('still reports a missing required key and a bad value of a known key', () => { + expect(run(isSample, { c: 1 })).toEqual({ valid: false, problems: [{ path: ['a'], message: 'required' }] }); + expect(run(isSample, { a: 'x', b: 'y', c: 1 }).problems).toEqual([{ path: ['b'], message: 'expected an integer' }]); + }); +}); + describe('standardSchema', () => { const schema = standardSchema('test-vendor', array(isString)); From 1e0777f4b68dcfc89bfa8ccd1ade4c9d9290d39d Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 29 Sep 2026 23:40:11 +0100 Subject: [PATCH 15/23] feat: re-export ConfigValidationError and ConfigValidationIssue loadSection throws ConfigValidationError, but the class was reachable only by importing cosmiconfig-extends, which a consumer under strict node_modules cannot do without adding it as a direct dependency. --- README.md | 2 +- src/index.ts | 1 + src/index.unit.test.ts | 17 +++++++++++++++++ src/load-section.ts | 2 +- test/package/check.cjs | 3 ++- test/package/check.mjs | 3 ++- 6 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 src/index.unit.test.ts diff --git a/README.md b/README.md index a03ab37..ff31362 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ The standalone name is namespaced because the plain `.config.ts` is the na Both are evaluated with `extends` applied, and both produce the same section for the same content. A section defined in both files (the unified file counts a section a preset supplies) throws an error naming both, since choosing one silently would hide a configuration that never takes effect. When neither file defines it the result is `undefined`. -The result is the schema's output, so defaults the schema applies are included. A section that fails its schema throws `ConfigValidationError` from cosmiconfig-extends, naming the file, with one normalised `{ path, message }` entry per problem. A unified file that exports anything but an object throws a `TypeError`, and so does any config file with no default export or a `null` one, since a configuration that never takes effect must not read as an absent file. Only an empty file, or one whose default export is `undefined`, is an empty config. +The result is the schema's output, so defaults the schema applies are included. A section that fails its schema throws `ConfigValidationError`, which this package re-exports along with its `ConfigValidationIssue` type so a caller can catch it by class without depending on cosmiconfig-extends, naming the file, with one normalised `{ path, message }` entry per problem. A unified file that exports anything but an object throws a `TypeError`, and so does any config file with no default export or a `null` one, since a configuration that never takes effect must not read as an absent file. Only an empty file, or one whose default export is `undefined`, is an empty config. `loadSection` also takes the options cosmiconfig-extends passes through: `alias` (the directory an authoring import such as `@exadev/config` resolves to, whatever the install layout), `fsCache`, `trust` (which `extends` references may load; local paths only by default) and `merge` (how a file and its presets combine; arrays replace by default). diff --git a/src/index.ts b/src/index.ts index ee1bfc1..5814d0c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +export { ConfigValidationError, type ConfigValidationIssue } from 'cosmiconfig-extends'; export type { ConfigFileOptions } from './config-file'; export { doctor, type DoctorOptions, type DoctorReport, MANIFEST_FIELD } from './doctor'; export { diff --git a/src/index.unit.test.ts b/src/index.unit.test.ts new file mode 100644 index 0000000..ba2c057 --- /dev/null +++ b/src/index.unit.test.ts @@ -0,0 +1,17 @@ +import * as validation from 'cosmiconfig-extends'; +import { describe, expect, it } from 'vitest'; +import { z } from 'zod'; + +import { ConfigValidationError, defineSection, loadSection } from './index'; +import { makeProject } from '../test/support/project'; + +describe('the package entry point', () => { + it('re-exports the class loadSection throws, so a caller can catch it without depending on cosmiconfig-extends', async () => { + const section = defineSection('toolA', z.strictObject({ include: z.array(z.string()) })); + const cwd = makeProject({ 'exadev.config.ts': 'export default { toolA: { include: 1 } };\n' }); + const failure: unknown = await loadSection(section, { cwd }).catch((error: unknown) => error); + + expect(ConfigValidationError).toBe(validation.ConfigValidationError); + expect(failure).toBeInstanceOf(ConfigValidationError); + }); +}); diff --git a/src/load-section.ts b/src/load-section.ts index 8614345..e074719 100644 --- a/src/load-section.ts +++ b/src/load-section.ts @@ -52,7 +52,7 @@ async function fromStandalone(name: string, cwd: string, options: ConfigFileOpti * * The section comes from `exadev.config.ts` (its key `section.name`) or from `exadev..config.ts`, both in `cwd`; either may instead use the extension `.mts` or `.cts`, and a file that exists under more than one extension throws. Neither file is searched for in a parent directory. Both are evaluated with `extends` applied, so a section that reaches the unified file through a preset counts as defined there. A section defined in both files throws an error naming both, since choosing one silently would hide a configuration that never takes effect. The result is the schema's output, and is `undefined` when neither file defines the section. * - * Throws `ConfigValidationError` when the section fails the schema, a `TypeError` when the unified file exports something other than an object, and an `Error` when `cwd` is not an existing directory. + * Throws `ConfigValidationError` (re-exported by this package, so a caller can test for it with `instanceof`) when the section fails the schema, a `TypeError` when the unified file exports something other than an object, and an `Error` when `cwd` is not an existing directory. */ export async function loadSection( section: Section, diff --git a/test/package/check.cjs b/test/package/check.cjs index 65d3ff2..f14ca52 100644 --- a/test/package/check.cjs +++ b/test/package/check.cjs @@ -1,11 +1,12 @@ // Runs in the same scratch project as check.mjs and loads sections through the installed package as CommonJS. const assert = require('node:assert/strict'); -const { defineSection, layoutSection, loadSection, withSections } = require('@exadev/config'); +const { ConfigValidationError, defineSection, layoutSection, loadSection, withSections } = require('@exadev/config'); const { z } = require('zod'); const toolA = defineSection('toolA', z.strictObject({ include: z.array(z.string()) })); assert.equal(withSections(toolA).length, 1); +assert.equal(typeof ConfigValidationError, 'function'); Promise.all([loadSection(layoutSection, { cwd: process.cwd() }), loadSection(toolA, { cwd: process.cwd() })]).then(([layout, section]) => { assert.deepEqual(layout, { groups: [{ name: 'core', rank: 0 }] }); assert.deepEqual(section, { include: ['src'] }); diff --git a/test/package/check.mjs b/test/package/check.mjs index 514e33a..6ffb865 100644 --- a/test/package/check.mjs +++ b/test/package/check.mjs @@ -1,7 +1,7 @@ // Runs in a scratch project that has the packed tarball, cosmiconfig and zod installed. It loads sections through the installed package as an ES module. import assert from 'node:assert/strict'; import { execFileSync } from 'node:child_process'; -import { defineSection, doctor, layoutSection, loadSection } from '@exadev/config'; +import { ConfigValidationError, defineSection, doctor, layoutSection, loadSection } from '@exadev/config'; import { z } from 'zod'; const toolA = defineSection('toolA', z.strictObject({ include: z.array(z.string()) })); @@ -27,3 +27,4 @@ assert.equal(run(['doctor', '--section', 'toolA']), 1); assert.equal(run(['unknown']), 2); assert.equal(run([]), 2); assert.equal(run(['doctor', '--help']), 0); +assert.equal(typeof ConfigValidationError, 'function'); From 537afd96f5311a423aee3e010e6bdf7fb12b3c5c Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 29 Sep 2026 23:40:38 +0100 Subject: [PATCH 16/23] fix: declare the runtime dependencies as caret ranges Exact pins on the published package's runtime dependencies stop a consumer taking a cosmiconfig-extends patch without a new release here, and install a second copy beside any other 1.x version, where ConfigValidationError differs between the copies and instanceof fails. The release age exclusion now names the package rather than one version. --- package.json | 4 ++-- pnpm-lock.yaml | 4 ++-- pnpm-workspace.yaml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 14274e0..4745b4e 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "exadev-config": "./dist/cli.js" }, "dependencies": { - "@standard-schema/spec": "1.1.0", - "cosmiconfig-extends": "1.0.0" + "@standard-schema/spec": "^1.1.0", + "cosmiconfig-extends": "^1.0.0" }, "devDependencies": { "@arethetypeswrong/cli": "0.18.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8d6638e..a6ecbbe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -167,10 +167,10 @@ importers: .: dependencies: '@standard-schema/spec': - specifier: 1.1.0 + specifier: ^1.1.0 version: 1.1.0 cosmiconfig-extends: - specifier: 1.0.0 + specifier: ^1.0.0 version: 1.0.0(cosmiconfig@10.0.1) devDependencies: '@arethetypeswrong/cli': diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index efab338..63d90b5 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,6 @@ # This is a single-package repo, not a real pnpm workspace: packages is deliberately empty. The file exists to carry pnpm settings. packages: [] -# Every dependency add or update writes an exact version, never a range: a floating range lets a routine `pnpm install` silently resolve to a version nobody has reviewed. +# Every development dependency add or update writes an exact version, never a range: a floating range lets a routine `pnpm install` silently resolve to a version nobody has reviewed. The runtime dependencies of the published package are the exception and use caret ranges (add them with `--save-exact=false`): an exact pin would stop a consumer taking a patch without a new release here, and would install a second copy beside any other version of the same package, so `instanceof` on its classes would fail across the copies. minimumReleaseAgeExclude: - - cosmiconfig-extends@1.0.0 + - cosmiconfig-extends saveExact: true From 403b0a9653aba38bbd3c49d1eaa3ccae8f44482c Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 29 Sep 2026 23:40:47 +0100 Subject: [PATCH 17/23] test: pin the zod version of the packaged-install check to the repository's own The check installed whatever zod was latest at run time, so the job was not reproducible. --- test/package/install-check.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/package/install-check.sh b/test/package/install-check.sh index a1650da..9468bc9 100644 --- a/test/package/install-check.sh +++ b/test/package/install-check.sh @@ -7,11 +7,13 @@ pack_dir=$1 cosmiconfig_major=$2 scratch=$3 here=$(cd "$(dirname "$0")" && pwd) +# The zod the repository itself tests with, so the consumer check does not drift with zod releases. +zod_version=$(node -p "require(process.argv[1]).devDependencies.zod" "$here/../../package.json") mkdir -p "$scratch" cd "$scratch" npm init -y > /dev/null -npm install "$pack_dir"/*.tgz "cosmiconfig@$cosmiconfig_major" zod +npm install "$pack_dir"/*.tgz "cosmiconfig@$cosmiconfig_major" "zod@$zod_version" cp "$here/check.mjs" "$here/check.cjs" . # The file is evaluated, not type-checked: it imports the installed package as an authoring file does, and defines a section no tool owns. From 4ece2aad2e9b9354e819ca3ec44795e785eac2b5 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 29 Sep 2026 23:40:57 +0100 Subject: [PATCH 18/23] docs: state what the type checker verifies and what waits for the load The authoring type is the schema's input type, so rules the schema enforces only at runtime pass the type checker, and the excess-property check applies only to an object literal passed directly. The packaged-install sentence now says which check runs the command. The authoring test's option list is described as the subset it is. --- README.md | 7 +++++-- test/types/authoring-types.integration.test.ts | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ff31362..1ac3038 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,14 @@ export default withSections(layoutSection, myTool)({ A section is accepted exactly when its descriptor is passed, so the type system reports the mistakes a config file is likely to contain: -- A key or value the section's schema does not allow is an error on that key, with a near-miss suggestion for a misspelt one. +- A key, or a value of a type, that the section's type does not allow is an error on that key, with a near-miss suggestion for a misspelt one. The type is the schema's input type, so a rule the schema enforces only at runtime (an integer minimum, unique names, a valid pattern) passes the type checker and is checked when a tool loads the section. - A section whose tool is installed but not passed to `withSections` is an unknown-property error on its key. +- A descriptor that could not be told apart in the type (a name that is not a literal, an envelope key such as `extends`, a name listed twice) is refused with a message saying why. - A section whose tool is not installed is a module-not-found error on the import. - A config that lists no sections accepts none. Without a section an empty mapped type would be `{}`, which TypeScript does not check for excess properties, so `defineConfig` accepts only the `extends` key and `withSections` needs at least one descriptor. +The unknown-key and misspelt-name errors come from TypeScript's excess-property check, which applies to an object literal passed directly to the function. A config built elsewhere and passed by variable or spread is not checked for extra keys, so `exadev-config doctor` (below) remains the check for a key nobody reads. + `extends` names presets, resolved and merged by cosmiconfig-extends. For the unified file a preset is a whole config file, so it can supply any section. `defineConfig` and the function `withSections` returns hand back their argument untouched. They apply no schema defaults, because `extends` merges what a file exports and a default applied while authoring would look like a value the author wrote and override the presets it extends. Defaults belong to a section's schema, applied when a tool loads it. @@ -150,7 +153,7 @@ pnpm build # tsdown: ESM, CJS and declarations pnpm test:mutation # Stryker, with a 100% break threshold ``` -CI also runs `pnpm exec publint` and `pnpm exec attw --pack` after the build, and installs the packed tarball into a scratch project on each supported Node line and both cosmiconfig majors (`test/package/install-check.sh`), where it loads sections and runs the command as ESM and as CommonJS. +CI also runs `pnpm exec publint` and `pnpm exec attw --pack` after the build, and installs the packed tarball into a scratch project on each supported Node line and both cosmiconfig majors (`test/package/install-check.sh`), where it loads sections as ESM and as CommonJS and runs the command from the ESM check. The authoring type checks are tests: `test/types/authoring-types.integration.test.ts` compiles the files in `test/types/fixtures/cases` with the TypeScript compiler API and asserts the exact errors each one produces, so a change that loosens the types fails a test instead of passing silently. diff --git a/test/types/authoring-types.integration.test.ts b/test/types/authoring-types.integration.test.ts index 785cede..69f5215 100644 --- a/test/types/authoring-types.integration.test.ts +++ b/test/types/authoring-types.integration.test.ts @@ -14,7 +14,7 @@ const FIXTURES = join(import.meta.dirname, 'fixtures'); const CASES = join(FIXTURES, 'cases'); /** - * The compiler options of the project's own tsconfig that decide how strictly an authoring file is checked. `isolatedDeclarations` is added only for the program that emits declarations. + * A subset of the compiler options of the project's own tsconfig that decide how strictly an authoring file is checked. `isolatedDeclarations` is added only for the program that emits declarations. */ const BASE_OPTIONS: ts.CompilerOptions = { exactOptionalPropertyTypes: true, From fb055d9aa533dfc668816b393b381b12554ae88b Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 29 Sep 2026 23:41:50 +0100 Subject: [PATCH 19/23] refactor: keep config values out of the layout messages and document its decisions The coherence messages quoted group names, patterns and pair members from the config, against the loader design that error messages do not echo input. They now name the path and the rule. The layout schema doc and README state the two other deliberate behaviours: a key written as undefined is rejected where the ESLint reader accepts it, and the relational rules wait for a valid shape. The Check contract says a relational guard may report fewer problems. --- README.md | 2 +- src/layout.ts | 10 ++++++---- src/layout.unit.test.ts | 14 +++++++------- src/load-section.integration.test.ts | 2 +- src/validation.ts | 2 +- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 1ac3038..25f506a 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ Notes for the tool author: - `isolatedGroups` lists pairs of groups that may not depend on each other in either direction. - `naming` gives the `scope` and `separator` of the package name convention. -The schema rejects an unknown key at every level and checks the rules that relate one part of the layout to another: group names are unique, a rank pattern compiles as a regular expression, and an isolated pair names two different declared groups. Per-rule policy (allow lists, required files and scripts) is not part of the layout; it belongs to the section of the tool that owns the rule. +The schema rejects an unknown key at every level and checks the rules that relate one part of the layout to another: group names are unique, a rank pattern compiles as a regular expression, and an isolated pair names two different declared groups. Messages name the path and the rule and never quote a value from the config. A key written with the value `undefined` is rejected, since the type does not allow it under `exactOptionalPropertyTypes`; `@exadev/eslint-config`'s own reader accepts it. The relational rules run once the shape is valid, so a shape error hides a duplicate group name until it is fixed. Per-rule policy (allow lists, required files and scripts) is not part of the layout; it belongs to the section of the tool that owns the rule. ## Finding sections nobody reads diff --git a/src/layout.ts b/src/layout.ts index 289b32b..f950b50 100644 --- a/src/layout.ts +++ b/src/layout.ts @@ -201,26 +201,26 @@ function isCoherent(layout: LayoutConfig, path: Path, report: Report): boolean { const declared = new Set(); layout.groups.forEach((group, index) => { if (declared.has(group.name)) { - report([...path, 'groups', index, 'name'], `group '${group.name}' is declared more than once`); + report([...path, 'groups', index, 'name'], 'the group name is declared more than once'); coherent = false; } declared.add(group.name); }); layout.nameRanks?.forEach((rule, index) => { if (!isValidRegularExpression(rule.pattern)) { - report([...path, 'nameRanks', index, 'pattern'], `'${rule.pattern}' is not a valid regular expression`); + report([...path, 'nameRanks', index, 'pattern'], 'not a valid regular expression'); coherent = false; } }); layout.isolatedGroups?.forEach((groups, index) => { groups.forEach((name, position) => { if (!declared.has(name)) { - report([...path, 'isolatedGroups', index, position], `'${name}' is not a declared group`); + report([...path, 'isolatedGroups', index, position], 'not a declared group'); coherent = false; } }); if (groups[0] === groups[1]) { - report([...path, 'isolatedGroups', index], `a group cannot be isolated from itself ('${groups[0]}')`); + report([...path, 'isolatedGroups', index], 'a group cannot be isolated from itself'); coherent = false; } }); @@ -233,6 +233,8 @@ const isLayoutConfig: Check = (value, path, report): value is Layo /** * The Standard Schema for {@link LayoutConfig}. It rejects unknown keys at every level, and checks the rules that relate one part of the layout to another. It applies no defaults and returns a valid value as it is. + * + * Three decisions are deliberate. A key present with the value `undefined` is rejected, as the type says under `exactOptionalPropertyTypes`, so `undefined` is never a way to omit a key; the reader in `@exadev/eslint-config` accepts it, and this is the one place the two differ. The relational rules run only on a layout whose shape is valid, so a shape error hides a duplicate group name until it is fixed. Messages name the path and the rule but never quote a value from the config. */ export const layoutSchema: StandardSchemaV1 = standardSchema('@exadev/config', isLayoutConfig); diff --git a/src/layout.unit.test.ts b/src/layout.unit.test.ts index 5a797c8..4ca7baf 100644 --- a/src/layout.unit.test.ts +++ b/src/layout.unit.test.ts @@ -174,14 +174,14 @@ describe('layoutSchema', () => { it('rejects a member that is not a declared group, at its position', async () => { expect(await problemsOf({ groups, isolatedGroups: [['a', 'missing'], ['other', 'b']] })).toEqual([ - { path: ['isolatedGroups', 0, 1], message: "'missing' is not a declared group" }, - { path: ['isolatedGroups', 1, 0], message: "'other' is not a declared group" }, + { path: ['isolatedGroups', 0, 1], message: 'not a declared group' }, + { path: ['isolatedGroups', 1, 0], message: 'not a declared group' }, ]); }); it('rejects a group isolated from itself', async () => { expect(await problemsOf({ groups, isolatedGroups: [['a', 'a']] })).toEqual([ - { path: ['isolatedGroups', 0], message: "a group cannot be isolated from itself ('a')" }, + { path: ['isolatedGroups', 0], message: 'a group cannot be isolated from itself' }, ]); }); @@ -196,15 +196,15 @@ describe('layoutSchema', () => { it('rejects a group name declared more than once, at every declaration after the first', async () => { expect(await problemsOf({ groups: [{ name: 'a' }, { name: 'a' }, { name: 'a' }] })).toEqual([ - { path: ['groups', 1, 'name'], message: "group 'a' is declared more than once" }, - { path: ['groups', 2, 'name'], message: "group 'a' is declared more than once" }, + { path: ['groups', 1, 'name'], message: 'the group name is declared more than once' }, + { path: ['groups', 2, 'name'], message: 'the group name is declared more than once' }, ]); }); it('rejects a rank pattern that is not a regular expression under the u flag', async () => { expect(await problemsOf({ groups: [], nameRanks: [{ pattern: '^ok$', rank: 1 }, { pattern: '(', rank: 1 }, { pattern: '\\-', rank: 1 }] })).toEqual([ - { path: ['nameRanks', 1, 'pattern'], message: "'(' is not a valid regular expression" }, - { path: ['nameRanks', 2, 'pattern'], message: "'\\-' is not a valid regular expression" }, + { path: ['nameRanks', 1, 'pattern'], message: 'not a valid regular expression' }, + { path: ['nameRanks', 2, 'pattern'], message: 'not a valid regular expression' }, ]); }); diff --git a/src/load-section.integration.test.ts b/src/load-section.integration.test.ts index 4ba07e0..188cb29 100644 --- a/src/load-section.integration.test.ts +++ b/src/load-section.integration.test.ts @@ -204,7 +204,7 @@ describe('loadSection', () => { it('validates through a Standard Schema that is not zod', async () => { const cwd = makeProject({ 'exadev.config.ts': "export default { layout: { groups: [{ name: 'core' }, { name: 'core' }] } };\n" }); - await expect(loadSection(layoutSection, { cwd })).rejects.toThrow("groups.1.name: group 'core' is declared more than once"); + await expect(loadSection(layoutSection, { cwd })).rejects.toThrow("groups.1.name: the group name is declared more than once"); }); }); diff --git a/src/validation.ts b/src/validation.ts index 70db639..99d2d7e 100644 --- a/src/validation.ts +++ b/src/validation.ts @@ -11,7 +11,7 @@ export type Path = readonly (string | number)[]; export type Report = (path: Path, message: string) => void; /** - * A type guard that also explains itself. It reports every problem it finds, without stopping at the first, and returns `true` exactly when it reported none. + * A type guard that also explains itself. It reports every problem it finds, without stopping at the first, and returns `true` exactly when it reported none. A guard that relates parts of a value to one another may inspect them only once each part is valid, so it can report fewer problems than the value has. */ export type Check = (value: unknown, path: Path, report: Report) => value is T; From 748092254ce05cc5af11f6e055005753581f4048 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Tue, 29 Sep 2026 23:44:13 +0100 Subject: [PATCH 20/23] test: stop the alias test re-evaluating the source, which hid the coverage of the files it loads The alias shim re-exported this package's source, so jiti evaluated a second copy that coverage attributed to the same files, reporting covered functions as uncovered. The shim is a stand-in now, since the test is about where the import resolves. Hooks get the same timeout as tests, because the authoring type test compiles its fixtures in one. --- src/load-section.integration.test.ts | 5 +++-- vitest.config.ts | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/load-section.integration.test.ts b/src/load-section.integration.test.ts index 188cb29..4f749eb 100644 --- a/src/load-section.integration.test.ts +++ b/src/load-section.integration.test.ts @@ -1,4 +1,4 @@ -import { join, resolve } from 'node:path'; +import { join } from 'node:path'; import { ConfigValidationError, type Merge } from 'cosmiconfig-extends'; import { describe, expect, it } from 'vitest'; @@ -273,7 +273,8 @@ describe('loadSection', () => { describe('authoring files', () => { it('loads a file that imports @exadev/config through the alias, as an authoring file does', async () => { const cwd = makeProject({ - 'shim/index.ts': `export * from ${JSON.stringify(resolve(import.meta.dirname, 'index.ts'))};\n`, + // A stand-in that does not evaluate this package's source a second time: coverage would attribute that copy to the same files and hide the lines the tests do cover. The test is about where the import resolves to. + 'shim/index.ts': ['export const layoutSection = { name: \'layout\', schema: {} };', 'export const withSections = () => (config: unknown) => config;', ''].join('\n'), 'node_modules/@acme/tool-a/index.js': "export const toolA = { name: 'toolA', schema: {} };\n", 'node_modules/@acme/tool-a/package.json': JSON.stringify({ name: '@acme/tool-a', type: 'module', main: 'index.js' }), 'exadev.config.ts': [ diff --git a/vitest.config.ts b/vitest.config.ts index 89fc222..a449224 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,9 +1,12 @@ import { defineConfig, type ViteUserConfig } from 'vitest/config'; +// Every case that loads a TypeScript file pays jiti's first-transpile cost, and the interop cases spawn a second cosmiconfig major. The authoring type test compiles every fixture with the TypeScript compiler in a hook, so hooks get the same allowance. +const SLOW_WORK_TIMEOUT_MS = 30_000; + const config: ViteUserConfig = defineConfig({ test: { - // Every case that loads a TypeScript file pays jiti's first-transpile cost, and the interop cases spawn a second cosmiconfig major. - testTimeout: 30_000, + testTimeout: SLOW_WORK_TIMEOUT_MS, + hookTimeout: SLOW_WORK_TIMEOUT_MS, coverage: { enabled: true, provider: 'v8', From b02b499f46bd8c08fdc457ab421f8ad29791f764 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Wed, 30 Sep 2026 00:00:04 +0100 Subject: [PATCH 21/23] fix: reject a malformed package.json in doctor instead of ignoring it A project package.json whose dependency fields were not objects, and an installed package.json that was not an object, were skipped, so doctor reported a clean result for input it could not read. Both are now validated and named in the error, with every problem listed. The dependency fields of installed packages are still not read. --- src/doctor.integration.test.ts | 54 +++++++++++++++----- src/doctor.ts | 90 ++++++++++++++++++++-------------- src/validation.ts | 26 ++++++++++ src/validation.unit.test.ts | 31 ++++++++++++ 4 files changed, 151 insertions(+), 50 deletions(-) diff --git a/src/doctor.integration.test.ts b/src/doctor.integration.test.ts index 793dc3f..4ee50fd 100644 --- a/src/doctor.integration.test.ts +++ b/src/doctor.integration.test.ts @@ -14,7 +14,8 @@ function project(dependencies: Record, config = 'export default return { 'package.json': JSON.stringify(dependencies), 'exadev.config.ts': config }; } -const declares = (...sections: readonly string[]): Record => ({ [MANIFEST_FIELD]: { sections } }); +// The field name is written out, not taken from MANIFEST_FIELD, so that a change to the constant is a change these tests see. +const declares = (...sections: readonly string[]): Record => ({ exadevConfig: { sections } }); describe('doctor', () => { it('reports nothing for a directory with no unified config, and still lists the built-in section', async () => { @@ -97,9 +98,32 @@ describe('doctor', () => { expect(await doctor({ cwd })).toMatchObject({ unowned: ['x'], known: ['layout'] }); }); - it('ignores a package.json whose dependency fields are not objects, and one that is not an object', async () => { - expect((await doctor({ cwd: makeProject(project({ dependencies: ['a'], devDependencies: 'b' })) })).known).toEqual(['layout']); - expect((await doctor({ cwd: makeProject({ 'package.json': '[]', 'exadev.config.ts': 'export default {};\n' }) })).known).toEqual(['layout']); + it('reads the manifest field under the documented name', () => { + expect(MANIFEST_FIELD).toBe('exadevConfig'); + }); + + it.each([ + ['a dependency field that is not an object', { dependencies: ['a'] }, 'dependencies: expected an object'], + ['a dependency field that is a string', { devDependencies: 'b' }, 'devDependencies: expected an object'], + ])('rejects a project package.json with %s', async (_name, packageJson, problem) => { + const cwd = makeProject(project(packageJson)); + + await expect(doctor({ cwd })).rejects.toThrow(new TypeError(`the project's package.json is invalid:\n ${problem}`)); + }); + + it('rejects a project package.json that is not an object, and an installed one', async () => { + await expect(doctor({ cwd: makeProject({ 'package.json': '[]', 'exadev.config.ts': 'export default {};\n' }) })).rejects.toThrow( + new TypeError("the project's package.json is invalid:\n (root): expected an object"), + ); + await expect(doctor({ cwd: makeProject({ ...project({ dependencies: { a: '1' } }), 'node_modules/a/package.json': '[]' }) })).rejects.toThrow( + new TypeError("the package.json of 'a' is invalid:\n (root): expected an object"), + ); + }); + + it('does not read the dependency fields of an installed package', async () => { + const cwd = makeProject({ ...project({ dependencies: { a: '1' } }, 'export default {};\n'), ...installed('a', { dependencies: 'not an object' }) }); + + expect((await doctor({ cwd })).known).toEqual(['layout']); }); it('treats a directory with no package.json as having no installed tools', async () => { @@ -167,29 +191,35 @@ describe('doctor', () => { }); it('names the project when its own manifest is malformed', async () => { - const cwd = makeProject(project({ [MANIFEST_FIELD]: {} })); + const cwd = makeProject(project({ exadevConfig: {} })); - await expect(doctor({ cwd })).rejects.toThrow(new TypeError(`invalid '${MANIFEST_FIELD}' in the project's package.json:\n sections: required`)); + await expect(doctor({ cwd })).rejects.toThrow(new TypeError("the project's package.json is invalid:\n exadevConfig.sections: required")); }); it('ignores a key of the manifest that it does not know, so a later manifest version does not break an older doctor', async () => { const cwd = makeProject({ ...project({ dependencies: { 'tool-a': '1' } }, 'export default { toolA: {} };\n'), - ...installed('tool-a', { [MANIFEST_FIELD]: { sections: ['toolA'], version: 2 } }), + ...installed('tool-a', { exadevConfig: { sections: ['toolA'], version: 2 } }), }); expect((await doctor({ cwd })).unowned).toEqual([]); }); describe('a malformed manifest', () => { + it('lists every problem, one to a line', async () => { + const cwd = makeProject({ ...project({ dependencies: { 'tool-a': '1' } }), ...installed('tool-a', { exadevConfig: { sections: [1, 2] } }) }); + + await expect(doctor({ cwd })).rejects.toThrow(new TypeError("the package.json of 'tool-a' is invalid:\n exadevConfig.sections.0: expected a string\n exadevConfig.sections.1: expected a string")); + }); + it.each([ - ['a string', 'sections', '(root): expected an object'], - ['no sections key', {}, 'sections: required'], - ['a non-string section', { sections: ['a', 2] }, 'sections.1: expected a string'], + ['a string', 'sections', ': expected an object'], + ['no sections key', {}, '.sections: required'], + ['a non-string section', { sections: ['a', 2] }, '.sections.1: expected a string'], ])('with %s is an error naming the package and the problem', async (_name, manifest, problem) => { - const cwd = makeProject({ ...project({ dependencies: { 'tool-a': '1' } }), ...installed('tool-a', { [MANIFEST_FIELD]: manifest }) }); + const cwd = makeProject({ ...project({ dependencies: { 'tool-a': '1' } }), ...installed('tool-a', { exadevConfig: manifest }) }); - await expect(doctor({ cwd })).rejects.toThrow(new TypeError(`invalid '${MANIFEST_FIELD}' in the package.json of 'tool-a':\n ${problem}`)); + await expect(doctor({ cwd })).rejects.toThrow(new TypeError(`the package.json of 'tool-a' is invalid:\n exadevConfig${problem}`)); }); }); }); diff --git a/src/doctor.ts b/src/doctor.ts index d84c4c1..b8c79b4 100644 --- a/src/doctor.ts +++ b/src/doctor.ts @@ -3,7 +3,7 @@ import { dirname, join } from 'node:path'; import { assertDirectory, type ConfigFileOptions, findConfigFile, readUnifiedFile, UNIFIED_BASE } from './config-file'; import { layoutSection } from './layout'; -import { array, type Check, isRecord, isString, looseObject, required } from './validation'; +import { array, type Check, isObject, isString, looseObject, optional, required, validated } from './validation'; /** * The `package.json` field through which a tool package declares the section names it owns: `"exadevConfig": { "sections": ["eslint"] }`. It is how `doctor` learns which sections an installed tool reads, without importing the tool. @@ -17,7 +17,36 @@ interface Manifest { // Loose, not strict: a future version of this package may add a key that an older doctor must not reject in every consumer that installs the tool. const isManifest: Check = looseObject({ sections: required(array(isString)) }); -const DEPENDENCY_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'] as const; +type Dependencies = Readonly>; + +/** + * The part of a `package.json` that the tool packages installed for a project are read for. + */ +interface ManifestHolder { + readonly [MANIFEST_FIELD]?: Manifest; +} + +/** + * The part of the project's own `package.json` that is read: the manifest field, and the dependency fields that name the installed tools. + */ +interface ProjectPackage extends ManifestHolder { + readonly dependencies?: Dependencies; + readonly devDependencies?: Dependencies; + readonly peerDependencies?: Dependencies; + readonly optionalDependencies?: Dependencies; +} + +const DEPENDENCY_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'] as const satisfies readonly (keyof ProjectPackage)[]; + +const holdsManifest: Check = looseObject({ [MANIFEST_FIELD]: optional(isManifest) }); + +const isProjectPackage: Check = looseObject({ + [MANIFEST_FIELD]: optional(isManifest), + dependencies: optional(isObject), + devDependencies: optional(isObject), + peerDependencies: optional(isObject), + optionalDependencies: optional(isObject), +}); /** * Options for {@link doctor}. @@ -76,48 +105,29 @@ function findInstalledManifest(cwd: string, name: string): unknown { } } -function dependencyNames(packageJson: unknown): readonly string[] { - if (!isRecord(packageJson)) { - return []; - } - - return DEPENDENCY_FIELDS.flatMap((field) => { - const dependencies = packageJson[field]; - - return isRecord(dependencies) ? Object.keys(dependencies) : []; - }); -} - -function declaredSections(owner: string, packageJson: unknown): readonly string[] { - if (!isRecord(packageJson) || !Object.hasOwn(packageJson, MANIFEST_FIELD)) { - return []; - } - const manifest = packageJson[MANIFEST_FIELD]; - const problems: string[] = []; - if (!isManifest(manifest, [], (path, message) => { - problems.push(`${path.join('.') || '(root)'}: ${message}`); - })) { - throw new TypeError(`invalid '${MANIFEST_FIELD}' in ${owner}:\n${problems.map((problem) => ` ${problem}`).join('\n')}`); - } - - return manifest.sections; +/** + * The names of the dependencies of `project` in every dependency field, which may repeat. + */ +function dependencyNames(project: ProjectPackage): readonly string[] { + return DEPENDENCY_FIELDS.flatMap((field) => Object.keys({ ...project[field] })); } /** - * The section names that the project in `cwd` and the packages installed for it declare through {@link MANIFEST_FIELD}; the project's own declaration is how a tool repository owns the section it dogfoods. A dependency that is not installed, or declares none, contributes nothing. + * The section names that the project in `cwd` and the packages installed for it declare through {@link MANIFEST_FIELD}; the project's own declaration is how a tool repository owns the section it dogfoods. A dependency that is not installed, or declares none, contributes nothing. Throws when a `package.json` it reads is not an object, or when a manifest or the project's dependency fields are malformed. */ function installedSections(cwd: string): readonly string[] { const projectFile = join(cwd, 'package.json'); if (statSync(projectFile, { throwIfNoEntry: false })?.isFile() !== true) { return []; } + const project = validated(isProjectPackage, readJson(projectFile), "the project's package.json"); + const installed = dependencyNames(project).flatMap((name) => { + const manifest = findInstalledManifest(cwd, name); - const project = readJson(projectFile); + return manifest === undefined ? [] : (validated(holdsManifest, manifest, `the package.json of '${name}'`)[MANIFEST_FIELD]?.sections ?? []); + }); - return [ - ...declaredSections("the project's package.json", project), - ...dependencyNames(project).flatMap((name) => declaredSections(`the package.json of '${name}'`, findInstalledManifest(cwd, name))), - ]; + return [...(project[MANIFEST_FIELD]?.sections ?? []), ...installed]; } /** @@ -125,15 +135,19 @@ function installedSections(cwd: string): readonly string[] { * * Typing cannot catch these: a section for a tool that is not installed, or under a misspelt name, is simply ignored by every tool. A section is owned when it is `layout`, when a dependency of the project in `cwd` declares it through {@link MANIFEST_FIELD}, or when it is in `options.listed`. * - * Throws when `cwd` is not an existing directory, when the config file cannot be loaded, when it does not export an object, or when an installed dependency's {@link MANIFEST_FIELD} is malformed. + * Throws when `cwd` is not an existing directory, when the config file cannot be loaded, when it does not export an object, or when a `package.json` it reads is not an object or has a malformed {@link MANIFEST_FIELD} or dependency field. */ export async function doctor(options: DoctorOptions): Promise { assertDirectory(options.cwd); - const file = findConfigFile(options.cwd, UNIFIED_BASE); - const config = file === undefined ? undefined : await readUnifiedFile(file, options); const known = [...new Set([layoutSection.name, ...installedSections(options.cwd), ...(options.listed ?? [])])].sort(); - if (file === undefined || config === undefined) { - return { file: undefined, defined: [], unowned: [], known }; + const nothingDefined: DoctorReport = { file: undefined, defined: [], unowned: [], known }; + const file = findConfigFile(options.cwd, UNIFIED_BASE); + if (file === undefined) { + return nothingDefined; + } + const config = await readUnifiedFile(file, options); + if (config === undefined) { + return nothingDefined; } const defined = Object.keys(config); diff --git a/src/validation.ts b/src/validation.ts index 99d2d7e..0f28f2d 100644 --- a/src/validation.ts +++ b/src/validation.ts @@ -37,6 +37,18 @@ export function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } +/** + * Accepts any non-null, non-array object, without looking at its properties. + */ +export const isObject: Check>> = (value, path, report): value is Readonly> => { + if (isRecord(value)) { + return true; + } + report(path, 'expected an object'); + + return false; +}; + function isInteger(value: unknown): value is number { return Number.isInteger(value); } @@ -207,3 +219,17 @@ export function standardSchema(vendor: string, check: Check): StandardSche }, }; } + +/** + * Return `value` when `check` accepts it. Otherwise throw a `TypeError` that names `subject` and lists every problem as `path: message`, the path being empty for the value itself. + */ +export function validated(check: Check, value: unknown, subject: string): T { + const problems: string[] = []; + if (!check(value, [], (path, message) => { + problems.push(`${path.join('.') || '(root)'}: ${message}`); + })) { + throw new TypeError(`${subject} is invalid:\n${problems.map((problem) => ` ${problem}`).join('\n')}`); + } + + return value; +} diff --git a/src/validation.unit.test.ts b/src/validation.unit.test.ts index 25b6c7b..9f47f33 100644 --- a/src/validation.unit.test.ts +++ b/src/validation.unit.test.ts @@ -4,6 +4,7 @@ import { array, type Check, integer, + isObject, isRecord, isString, literal, @@ -14,6 +15,7 @@ import { required, standardSchema, strictObject, + validated, } from './validation'; const NEGATIVE = -1; @@ -236,3 +238,32 @@ describe('standardSchema', () => { }); }); }); + +describe('isObject', () => { + it('accepts any object whatever its properties', () => { + expect(run(isObject, {}).valid).toBe(true); + expect(run(isObject, { anything: 1 }).valid).toBe(true); + }); + + it.each([[null], [[]], ['a'], [1], [undefined]])('rejects %j as not an object', (value) => { + expect(run(isObject, value)).toEqual({ valid: false, problems: [{ path: [], message: 'expected an object' }] }); + }); +}); + +describe('validated', () => { + const isSample = strictObject<{ readonly a: string; readonly b: string }>({ a: required(isString), b: required(isString) }); + + it('returns the value it was given when the check accepts it', () => { + const value = { a: 'x', b: 'y' }; + + expect(validated(isSample, value, 'the sample')).toBe(value); + }); + + it('throws a TypeError naming the subject and listing each problem with its path on its own line', () => { + expect(() => validated(isSample, { a: 1, b: 2 }, 'the sample')).toThrow(new TypeError('the sample is invalid:\n a: expected a string\n b: expected a string')); + }); + + it('names the value itself as (root)', () => { + expect(() => validated(isSample, 'text', 'the sample')).toThrow(new TypeError('the sample is invalid:\n (root): expected an object')); + }); +}); From 0cb0b09d53cd1975f7a6ec5444ee06311f901f48 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Wed, 30 Sep 2026 00:00:11 +0100 Subject: [PATCH 22/23] test: kill the surviving mutants of the section, layout and command code Descriptors in the section unit tests are built in a hook, because a module level call that throws fails the suite at load with no failing test, which a mutation run counts as a survivor. The layout schema vendor and the manifest field name are asserted. The explorer no longer sets a cache option that a fresh explorer per call made unobservable, and takes its module name from the unified file base, so the two cannot drift. The command resolves the working directory through process.cwd, whose fallback was indistinguishable from resolving an empty path. --- src/config-file.ts | 6 +++--- src/doctor-command.ts | 2 +- src/layout.unit.test.ts | 6 ++++++ src/section.unit.test.ts | 14 +++++++++++--- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/config-file.ts b/src/config-file.ts index f928a9f..8ac8d7c 100644 --- a/src/config-file.ts +++ b/src/config-file.ts @@ -78,13 +78,13 @@ function requireDefaultExport(loader: Loader): Loader { } /** - * The evaluated content of the config file at `file` after `extends` is applied, or `undefined` when the file is empty or has `undefined` as its default export. Throws when the file does not export a default value. Every call reads the file afresh. + * The evaluated content of the config file at `file` after `extends` is applied, or `undefined` when the file is empty or has `undefined` as its default export. Throws when the file does not export a default value. Every call reads the file afresh, because it builds an explorer of its own and so shares no cache. */ export async function readConfigFile(file: string, options: ConfigFileOptions): Promise { const checked = requireDefaultExport(createJitiLoader(options).loader); - const explorer = createExplorer('exadev', { + const explorer = createExplorer(UNIFIED_BASE, { ...options, - cosmiconfig: { cache: false, loaders: { '.ts': checked, '.mts': checked, '.cts': checked } }, + cosmiconfig: { loaders: { '.ts': checked, '.mts': checked, '.cts': checked } }, }); const result = await explorer.load(file); diff --git a/src/doctor-command.ts b/src/doctor-command.ts index 4d12f02..6930145 100644 --- a/src/doctor-command.ts +++ b/src/doctor-command.ts @@ -42,7 +42,7 @@ async function runDoctor(args: readonly string[], output: CommandOutput): Promis return EXIT_CODES.clean; } - const cwd = resolve(values.cwd ?? '.'); + const cwd = resolve(values.cwd ?? process.cwd()); const report = await doctor({ cwd, listed: values.section ?? [] }); if (report.file === undefined) { output.stdout(`${cwd}: nothing to check, since exadev.config.ts is absent or empty there. The command does not search parent directories.\n`); diff --git a/src/layout.unit.test.ts b/src/layout.unit.test.ts index 4ca7baf..e30144e 100644 --- a/src/layout.unit.test.ts +++ b/src/layout.unit.test.ts @@ -212,3 +212,9 @@ describe('layoutSchema', () => { expect(await problemsOf({ groups: [{ name: 'a' }, { name: 'a', rank: 'x' }] })).toEqual([{ path: ['groups', 1, 'rank'], message: 'expected an integer' }]); }); }); + +describe('layoutSchema', () => { + it('declares the vendor that names this package', () => { + expect(layoutSchema['~standard'].vendor).toBe('@exadev/config'); + }); +}); diff --git a/src/section.unit.test.ts b/src/section.unit.test.ts index ff6a43e..3a8ec8d 100644 --- a/src/section.unit.test.ts +++ b/src/section.unit.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, expectTypeOf, it } from 'vitest'; +import { beforeEach, describe, expect, expectTypeOf, it } from 'vitest'; import { z } from 'zod'; import { type Config, type ConfigOf, defineConfig, defineSection, type Envelope, type Section, type SectionsOf, withSections } from './section'; @@ -13,8 +13,16 @@ const toolAConfigSchema: z.ZodType = z.strictObject({ level: z.exactOptional(z.enum(['low', 'high'])), }); -const toolA: Section<'toolA', z.ZodType> = defineSection('toolA', toolAConfigSchema); -const toolB = defineSection('toolB', z.strictObject({ rules: z.record(z.string(), z.string()) })); +const toolBConfigSchema = z.strictObject({ rules: z.record(z.string(), z.string()) }); + +// Built in a hook, not at module level: when a change to defineSection makes it throw, the suite would fail to load and report no failing test, which a mutation run reads as a surviving mutant. +let toolA: Section<'toolA', z.ZodType>; +let toolB: Section<'toolB', typeof toolBConfigSchema>; + +beforeEach(() => { + toolA = defineSection('toolA', toolAConfigSchema); + toolB = defineSection('toolB', toolBConfigSchema); +}); /** * The same string with its literal type widened, so a call the compiler refuses for the literal reaches the runtime check. From 7c57cbef98e1d37c5e843d72fb0fc3b5187fd908 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Wed, 30 Sep 2026 00:12:26 +0100 Subject: [PATCH 23/23] ci: run mutation testing on manual dispatch only The mutation job no longer runs on pull requests or pushes and no longer gates required checks or the release job. The script and its threshold are unchanged for manual runs. --- .github/workflows/ci.yml | 6 ++++-- README.md | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aaebfff..2a6f0d8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,6 +87,8 @@ jobs: mutation: name: Mutation + # Manual only: run it from the Actions tab (workflow_dispatch) when a change warrants a full mutation pass. It is not a required check and does not gate a release. + if: github.event_name == 'workflow_dispatch' needs: determine-runner runs-on: ${{ fromJson(needs.determine-runner.outputs.selected-runner) }} timeout-minutes: 60 @@ -148,7 +150,7 @@ jobs: required-checks: name: Required Checks - needs: [determine-runner, commitlint, lint, typecheck, package, mutation] + needs: [determine-runner, commitlint, lint, typecheck, package] # Always runs, even if a dependency failed, so it reports that failure itself rather than being skipped: a required check that is silently absent blocks a PR indefinitely instead of failing it visibly. if: always() runs-on: ${{ fromJson(needs.determine-runner.outputs.selected-runner) }} @@ -164,7 +166,7 @@ jobs: release: name: Release - needs: [commitlint, lint, typecheck, package, mutation] + needs: [commitlint, lint, typecheck, package] if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') # Deliberately a GitHub-hosted runner, not the resolved one: npm trusted publishing only works from GitHub-hosted runners, so the OIDC token exchange cannot happen on the self-hosted fleet or Blacksmith. runs-on: ubuntu-latest diff --git a/README.md b/README.md index 25f506a..4725980 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ pnpm lint:check # eslint, check only; what CI runs pnpm typecheck pnpm test # vitest, with coverage pnpm build # tsdown: ESM, CJS and declarations -pnpm test:mutation # Stryker, with a 100% break threshold +pnpm test:mutation # Stryker, with a 100% break threshold; CI runs it on manual dispatch only ``` CI also runs `pnpm exec publint` and `pnpm exec attw --pack` after the build, and installs the packed tarball into a scratch project on each supported Node line and both cosmiconfig majors (`test/package/install-check.sh`), where it loads sections as ESM and as CommonJS and runs the command from the ESM check.