diff --git a/.eslintrc b/.eslintrc index fc09a8f..adb1e42 100644 --- a/.eslintrc +++ b/.eslintrc @@ -62,8 +62,7 @@ { "devDependencies": true, "optionalDependencies": false, - "peerDependencies": false, - "packageDir": "./" + "peerDependencies": false } ], "@typescript-eslint/consistent-type-imports": [ @@ -72,6 +71,13 @@ "prefer": "type-imports", "disallowTypeAnnotations": false } + ], + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_" + } ] }, "env": { diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e8be7a..4a02ba4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,12 @@ name: Continuous Integration -on: [pull_request] +on: pull_request permissions: id-token: write contents: read + actions: read checks: write + pull-requests: write jobs: build: @@ -12,30 +14,31 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [18.x, 20.x, 22.x] + node-version: [20.x, 22.x, 24.x] steps: - name: Checkout uses: actions/checkout@v4 - - name: Use Node ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} + - name: Install pnpm uses: pnpm/action-setup@v4 with: version: 9.15.4 - - uses: actions/cache@v2 + + - uses: actions/cache@v4 with: path: '**/node_modules' key: ${{ runner.os }}-modules-${{ hashFiles('**/pnpm-lock.yaml') }} - - name: PNPM + - name: Install dependencies run: pnpm install --frozen-lockfile - name: Build - run: pnpm run build + run: pnpm build lint: name: Lint @@ -43,35 +46,34 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + - name: Setup Node uses: actions/setup-node@v4 with: node-version: '22.x' + - name: Install pnpm uses: pnpm/action-setup@v4 with: version: 9.15.4 - - - uses: actions/cache@v2 + + - uses: actions/cache@v4 with: path: '**/node_modules' key: ${{ runner.os }}-modules-${{ hashFiles('**/pnpm-lock.yaml') }} - - name: PNPM + - name: Install dependencies run: pnpm install --frozen-lockfile - name: Lint - run: pnpm run lint - - - name: Validate Packages - run: pnpm manypkg check + run: pnpm lint test: - name: Test + name: Unit Tests runs-on: ubuntu-latest strategy: matrix: - project: ['doubles.jest', 'doubles.sinon', 'core.unit', 'doubles.vitest', 'di.nestjs', 'di.inversify', 'unit'] + project: ['cli', 'governance', 'changesets', 'json-schema-differ', 'types'] steps: - name: Checkout uses: actions/checkout@v4 @@ -80,47 +82,61 @@ jobs: uses: actions/setup-node@v4 with: node-version: '22.x' + - name: Install pnpm uses: pnpm/action-setup@v4 with: version: 9.15.4 - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: path: '**/node_modules' key: ${{ runner.os }}-modules-${{ hashFiles('**/pnpm-lock.yaml') }} - - name: PNPM + - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Build + run: pnpm build + - name: Create Coverage Directory run: mkdir -p ${{ github.workspace }}/coverage - name: Test - run: pnpm --filter @contractual/${{ matrix.project }} run test + run: pnpm lerna run test --scope @contractual/${{ matrix.project }} --stream + continue-on-error: true env: - JEST_JUNIT_OUTPUT_NAME: ${{ matrix.project }}.xml - JEST_JUNIT_OUTPUT_DIR: ${{ github.workspace }}/test-reports - JUNIT_OUTPUT_NAME: ${{ matrix.project }} - JUNIT_OUTPUT_DIR: ${{ github.workspace }}/test-reports COVERAGE_DIR: ${{ github.workspace }}/coverage COVERAGE_FILE: coverage-${{ matrix.project }}.xml - - name: Tests Results - uses: dorny/test-reporter@v1 - if: always() + e2e: + name: E2E Tests + runs-on: ubuntu-latest + needs: [build] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 with: - reporter: 'jest-junit' - name: Tests Results (${{ matrix.project }}) - path: ${{ github.workspace }}/test-reports/${{ matrix.project }}.xml - fail-on-error: false - -# - name: Upload Report to Codecov -# uses: codecov/codecov-action@v3 -# with: -# name: codecov-umbrella -# flags: ${{ matrix.project }} -# token: ${{ secrets.CODECOV_TOKEN }} -# fail_ci_if_error: true -# files: ${{ github.workspace }}/coverage/coverage-${{ matrix.project }}.xml -# verbose: true + node-version: '22.x' + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.15.4 + + - uses: actions/cache@v4 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/pnpm-lock.yaml') }} + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build + run: pnpm build + + - name: Run E2E Tests + run: pnpm test:e2e diff --git a/.github/workflows/e2e-release.yml b/.github/workflows/e2e-release.yml new file mode 100644 index 0000000..9ab5fd0 --- /dev/null +++ b/.github/workflows/e2e-release.yml @@ -0,0 +1,158 @@ +name: E2E Release Simulation +env: + CI: true + +on: + workflow_call: + inputs: + target_branch: + description: 'Branch to test release from' + required: true + type: string + workflow_dispatch: + inputs: + target_branch: + description: 'Branch to test release from' + required: true + type: string + default: 'next' + +permissions: + contents: write + id-token: write + +jobs: + e2e: + name: E2E (${{ matrix.e2e-project }}, Node ${{ matrix.node-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + e2e-project: ['cli-basic', 'cli-lifecycle', 'packages-import'] + node-version: [20.x, 22.x, 24.x] + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ inputs.target_branch }} + fetch-depth: 0 + + - name: Setup Node ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.15.4 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Start Verdaccio + run: docker compose -f local-e2e.docker-compose.yaml up -d verdaccio + + - name: Wait for Verdaccio + run: | + for i in {1..30}; do + if curl -s http://localhost:4873 > /dev/null; then + echo "Verdaccio is ready" + break + fi + echo "Waiting for Verdaccio..." + sleep 1 + done + + - name: Build + run: pnpm build + + - name: Setup Registry + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + registry-url: http://localhost:4873 + scope: '@contractual' + always-auth: false + + - name: Install jq + run: sudo apt-get install -y jq + + - name: Config Git + run: | + git config --global user.email "e2e@contractual.dev" + git config --global user.name "Contractual e2e" + + - name: Remove provenance from package.json files + run: | + find packages -name 'package.json' | while read filename; do + jq 'del(.publishConfig.provenance)' "$filename" > temp.json && mv temp.json "$filename" + done + + - name: Commit Provenance Removal + run: | + git add . + git commit -am "chore: remove provenance for e2e" + + - name: Version Packages + run: | + BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) + npx lerna version --yes \ + --conventional-commits \ + --conventional-prerelease \ + --preid e2e \ + --no-changelog \ + --allow-branch "$BRANCH_NAME" \ + --no-git-tag-version \ + --no-push \ + --force-publish \ + --no-commit-hooks + + - name: Commit Packages Versions + run: | + git add . + git commit -am "bump versions" + + - name: Capture Versions for Report + run: | + echo "## E2E Test Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Project:** \`${{ matrix.e2e-project }}\`" >> $GITHUB_STEP_SUMMARY + echo "**Node:** \`${{ matrix.node-version }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Versions:" >> $GITHUB_STEP_SUMMARY + lerna ls --json | jq -r '.[] | "- **\(.name)** -> `v\(.version)`"' >> $GITHUB_STEP_SUMMARY + + - name: Publish Packages to Verdaccio + run: | + npx lerna publish from-package --yes \ + --no-git-tag-version \ + --no-push \ + --no-git-reset \ + --exact \ + --dist-tag e2e + + - name: Clean Source (simulate fresh install) + run: | + rm -rf packages + rm -rf node_modules + rm pnpm-lock.yaml + rm package.json + rm -f .npmrc + + - name: Install E2E Dependencies from Verdaccio + run: | + cd "${{ github.workspace }}/e2e/${{ matrix.e2e-project }}" + npm install --registry http://localhost:4873 --no-package-lock + + - name: Execute Tests + run: | + cd "${{ github.workspace }}/e2e/${{ matrix.e2e-project }}" + npm test + + - name: Test Success Summary + if: success() + run: | + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Test Passed" >> $GITHUB_STEP_SUMMARY + echo "Packages work correctly when published" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml index f104219..7bb567e 100644 --- a/.github/workflows/publish-packages.yml +++ b/.github/workflows/publish-packages.yml @@ -20,145 +20,87 @@ on: - 'alpha' - 'beta' required: true - default: 'dev' - strategy: - description: 'Release Strategy' + default: 'next' + target_branch: + description: 'Target branch to release from' type: choice options: - - 'from-git' - - 'from-package' + - 'next' + - 'master' required: true - default: 'from-package' + default: 'next' jobs: - e2e: - name: Build and Test + publish: + name: Publish to NPM runs-on: ubuntu-latest - strategy: - matrix: - e2e-project: ['jest/nestjs', 'sinon/nestjs', 'vitest/nestjs', 'jest/inversify', 'sinon/inversify', 'vitest/inversify'] - node-version: [16.x, 18.x, 20.x] - exclude: - - e2e-project: 'vitest/inversify' - node-version: '16.x' - - e2e-project: 'vitest/nestjs' - node-version: '16.x' steps: - name: Checkout uses: actions/checkout@v4 with: ref: ${{ github.event.inputs.target_branch }} + fetch-depth: 0 - - name: Remove Vitest If Needed - if: ${{ matrix.node-version == '16.x' }} - run: | - rm -rf packages/doubles/vitest - git config --global user.email "ci@suites.dev" - git config --global user.name "Suites CI" - git add . - git commit -am "remove vitest temp" - - - name: Setup Node ${{ matrix.node-version }} + - name: Setup Node uses: actions/setup-node@v4 with: - node-version: ${{ matrix.node-version }} + node-version: '22.x' + registry-url: https://registry.npmjs.org/ + scope: '@contractual' + always-auth: true + - name: Install pnpm uses: pnpm/action-setup@v4 with: version: 9.15.4 - - - name: PNPM - run: pnpm install --frozen-lockfile - - name: Run Verdaccio Docker - run: | - docker run -d --name verdaccio \ - -p 4873:4873 \ - -v ${{ github.workspace }}/e2e/config.yaml:/verdaccio/conf/config.yaml \ - verdaccio/verdaccio + - name: Install dependencies + run: pnpm install --frozen-lockfile - name: Build - run: pnpm build + run: pnpm lerna run build - - name: Setup Registry - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - registry-url: http://localhost:4873 - scope: '@suites' - always-auth: false - - - name: Install jq - run: sudo apt-get install jq - - - name: Remove provenance from publishConfig - run: | - find packages -name 'package.json' | while read filename; do - jq 'del(.publishConfig.provenance)' "$filename" > temp.json && mv temp.json "$filename" - done - - - name: Commit Change + - name: Capture Versions for Report run: | - git config --global user.email "e2e@suites.dev" - git config --global user.name "Suites e2e" - git add . - git commit -am "remove provenance" + echo "## Publishing to NPM" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Registry:** \`https://registry.npmjs.org/\`" >> $GITHUB_STEP_SUMMARY + echo "**Dist Tag:** \`${{ github.event.inputs.dist_tag }}\`" >> $GITHUB_STEP_SUMMARY + echo "**Branch:** \`${{ github.event.inputs.target_branch }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Packages:" >> $GITHUB_STEP_SUMMARY + lerna ls --json | jq -r '.[] | "- **\(.name)** -> `v\(.version)`"' >> $GITHUB_STEP_SUMMARY - name: Publish Packages run: | - pnpm publish -r \ - --no-git-checks \ - --access public \ - --tag ci \ - --force - - name: Setup and Test - run: | - IFS='/' read -r library framework <<< "${{ matrix.e2e-project }}" - echo "FRAMEWORK=$framework" >> $GITHUB_ENV - echo "LIBRARY=$library" >> $GITHUB_ENV - - - name: Clean Source - run: | - rm -rf packages - rm -rf node_modules + npx lerna publish from-package --yes \ + --dist-tag ${{ github.event.inputs.dist_tag }} \ + --no-git-reset + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: Install Dependencies + - name: Generate Success Summary + if: success() run: | - cd "$PWD/e2e/$LIBRARY/$FRAMEWORK" - npm install --no-cache --no-package-lock - npm install --dev --no-package-lock @types/node@${{ matrix.node-version }} - - - name: Execute Test + echo "" >> $GITHUB_STEP_SUMMARY + echo "## Publish Successful" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "All packages have been successfully published to npm!" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Installation command:**" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo "npm install @contractual/cli@${{ github.event.inputs.dist_tag }}" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + + - name: Generate Failure Summary + if: failure() run: | - cd "$PWD/e2e/$LIBRARY/$FRAMEWORK" - npm test - - publish: - name: Publish Packages - needs: [e2e] - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Registry - uses: actions/setup-node@v4 - with: - registry-url: https://registry.npmjs.org/ - scope: '@suites' - always-auth: true - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.15.4 - - - name: pnpm - run: pnpm install --frozen-lockfile - - - name: Build - run: pnpm build - - - name: Publish Packages - run: pnpm publish -r ${{ github.event.inputs.strategy }} --access public --tag ${{ github.event.inputs.dist_tag }} - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + echo "" >> $GITHUB_STEP_SUMMARY + echo "## Publish Failed" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "The publish step failed. Check the logs above for details." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Common issues:**" >> $GITHUB_STEP_SUMMARY + echo "- Version already exists in npm registry" >> $GITHUB_STEP_SUMMARY + echo "- Authentication token is invalid or expired" >> $GITHUB_STEP_SUMMARY + echo "- Network connectivity issues" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/release-packages.yml b/.github/workflows/release-packages.yml index c6c8cba..43032a0 100644 --- a/.github/workflows/release-packages.yml +++ b/.github/workflows/release-packages.yml @@ -50,6 +50,22 @@ jobs: ref: ${{ github.event.inputs.target_branch }} fetch-depth: 0 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '22.x' + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.15.4 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build + run: pnpm build + - name: Config Git User run: | git config --global user.name "${{ github.actor }}" @@ -58,27 +74,24 @@ jobs: - name: Prerelease Version (Exact Version) if: ${{ github.event.inputs.release_type == 'prerelease' && github.event.inputs.exact_version }} run: | - pnpm version ${{ github.event.inputs.exact_version }} \ - --workspaces-update \ - --no-git-tag-version \ - --no-commit-hooks + npx lerna version ${{ github.event.inputs.exact_version }} --yes --no-changelog env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Prerelease Version if: ${{ github.event.inputs.release_type == 'prerelease' && !github.event.inputs.exact_version }} run: | - pnpm version prerelease \ + npx lerna version --yes \ + --conventional-commits \ + --conventional-prerelease \ --preid ${{ github.event.inputs.preid }} \ - --workspaces-update \ - --no-git-tag-version \ - --no-commit-hooks + --no-changelog env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Graduate Version if: ${{ github.event.inputs.release_type == 'graduate' }} - run: pnpm version --workspaces-update from-git + run: npx lerna version --conventional-graduate --yes env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -88,3 +101,19 @@ jobs: git push origin --tags env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate Release Summary + run: | + echo "## Release Prepared" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Branch:** \`${{ github.event.inputs.target_branch }}\`" >> $GITHUB_STEP_SUMMARY + echo "**Release Type:** \`${{ github.event.inputs.release_type }}\`" >> $GITHUB_STEP_SUMMARY + echo "**Preid:** \`${{ github.event.inputs.preid }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Tagged Packages" >> $GITHUB_STEP_SUMMARY + git tag --sort=-creatordate | head -10 | while read tag; do + echo "- \`$tag\`" >> $GITHUB_STEP_SUMMARY + done + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Next Steps" >> $GITHUB_STEP_SUMMARY + echo "Run the **Publish Packages** workflow to publish to npm" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/release-preview.yml b/.github/workflows/release-preview.yml new file mode 100644 index 0000000..46b46ae --- /dev/null +++ b/.github/workflows/release-preview.yml @@ -0,0 +1,171 @@ +name: Release Preview + +on: + workflow_dispatch: + inputs: + target_branch: + description: 'Branch to preview release from' + type: choice + options: + - 'next' + - 'master' + required: true + default: 'next' + release_type: + description: 'Release Type' + type: choice + options: + - 'prerelease' + - 'graduate' + - 'auto' + required: true + default: 'auto' + preid: + description: 'Prerelease ID (for prerelease type)' + type: choice + options: + - 'next' + - 'rc' + - 'alpha' + - 'beta' + - 'dev' + required: false + default: 'next' + +permissions: + contents: read + +jobs: + preview: + name: Preview Release + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ inputs.target_branch }} + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '22.x' + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.15.4 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Install conventional-changelog-cli + run: npm install -g conventional-changelog-cli + + - name: Config Git + run: | + git config --global user.email "preview@contractual.dev" + git config --global user.name "Release Preview Bot" + + - name: Show Changed Packages + run: | + echo "## Changed Packages" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Packages that have changed since last release:" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + npx lerna changed --json 2>/dev/null | jq -r '.[] | "- **\(.name)** (current: `v\(.version)`)"' >> $GITHUB_STEP_SUMMARY || echo "No changed packages" >> $GITHUB_STEP_SUMMARY + + - name: Preview Versions (Prerelease) + if: ${{ inputs.release_type == 'prerelease' }} + run: | + npx lerna version prerelease --yes \ + --preid ${{ inputs.preid }} \ + --no-git-tag-version \ + --no-push \ + --allow-branch ${{ inputs.target_branch }} + + - name: Preview Versions (Graduate) + if: ${{ inputs.release_type == 'graduate' }} + run: | + npx lerna version --yes \ + --conventional-graduate \ + --no-git-tag-version \ + --no-push \ + --allow-branch ${{ inputs.target_branch }} + + - name: Preview Versions (Auto - Conventional Commits) + if: ${{ inputs.release_type == 'auto' }} + run: | + npx lerna version --yes \ + --conventional-commits \ + --no-git-tag-version \ + --no-push \ + --allow-branch ${{ inputs.target_branch }} + + - name: Generate Version Summary + run: | + echo "" >> $GITHUB_STEP_SUMMARY + echo "## Proposed Versions" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Release Type:** \`${{ inputs.release_type }}\`" >> $GITHUB_STEP_SUMMARY + if [ "${{ inputs.release_type }}" = "prerelease" ]; then + echo "**Prerelease ID:** \`${{ inputs.preid }}\`" >> $GITHUB_STEP_SUMMARY + fi + echo "" >> $GITHUB_STEP_SUMMARY + npx lerna ls --json | jq -r '.[] | "- **\(.name)** -> `v\(.version)`"' >> $GITHUB_STEP_SUMMARY + + - name: Show Git Diff + run: | + echo "" >> $GITHUB_STEP_SUMMARY + echo "## Package.json Changes" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo '```diff' >> $GITHUB_STEP_SUMMARY + git diff packages/*/package.json | head -100 >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + + - name: Generate Changelog Preview + run: | + echo "" >> $GITHUB_STEP_SUMMARY + echo "## Changelog Preview" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + npx lerna exec --concurrency 1 --stream -- \ + 'conventional-changelog --preset angular --release-count 1 \ + --commit-path $PWD --pkg $PWD/package.json' 2>/dev/null | \ + sed 's/^[^:]*: //' >> $GITHUB_STEP_SUMMARY || echo "No changelog entries generated" >> $GITHUB_STEP_SUMMARY + + - name: Show Conventional Commit Analysis + run: | + echo "" >> $GITHUB_STEP_SUMMARY + echo "## Commit Analysis" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + breaking=$(git log --oneline --no-merges $(git describe --tags --abbrev=0 2>/dev/null || echo HEAD~50)..HEAD 2>/dev/null | grep -c "!" || echo "0") + features=$(git log --oneline --no-merges $(git describe --tags --abbrev=0 2>/dev/null || echo HEAD~50)..HEAD 2>/dev/null | grep -c "^[a-z0-9]* feat" || echo "0") + fixes=$(git log --oneline --no-merges $(git describe --tags --abbrev=0 2>/dev/null || echo HEAD~50)..HEAD 2>/dev/null | grep -c "^[a-z0-9]* fix" || echo "0") + + echo "- **Breaking Changes**: $breaking" >> $GITHUB_STEP_SUMMARY + echo "- **Features**: $features" >> $GITHUB_STEP_SUMMARY + echo "- **Fixes**: $fixes" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "$breaking" -gt 0 ]; then + echo "**This release contains breaking changes!**" >> $GITHUB_STEP_SUMMARY + fi + + - name: Cleanup + if: always() + run: | + git reset --hard HEAD + git clean -fd + + - name: Summary Footer + run: | + echo "" >> $GITHUB_STEP_SUMMARY + echo "---" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**This is a preview only.** No changes have been committed or pushed." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "To proceed with this release:" >> $GITHUB_STEP_SUMMARY + echo "1. Run **Prepare Release** workflow" >> $GITHUB_STEP_SUMMARY + echo "2. Then run **Publish Packages** workflow" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/set-coverage.yml b/.github/workflows/set-coverage.yml index 4e9c09a..c6be26c 100644 --- a/.github/workflows/set-coverage.yml +++ b/.github/workflows/set-coverage.yml @@ -15,52 +15,79 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - project: ['doubles.jest', 'doubles.sinon', 'core.unit', 'doubles.vitest', 'di.nestjs', 'di.inversify', 'unit'] + project: ['cli', 'governance', 'changesets', 'json-schema-differ', 'types'] steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: '18.x' + node-version: '22.x' - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: path: '**/node_modules' key: ${{ runner.os }}-modules-${{ hashFiles('**/pnpm-lock.yaml') }} - - name: pnpm + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.15.4 + + - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Build + run: pnpm build + - name: Create Coverage Directory run: mkdir -p ${{ github.workspace }}/coverage - name: Test - run: pnpm --filter @contractual/${{ matrix.project }} run test + run: pnpm lerna run test --scope @contractual/${{ matrix.project }} --stream + continue-on-error: true env: - JEST_JUNIT_OUTPUT_NAME: ${{ matrix.project }}.xml - JEST_JUNIT_OUTPUT_DIR: ${{ github.workspace }}/test-reports - JUNIT_OUTPUT_NAME: ${{ matrix.project }} - JUNIT_OUTPUT_DIR: ${{ github.workspace }}/test-reports COVERAGE_DIR: ${{ github.workspace }}/coverage COVERAGE_FILE: coverage-${{ matrix.project }}.xml - - name: Tests Results - uses: dorny/test-reporter@v1 - if: always() - with: - reporter: 'jest-junit' - name: Tests Results (${{ matrix.project }}) - path: ${{ github.workspace }}/test-reports/${{ matrix.project }}.xml - fail-on-error: false - - name: Upload Report to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: name: codecov-umbrella flags: ${{ matrix.project }} token: ${{ secrets.CODECOV_TOKEN }} - fail_ci_if_error: true + fail_ci_if_error: false files: ${{ github.workspace }}/coverage/coverage-${{ matrix.project }}.xml - verbose: true \ No newline at end of file + verbose: true + + e2e: + name: E2E Tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '22.x' + + - uses: actions/cache@v4 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/pnpm-lock.yaml') }} + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.15.4 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build + run: pnpm build + + - name: Run E2E Tests + run: pnpm test:e2e diff --git a/.github/workflows/tag-global.yml b/.github/workflows/tag-global.yml index 68079f2..31f955e 100644 --- a/.github/workflows/tag-global.yml +++ b/.github/workflows/tag-global.yml @@ -24,10 +24,18 @@ jobs: - name: Config Git run: | - git config --global user.email "omer.moradd@gmail.com" - git config --global user.name "Omer Morad" + git config --global user.email "${{ github.actor }}@users.noreply.github.com" + git config --global user.name "${{ github.actor }}" - name: Tag and Push run: | git tag -a ${{ inputs.tag_name }} -m "Tag Version ${{ inputs.tag_name }}" - git push origin ${{ inputs.tag_name }} \ No newline at end of file + git push origin ${{ inputs.tag_name }} + + - name: Generate Summary + run: | + echo "## Tag Created" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Tag:** \`${{ inputs.tag_name }}\`" >> $GITHUB_STEP_SUMMARY + echo "**Commit:** \`$(git rev-parse HEAD)\`" >> $GITHUB_STEP_SUMMARY + echo "**Branch:** \`$(git rev-parse --abbrev-ref HEAD)\`" >> $GITHUB_STEP_SUMMARY diff --git a/.gitignore b/.gitignore index 515c51e..885b5cc 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ yarn-error.log* lerna-debug.log* .pnpm-debug.log* +junit.xml + # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/README.md b/README.md index eeff71e..f21b5d8 100644 --- a/README.md +++ b/README.md @@ -1,173 +1,109 @@ -

Contractual

- -Contractual is a tool for managing API and data schemas as structured contracts. It ensures that schemas -are defined, versioned, and enforced across teams, whether for REST APIs, event-driven systems, or structured data -exchanges. - -Common use cases include: \ -πŸ”Ή Keeping API Contracts in Sync Between Backend and Frontend \ -πŸ”Ή Generating Type-Safe Clients and Server Contracts \ -πŸ”Ή Preventing Breaking Changes and Detecting Schema Drift \ -πŸ”Ή Ensuring Consistency Between Backend and Data Teams \ -πŸ”Ή Generating Language-Specific Types from a Shared Contract - -By treating schemas as first-class entities, Contractual eliminates uncertainty at integration points, enabling backend, -frontend, and data engineering teams to maintain predictable and enforceable APIs and structured data across the entire -stack. - -> Initially built for the **Node.js and TypeScript ecosystem**, Contractual is planned to support additional -> languages. - -## πŸš€ In Practice - -### Install Contractual - -To get started, install the Contractual CLI globally: - -```bash -npm i -g @contractual/cli -``` - -### Initialize Your Project - -Run the `init` command to scaffold a new project: - -```bash -contractual init -``` - -This command creates the following project structure: - -``` -frontend/ # Your frontend application -server/ # Your server application -contractual/ # Contractual files -β”œβ”€β”€ api.tsp # TypeSpec API definition -β”œβ”€β”€ specs/ # OpenAPI auto-generated specs -``` - -> Contractual works seamlessly with **monorepos**, **monoliths**, and distributed repositories. - -### Define Your API - -Write your API definition in the `api.tsp` file. For example: - -```tsp -import "@typespec/http"; -import "@typespec/openapi"; -import "@typespec/openapi3"; - -using TypeSpec.Http; - -@service({ - title: "Petstore API", -}) -namespace PetstoreAPI; - -model Pet { - id: string; - name: string; -} - -@route("/pet") -@post -op addPet(@body body: Pet): Pet; -``` - -> You can experiment and validate your API definitions [using the TypeSpec playground](https://typespec.io/playground/) - -### Manage API Changes - -#### Save the Current State of Your API - -Run the `spec graduate` command to save the current state of your OpenAPI spec: - -```bash -contractual spec graduate -``` - -This will generate a new OpenAPI (3.1.0) YAML file with versioning, enabling to track API changes over time. The -updated structure will look like this: - -``` -contractual/ -β”œβ”€β”€ api.tsp # TypeSpec API definition -β”œβ”€β”€ specs/ # OpenAPI auto-generated specs -β”‚ β”œβ”€β”€ openapi-v1.0.0.yaml -client/ # Generated API clients -server/ # Server contracts -e2e/ # Type-safe API-driven tests -``` - -> You can track API evolution and changes easily with clear, versioned OpenAPI specs. - -Here’s a quick video showing how this works: - -
- -
- -### Generate Contracts - -Run the `contract generate` command to generate type-safe clients, server contracts, and updated OpenAPI specs: - -```bash -contractual contract generate -``` - -This command creates: - -- **Type-safe client libraries** [using **ts-rest**](https://ts-rest.com), integrated with **Zod** for runtime - validation. -- **Server contracts** for frameworks like **Express**, **Fastify**, and **NestJS**. -- **Updated OpenAPI specs**. - -Here’s a short video showing contract generation in action: - -
- -
-``` - -## πŸ” Why Contractual? - -Maintaining the consistency of schemas across various services presents significant challenges. As systems evolve, -type-definitions and schemas drift, unnoticed breaking changes occur, and different teams find it challenging to -synchronize. APIs, event schemas, and structured data formats often become disconnected from their original intent, -leading to brittle integrations, manual fixes, and unexpected failures. - -**Some of the biggest pain points teams face include:** - -- **Schema Drift & Misalignment:** APIs and data contracts become inconsistent across teams, leading to mismatches, broken integrations, and regressions. - -- **Untracked Changes & Breaking Updates:** Without tracking modifications, updates can unexpectedly break consumers, causing downtime and costly debugging. - -- **Scattered Schemas & Code Maintenance:** Outdated documentation and manually managed type definitions create unreliable integrations and make maintaining entity models error-prone. - -## πŸ”‘ The Contract-First Approach -Most teams take a **code-first** approach to API development, where schemas are generated after implementation. This often results in **misalignment between services, outdated documentation, and accidental breaking changes.** Backend teams define APIs, frontend teams consume them, and data engineers rely on structured data formatsβ€”all of which can drift over time when schemas are an afterthought. - -A **contract-first** approach flips this process: schemas are designed before any implementation begins, ensuring that API structures, event definitions, and data formats remain stable and predictable. This approach allows teams to: - -- Define schemas upfront and enforce them as the single source of truth. - -- Track changes and prevent breaking updates before they impact consumers. - -- Generate type-safe clients and server contracts in multiple languages, reducing friction between teams. - -## πŸ“˜ Roadmap - -Want to contribute? Check out the alpha version [Roadmap](https://github.com/contractual-dev/contractual/issues/8) and -join the journey! πŸš€ - -## ❀️ Join the Community - -Contractual is open-source, and we’re looking for contributors to help shape its future, if you’re interested in -collaborating, please reach out. - -πŸ“© **Feedback or Questions?** Reach out -via [GitHub Discussions](https://github.com/contractual-dev/contractual/discussions). - -## πŸ”’ License - -Licensed under [MIT](LICENSE). +

+ Contractual +

+ +

Contractual

+ +

+Schema contract lifecycle for OpenAPI, JSON Schema, and AsyncAPI +
+Linting β€’ Breaking change detection β€’ Versioning β€’ Release automation +

+ +
+ license + PRs welcome + npm downloads +
+ +

+ Docs +   β€’   + Quickstart +   β€’   + Breaking Detection +   β€’   + GitHub Action +

+ +

+Supported Formats: OpenAPI, JSON Schema, AsyncAPI +

+ +## Features + +- **Structural Breaking Change Detection** - Compares specs against versioned snapshots using structural diffing, not string comparison. Catches removed fields, type changes, and endpoint deletions. + +- **Automated Versioning** - Changesets declare bump levels (major/minor/patch). `contractual version` consumes them, bumps versions, updates snapshots, and generates changelogs. + +- **CI Integration** - GitHub Action posts diff tables on PRs, auto-generates changesets, and opens Version PRs for release automation. + +- **Format Agnostic** - Works with OpenAPI, JSON Schema, and AsyncAPI. Custom linters and differs can be configured per contract. + +## Quick Example + +### Detect changes + +```bash +$ contractual diff + +orders-api: 3 changes (2 breaking, 1 non-breaking) β€” suggested bump: major + + BREAKING Removed endpoint GET /orders/{id}/details + BREAKING Changed type of field 'amount': string β†’ number + non-breaking Added optional field 'tracking_url' +``` + +### Generate a changeset + +```bash +$ contractual changeset + +? Bump type for orders-api: major +? Summary: Remove deprecated endpoint, change amount type + +Wrote .contractual/changesets/fuzzy-lion-dances.md +``` + +### Bump versions + +```bash +$ contractual version + +orders-api 1.4.2 β†’ 2.0.0 (major) + +Updated .contractual/versions.json +Updated CHANGELOG.md +``` + +## Installation + +```bash +npm install -g @contractual/cli +``` + +Or with other package managers: + +```bash +pnpm add -g @contractual/cli +yarn global add @contractual/cli +``` + +## Getting Started + +1. **Initialize** - `contractual init` scans for specs and creates `contractual.yaml` +2. **Lint** - `contractual lint` validates specs +3. **Detect changes** - `contractual diff` shows all changes classified +4. **CI gate** - `contractual breaking` fails if breaking changes exist +5. **Version** - `contractual changeset` + `contractual version` for releases + +[β†’ Full Quickstart Guide](https://contractual.dev/getting-started/quickstart) + +## Community + +- [Documentation](https://contractual.dev) +- [GitHub Issues](https://github.com/contractual-dev/contractual/issues) + +## License + +[MIT](LICENSE) diff --git a/contract-generate.gif b/contract-generate.gif deleted file mode 100644 index acc7946..0000000 Binary files a/contract-generate.gif and /dev/null differ diff --git a/e2e/.gitignore b/e2e/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/e2e/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/e2e/cli-basic/cli-install.test.ts b/e2e/cli-basic/cli-install.test.ts new file mode 100644 index 0000000..3ebfed7 --- /dev/null +++ b/e2e/cli-basic/cli-install.test.ts @@ -0,0 +1,79 @@ +import { execSync } from 'node:child_process'; +import { describe, test, expect } from 'vitest'; + +function run(command: string): string { + return execSync(command, { + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + env: { ...process.env, NO_COLOR: '1', FORCE_COLOR: '0' }, + }); +} + +describe('CLI Installation and Basic Commands', () => { + test('contractual binary is available after npm install', () => { + const result = run('npx @contractual/cli --version'); + expect(result).toMatch(/\d+\.\d+\.\d+/); + }); + + test('contractual --help shows available commands', () => { + const result = run('npx @contractual/cli --help'); + expect(result).toContain('init'); + expect(result).toContain('lint'); + expect(result).toContain('diff'); + expect(result).toContain('breaking'); + expect(result).toContain('changeset'); + expect(result).toContain('version'); + expect(result).toContain('status'); + expect(result).toContain('contract'); + expect(result).toContain('pre'); + }); + + test('contractual init --help shows init options', () => { + const result = run('npx @contractual/cli init --help'); + expect(result.toLowerCase()).toContain('initialize'); + }); + + test('contractual lint --help shows lint options', () => { + const result = run('npx @contractual/cli lint --help'); + expect(result).toContain('--format'); + }); + + test('contractual breaking --help shows breaking options', () => { + const result = run('npx @contractual/cli breaking --help'); + expect(result).toContain('--format'); + }); + + test('contractual diff --help shows diff options', () => { + const result = run('npx @contractual/cli diff --help'); + expect(result).toContain('--format'); + expect(result).toContain('--severity'); + expect(result).toContain('--verbose'); + }); + + test('contractual contract --help shows subcommands', () => { + const result = run('npx @contractual/cli contract --help'); + expect(result).toContain('add'); + expect(result).toContain('list'); + }); + + test('contractual contract add --help shows add options', () => { + const result = run('npx @contractual/cli contract add --help'); + expect(result).toContain('--name'); + expect(result).toContain('--type'); + expect(result).toContain('--path'); + }); + + test('contractual pre --help shows subcommands', () => { + const result = run('npx @contractual/cli pre --help'); + expect(result).toContain('enter'); + expect(result).toContain('exit'); + expect(result).toContain('status'); + }); + + test('contractual version --help shows version options', () => { + const result = run('npx @contractual/cli version --help'); + expect(result).toContain('--dry-run'); + expect(result).toContain('--json'); + expect(result).toContain('--yes'); + }); +}); diff --git a/e2e/cli-basic/package.json b/e2e/cli-basic/package.json new file mode 100644 index 0000000..f907462 --- /dev/null +++ b/e2e/cli-basic/package.json @@ -0,0 +1,20 @@ +{ + "name": "contractual-e2e-cli-basic", + "private": true, + "version": "0.0.0", + "type": "module", + "dependencies": { + "@contractual/cli": "e2e" + }, + "devDependencies": { + "@types/node": "^22.10.2", + "vitest": "^3.0.3", + "typescript": "~5.7.2" + }, + "scripts": { + "test": "vitest run" + }, + "engines": { + "node": ">=20.0.0" + } +} diff --git a/e2e/cli-basic/tsconfig.json b/e2e/cli-basic/tsconfig.json new file mode 100644 index 0000000..18f6945 --- /dev/null +++ b/e2e/cli-basic/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "." + }, + "include": ["./**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/e2e/cli-basic/vitest.config.ts b/e2e/cli-basic/vitest.config.ts new file mode 100644 index 0000000..df1ce1b --- /dev/null +++ b/e2e/cli-basic/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['**/*.test.ts'], + testTimeout: 30_000, + globals: true, + }, +}); diff --git a/e2e/cli-lifecycle/fixtures/json-schema/order-base.json b/e2e/cli-lifecycle/fixtures/json-schema/order-base.json new file mode 100644 index 0000000..cec8f1b --- /dev/null +++ b/e2e/cli-lifecycle/fixtures/json-schema/order-base.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/order.schema.json", + "title": "Order", + "description": "An order in the system", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique order identifier" + }, + "status": { + "type": "string", + "enum": ["pending", "processing", "shipped", "delivered"], + "description": "Current order status" + }, + "amount": { + "type": "string", + "description": "Order total amount as string" + }, + "currency": { + "type": "string", + "format": "iso-4217", + "description": "Currency code (ISO 4217)" + }, + "items": { + "type": "array", + "description": "Line items in the order", + "items": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "quantity": { + "type": "integer", + "minimum": 1 + }, + "price": { + "type": "string" + } + }, + "required": ["sku", "quantity", "price"] + } + }, + "shipping_address": { + "type": "object", + "description": "Shipping address", + "properties": { + "street": { + "type": "string" + }, + "city": { + "type": "string" + }, + "zip": { + "type": "string", + "maxLength": 10 + }, + "country": { + "type": "string" + } + }, + "required": ["street", "city", "zip", "country"] + }, + "customer_email": { + "type": "string", + "format": "email", + "description": "Customer email address" + }, + "notes": { + "type": "string", + "description": "Optional order notes", + "maxLength": 500 + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Order creation timestamp" + }, + "metadata": { + "type": "object", + "description": "Additional metadata", + "additionalProperties": true + } + }, + "required": ["id", "status", "amount", "currency", "items", "created_at"], + "additionalProperties": false +} diff --git a/e2e/cli-lifecycle/fixtures/json-schema/order-field-removed.json b/e2e/cli-lifecycle/fixtures/json-schema/order-field-removed.json new file mode 100644 index 0000000..4b8cb38 --- /dev/null +++ b/e2e/cli-lifecycle/fixtures/json-schema/order-field-removed.json @@ -0,0 +1,84 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/order.schema.json", + "title": "Order", + "description": "An order in the system", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique order identifier" + }, + "status": { + "type": "string", + "enum": ["pending", "processing", "shipped", "delivered"], + "description": "Current order status" + }, + "amount": { + "type": "string", + "description": "Order total amount as string" + }, + "currency": { + "type": "string", + "format": "iso-4217", + "description": "Currency code (ISO 4217)" + }, + "items": { + "type": "array", + "description": "Line items in the order", + "items": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "quantity": { + "type": "integer", + "minimum": 1 + }, + "price": { + "type": "string" + } + }, + "required": ["sku", "quantity", "price"] + } + }, + "shipping_address": { + "type": "object", + "description": "Shipping address", + "properties": { + "street": { + "type": "string" + }, + "city": { + "type": "string" + }, + "zip": { + "type": "string", + "maxLength": 10 + }, + "country": { + "type": "string" + } + }, + "required": ["street", "city", "zip", "country"] + }, + "notes": { + "type": "string", + "description": "Optional order notes", + "maxLength": 500 + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Order creation timestamp" + }, + "metadata": { + "type": "object", + "description": "Additional metadata", + "additionalProperties": true + } + }, + "required": ["id", "status", "amount", "currency", "items", "created_at"], + "additionalProperties": false +} diff --git a/e2e/cli-lifecycle/fixtures/json-schema/order-optional-field-added.json b/e2e/cli-lifecycle/fixtures/json-schema/order-optional-field-added.json new file mode 100644 index 0000000..bb1166e --- /dev/null +++ b/e2e/cli-lifecycle/fixtures/json-schema/order-optional-field-added.json @@ -0,0 +1,93 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/order.schema.json", + "title": "Order", + "description": "An order in the system", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique order identifier" + }, + "status": { + "type": "string", + "enum": ["pending", "processing", "shipped", "delivered"], + "description": "Current order status" + }, + "amount": { + "type": "string", + "description": "Order total amount as string" + }, + "currency": { + "type": "string", + "format": "iso-4217", + "description": "Currency code (ISO 4217)" + }, + "items": { + "type": "array", + "description": "Line items in the order", + "items": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "quantity": { + "type": "integer", + "minimum": 1 + }, + "price": { + "type": "string" + } + }, + "required": ["sku", "quantity", "price"] + } + }, + "shipping_address": { + "type": "object", + "description": "Shipping address", + "properties": { + "street": { + "type": "string" + }, + "city": { + "type": "string" + }, + "zip": { + "type": "string", + "maxLength": 10 + }, + "country": { + "type": "string" + } + }, + "required": ["street", "city", "zip", "country"] + }, + "customer_email": { + "type": "string", + "format": "email", + "description": "Customer email address" + }, + "notes": { + "type": "string", + "description": "Optional order notes", + "maxLength": 500 + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Order creation timestamp" + }, + "metadata": { + "type": "object", + "description": "Additional metadata", + "additionalProperties": true + }, + "tracking_number": { + "type": "string", + "description": "Shipment tracking number" + } + }, + "required": ["id", "status", "amount", "currency", "items", "created_at"], + "additionalProperties": false +} diff --git a/e2e/cli-lifecycle/full-lifecycle.test.ts b/e2e/cli-lifecycle/full-lifecycle.test.ts new file mode 100644 index 0000000..63189ee --- /dev/null +++ b/e2e/cli-lifecycle/full-lifecycle.test.ts @@ -0,0 +1,306 @@ +import { describe, test, expect, afterEach } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + run, + setupRepoWithConfig, + copyFixture, + writeFile, + readJSON, + fileExists, + listFiles, +} from './helpers.js'; + +describe('Full CLI Lifecycle (installed from Verdaccio)', () => { + let repo: { dir: string; cleanup: () => void }; + + afterEach(() => { + repo?.cleanup(); + }); + + test('init detects JSON Schema and creates config', () => { + repo = createTempRepo(); + const { dir } = repo; + + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.schema.json')); + + const result = run('init', dir); + expect(result.exitCode).toBe(0); + expect(fileExists(dir, 'contractual.yaml')).toBe(true); + expect(fileExists(dir, '.contractual/versions.json')).toBe(true); + }); + + test('status shows contract versions', () => { + repo = createTempRepo(); + const { dir } = repo; + + setupRepoWithConfig(dir, [{ name: 'order', type: 'json-schema', path: 'schemas/order.json' }]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + order: { version: '1.2.3', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('status', dir); + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/order/); + expect(result.stdout).toMatch(/1\.2\.3/); + }); + + test('breaking detects field removal', () => { + repo = createTempRepo(); + const { dir } = repo; + + setupRepoWithConfig(dir, [{ name: 'order', type: 'json-schema', path: 'schemas/order.json' }]); + copyFixture('json-schema/order-base.json', path.join(dir, '.contractual/snapshots/order.json')); + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + order: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('breaking --format json', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + const parsed = JSON.parse(result.stdout); + expect(parsed.hasBreaking).toBe(true); + }); + + test('changeset creates a changeset file for breaking change', () => { + repo = createTempRepo(); + const { dir } = repo; + + setupRepoWithConfig(dir, [{ name: 'order', type: 'json-schema', path: 'schemas/order.json' }]); + copyFixture('json-schema/order-base.json', path.join(dir, '.contractual/snapshots/order.json')); + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + order: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('changeset', dir); + expect(result.exitCode).toBe(0); + + const changesets = listFiles(dir, '.contractual/changesets').filter((f) => f.endsWith('.md')); + expect(changesets.length).toBeGreaterThan(0); + }); + + test('complete lifecycle: changeset -> version', () => { + repo = createTempRepo(); + const { dir } = repo; + + // Setup with a JSON Schema contract + setupRepoWithConfig(dir, [{ name: 'order', type: 'json-schema', path: 'schemas/order.json' }]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture('json-schema/order-base.json', path.join(dir, '.contractual/snapshots/order.json')); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + order: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Make a breaking change + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + + // Run changeset + const changesetResult = run('changeset', dir); + expect(changesetResult.exitCode).toBe(0); + + // Verify changeset exists + const changesets = listFiles(dir, '.contractual/changesets').filter((f) => f.endsWith('.md')); + expect(changesets.length).toBeGreaterThan(0); + + // Run version + const versionResult = run('version', dir); + expect(versionResult.exitCode).toBe(0); + expect(versionResult.stdout).toMatch(/2\.0\.0/); + + // Verify version updated + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['order'].version).toBe('2.0.0'); + }); + + test('diff shows changes between spec and snapshot', () => { + repo = createTempRepo(); + const { dir } = repo; + + setupRepoWithConfig(dir, [{ name: 'order', type: 'json-schema', path: 'schemas/order.json' }]); + copyFixture('json-schema/order-base.json', path.join(dir, '.contractual/snapshots/order.json')); + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + order: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('diff --format json', dir); + expect(result.exitCode).toBe(0); + const parsed = JSON.parse(result.stdout); + expect(parsed.contracts).toBeDefined(); + expect(parsed.contracts.order).toBeDefined(); + expect(parsed.contracts.order.changes.length).toBeGreaterThan(0); + }); + + test('contract list shows configured contracts', () => { + repo = createTempRepo(); + const { dir } = repo; + + setupRepoWithConfig(dir, [{ name: 'order', type: 'json-schema', path: 'schemas/order.json' }]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + order: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('contract list --json', dir); + expect(result.exitCode).toBe(0); + const contracts = JSON.parse(result.stdout); + expect(Array.isArray(contracts)).toBe(true); + expect(contracts[0].name).toBe('order'); + }); + + test('contract add adds new contract to config', () => { + repo = createTempRepo(); + const { dir } = repo; + + setupRepoWithConfig(dir, [{ name: 'order', type: 'json-schema', path: 'schemas/order.json' }]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/user.json')); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + order: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('contract add --name user --type json-schema --path schemas/user.json -y', dir); + expect(result.exitCode).toBe(0); + + const listResult = run('contract list --json', dir); + const contracts = JSON.parse(listResult.stdout); + expect(contracts.length).toBe(2); + expect(contracts.find((c: { name: string }) => c.name === 'user')).toBeDefined(); + }); + + test('pre enter/exit manages pre-release mode', () => { + repo = createTempRepo(); + const { dir } = repo; + + setupRepoWithConfig(dir, [{ name: 'order', type: 'json-schema', path: 'schemas/order.json' }]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + order: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Enter pre-release mode + const enterResult = run('pre enter beta', dir); + expect(enterResult.exitCode).toBe(0); + expect(fileExists(dir, '.contractual/pre.json')).toBe(true); + + // Check status + const statusResult = run('pre status', dir); + expect(statusResult.exitCode).toBe(0); + expect(statusResult.stdout).toMatch(/beta/i); + + // Exit pre-release mode + const exitResult = run('pre exit', dir); + expect(exitResult.exitCode).toBe(0); + expect(fileExists(dir, '.contractual/pre.json')).toBe(false); + }); + + test('version --dry-run shows preview without changes', () => { + repo = createTempRepo(); + const { dir } = repo; + + setupRepoWithConfig(dir, [{ name: 'order', type: 'json-schema', path: 'schemas/order.json' }]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture('json-schema/order-base.json', path.join(dir, '.contractual/snapshots/order.json')); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + order: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Create changeset + writeFile( + dir, + '.contractual/changesets/test.md', + `--- +"order": minor +--- + +Test change +` + ); + + const result = run('version --dry-run', dir); + expect(result.exitCode).toBe(0); + + // Verify version NOT changed + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['order'].version).toBe('1.0.0'); + }); + + test('version --json outputs structured result', () => { + repo = createTempRepo(); + const { dir } = repo; + + setupRepoWithConfig(dir, [{ name: 'order', type: 'json-schema', path: 'schemas/order.json' }]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture('json-schema/order-base.json', path.join(dir, '.contractual/snapshots/order.json')); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + order: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Create changeset + writeFile( + dir, + '.contractual/changesets/test.md', + `--- +"order": patch +--- + +Bug fix +` + ); + + const result = run('version --json', dir); + expect(result.exitCode).toBe(0); + + const output = JSON.parse(result.stdout); + expect(output.bumps).toBeDefined(); + expect(Array.isArray(output.bumps)).toBe(true); + }); +}); diff --git a/e2e/cli-lifecycle/helpers.ts b/e2e/cli-lifecycle/helpers.ts new file mode 100644 index 0000000..b844588 --- /dev/null +++ b/e2e/cli-lifecycle/helpers.ts @@ -0,0 +1,167 @@ +import { execSync } from 'node:child_process'; +import { + mkdtempSync, + mkdirSync, + writeFileSync, + readFileSync, + existsSync, + rmSync, + readdirSync, + cpSync, +} from 'node:fs'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { parse as parseYaml, stringify as stringifyYaml } from 'yaml'; + +/** Path to the fixtures directory */ +const FIXTURES_DIR = new URL('./fixtures', import.meta.url).pathname; + +/** + * Create a temp directory that auto-cleans after test + */ +export function createTempRepo(): { dir: string; cleanup: () => void } { + const dir = mkdtempSync(path.join(tmpdir(), 'contractual-e2e-')); + + return { + dir, + cleanup: () => { + try { + rmSync(dir, { recursive: true, force: true }); + } catch { + // Ignore cleanup errors + } + }, + }; +} + +/** + * Run a contractual CLI command in the given directory + */ +export function run( + command: string, + cwd: string, + options?: { expectFail?: boolean } +): { stdout: string; stderr: string; exitCode: number } { + const fullCmd = `npx @contractual/cli ${command}`; + + try { + const stdout = execSync(fullCmd, { + cwd, + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + env: { ...process.env, NO_COLOR: '1', FORCE_COLOR: '0' }, + }); + return { stdout, stderr: '', exitCode: 0 }; + } catch (err: unknown) { + const error = err as { stdout?: string; stderr?: string; status?: number }; + if (!options?.expectFail) { + throw new Error( + `Command failed: ${fullCmd}\n` + + `Exit code: ${error.status}\n` + + `stdout: ${error.stdout}\n` + + `stderr: ${error.stderr}` + ); + } + return { + stdout: error.stdout ?? '', + stderr: error.stderr ?? '', + exitCode: error.status ?? 1, + }; + } +} + +/** + * Copy a fixture file to the temp repo + */ +export function copyFixture(fixturePath: string, destPath: string): void { + const src = path.join(FIXTURES_DIR, fixturePath); + const destDir = path.dirname(destPath); + + if (!existsSync(destDir)) { + mkdirSync(destDir, { recursive: true }); + } + + cpSync(src, destPath); +} + +/** + * Write a file in the temp repo + */ +export function writeFile(repoDir: string, relativePath: string, content: string): void { + const fullPath = path.join(repoDir, relativePath); + const dir = path.dirname(fullPath); + + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); + } + + writeFileSync(fullPath, content); +} + +/** + * Read a file from the temp repo + */ +export function readFile(repoDir: string, relativePath: string): string { + return readFileSync(path.join(repoDir, relativePath), 'utf-8'); +} + +/** + * Check if a file exists in the temp repo + */ +export function fileExists(repoDir: string, relativePath: string): boolean { + return existsSync(path.join(repoDir, relativePath)); +} + +/** + * List files in a directory within the temp repo + */ +export function listFiles(repoDir: string, relativePath: string): string[] { + const fullPath = path.join(repoDir, relativePath); + if (!existsSync(fullPath)) return []; + return readdirSync(fullPath); +} + +/** + * Read and parse JSON from the temp repo + */ +export function readJSON(repoDir: string, relativePath: string): unknown { + return JSON.parse(readFile(repoDir, relativePath)); +} + +/** + * Read and parse YAML from the temp repo + */ +export function readYAML(repoDir: string, relativePath: string): unknown { + const content = readFile(repoDir, relativePath); + return parseYaml(content); +} + +/** + * Setup a repo with a contractual.yaml config and .contractual directory + */ +export function setupRepoWithConfig( + repoDir: string, + contracts: Array<{ + name: string; + type: string; + path: string; + }> +): void { + const config = { + contracts: contracts.map((c) => ({ + name: c.name, + type: c.type, + path: c.path, + })), + }; + + writeFile(repoDir, 'contractual.yaml', stringifyYaml(config)); + + // Create .contractual directory structure + mkdirSync(path.join(repoDir, '.contractual/changesets'), { recursive: true }); + mkdirSync(path.join(repoDir, '.contractual/snapshots'), { recursive: true }); + + if (!fileExists(repoDir, '.contractual/versions.json')) { + writeFile(repoDir, '.contractual/versions.json', '{}'); + } +} diff --git a/e2e/cli-lifecycle/package.json b/e2e/cli-lifecycle/package.json new file mode 100644 index 0000000..78d3385 --- /dev/null +++ b/e2e/cli-lifecycle/package.json @@ -0,0 +1,21 @@ +{ + "name": "contractual-e2e-cli-lifecycle", + "private": true, + "version": "0.0.0", + "type": "module", + "dependencies": { + "@contractual/cli": "e2e" + }, + "devDependencies": { + "@types/node": "^22.10.2", + "vitest": "^3.0.3", + "typescript": "~5.7.2", + "yaml": "^2.8.2" + }, + "scripts": { + "test": "vitest run" + }, + "engines": { + "node": ">=20.0.0" + } +} diff --git a/e2e/cli-lifecycle/tsconfig.json b/e2e/cli-lifecycle/tsconfig.json new file mode 100644 index 0000000..18f6945 --- /dev/null +++ b/e2e/cli-lifecycle/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "." + }, + "include": ["./**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/e2e/cli-lifecycle/vitest.config.ts b/e2e/cli-lifecycle/vitest.config.ts new file mode 100644 index 0000000..795884b --- /dev/null +++ b/e2e/cli-lifecycle/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['**/*.test.ts'], + testTimeout: 60_000, + hookTimeout: 30_000, + globals: true, + }, +}); diff --git a/e2e/config.yaml b/e2e/config.yaml new file mode 100644 index 0000000..f74202d --- /dev/null +++ b/e2e/config.yaml @@ -0,0 +1,24 @@ +storage: /verdaccio/storage + +uplinks: + npmjs: + url: https://registry.npmjs.org/ + +packages: + '@contractual/*': + access: $all + publish: $all + unpublish: $all + '**': + access: $all + publish: $all + proxy: npmjs + +auth: + htpasswd: + file: ./htpasswd + algorithm: bcrypt + rounds: 10 + +logs: + - { type: stdout, format: pretty, level: http } diff --git a/e2e/packages-import/imports.test.ts b/e2e/packages-import/imports.test.ts new file mode 100644 index 0000000..4b247a1 --- /dev/null +++ b/e2e/packages-import/imports.test.ts @@ -0,0 +1,38 @@ +import { describe, test, expect } from 'vitest'; + +describe('Package Imports (ESM)', () => { + test('@contractual/types exports are defined', async () => { + const types = await import('@contractual/types'); + // TypeScript type check - if this compiles, types are exported correctly + expect(types).toBeDefined(); + }); + + test('@contractual/changesets exports versioning utilities', async () => { + const changesets = await import('@contractual/changesets'); + expect(changesets).toBeDefined(); + // Check for key exports + expect(typeof changesets).toBe('object'); + }); + + test('@contractual/governance exports are available', async () => { + const governance = await import('@contractual/governance'); + expect(governance).toBeDefined(); + expect(typeof governance).toBe('object'); + }); + + test('@contractual/differs.json-schema exports differ', async () => { + const differ = await import('@contractual/differs.json-schema'); + expect(differ).toBeDefined(); + expect(typeof differ).toBe('object'); + }); + + test('@contractual/governance/linters subpath export works', async () => { + const linters = await import('@contractual/governance/linters'); + expect(linters).toBeDefined(); + }); + + test('@contractual/governance/differs subpath export works', async () => { + const differs = await import('@contractual/governance/differs'); + expect(differs).toBeDefined(); + }); +}); diff --git a/e2e/packages-import/package.json b/e2e/packages-import/package.json new file mode 100644 index 0000000..ee0f6d8 --- /dev/null +++ b/e2e/packages-import/package.json @@ -0,0 +1,23 @@ +{ + "name": "contractual-e2e-packages-import", + "private": true, + "version": "0.0.0", + "type": "module", + "dependencies": { + "@contractual/types": "e2e", + "@contractual/changesets": "e2e", + "@contractual/governance": "e2e", + "@contractual/differs.json-schema": "e2e" + }, + "devDependencies": { + "@types/node": "^22.10.2", + "vitest": "^3.0.3", + "typescript": "~5.7.2" + }, + "scripts": { + "test": "tsc --noEmit && vitest run" + }, + "engines": { + "node": ">=20.0.0" + } +} diff --git a/e2e/packages-import/tsconfig.json b/e2e/packages-import/tsconfig.json new file mode 100644 index 0000000..18f6945 --- /dev/null +++ b/e2e/packages-import/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "." + }, + "include": ["./**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/e2e/packages-import/vitest.config.ts b/e2e/packages-import/vitest.config.ts new file mode 100644 index 0000000..df1ce1b --- /dev/null +++ b/e2e/packages-import/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['**/*.test.ts'], + testTimeout: 30_000, + globals: true, + }, +}); diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json new file mode 100644 index 0000000..791b27e --- /dev/null +++ b/e2e/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true + }, + "exclude": ["node_modules", "**/node_modules"] +} diff --git a/lerna.json b/lerna.json new file mode 100644 index 0000000..686ebce --- /dev/null +++ b/lerna.json @@ -0,0 +1,27 @@ +{ + "$schema": "node_modules/lerna/schemas/lerna-schema.json", + "version": "independent", + "command": { + "publish": { + "registry": "https://registry.npmjs.org/", + "allowBranch": [ + "master", + "next" + ], + "message": "chore(*): release packages [skip ci]", + "conventionalCommits": true + }, + "version": { + "allowBranch": [ + "master", + "next" + ], + "message": "chore(*): version packages [skip ci]", + "conventionalCommits": true + } + }, + "npmClient": "pnpm", + "packages": [ + "packages/*" + ] +} diff --git a/local-e2e.docker-compose.yaml b/local-e2e.docker-compose.yaml new file mode 100644 index 0000000..c205944 --- /dev/null +++ b/local-e2e.docker-compose.yaml @@ -0,0 +1,32 @@ +name: contractual-local-e2e + +services: + verdaccio: + image: verdaccio/verdaccio + ports: + - '4873:4873' + volumes: + - ./e2e/config.yaml:/verdaccio/conf/config.yaml + - verdaccio-storage:/verdaccio/storage + networks: + - e2e-network + + executor: + image: node:22-alpine + working_dir: /workspace + volumes: + - .:/workspace + command: ['tail', '-f', '/dev/null'] + networks: + - e2e-network + depends_on: + - verdaccio + environment: + - NPM_CONFIG_PROVENANCE=false + +networks: + e2e-network: + driver: bridge + +volumes: + verdaccio-storage: diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..cde1ab0 Binary files /dev/null and b/logo.png differ diff --git a/package.json b/package.json index 0cec923..14a20c1 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "url": "https://github.com/contractual-dev/contractual.git" }, "engines": { - "node": "^18.12.0 || >=20.0.0" + "node": ">=20.0.0" }, "bugs": { "url": "https://github.com/contractual-dev/contractual.git" @@ -22,60 +22,42 @@ } ], "scripts": { - "build": "pnpm -r run build", - "tester": "pnpm -r run test", - "lint": "pnpm -r run lint", - "prepare": "husky" + "build": "pnpm lerna run build --stream", + "build:watch": "pnpm -r run build:watch", + "test": "pnpm lerna run test --stream", + "test:e2e": "vitest run --config vitest.config.e2e.ts", + "test:e2e:watch": "vitest --config vitest.config.e2e.ts", + "lint": "pnpm lerna run lint --parallel", + "prepare": "husky", + "version:preview": "lerna changed --json | jq -r '.[] | \"\\(.name) β†’ v\\(.version)\"'", + "publish:dry": "lerna publish from-package --yes --no-git-reset --dry-run", + "e2e:verdaccio:up": "docker compose -f local-e2e.docker-compose.yaml up -d", + "e2e:verdaccio:down": "docker compose -f local-e2e.docker-compose.yaml down -v" }, - "dependencies": { - "@manypkg/cli": "^0.21.4", + "devDependencies": { "@types/node": "^22.10.2", + "lerna": "^8.2.3", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", - "@vitest/coverage-c8": "^0.33.0", - "@vitest/coverage-v8": "3.0.3", - "braces": "3.0.3", + "@vitest/coverage-v8": "^3.0.3", "eslint": "^8.57.0", "eslint-config-prettier": "^9.0.0", "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-import": "^2.29.1", "eslint-plugin-prettier": "^5.0.1", - "follow-redirects": "1.15.6", - "ip": "2.0.1", - "lerna": "^7.3.1", + "husky": "^8.0.3", + "json-schema-typed": "^8.0.2", "lint-staged": "^14.0.1", - "madge": "^7.0.0", - "micromatch": "4.0.8", "prettier": "^3.2.5", "rimraf": "^5.0.5", - "rxjs": "^7.8.1", - "tar": "6.2.0", - "ts-jest": "^29.1.3", - "ts-node": "^10.9.1", "typescript": "~5.7.2", - "vitest": "^3.0.3" + "vitest": "^3.0.3", + "yaml": "^2.8.2" }, - "workspaces": [ - "packages/generators/*", - "packages/providers/*", - "packages/types/*", - "packages/*" - ], "lint-staged": { "*.ts": [ "eslint --ext .ts --fix" ] }, - "jest-junit": { - "outputDirectory": "test-reports", - "ancestorSeparator": " β€Ί ", - "uniqueOutputName": "true", - "suiteNameTemplate": "{filepath}", - "classNameTemplate": "{classname}", - "titleTemplate": "{title}" - }, - "packageManager": "pnpm@9.15.4", - "devDependencies": { - "husky": "^8.0.3" - } + "packageManager": "pnpm@9.15.4" } diff --git a/packages/changesets/changesets/consume.ts b/packages/changesets/changesets/consume.ts new file mode 100644 index 0000000..e7ee36b --- /dev/null +++ b/packages/changesets/changesets/consume.ts @@ -0,0 +1,110 @@ +/** + * Changeset consumption utilities + * Processes changesets for the version command + */ + +import type { BumpType, ChangesetFile } from '@contractual/types'; + +/** + * Bump type priority for comparison (higher = more significant) + */ +const BUMP_PRIORITY: Readonly> = { + major: 3, + minor: 2, + patch: 1, +} as const; + +/** + * Compare two bump types and return the higher priority one + */ +function higherBump(a: BumpType, b: BumpType): BumpType { + return BUMP_PRIORITY[a] >= BUMP_PRIORITY[b] ? a : b; +} + +/** + * Aggregate bumps from multiple changesets + * For each contract, the highest bump type wins + * + * @param changesets - Array of parsed changeset files + * @returns Map of contract name to aggregated bump type + */ +export function aggregateBumps( + changesets: readonly ChangesetFile[] +): Record { + const aggregated: Record = {}; + + for (const changeset of changesets) { + for (const [contract, bump] of Object.entries(changeset.bumps)) { + const existing = aggregated[contract]; + aggregated[contract] = existing ? higherBump(existing, bump) : bump; + } + } + + return aggregated; +} + +/** + * Extract the markdown section for a specific contract from changesets + * Combines all change descriptions for the contract across all changesets + * + * @param changesets - Array of parsed changeset files + * @param contractName - Name of the contract to extract changes for + * @returns Combined markdown content for the contract + */ +export function extractContractChanges( + changesets: readonly ChangesetFile[], + contractName: string +): string { + const sections: string[] = []; + + for (const changeset of changesets) { + // Only process changesets that affect this contract + if (!(contractName in changeset.bumps)) { + continue; + } + + // Extract the section for this contract from the body + const section = extractSectionFromBody(changeset.body, contractName); + if (section) { + sections.push(section); + } + } + + return sections.join('\n\n'); +} + +/** + * Extract a contract's section from a changeset body + * Looks for ## ContractName and extracts content until the next ## or end + */ +function extractSectionFromBody(body: string, contractName: string): string { + const lines = body.split('\n'); + const headerPattern = new RegExp(`^##\\s+${escapeRegex(contractName)}\\s*$`); + + let inSection = false; + const sectionLines: string[] = []; + + for (const line of lines) { + if (headerPattern.test(line)) { + inSection = true; + continue; + } + + if (inSection) { + // Check if we've hit the next section + if (line.startsWith('## ')) { + break; + } + sectionLines.push(line); + } + } + + return sectionLines.join('\n').trim(); +} + +/** + * Escape special regex characters in a string + */ +function escapeRegex(str: string): string { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} diff --git a/packages/changesets/changesets/create.ts b/packages/changesets/changesets/create.ts new file mode 100644 index 0000000..b2801ea --- /dev/null +++ b/packages/changesets/changesets/create.ts @@ -0,0 +1,101 @@ +/** + * Changeset creation utilities + * Creates changeset files from diff results + */ + +import type { DiffResult, BumpType, ChangeSeverity } from '@contractual/types'; +import { generateChangesetName } from './naming.js'; + +/** + * Result of creating a changeset file + */ +export interface CreateChangesetResult { + /** Generated filename (e.g., "brave-tigers-fly.md") */ + filename: string; + /** Full content of the changeset file including frontmatter and body */ + content: string; +} + +/** + * Severity label mapping for display purposes + */ +const SEVERITY_LABELS: Record = { + breaking: 'BREAKING', + 'non-breaking': 'minor', + patch: 'patch', + unknown: 'unknown', +} as const; + +/** + * Map change severity to display label + */ +function severityToLabel(severity: ChangeSeverity): string { + return SEVERITY_LABELS[severity]; +} + +/** + * Determine the suggested bump type for a contract based on its changes + */ +function determineBumpType(result: DiffResult): BumpType { + if (result.summary.breaking > 0) { + return 'major'; + } + if (result.summary.nonBreaking > 0) { + return 'minor'; + } + return 'patch'; +} + +/** + * Create a changeset from diff results + * + * @param results - Array of diff results from comparing specs + * @param overrides - Optional map of contract name to bump type overrides + * @returns Object with filename and content for the changeset file + */ +export function createChangeset( + results: DiffResult[], + overrides?: Readonly> +): CreateChangesetResult { + const filename = `${generateChangesetName()}.md`; + + // Build YAML frontmatter + const bumps: Record = {}; + for (const result of results) { + if (result.changes.length === 0) { + continue; + } + const suggestedBump = determineBumpType(result); + bumps[result.contract] = overrides?.[result.contract] ?? suggestedBump; + } + + // Generate YAML frontmatter + const yamlLines = Object.entries(bumps).map( + ([contract, bump]) => `"${contract}": ${bump}` + ); + const frontmatter = `---\n${yamlLines.join('\n')}\n---`; + + // Generate markdown body with changes grouped by contract + const bodyParts: string[] = []; + + for (const result of results) { + if (result.changes.length === 0) { + continue; + } + + bodyParts.push(`## ${result.contract}`); + bodyParts.push(''); + + for (const change of result.changes) { + const label = severityToLabel(change.severity); + bodyParts.push(`- **[${label}]** ${change.message}`); + } + + bodyParts.push(''); + } + + const body = bodyParts.join('\n').trim(); + const content = `${frontmatter}\n\n${body}\n`; + + return { filename, content }; +} diff --git a/packages/changesets/changesets/index.ts b/packages/changesets/changesets/index.ts new file mode 100644 index 0000000..dbc6070 --- /dev/null +++ b/packages/changesets/changesets/index.ts @@ -0,0 +1,4 @@ +export * from './naming.js'; +export * from './create.js'; +export * from './read.js'; +export * from './consume.js'; diff --git a/packages/changesets/changesets/naming.ts b/packages/changesets/changesets/naming.ts new file mode 100644 index 0000000..b849edd --- /dev/null +++ b/packages/changesets/changesets/naming.ts @@ -0,0 +1,126 @@ +/** + * Changeset naming utilities + * Generates random three-word names for changeset files + */ + +const ADJECTIVES = [ + 'brave', + 'calm', + 'eager', + 'fancy', + 'gentle', + 'happy', + 'jolly', + 'kind', + 'lively', + 'mighty', + 'noble', + 'proud', + 'quick', + 'ready', + 'sharp', + 'swift', + 'tender', + 'vivid', + 'warm', + 'zesty', +] as const; + +const NOUNS = [ + 'bears', + 'cats', + 'dogs', + 'eagles', + 'foxes', + 'geese', + 'hawks', + 'ibis', + 'jays', + 'kites', + 'lions', + 'mice', + 'newts', + 'owls', + 'pandas', + 'quails', + 'ravens', + 'seals', + 'tigers', + 'wolves', +] as const; + +const VERBS = [ + 'fly', + 'run', + 'jump', + 'swim', + 'dance', + 'sing', + 'play', + 'rest', + 'hunt', + 'roam', + 'climb', + 'glide', + 'soar', + 'leap', + 'dash', + 'drift', + 'march', + 'prowl', + 'race', + 'sprint', +] as const; + +/** + * Get a random element from an array + * @throws {Error} If the array is empty + */ +function randomElement(array: readonly T[]): T { + if (array.length === 0) { + throw new Error('Cannot get random element from empty array'); + } + const index = Math.floor(Math.random() * array.length); + const element = array[index]; + // This assertion is safe because we've checked array.length > 0 and index is within bounds + return element as T; +} + +/** + * Generate a random three-word changeset name + * Format: adjective-noun-verb (e.g., "brave-tigers-fly") + */ +export function generateChangesetName(): string { + const adjective = randomElement(ADJECTIVES); + const noun = randomElement(NOUNS); + const verb = randomElement(VERBS); + + return `${adjective}-${noun}-${verb}`; +} + +/** + * Generate a unique changeset name that doesn't collide with existing names + * Retries with new random names until a unique one is found + * + * @param existingNames - Array of existing changeset names to avoid + * @param maxRetries - Maximum number of retries before giving up (default: 100) + * @returns A unique changeset name + * @throws Error if unable to generate a unique name after max retries + */ +export function generateUniqueChangesetName( + existingNames: string[], + maxRetries = 100 +): string { + const existingSet = new Set(existingNames); + + for (let i = 0; i < maxRetries; i++) { + const name = generateChangesetName(); + if (!existingSet.has(name)) { + return name; + } + } + + // Fallback: append timestamp to ensure uniqueness + const baseName = generateChangesetName(); + return `${baseName}-${Date.now()}`; +} diff --git a/packages/changesets/changesets/read.ts b/packages/changesets/changesets/read.ts new file mode 100644 index 0000000..441322b --- /dev/null +++ b/packages/changesets/changesets/read.ts @@ -0,0 +1,141 @@ +/** + * Changeset reading and parsing utilities + * Parses changeset files from the .contractual/changesets directory + */ + +import { readdir, readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { parse as parseYaml } from 'yaml'; +import type { BumpType, ChangesetFile } from '@contractual/types'; + +/** + * Valid bump type values + */ +const VALID_BUMP_TYPES = ['major', 'minor', 'patch'] as const; + +/** + * Error thrown when parsing a changeset file fails + */ +export class ChangesetParseError extends Error { + constructor( + message: string, + public readonly filename?: string + ) { + super(filename ? `Failed to parse changeset "${filename}": ${message}` : message); + this.name = 'ChangesetParseError'; + } +} + +/** + * Parsed changeset content (before wrapping with filename) + */ +export interface ParsedChangesetContent { + /** Map of contract name to bump type */ + bumps: Record; + /** Markdown body with change descriptions */ + body: string; +} + +/** + * Validate that a value is a valid bump type + */ +function isValidBumpType(value: unknown): value is BumpType { + return VALID_BUMP_TYPES.includes(value as BumpType); +} + +/** + * Parse a changeset file content into its components + * + * @param content - Raw content of the changeset file + * @returns Parsed bumps and body + * @throws {ChangesetParseError} If the content is malformed + */ +export function parseChangeset(content: string): ParsedChangesetContent { + const trimmed = content.trim(); + + // Check for frontmatter delimiter + if (!trimmed.startsWith('---')) { + throw new ChangesetParseError('Changeset must start with YAML frontmatter (---)'); + } + + // Find the closing frontmatter delimiter + const secondDelimiter = trimmed.indexOf('---', 3); + if (secondDelimiter === -1) { + throw new ChangesetParseError('Changeset frontmatter must be closed with ---'); + } + + // Extract YAML and body + const yamlContent = trimmed.slice(3, secondDelimiter).trim(); + const body = trimmed.slice(secondDelimiter + 3).trim(); + + // Parse YAML + let parsed: unknown; + try { + parsed = parseYaml(yamlContent); + } catch (yamlError) { + const message = yamlError instanceof Error ? yamlError.message : 'Invalid YAML'; + throw new ChangesetParseError(`Invalid YAML in frontmatter: ${message}`); + } + + if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) { + throw new ChangesetParseError('Changeset frontmatter must contain a YAML object'); + } + + // Validate and extract bumps + const bumps: Record = {}; + + for (const [key, value] of Object.entries(parsed as Record)) { + if (!isValidBumpType(value)) { + throw new ChangesetParseError( + `Invalid bump type "${String(value)}" for contract "${key}". Must be ${VALID_BUMP_TYPES.join(', ')}.` + ); + } + bumps[key] = value; + } + + return { bumps, body }; +} + +/** + * Read all changeset files from a directory + * + * @param changesetsDir - Path to the changesets directory + * @returns Array of parsed changeset files + */ +export async function readChangesets(changesetsDir: string): Promise { + let files: string[]; + + try { + files = await readdir(changesetsDir); + } catch (error) { + // Directory doesn't exist or can't be read - no changesets + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + return []; + } + throw error; + } + + // Filter to only .md files + const mdFiles = files.filter((file) => file.endsWith('.md')); + + const changesets: ChangesetFile[] = []; + + for (const filename of mdFiles) { + const filePath = join(changesetsDir, filename); + const content = await readFile(filePath, 'utf-8'); + + try { + const { bumps, body } = parseChangeset(content); + changesets.push({ filename, path: filePath, bumps, body }); + } catch (error) { + // Re-throw with filename context + if (error instanceof ChangesetParseError) { + throw new ChangesetParseError(error.message, filename); + } + const message = error instanceof Error ? error.message : 'Unknown error'; + throw new ChangesetParseError(message, filename); + } + } + + return changesets; +} diff --git a/packages/changesets/index.ts b/packages/changesets/index.ts new file mode 100644 index 0000000..4be46a2 --- /dev/null +++ b/packages/changesets/index.ts @@ -0,0 +1,31 @@ +// Changesets +export { + generateChangesetName, + generateUniqueChangesetName, +} from './changesets/naming.js'; +export { createChangeset, type CreateChangesetResult } from './changesets/create.js'; +export { + parseChangeset, + readChangesets, + ChangesetParseError, + type ParsedChangesetContent, +} from './changesets/read.js'; +export { aggregateBumps, extractContractChanges } from './changesets/consume.js'; + +// Versioning +export { + VersionManager, + PreReleaseManager, + VERSIONS_FILE, + SNAPSHOTS_DIR, + CHANGESETS_DIR, + PRE_RELEASE_FILE, + DEFAULT_VERSION, + SPEC_EXTENSIONS, + incrementVersion, + incrementVersionWithPreRelease, + VersionError, + type BumpOperationResult, +} from './versioning/manager.js'; +export { formatDate, appendChangelog } from './versioning/changelog.js'; +export { updateSpecVersion } from './versioning/spec-updater.js'; diff --git a/packages/changesets/package.json b/packages/changesets/package.json new file mode 100644 index 0000000..57f40ef --- /dev/null +++ b/packages/changesets/package.json @@ -0,0 +1,53 @@ +{ + "name": "@contractual/changesets", + "private": false, + "version": "0.1.0-dev.5", + "description": "Changeset creation, parsing, and versioning utilities for Contractual", + "license": "MIT", + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./changesets": { + "types": "./dist/changesets/index.d.ts", + "import": "./dist/changesets/index.js" + }, + "./versioning": { + "types": "./dist/versioning/index.d.ts", + "import": "./dist/versioning/index.js" + } + }, + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/contractual-dev/contractual.git", + "directory": "packages/changesets" + }, + "engines": { + "node": ">=20.0.0" + }, + "scripts": { + "prebuild": "pnpm rimraf dist", + "build": "tsc -p tsconfig.build.json" + }, + "files": [ + "dist" + ], + "dependencies": { + "@contractual/types": "workspace:*", + "semver": "^7.7.1", + "yaml": "^2.7.0" + }, + "devDependencies": { + "@types/semver": "^7.5.8" + }, + "publishConfig": { + "access": "public", + "provenance": true + } +} diff --git a/packages/changesets/tsconfig.build.json b/packages/changesets/tsconfig.build.json new file mode 100644 index 0000000..46b1b04 --- /dev/null +++ b/packages/changesets/tsconfig.build.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "composite": true, + "incremental": true + }, + "references": [ + { "path": "../types/tsconfig.build.json" } + ] +} diff --git a/packages/changesets/tsconfig.json b/packages/changesets/tsconfig.json new file mode 100644 index 0000000..c256fc3 --- /dev/null +++ b/packages/changesets/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "./dist", + "strict": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "exactOptionalPropertyTypes": false, + "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/changesets/versioning/changelog.ts b/packages/changesets/versioning/changelog.ts new file mode 100644 index 0000000..bdb7be6 --- /dev/null +++ b/packages/changesets/versioning/changelog.ts @@ -0,0 +1,74 @@ +import { existsSync, readFileSync, writeFileSync } from 'node:fs'; +import type { BumpResult } from '@contractual/types'; + +/** + * Format a date as YYYY-MM-DD + * @param date - The date to format + * @returns The formatted date string + */ +export function formatDate(date: Date): string { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; +} + +/** + * Format a single bump result as a changelog entry + */ +function formatBumpEntry(bump: BumpResult, date: Date): string { + const dateStr = formatDate(date); + const header = `## [${bump.contract}] v${bump.newVersion} - ${dateStr}`; + const changes = bump.changes.trim(); + + return `${header}\n\n${changes}`; +} + +/** + * Append version bump entries to CHANGELOG.md + * Creates the file if it doesn't exist. + * Inserts entries after the "# Changelog" heading, or at the top if no heading. + * @param changelogPath - Path to the CHANGELOG.md file + * @param bumps - Array of bump results to append + */ +export function appendChangelog(changelogPath: string, bumps: BumpResult[]): void { + if (bumps.length === 0) { + return; + } + + const date = new Date(); + const newEntries = bumps + .map((bump) => formatBumpEntry(bump, date)) + .join('\n\n'); + + if (!existsSync(changelogPath)) { + // Create new changelog with header and entries + const content = `# Changelog\n\n${newEntries}\n`; + writeFileSync(changelogPath, content, 'utf-8'); + return; + } + + const existingContent = readFileSync(changelogPath, 'utf-8'); + const changelogHeading = '# Changelog'; + const headingIndex = existingContent.indexOf(changelogHeading); + + let newContent: string; + + if (headingIndex !== -1) { + // Insert after the heading line + const afterHeading = headingIndex + changelogHeading.length; + const beforeHeading = existingContent.slice(0, afterHeading); + const afterContent = existingContent.slice(afterHeading); + + // Preserve any blank lines after heading, then insert entries + const leadingWhitespaceMatch = afterContent.match(/^(\r?\n)*/); + const leadingWhitespace = leadingWhitespaceMatch?.[0] ?? '\n\n'; + + newContent = `${beforeHeading}\n\n${newEntries}${leadingWhitespace.length > 2 ? leadingWhitespace : '\n\n'}${afterContent.trimStart()}`; + } else { + // No heading found, prepend entries with heading + newContent = `# Changelog\n\n${newEntries}\n\n${existingContent}`; + } + + writeFileSync(changelogPath, newContent, 'utf-8'); +} diff --git a/packages/changesets/versioning/index.ts b/packages/changesets/versioning/index.ts new file mode 100644 index 0000000..d6f8d13 --- /dev/null +++ b/packages/changesets/versioning/index.ts @@ -0,0 +1,2 @@ +export * from './manager.js'; +export * from './changelog.js'; diff --git a/packages/changesets/versioning/manager.ts b/packages/changesets/versioning/manager.ts new file mode 100644 index 0000000..9656d0c --- /dev/null +++ b/packages/changesets/versioning/manager.ts @@ -0,0 +1,371 @@ +import { existsSync, readFileSync, writeFileSync, copyFileSync, unlinkSync } from 'node:fs'; +import { join, extname } from 'node:path'; +import * as semver from 'semver'; +import type { VersionsFile, SimpleVersionEntry, BumpType, PreReleaseState } from '@contractual/types'; + +/** + * Default version for new contracts + */ +export const DEFAULT_VERSION = '0.0.0' as const; + +/** + * Supported spec file extensions for snapshot lookup + */ +export const SPEC_EXTENSIONS = ['.yaml', '.yml', '.json'] as const; + +/** + * Error thrown when version operations fail + */ +export class VersionError extends Error { + constructor( + message: string, + public readonly version?: string, + public readonly bumpType?: BumpType + ) { + super(message); + this.name = 'VersionError'; + } +} + +/** + * Result of a version bump operation + */ +export interface BumpOperationResult { + /** Version before the bump */ + oldVersion: string; + /** Version after the bump */ + newVersion: string; +} + +/** + * Increment a version by a bump type + * @param version - The current version string (must be valid semver) + * @param bumpType - The type of version bump (major, minor, patch) + * @returns The new version string + * @throws {VersionError} If the version is invalid or increment fails + */ +export function incrementVersion(version: string, bumpType: BumpType): string { + if (!semver.valid(version)) { + throw new VersionError(`Invalid semver version: ${version}`, version, bumpType); + } + const newVersion = semver.inc(version, bumpType); + if (!newVersion) { + throw new VersionError( + `Failed to increment version ${version} with type ${bumpType}`, + version, + bumpType + ); + } + return newVersion; +} + +/** + * Filename for the versions registry + */ +export const VERSIONS_FILE = 'versions.json' as const; + +/** + * Directory name for snapshots + */ +export const SNAPSHOTS_DIR = 'snapshots' as const; + +/** + * Directory name for changesets + */ +export const CHANGESETS_DIR = 'changesets' as const; + +/** + * Filename for pre-release state + */ +export const PRE_RELEASE_FILE = 'pre.json' as const; + +/** + * Manages contract versions and snapshots + */ +export class VersionManager { + private readonly versionsPath: string; + private readonly snapshotsDir: string; + private versions: VersionsFile; + + constructor(contractualDir: string) { + this.versionsPath = join(contractualDir, VERSIONS_FILE); + this.snapshotsDir = join(contractualDir, SNAPSHOTS_DIR); + this.versions = this.load(); + } + + /** + * Load versions.json from disk + * @throws {VersionError} If the file exists but contains invalid JSON + */ + private load(): VersionsFile { + if (!existsSync(this.versionsPath)) { + return {}; + } + + const content = readFileSync(this.versionsPath, 'utf-8'); + try { + const parsed: unknown = JSON.parse(content); + if (!this.isValidVersionsFile(parsed)) { + throw new VersionError(`Invalid versions.json format at ${this.versionsPath}`); + } + return parsed; + } catch (error) { + if (error instanceof VersionError) { + throw error; + } + const message = error instanceof Error ? error.message : 'Unknown error'; + throw new VersionError(`Failed to parse versions.json: ${message}`); + } + } + + /** + * Type guard to validate VersionsFile structure + */ + private isValidVersionsFile(value: unknown): value is VersionsFile { + if (typeof value !== 'object' || value === null || Array.isArray(value)) { + return false; + } + for (const entry of Object.values(value as Record)) { + if (!this.isValidVersionEntry(entry)) { + return false; + } + } + return true; + } + + /** + * Type guard to validate SimpleVersionEntry structure + */ + private isValidVersionEntry(value: unknown): value is SimpleVersionEntry { + if (typeof value !== 'object' || value === null) { + return false; + } + const entry = value as Record; + return typeof entry.version === 'string' && typeof entry.released === 'string'; + } + + /** + * Get current version for a contract + * @param contractName - The contract name to look up + * @returns The version string, or null if not found + */ + getVersion(contractName: string): string | null { + const entry = this.versions[contractName]; + return entry?.version ?? null; + } + + /** + * Get snapshot path for a contract + * @param contractName - The contract name to look up + * @returns The snapshot file path, or null if not found + */ + getSnapshotPath(contractName: string): string | null { + for (const ext of SPEC_EXTENSIONS) { + const snapshotPath = join(this.snapshotsDir, `${contractName}${ext}`); + if (existsSync(snapshotPath)) { + return snapshotPath; + } + } + + return null; + } + + /** + * Bump version, update versions.json, and copy spec to snapshots + * @param contractName - The contract name to bump + * @param bumpType - The type of version bump (major, minor, patch) + * @param specPath - Path to the current spec file to snapshot + * @returns The old and new version strings + * @throws {VersionError} If the bump operation fails + */ + bump(contractName: string, bumpType: BumpType, specPath: string): BumpOperationResult { + const currentEntry = this.versions[contractName]; + const oldVersion = currentEntry?.version ?? DEFAULT_VERSION; + + // Use the shared incrementVersion function for consistent error handling + const newVersion = incrementVersion(oldVersion, bumpType); + + // Update versions entry + this.versions[contractName] = { + version: newVersion, + released: new Date().toISOString(), + }; + + // Copy spec to snapshots directory + const ext = extname(specPath) || '.yaml'; + const snapshotPath = join(this.snapshotsDir, `${contractName}${ext}`); + copyFileSync(specPath, snapshotPath); + + // Save versions.json + this.save(); + + return { oldVersion, newVersion }; + } + + /** + * Set version for a contract (used for initial version setup) + * @param contractName - The contract name + * @param version - The version to set + * @param specPath - Path to the spec file to snapshot + */ + setVersion(contractName: string, version: string, specPath: string): void { + if (!semver.valid(version)) { + throw new VersionError(`Invalid semver version: ${version}`, version); + } + + // Update versions entry + this.versions[contractName] = { + version, + released: new Date().toISOString(), + }; + + // Copy spec to snapshots directory + const ext = extname(specPath) || '.yaml'; + const snapshotPath = join(this.snapshotsDir, `${contractName}${ext}`); + copyFileSync(specPath, snapshotPath); + + // Save versions.json + this.save(); + } + + /** + * Save versions.json to disk + */ + private save(): void { + const content = JSON.stringify(this.versions, null, 2); + writeFileSync(this.versionsPath, content, 'utf-8'); + } + + /** + * Get all contract versions + * @returns Map of contract names to versions + */ + getAllVersions(): Record { + const result: Record = {}; + for (const [name, entry] of Object.entries(this.versions)) { + result[name] = entry.version; + } + return result; + } +} + +/** + * Manages pre-release state + */ +export class PreReleaseManager { + private readonly prePath: string; + + constructor(contractualDir: string) { + this.prePath = join(contractualDir, PRE_RELEASE_FILE); + } + + /** + * Check if pre-release mode is active + */ + isActive(): boolean { + return existsSync(this.prePath); + } + + /** + * Get current pre-release state + * @returns The pre-release state, or null if not in pre-release mode + */ + getState(): PreReleaseState | null { + if (!this.isActive()) { + return null; + } + + try { + const content = readFileSync(this.prePath, 'utf-8'); + return JSON.parse(content) as PreReleaseState; + } catch { + return null; + } + } + + /** + * Enter pre-release mode + * @param tag - The pre-release tag (e.g., "alpha", "beta", "rc") + * @param versionManager - VersionManager to get current versions + */ + enter(tag: string, versionManager: VersionManager): void { + if (this.isActive()) { + throw new VersionError(`Already in pre-release mode. Run 'pre exit' first.`); + } + + // Validate tag + if (!/^[a-zA-Z][a-zA-Z0-9-]*$/.test(tag)) { + throw new VersionError( + `Invalid pre-release tag: ${tag}. Must start with letter, contain only letters, numbers, and hyphens.` + ); + } + + const state: PreReleaseState = { + tag, + enteredAt: new Date().toISOString(), + initialVersions: versionManager.getAllVersions(), + }; + + writeFileSync(this.prePath, JSON.stringify(state, null, 2), 'utf-8'); + } + + /** + * Exit pre-release mode + */ + exit(): void { + if (!this.isActive()) { + throw new VersionError('Not in pre-release mode.'); + } + + unlinkSync(this.prePath); + } + + /** + * Get the pre-release tag + * @returns The tag, or null if not in pre-release mode + */ + getTag(): string | null { + const state = this.getState(); + return state?.tag ?? null; + } +} + +/** + * Increment a version with pre-release support + * @param version - The current version string + * @param bumpType - The type of version bump + * @param preReleaseTag - Optional pre-release tag (e.g., "beta") + * @returns The new version string + */ +export function incrementVersionWithPreRelease( + version: string, + bumpType: BumpType, + preReleaseTag?: string +): string { + if (!semver.valid(version)) { + throw new VersionError(`Invalid semver version: ${version}`, version, bumpType); + } + + const parsed = semver.parse(version); + if (!parsed) { + throw new VersionError(`Failed to parse version: ${version}`, version, bumpType); + } + + // If no pre-release tag, use normal increment + if (!preReleaseTag) { + return incrementVersion(version, bumpType); + } + + // If already a pre-release with the same tag, just bump the pre-release number + if (parsed.prerelease.length > 0 && parsed.prerelease[0] === preReleaseTag) { + const newVersion = semver.inc(version, 'prerelease', preReleaseTag); + if (!newVersion) { + throw new VersionError(`Failed to increment pre-release version`, version, bumpType); + } + return newVersion; + } + + // Otherwise, apply the bump type and start a new pre-release + const baseVersion = incrementVersion(version, bumpType); + return `${baseVersion}-${preReleaseTag}.0`; +} diff --git a/packages/changesets/versioning/spec-updater.ts b/packages/changesets/versioning/spec-updater.ts new file mode 100644 index 0000000..c9b8653 --- /dev/null +++ b/packages/changesets/versioning/spec-updater.ts @@ -0,0 +1,110 @@ +/** + * Spec Version Updater + * + * Updates the version field inside a spec file (e.g., info.version in OpenAPI) + * to keep the spec's internal version in sync with the tracked version. + */ + +import { readFileSync, writeFileSync } from 'node:fs'; +import { extname } from 'node:path'; +import { parseDocument } from 'yaml'; +import type { ContractType } from '@contractual/types'; + +/** + * Contract types that have a version field in the spec + */ +const VERSION_FIELD_PATHS: Partial> = { + openapi: ['info', 'version'], + asyncapi: ['info', 'version'], + odcs: ['version'], +}; + +/** + * Detect indentation used in a JSON file + */ +function detectJsonIndent(content: string): number { + const match = content.match(/^(\s+)"/m); + return match?.[1]?.length ?? 2; +} + +/** + * Ensure nested path exists in a plain object, creating intermediate objects as needed. + * Returns the parent object of the last key. + */ +function ensureJsonPath( + root: Record, + fieldPath: readonly string[] +): Record { + let current = root; + for (let i = 0; i < fieldPath.length - 1; i++) { + const key = fieldPath[i]!; + if (typeof current[key] !== 'object' || current[key] === null) { + current[key] = {}; + } + current = current[key] as Record; + } + return current; +} + +/** + * Update the version field inside a JSON spec file. + * Creates missing intermediate objects if needed. + */ +function updateJsonSpec(specPath: string, fieldPath: readonly string[], newVersion: string): void { + const content = readFileSync(specPath, 'utf-8'); + const spec = JSON.parse(content) as Record; + + const parent = ensureJsonPath(spec, fieldPath); + parent[fieldPath.at(-1)!] = newVersion; + + const indent = detectJsonIndent(content); + const trailingNewline = content.endsWith('\n') ? '\n' : ''; + writeFileSync(specPath, JSON.stringify(spec, null, indent) + trailingNewline, 'utf-8'); +} + +/** + * Update the version field inside a YAML spec file. + * Uses parseDocument() to preserve comments, formatting, and key ordering. + * Creates missing intermediate keys if needed. + */ +function updateYamlSpec(specPath: string, fieldPath: readonly string[], newVersion: string): void { + const content = readFileSync(specPath, 'utf-8'); + const doc = parseDocument(content); + + // setIn creates intermediate nodes automatically + doc.setIn([...fieldPath], newVersion); + + writeFileSync(specPath, doc.toString(), 'utf-8'); +} + +/** + * Update the version field inside a spec file. + * + * Reads the spec, updates the appropriate version field based on contract type, + * and writes it back preserving the original format (YAML comments, JSON indentation). + * If the version field (or its parent, e.g. `info`) is missing, it will be created. + * + * @param specPath - Absolute path to the spec file + * @param newVersion - The new version string to set + * @param contractType - The type of contract (openapi, asyncapi, odcs, json-schema) + * @returns true if the file was updated, false if the spec type has no version field (e.g. json-schema) + */ +export function updateSpecVersion( + specPath: string, + newVersion: string, + contractType: ContractType +): boolean { + const fieldPath = VERSION_FIELD_PATHS[contractType]; + if (!fieldPath) { + return false; + } + + const ext = extname(specPath).toLowerCase(); + if (ext === '.json') { + updateJsonSpec(specPath, fieldPath, newVersion); + } else { + updateYamlSpec(specPath, fieldPath, newVersion); + } + + return true; +} diff --git a/packages/cli/bin/cli.js b/packages/cli/bin/cli.js index 924fff1..6021ca3 100755 --- a/packages/cli/bin/cli.js +++ b/packages/cli/bin/cli.js @@ -1,2 +1,2 @@ #!/usr/bin/env node -import '../dist/index.js'; +import '../dist/commands.js'; diff --git a/packages/cli/package.json b/packages/cli/package.json index 38f1cf3..1852b64 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,12 +1,20 @@ { "name": "@contractual/cli", "private": false, - "version": "0.0.0", + "version": "0.1.0-dev.5", "license": "MIT", "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "bin": { "contractual": "./bin/cli.js" }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, "repository": { "type": "git", "url": "https://github.com/contractual-dev/contractual.git", @@ -33,7 +41,7 @@ } ], "engines": { - "node": ">=18.12.0" + "node": ">=20.0.0" }, "scripts": { "prebuild": "pnpm rimraf dist", @@ -45,23 +53,28 @@ "files": [ "bin", "dist", + "src/config/schema.json", "README.md" ], "dependencies": { - "@contractual/generators.contract": "workspace:*", - "@contractual/generators.diff": "workspace:*", - "@contractual/generators.spec": "workspace:*", + "@contractual/changesets": "workspace:*", + "@contractual/governance": "workspace:*", + "@contractual/types": "workspace:*", + "@inquirer/prompts": "^8.1.0", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", "chalk": "^5.4.1", "commander": "^12.1.0", - "inquirer": "^12.3.2", - "ora": "^8.1.1" + "fast-glob": "^3.3.3", + "ora": "^8.1.1", + "yaml": "^2.7.0" }, "publishConfig": { "access": "public", "provenance": true }, "devDependencies": { - "@vitest/coverage-c8": "^0.33.0", + "@vitest/coverage-v8": "^3.0.0", "vitest": "^3.0.3" } } diff --git a/packages/cli/src/commands.ts b/packages/cli/src/commands.ts index 1bff637..cd3a3d6 100644 --- a/packages/cli/src/commands.ts +++ b/packages/cli/src/commands.ts @@ -1,22 +1,102 @@ import { Command } from 'commander'; -import { graduateSpec, generateContract } from './commands/generate.command.js'; +import { initCommand } from './commands/init.command.js'; +import { contractAddCommand, contractListCommand } from './commands/contract.command.js'; +import { lintCommand } from './commands/lint.command.js'; +import { diffCommand } from './commands/diff.command.js'; +import { breakingCommand } from './commands/breaking.command.js'; +import { changesetCommand } from './commands/changeset.command.js'; +import { versionCommand } from './commands/version.command.js'; +import { preEnterCommand, preExitCommand, preStatusCommand } from './commands/pre.command.js'; +import { statusCommand } from './commands/status.command.js'; const program = new Command(); -program.name('contractual'); - -const generateContractCommand = new Command('generate') - .description('Generate resources') - .command('contract') - .description('Generate a contract based on the provided OpenAPI file') - .action(() => { - return generateContract(); - }); - -const graduateSpecCommand = new Command('graduate').command('spec').action(() => { - return graduateSpec(); -}); -program.addCommand(graduateSpecCommand); -program.addCommand(generateContractCommand); +program.name('contractual').description('Schema contract lifecycle orchestrator').version('0.1.0'); + +program + .command('init') + .description('Initialize Contractual in this repository') + .option('-V, --initial-version ', 'Initial version for contracts') + .option('--versioning ', 'Versioning mode: independent, fixed') + .option('-y, --yes', 'Skip prompts and use defaults') + .option('--force', 'Reinitialize existing project') + .action(initCommand); + +const contractCmd = program.command('contract').description('Manage contracts'); + +contractCmd + .command('add') + .description('Add a new contract to the configuration') + .option('-n, --name ', 'Contract name') + .option('-t, --type ', 'Contract type: openapi, asyncapi, json-schema, odcs') + .option('-p, --path ', 'Path to spec file') + .option('--initial-version ', 'Initial version (default: 0.0.0)') + .option('--skip-validation', 'Skip spec validation') + .option('-y, --yes', 'Skip prompts and use defaults') + .action(contractAddCommand); + +contractCmd + .command('list [name]') + .description('List contracts (optionally filter by name)') + .option('--json', 'Output as JSON') + .action(contractListCommand); + +program + .command('lint') + .description('Lint all configured contracts') + .option('-c, --contract ', 'Lint specific contract') + .option('--format ', 'Output format: text, json', 'text') + .option('--fail-on-warn', 'Exit 1 on warnings') + .action(lintCommand); + +program + .command('diff') + .description('Show all changes between current specs and last versioned snapshots') + .option('-c, --contract ', 'Diff specific contract') + .option('--format ', 'Output format: text, json', 'text') + .option('--severity ', 'Filter: all, breaking, non-breaking, patch', 'all') + .option('--verbose', 'Show JSON Pointer paths for each change') + .action(diffCommand); + +program + .command('breaking') + .description('Detect breaking changes against last snapshot') + .option('-c, --contract ', 'Check specific contract') + .option('--format ', 'Output format: text, json', 'text') + .option('--fail-on ', 'Exit 1 on: breaking, non-breaking, any', 'breaking') + .action(breakingCommand); -program.parse(process.argv); +program + .command('changeset') + .description('Create changeset from detected changes') + .action(changesetCommand); + +program + .command('version') + .description('Consume changesets and bump versions') + .option('-y, --yes', 'Skip confirmation prompt') + .option('--dry-run', 'Preview without applying') + .option('--json', 'Output JSON (implies --yes)') + .option('--no-sync-version', 'Skip updating version field inside spec files') + .action(versionCommand); + +const preCmd = program.command('pre').description('Manage pre-release versions'); + +preCmd + .command('enter ') + .description('Enter pre-release mode (e.g., alpha, beta, rc)') + .action(preEnterCommand); + +preCmd.command('exit').description('Exit pre-release mode').action(preExitCommand); + +preCmd.command('status').description('Show pre-release status').action(preStatusCommand); + +program + .command('status') + .description('Show current versions and pending changesets') + .action(statusCommand); + +program.parseAsync(process.argv).catch((err) => { + console.error(`Error: ${err.message}`); + process.exit(2); +}); diff --git a/packages/cli/src/commands/breaking.command.ts b/packages/cli/src/commands/breaking.command.ts new file mode 100644 index 0000000..596e844 --- /dev/null +++ b/packages/cli/src/commands/breaking.command.ts @@ -0,0 +1,166 @@ +/** + * Breaking Command + * + * Detect breaking changes against snapshots. + * This is a CI gate β€” exits 1 if breaking changes are found. + * + * Uses the shared diffContracts() function internally. + */ + +import chalk from 'chalk'; +import ora from 'ora'; +import { loadConfig } from '../config/index.js'; +import { diffContracts } from '../core/diff.js'; +import { formatSeverity } from '../utils/output.js'; +import type { DiffResult } from '@contractual/types'; + +interface BreakingOptions { + contract?: string; + format?: 'text' | 'json'; + failOn?: 'breaking' | 'non-breaking' | 'any'; +} + +interface BreakingCommandResult { + hasBreaking: boolean; + results: DiffResult[]; +} + +/** + * Detect breaking changes against snapshots + */ +export async function breakingCommand(options: BreakingOptions): Promise { + const spinner = ora('Loading configuration...').start(); + + let config; + try { + config = loadConfig(); + spinner.succeed('Configuration loaded'); + } catch (error) { + spinner.fail('Failed to load configuration'); + const message = error instanceof Error ? error.message : 'Unknown error'; + console.error(chalk.red(message)); + process.exitCode = 1; + return; + } + + const checkSpinner = ora('Checking for breaking changes...').start(); + + try { + const { results } = await diffContracts(config, { + contracts: options.contract ? [options.contract] : undefined, + includeEmpty: true, + }); + + const hasBreaking = results.some((r) => r.summary.breaking > 0); + + if (hasBreaking) { + checkSpinner.fail('Breaking changes detected'); + } else { + checkSpinner.succeed('No breaking changes'); + } + + // Output results + console.log(); + if (options.format === 'json') { + const output: BreakingCommandResult = { hasBreaking, results }; + console.log(JSON.stringify(output, null, 2)); + } else { + printTextResults(results); + } + + // Determine exit code based on --fail-on option + const failOn = options.failOn ?? 'breaking'; + let shouldFail = false; + + if (failOn === 'any') { + // Fail on any detected changes + shouldFail = results.some((r) => r.changes.length > 0); + } else if (failOn === 'non-breaking') { + // Fail on non-breaking or breaking changes + shouldFail = results.some((r) => r.summary.breaking > 0 || r.summary.nonBreaking > 0); + } else { + // Default: fail only on breaking changes + shouldFail = hasBreaking; + } + + if (shouldFail) { + process.exitCode = 1; + } + } catch (error) { + checkSpinner.fail('Failed to check for breaking changes'); + const message = error instanceof Error ? error.message : 'Unknown error'; + console.error(chalk.red('Error:'), message); + process.exitCode = 1; + } +} + +/** + * Print results in human-readable text format + */ +function printTextResults(results: DiffResult[]): void { + if (results.length === 0) { + console.log(chalk.gray('No contracts were checked.')); + return; + } + + for (const result of results) { + console.log(chalk.bold.underline(result.contract)); + console.log(); + + if (result.changes.length === 0) { + console.log(chalk.gray(' No changes detected.')); + console.log(); + continue; + } + + // Group changes by severity + const breaking = result.changes.filter((c) => c.severity === 'breaking'); + const nonBreaking = result.changes.filter((c) => c.severity === 'non-breaking'); + const patch = result.changes.filter((c) => c.severity === 'patch'); + const unknown = result.changes.filter((c) => c.severity === 'unknown'); + + // Print summary + console.log( + ` Summary: ` + + `${chalk.red(String(result.summary.breaking))} breaking, ` + + `${chalk.yellow(String(result.summary.nonBreaking))} non-breaking, ` + + `${chalk.green(String(result.summary.patch))} patch, ` + + `${chalk.gray(String(result.summary.unknown))} unknown` + ); + console.log(` Suggested bump: ${chalk.cyan(result.suggestedBump)}`); + console.log(); + + // Print changes by severity + if (breaking.length > 0) { + console.log(` ${formatSeverity('breaking')} Changes:`); + for (const change of breaking) { + console.log(` - ${change.path}: ${change.message}`); + } + console.log(); + } + + if (nonBreaking.length > 0) { + console.log(` ${formatSeverity('non-breaking')} Changes:`); + for (const change of nonBreaking) { + console.log(` - ${change.path}: ${change.message}`); + } + console.log(); + } + + if (patch.length > 0) { + console.log(` ${formatSeverity('patch')} Changes:`); + for (const change of patch) { + console.log(` - ${change.path}: ${change.message}`); + } + console.log(); + } + + if (unknown.length > 0) { + console.log(` ${formatSeverity('unknown')} Changes:`); + for (const change of unknown) { + console.log(` - ${change.path}: ${change.message}`); + } + console.log(); + } + } +} diff --git a/packages/cli/src/commands/changeset.command.ts b/packages/cli/src/commands/changeset.command.ts new file mode 100644 index 0000000..ba6a733 --- /dev/null +++ b/packages/cli/src/commands/changeset.command.ts @@ -0,0 +1,79 @@ +/** + * Changeset Command + * + * Auto-generate changeset from detected changes. + * Uses the shared diffContracts() function internally. + */ + +import { writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import chalk from 'chalk'; +import ora from 'ora'; +import { loadConfig } from '../config/index.js'; +import { diffContracts } from '../core/diff.js'; +import { CHANGESETS_DIR } from '../utils/files.js'; +import { + createChangeset, + generateUniqueChangesetName, + readChangesets, +} from '@contractual/changesets'; + +/** + * Auto-generate changeset from detected changes + */ +export async function changesetCommand(): Promise { + const spinner = ora('Loading configuration...').start(); + + let config; + try { + config = loadConfig(); + spinner.succeed('Configuration loaded'); + } catch (error) { + spinner.fail('Failed to load configuration'); + const message = error instanceof Error ? error.message : 'Unknown error'; + console.error(chalk.red(message)); + process.exit(1); + } + + // Detect changes for all contracts using shared diff logic + const diffSpinner = ora('Detecting changes...').start(); + + try { + const { results: diffResults, contractualDir } = await diffContracts(config, { + includeEmpty: false, // Only get contracts with actual changes + }); + + if (diffResults.length === 0) { + diffSpinner.succeed('No changes detected'); + console.log(chalk.gray('No changeset created.')); + process.exit(0); + } + + diffSpinner.succeed(`Detected changes in ${diffResults.length} contract(s)`); + + // Create changeset content + const { content: changesetContent } = createChangeset(diffResults); + + // Read existing changesets to ensure unique name + const changesetsDir = join(contractualDir, CHANGESETS_DIR); + const existingChangesets = await readChangesets(changesetsDir); + const existingNames = existingChangesets.map((c) => c.filename.replace(/\.md$/, '')); + const changesetName = generateUniqueChangesetName(existingNames); + + // Write changeset file + const changesetPath = join(changesetsDir, `${changesetName}.md`); + + writeFileSync(changesetPath, changesetContent, 'utf-8'); + + console.log(); + console.log(chalk.green('Created changeset:'), chalk.cyan(changesetPath)); + console.log(); + console.log(chalk.gray('You can edit this file to add more details about the changes.')); + console.log(chalk.gray('Run `contractual version` to consume changesets and bump versions.')); + } catch (error) { + diffSpinner.fail('Failed to detect changes'); + const message = error instanceof Error ? error.message : 'Unknown error'; + console.error(chalk.red('Error:'), message); + process.exit(1); + } +} diff --git a/packages/cli/src/commands/contract.command.ts b/packages/cli/src/commands/contract.command.ts new file mode 100644 index 0000000..c62b9a9 --- /dev/null +++ b/packages/cli/src/commands/contract.command.ts @@ -0,0 +1,374 @@ +import { existsSync, readFileSync, writeFileSync } from 'node:fs'; +import { join, resolve, extname } from 'node:path'; +import chalk from 'chalk'; +import { parse as parseYaml, stringify as stringifyYaml } from 'yaml'; +import { VersionManager, updateSpecVersion } from '@contractual/changesets'; +import { loadConfig } from '../config/index.js'; +import { + ensureContractualDir, + detectSpecType, + CONTRACTUAL_DIR, + findContractualDir, +} from '../utils/files.js'; +import { + promptInput, + promptSelect, + promptVersion, + CONTRACT_TYPE_CHOICES, + type PromptOptions, +} from '../utils/prompts.js'; +import type { ContractDefinition, ContractType } from '@contractual/types'; + +/** + * Default version for new contracts + */ +const DEFAULT_VERSION = '0.0.0'; + +/** + * Options for the contract add command + */ +interface ContractAddOptions extends PromptOptions { + /** Contract name */ + name?: string; + /** Contract type */ + type?: ContractType; + /** Path to spec file */ + path?: string; + /** Initial version */ + initialVersion?: string; + /** Skip validation */ + skipValidation?: boolean; +} + +/** + * Add a new contract to the configuration + */ +export async function contractAddCommand(options: ContractAddOptions = {}): Promise { + const cwd = process.cwd(); + const configPath = join(cwd, 'contractual.yaml'); + + // Check if initialized + if (!existsSync(configPath)) { + console.log(chalk.red('Not initialized:') + ' contractual.yaml not found'); + console.log(chalk.dim('Run `contractual init` first')); + process.exitCode = 1; + return; + } + + // Read existing config + const configContent = readFileSync(configPath, 'utf-8'); + const config = parseYaml(configContent) as { + contracts?: ContractDefinition[]; + changeset?: unknown; + versioning?: unknown; + ai?: unknown; + }; + + if (!config.contracts) { + config.contracts = []; + } + + // Get contract details through prompts or options + const contractName = await getContractName(config.contracts, options); + if (!contractName) return; + + const specPath = await getSpecPath(cwd, options); + if (!specPath) return; + + const contractType = await getContractType(cwd, specPath, options); + if (!contractType) return; + + const version = await getVersion(options); + + // Validate spec file + if (!options.skipValidation) { + const absolutePath = resolve(cwd, specPath); + const detectedType = detectSpecType(absolutePath); + + if (!detectedType) { + console.log(chalk.red('Invalid spec file:') + ' Could not detect spec type'); + console.log(chalk.dim(`Expected: ${contractType}`)); + process.exitCode = 1; + return; + } + + if (detectedType !== contractType) { + console.log( + chalk.yellow('Type mismatch:') + + ` Detected ${chalk.cyan(detectedType)}, specified ${chalk.cyan(contractType)}` + ); + console.log(chalk.dim('Use --skip-validation to override')); + process.exitCode = 1; + return; + } + + console.log(chalk.green('βœ“') + ` Valid ${contractType} spec`); + } + + // Create contract definition + const contract: ContractDefinition = { + name: contractName, + type: contractType, + path: specPath, + }; + + // Add to config + config.contracts.push(contract); + + // Write updated config + const yamlContent = stringifyYaml(config, { + lineWidth: 100, + singleQuote: true, + }); + writeFileSync(configPath, yamlContent, 'utf-8'); + + // Ensure .contractual directory exists and create snapshot + const contractualDir = findContractualDir(cwd) ?? join(cwd, CONTRACTUAL_DIR); + ensureContractualDir(cwd); + + const versionManager = new VersionManager(contractualDir); + const absolutePath = resolve(cwd, specPath); + updateSpecVersion(absolutePath, version, contractType); + versionManager.setVersion(contractName, version, absolutePath); + + // Print summary + const snapshotExt = extname(specPath) || '.yaml'; + console.log(); + console.log( + chalk.green('βœ“') + ` Added ${chalk.cyan(contractName)} (${contractType}) at v${version}` + ); + console.log(); + console.log(chalk.bold('Updated:')); + console.log(` ${chalk.yellow('~')} contractual.yaml`); + console.log(chalk.bold('Created:')); + console.log(` ${chalk.green('+')} .contractual/snapshots/${contractName}${snapshotExt}`); + console.log(` ${chalk.green('+')} .contractual/versions.json (updated)`); +} + +/** + * Get contract name through prompts or options + */ +async function getContractName( + existingContracts: ContractDefinition[], + options: ContractAddOptions +): Promise { + const existingNames = new Set(existingContracts.map((c) => c.name)); + + if (options.name) { + if (existingNames.has(options.name)) { + console.log(chalk.red('Contract exists:') + ` ${options.name} already defined`); + process.exitCode = 1; + return null; + } + return options.name; + } + + const name = await promptInput('Contract name:', '', options); + + if (!name) { + console.log(chalk.red('Contract name is required')); + process.exitCode = 1; + return null; + } + + if (existingNames.has(name)) { + console.log(chalk.red('Contract exists:') + ` ${name} already defined`); + process.exitCode = 1; + return null; + } + + // Validate name format (alphanumeric, hyphens, underscores) + if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name)) { + console.log( + chalk.red('Invalid name:') + + ' Must start with letter, contain only letters, numbers, hyphens, underscores' + ); + process.exitCode = 1; + return null; + } + + return name; +} + +/** + * Get spec path through prompts or options + */ +async function getSpecPath(cwd: string, options: ContractAddOptions): Promise { + if (options.path) { + const absolutePath = resolve(cwd, options.path); + if (!existsSync(absolutePath)) { + console.log(chalk.red('File not found:') + ` ${options.path}`); + process.exitCode = 1; + return null; + } + return options.path; + } + + const path = await promptInput('Path to spec file:', '', options); + + if (!path) { + console.log(chalk.red('Spec path is required')); + process.exitCode = 1; + return null; + } + + const absolutePath = resolve(cwd, path); + if (!existsSync(absolutePath)) { + console.log(chalk.red('File not found:') + ` ${path}`); + process.exitCode = 1; + return null; + } + + return path; +} + +/** + * Get contract type through prompts or options + */ +async function getContractType( + cwd: string, + specPath: string, + options: ContractAddOptions +): Promise { + if (options.type) { + return options.type; + } + + // Try to auto-detect type + const absolutePath = resolve(cwd, specPath); + const detectedType = detectSpecType(absolutePath); + + if (detectedType && options.yes) { + return detectedType; + } + + const typeChoices = CONTRACT_TYPE_CHOICES.map((c) => ({ + ...c, + name: detectedType === c.value ? `${c.name} (detected)` : c.name, + })); + + return promptSelect('Contract type:', [...typeChoices], detectedType ?? 'openapi', options); +} + +/** + * Get version through prompts or options + */ +async function getVersion(options: ContractAddOptions): Promise { + if (options.initialVersion) { + return options.initialVersion; + } + + return promptVersion('Initial version:', DEFAULT_VERSION, options); +} + +/** + * Options for the contract list command + */ +interface ContractListOptions { + /** Output as JSON */ + json?: boolean; +} + +/** + * Contract info for list output + */ +interface ContractInfo { + name: string; + type: ContractType; + version: string; + path: string; +} + +/** + * List contracts + */ +export async function contractListCommand( + name: string | undefined, + options: ContractListOptions = {} +): Promise { + let config; + try { + config = loadConfig(); + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + console.error(chalk.red('Failed to load configuration:'), message); + process.exitCode = 1; + return; + } + + const contractualDir = findContractualDir(config.configDir); + const versionManager = contractualDir ? new VersionManager(contractualDir) : null; + + // Build contract info list + let contracts: ContractInfo[] = config.contracts.map((c) => ({ + name: c.name, + type: c.type, + version: versionManager?.getVersion(c.name) ?? '0.0.0', + path: c.path, + })); + + // Filter by name if provided + if (name) { + contracts = contracts.filter((c) => c.name === name); + if (contracts.length === 0) { + console.error(chalk.red(`Contract not found: ${name}`)); + process.exitCode = 1; + return; + } + } + + // Output + if (options.json) { + console.log(JSON.stringify(contracts, null, 2)); + return; + } + + // Table output + if (contracts.length === 0) { + console.log(chalk.dim('No contracts configured.')); + return; + } + + // Calculate column widths + const maxNameLen = Math.max(4, ...contracts.map((c) => c.name.length)); + const maxTypeLen = Math.max(4, ...contracts.map((c) => c.type.length)); + const maxVersionLen = Math.max(7, ...contracts.map((c) => c.version.length)); + + // Header + const header = + `${'Name'.padEnd(maxNameLen)} ` + + `${'Type'.padEnd(maxTypeLen)} ` + + `${'Version'.padEnd(maxVersionLen)} ` + + `Path`; + console.log(chalk.dim(header)); + console.log(chalk.dim('─'.repeat(header.length + 10))); + + // Rows + for (const contract of contracts) { + const typeColor = getTypeColor(contract.type); + console.log( + `${chalk.cyan(contract.name.padEnd(maxNameLen))} ` + + `${typeColor(contract.type.padEnd(maxTypeLen))} ` + + `${chalk.green(contract.version.padEnd(maxVersionLen))} ` + + `${chalk.dim(contract.path)}` + ); + } +} + +/** + * Get chalk color function for contract type + */ +function getTypeColor(type: ContractType): (text: string) => string { + switch (type) { + case 'openapi': + return chalk.green; + case 'asyncapi': + return chalk.magenta; + case 'json-schema': + return chalk.blue; + case 'odcs': + return chalk.yellow; + default: + return chalk.white; + } +} diff --git a/packages/cli/src/commands/diff.command.ts b/packages/cli/src/commands/diff.command.ts new file mode 100644 index 0000000..e006aed --- /dev/null +++ b/packages/cli/src/commands/diff.command.ts @@ -0,0 +1,71 @@ +/** + * Diff Command + * + * Show all changes between current specs and their last versioned snapshots, + * classified by severity. + * + * Unlike `breaking`, which is a CI gate (exits 1 on breaking changes), + * `diff` is informational β€” it always exits 0 on success. + */ + +import chalk from 'chalk'; +import ora from 'ora'; +import { loadConfig } from '../config/index.js'; +import { diffContracts } from '../core/diff.js'; +import { formatDiffText, formatDiffJson, filterBySeverity } from '../formatters/diff.js'; + +interface DiffOptions { + contract?: string; + format?: 'text' | 'json'; + severity?: 'all' | 'breaking' | 'non-breaking' | 'patch'; + verbose?: boolean; +} + +/** + * Show all changes between current specs and last versioned snapshots + */ +export async function diffCommand(options: DiffOptions): Promise { + const spinner = ora('Loading configuration...').start(); + + let config; + try { + config = loadConfig(); + spinner.succeed('Configuration loaded'); + } catch (error) { + spinner.fail('Failed to load configuration'); + const message = error instanceof Error ? error.message : 'Unknown error'; + console.error(chalk.red(message)); + process.exitCode = 2; + return; + } + + const diffSpinner = ora('Comparing specs against snapshots...').start(); + + try { + const { results } = await diffContracts(config, { + contracts: options.contract ? [options.contract] : undefined, + includeEmpty: true, // Show "no changes" for contracts with no diff + }); + + diffSpinner.succeed('Comparison complete'); + console.log(); + + // Apply severity filter + const filtered = filterBySeverity(results, options.severity ?? 'all'); + + // Output results + if (options.format === 'json') { + console.log(formatDiffJson(filtered)); + } else { + formatDiffText(filtered, { verbose: options.verbose }); + } + + // diff always exits 0 on success (it's informational, not a gate) + process.exitCode = 0; + } catch (error) { + diffSpinner.fail('Comparison failed'); + const message = error instanceof Error ? error.message : 'Unknown error'; + console.error(chalk.red('Error:'), message); + process.exitCode = 3; // Tool execution error + } +} diff --git a/packages/cli/src/commands/generate.command.ts b/packages/cli/src/commands/generate.command.ts deleted file mode 100644 index 01d1e2c..0000000 --- a/packages/cli/src/commands/generate.command.ts +++ /dev/null @@ -1,29 +0,0 @@ -import inquirer from 'inquirer'; -import { createContractCommandHandler } from '@contractual/generators.contract'; -import ora from 'ora'; -import chalk from 'chalk'; -import path from 'node:path'; -import process from 'node:process'; -import { - generateSpecification, - getLatestVersion, - initializePaths, -} from '@contractual/generators.spec'; - -export function generateContract() { - return createContractCommandHandler( - ora, - chalk, - console, - path.resolve( - process.cwd(), - 'contractual', - 'specs', - `openapi-v${getLatestVersion(initializePaths().configFilePath)}.yaml` - ) - ).handle(); -} - -export function graduateSpec() { - return generateSpecification(inquirer); -} diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts new file mode 100644 index 0000000..1263f3e --- /dev/null +++ b/packages/cli/src/commands/index.ts @@ -0,0 +1,6 @@ +export * from './init.command.js'; +export * from './lint.command.js'; +export * from './breaking.command.js'; +export * from './changeset.command.js'; +export * from './version.command.js'; +export * from './status.command.js'; diff --git a/packages/cli/src/commands/init.command.ts b/packages/cli/src/commands/init.command.ts new file mode 100644 index 0000000..376ba3e --- /dev/null +++ b/packages/cli/src/commands/init.command.ts @@ -0,0 +1,397 @@ +import { existsSync, writeFileSync, readFileSync } from 'node:fs'; +import { join, basename } from 'node:path'; +import fg from 'fast-glob'; +import chalk from 'chalk'; +import ora from 'ora'; +import { parse as parseYaml, stringify as stringifyYaml } from 'yaml'; +import { VersionManager } from '@contractual/changesets'; +import { + ensureContractualDir, + detectSpecType, + CONTRACTUAL_DIR, + getSnapshotPath, +} from '../utils/files.js'; +import { + promptSelect, + promptVersion, + promptConfirm, + VERSION_CHOICES, + VERSIONING_MODE_CHOICES, + type PromptOptions, +} from '../utils/prompts.js'; +import type { ContractDefinition, ContractType, VersioningMode } from '@contractual/types'; + +/** + * Default starting version for new contracts + */ +const DEFAULT_VERSION = '0.0.0'; + +/** + * Default versioning mode + */ +const DEFAULT_VERSIONING_MODE: VersioningMode = 'independent'; + +/** + * Options for the init command + */ +interface InitOptions extends PromptOptions { + /** Initial version for contracts */ + initialVersion?: string; + /** Versioning mode */ + versioning?: VersioningMode; + /** Force reinitialize */ + force?: boolean; +} + +/** + * Glob patterns to find spec files + */ +const SPEC_PATTERNS = [ + '**/*.openapi.yaml', + '**/*.openapi.yml', + '**/*.openapi.json', + '**/openapi.yaml', + '**/openapi.yml', + '**/openapi.json', + '**/*.asyncapi.yaml', + '**/*.asyncapi.yml', + '**/*.asyncapi.json', + '**/*.schema.json', + '**/*.odcs.yaml', + '**/*.odcs.yml', +]; + +/** + * Directories to ignore when scanning + */ +const IGNORE_PATTERNS = [ + '**/node_modules/**', + '**/.git/**', + '**/dist/**', + '**/build/**', + '**/.contractual/**', +]; + +/** + * Extract contract name from file path + * Uses the filename stem without extension patterns + */ +function extractContractName(filePath: string): string { + const base = basename(filePath); + + // Remove known suffixes and extensions + let name = base + .replace(/\.openapi\.(ya?ml|json)$/i, '') + .replace(/\.asyncapi\.(ya?ml|json)$/i, '') + .replace(/\.schema\.json$/i, '') + .replace(/\.odcs\.ya?ml$/i, '') + .replace(/\.(ya?ml|json)$/i, ''); + + // Handle generic names like "openapi" -> use parent directory name + if (['openapi', 'asyncapi', 'schema', 'spec', 'api'].includes(name.toLowerCase())) { + const parts = filePath.split('/'); + if (parts.length >= 2) { + name = parts[parts.length - 2]; + } + } + + return name; +} + +/** + * Get initial version through prompts or options + */ +async function getInitialVersion(options: InitOptions): Promise { + // If version provided via CLI, use it + if (options.initialVersion) { + return options.initialVersion; + } + + // Prompt for version + const versionChoice = await promptSelect( + 'Initial version for contracts:', + [...VERSION_CHOICES], + '0.0.0', + options + ); + + if (versionChoice === 'custom') { + return promptVersion('Enter version:', DEFAULT_VERSION, options); + } + + return versionChoice; +} + +/** + * Get versioning mode through prompts or options + */ +async function getVersioningMode(options: InitOptions): Promise { + if (options.versioning) { + return options.versioning; + } + + return promptSelect( + 'Versioning mode:', + [...VERSIONING_MODE_CHOICES], + DEFAULT_VERSIONING_MODE, + options + ); +} + +/** + * Initialize Contractual in a repository + * + * Scans for spec files and generates contractual.yaml configuration + */ +export async function initCommand(options: InitOptions = {}): Promise { + const cwd = process.cwd(); + const configPath = join(cwd, 'contractual.yaml'); + const contractualDir = join(cwd, CONTRACTUAL_DIR); + + // Check if already initialized + if (existsSync(configPath) && !options.force) { + // Try to handle existing project with uninitialized contracts + await handleExistingProject(cwd, configPath, contractualDir, options); + return; + } + + const spinner = ora('Scanning for spec files...').start(); + + try { + // Scan for spec files + const files = await fg(SPEC_PATTERNS, { + cwd, + ignore: IGNORE_PATTERNS, + absolute: false, + onlyFiles: true, + }); + + spinner.succeed(`Found ${files.length} potential spec file(s)`); + + // Build contract definitions + const contracts: ContractDefinition[] = []; + const seenNames = new Set(); + + for (const filePath of files) { + const absolutePath = join(cwd, filePath); + const detectedType = detectSpecType(absolutePath); + + if (!detectedType) { + continue; + } + + let name = extractContractName(filePath); + + // Ensure unique names + if (seenNames.has(name)) { + let counter = 2; + while (seenNames.has(`${name}-${counter}`)) { + counter++; + } + name = `${name}-${counter}`; + } + seenNames.add(name); + + contracts.push({ + name, + type: detectedType, + path: filePath, + }); + } + + if (contracts.length === 0) { + console.log(chalk.yellow('\nNo spec files found')); + console.log(chalk.dim('\nSupported file patterns:')); + console.log(chalk.dim(' - *.openapi.yaml/json')); + console.log(chalk.dim(' - *.asyncapi.yaml/json')); + console.log(chalk.dim(' - *.schema.json')); + console.log(chalk.dim(' - *.odcs.yaml')); + console.log(chalk.dim('\nYou can manually create contractual.yaml to define contracts.')); + return; + } + + // Show found contracts + console.log(); + for (const contract of contracts) { + const typeColor = getTypeColor(contract.type); + console.log( + ` ${chalk.dim('Found:')} ${contract.path} ${chalk.dim('(')}${typeColor(contract.type)}${chalk.dim(')')}` + ); + } + console.log(); + + // Get version and mode through prompts + const initialVersion = await getInitialVersion(options); + const versioningMode = await getVersioningMode(options); + + // Generate config + const config: Record = { + contracts, + changeset: { + autoDetect: true, + requireOnPR: true, + }, + }; + + // Only add versioning section if not using defaults + if (versioningMode !== 'independent') { + config.versioning = { + mode: versioningMode, + }; + } + + // Write contractual.yaml + const yamlContent = stringifyYaml(config, { + lineWidth: 100, + singleQuote: true, + }); + writeFileSync(configPath, yamlContent, 'utf-8'); + + // Create .contractual directory structure + const createdDir = ensureContractualDir(cwd); + + // Create snapshots and set initial versions + const versionManager = new VersionManager(createdDir); + for (const contract of contracts) { + const absolutePath = join(cwd, contract.path); + versionManager.setVersion(contract.name, initialVersion, absolutePath); + } + + // Print summary + console.log(); + console.log(chalk.green('βœ“') + ' Initialized Contractual'); + console.log(); + console.log(chalk.bold('Created:')); + console.log(` ${chalk.green('+')} contractual.yaml`); + console.log(` ${chalk.green('+')} .contractual/`); + console.log(` ${chalk.green('+')} .contractual/changesets/`); + console.log(` ${chalk.green('+')} .contractual/snapshots/`); + console.log(` ${chalk.green('+')} .contractual/versions.json`); + + console.log(); + console.log(chalk.bold(`Detected ${contracts.length} contract(s) at v${initialVersion}:`)); + + for (const contract of contracts) { + const typeColor = getTypeColor(contract.type); + console.log( + ` ${chalk.cyan(contract.name)} ${chalk.dim('(')}${typeColor(contract.type)}${chalk.dim(')')}` + ); + console.log(` ${chalk.dim(contract.path)}`); + } + + if (versioningMode !== 'independent') { + console.log(); + console.log(chalk.dim(`Versioning mode: ${versioningMode}`)); + } + + console.log(); + console.log(chalk.dim('Next steps:')); + console.log(chalk.dim(' 1. Run `contractual lint` to validate your specs')); + console.log(chalk.dim(' 2. Make changes to your specs')); + console.log(chalk.dim(' 3. Run `contractual diff` to see changes')); + console.log(chalk.dim(' 4. Run `contractual changeset` to record changes')); + } catch (error) { + spinner.fail('Initialization failed'); + const message = error instanceof Error ? error.message : 'Unknown error'; + console.error(chalk.red(message)); + process.exitCode = 1; + } +} + +/** + * Handle existing project - initialize uninitialized contracts + */ +async function handleExistingProject( + cwd: string, + configPath: string, + contractualDir: string, + options: InitOptions +): Promise { + // Read existing config + const configContent = readFileSync(configPath, 'utf-8'); + const config = parseYaml(configContent) as { contracts?: ContractDefinition[] }; + + if (!config.contracts || config.contracts.length === 0) { + console.log(chalk.red('Already initialized:') + ' contractual.yaml exists'); + console.log(chalk.dim('Use `contractual status` to see current state')); + process.exitCode = 1; + return; + } + + // Ensure .contractual directory exists + ensureContractualDir(cwd); + + // Find contracts without snapshots + const uninitializedContracts: ContractDefinition[] = []; + for (const contract of config.contracts) { + const snapshotPath = getSnapshotPath(contract.name, contractualDir); + if (!snapshotPath) { + uninitializedContracts.push(contract); + } + } + + if (uninitializedContracts.length === 0) { + console.log(chalk.yellow('Already initialized:') + ' contractual.yaml exists'); + console.log(chalk.dim('All contracts have snapshots.')); + console.log(chalk.dim('Use `contractual status` to see current state')); + console.log(chalk.dim('Use `--force` to reinitialize')); + return; + } + + // Show uninitialized contracts + console.log( + chalk.yellow(`Found ${uninitializedContracts.length} contract(s) without version history:`) + ); + for (const contract of uninitializedContracts) { + console.log( + ` ${chalk.dim('-')} ${chalk.cyan(contract.name)} ${chalk.dim(`(${contract.type})`)}` + ); + } + console.log(); + + // Confirm initialization + const shouldInitialize = await promptConfirm( + `Initialize with version ${DEFAULT_VERSION}?`, + true, + options + ); + + if (!shouldInitialize) { + console.log(chalk.dim('Skipped initialization')); + return; + } + + // Initialize uninitialized contracts + const versionManager = new VersionManager(contractualDir); + for (const contract of uninitializedContracts) { + const absolutePath = join(cwd, contract.path); + if (!existsSync(absolutePath)) { + console.log( + chalk.yellow(` Skipped ${contract.name}: spec file not found at ${contract.path}`) + ); + continue; + } + versionManager.setVersion(contract.name, DEFAULT_VERSION, absolutePath); + console.log( + chalk.green('βœ“') + ` Initialized ${chalk.cyan(contract.name)} at v${DEFAULT_VERSION}` + ); + } +} + +/** + * Get chalk color function for contract type + */ +function getTypeColor(type: ContractType): (text: string) => string { + switch (type) { + case 'openapi': + return chalk.green; + case 'asyncapi': + return chalk.magenta; + case 'json-schema': + return chalk.blue; + case 'odcs': + return chalk.yellow; + default: + return chalk.white; + } +} diff --git a/packages/cli/src/commands/lint.command.ts b/packages/cli/src/commands/lint.command.ts new file mode 100644 index 0000000..a2a7ca1 --- /dev/null +++ b/packages/cli/src/commands/lint.command.ts @@ -0,0 +1,224 @@ +import chalk from 'chalk'; +import ora from 'ora'; +import { loadConfig } from '../config/index.js'; +import { getLinter as getRegisteredLinter } from '../governance/index.js'; +import { printSuccess, printError, printWarning } from '../utils/output.js'; +import type { LintResult, LintFn, ResolvedContract } from '@contractual/types'; + +/** + * Options for the lint command + */ +export interface LintOptions { + /** Filter to specific contract name */ + contract?: string; + /** Output format */ + format?: 'text' | 'json'; + /** Exit 1 on warnings */ + failOnWarn?: boolean; +} + +/** + * Result type for linter lookup + */ +type LinterLookupResult = + | { status: 'disabled' } + | { status: 'not-found'; type: string } + | { status: 'found'; linter: LintFn }; + +/** + * Get linter for a contract using the governance registry + */ +function getLinterForContract(contract: ResolvedContract): LinterLookupResult { + // Check if linting is explicitly disabled + if (contract.lint === false) { + return { status: 'disabled' }; + } + + // Use the governance registry to get the linter + const linter = getRegisteredLinter(contract.type, contract.lint); + + if (linter === null) { + return { status: 'disabled' }; + } + + if (!linter) { + return { status: 'not-found', type: contract.type }; + } + + return { status: 'found', linter }; +} + +/** + * Run linters for all configured contracts + */ +export async function lintCommand(options: LintOptions = {}): Promise { + const { contract: filterContract, format = 'text', failOnWarn = false } = options; + + try { + // Load config + const config = loadConfig(); + + // Filter contracts if specified + let contracts = config.contracts; + if (filterContract) { + contracts = contracts.filter((c) => c.name === filterContract); + if (contracts.length === 0) { + if (format === 'json') { + console.log(JSON.stringify({ error: `Contract "${filterContract}" not found` })); + } else { + printError(`Contract "${filterContract}" not found`); + } + process.exitCode = 1; + return; + } + } + + if (contracts.length === 0) { + if (format === 'json') { + console.log(JSON.stringify({ results: [], errors: 0, warnings: 0 })); + } else { + printWarning('No contracts configured'); + } + return; + } + + const results: LintResult[] = []; + const spinner = format === 'text' ? ora('Linting contracts...').start() : null; + + for (const contract of contracts) { + if (spinner) { + spinner.text = `Linting ${contract.name}...`; + } + + const linterResult = getLinterForContract(contract); + + // Linting explicitly disabled + if (linterResult.status === 'disabled') { + if (format === 'text') { + spinner?.stopAndPersist({ + symbol: chalk.dim('-'), + text: `${contract.name}: ${chalk.dim('linting disabled')}`, + }); + spinner?.start(); + } + continue; + } + + // No linter registered for this type + if (linterResult.status === 'not-found') { + if (format === 'text') { + spinner?.stopAndPersist({ + symbol: chalk.yellow('!'), + text: `${contract.name}: ${chalk.yellow(`no linter available for ${linterResult.type}`)}`, + }); + spinner?.start(); + } + continue; + } + + try { + const result = await linterResult.linter(contract.absolutePath); + results.push({ + ...result, + contract: contract.name, + }); + } catch (error) { + const message = error instanceof Error ? error.message : 'Linter execution failed'; + results.push({ + contract: contract.name, + specPath: contract.absolutePath, + errors: [ + { + path: '', + message, + severity: 'error', + }, + ], + warnings: [], + }); + } + } + + spinner?.stop(); + + // Calculate totals + const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0); + const totalWarnings = results.reduce((sum, r) => sum + r.warnings.length, 0); + + // Output results + if (format === 'json') { + console.log( + JSON.stringify( + { + results, + errors: totalErrors, + warnings: totalWarnings, + }, + null, + 2 + ) + ); + } else { + // Text format output + console.log(); + + for (const result of results) { + const hasErrors = result.errors.length > 0; + const hasWarnings = result.warnings.length > 0; + + if (!hasErrors && !hasWarnings) { + printSuccess(`${result.contract}: No issues found`); + continue; + } + + // Print contract header + const errorCount = result.errors.length; + const warningCount = result.warnings.length; + const summary = []; + if (errorCount > 0) summary.push(chalk.red(`${errorCount} error(s)`)); + if (warningCount > 0) summary.push(chalk.yellow(`${warningCount} warning(s)`)); + + console.log(`${chalk.bold(result.contract)}: ${summary.join(', ')}`); + + // Print errors + for (const error of result.errors) { + const location = error.path ? chalk.dim(`[${error.path}]`) : ''; + const rule = error.rule ? chalk.dim(`(${error.rule})`) : ''; + console.log(` ${chalk.red('error')} ${location} ${error.message} ${rule}`); + } + + // Print warnings + for (const warning of result.warnings) { + const location = warning.path ? chalk.dim(`[${warning.path}]`) : ''; + const rule = warning.rule ? chalk.dim(`(${warning.rule})`) : ''; + console.log(` ${chalk.yellow('warn')} ${location} ${warning.message} ${rule}`); + } + + console.log(); + } + + // Summary + if (results.length > 0) { + console.log( + chalk.dim( + `Checked ${results.length} contract(s): ` + + `${totalErrors} error(s), ${totalWarnings} warning(s)` + ) + ); + } + } + + // Exit with error code if there were errors (or warnings if --fail-on-warn) + if (totalErrors > 0 || (failOnWarn && totalWarnings > 0)) { + process.exitCode = 1; + } + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + if (options.format === 'json') { + console.log(JSON.stringify({ error: message })); + } else { + printError(message); + } + process.exitCode = 1; + } +} diff --git a/packages/cli/src/commands/pre.command.ts b/packages/cli/src/commands/pre.command.ts new file mode 100644 index 0000000..e962aa6 --- /dev/null +++ b/packages/cli/src/commands/pre.command.ts @@ -0,0 +1,172 @@ +import chalk from 'chalk'; +import { VersionManager, PreReleaseManager } from '@contractual/changesets'; +import { findContractualDir } from '../utils/files.js'; + +/** + * Enter pre-release mode + * @param tag - The pre-release tag (e.g., "alpha", "beta", "rc") + */ +export async function preEnterCommand(tag: string): Promise { + const cwd = process.cwd(); + const contractualDir = findContractualDir(cwd); + + if (!contractualDir) { + console.error(chalk.red('No .contractual directory found. Run `contractual init` first.')); + process.exitCode = 1; + return; + } + + try { + const versionManager = new VersionManager(contractualDir); + const preManager = new PreReleaseManager(contractualDir); + + if (preManager.isActive()) { + const state = preManager.getState(); + console.log(chalk.yellow('Already in pre-release mode:') + ` ${state?.tag}`); + console.log(chalk.dim('Run `contractual pre exit` to leave pre-release mode first.')); + process.exitCode = 1; + return; + } + + preManager.enter(tag, versionManager); + + console.log(chalk.green('βœ“') + ` Entered pre-release mode: ${chalk.cyan(tag)}`); + console.log(); + console.log(chalk.bold('Created:')); + console.log(` ${chalk.green('+')} .contractual/pre.json`); + console.log(); + console.log(chalk.dim(`Next versions will use ${tag} identifier (e.g., 2.0.0-${tag}.0)`)); + console.log( + chalk.dim('Run `contractual version` to apply changesets with pre-release versions.') + ); + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + console.error(chalk.red('Failed to enter pre-release mode:'), message); + process.exitCode = 1; + } +} + +/** + * Exit pre-release mode + */ +export async function preExitCommand(): Promise { + const cwd = process.cwd(); + const contractualDir = findContractualDir(cwd); + + if (!contractualDir) { + console.error(chalk.red('No .contractual directory found. Run `contractual init` first.')); + process.exitCode = 1; + return; + } + + try { + const versionManager = new VersionManager(contractualDir); + const preManager = new PreReleaseManager(contractualDir); + + if (!preManager.isActive()) { + console.log(chalk.yellow('Not in pre-release mode.')); + return; + } + + const state = preManager.getState(); + const currentVersions = versionManager.getAllVersions(); + + // Show what will change + console.log(chalk.bold('Exiting pre-release mode')); + console.log(); + + const hasPreReleaseVersions = Object.entries(currentVersions).some(([_, version]) => + version.includes('-') + ); + + if (hasPreReleaseVersions) { + console.log(chalk.dim('Current pre-release versions:')); + for (const [contract, version] of Object.entries(currentVersions)) { + if (version.includes('-')) { + // Extract base version + const baseVersion = version.split('-')[0]; + console.log( + ` ${chalk.cyan(contract)}: ${chalk.gray(version)} β†’ ${chalk.green(baseVersion)}` + ); + } + } + console.log(); + console.log(chalk.dim('Run `contractual version` after exiting to finalize versions.')); + } + + preManager.exit(); + + console.log(); + console.log(chalk.green('βœ“') + ' Exited pre-release mode'); + console.log(); + console.log(chalk.bold('Removed:')); + console.log(` ${chalk.red('-')} .contractual/pre.json`); + + if (state) { + console.log(); + console.log(chalk.dim(`Pre-release tag was: ${state.tag}`)); + console.log(chalk.dim(`Entered at: ${new Date(state.enteredAt).toLocaleString()}`)); + } + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + console.error(chalk.red('Failed to exit pre-release mode:'), message); + process.exitCode = 1; + } +} + +/** + * Show pre-release status + */ +export async function preStatusCommand(): Promise { + const cwd = process.cwd(); + const contractualDir = findContractualDir(cwd); + + if (!contractualDir) { + console.error(chalk.red('No .contractual directory found. Run `contractual init` first.')); + process.exitCode = 1; + return; + } + + const preManager = new PreReleaseManager(contractualDir); + + if (!preManager.isActive()) { + console.log(chalk.dim('Not in pre-release mode.')); + console.log(chalk.dim('Run `contractual pre enter ` to enter pre-release mode.')); + return; + } + + const state = preManager.getState(); + if (!state) { + console.log(chalk.yellow('Pre-release state file is corrupted.')); + console.log(chalk.dim('Run `contractual pre exit` to reset.')); + return; + } + + const versionManager = new VersionManager(contractualDir); + const currentVersions = versionManager.getAllVersions(); + + console.log(chalk.bold('Pre-release Status')); + console.log(); + console.log(` ${chalk.dim('Tag:')} ${chalk.cyan(state.tag)}`); + console.log(` ${chalk.dim('Since:')} ${new Date(state.enteredAt).toLocaleString()}`); + + // Show version changes since entering pre-release + const changedContracts = Object.entries(currentVersions).filter(([name, version]) => { + const initial = state.initialVersions[name]; + return initial && initial !== version; + }); + + if (changedContracts.length > 0) { + console.log(); + console.log(chalk.bold('Version changes since entering pre-release:')); + for (const [name, version] of changedContracts) { + const initial = state.initialVersions[name]; + console.log(` ${chalk.cyan(name)}: ${chalk.gray(initial)} β†’ ${chalk.green(version)}`); + } + } + + console.log(); + console.log(chalk.dim('Commands:')); + console.log(chalk.dim(' contractual version Apply changesets with pre-release versions')); + console.log(chalk.dim(' contractual pre exit Exit pre-release mode')); +} diff --git a/packages/cli/src/commands/status.command.ts b/packages/cli/src/commands/status.command.ts new file mode 100644 index 0000000..1782947 --- /dev/null +++ b/packages/cli/src/commands/status.command.ts @@ -0,0 +1,144 @@ +import { existsSync, readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import chalk from 'chalk'; +import { loadConfig } from '../config/index.js'; +import { findContractualDir, VERSIONS_FILE, CHANGESETS_DIR } from '../utils/files.js'; +import { aggregateBumps, incrementVersion, readChangesets } from '@contractual/changesets'; +import type { VersionsFile, ChangesetFile } from '@contractual/types'; + +/** + * Read versions.json file + */ +function readVersions(contractualDir: string): VersionsFile { + const versionsPath = join(contractualDir, VERSIONS_FILE); + + if (!existsSync(versionsPath)) { + return {}; + } + + try { + const content = readFileSync(versionsPath, 'utf-8'); + return JSON.parse(content) as VersionsFile; + } catch { + return {}; + } +} + +/** + * Show current state - versions, pending changesets, projected bumps + */ +export async function statusCommand(): Promise { + try { + // Load config + const config = loadConfig(); + const contractualDir = findContractualDir(); + + if (!contractualDir) { + console.log(chalk.red('Not initialized:') + ' .contractual directory not found'); + console.log(chalk.dim('Run `contractual init` to get started')); + process.exitCode = 1; + return; + } + + // Read versions + const versions = readVersions(contractualDir); + + // Read pending changesets + const changesetsDir = join(contractualDir, CHANGESETS_DIR); + let changesets: ChangesetFile[] = []; + try { + changesets = await readChangesets(changesetsDir); + } catch (error) { + // If parsing fails, show a warning but continue + const message = error instanceof Error ? error.message : 'Unknown error'; + console.warn(chalk.yellow(`Warning: ${message}`)); + } + + // Calculate projected bumps + const projectedBumps = aggregateBumps(changesets); + + // Print header + console.log(chalk.bold('\nContractual Status\n')); + + // Print contract versions + console.log(chalk.bold.underline('Contracts')); + console.log(); + + if (config.contracts.length === 0) { + console.log(chalk.dim(' No contracts configured')); + } else { + for (const contract of config.contracts) { + const versionEntry = versions[contract.name]; + const currentVersion = versionEntry?.version ?? '0.0.0'; + const bump = projectedBumps[contract.name]; + + let projectedVersion: string | null = null; + if (bump) { + projectedVersion = incrementVersion(currentVersion, bump); + } + + // Contract name and type + const typeLabel = chalk.dim(`(${contract.type})`); + console.log(` ${chalk.cyan(contract.name)} ${typeLabel}`); + + // Version info + const versionLabel = versionEntry + ? chalk.green(`v${currentVersion}`) + : chalk.dim('v0.0.0 (unreleased)'); + + if (projectedVersion) { + const bumpColor = + bump === 'major' ? chalk.red : bump === 'minor' ? chalk.yellow : chalk.green; + console.log( + ` ${versionLabel} ${chalk.dim('->')} ${bumpColor(`v${projectedVersion}`)} ${chalk.dim(`(${bump})`)}` + ); + } else { + console.log(` ${versionLabel}`); + } + + // Release date + if (versionEntry?.released) { + const date = new Date(versionEntry.released).toLocaleDateString(); + console.log(` ${chalk.dim(`Released: ${date}`)}`); + } + + console.log(); + } + } + + // Print pending changesets + console.log(chalk.bold.underline('Pending Changesets')); + console.log(); + + if (changesets.length === 0) { + console.log(chalk.dim(' No pending changesets')); + console.log(chalk.dim(' Run `contractual add` to create one')); + } else { + console.log(` ${chalk.yellow(changesets.length.toString())} changeset(s) pending\n`); + + for (const changeset of changesets) { + console.log(` ${chalk.dim('-')} ${changeset.filename}`); + + for (const [contract, bump] of Object.entries(changeset.bumps)) { + const bumpColor = + bump === 'major' ? chalk.red : bump === 'minor' ? chalk.yellow : chalk.green; + console.log(` ${chalk.cyan(contract)}: ${bumpColor(bump)}`); + } + } + } + + console.log(); + + // Summary + const hasProjectedBumps = Object.keys(projectedBumps).length > 0; + if (hasProjectedBumps) { + console.log( + chalk.dim('Run `contractual version` to apply pending changesets and bump versions') + ); + } + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + console.error(chalk.red('Error:'), message); + process.exitCode = 1; + } +} diff --git a/packages/cli/src/commands/version.command.ts b/packages/cli/src/commands/version.command.ts new file mode 100644 index 0000000..c20dae4 --- /dev/null +++ b/packages/cli/src/commands/version.command.ts @@ -0,0 +1,336 @@ +import { existsSync, unlinkSync } from 'node:fs'; +import { join } from 'node:path'; +import chalk from 'chalk'; +import ora from 'ora'; +import { loadConfig } from '../config/index.js'; +import { findContractualDir, CHANGESETS_DIR } from '../utils/files.js'; +import { promptConfirm, type PromptOptions } from '../utils/prompts.js'; +import { + VersionManager, + PreReleaseManager, + readChangesets, + aggregateBumps, + extractContractChanges, + appendChangelog, + incrementVersion, + incrementVersionWithPreRelease, + updateSpecVersion, +} from '@contractual/changesets'; +import type { BumpResult, BumpType } from '@contractual/types'; + +/** + * Options for the version command + */ +interface VersionOptions extends PromptOptions { + /** Preview without applying */ + dryRun?: boolean; + /** Output JSON (implies --yes) */ + json?: boolean; + /** Skip updating version field inside spec files */ + syncVersion?: boolean; +} + +/** + * Pending version bump info + */ +interface PendingBump { + contract: string; + currentVersion: string; + nextVersion: string; + bumpType: BumpType; +} + +/** + * Consume changesets and bump versions + */ +export async function versionCommand(options: VersionOptions = {}): Promise { + // JSON output implies --yes (no prompts) + if (options.json) { + options.yes = true; + } + + const spinner = ora('Loading configuration...').start(); + + let config; + try { + config = loadConfig(); + spinner.succeed('Configuration loaded'); + } catch (error) { + spinner.fail('Failed to load configuration'); + const message = error instanceof Error ? error.message : 'Unknown error'; + console.error(chalk.red(message)); + process.exit(1); + } + + const contractualDir = findContractualDir(config.configDir); + if (!contractualDir) { + console.error(chalk.red('No .contractual directory found. Run `contractual init` first.')); + process.exit(1); + } + + // Read all changesets + const readSpinner = ora('Reading changesets...').start(); + const changesetsDir = join(contractualDir, CHANGESETS_DIR); + const changesets = await readChangesets(changesetsDir); + + if (changesets.length === 0) { + readSpinner.succeed('No pending changesets'); + if (options.json) { + console.log(JSON.stringify({ bumps: [], changesets: 0 }, null, 2)); + } else { + console.log(chalk.gray('Nothing to version.')); + } + process.exit(0); + } + + readSpinner.succeed(`Found ${changesets.length} changeset(s)`); + + // Aggregate bumps (highest wins per contract) + const aggregatedBumps = aggregateBumps(changesets); + + if (Object.keys(aggregatedBumps).length === 0) { + if (options.json) { + console.log(JSON.stringify({ bumps: [], changesets: changesets.length }, null, 2)); + } else { + console.log(chalk.gray('No version bumps required.')); + } + process.exit(0); + } + + // Initialize version manager for reading current versions + const versionManager = new VersionManager(contractualDir); + const preManager = new PreReleaseManager(contractualDir); + const preReleaseTag = preManager.getTag(); + + // Calculate pending bumps (preview) + const pendingBumps: PendingBump[] = []; + + for (const [contractName, bumpType] of Object.entries(aggregatedBumps)) { + const contract = config.contracts.find((c) => c.name === contractName); + if (!contract) { + continue; + } + + const currentVersion = versionManager.getVersion(contractName) ?? '0.0.0'; + const nextVersion = preReleaseTag + ? incrementVersionWithPreRelease(currentVersion, bumpType, preReleaseTag) + : incrementVersion(currentVersion, bumpType); + + pendingBumps.push({ + contract: contractName, + currentVersion, + nextVersion, + bumpType, + }); + } + + // Show pre-release mode notice + if (preReleaseTag && !options.json) { + console.log(chalk.cyan(`Pre-release mode: ${preReleaseTag}`)); + } + + // Show preview + if (options.json) { + if (options.dryRun) { + console.log( + JSON.stringify( + { + dryRun: true, + bumps: pendingBumps.map((b) => ({ + contract: b.contract, + current: b.currentVersion, + next: b.nextVersion, + type: b.bumpType, + })), + changesets: changesets.length, + }, + null, + 2 + ) + ); + return; + } + } else { + printPreviewTable(pendingBumps); + + if (options.dryRun) { + console.log(); + console.log(chalk.dim('Dry run - no changes applied')); + return; + } + } + + // Confirm before applying (unless --yes) + if (!options.json) { + const shouldApply = await promptConfirm('Apply these version bumps?', true, options); + + if (!shouldApply) { + console.log(chalk.dim('Cancelled')); + return; + } + } + + // Apply version bumps + const bumpSpinner = options.json ? null : ora('Applying version bumps...').start(); + const bumpResults: BumpResult[] = []; + const consumedChangesetPaths: string[] = []; + + for (const [contractName, bumpType] of Object.entries(aggregatedBumps)) { + const contract = config.contracts.find((c) => c.name === contractName); + if (!contract) { + if (!options.json) { + console.warn( + chalk.yellow(`Warning: Contract "${contractName}" not found in config, skipping.`) + ); + } + continue; + } + + const oldVersion = versionManager.getVersion(contractName) ?? '0.0.0'; + let newVersion: string; + + const shouldSyncVersion = options.syncVersion !== false && contract.syncVersion !== false; + + if (preReleaseTag) { + // Use pre-release version increment + newVersion = incrementVersionWithPreRelease(oldVersion, bumpType, preReleaseTag); + if (shouldSyncVersion) { + updateSpecVersion(contract.absolutePath, newVersion, contract.type); + } + versionManager.setVersion(contractName, newVersion, contract.absolutePath); + } else { + // Normal bump β€” compute version first, update spec, then bump (which copies to snapshots) + newVersion = incrementVersion(oldVersion, bumpType); + if (shouldSyncVersion) { + updateSpecVersion(contract.absolutePath, newVersion, contract.type); + } + versionManager.bump(contractName, bumpType, contract.absolutePath); + } + + // Extract changes text from changesets for this contract + const changes = extractContractChanges(changesets, contractName); + + bumpResults.push({ + contract: contractName, + oldVersion, + newVersion, + bumpType, + changes, + }); + } + + bumpSpinner?.succeed('Version bumps applied'); + + // Append to CHANGELOG.md + const changelogSpinner = options.json ? null : ora('Updating changelog...').start(); + const changelogPath = join(config.configDir, 'CHANGELOG.md'); + try { + appendChangelog(changelogPath, bumpResults); + changelogSpinner?.succeed('Changelog updated'); + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + changelogSpinner?.warn(`Failed to update changelog: ${message}`); + } + + // Delete consumed changeset files + const cleanupSpinner = options.json ? null : ora('Cleaning up changesets...').start(); + + for (const changeset of changesets) { + const changesetPath = join(changesetsDir, changeset.filename); + try { + if (existsSync(changesetPath)) { + unlinkSync(changesetPath); + consumedChangesetPaths.push(changeset.filename); + } + } catch { + // Ignore cleanup errors + } + } + cleanupSpinner?.succeed(`Removed ${consumedChangesetPaths.length} changeset(s)`); + + // Print summary + if (options.json) { + console.log( + JSON.stringify( + { + bumps: bumpResults.map((r) => ({ + contract: r.contract, + old: r.oldVersion, + new: r.newVersion, + type: r.bumpType, + })), + changesets: consumedChangesetPaths.length, + }, + null, + 2 + ) + ); + } else { + console.log(); + console.log(chalk.bold('Version Summary:')); + console.log(); + + for (const result of bumpResults) { + console.log( + ` ${chalk.cyan(result.contract)}: ` + + `${chalk.gray(result.oldVersion)} -> ${chalk.green(result.newVersion)} ` + + `(${result.bumpType})` + ); + } + + console.log(); + console.log(chalk.green('Done!'), `${bumpResults.length} contract(s) versioned.`); + } +} + +/** + * Print a preview table of pending version bumps + */ +function printPreviewTable(bumps: PendingBump[]): void { + console.log(); + console.log(chalk.bold('Pending version bumps:')); + console.log(); + + // Calculate column widths + const maxContractLen = Math.max(8, ...bumps.map((b) => b.contract.length)); + const maxCurrentLen = Math.max(7, ...bumps.map((b) => b.currentVersion.length)); + const maxNextLen = Math.max(4, ...bumps.map((b) => b.nextVersion.length)); + + // Header + const header = + ` ${'Contract'.padEnd(maxContractLen)} ` + + `${'Current'.padEnd(maxCurrentLen)} ` + + `${'β†’'} ` + + `${'Next'.padEnd(maxNextLen)} ` + + `Reason`; + console.log(chalk.dim(header)); + console.log(chalk.dim(' ' + '─'.repeat(header.length - 2))); + + // Rows + for (const bump of bumps) { + const reason = getBumpReason(bump.bumpType); + console.log( + ` ${chalk.cyan(bump.contract.padEnd(maxContractLen))} ` + + `${chalk.gray(bump.currentVersion.padEnd(maxCurrentLen))} ` + + `${chalk.dim('β†’')} ` + + `${chalk.green(bump.nextVersion.padEnd(maxNextLen))} ` + + `${reason}` + ); + } +} + +/** + * Get human-readable reason for bump type + */ +function getBumpReason(bumpType: BumpType): string { + switch (bumpType) { + case 'major': + return chalk.red('major (breaking)'); + case 'minor': + return chalk.yellow('minor (feature)'); + case 'patch': + return chalk.dim('patch (fix)'); + default: + return bumpType; + } +} diff --git a/packages/cli/src/config/index.ts b/packages/cli/src/config/index.ts new file mode 100644 index 0000000..1febe5a --- /dev/null +++ b/packages/cli/src/config/index.ts @@ -0,0 +1,2 @@ +export * from './loader.js'; +export * from './validator.js'; diff --git a/packages/cli/src/config/loader.ts b/packages/cli/src/config/loader.ts new file mode 100644 index 0000000..7a8d65d --- /dev/null +++ b/packages/cli/src/config/loader.ts @@ -0,0 +1,147 @@ +import { existsSync, readFileSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; +import { parse as parseYaml } from 'yaml'; +import fg from 'fast-glob'; +import type { ContractualConfig, ResolvedConfig, ResolvedContract } from '@contractual/types'; +import { validateConfig, formatValidationErrors } from './validator.js'; + +/** + * Config file names to search for + */ +const CONFIG_FILENAMES = ['contractual.yaml', 'contractual.yml']; + +/** + * Error thrown when config cannot be loaded + */ +export class ConfigError extends Error { + constructor(message: string) { + super(message); + this.name = 'ConfigError'; + } +} + +/** + * Find contractual.yaml by walking up from the given directory + */ +export function findConfigFile(startDir: string = process.cwd()): string | null { + let currentDir = resolve(startDir); + const root = dirname(currentDir); + + while (currentDir !== root) { + for (const filename of CONFIG_FILENAMES) { + const configPath = join(currentDir, filename); + if (existsSync(configPath)) { + return configPath; + } + } + const parentDir = dirname(currentDir); + if (parentDir === currentDir) break; + currentDir = parentDir; + } + + // Check root directory too + for (const filename of CONFIG_FILENAMES) { + const configPath = join(currentDir, filename); + if (existsSync(configPath)) { + return configPath; + } + } + + return null; +} + +/** + * Parse YAML config file + */ +export function parseConfigFile(configPath: string): unknown { + const content = readFileSync(configPath, 'utf-8'); + + try { + return parseYaml(content); + } catch (err) { + const message = err instanceof Error ? err.message : 'Unknown parse error'; + throw new ConfigError(`Failed to parse ${configPath}: ${message}`); + } +} + +/** + * Resolve contract paths to absolute paths + */ +export function resolveContractPaths( + config: ContractualConfig, + configDir: string +): ResolvedContract[] { + const resolved: ResolvedContract[] = []; + + for (const contract of config.contracts) { + const pattern = contract.path; + const absolutePattern = resolve(configDir, pattern); + + // Check if it's a glob pattern + if (pattern.includes('*')) { + const matches = fg.sync(absolutePattern, { onlyFiles: true }); + if (matches.length === 0) { + console.warn( + `Warning: No files matched pattern "${pattern}" for contract "${contract.name}"` + ); + } + // For glob patterns, use the first match as the primary path + // In the future, we might support multiple specs per contract + if (matches.length > 0) { + resolved.push({ + ...contract, + absolutePath: matches[0], + }); + } + } else { + // Direct file path + if (!existsSync(absolutePattern)) { + console.warn(`Warning: File not found "${pattern}" for contract "${contract.name}"`); + } + resolved.push({ + ...contract, + absolutePath: absolutePattern, + }); + } + } + + return resolved; +} + +/** + * Load and validate config from a file path + */ +export function loadConfigFromPath(configPath: string): ResolvedConfig { + const parsed = parseConfigFile(configPath); + + const validation = validateConfig(parsed); + if (!validation.valid) { + throw new ConfigError( + `Invalid configuration in ${configPath}:\n${formatValidationErrors(validation.errors)}` + ); + } + + const config = parsed as ContractualConfig; + const configDir = dirname(configPath); + const resolvedContracts = resolveContractPaths(config, configDir); + + return { + ...config, + contracts: resolvedContracts, + configDir, + configPath, + }; +} + +/** + * Load config by searching from the current directory + */ +export function loadConfig(startDir?: string): ResolvedConfig { + const configPath = findConfigFile(startDir); + + if (!configPath) { + throw new ConfigError('No contractual.yaml found. Run `contractual init` to get started.'); + } + + return loadConfigFromPath(configPath); +} diff --git a/packages/cli/src/config/schema.json b/packages/cli/src/config/schema.json new file mode 100644 index 0000000..2b59b0d --- /dev/null +++ b/packages/cli/src/config/schema.json @@ -0,0 +1,126 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://contractual.dev/schemas/config.json", + "title": "Contractual Configuration", + "description": "Configuration file for Contractual schema contract lifecycle orchestrator", + "type": "object", + "required": ["contracts"], + "properties": { + "contracts": { + "type": "array", + "description": "List of contract definitions", + "minItems": 1, + "items": { + "type": "object", + "required": ["name", "type", "path"], + "properties": { + "name": { + "type": "string", + "description": "Unique identifier for the contract", + "pattern": "^[a-z][a-z0-9-]*$" + }, + "type": { + "type": "string", + "description": "Type of schema", + "enum": ["openapi", "json-schema", "asyncapi", "odcs"] + }, + "path": { + "type": "string", + "description": "Path to spec file (can be glob pattern)" + }, + "lint": { + "oneOf": [ + { "type": "string", "description": "Linter tool name or custom command" }, + { "type": "boolean", "const": false, "description": "Disable linting" } + ] + }, + "breaking": { + "oneOf": [ + { "type": "string", "description": "Differ tool name or custom command" }, + { "type": "boolean", "const": false, "description": "Disable breaking change detection" } + ] + }, + "syncVersion": { + "type": "boolean", + "description": "Sync version field inside the spec file on version bump (default: true). Set to false to skip.", + "default": true + }, + "generate": { + "type": "array", + "description": "Output generation commands", + "items": { "type": "string" } + } + }, + "additionalProperties": false + } + }, + "versioning": { + "type": "object", + "description": "Versioning configuration", + "properties": { + "mode": { + "type": "string", + "enum": ["independent", "fixed"], + "description": "Versioning mode: independent (each contract separate) or fixed (all share same version)", + "default": "independent" + } + }, + "additionalProperties": false + }, + "changeset": { + "type": "object", + "description": "Changeset behavior configuration", + "properties": { + "autoDetect": { + "type": "boolean", + "description": "Auto-detect change classifications", + "default": true + }, + "requireOnPR": { + "type": "boolean", + "description": "Require changeset for spec changes in PRs", + "default": true + } + }, + "additionalProperties": false + }, + "ai": { + "type": "object", + "description": "AI/LLM integration configuration", + "properties": { + "provider": { + "type": "string", + "enum": ["anthropic"], + "default": "anthropic" + }, + "model": { + "type": "string", + "description": "Model to use" + }, + "features": { + "type": "object", + "properties": { + "explain": { + "type": "boolean", + "description": "PR change explanations", + "default": true + }, + "changelog": { + "type": "boolean", + "description": "AI-enriched changelog entries", + "default": true + }, + "enhance": { + "type": "boolean", + "description": "Spec metadata enhancement", + "default": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/packages/cli/src/config/validator.ts b/packages/cli/src/config/validator.ts new file mode 100644 index 0000000..c152a43 --- /dev/null +++ b/packages/cli/src/config/validator.ts @@ -0,0 +1,58 @@ +import Ajv from 'ajv'; +import addFormats from 'ajv-formats'; +const AjvConstructor = Ajv.default ?? Ajv; +const addFormatsFunc = addFormats.default ?? addFormats; +import type { ContractualConfig } from '@contractual/types'; +import configSchema from './schema.json' with { type: 'json' }; + +/** + * Validation error with path and message + */ +export interface ValidationError { + path: string; + message: string; +} + +/** + * Result of config validation + */ +export interface ValidationResult { + valid: boolean; + errors: ValidationError[]; +} + +/** + * Validates a parsed config object against the JSON schema + */ +export function validateConfig(config: unknown): ValidationResult { + const ajv = new AjvConstructor({ allErrors: true, strict: false }); + addFormatsFunc(ajv); + + const validate = ajv.compile(configSchema); + const valid = validate(config); + + if (valid) { + return { valid: true, errors: [] }; + } + + const errors: ValidationError[] = (validate.errors ?? []).map((err) => ({ + path: err.instancePath || '/', + message: err.message ?? 'Unknown validation error', + })); + + return { valid: false, errors }; +} + +/** + * Type guard to check if config is valid ContractualConfig + */ +export function isValidConfig(config: unknown): config is ContractualConfig { + return validateConfig(config).valid; +} + +/** + * Format validation errors for display + */ +export function formatValidationErrors(errors: ValidationError[]): string { + return errors.map((err) => ` ${err.path}: ${err.message}`).join('\n'); +} diff --git a/packages/cli/src/core/diff.ts b/packages/cli/src/core/diff.ts new file mode 100644 index 0000000..2ff6b4e --- /dev/null +++ b/packages/cli/src/core/diff.ts @@ -0,0 +1,128 @@ +/** + * Core diffing logic + * + * Shared module for running diffs across contracts. + * Used by `diff`, `breaking`, and `changeset` commands. + */ + +import type { ResolvedConfig, ResolvedContract, DiffResult } from '@contractual/types'; +import { getDiffer } from '../governance/index.js'; +import { findContractualDir, getSnapshotPath } from '../utils/files.js'; + +export interface DiffOptions { + /** Filter to specific contract names */ + contracts?: string[]; + /** Include contracts with no changes in results */ + includeEmpty?: boolean; +} + +export interface DiffContractsResult { + results: DiffResult[]; + contractualDir: string; +} + +/** + * Core diffing function. Runs the appropriate differ for each contract, + * comparing current spec against last versioned snapshot. + * + * Returns classified changes for all contracts. + * This is the primitive that `diff`, `breaking`, and `changeset` all use. + */ +export async function diffContracts( + config: ResolvedConfig, + options: DiffOptions = {} +): Promise { + const contractualDir = findContractualDir(config.configDir); + if (!contractualDir) { + throw new Error('No .contractual directory found. Run `contractual init` first.'); + } + + // Filter contracts if specific ones requested + let contracts: ResolvedContract[] = config.contracts; + if (options.contracts && options.contracts.length > 0) { + contracts = config.contracts.filter((c) => options.contracts!.includes(c.name)); + + // Check for missing contracts + for (const name of options.contracts) { + if (!config.contracts.some((c) => c.name === name)) { + throw new Error(`Contract "${name}" not found in configuration.`); + } + } + } + + const results: DiffResult[] = []; + + for (const contract of contracts) { + const result = await diffSingleContract(contract, contractualDir); + + // Include result if it has changes, or if includeEmpty is true + if (result.changes.length > 0 || options.includeEmpty) { + results.push(result); + } + } + + return { results, contractualDir }; +} + +/** + * Diff a single contract against its snapshot + */ +async function diffSingleContract( + contract: ResolvedContract, + contractualDir: string +): Promise { + // Get snapshot path + const snapshotPath = getSnapshotPath(contract.name, contractualDir); + + // No snapshot = first version, no changes + if (!snapshotPath) { + return createEmptyResult(contract.name, 'first-version'); + } + + // Check if breaking detection is disabled + if (contract.breaking === false) { + return createEmptyResult(contract.name, 'disabled'); + } + + // Get the differ from the registry + const differ = getDiffer(contract.type, contract.breaking); + + if (differ === null) { + // Disabled via config override + return createEmptyResult(contract.name, 'disabled'); + } + + if (!differ) { + // No differ registered for this type + return createEmptyResult(contract.name, 'no-differ'); + } + + // Run the differ: snapshot (old) vs current spec (new) + const diffResult = await differ(snapshotPath, contract.absolutePath); + + // Override contract name to match config + return { + ...diffResult, + contract: contract.name, + }; +} + +/** + * Create an empty diff result + */ +function createEmptyResult( + contractName: string, + _reason: 'first-version' | 'disabled' | 'no-differ' +): DiffResult { + return { + contract: contractName, + changes: [], + summary: { + breaking: 0, + nonBreaking: 0, + patch: 0, + unknown: 0, + }, + suggestedBump: 'none', + }; +} diff --git a/packages/cli/src/formatters/diff.ts b/packages/cli/src/formatters/diff.ts new file mode 100644 index 0000000..ff1b495 --- /dev/null +++ b/packages/cli/src/formatters/diff.ts @@ -0,0 +1,164 @@ +/** + * Diff output formatters + * + * Format diff results for text and JSON output. + */ + +import chalk from 'chalk'; +import type { DiffResult, Change, ChangeSeverity, DiffSummary } from '@contractual/types'; + +export interface FormatOptions { + /** Show JSON Pointer paths for each change */ + verbose?: boolean; +} + +/** + * Format diff results as human-readable text + */ +export function formatDiffText(results: DiffResult[], options: FormatOptions = {}): void { + if (results.length === 0) { + console.log(chalk.dim('No contracts to diff.')); + return; + } + + for (const result of results) { + formatContractResult(result, options); + } +} + +/** + * Format a single contract's diff result + */ +function formatContractResult(result: DiffResult, options: FormatOptions): void { + if (result.changes.length === 0) { + console.log(`${chalk.cyan(result.contract)}: ${chalk.dim('no changes')}`); + console.log(); + return; + } + + // Build summary parts + const parts: string[] = []; + if (result.summary.breaking > 0) { + parts.push(`${result.summary.breaking} breaking`); + } + if (result.summary.nonBreaking > 0) { + parts.push(`${result.summary.nonBreaking} non-breaking`); + } + if (result.summary.patch > 0) { + parts.push(`${result.summary.patch} patch`); + } + if (result.summary.unknown > 0) { + parts.push(`${result.summary.unknown} unknown`); + } + + // Color for suggested bump + const bumpColor = + result.suggestedBump === 'major' + ? chalk.red + : result.suggestedBump === 'minor' + ? chalk.yellow + : chalk.green; + + // Header line + console.log( + `${chalk.cyan(result.contract)}: ${result.changes.length} change(s) (${parts.join(', ')}) β€” suggested bump: ${bumpColor(result.suggestedBump)}` + ); + console.log(); + + // Each change + for (const change of result.changes) { + const label = formatSeverityLabel(change.severity); + console.log(` ${label} ${change.message}`); + if (options.verbose && change.path) { + console.log(`${' '.repeat(14)}path: ${chalk.dim(change.path)}`); + } + } + console.log(); +} + +/** + * Format severity as a colored, padded label + */ +function formatSeverityLabel(severity: ChangeSeverity): string { + switch (severity) { + case 'breaking': + return chalk.red.bold('BREAKING'.padEnd(12)); + case 'non-breaking': + return chalk.yellow('non-breaking'); + case 'patch': + return chalk.green('patch'.padEnd(12)); + case 'unknown': + return chalk.gray('unknown'.padEnd(12)); + default: + return chalk.gray(String(severity).padEnd(12)); + } +} + +/** + * Format diff results as JSON + * + * Output format: + * { + * "contracts": { + * "order": { "changes": [...], "summary": {...}, "suggestedBump": "minor" }, + * "petstore": { "changes": [...], "summary": {...}, "suggestedBump": "none" } + * } + * } + */ +export function formatDiffJson(results: DiffResult[]): string { + const contracts: Record> = {}; + + for (const result of results) { + const { contract, ...rest } = result; + contracts[contract] = rest; + } + + return JSON.stringify({ contracts }, null, 2); +} + +/** + * Filter results by severity level + */ +export function filterBySeverity(results: DiffResult[], severity: string): DiffResult[] { + if (severity === 'all') { + return results; + } + + return results.map((r) => { + const filteredChanges = r.changes.filter((c) => c.severity === severity); + return { + ...r, + changes: filteredChanges, + summary: recalculateSummary(filteredChanges), + suggestedBump: calculateSuggestedBump(filteredChanges), + }; + }); +} + +/** + * Recalculate summary from filtered changes + */ +function recalculateSummary(changes: Change[]): DiffSummary { + return { + breaking: changes.filter((c) => c.severity === 'breaking').length, + nonBreaking: changes.filter((c) => c.severity === 'non-breaking').length, + patch: changes.filter((c) => c.severity === 'patch').length, + unknown: changes.filter((c) => c.severity === 'unknown').length, + }; +} + +/** + * Calculate suggested bump from changes + */ +function calculateSuggestedBump(changes: Change[]): 'major' | 'minor' | 'patch' | 'none' { + if (changes.some((c) => c.severity === 'breaking')) { + return 'major'; + } + if (changes.some((c) => c.severity === 'non-breaking')) { + return 'minor'; + } + if (changes.some((c) => c.severity === 'patch')) { + return 'patch'; + } + return 'none'; +} diff --git a/packages/cli/src/governance/index.ts b/packages/cli/src/governance/index.ts new file mode 100644 index 0000000..7148303 --- /dev/null +++ b/packages/cli/src/governance/index.ts @@ -0,0 +1,31 @@ +/** + * Re-export governance engines from @contractual/governance + * + * This module re-exports the governance registry functions and auto-registers + * all built-in engines on import. + */ + +// Import to trigger auto-registration of all engines +import '@contractual/governance'; + +// Re-export registry functions +export { + registerLinter, + registerDiffer, + getLinter, + getDiffer, + hasLinter, + hasDiffer, + getRegisteredLinterTypes, + getRegisteredDifferTypes, +} from '@contractual/governance'; + +// Re-export individual engines for direct use +export { lintOpenAPI, lintJsonSchema, diffOpenApi, diffJsonSchema } from '@contractual/governance'; + +// Re-export runner utilities for custom commands +export { + executeCustomCommand, + parseCustomLintOutput, + parseCustomDiffOutput, +} from '@contractual/governance'; diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index d0d3bbd..cc6993d 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1 +1,38 @@ -export * from './commands.js'; +// Public API for programmatic use +export { loadConfig, ConfigError } from './config/index.js'; +export { registerLinter, registerDiffer, getLinter, getDiffer } from './governance/index.js'; + +// Core diffing +export { diffContracts } from './core/diff.js'; +export type { DiffOptions, DiffContractsResult } from './core/diff.js'; + +// File utilities +export { + findContractualDir, + getSnapshotPath, + copyToSnapshots, + CONTRACTUAL_DIR, +} from './utils/files.js'; + +// Re-export from @contractual/changesets +export { + VersionManager, + PreReleaseManager, + createChangeset, + readChangesets, + aggregateBumps, + generateChangesetName, + extractContractChanges, + appendChangelog, + incrementVersion, + incrementVersionWithPreRelease, + VERSIONS_FILE, + SNAPSHOTS_DIR, + CHANGESETS_DIR, + PRE_RELEASE_FILE, + updateSpecVersion, +} from '@contractual/changesets'; +export type { BumpOperationResult } from '@contractual/changesets'; + +// Re-export types from @contractual/types +export type * from '@contractual/types'; diff --git a/packages/cli/src/utils/exec.ts b/packages/cli/src/utils/exec.ts new file mode 100644 index 0000000..8c0a540 --- /dev/null +++ b/packages/cli/src/utils/exec.ts @@ -0,0 +1,105 @@ +import { execSync, type ExecSyncOptions } from 'node:child_process'; + +/** + * Result of executing a command + */ +export interface ExecResult { + /** Standard output */ + stdout: string; + /** Standard error */ + stderr: string; + /** Exit code */ + exitCode: number; +} + +/** + * Error shape from execSync when command fails + */ +interface ExecSyncError { + stdout?: Buffer | string; + stderr?: Buffer | string; + status?: number; +} + +/** + * Type guard for execSync errors + */ +function isExecSyncError(err: unknown): err is ExecSyncError { + return ( + typeof err === 'object' && + err !== null && + ('status' in err || 'stdout' in err || 'stderr' in err) + ); +} + +/** + * Substitute placeholders in a command string + */ +export function substitutePlaceholders( + command: string, + substitutions: Record +): string { + let result = command; + for (const [key, value] of Object.entries(substitutions)) { + result = result.replace(new RegExp(`\\{${key}\\}`, 'g'), value); + } + return result; +} + +/** + * Check if a string contains placeholders + */ +export function hasPlaceholders(command: string): boolean { + return /\{(spec|old|new)\}/.test(command); +} + +/** + * Execute a command and return the result + */ +export function execCommand( + command: string, + substitutions: Record = {}, + options: ExecSyncOptions = {} +): ExecResult { + const resolvedCommand = substitutePlaceholders(command, substitutions); + + try { + const stdout = execSync(resolvedCommand, { + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + ...options, + }) as string; + return { stdout, stderr: '', exitCode: 0 }; + } catch (err: unknown) { + if (isExecSyncError(err)) { + const stdout = typeof err.stdout === 'string' ? err.stdout : (err.stdout?.toString() ?? ''); + const stderr = typeof err.stderr === 'string' ? err.stderr : (err.stderr?.toString() ?? ''); + return { + stdout, + stderr, + exitCode: err.status ?? 1, + }; + } + // Unknown error type - return generic failure + return { + stdout: '', + stderr: err instanceof Error ? err.message : 'Unknown error', + exitCode: 1, + }; + } +} + +/** + * Execute a command and throw if it fails + */ +export function execCommandOrThrow( + command: string, + substitutions: Record = {}, + options: ExecSyncOptions = {} +): string { + const result = execCommand(command, substitutions, options); + if (result.exitCode !== 0) { + throw new Error(`Command failed with exit code ${result.exitCode}: ${result.stderr}`); + } + return result.stdout; +} diff --git a/packages/cli/src/utils/files.ts b/packages/cli/src/utils/files.ts new file mode 100644 index 0000000..d73fd4f --- /dev/null +++ b/packages/cli/src/utils/files.ts @@ -0,0 +1,160 @@ +import { existsSync, readFileSync, mkdirSync, writeFileSync, copyFileSync } from 'node:fs'; +import { dirname, extname, join, resolve } from 'node:path'; +import { parse as parseYaml } from 'yaml'; +import type { ContractType } from '@contractual/types'; +import { CHANGESETS_DIR, SNAPSHOTS_DIR, VERSIONS_FILE } from '@contractual/changesets'; + +/** + * The .contractual directory name + */ +export const CONTRACTUAL_DIR = '.contractual'; + +// Re-export constants from @contractual/changesets for backward compatibility +export { CHANGESETS_DIR, SNAPSHOTS_DIR, VERSIONS_FILE }; + +/** + * Find the .contractual directory starting from a given path + */ +export function findContractualDir(startDir: string = process.cwd()): string | null { + let currentDir = resolve(startDir); + const root = dirname(currentDir); + + while (currentDir !== root) { + const contractualDir = join(currentDir, CONTRACTUAL_DIR); + if (existsSync(contractualDir)) { + return contractualDir; + } + const parentDir = dirname(currentDir); + if (parentDir === currentDir) break; + currentDir = parentDir; + } + + // Check root directory too + const contractualDir = join(currentDir, CONTRACTUAL_DIR); + if (existsSync(contractualDir)) { + return contractualDir; + } + + return null; +} + +/** + * Ensure the .contractual directory structure exists + */ +export function ensureContractualDir(baseDir: string): string { + const contractualDir = join(baseDir, CONTRACTUAL_DIR); + const changesetsDir = join(contractualDir, CHANGESETS_DIR); + const snapshotsDir = join(contractualDir, SNAPSHOTS_DIR); + + mkdirSync(changesetsDir, { recursive: true }); + mkdirSync(snapshotsDir, { recursive: true }); + + // Create versions.json if it doesn't exist + const versionsPath = join(contractualDir, VERSIONS_FILE); + if (!existsSync(versionsPath)) { + writeFileSync(versionsPath, '{}', 'utf-8'); + } + + // Create .gitkeep files + const changesetsGitkeep = join(changesetsDir, '.gitkeep'); + const snapshotsGitkeep = join(snapshotsDir, '.gitkeep'); + if (!existsSync(changesetsGitkeep)) { + writeFileSync(changesetsGitkeep, '', 'utf-8'); + } + if (!existsSync(snapshotsGitkeep)) { + writeFileSync(snapshotsGitkeep, '', 'utf-8'); + } + + return contractualDir; +} + +/** + * Read and parse a spec file (YAML or JSON) + */ +export function readSpecFile(filePath: string): unknown { + const content = readFileSync(filePath, 'utf-8'); + const ext = extname(filePath).toLowerCase(); + + if (ext === '.json') { + return JSON.parse(content); + } + + // YAML (handles .yaml, .yml) + return parseYaml(content); +} + +/** + * Detect spec type from file path and content + */ +export function detectSpecType(filePath: string): ContractType | null { + // Check extension patterns first + if (/\.openapi\.(ya?ml|json)$/i.test(filePath)) return 'openapi'; + if (/\.asyncapi\.(ya?ml|json)$/i.test(filePath)) return 'asyncapi'; + if (/\.odcs\.ya?ml$/i.test(filePath)) return 'odcs'; + if (/\.schema\.json$/i.test(filePath)) return 'json-schema'; + + // Content sniffing + try { + const spec = readSpecFile(filePath) as Record; + if (!spec || typeof spec !== 'object') return null; + + // OpenAPI detection + if ('openapi' in spec || 'swagger' in spec) return 'openapi'; + + // AsyncAPI detection + if ('asyncapi' in spec) return 'asyncapi'; + + // ODCS detection + if ('dataContractSpecification' in spec || spec.kind === 'DataContract') return 'odcs'; + + // JSON Schema detection + if ( + ('$schema' in spec && String(spec.$schema).includes('json-schema')) || + ('type' in spec && 'properties' in spec) + ) + return 'json-schema'; + + return null; + } catch { + return null; + } +} + +/** + * Get the file extension for a contract type + */ +export function getSpecExtension(filePath: string): string { + const ext = extname(filePath).toLowerCase(); + return ext || '.yaml'; +} + +/** + * Copy a spec file to the snapshots directory + */ +export function copyToSnapshots( + specPath: string, + contractName: string, + contractualDir: string +): string { + const ext = getSpecExtension(specPath); + const snapshotPath = join(contractualDir, SNAPSHOTS_DIR, `${contractName}${ext}`); + copyFileSync(specPath, snapshotPath); + return snapshotPath; +} + +/** + * Get the snapshot path for a contract + */ +export function getSnapshotPath(contractName: string, contractualDir: string): string | null { + const snapshotsDir = join(contractualDir, SNAPSHOTS_DIR); + const extensions = ['.yaml', '.yml', '.json']; + + for (const ext of extensions) { + const snapshotPath = join(snapshotsDir, `${contractName}${ext}`); + if (existsSync(snapshotPath)) { + return snapshotPath; + } + } + + return null; +} diff --git a/packages/cli/src/utils/index.ts b/packages/cli/src/utils/index.ts new file mode 100644 index 0000000..5741bd7 --- /dev/null +++ b/packages/cli/src/utils/index.ts @@ -0,0 +1,3 @@ +export * from './exec.js'; +export * from './files.js'; +export * from './output.js'; diff --git a/packages/cli/src/utils/output.ts b/packages/cli/src/utils/output.ts new file mode 100644 index 0000000..8f23bbe --- /dev/null +++ b/packages/cli/src/utils/output.ts @@ -0,0 +1,87 @@ +import chalk from 'chalk'; + +/** + * Print a success message with green checkmark + */ +export function printSuccess(message: string): void { + console.log(chalk.green('βœ“') + ' ' + message); +} + +/** + * Print an error message with red X + */ +export function printError(message: string): void { + console.log(chalk.red('βœ—') + ' ' + message); +} + +/** + * Print a warning message with yellow warning symbol + */ +export function printWarning(message: string): void { + console.log(chalk.yellow('⚠') + ' ' + message); +} + +/** + * Print an info message with blue info symbol + */ +export function printInfo(message: string): void { + console.log(chalk.blue('β„Ή') + ' ' + message); +} + +/** + * Print a simple ASCII table + */ +export function printTable(headers: string[], rows: string[][]): void { + if (headers.length === 0) { + return; + } + + // Calculate column widths + const columnWidths = headers.map((header, index) => { + const maxRowWidth = rows.reduce((max, row) => { + const cellLength = row[index]?.length ?? 0; + return Math.max(max, cellLength); + }, 0); + return Math.max(header.length, maxRowWidth); + }); + + // Create separator line + const separator = '+' + columnWidths.map((width) => '-'.repeat(width + 2)).join('+') + '+'; + + // Format a row + const formatRow = (cells: string[]): string => { + return ( + '|' + + cells.map((cell, index) => ' ' + (cell ?? '').padEnd(columnWidths[index]) + ' ').join('|') + + '|' + ); + }; + + // Print table + console.log(separator); + console.log(formatRow(headers)); + console.log(separator); + for (const row of rows) { + console.log(formatRow(row)); + } + console.log(separator); +} + +/** + * Format severity level with appropriate color + */ +export function formatSeverity( + severity: 'breaking' | 'non-breaking' | 'patch' | 'unknown' +): string { + switch (severity) { + case 'breaking': + return chalk.red.bold('BREAKING'); + case 'non-breaking': + return chalk.yellow('NON-BREAKING'); + case 'patch': + return chalk.green('PATCH'); + case 'unknown': + default: + return chalk.gray('UNKNOWN'); + } +} diff --git a/packages/cli/src/utils/prompts.ts b/packages/cli/src/utils/prompts.ts new file mode 100644 index 0000000..5d135f7 --- /dev/null +++ b/packages/cli/src/utils/prompts.ts @@ -0,0 +1,160 @@ +import { select, input, confirm } from '@inquirer/prompts'; + +/** + * Check if running in interactive mode (TTY available) + */ +export function isInteractive(): boolean { + return process.stdin.isTTY === true && process.stdout.isTTY === true; +} + +/** + * Check if running in CI environment + */ +export function isCI(): boolean { + return ( + process.env.CI === 'true' || + process.env.CI === '1' || + process.env.CONTINUOUS_INTEGRATION === 'true' || + process.env.GITHUB_ACTIONS === 'true' || + process.env.GITLAB_CI === 'true' || + process.env.CIRCLECI === 'true' + ); +} + +/** + * Options for prompt functions + */ +export interface PromptOptions { + /** Skip prompts and use defaults (--yes flag) */ + yes?: boolean; + /** Force interactive mode even in CI */ + interactive?: boolean; +} + +/** + * Determine if prompts should be shown + */ +export function shouldPrompt(options: PromptOptions): boolean { + if (options.yes) return false; + if (options.interactive) return true; + if (isCI()) return false; + return isInteractive(); +} + +/** + * Prompt for a selection from a list of choices + */ +export async function promptSelect( + message: string, + choices: Array<{ value: T; name: string; description?: string }>, + defaultValue: T, + options: PromptOptions = {} +): Promise { + if (!shouldPrompt(options)) { + return defaultValue; + } + + return select({ + message, + choices: choices.map((c) => ({ + value: c.value, + name: c.name, + description: c.description, + })), + default: defaultValue, + }); +} + +/** + * Prompt for text input + */ +export async function promptInput( + message: string, + defaultValue: string, + options: PromptOptions = {} +): Promise { + if (!shouldPrompt(options)) { + return defaultValue; + } + + return input({ + message, + default: defaultValue, + }); +} + +/** + * Prompt for confirmation (yes/no) + */ +export async function promptConfirm( + message: string, + defaultValue: boolean, + options: PromptOptions = {} +): Promise { + if (!shouldPrompt(options)) { + return defaultValue; + } + + return confirm({ + message, + default: defaultValue, + }); +} + +/** + * Prompt for version input with validation + */ +export async function promptVersion( + message: string, + defaultValue: string, + options: PromptOptions = {} +): Promise { + if (!shouldPrompt(options)) { + return defaultValue; + } + + const result = await input({ + message, + default: defaultValue, + validate: (value) => { + const semverRegex = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/; + if (semverRegex.test(value)) { + return true; + } + return 'Please enter a valid semver version (e.g., 0.0.0, 1.2.3-beta.1)'; + }, + }); + + return result; +} + +/** + * Common version choices for init + */ +export const VERSION_CHOICES = [ + { value: '0.0.0', name: '0.0.0', description: 'Start fresh (recommended for new projects)' }, + { value: '1.0.0', name: '1.0.0', description: 'Production-ready' }, + { value: 'custom', name: 'Custom', description: 'Enter a custom version' }, +] as const; + +/** + * Common versioning mode choices + */ +export const VERSIONING_MODE_CHOICES = [ + { + value: 'independent', + name: 'Independent', + description: 'Each contract versioned separately', + }, + { value: 'fixed', name: 'Fixed', description: 'All contracts share same version' }, +] as const; + +/** + * Contract type choices + */ +export const CONTRACT_TYPE_CHOICES = [ + { value: 'openapi', name: 'OpenAPI', description: 'OpenAPI/Swagger specification' }, + { value: 'asyncapi', name: 'AsyncAPI', description: 'AsyncAPI specification' }, + { value: 'json-schema', name: 'JSON Schema', description: 'JSON Schema definition' }, + { value: 'odcs', name: 'ODCS', description: 'Open Data Contract Standard' }, +] as const; diff --git a/packages/cli/tsconfig.build.json b/packages/cli/tsconfig.build.json index 5695712..ad568f0 100644 --- a/packages/cli/tsconfig.build.json +++ b/packages/cli/tsconfig.build.json @@ -3,16 +3,14 @@ "compilerOptions": { "rootDir": "src", "baseUrl": ".", - "outDir": "dist", - "esModuleInterop": true, + "outDir": "dist" }, + "include": ["src/**/*"], "exclude": [ - "index.ts", "dist", "node_modules", "test", "**/*.spec.ts", - "**/*.test.ts", - "jest.config.ts" + "**/*.test.ts" ] } \ No newline at end of file diff --git a/packages/contract/.DS_Store b/packages/contract/.DS_Store deleted file mode 100644 index 2af1a89..0000000 Binary files a/packages/contract/.DS_Store and /dev/null differ diff --git a/packages/contract/.gitignore b/packages/contract/.gitignore deleted file mode 100644 index 1c849ee..0000000 --- a/packages/contract/.gitignore +++ /dev/null @@ -1 +0,0 @@ -contract/*.js \ No newline at end of file diff --git a/packages/contract/contract/index.d.ts b/packages/contract/contract/index.d.ts deleted file mode 100644 index 817096b..0000000 --- a/packages/contract/contract/index.d.ts +++ /dev/null @@ -1,212 +0,0 @@ -import { z } from 'zod'; -export declare const CreatePetRequest: z.ZodObject<{ - name: z.ZodString; - status: z.ZodOptional>; -}, "strip", z.ZodTypeAny, { - name?: string; - status?: "available" | "pending" | "sold"; -}, { - name?: string; - status?: "available" | "pending" | "sold"; -}>; -export declare const CreatePetResponse: z.ZodObject<{ - id: z.ZodNumber; -}, "strip", z.ZodTypeAny, { - id?: number; -}, { - id?: number; -}>; -export declare const UpdatePetRequest: z.ZodObject<{ - name: z.ZodString; - status: z.ZodOptional>; -}, "strip", z.ZodTypeAny, { - name?: string; - status?: "available" | "pending" | "sold"; -}, { - name?: string; - status?: "available" | "pending" | "sold"; -}>; -export declare const GetPetResponse: z.ZodObject<{ - id: z.ZodNumber; - name: z.ZodString; - status: z.ZodEnum<["available", "pending", "sold"]>; -}, "strip", z.ZodTypeAny, { - name?: string; - status?: "available" | "pending" | "sold"; - id?: number; -}, { - name?: string; - status?: "available" | "pending" | "sold"; - id?: number; -}>; -export declare const ApiContract: { - addPet: { - method: "POST"; - path: string; - description: string; - body: z.ZodObject<{ - name: z.ZodString; - status: z.ZodOptional>; - }, "strip", z.ZodTypeAny, { - name?: string; - status?: "available" | "pending" | "sold"; - }, { - name?: string; - status?: "available" | "pending" | "sold"; - }>; - responses: { - 200: z.ZodObject<{ - id: z.ZodNumber; - }, "strip", z.ZodTypeAny, { - id?: number; - }, { - id?: number; - }>; - }; - }; - updatePet: { - method: "PUT"; - path: string; - description: string; - body: z.ZodObject<{ - name: z.ZodString; - status: z.ZodOptional>; - }, "strip", z.ZodTypeAny, { - name?: string; - status?: "available" | "pending" | "sold"; - }, { - name?: string; - status?: "available" | "pending" | "sold"; - }>; - responses: { - 200: z.ZodObject<{ - id: z.ZodNumber; - name: z.ZodString; - status: z.ZodEnum<["available", "pending", "sold"]>; - }, "strip", z.ZodTypeAny, { - name?: string; - status?: "available" | "pending" | "sold"; - id?: number; - }, { - name?: string; - status?: "available" | "pending" | "sold"; - id?: number; - }>; - }; - }; - getPetById: { - method: "GET"; - path: string; - description: string; - pathParams: z.ZodObject<{ - petId: z.ZodNumber; - }, "strip", z.ZodTypeAny, { - petId?: number; - }, { - petId?: number; - }>; - responses: { - 200: z.ZodObject<{ - id: z.ZodNumber; - name: z.ZodString; - status: z.ZodEnum<["available", "pending", "sold"]>; - }, "strip", z.ZodTypeAny, { - name?: string; - status?: "available" | "pending" | "sold"; - id?: number; - }, { - name?: string; - status?: "available" | "pending" | "sold"; - id?: number; - }>; - }; - }; -}; -export declare const ApiOperations: { - 'add pet': "addPet"; - 'update pet': "updatePet"; - 'get pet by id': "getPetById"; -}; -export declare const contract: { - addPet: { - description: string; - body: z.ZodObject<{ - name: z.ZodString; - status: z.ZodOptional>; - }, "strip", z.ZodTypeAny, { - name?: string; - status?: "available" | "pending" | "sold"; - }, { - name?: string; - status?: "available" | "pending" | "sold"; - }>; - method: "POST"; - path: string; - responses: { - 200: z.ZodObject<{ - id: z.ZodNumber; - }, "strip", z.ZodTypeAny, { - id?: number; - }, { - id?: number; - }>; - }; - }; - updatePet: { - description: string; - body: z.ZodObject<{ - name: z.ZodString; - status: z.ZodOptional>; - }, "strip", z.ZodTypeAny, { - name?: string; - status?: "available" | "pending" | "sold"; - }, { - name?: string; - status?: "available" | "pending" | "sold"; - }>; - method: "PUT"; - path: string; - responses: { - 200: z.ZodObject<{ - id: z.ZodNumber; - name: z.ZodString; - status: z.ZodEnum<["available", "pending", "sold"]>; - }, "strip", z.ZodTypeAny, { - name?: string; - status?: "available" | "pending" | "sold"; - id?: number; - }, { - name?: string; - status?: "available" | "pending" | "sold"; - id?: number; - }>; - }; - }; - getPetById: { - description: string; - pathParams: z.ZodObject<{ - petId: z.ZodNumber; - }, "strip", z.ZodTypeAny, { - petId?: number; - }, { - petId?: number; - }>; - method: "GET"; - path: string; - responses: { - 200: z.ZodObject<{ - id: z.ZodNumber; - name: z.ZodString; - status: z.ZodEnum<["available", "pending", "sold"]>; - }, "strip", z.ZodTypeAny, { - name?: string; - status?: "available" | "pending" | "sold"; - id?: number; - }, { - name?: string; - status?: "available" | "pending" | "sold"; - id?: number; - }>; - }; - }; -}; diff --git a/packages/contract/package.json b/packages/contract/package.json deleted file mode 100644 index 572bb6c..0000000 --- a/packages/contract/package.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "name": "@contractual/contract", - "private": false, - "version": "0.0.0", - "license": "MIT", - "type": "module", - "exports": { - "./contract": { - "import": "./contract/index.js", - "require": "./contract/index.js" - } - }, - "typings": "contract/index.d.ts", - "repository": { - "type": "git", - "url": "https://github.com/contractual-dev/contractual.git", - "directory": "packages/contract" - }, - "homepage": "https://contractual.dev", - "bugs": { - "url": "https://github.com/contractual-dev/contractual/issues" - }, - "contributors": [ - { - "name": "Omer Morad", - "email": "omer.moradd@gmail.com" - } - ], - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/contractual-dev" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/contractual-dev" - } - ], - "engines": { - "node": ">=18.12.0" - }, - "scripts": { - "prebuild": "pnpm rimraf dist", - "build": "exit 0", - "build:watch": "tsc -p tsconfig.build.json --watch", - "test": "vitest run", - "lint": "exit 0" - }, - "files": [ - "contract", - "dist", - "README.md" - ], - "dependencies": { - "@ts-rest/core": "^3.51.0", - "axios": "^1.7.9", - "zod": "^3.24.1" - }, - "publishConfig": { - "access": "public", - "provenance": true - } -} diff --git a/packages/contract/tsconfig.build.json b/packages/contract/tsconfig.build.json deleted file mode 100644 index 91d6c8b..0000000 --- a/packages/contract/tsconfig.build.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "../../tsconfig.build.json", - "compilerOptions": { - "rootDir": "contract", - "baseUrl": ".", - "outDir": "dist", - "esModuleInterop": true}, - "exclude": [ - "index.ts", - "contract", - "dist", - "node_modules", - "test", - "**/*.spec.ts", - "jest.config.ts" - ] -} \ No newline at end of file diff --git a/packages/contract/tsconfig.json b/packages/contract/tsconfig.json deleted file mode 100644 index 7460ef4..0000000 --- a/packages/contract/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../tsconfig.json" -} \ No newline at end of file diff --git a/packages/differs.core/package.json b/packages/differs.core/package.json new file mode 100644 index 0000000..af6d148 --- /dev/null +++ b/packages/differs.core/package.json @@ -0,0 +1,55 @@ +{ + "name": "@contractual/differs.core", + "version": "0.1.0-dev.5", + "description": "Core schema diffing primitives β€” walker, classifiers, ref-resolver β€” shared across Contractual differs", + "license": "MIT", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "repository": { + "type": "git", + "url": "https://github.com/contractual-dev/contractual.git", + "directory": "packages/differs.core" + }, + "homepage": "https://github.com/contractual-dev/contractual/tree/main/packages/differs.core", + "bugs": { + "url": "https://github.com/contractual-dev/contractual/issues" + }, + "keywords": [ + "json-schema", + "diff", + "breaking-changes", + "semver", + "schema", + "api", + "compatibility" + ], + "engines": { + "node": ">=20.0.0" + }, + "scripts": { + "prebuild": "rimraf dist", + "build": "tsc -p tsconfig.build.json", + "build:watch": "tsc -p tsconfig.build.json --watch" + }, + "files": [ + "dist", + "README.md" + ], + "dependencies": { + "@contractual/types": "workspace:*" + }, + "devDependencies": { + "rimraf": "^5.0.5" + }, + "publishConfig": { + "access": "public", + "provenance": true + } +} diff --git a/packages/differs.core/src/assemble.ts b/packages/differs.core/src/assemble.ts new file mode 100644 index 0000000..8b113df --- /dev/null +++ b/packages/differs.core/src/assemble.ts @@ -0,0 +1,59 @@ +/** + * Result assembly β€” classify, format, summarize, and compute suggestedBump + */ + +import type { RawChange, Change, ChangeSeverity, DiffResult } from '@contractual/types'; +import { classify, classifyPropertyAdded } from './classifiers.js'; +import { formatChangeMessage } from './format.js'; + +export interface AssembleOptions { + contract?: string; + classifyFn?: (change: RawChange, newSchema: unknown) => ChangeSeverity; +} + +export function assembleResult( + rawChanges: RawChange[], + resolvedNewSchema: unknown, + options?: AssembleOptions +): DiffResult { + const classifyChange = options?.classifyFn ?? defaultClassify; + + const changes: Change[] = rawChanges.map((raw) => ({ + path: raw.path, + severity: classifyChange(raw, resolvedNewSchema), + category: raw.type, + message: formatChangeMessage(raw), + oldValue: raw.oldValue, + newValue: raw.newValue, + })); + + const summary = { + breaking: changes.filter((c) => c.severity === 'breaking').length, + nonBreaking: changes.filter((c) => c.severity === 'non-breaking').length, + patch: changes.filter((c) => c.severity === 'patch').length, + unknown: changes.filter((c) => c.severity === 'unknown').length, + }; + + const suggestedBump = + summary.breaking > 0 + ? 'major' + : summary.nonBreaking > 0 + ? 'minor' + : summary.patch > 0 + ? 'patch' + : 'none'; + + return { + contract: options?.contract ?? '', + changes, + summary, + suggestedBump, + }; +} + +function defaultClassify(change: RawChange, newSchema: unknown): ChangeSeverity { + if (change.type === 'property-added') { + return classifyPropertyAdded(change, newSchema); + } + return classify(change); +} diff --git a/packages/differs.core/src/classifiers.ts b/packages/differs.core/src/classifiers.ts new file mode 100644 index 0000000..59b3930 --- /dev/null +++ b/packages/differs.core/src/classifiers.ts @@ -0,0 +1,417 @@ +/** + * JSON Schema Change Classifiers + * + * Classifies raw structural changes into severity levels for semantic versioning. + * Follows API compatibility principles where breaking changes require major bumps, + * additive changes require minor bumps, and metadata changes are patches. + */ + +import type { RawChange, ChangeType, ChangeSeverity } from '@contractual/types'; + +/** + * Change types that are always breaking (require major version bump) + * + * These changes can break existing consumers: + * - Removing properties removes data they may depend on + * - Adding required fields forces consumers to provide new data + * - Type changes can break parsing/validation + * - Enum removals can invalidate existing data + * - Tightened constraints can reject previously valid data + * - Composition option additions can change validation semantics + * + * Aligned with Strands API classification rules. + */ +const BREAKING_CHANGES: ReadonlySet = new Set([ + 'property-removed', + 'required-added', + 'type-changed', + 'type-narrowed', + 'enum-value-removed', + 'enum-added', + 'constraint-tightened', + 'additional-properties-denied', + 'items-changed', + 'min-items-increased', + 'max-items-decreased', + 'ref-target-changed', + 'dependent-required-added', + // Composition breaking changes (per Strands API) + 'anyof-option-added', + 'oneof-option-added', + 'allof-member-added', + 'not-schema-changed', + // OpenAPI structural breaking changes + 'path-removed', + 'operation-removed', + 'parameter-required-added', + 'parameter-removed', + 'parameter-required-changed', + 'request-body-added', + 'response-removed', + 'security-changed', +]); + +/** + * Change types that are non-breaking (require minor version bump) + * + * These changes are backward compatible additions/relaxations: + * - Adding optional properties extends the schema without breaking + * - Removing required constraints makes the schema more permissive + * - Type widening accepts more values + * - Loosened constraints accept more values + * - Composition option removals make schema less restrictive + * + * Aligned with Strands API classification rules. + */ +const NON_BREAKING_CHANGES: ReadonlySet = new Set([ + 'property-added', + 'required-removed', + 'type-widened', + 'enum-value-added', + 'enum-removed', + 'constraint-loosened', + 'additional-properties-allowed', + 'additional-properties-changed', + 'dependent-required-removed', + // Composition non-breaking changes (per Strands API) + 'anyof-option-removed', + 'oneof-option-removed', + 'allof-member-removed', + // OpenAPI structural non-breaking changes + 'parameter-added', + 'path-added', + 'operation-added', + 'request-body-removed', + 'response-added', + 'server-changed', +]); + +/** + * Change types that are patches (documentation/metadata only) + * + * These changes don't affect validation behavior: + * - Description changes are documentation only + * - Title changes are display metadata + * - Default/example changes don't affect validation + * - Format is an annotation (per Strands API) - doesn't affect validation + * - Annotation keywords (deprecated, readOnly, writeOnly) + * - Content keywords (contentEncoding, contentMediaType, contentSchema) + * + * Aligned with Strands API classification rules. + */ +const PATCH_CHANGES: ReadonlySet = new Set([ + // Metadata changes + 'description-changed', + 'title-changed', + 'default-changed', + 'examples-changed', + // Format is annotation (patch per Strands API) + 'format-added', + 'format-removed', + 'format-changed', + // Annotation keywords (Draft 2019-09+) + 'deprecated-changed', + 'read-only-changed', + 'write-only-changed', + // Content keywords + 'content-encoding-changed', + 'content-media-type-changed', + 'content-schema-changed', +]); + +/** + * Change types that require manual review + * + * These changes are too complex to classify automatically: + * - Generic composition changes require semantic analysis + * - Complex keywords (propertyNames, dependentSchemas, unevaluated*) + * - Conditional schema changes (if/then/else) + * - Unknown changes need human evaluation + * + * Aligned with Strands API classification rules. + */ +const UNKNOWN_CHANGES: ReadonlySet = new Set([ + // Complex object keywords + 'property-names-changed', + 'dependent-schemas-changed', + 'unevaluated-properties-changed', + // Complex array keywords + 'unevaluated-items-changed', + 'min-contains-changed', + 'max-contains-changed', + // Conditional schema + 'if-then-else-changed', + // Legacy/generic composition + 'composition-changed', + // OpenAPI context-dependent (schema-level changes handled by walker) + 'parameter-schema-changed', + 'response-schema-changed', + // Catch-all + 'unknown-change', +]); + +/** + * Export classification sets for external analysis + */ +export const CLASSIFICATION_SETS = { + breaking: BREAKING_CHANGES, + nonBreaking: NON_BREAKING_CHANGES, + patch: PATCH_CHANGES, + unknown: UNKNOWN_CHANGES, +} as const; + +/** + * Classify a raw change into a severity level + * + * Uses the Strands API classification rules where: + * - Breaking changes require major version bump + * - Non-breaking changes require minor version bump + * - Patch changes are metadata/annotation only + * - Unknown changes require manual review + * + * @param change - The raw change to classify + * @returns The severity classification + * + * @example + * ```typescript + * const change: RawChange = { + * path: '/properties/name', + * type: 'property-removed', + * oldValue: { type: 'string' }, + * }; + * const severity = classify(change); + * // severity === 'breaking' + * ``` + */ +export function classify(change: RawChange): ChangeSeverity { + const { type } = change; + + // Check each category in order of specificity + if (BREAKING_CHANGES.has(type)) { + return 'breaking'; + } + + if (NON_BREAKING_CHANGES.has(type)) { + return 'non-breaking'; + } + + if (PATCH_CHANGES.has(type)) { + return 'patch'; + } + + if (UNKNOWN_CHANGES.has(type)) { + return 'unknown'; + } + + // Defensive: any unhandled type is unknown + return 'unknown'; +} + +/** + * Classify a property-added change with schema context + * + * Property additions are breaking if the property is required, + * otherwise they are non-breaking (additive). + * + * @param change - The property-added change + * @param newSchema - The new schema for context (to check required array) + * @returns The severity classification + * + * @example + * ```typescript + * const change: RawChange = { + * path: '/properties/email', + * type: 'property-added', + * newValue: { type: 'string', format: 'email' }, + * }; + * + * const schema = { + * type: 'object', + * properties: { email: { type: 'string', format: 'email' } }, + * required: ['email'], // email is required! + * }; + * + * const severity = classifyPropertyAdded(change, schema); + * // severity === 'breaking' (because email is in required[]) + * ``` + */ +export function classifyPropertyAdded(change: RawChange, newSchema: unknown): ChangeSeverity { + // Validate change type + if (change.type !== 'property-added') { + return classify(change); + } + + // Extract property name from path + const propertyName = extractPropertyName(change.path); + if (!propertyName) { + // Cannot determine property name, fall back to non-breaking + return 'non-breaking'; + } + + // Find the parent schema containing this property + const parentSchema = findParentSchema(change.path, newSchema); + if (!parentSchema) { + // Cannot find parent schema, fall back to non-breaking + return 'non-breaking'; + } + + // Check if property is in the required array + const required = getRequiredArray(parentSchema); + if (required.includes(propertyName)) { + // Adding a required property is breaking + return 'breaking'; + } + + // Adding an optional property is non-breaking + return 'non-breaking'; +} + +/** + * Extract the property name from a JSON Pointer path + * + * @param path - JSON Pointer path (e.g., '/properties/name' or '/properties/user/properties/email') + * @returns The property name or null if not found + */ +function extractPropertyName(path: string): string | null { + // Match the last /properties/NAME segment + const match = path.match(/\/properties\/([^/]+)$/); + if (match?.[1] !== undefined) { + return decodeJsonPointerSegment(match[1]); + } + + return null; +} + +/** + * Decode a JSON Pointer segment (handles ~0 and ~1 escapes) + * + * @param segment - The encoded segment + * @returns The decoded segment + */ +function decodeJsonPointerSegment(segment: string): string { + return segment.replace(/~1/g, '/').replace(/~0/g, '~'); +} + +/** + * Find the parent schema containing a property + * + * @param path - JSON Pointer path to the property + * @param schema - The root schema + * @returns The parent schema or null if not found + */ +function findParentSchema(path: string, schema: unknown): unknown { + if (!isObject(schema)) { + return null; + } + + // Remove the last segment to get parent path + // e.g., '/properties/name' -> '' (root) + // e.g., '/properties/user/properties/email' -> '/properties/user' + const segments = path.split('/').filter(Boolean); + + // We need to navigate to the schema containing /properties/NAME + // So we remove 'properties' and 'NAME' from the end + if (segments.length < 2) { + // Path is too short, parent is root + return schema; + } + + // Remove 'NAME' and 'properties' from the end + const parentSegments = segments.slice(0, -2); + + // Navigate to parent + let current: unknown = schema; + for (const segment of parentSegments) { + if (!isObject(current)) { + return null; + } + const decoded = decodeJsonPointerSegment(segment); + current = (current as Record)[decoded]; + } + + return current; +} + +/** + * Get the required array from a schema object + * + * @param schema - The schema object + * @returns Array of required property names + */ +function getRequiredArray(schema: unknown): string[] { + if (!isObject(schema)) { + return []; + } + + const obj = schema as Record; + const required = obj['required']; + + if (!Array.isArray(required)) { + return []; + } + + return required.filter((item): item is string => typeof item === 'string'); +} + +/** + * Type guard for objects + */ +function isObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +/** + * Batch classify multiple changes + * + * @param changes - Array of raw changes + * @param newSchema - Optional schema for context-aware classification + * @returns Map of change to severity + */ +export function classifyAll( + changes: readonly RawChange[], + newSchema?: unknown +): Map { + const results = new Map(); + + for (const change of changes) { + if (change.type === 'property-added' && newSchema !== undefined) { + results.set(change, classifyPropertyAdded(change, newSchema)); + } else { + results.set(change, classify(change)); + } + } + + return results; +} + +/** + * Get a human-readable message for a classified change + * + * @param change - The raw change + * @param severity - The classified severity + * @returns Human-readable description + */ +export function getChangeMessage(change: RawChange, severity: ChangeSeverity): string { + const severityLabel = + severity === 'breaking' + ? 'BREAKING' + : severity === 'non-breaking' + ? 'Non-breaking' + : severity === 'patch' + ? 'Patch' + : 'Unknown'; + + const typeLabel = formatChangeType(change.type); + + return `[${severityLabel}] ${typeLabel} at ${change.path}`; +} + +/** + * Format a change type into a human-readable label + */ +function formatChangeType(type: ChangeType): string { + return type + .split('-') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); +} diff --git a/packages/differs.core/src/format.ts b/packages/differs.core/src/format.ts new file mode 100644 index 0000000..156896e --- /dev/null +++ b/packages/differs.core/src/format.ts @@ -0,0 +1,237 @@ +/** + * Human-readable message formatting for raw changes + */ + +import type { RawChange } from '@contractual/types'; + +/** + * Format a human-readable message for a change + * + * @param change - The raw change to format + * @returns Human-readable message describing the change + */ +export function formatChangeMessage(change: RawChange): string { + const pathDisplay = decodeJsonPointer(change.path || '/'); + + switch (change.type) { + case 'property-added': + return `Property added at ${pathDisplay}`; + + case 'property-removed': + return `Property removed at ${pathDisplay}`; + + case 'required-added': + return `Field "${formatRequiredFieldName(change.newValue)}" made required at ${pathDisplay}`; + + case 'required-removed': + return `Field "${formatRequiredFieldName(change.oldValue)}" made optional at ${pathDisplay}`; + + case 'type-changed': + return `Type changed from ${formatValue(change.oldValue)} to ${formatValue(change.newValue)} at ${pathDisplay}`; + + case 'type-narrowed': + return `Type narrowed from ${formatValue(change.oldValue)} to ${formatValue(change.newValue)} at ${pathDisplay}`; + + case 'type-widened': + return `Type widened from ${formatValue(change.oldValue)} to ${formatValue(change.newValue)} at ${pathDisplay}`; + + case 'enum-value-added': + return `Enum value ${formatValue(change.newValue)} added at ${pathDisplay}`; + + case 'enum-value-removed': + return `Enum value ${formatValue(change.oldValue)} removed at ${pathDisplay}`; + + case 'enum-added': + return `Enum constraint added at ${pathDisplay}`; + + case 'enum-removed': + return `Enum constraint removed at ${pathDisplay}`; + + case 'constraint-tightened': + return formatConstraintMessage(change, 'tightened', pathDisplay); + + case 'constraint-loosened': + return formatConstraintMessage(change, 'loosened', pathDisplay); + + case 'format-added': + return `Format "${change.newValue}" added at ${pathDisplay}`; + + case 'format-removed': + return `Format "${change.oldValue}" removed at ${pathDisplay}`; + + case 'format-changed': + return `Format changed from "${change.oldValue}" to "${change.newValue}" at ${pathDisplay}`; + + case 'additional-properties-denied': + return `Additional properties denied at ${pathDisplay}`; + + case 'additional-properties-allowed': + return `Additional properties allowed at ${pathDisplay}`; + + case 'additional-properties-changed': + return `Additional properties schema changed at ${pathDisplay}`; + + case 'items-changed': + return `Array items schema changed at ${pathDisplay}`; + + case 'min-items-increased': + return `Minimum items increased from ${formatValue(change.oldValue)} to ${formatValue(change.newValue)} at ${pathDisplay}`; + + case 'max-items-decreased': + return `Maximum items decreased from ${formatValue(change.oldValue)} to ${formatValue(change.newValue)} at ${pathDisplay}`; + + case 'composition-changed': + return `Composition (allOf/anyOf/oneOf) changed at ${pathDisplay}`; + + case 'ref-target-changed': + return `Reference target changed at ${pathDisplay}`; + + case 'description-changed': + return `Description changed at ${pathDisplay}`; + + case 'title-changed': + return `Title changed at ${pathDisplay}`; + + case 'default-changed': + return `Default value changed at ${pathDisplay}`; + + case 'examples-changed': + return `Examples changed at ${pathDisplay}`; + + case 'deprecated-changed': + return change.newValue + ? `Deprecated at ${pathDisplay}` + : `No longer deprecated at ${pathDisplay}`; + + case 'read-only-changed': + return `readOnly changed at ${pathDisplay}`; + + case 'write-only-changed': + return `writeOnly changed at ${pathDisplay}`; + + // OpenAPI structural changes + case 'path-added': + return `New path added: ${change.newValue ?? pathDisplay}`; + + case 'path-removed': + return `Path removed: ${change.oldValue ?? pathDisplay}`; + + case 'operation-added': + return `New operation added: ${change.newValue ?? pathDisplay}`; + + case 'operation-removed': + return `Operation removed: ${change.oldValue ?? pathDisplay}`; + + case 'parameter-added': + return `Optional parameter added at ${pathDisplay}`; + + case 'parameter-required-added': + return `Required parameter added at ${pathDisplay}`; + + case 'parameter-removed': + return `Parameter removed at ${pathDisplay}`; + + case 'parameter-required-changed': + return `Parameter required changed from ${formatValue(change.oldValue)} to ${formatValue(change.newValue)} at ${pathDisplay}`; + + case 'parameter-schema-changed': + return `Parameter schema changed at ${pathDisplay}`; + + case 'request-body-added': + return `Request body added at ${pathDisplay}`; + + case 'request-body-removed': + return `Request body removed at ${pathDisplay}`; + + case 'response-added': + return `Response ${formatValue(change.newValue)} added at ${pathDisplay}`; + + case 'response-removed': + return `Response ${formatValue(change.oldValue)} removed at ${pathDisplay}`; + + case 'response-schema-changed': + return `Response schema changed at ${pathDisplay}`; + + case 'security-changed': + return `Security changed at ${pathDisplay}`; + + case 'server-changed': + return `Server changed at ${pathDisplay}`; + + case 'unknown-change': + default: + return `Unknown change at ${pathDisplay}`; + } +} + +/** + * Decode a JSON Pointer path for human-readable display (RFC 6901) + * ~1 β†’ / + * ~0 β†’ ~ + */ +function decodeJsonPointer(path: string): string { + return path.replace(/~1/g, '/').replace(/~0/g, '~'); +} + +/** + * Format a value for display in messages + */ +function formatValue(value: unknown): string { + if (value === undefined) { + return 'undefined'; + } + if (value === null) { + return 'null'; + } + if (typeof value === 'string') { + return `"${value}"`; + } + if (Array.isArray(value)) { + return JSON.stringify(value); + } + if (typeof value === 'object') { + return JSON.stringify(value); + } + return String(value); +} + +/** + * Format required field name from change value + */ +function formatRequiredFieldName(value: unknown): string { + if (typeof value === 'string') { + return value; + } + return String(value); +} + +/** + * Format constraint change message + */ +function formatConstraintMessage( + change: RawChange, + direction: 'tightened' | 'loosened', + pathDisplay: string +): string { + const constraintName = extractConstraintName(change.path); + const oldVal = change.oldValue !== undefined ? formatValue(change.oldValue) : 'none'; + const newVal = change.newValue !== undefined ? formatValue(change.newValue) : 'none'; + + if (constraintName) { + return `Constraint "${constraintName}" ${direction} from ${oldVal} to ${newVal} at ${pathDisplay}`; + } + + return `Constraint ${direction} from ${oldVal} to ${newVal} at ${pathDisplay}`; +} + +/** + * Extract constraint name from path + */ +function extractConstraintName(path: string): string | null { + const segments = path.split('/'); + const lastSegment = segments[segments.length - 1]; + if (lastSegment && lastSegment !== '') { + return lastSegment; + } + return null; +} diff --git a/packages/differs.core/src/index.ts b/packages/differs.core/src/index.ts new file mode 100644 index 0000000..bbb5561 --- /dev/null +++ b/packages/differs.core/src/index.ts @@ -0,0 +1,50 @@ +// Core walker +export { walk } from './walker.js'; + +// Classification +export { classify, classifyPropertyAdded, classifyAll, CLASSIFICATION_SETS } from './classifiers.js'; + +// Ref resolution +export { resolveRefs, hasUnresolvedRefs, extractRefs, validateRefs, type ResolveResult } from './ref-resolver.js'; + +// Message formatting +export { formatChangeMessage } from './format.js'; + +// Result assembly +export { assembleResult, type AssembleOptions } from './assemble.js'; + +// Schema types and utilities +export type { + ResolvedSchema, + JSONSchemaType, + NormalizedType, + ConstraintKey, + ConstraintDirection, + ConstraintMeta, + CompositionKeyword, + MetadataKey, + AnnotationKey, + ContentKey, + WalkerContext, +} from './types.js'; + +export { + isSchemaObject, + isSchemaArray, + normalizeType, + arraysEqual, + deepEqual, + escapeJsonPointer, + joinPath, + CONSTRAINT_KEYS, + CONSTRAINT_DIRECTION, + COMPOSITION_KEYWORDS, + METADATA_KEYS, + ANNOTATION_KEYS, + CONTENT_KEYS, + DEFAULT_MAX_DEPTH, +} from './types.js'; + +// Re-export governance types from @contractual/types for convenience +export type { ChangeType, ChangeSeverity, RawChange, Change, DiffResult, DiffSummary, SuggestedBump } from '@contractual/types'; +export { CHANGE_TYPE_SEVERITY } from '@contractual/types'; diff --git a/packages/differs.core/src/ref-resolver.ts b/packages/differs.core/src/ref-resolver.ts new file mode 100644 index 0000000..c125601 --- /dev/null +++ b/packages/differs.core/src/ref-resolver.ts @@ -0,0 +1,435 @@ +/** + * JSON Schema $ref Resolver + * + * Resolves all $ref pointers in a JSON Schema, producing a self-contained schema. + * Handles internal references ($defs, definitions) and detects circular references. + * + * This resolver operates on in-memory schema objects and does not fetch external URLs. + * External references are flagged as warnings. + */ + +/** + * Result of resolving references in a schema + */ +export interface ResolveResult { + /** The resolved schema with $refs replaced */ + readonly schema: unknown; + /** Warnings encountered during resolution (circular refs, external refs, etc.) */ + readonly warnings: string[]; +} + +/** + * Internal context for tracking resolution state + */ +interface ResolveContext { + /** The root schema for resolving internal references */ + readonly root: unknown; + /** Accumulated warnings */ + readonly warnings: string[]; + /** Set of $ref paths currently being resolved (for circular detection) */ + readonly resolving: Set; + /** Cache of already resolved $refs to their values */ + readonly cache: Map; + /** Current JSON Pointer path (for error messages) */ + currentPath: string; +} + +/** + * Resolve all $ref pointers in a JSON Schema + * + * Creates a self-contained schema by inlining all internal references. + * Does not modify the original schema. + * + * @param schema - The JSON Schema to resolve + * @returns The resolved schema and any warnings + * + * @example + * ```typescript + * const schema = { + * type: 'object', + * properties: { + * user: { $ref: '#/$defs/User' } + * }, + * $defs: { + * User: { type: 'object', properties: { name: { type: 'string' } } } + * } + * }; + * + * const result = resolveRefs(schema); + * // result.schema.properties.user === { type: 'object', properties: { name: { type: 'string' } } } + * // result.warnings === [] + * ``` + */ +export function resolveRefs(schema: unknown): ResolveResult { + const context = { + root: schema, + warnings: [], + resolving: new Set(), + cache: new Map(), + currentPath: '', + } satisfies ResolveContext; + + const resolved = resolveNode(schema, context); + + return { + schema: resolved, + warnings: context.warnings, + }; +} + +/** + * Recursively resolve a schema node + */ +function resolveNode(node: unknown, context: ResolveContext): unknown { + // Handle non-objects + if (!isObject(node)) { + return node; + } + + const obj = node as Record; + + // Check if this is a $ref node + if (typeof obj['$ref'] === 'string') { + return resolveRef(obj['$ref'], obj, context); + } + + // Recursively resolve all properties + const resolved: Record = {}; + + for (const [key, value] of Object.entries(obj)) { + const previousPath = context.currentPath; + context.currentPath = `${context.currentPath}/${encodeJsonPointerSegment(key)}`; + + if (Array.isArray(value)) { + resolved[key] = value.map((item, index) => { + const itemPath = context.currentPath; + context.currentPath = `${itemPath}/${index}`; + const resolvedItem = resolveNode(item, context); + context.currentPath = itemPath; + return resolvedItem; + }); + } else { + resolved[key] = resolveNode(value, context); + } + + context.currentPath = previousPath; + } + + return resolved; +} + +/** + * Resolve a $ref pointer + */ +function resolveRef( + ref: string, + refNode: Record, + context: ResolveContext +): unknown { + // Check for external references + if (isExternalRef(ref)) { + context.warnings.push( + `External reference not resolved: ${ref} at ${context.currentPath || '/'}` + ); + // Return the $ref node as-is for external refs + return refNode; + } + + // Check for circular reference + if (context.resolving.has(ref)) { + context.warnings.push(`Circular reference detected: ${ref} at ${context.currentPath || '/'}`); + // Return a placeholder to break the cycle + return { + $circularRef: ref, + $comment: 'Circular reference - see original location', + }; + } + + // Check cache + if (context.cache.has(ref)) { + return context.cache.get(ref); + } + + // Mark as currently resolving + context.resolving.add(ref); + + try { + // Resolve the reference + const target = resolvePointer(ref, context.root); + + if (target === undefined) { + context.warnings.push(`Reference not found: ${ref} at ${context.currentPath || '/'}`); + // Return the $ref node as-is if target not found + return refNode; + } + + // Recursively resolve the target (it may contain more $refs) + const resolved = resolveNode(target, context); + + // Merge any sibling properties from the $ref node + // JSON Schema allows properties alongside $ref in draft 2019-09+ + const siblings = extractSiblingProperties(refNode); + const merged = mergeSiblings(resolved, siblings); + + // Cache the result + context.cache.set(ref, merged); + + return merged; + } finally { + // Remove from resolving set + context.resolving.delete(ref); + } +} + +/** + * Check if a reference is external (http://, https://, file://) + */ +function isExternalRef(ref: string): boolean { + return ( + ref.startsWith('http://') || + ref.startsWith('https://') || + ref.startsWith('file://') || + // Relative file references (not starting with #) + (!ref.startsWith('#') && + (ref.endsWith('.json') || ref.endsWith('.yaml') || ref.endsWith('.yml'))) + ); +} + +/** + * Resolve a JSON Pointer within a document + * + * Supports: + * - #/$defs/Name + * - #/definitions/Name + * - #/properties/field/items + * + * @param pointer - The JSON Pointer (e.g., '#/$defs/User') + * @param root - The root document + * @returns The resolved value or undefined if not found + */ +function resolvePointer(pointer: string, root: unknown): unknown { + // Handle empty or root pointer + if (pointer === '#' || pointer === '') { + return root; + } + + // Remove the leading # if present + let path = pointer; + if (path.startsWith('#')) { + path = path.slice(1); + } + + // Remove leading slash + if (path.startsWith('/')) { + path = path.slice(1); + } + + // Handle empty path after normalization + if (!path) { + return root; + } + + // Split into segments and navigate + const segments = path.split('/'); + let current: unknown = root; + + for (const segment of segments) { + if (!isObject(current) && !Array.isArray(current)) { + return undefined; + } + + const decoded = decodeJsonPointerSegment(segment); + + if (Array.isArray(current)) { + const index = parseInt(decoded, 10); + if (isNaN(index) || index < 0 || index >= current.length) { + return undefined; + } + current = current[index]; + } else { + const obj = current as Record; + if (!(decoded in obj)) { + return undefined; + } + current = obj[decoded]; + } + } + + return current; +} + +/** + * Decode a JSON Pointer segment (RFC 6901) + * + * ~0 -> ~ + * ~1 -> / + */ +function decodeJsonPointerSegment(segment: string): string { + return segment.replace(/~1/g, '/').replace(/~0/g, '~'); +} + +/** + * Encode a JSON Pointer segment (RFC 6901) + * + * ~ -> ~0 + * / -> ~1 + */ +function encodeJsonPointerSegment(segment: string): string { + return segment.replace(/~/g, '~0').replace(/\//g, '~1'); +} + +/** + * Extract sibling properties from a $ref node + * + * In JSON Schema draft 2019-09+, properties alongside $ref are allowed + * and should be merged with the referenced schema. + */ +function extractSiblingProperties(refNode: Record): Record { + const siblings: Record = {}; + + for (const [key, value] of Object.entries(refNode)) { + if (key !== '$ref') { + siblings[key] = value; + } + } + + return siblings; +} + +/** + * Merge sibling properties with a resolved schema + */ +function mergeSiblings(resolved: unknown, siblings: Record): unknown { + // If no siblings, return as-is + if (Object.keys(siblings).length === 0) { + return resolved; + } + + // If resolved is not an object, wrap in allOf + if (!isObject(resolved)) { + return { + allOf: [resolved, siblings], + }; + } + + // Merge properties, siblings override resolved + return { + ...(resolved as Record), + ...siblings, + }; +} + +/** + * Type guard for objects + */ +function isObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +/** + * Check if a schema contains any unresolved $refs + * + * @param schema - The schema to check + * @returns True if the schema contains $ref pointers + */ +export function hasUnresolvedRefs(schema: unknown): boolean { + if (!isObject(schema)) { + return false; + } + + const obj = schema as Record; + + // Check if this node is a $ref + if (typeof obj['$ref'] === 'string' && !obj['$circularRef']) { + return true; + } + + // Check children + for (const value of Object.values(obj)) { + if (Array.isArray(value)) { + for (const item of value) { + if (hasUnresolvedRefs(item)) { + return true; + } + } + } else if (hasUnresolvedRefs(value)) { + return true; + } + } + + return false; +} + +/** + * Extract all $ref pointers from a schema + * + * @param schema - The schema to analyze + * @returns Array of { pointer, path } objects + */ +export function extractRefs(schema: unknown): Array<{ pointer: string; path: string }> { + const refs: Array<{ pointer: string; path: string }> = []; + collectRefs(schema, '', refs); + return refs; +} + +/** + * Recursively collect $refs + */ +function collectRefs( + node: unknown, + path: string, + refs: Array<{ pointer: string; path: string }> +): void { + if (!isObject(node)) { + return; + } + + const obj = node as Record; + + if (typeof obj['$ref'] === 'string') { + refs.push({ pointer: obj['$ref'], path: path || '/' }); + } + + for (const [key, value] of Object.entries(obj)) { + const childPath = `${path}/${encodeJsonPointerSegment(key)}`; + + if (Array.isArray(value)) { + value.forEach((item, index) => { + collectRefs(item, `${childPath}/${index}`, refs); + }); + } else { + collectRefs(value, childPath, refs); + } + } +} + +/** + * Validate that all internal references in a schema are resolvable + * + * @param schema - The schema to validate + * @returns Object with valid flag and any error messages + */ +export function validateRefs(schema: unknown): { + valid: boolean; + errors: string[]; +} { + const errors: string[] = []; + const refs = extractRefs(schema); + + for (const { pointer, path } of refs) { + // Skip external refs for this validation + if (isExternalRef(pointer)) { + continue; + } + + const resolved = resolvePointer(pointer, schema); + if (resolved === undefined) { + errors.push(`Invalid reference at ${path}: ${pointer} not found`); + } + } + + return { + valid: errors.length === 0, + errors, + }; +} diff --git a/packages/differs.core/src/types.ts b/packages/differs.core/src/types.ts new file mode 100644 index 0000000..c907e57 --- /dev/null +++ b/packages/differs.core/src/types.ts @@ -0,0 +1,347 @@ +/** + * Core schema types and utilities for Contractual differs + */ + +// Re-export governance types from @contractual/types +export type { + ChangeType, + ChangeSeverity, + RawChange, + Change, + DiffResult, + DiffSummary, + SuggestedBump, +} from '@contractual/types'; + +export { CHANGE_TYPE_SEVERITY } from '@contractual/types'; + +// ============================================================================ +// JSON Schema Types +// ============================================================================ + +/** + * JSON Schema type values + */ +export type JSONSchemaType = + | 'string' + | 'number' + | 'integer' + | 'boolean' + | 'object' + | 'array' + | 'null'; + +/** + * Normalized type representation (always an array for comparison) + */ +export type NormalizedType = JSONSchemaType[]; + +/** + * JSON Schema constraint keys that can be tightened or loosened + */ +export type ConstraintKey = + | 'minimum' + | 'maximum' + | 'exclusiveMinimum' + | 'exclusiveMaximum' + | 'minLength' + | 'maxLength' + | 'minItems' + | 'maxItems' + | 'minProperties' + | 'maxProperties' + | 'minContains' + | 'maxContains' + | 'pattern' + | 'multipleOf' + | 'uniqueItems'; + +/** + * Constraint comparison direction + */ +export type ConstraintDirection = 'min' | 'max' | 'exact'; + +/** + * Constraint metadata for determining tightened vs loosened + */ +export interface ConstraintMeta { + readonly key: ConstraintKey; + readonly direction: ConstraintDirection; +} + +/** + * Map of constraint keys to their comparison direction + */ +export const CONSTRAINT_DIRECTION: Record = { + minimum: 'min', + maximum: 'max', + exclusiveMinimum: 'min', + exclusiveMaximum: 'max', + minLength: 'min', + maxLength: 'max', + minItems: 'min', + maxItems: 'max', + minProperties: 'min', + maxProperties: 'max', + minContains: 'min', + maxContains: 'max', + pattern: 'exact', + multipleOf: 'exact', + uniqueItems: 'exact', +}; + +/** + * All constraint keys for iteration + */ +export const CONSTRAINT_KEYS: ConstraintKey[] = [ + 'minimum', + 'maximum', + 'exclusiveMinimum', + 'exclusiveMaximum', + 'minLength', + 'maxLength', + 'minItems', + 'maxItems', + 'minProperties', + 'maxProperties', + 'minContains', + 'maxContains', + 'pattern', + 'multipleOf', + 'uniqueItems', +]; + +/** + * Composition keywords in JSON Schema + */ +export type CompositionKeyword = 'anyOf' | 'oneOf' | 'allOf' | 'if' | 'then' | 'else' | 'not'; + +/** + * All composition keywords for iteration + */ +export const COMPOSITION_KEYWORDS: CompositionKeyword[] = [ + 'anyOf', + 'oneOf', + 'allOf', + 'if', + 'then', + 'else', + 'not', +]; + +/** + * Metadata keys that are compared (patch-level changes) + */ +export type MetadataKey = 'description' | 'title' | 'default' | 'examples'; + +/** + * All metadata keys for iteration + */ +export const METADATA_KEYS: MetadataKey[] = ['description', 'title', 'default', 'examples']; + +/** + * Annotation keys (patch-level changes per Strands API) + */ +export type AnnotationKey = 'deprecated' | 'readOnly' | 'writeOnly'; + +/** + * All annotation keys for iteration + */ +export const ANNOTATION_KEYS: AnnotationKey[] = ['deprecated', 'readOnly', 'writeOnly']; + +/** + * Content keywords (patch-level changes per Strands API) + */ +export type ContentKey = 'contentEncoding' | 'contentMediaType' | 'contentSchema'; + +/** + * All content keys for iteration + */ +export const CONTENT_KEYS: ContentKey[] = ['contentEncoding', 'contentMediaType', 'contentSchema']; + +/** + * Resolved JSON Schema object (refs already resolved) + */ +export interface ResolvedSchema { + // Schema identification + $schema?: string; + $id?: string; + $ref?: string; + $defs?: Record; + + // Type + type?: JSONSchemaType | JSONSchemaType[]; + + // Metadata + title?: string; + description?: string; + default?: unknown; + examples?: unknown[]; + + // Annotations (Draft 2019-09+) + deprecated?: boolean; + readOnly?: boolean; + writeOnly?: boolean; + + // Enum + enum?: unknown[]; + const?: unknown; + + // Format + format?: string; + + // Numeric constraints + minimum?: number; + maximum?: number; + exclusiveMinimum?: number; + exclusiveMaximum?: number; + multipleOf?: number; + + // String constraints + minLength?: number; + maxLength?: number; + pattern?: string; + + // Content keywords (Draft 7+) + contentEncoding?: string; + contentMediaType?: string; + contentSchema?: ResolvedSchema; + + // Object keywords + properties?: Record; + required?: string[]; + additionalProperties?: boolean | ResolvedSchema; + minProperties?: number; + maxProperties?: number; + propertyNames?: ResolvedSchema; + patternProperties?: Record; + dependentRequired?: Record; + dependentSchemas?: Record; + unevaluatedProperties?: boolean | ResolvedSchema; + + // Array keywords + items?: ResolvedSchema | ResolvedSchema[]; + prefixItems?: ResolvedSchema[]; + minItems?: number; + maxItems?: number; + uniqueItems?: boolean; + contains?: ResolvedSchema; + minContains?: number; + maxContains?: number; + unevaluatedItems?: boolean | ResolvedSchema; + + // Composition + anyOf?: ResolvedSchema[]; + oneOf?: ResolvedSchema[]; + allOf?: ResolvedSchema[]; + if?: ResolvedSchema; + then?: ResolvedSchema; + else?: ResolvedSchema; + not?: ResolvedSchema; + + // Allow additional properties for extensibility + [key: string]: unknown; +} + +/** + * Walker context for tracking traversal state + */ +export interface WalkerContext { + /** Current JSON Pointer path */ + readonly path: string; + /** Depth of recursion (for cycle detection) */ + readonly depth: number; + /** Maximum allowed depth */ + readonly maxDepth: number; +} + +/** + * Default maximum recursion depth + */ +export const DEFAULT_MAX_DEPTH = 100; + +/** + * Type guard to check if value is a schema object + */ +export function isSchemaObject(value: unknown): value is ResolvedSchema { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +/** + * Type guard to check if value is an array of schemas + */ +export function isSchemaArray(value: unknown): value is ResolvedSchema[] { + return Array.isArray(value) && value.every(isSchemaObject); +} + +/** + * Normalize type to array for consistent comparison + */ +export function normalizeType(type: JSONSchemaType | JSONSchemaType[] | undefined): NormalizedType { + if (type === undefined) { + return []; + } + if (Array.isArray(type)) { + return [...type].sort(); + } + return [type]; +} + +/** + * Check if two arrays have the same elements (order-independent) + */ +export function arraysEqual(a: T[], b: T[]): boolean { + if (a.length !== b.length) return false; + const sortedA = [...a].sort(); + const sortedB = [...b].sort(); + return sortedA.every((val, idx) => val === sortedB[idx]); +} + +/** + * Deep equality check for JSON values + */ +export function deepEqual(a: unknown, b: unknown): boolean { + if (a === b) { + return true; + } + + if (typeof a !== typeof b) { + return false; + } + + if (a === null || b === null) return a === b; + + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) return false; + return a.every((val, idx) => deepEqual(val, b[idx])); + } + + if (typeof a === 'object' && typeof b === 'object') { + const aObj = a as Record; + const bObj = b as Record; + const aKeys = Object.keys(aObj); + const bKeys = Object.keys(bObj); + if (aKeys.length !== bKeys.length) return false; + return aKeys.every((key) => deepEqual(aObj[key], bObj[key])); + } + + return false; +} + +/** + * Escape JSON Pointer segment according to RFC 6901 + */ +export function escapeJsonPointer(segment: string): string { + return segment.replace(/~/g, '~0').replace(/\//g, '~1'); +} + +/** + * Join path segments into a JSON Pointer + */ +export function joinPath(basePath: string, ...segments: string[]): string { + const escaped = segments.map(escapeJsonPointer); + if (basePath === '') { + return escaped.length > 0 ? '/' + escaped.join('/') : ''; + } + return basePath + '/' + escaped.join('/'); +} diff --git a/packages/differs.core/src/walker.ts b/packages/differs.core/src/walker.ts new file mode 100644 index 0000000..e1d47db --- /dev/null +++ b/packages/differs.core/src/walker.ts @@ -0,0 +1,1578 @@ +/** + * JSON Schema structural walker + * + * Recursively walks two resolved JSON Schemas side-by-side (DFS) + * and emits RawChange for every structural difference. + */ + +import type { ChangeType, RawChange, CONTENT_KEYS } from './types.js'; +import { + ANNOTATION_KEYS, + arraysEqual, + CONSTRAINT_DIRECTION, + CONSTRAINT_KEYS, + type ConstraintKey, + deepEqual, + DEFAULT_MAX_DEPTH, + isSchemaObject, + joinPath, + METADATA_KEYS, + type NormalizedType, + normalizeType, + type ResolvedSchema, +} from './types.js'; + +/** + * Walk two resolved JSON Schemas and emit changes + * + * @param oldSchema - The original schema (resolved, no $refs) + * @param newSchema - The new schema (resolved, no $refs) + * @param basePath - JSON Pointer base path (default: '') + * @returns Array of raw changes detected + */ +export function walk(oldSchema: unknown, newSchema: unknown, basePath: string = ''): RawChange[] { + return walkInternal(oldSchema, newSchema, basePath, 0); +} + +/** + * Internal walk function with depth tracking + */ +function walkInternal( + oldSchema: unknown, + newSchema: unknown, + path: string, + depth: number +): RawChange[] { + const changes: RawChange[] = []; + + // Prevent infinite recursion + if (depth > DEFAULT_MAX_DEPTH) { + return changes; + } + + // Handle null/undefined schemas + if (oldSchema === undefined && newSchema === undefined) { + return changes; + } + + // Normalize to objects for comparison + const oldObj = isSchemaObject(oldSchema) ? oldSchema : null; + const newObj = isSchemaObject(newSchema) ? newSchema : null; + + // Schema added or removed entirely + if (oldObj === null && newObj !== null) { + changes.push({ + path, + type: 'property-added', + oldValue: undefined, + newValue: newSchema, + }); + return changes; + } + + if (oldObj !== null && newObj === null) { + changes.push({ + path, + type: 'property-removed', + oldValue: oldSchema, + newValue: undefined, + }); + return changes; + } + + // Both are non-object (boolean schemas in JSON Schema draft-06+) + if (oldObj === null && newObj === null) { + if (oldSchema !== newSchema) { + changes.push({ + path, + type: 'unknown-change', + oldValue: oldSchema, + newValue: newSchema, + }); + } + return changes; + } + + // Both are schema objects - compare all aspects + const old = oldObj as ResolvedSchema; + const newS = newObj as ResolvedSchema; + + // 1. Metadata changes (title, description, default, examples) + changes.push(...compareMetadata(old, newS, path)); + + // 2. Annotation changes (deprecated, readOnly, writeOnly) + changes.push(...compareAnnotations(old, newS, path)); + + // 3. Content keyword changes (contentEncoding, contentMediaType, contentSchema) + changes.push(...compareContentKeywords(old, newS, path, depth)); + + // 4. Type changes + changes.push(...compareType(old, newS, path)); + + // 5. Enum changes + changes.push(...compareEnum(old, newS, path)); + + // 6. Format changes + changes.push(...compareFormat(old, newS, path)); + + // 7. Constraint changes + changes.push(...compareConstraints(old, newS, path)); + + // 8. Properties changes (recurse) + changes.push(...compareProperties(old, newS, path, depth)); + + // 9. Required changes + changes.push(...compareRequired(old, newS, path)); + + // 10. additionalProperties changes + changes.push(...compareAdditionalProperties(old, newS, path, depth)); + + // 11. propertyNames changes + changes.push(...comparePropertyNames(old, newS, path, depth)); + + // 12. dependentRequired changes + changes.push(...compareDependentRequired(old, newS, path)); + + // 13. dependentSchemas changes + changes.push(...compareDependentSchemas(old, newS, path, depth)); + + // 14. unevaluatedProperties changes + changes.push(...compareUnevaluatedProperties(old, newS, path, depth)); + + // 15. Array items changes (recurse) + changes.push(...compareArrayItems(old, newS, path, depth)); + + // 16. unevaluatedItems changes + changes.push(...compareUnevaluatedItems(old, newS, path, depth)); + + // 17. minContains/maxContains changes + changes.push(...compareMinMaxContains(old, newS, path)); + + // 18. Composition changes (anyOf, oneOf, allOf, if/then/else, not) + changes.push(...compareComposition(old, newS, path, depth)); + + return changes; +} + +/** + * Mapping of metadata keys to their change types + */ +const METADATA_CHANGE_TYPES: Readonly> = { + description: 'description-changed', + title: 'title-changed', + default: 'default-changed', + examples: 'examples-changed', +} as const; + +/** + * Compare metadata fields (description, title, default, examples) + */ +function compareMetadata( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string +): RawChange[] { + const changes: RawChange[] = []; + + for (const key of METADATA_KEYS) { + const oldValue = oldSchema[key]; + const newValue = newSchema[key]; + + if (!deepEqual(oldValue, newValue)) { + changes.push({ + path: joinPath(path, key), + type: METADATA_CHANGE_TYPES[key], + oldValue, + newValue, + }); + } + } + + return changes; +} + +/** + * Mapping of annotation keys to their change types + */ +const ANNOTATION_CHANGE_TYPES: Readonly> = { + deprecated: 'deprecated-changed', + readOnly: 'read-only-changed', + writeOnly: 'write-only-changed', +} as const; + +/** + * Compare annotation fields (deprecated, readOnly, writeOnly) + * These are patch-level changes per Strands API + */ +function compareAnnotations( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string +): RawChange[] { + const changes: RawChange[] = []; + + for (const key of ANNOTATION_KEYS) { + const oldValue = oldSchema[key]; + const newValue = newSchema[key]; + + if (oldValue !== newValue) { + changes.push({ + path: joinPath(path, key), + type: ANNOTATION_CHANGE_TYPES[key], + oldValue, + newValue, + }); + } + } + + return changes; +} + +/** + * Mapping of content keys to their change types + */ +const CONTENT_CHANGE_TYPES: Readonly> = { + contentEncoding: 'content-encoding-changed', + contentMediaType: 'content-media-type-changed', + contentSchema: 'content-schema-changed', +} as const; + +/** + * Compare content keywords (contentEncoding, contentMediaType, contentSchema) + * These are patch-level changes per Strands API + */ +function compareContentKeywords( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string, + depth: number +): RawChange[] { + const changes: RawChange[] = []; + + // Compare contentEncoding and contentMediaType (simple string values) + for (const key of ['contentEncoding', 'contentMediaType'] as const) { + const oldValue = oldSchema[key]; + const newValue = newSchema[key]; + + if (oldValue !== newValue) { + changes.push({ + path: joinPath(path, key), + type: CONTENT_CHANGE_TYPES[key], + oldValue, + newValue, + }); + } + } + + // Compare contentSchema (recurse into schema) + const oldContentSchema = oldSchema.contentSchema; + const newContentSchema = newSchema.contentSchema; + + if (!deepEqual(oldContentSchema, newContentSchema)) { + if (isSchemaObject(oldContentSchema) && isSchemaObject(newContentSchema)) { + // Both are schemas - recurse but wrap all changes as content-schema-changed + const nestedChanges = walkInternal( + oldContentSchema, + newContentSchema, + joinPath(path, 'contentSchema'), + depth + 1 + ); + // If there are nested changes, report as content-schema-changed + if (nestedChanges.length > 0) { + changes.push({ + path: joinPath(path, 'contentSchema'), + type: 'content-schema-changed', + oldValue: oldContentSchema, + newValue: newContentSchema, + }); + } + } else { + // Schema added, removed, or type changed + changes.push({ + path: joinPath(path, 'contentSchema'), + type: 'content-schema-changed', + oldValue: oldContentSchema, + newValue: newContentSchema, + }); + } + } + + return changes; +} + +/** + * Compare type field, detecting narrowed/widened/changed + */ +function compareType( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string +): RawChange[] { + const changes: RawChange[] = []; + + const oldType = normalizeType(oldSchema.type); + const newType = normalizeType(newSchema.type); + + // No type defined in either + if (oldType.length === 0 && newType.length === 0) { + return changes; + } + + // Types are identical + if (arraysEqual(oldType, newType)) { + return changes; + } + + // Determine change type + const changeType = determineTypeChange(oldType, newType); + + changes.push({ + path: joinPath(path, 'type'), + type: changeType, + oldValue: oldSchema.type, + newValue: newSchema.type, + }); + + return changes; +} + +/** + * Determine if type change is narrowed, widened, or changed + */ +function determineTypeChange(oldType: NormalizedType, newType: NormalizedType): ChangeType { + // Type added where none existed + if (oldType.length === 0 && newType.length > 0) { + return 'type-narrowed'; // Adding type constraint narrows + } + + // Type removed where one existed + if (oldType.length > 0 && newType.length === 0) { + return 'type-widened'; // Removing type constraint widens + } + + // Check if new is subset of old (narrowed) + const oldSet = new Set(oldType); + const newSet = new Set(newType); + + const isSubset = newType.every((t) => oldSet.has(t)); + const isSuperset = oldType.every((t) => newSet.has(t)); + + if (isSubset && !isSuperset) { + return 'type-narrowed'; // New allows fewer types + } + + if (isSuperset && !isSubset) { + return 'type-widened'; // New allows more types + } + + // Types changed incompatibly (neither subset nor superset) + return 'type-changed'; +} + +/** + * Compare enum values + */ +function compareEnum( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string +): RawChange[] { + const changes: RawChange[] = []; + + const oldEnum = oldSchema.enum; + const newEnum = newSchema.enum; + + // Enum added + if (oldEnum === undefined && newEnum !== undefined) { + changes.push({ + path: joinPath(path, 'enum'), + type: 'enum-added', + oldValue: undefined, + newValue: newEnum, + }); + return changes; + } + + // Enum removed + if (oldEnum !== undefined && newEnum === undefined) { + changes.push({ + path: joinPath(path, 'enum'), + type: 'enum-removed', + oldValue: oldEnum, + newValue: undefined, + }); + return changes; + } + + // Both have enums - compare values + if (oldEnum !== undefined && newEnum !== undefined) { + // Find removed values + for (const oldValue of oldEnum) { + const exists = newEnum.some((v) => deepEqual(v, oldValue)); + if (!exists) { + changes.push({ + path: joinPath(path, 'enum'), + type: 'enum-value-removed', + oldValue, + newValue: undefined, + }); + } + } + + // Find added values + for (const newValue of newEnum) { + const exists = oldEnum.some((v) => deepEqual(v, newValue)); + if (!exists) { + changes.push({ + path: joinPath(path, 'enum'), + type: 'enum-value-added', + oldValue: undefined, + newValue, + }); + } + } + } + + // Also check const (single-value enum) + if (!deepEqual(oldSchema.const, newSchema.const)) { + if (oldSchema.const === undefined && newSchema.const !== undefined) { + changes.push({ + path: joinPath(path, 'const'), + type: 'enum-added', + oldValue: undefined, + newValue: newSchema.const, + }); + } else if (oldSchema.const !== undefined && newSchema.const === undefined) { + changes.push({ + path: joinPath(path, 'const'), + type: 'enum-removed', + oldValue: oldSchema.const, + newValue: undefined, + }); + } else { + changes.push({ + path: joinPath(path, 'const'), + type: 'enum-value-removed', + oldValue: oldSchema.const, + newValue: newSchema.const, + }); + } + } + + return changes; +} + +/** + * Compare format field + */ +function compareFormat( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string +): RawChange[] { + const changes: RawChange[] = []; + + const oldFormat = oldSchema.format; + const newFormat = newSchema.format; + + if (oldFormat === newFormat) { + return changes; + } + + if (oldFormat === undefined && newFormat !== undefined) { + changes.push({ + path: joinPath(path, 'format'), + type: 'format-added', + oldValue: undefined, + newValue: newFormat, + }); + } else if (oldFormat !== undefined && newFormat === undefined) { + changes.push({ + path: joinPath(path, 'format'), + type: 'format-removed', + oldValue: oldFormat, + newValue: undefined, + }); + } else { + changes.push({ + path: joinPath(path, 'format'), + type: 'format-changed', + oldValue: oldFormat, + newValue: newFormat, + }); + } + + return changes; +} + +/** + * Compare numeric and string constraints + */ +function compareConstraints( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string +): RawChange[] { + const changes: RawChange[] = []; + + for (const key of CONSTRAINT_KEYS) { + const oldValue = oldSchema[key]; + const newValue = newSchema[key]; + + if (deepEqual(oldValue, newValue)) { + continue; + } + + const direction = CONSTRAINT_DIRECTION[key]; + const changeType = determineConstraintChange(key, direction, oldValue, newValue); + + changes.push({ + path: joinPath(path, key), + type: changeType, + oldValue, + newValue, + }); + } + + return changes; +} + +/** + * Determine if constraint change is tightened or loosened + */ +function determineConstraintChange( + key: ConstraintKey, + direction: 'min' | 'max' | 'exact', + oldValue: unknown, + newValue: unknown +): ChangeType { + // Handle special cases for minItems/maxItems + if (key === 'minItems') { + if (oldValue === undefined && typeof newValue === 'number') { + return 'min-items-increased'; + } + if (typeof oldValue === 'number' && newValue === undefined) { + return 'constraint-loosened'; + } + if (typeof oldValue === 'number' && typeof newValue === 'number') { + return newValue > oldValue ? 'min-items-increased' : 'constraint-loosened'; + } + } + + if (key === 'maxItems') { + if (oldValue === undefined && typeof newValue === 'number') { + return 'max-items-decreased'; + } + if (typeof oldValue === 'number' && newValue === undefined) { + return 'constraint-loosened'; + } + if (typeof oldValue === 'number' && typeof newValue === 'number') { + return newValue < oldValue ? 'max-items-decreased' : 'constraint-loosened'; + } + } + + // Pattern and multipleOf are exact - any change is significant + if (direction === 'exact') { + return 'constraint-tightened'; // Conservative: treat as tightening + } + + // For min constraints: increasing tightens, decreasing loosens + if (direction === 'min') { + if (oldValue === undefined && newValue !== undefined) { + return 'constraint-tightened'; // Adding min constraint tightens + } + if (oldValue !== undefined && newValue === undefined) { + return 'constraint-loosened'; // Removing min constraint loosens + } + if (typeof oldValue === 'number' && typeof newValue === 'number') { + return newValue > oldValue ? 'constraint-tightened' : 'constraint-loosened'; + } + } + + // For max constraints: decreasing tightens, increasing loosens + if (direction === 'max') { + if (oldValue === undefined && newValue !== undefined) { + return 'constraint-tightened'; // Adding max constraint tightens + } + if (oldValue !== undefined && newValue === undefined) { + return 'constraint-loosened'; // Removing max constraint loosens + } + if (typeof oldValue === 'number' && typeof newValue === 'number') { + return newValue < oldValue ? 'constraint-tightened' : 'constraint-loosened'; + } + } + + // Handle uniqueItems specially + if (key === 'uniqueItems') { + if (newValue === true && oldValue !== true) { + return 'constraint-tightened'; + } + if (newValue !== true && oldValue === true) { + return 'constraint-loosened'; + } + } + + return 'constraint-tightened'; // Conservative default +} + +/** + * Compare object properties (recursive) + */ +function compareProperties( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string, + depth: number +): RawChange[] { + const changes: RawChange[] = []; + + const oldProps = oldSchema.properties ?? {}; + const newProps = newSchema.properties ?? {}; + + const oldKeys = new Set(Object.keys(oldProps)); + const newKeys = new Set(Object.keys(newProps)); + + // Find removed properties + for (const key of oldKeys) { + if (!newKeys.has(key)) { + changes.push({ + path: joinPath(path, 'properties', key), + type: 'property-removed', + oldValue: oldProps[key], + newValue: undefined, + }); + } + } + + // Find added properties + for (const key of newKeys) { + if (!oldKeys.has(key)) { + changes.push({ + path: joinPath(path, 'properties', key), + type: 'property-added', + oldValue: undefined, + newValue: newProps[key], + }); + } + } + + // Recurse into common properties + for (const key of oldKeys) { + if (newKeys.has(key)) { + const nestedChanges = walkInternal( + oldProps[key], + newProps[key], + joinPath(path, 'properties', key), + depth + 1 + ); + changes.push(...nestedChanges); + } + } + + // Also compare patternProperties if present + const oldPatternProps = oldSchema.patternProperties ?? {}; + const newPatternProps = newSchema.patternProperties ?? {}; + const oldPatternKeys = new Set(Object.keys(oldPatternProps)); + const newPatternKeys = new Set(Object.keys(newPatternProps)); + + for (const pattern of oldPatternKeys) { + if (!newPatternKeys.has(pattern)) { + changes.push({ + path: joinPath(path, 'patternProperties', pattern), + type: 'property-removed', + oldValue: oldPatternProps[pattern], + newValue: undefined, + }); + } + } + + for (const pattern of newPatternKeys) { + if (!oldPatternKeys.has(pattern)) { + changes.push({ + path: joinPath(path, 'patternProperties', pattern), + type: 'property-added', + oldValue: undefined, + newValue: newPatternProps[pattern], + }); + } + } + + for (const pattern of oldPatternKeys) { + if (newPatternKeys.has(pattern)) { + const nestedChanges = walkInternal( + oldPatternProps[pattern], + newPatternProps[pattern], + joinPath(path, 'patternProperties', pattern), + depth + 1 + ); + changes.push(...nestedChanges); + } + } + + return changes; +} + +/** + * Compare required array + */ +function compareRequired( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string +): RawChange[] { + const changes: RawChange[] = []; + + const oldRequired = new Set(oldSchema.required ?? []); + const newRequired = new Set(newSchema.required ?? []); + + // Find removed required fields + for (const field of oldRequired) { + if (!newRequired.has(field)) { + changes.push({ + path: joinPath(path, 'required'), + type: 'required-removed', + oldValue: field, + newValue: undefined, + }); + } + } + + // Find added required fields + for (const field of newRequired) { + if (!oldRequired.has(field)) { + changes.push({ + path: joinPath(path, 'required'), + type: 'required-added', + oldValue: undefined, + newValue: field, + }); + } + } + + return changes; +} + +/** + * Compare additionalProperties + */ +function compareAdditionalProperties( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string, + depth: number +): RawChange[] { + const changes: RawChange[] = []; + + const oldAP = oldSchema.additionalProperties; + const newAP = newSchema.additionalProperties; + + // No change + if (deepEqual(oldAP, newAP)) { + return changes; + } + + // Normalize: undefined means allowed (true) + const oldAllows = oldAP !== false; + const newAllows = newAP !== false; + + // Check for denied/allowed transitions + if (oldAllows && !newAllows) { + changes.push({ + path: joinPath(path, 'additionalProperties'), + type: 'additional-properties-denied', + oldValue: oldAP, + newValue: newAP, + }); + return changes; + } + + if (!oldAllows && newAllows) { + changes.push({ + path: joinPath(path, 'additionalProperties'), + type: 'additional-properties-allowed', + oldValue: oldAP, + newValue: newAP, + }); + return changes; + } + + // Both allow but with different schemas + if (isSchemaObject(oldAP) && isSchemaObject(newAP)) { + const nestedChanges = walkInternal( + oldAP, + newAP, + joinPath(path, 'additionalProperties'), + depth + 1 + ); + if (nestedChanges.length > 0) { + changes.push(...nestedChanges); + } + return changes; + } + + // One is boolean, one is schema, or other differences + if (oldAP !== newAP) { + changes.push({ + path: joinPath(path, 'additionalProperties'), + type: 'additional-properties-changed', + oldValue: oldAP, + newValue: newAP, + }); + } + + return changes; +} + +/** + * Compare propertyNames schema + */ +function comparePropertyNames( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string, + _depth: number +): RawChange[] { + const changes: RawChange[] = []; + + const oldPN = oldSchema.propertyNames; + const newPN = newSchema.propertyNames; + + if (deepEqual(oldPN, newPN)) { + return changes; + } + + // Any change to propertyNames is complex and requires manual review + if (isSchemaObject(oldPN) && isSchemaObject(newPN)) { + // Could recurse, but propertyNames changes are fundamentally unknown + changes.push({ + path: joinPath(path, 'propertyNames'), + type: 'property-names-changed', + oldValue: oldPN, + newValue: newPN, + }); + } else if (oldPN !== undefined || newPN !== undefined) { + changes.push({ + path: joinPath(path, 'propertyNames'), + type: 'property-names-changed', + oldValue: oldPN, + newValue: newPN, + }); + } + + return changes; +} + +/** + * Compare dependentRequired (Draft 2019-09+) + */ +function compareDependentRequired( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string +): RawChange[] { + const changes: RawChange[] = []; + + const oldDR = oldSchema.dependentRequired ?? {}; + const newDR = newSchema.dependentRequired ?? {}; + + const oldKeys = new Set(Object.keys(oldDR)); + const newKeys = new Set(Object.keys(newDR)); + + // Find removed dependencies (non-breaking) + for (const key of oldKeys) { + if (!newKeys.has(key)) { + changes.push({ + path: joinPath(path, 'dependentRequired', key), + type: 'dependent-required-removed', + oldValue: oldDR[key], + newValue: undefined, + }); + } + } + + // Find added dependencies (breaking) + for (const key of newKeys) { + if (!oldKeys.has(key)) { + changes.push({ + path: joinPath(path, 'dependentRequired', key), + type: 'dependent-required-added', + oldValue: undefined, + newValue: newDR[key], + }); + } + } + + // Compare modified dependencies + for (const key of oldKeys) { + if (newKeys.has(key)) { + const oldReqs = new Set(oldDR[key] ?? []); + const newReqs = new Set(newDR[key] ?? []); + + // Find removed requirements (non-breaking) + for (const req of oldReqs) { + if (!newReqs.has(req)) { + changes.push({ + path: joinPath(path, 'dependentRequired', key), + type: 'dependent-required-removed', + oldValue: req, + newValue: undefined, + }); + } + } + + // Find added requirements (breaking) + for (const req of newReqs) { + if (!oldReqs.has(req)) { + changes.push({ + path: joinPath(path, 'dependentRequired', key), + type: 'dependent-required-added', + oldValue: undefined, + newValue: req, + }); + } + } + } + } + + return changes; +} + +/** + * Compare dependentSchemas (Draft 2019-09+) + */ +function compareDependentSchemas( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string, + _depth: number +): RawChange[] { + const changes: RawChange[] = []; + + const oldDS = oldSchema.dependentSchemas ?? {}; + const newDS = newSchema.dependentSchemas ?? {}; + + const oldKeys = new Set(Object.keys(oldDS)); + const newKeys = new Set(Object.keys(newDS)); + const allKeys = new Set([...oldKeys, ...newKeys]); + + for (const key of allKeys) { + const oldValue = oldDS[key]; + const newValue = newDS[key]; + + if (!deepEqual(oldValue, newValue)) { + // dependentSchemas changes require manual review + changes.push({ + path: joinPath(path, 'dependentSchemas', key), + type: 'dependent-schemas-changed', + oldValue, + newValue, + }); + } + } + + return changes; +} + +/** + * Compare unevaluatedProperties (Draft 2019-09+) + */ +function compareUnevaluatedProperties( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string, + _depth: number +): RawChange[] { + const changes: RawChange[] = []; + + const oldUP = oldSchema.unevaluatedProperties; + const newUP = newSchema.unevaluatedProperties; + + if (deepEqual(oldUP, newUP)) { + return changes; + } + + // unevaluatedProperties changes require manual review + changes.push({ + path: joinPath(path, 'unevaluatedProperties'), + type: 'unevaluated-properties-changed', + oldValue: oldUP, + newValue: newUP, + }); + + return changes; +} + +/** + * Compare array items schema (recursive) + */ +function compareArrayItems( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string, + depth: number +): RawChange[] { + const changes: RawChange[] = []; + + const oldItems = oldSchema.items; + const newItems = newSchema.items; + + // Compare single items schema + if (isSchemaObject(oldItems) && isSchemaObject(newItems)) { + const nestedChanges = walkInternal(oldItems, newItems, joinPath(path, 'items'), depth + 1); + changes.push(...nestedChanges); + } else if (oldItems !== undefined || newItems !== undefined) { + // Items added, removed, or changed type (array to object or vice versa) + if (!deepEqual(oldItems, newItems)) { + // Handle tuple items (array of schemas) + if (Array.isArray(oldItems) && Array.isArray(newItems)) { + const maxLen = Math.max(oldItems.length, newItems.length); + for (let i = 0; i < maxLen; i++) { + const nestedChanges = walkInternal( + oldItems[i], + newItems[i], + joinPath(path, 'items', String(i)), + depth + 1 + ); + changes.push(...nestedChanges); + } + } else if (Array.isArray(oldItems) !== Array.isArray(newItems)) { + // Structural change between tuple and list validation + changes.push({ + path: joinPath(path, 'items'), + type: 'items-changed', + oldValue: oldItems, + newValue: newItems, + }); + } else if (oldItems === undefined || newItems === undefined) { + changes.push({ + path: joinPath(path, 'items'), + type: 'items-changed', + oldValue: oldItems, + newValue: newItems, + }); + } + } + } + + // Compare prefixItems (JSON Schema draft 2020-12) + const oldPrefixItems = oldSchema.prefixItems; + const newPrefixItems = newSchema.prefixItems; + + if (Array.isArray(oldPrefixItems) || Array.isArray(newPrefixItems)) { + const oldArr = oldPrefixItems ?? []; + const newArr = newPrefixItems ?? []; + const maxLen = Math.max(oldArr.length, newArr.length); + + for (let i = 0; i < maxLen; i++) { + const nestedChanges = walkInternal( + oldArr[i], + newArr[i], + joinPath(path, 'prefixItems', String(i)), + depth + 1 + ); + changes.push(...nestedChanges); + } + } + + // Compare contains schema + if (oldSchema.contains !== undefined || newSchema.contains !== undefined) { + if (!deepEqual(oldSchema.contains, newSchema.contains)) { + if (isSchemaObject(oldSchema.contains) && isSchemaObject(newSchema.contains)) { + const nestedChanges = walkInternal( + oldSchema.contains, + newSchema.contains, + joinPath(path, 'contains'), + depth + 1 + ); + changes.push(...nestedChanges); + } else { + changes.push({ + path: joinPath(path, 'contains'), + type: 'items-changed', + oldValue: oldSchema.contains, + newValue: newSchema.contains, + }); + } + } + } + + return changes; +} + +/** + * Compare unevaluatedItems (Draft 2020-12) + */ +function compareUnevaluatedItems( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string, + _depth: number +): RawChange[] { + const changes: RawChange[] = []; + + const oldUI = oldSchema.unevaluatedItems; + const newUI = newSchema.unevaluatedItems; + + if (deepEqual(oldUI, newUI)) { + return changes; + } + + // unevaluatedItems changes require manual review + changes.push({ + path: joinPath(path, 'unevaluatedItems'), + type: 'unevaluated-items-changed', + oldValue: oldUI, + newValue: newUI, + }); + + return changes; +} + +/** + * Compare minContains and maxContains (Draft 2019-09+) + */ +function compareMinMaxContains( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string +): RawChange[] { + const changes: RawChange[] = []; + + // Compare minContains + const oldMinContains = oldSchema.minContains; + const newMinContains = newSchema.minContains; + + if (oldMinContains !== newMinContains) { + changes.push({ + path: joinPath(path, 'minContains'), + type: 'min-contains-changed', + oldValue: oldMinContains, + newValue: newMinContains, + }); + } + + // Compare maxContains + const oldMaxContains = oldSchema.maxContains; + const newMaxContains = newSchema.maxContains; + + if (oldMaxContains !== newMaxContains) { + changes.push({ + path: joinPath(path, 'maxContains'), + type: 'max-contains-changed', + oldValue: oldMaxContains, + newValue: newMaxContains, + }); + } + + return changes; +} + +/** + * Compare composition keywords (anyOf, oneOf, allOf, if/then/else, not) + * + * Provides detailed analysis of composition changes with granular change types + * aligned with Strands API classification. + */ +function compareComposition( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string, + depth: number +): RawChange[] { + const changes: RawChange[] = []; + + // Compare anyOf + changes.push(...compareAnyOf(oldSchema, newSchema, path, depth)); + + // Compare oneOf + changes.push(...compareOneOf(oldSchema, newSchema, path, depth)); + + // Compare allOf + changes.push(...compareAllOf(oldSchema, newSchema, path, depth)); + + // Compare not + changes.push(...compareNot(oldSchema, newSchema, path, depth)); + + // Compare if/then/else + changes.push(...compareIfThenElse(oldSchema, newSchema, path, depth)); + + return changes; +} + +/** + * Compare anyOf composition (option additions are breaking, removals are non-breaking) + */ +function compareAnyOf( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string, + depth: number +): RawChange[] { + const changes: RawChange[] = []; + + const oldAnyOf = oldSchema.anyOf; + const newAnyOf = newSchema.anyOf; + + // No anyOf in either + if (oldAnyOf === undefined && newAnyOf === undefined) { + return changes; + } + + // anyOf added + if (oldAnyOf === undefined && newAnyOf !== undefined) { + for (let i = 0; i < newAnyOf.length; i++) { + changes.push({ + path: joinPath(path, 'anyOf', String(i)), + type: 'anyof-option-added', + oldValue: undefined, + newValue: newAnyOf[i], + }); + } + return changes; + } + + // anyOf removed + if (oldAnyOf !== undefined && newAnyOf === undefined) { + for (let i = 0; i < oldAnyOf.length; i++) { + changes.push({ + path: joinPath(path, 'anyOf', String(i)), + type: 'anyof-option-removed', + oldValue: oldAnyOf[i], + newValue: undefined, + }); + } + return changes; + } + + // Both have anyOf - compare options + if (oldAnyOf !== undefined && newAnyOf !== undefined) { + const matched = matchCompositionOptions(oldAnyOf, newAnyOf); + + // Report removed options + for (const idx of matched.removed) { + changes.push({ + path: joinPath(path, 'anyOf', String(idx)), + type: 'anyof-option-removed', + oldValue: oldAnyOf[idx], + newValue: undefined, + }); + } + + // Report added options + for (const idx of matched.added) { + changes.push({ + path: joinPath(path, 'anyOf', String(idx)), + type: 'anyof-option-added', + oldValue: undefined, + newValue: newAnyOf[idx], + }); + } + + // Recurse into matched options for nested changes + for (const [oldIdx, newIdx] of matched.matched) { + const nestedChanges = walkInternal( + oldAnyOf[oldIdx], + newAnyOf[newIdx], + joinPath(path, 'anyOf', String(newIdx)), + depth + 1 + ); + changes.push(...nestedChanges); + } + } + + return changes; +} + +/** + * Compare oneOf composition (option additions are breaking, removals are non-breaking) + */ +function compareOneOf( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string, + depth: number +): RawChange[] { + const changes: RawChange[] = []; + + const oldOneOf = oldSchema.oneOf; + const newOneOf = newSchema.oneOf; + + // No oneOf in either + if (oldOneOf === undefined && newOneOf === undefined) { + return changes; + } + + // oneOf added + if (oldOneOf === undefined && newOneOf !== undefined) { + for (let i = 0; i < newOneOf.length; i++) { + changes.push({ + path: joinPath(path, 'oneOf', String(i)), + type: 'oneof-option-added', + oldValue: undefined, + newValue: newOneOf[i], + }); + } + return changes; + } + + // oneOf removed + if (oldOneOf !== undefined && newOneOf === undefined) { + for (let i = 0; i < oldOneOf.length; i++) { + changes.push({ + path: joinPath(path, 'oneOf', String(i)), + type: 'oneof-option-removed', + oldValue: oldOneOf[i], + newValue: undefined, + }); + } + return changes; + } + + // Both have oneOf - compare options + if (oldOneOf !== undefined && newOneOf !== undefined) { + const matched = matchCompositionOptions(oldOneOf, newOneOf); + + // Report removed options + for (const idx of matched.removed) { + changes.push({ + path: joinPath(path, 'oneOf', String(idx)), + type: 'oneof-option-removed', + oldValue: oldOneOf[idx], + newValue: undefined, + }); + } + + // Report added options + for (const idx of matched.added) { + changes.push({ + path: joinPath(path, 'oneOf', String(idx)), + type: 'oneof-option-added', + oldValue: undefined, + newValue: newOneOf[idx], + }); + } + + // Recurse into matched options for nested changes + for (const [oldIdx, newIdx] of matched.matched) { + const nestedChanges = walkInternal( + oldOneOf[oldIdx], + newOneOf[newIdx], + joinPath(path, 'oneOf', String(newIdx)), + depth + 1 + ); + changes.push(...nestedChanges); + } + } + + return changes; +} + +/** + * Compare allOf composition (member additions are breaking, removals are non-breaking) + */ +function compareAllOf( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string, + depth: number +): RawChange[] { + const changes: RawChange[] = []; + + const oldAllOf = oldSchema.allOf; + const newAllOf = newSchema.allOf; + + // No allOf in either + if (oldAllOf === undefined && newAllOf === undefined) { + return changes; + } + + // allOf added + if (oldAllOf === undefined && newAllOf !== undefined) { + for (let i = 0; i < newAllOf.length; i++) { + changes.push({ + path: joinPath(path, 'allOf', String(i)), + type: 'allof-member-added', + oldValue: undefined, + newValue: newAllOf[i], + }); + } + return changes; + } + + // allOf removed + if (oldAllOf !== undefined && newAllOf === undefined) { + for (let i = 0; i < oldAllOf.length; i++) { + changes.push({ + path: joinPath(path, 'allOf', String(i)), + type: 'allof-member-removed', + oldValue: oldAllOf[i], + newValue: undefined, + }); + } + return changes; + } + + // Both have allOf - compare members + if (oldAllOf !== undefined && newAllOf !== undefined) { + const matched = matchCompositionOptions(oldAllOf, newAllOf); + + // Report removed members + for (const idx of matched.removed) { + changes.push({ + path: joinPath(path, 'allOf', String(idx)), + type: 'allof-member-removed', + oldValue: oldAllOf[idx], + newValue: undefined, + }); + } + + // Report added members + for (const idx of matched.added) { + changes.push({ + path: joinPath(path, 'allOf', String(idx)), + type: 'allof-member-added', + oldValue: undefined, + newValue: newAllOf[idx], + }); + } + + // Recurse into matched members for nested changes + for (const [oldIdx, newIdx] of matched.matched) { + const nestedChanges = walkInternal( + oldAllOf[oldIdx], + newAllOf[newIdx], + joinPath(path, 'allOf', String(newIdx)), + depth + 1 + ); + changes.push(...nestedChanges); + } + } + + return changes; +} + +/** + * Compare not schema (any change is breaking) + */ +function compareNot( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string, + _depth: number +): RawChange[] { + const changes: RawChange[] = []; + + const oldNot = oldSchema.not; + const newNot = newSchema.not; + + if (deepEqual(oldNot, newNot)) { + return changes; + } + + // Any change to not schema is breaking + changes.push({ + path: joinPath(path, 'not'), + type: 'not-schema-changed', + oldValue: oldNot, + newValue: newNot, + }); + + return changes; +} + +/** + * Compare if/then/else conditional schema (complex, requires manual review) + */ +function compareIfThenElse( + oldSchema: ResolvedSchema, + newSchema: ResolvedSchema, + path: string, + _depth: number +): RawChange[] { + const changes: RawChange[] = []; + + const oldIf = oldSchema.if; + const newIf = newSchema.if; + const oldThen = oldSchema.then; + const newThen = newSchema.then; + const oldElse = oldSchema.else; + const newElse = newSchema.else; + + // Check if any of the if/then/else keywords changed + const ifChanged = !deepEqual(oldIf, newIf); + const thenChanged = !deepEqual(oldThen, newThen); + const elseChanged = !deepEqual(oldElse, newElse); + + if (ifChanged || thenChanged || elseChanged) { + // Report as a single if-then-else-changed for simplicity + // These are complex and require manual review + changes.push({ + path: joinPath(path, 'if'), + type: 'if-then-else-changed', + oldValue: { if: oldIf, then: oldThen, else: oldElse }, + newValue: { if: newIf, then: newThen, else: newElse }, + }); + } + + return changes; +} + +/** + * Match composition options between old and new arrays + * Uses structural similarity to find corresponding options + */ +function matchCompositionOptions( + oldOptions: ResolvedSchema[], + newOptions: ResolvedSchema[] +): { + matched: Array<[number, number]>; + removed: number[]; + added: number[]; +} { + const matched: Array<[number, number]> = []; + const usedOld = new Set(); + const usedNew = new Set(); + + // First pass: find exact matches + for (let i = 0; i < oldOptions.length; i++) { + for (let j = 0; j < newOptions.length; j++) { + if (usedNew.has(j)) continue; + if (deepEqual(oldOptions[i], newOptions[j])) { + matched.push([i, j]); + usedOld.add(i); + usedNew.add(j); + break; + } + } + } + + // Second pass: match by position for remaining unmatched + // This handles cases where schemas are modified but at same position + for (let i = 0; i < oldOptions.length; i++) { + if (usedOld.has(i)) continue; + if (i < newOptions.length && !usedNew.has(i)) { + // Match by position as a heuristic + matched.push([i, i]); + usedOld.add(i); + usedNew.add(i); + } + } + + // Collect removed (in old but not matched) + const removed: number[] = []; + for (let i = 0; i < oldOptions.length; i++) { + if (!usedOld.has(i)) { + removed.push(i); + } + } + + // Collect added (in new but not matched) + const added: number[] = []; + for (let j = 0; j < newOptions.length; j++) { + if (!usedNew.has(j)) { + added.push(j); + } + } + + return { matched, removed, added }; +} diff --git a/packages/differs.core/tsconfig.build.json b/packages/differs.core/tsconfig.build.json new file mode 100644 index 0000000..2333a75 --- /dev/null +++ b/packages/differs.core/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "exclude": ["node_modules", "dist", "tests", "**/*.test.ts"] +} diff --git a/packages/differs.core/tsconfig.json b/packages/differs.core/tsconfig.json new file mode 100644 index 0000000..e666b16 --- /dev/null +++ b/packages/differs.core/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "tests"] +} diff --git a/packages/contract/.eslintrc b/packages/differs.json-schema/.eslintrc similarity index 94% rename from packages/contract/.eslintrc rename to packages/differs.json-schema/.eslintrc index f1ca71a..5e998f8 100644 --- a/packages/contract/.eslintrc +++ b/packages/differs.json-schema/.eslintrc @@ -1,3 +1,3 @@ { "extends": "../../.eslintrc" -} \ No newline at end of file +} diff --git a/packages/differs.json-schema/.gitignore b/packages/differs.json-schema/.gitignore new file mode 100644 index 0000000..83631f8 --- /dev/null +++ b/packages/differs.json-schema/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +*.tsbuildinfo diff --git a/packages/differs.json-schema/README.md b/packages/differs.json-schema/README.md new file mode 100644 index 0000000..98df287 --- /dev/null +++ b/packages/differs.json-schema/README.md @@ -0,0 +1,180 @@ +# @contractual/differs.json-schema + +Production-quality breaking change detection for JSON Schema. + +Detects and classifies structural changes between JSON Schema versions, recommending semantic version bumps (major/minor/patch). + +## Why? + +JSON Schema has 150M+ weekly npm downloads but **no production-quality breaking change detection tool**. This package fills that gap. + +## Installation + +```bash +npm install @contractual/differs.json-schema +``` + +## Usage + +### Compare Schema Files + +```typescript +import { diffJsonSchema } from '@contractual/differs.json-schema'; + +const result = await diffJsonSchema('v1/user.schema.json', 'v2/user.schema.json'); + +console.log(`Suggested bump: ${result.suggestedBump}`); +console.log(`Breaking: ${result.summary.breaking}`); +console.log(`Non-breaking: ${result.summary.nonBreaking}`); + +// List breaking changes +for (const change of result.changes) { + if (change.severity === 'breaking') { + console.log(`❌ ${change.message}`); + } +} +``` + +### Compare Schema Objects + +```typescript +import { diffJsonSchemaObjects } from '@contractual/differs.json-schema'; + +const oldSchema = { type: 'object', properties: { name: { type: 'string' } } }; +const newSchema = { type: 'object', properties: { name: { type: 'number' } } }; + +const result = diffJsonSchemaObjects(oldSchema, newSchema); +// result.suggestedBump === 'major' +// result.changes[0].message === 'Type changed from "string" to "number"...' +``` + +### Quick Breaking Check + +```typescript +import { hasBreakingChanges } from '@contractual/differs.json-schema'; + +if (hasBreakingChanges(oldSchema, newSchema)) { + throw new Error('Breaking changes detected!'); +} +``` + +## Change Classifications + +### Breaking Changes (β†’ major bump) + +| Category | Example | +|----------|---------| +| `property-removed` | Field deleted from schema | +| `required-added` | Existing field became required | +| `type-changed` | Type changed incompatibly | +| `type-narrowed` | Union type reduced | +| `enum-value-removed` | Enum option removed | +| `constraint-tightened` | minLength/maxLength made stricter | +| `additional-properties-denied` | Open schema closed | +| `min-items-increased` | Array minimum increased | +| `max-items-decreased` | Array maximum decreased | + +### Non-Breaking Changes (β†’ minor bump) + +| Category | Example | +|----------|---------| +| `property-added` | New optional field | +| `required-removed` | Field became optional | +| `type-widened` | Type accepts more values | +| `enum-value-added` | New enum option | +| `constraint-loosened` | Constraints relaxed | +| `additional-properties-allowed` | Closed schema opened | + +### Patch Changes (β†’ patch bump) + +| Category | Example | +|----------|---------| +| `description-changed` | Documentation updated | +| `title-changed` | Title updated | +| `default-changed` | Default value updated | +| `examples-changed` | Examples updated | + +## Advanced Usage + +### Resolve $refs + +```typescript +import { resolveRefs } from '@contractual/differs.json-schema'; + +const schema = { + type: 'object', + properties: { + user: { $ref: '#/$defs/User' } + }, + $defs: { + User: { type: 'object', properties: { name: { type: 'string' } } } + } +}; + +const { schema: resolved, warnings } = resolveRefs(schema); +// resolved.properties.user === { type: 'object', properties: { name: { type: 'string' } } } +``` + +### Custom Classification + +```typescript +import { walk, classify, CLASSIFICATION_SETS } from '@contractual/differs.json-schema'; + +// Get raw changes +const rawChanges = walk(resolvedOld, resolvedNew, ''); + +// Classify with custom rules +for (const change of rawChanges) { + if (CLASSIFICATION_SETS.BREAKING.has(change.type)) { + console.log('Breaking:', change.path); + } +} +``` + +## Supported JSON Schema Versions + +- Draft-07 (primary target) +- Draft 2019-09 +- Draft 2020-12 + +## API Reference + +### Main Functions + +- `diffJsonSchema(oldPath, newPath)` - Compare two schema files +- `diffJsonSchemaObjects(oldSchema, newSchema)` - Compare two schema objects +- `hasBreakingChanges(oldSchema, newSchema)` - Quick boolean check + +### Utilities + +- `resolveRefs(schema)` - Resolve all $ref pointers +- `walk(oldSchema, newSchema, basePath)` - Low-level schema walker +- `classify(change)` - Classify a raw change +- `classifyPropertyAdded(change, newSchema)` - Context-aware property classification + +### Types + +```typescript +interface DiffResult { + changes: Change[]; + summary: DiffSummary; + suggestedBump: 'major' | 'minor' | 'patch' | 'none'; +} + +interface Change { + path: string; + severity: 'breaking' | 'non-breaking' | 'patch' | 'unknown'; + category: string; + message: string; + oldValue?: unknown; + newValue?: unknown; +} +``` + +## Part of Contractual + +This package is extracted from [Contractual](https://github.com/contractual-dev/contractual), the schema contract lifecycle orchestrator. + +## License + +MIT diff --git a/packages/differs.json-schema/package.json b/packages/differs.json-schema/package.json new file mode 100644 index 0000000..ff0cb02 --- /dev/null +++ b/packages/differs.json-schema/package.json @@ -0,0 +1,62 @@ +{ + "name": "@contractual/differs.json-schema", + "version": "0.1.0-dev.5", + "description": "Detect breaking changes between JSON Schema versions with semantic classification", + "license": "MIT", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "repository": { + "type": "git", + "url": "https://github.com/contractual-dev/contractual.git", + "directory": "packages/differs.json-schema" + }, + "homepage": "https://github.com/contractual-dev/contractual/tree/main/packages/differs.json-schema", + "bugs": { + "url": "https://github.com/contractual-dev/contractual/issues" + }, + "keywords": [ + "json-schema", + "diff", + "breaking-changes", + "semver", + "schema", + "api", + "compatibility", + "migration", + "validation" + ], + "engines": { + "node": ">=20.0.0" + }, + "scripts": { + "prebuild": "rimraf dist", + "build": "tsc -p tsconfig.build.json", + "build:watch": "tsc -p tsconfig.build.json --watch", + "test": "vitest run", + "test:watch": "vitest", + "lint": "eslint \"src/**/*.ts\"" + }, + "files": [ + "dist", + "README.md" + ], + "dependencies": { + "@contractual/differs.core": "workspace:*", + "@contractual/types": "workspace:*" + }, + "devDependencies": { + "rimraf": "^5.0.5", + "vitest": "^3.0.3" + }, + "publishConfig": { + "access": "public", + "provenance": true + } +} diff --git a/packages/differs.json-schema/src/compare.ts b/packages/differs.json-schema/src/compare.ts new file mode 100644 index 0000000..07f126b --- /dev/null +++ b/packages/differs.json-schema/src/compare.ts @@ -0,0 +1,293 @@ +/** + * Strands-compatible JSON Schema comparison API + * + * Provides schema comparison with output format matching the Strands API + * (https://strands.octue.com/api/compare-schemas) + */ + +import type { ChangeSeverity, RawChange } from '@contractual/types'; +import type { + CompareResult, + CompareOptions, + StrandsTrace, + StrandsCompatibility, + StrandsVersion, + SemanticVersion, +} from './types.js'; +import { resolveRefs, walk, classify, classifyPropertyAdded } from '@contractual/differs.core'; + +/** + * Compare two JSON Schema objects and return Strands-compatible result + * + * @param sourceSchema - The source (old/baseline) schema object + * @param targetSchema - The target (new/current) schema object + * @param options - Optional comparison options + * @returns CompareResult in Strands API format + * + * @example + * ```typescript + * const source = { type: 'object', properties: { name: { type: 'string' } } }; + * const target = { type: 'object', properties: { name: { type: 'number' } } }; + * + * const result = compareSchemas(source, target, { currentVersion: '1.0.0' }); + * console.log(result.version); // 'major' + * console.log(result.newVersion); // { major: 2, minor: 0, patch: 0, version: '2.0.0' } + * ``` + */ +export function compareSchemas( + sourceSchema: unknown, + targetSchema: unknown, + options?: CompareOptions +): CompareResult { + // Resolve $refs in both schemas + const resolvedSourceResult = resolveRefs(sourceSchema); + const resolvedTargetResult = resolveRefs(targetSchema); + + const resolvedSource = resolvedSourceResult.schema; + const resolvedTarget = resolvedTargetResult.schema; + + // Walk both schemas and detect raw changes + const rawChanges = walk(resolvedSource, resolvedTarget, ''); + + // Classify changes and generate traces + const traces: StrandsTrace[] = []; + let hasBreaking = false; + let hasNonBreaking = false; + let hasPatch = false; + let hasUnknown = false; + + for (const raw of rawChanges) { + const severity = classifyChange(raw, resolvedTarget); + + // Track highest severity + switch (severity) { + case 'breaking': + hasBreaking = true; + break; + case 'non-breaking': + hasNonBreaking = true; + break; + case 'patch': + hasPatch = true; + break; + case 'unknown': + hasUnknown = true; + break; + } + + // Generate trace + const trace = rawChangeToTrace(raw, severity); + traces.push(trace); + } + + // Determine version bump + const version = determineVersion(hasBreaking, hasNonBreaking, hasPatch, hasUnknown, traces); + + // Compute new version if current version provided + const newVersion = options?.currentVersion + ? computeNewVersion(options.currentVersion, version) + : null; + + return { + version, + traces, + newVersion, + }; +} + +/** + * Classify a change, with context-aware classification for property-added + */ +function classifyChange(change: RawChange, newSchema: unknown): ChangeSeverity { + if (change.type === 'property-added') { + return classifyPropertyAdded(change, newSchema); + } + return classify(change); +} + +/** + * Convert a RawChange to a Strands trace + */ +function rawChangeToTrace(change: RawChange, severity: ChangeSeverity): StrandsTrace { + const compatibility = severityToCompatibility(severity); + + // Determine left/right paths based on change type + const isAddition = + change.type.includes('added') || + change.type === 'type-widened' || + change.type === 'constraint-loosened'; + const isRemoval = + change.type.includes('removed') || + change.type === 'type-narrowed' || + change.type === 'constraint-tightened'; + + let left: string | null = change.path; + let right: string | null = change.path; + + // For additions, left is null (didn't exist in source) + if (isAddition && change.oldValue === undefined) { + left = null; + } + + // For removals, right is null (doesn't exist in target) + if (isRemoval && change.newValue === undefined) { + right = null; + } + + return { + compatibility, + left, + right, + }; +} + +/** + * Map ChangeSeverity to Strands compatibility + */ +function severityToCompatibility(severity: ChangeSeverity): StrandsCompatibility { + switch (severity) { + case 'breaking': + return 'incompatible'; + case 'non-breaking': + case 'patch': + return 'compatible'; + case 'unknown': + default: + return 'unknown'; + } +} + +/** + * Determine the version bump level based on detected changes + */ +function determineVersion( + hasBreaking: boolean, + hasNonBreaking: boolean, + hasPatch: boolean, + hasUnknown: boolean, + traces: StrandsTrace[] +): StrandsVersion { + // No changes = equal + if (traces.length === 0) { + return 'equal'; + } + + // If there are unknown changes, we can't determine version + if (hasUnknown && !hasBreaking && !hasNonBreaking) { + return null; + } + + // Priority: breaking > non-breaking > patch + if (hasBreaking) { + return 'major'; + } + + if (hasNonBreaking) { + return 'minor'; + } + + if (hasPatch) { + return 'patch'; + } + + // Only unknown changes + return null; +} + +/** + * Compute the new semantic version based on current version and bump level + */ +function computeNewVersion( + currentVersion: string, + bumpLevel: StrandsVersion +): SemanticVersion | null { + if (bumpLevel === null || bumpLevel === 'equal') { + // Parse current version and return as-is for equal + const parsed = parseVersion(currentVersion); + if (!parsed) return null; + return bumpLevel === 'equal' ? parsed : null; + } + + const parsed = parseVersion(currentVersion); + if (!parsed) { + return null; + } + + let { major, minor, patch } = parsed; + + switch (bumpLevel) { + case 'major': + major += 1; + minor = 0; + patch = 0; + break; + case 'minor': + minor += 1; + patch = 0; + break; + case 'patch': + patch += 1; + break; + } + + return { + major, + minor, + patch, + version: `${major}.${minor}.${patch}`, + }; +} + +/** + * Parse a semver string into components + */ +function parseVersion(version: string): SemanticVersion | null { + // Remove 'v' prefix if present + const cleaned = version.startsWith('v') ? version.slice(1) : version; + + // Match semver pattern (with optional prerelease/build) + const match = cleaned.match(/^(\d+)\.(\d+)\.(\d+)/); + if (!match) { + return null; + } + + const major = parseInt(match[1]!, 10); + const minor = parseInt(match[2]!, 10); + const patch = parseInt(match[3]!, 10); + + return { + major, + minor, + patch, + version: `${major}.${minor}.${patch}`, + }; +} + +/** + * Compare two JSON Schema objects and return a simple compatibility result + * + * Convenience function for quick compatibility checks. + * + * @param sourceSchema - The source (old/baseline) schema object + * @param targetSchema - The target (new/current) schema object + * @returns 'compatible' | 'incompatible' | 'unknown' + */ +export function checkCompatibility( + sourceSchema: unknown, + targetSchema: unknown +): StrandsCompatibility { + const result = compareSchemas(sourceSchema, targetSchema); + + // If any trace is incompatible, overall is incompatible + if (result.traces.some((t) => t.compatibility === 'incompatible')) { + return 'incompatible'; + } + + // If any trace is unknown, overall is unknown + if (result.traces.some((t) => t.compatibility === 'unknown')) { + return 'unknown'; + } + + // All compatible + return 'compatible'; +} diff --git a/packages/differs.json-schema/src/differ.ts b/packages/differs.json-schema/src/differ.ts new file mode 100644 index 0000000..c533b4a --- /dev/null +++ b/packages/differs.json-schema/src/differ.ts @@ -0,0 +1,78 @@ +/** + * JSON Schema Structural Differ + * + * Compares two JSON Schema files and detects structural changes, + * classifying them by severity for semver bump decisions. + */ + +import { readFile } from 'node:fs/promises'; +import type { DiffResult } from '@contractual/types'; +import { resolveRefs, walk, assembleResult, formatChangeMessage } from '@contractual/differs.core'; + +// Re-export for backward compatibility +export { formatChangeMessage }; + +/** + * Diff two JSON Schema files and detect structural changes + * + * @param oldPath - Path to the old/base schema file + * @param newPath - Path to the new/changed schema file + * @returns DiffResult with classified changes and suggested bump + */ +export async function diffJsonSchema(oldPath: string, newPath: string): Promise { + let oldContent: string; + let newContent: string; + + try { + oldContent = await readFile(oldPath, 'utf-8'); + } catch (error) { + throw new Error( + `Failed to read old schema file "${oldPath}": ${error instanceof Error ? error.message : String(error)}` + ); + } + + try { + newContent = await readFile(newPath, 'utf-8'); + } catch (error) { + throw new Error( + `Failed to read new schema file "${newPath}": ${error instanceof Error ? error.message : String(error)}` + ); + } + + let oldSchema: unknown; + let newSchema: unknown; + + try { + oldSchema = JSON.parse(oldContent); + } catch (error) { + throw new Error( + `Failed to parse old schema as JSON "${oldPath}": ${error instanceof Error ? error.message : String(error)}` + ); + } + + try { + newSchema = JSON.parse(newContent); + } catch (error) { + throw new Error( + `Failed to parse new schema as JSON "${newPath}": ${error instanceof Error ? error.message : String(error)}` + ); + } + + return diffJsonSchemaObjects(oldSchema, newSchema); +} + +/** + * Diff two JSON Schema objects and detect structural changes + * + * @param oldSchema - The old/base schema object + * @param newSchema - The new/changed schema object + * @returns DiffResult with classified changes and suggested bump + */ +export function diffJsonSchemaObjects(oldSchema: unknown, newSchema: unknown): DiffResult { + const resolvedOld = resolveRefs(oldSchema).schema; + const resolvedNew = resolveRefs(newSchema).schema; + + const rawChanges = walk(resolvedOld, resolvedNew, ''); + + return assembleResult(rawChanges, resolvedNew); +} diff --git a/packages/differs.json-schema/src/index.ts b/packages/differs.json-schema/src/index.ts new file mode 100644 index 0000000..a23c624 --- /dev/null +++ b/packages/differs.json-schema/src/index.ts @@ -0,0 +1,81 @@ +/** + * @contractual/differs.json-schema + * + * Detect and classify breaking changes between JSON Schema versions. + * + * @packageDocumentation + */ + +// ============================================================================= +// Strands-compatible API (primary) +// ============================================================================= + +export { compareSchemas, checkCompatibility } from './compare.js'; + +// ============================================================================= +// Legacy file-based API (backward compatible) +// ============================================================================= + +export { diffJsonSchema, diffJsonSchemaObjects, formatChangeMessage } from './differ.js'; + +// ============================================================================= +// Re-exports from @contractual/differs.core (backward compatibility) +// ============================================================================= + +export { + // Classification + classify, + classifyPropertyAdded, + classifyAll, + CLASSIFICATION_SETS, + // Ref resolution + resolveRefs, + hasUnresolvedRefs, + extractRefs, + validateRefs, + type ResolveResult, + // Walker + walk, + // Result assembly + assembleResult, + type AssembleOptions, + // Schema types and utilities + type ResolvedSchema, + type JSONSchemaType, + type NormalizedType, + type ConstraintKey, + type ConstraintDirection, + type CompositionKeyword, + type MetadataKey, + type AnnotationKey, + type ContentKey, + type WalkerContext, + isSchemaObject, + isSchemaArray, + normalizeType, + arraysEqual, + deepEqual, + escapeJsonPointer, + joinPath, + CONSTRAINT_KEYS, + CONSTRAINT_DIRECTION, + COMPOSITION_KEYWORDS, + METADATA_KEYS, + ANNOTATION_KEYS, + CONTENT_KEYS, + DEFAULT_MAX_DEPTH, +} from '@contractual/differs.core'; + +// ============================================================================= +// Strands API Types +// ============================================================================= + +export type { + CompareResult, + CompareOptions, + StrandsTrace, + StrandsCompatibility, + StrandsVersion, + SemanticVersion, + JsonSchemaDraft, +} from './types.js'; diff --git a/packages/differs.json-schema/src/types.ts b/packages/differs.json-schema/src/types.ts new file mode 100644 index 0000000..4ca6e5a --- /dev/null +++ b/packages/differs.json-schema/src/types.ts @@ -0,0 +1,67 @@ +/** + * Strands API types for JSON Schema comparison + * + * These types are specific to the Strands-compatible comparison API. + * Core schema diffing types are in @contractual/differs.core. + */ + +/** + * Strands compatibility classification for a trace + */ +export type StrandsCompatibility = 'incompatible' | 'compatible' | 'unknown'; + +/** + * Strands version bump level + */ +export type StrandsVersion = 'equal' | 'patch' | 'minor' | 'major' | null; + +/** + * A single trace showing where schemas differ (Strands format) + */ +export interface StrandsTrace { + /** Compatibility status of this change */ + readonly compatibility: StrandsCompatibility; + /** JSON Pointer path in the source (left) schema, null if added */ + readonly left: string | null; + /** JSON Pointer path in the target (right) schema, null if removed */ + readonly right: string | null; +} + +/** + * Semantic version object + */ +export interface SemanticVersion { + readonly major: number; + readonly minor: number; + readonly patch: number; + readonly version: string; +} + +/** + * Strands API response format + */ +export interface CompareResult { + /** Version bump level, null if unknown/error */ + readonly version: StrandsVersion; + /** List of traces showing where schemas differ */ + readonly traces: StrandsTrace[]; + /** Computed new version based on current version + bump */ + readonly newVersion: SemanticVersion | null; + /** Error or warning message */ + readonly message?: string; +} + +/** + * Options for schema comparison + */ +export interface CompareOptions { + /** Current version to compute newVersion from (e.g., "1.0.0") */ + readonly currentVersion?: string; + /** Draft version for validation rules */ + readonly draft?: JsonSchemaDraft; +} + +/** + * Supported JSON Schema drafts + */ +export type JsonSchemaDraft = 'draft-07' | 'draft-2019-09' | 'draft-2020-12'; diff --git a/packages/differs.json-schema/tsconfig.build.json b/packages/differs.json-schema/tsconfig.build.json new file mode 100644 index 0000000..2333a75 --- /dev/null +++ b/packages/differs.json-schema/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "exclude": ["node_modules", "dist", "tests", "**/*.test.ts"] +} diff --git a/packages/differs.json-schema/tsconfig.json b/packages/differs.json-schema/tsconfig.json new file mode 100644 index 0000000..e666b16 --- /dev/null +++ b/packages/differs.json-schema/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "tests"] +} diff --git a/packages/differs.openapi/package.json b/packages/differs.openapi/package.json new file mode 100644 index 0000000..6f12ad4 --- /dev/null +++ b/packages/differs.openapi/package.json @@ -0,0 +1,44 @@ +{ + "name": "@contractual/differs.openapi", + "private": false, + "version": "0.1.0-dev.5", + "description": "OpenAPI 3.0/3.1 breaking change detection for Contractual", + "license": "MIT", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "repository": { + "type": "git", + "url": "https://github.com/contractual-dev/contractual.git", + "directory": "packages/differs.openapi" + }, + "engines": { + "node": ">=20.0.0" + }, + "scripts": { + "prebuild": "rimraf dist", + "build": "tsc -p tsconfig.build.json" + }, + "files": [ + "dist", + "README.md" + ], + "dependencies": { + "@contractual/differs.core": "workspace:*", + "@contractual/types": "workspace:*", + "@redocly/openapi-core": "^1.0.0" + }, + "devDependencies": { + "rimraf": "^5.0.5" + }, + "publishConfig": { + "access": "public", + "provenance": true + } +} diff --git a/packages/differs.openapi/src/differ.ts b/packages/differs.openapi/src/differ.ts new file mode 100644 index 0000000..2d0cc51 --- /dev/null +++ b/packages/differs.openapi/src/differ.ts @@ -0,0 +1,40 @@ +/** + * OpenAPI Differ + * + * Compares two OpenAPI specifications and detects breaking changes. + * Supports OpenAPI 3.0 and 3.1. + */ + +import type { DiffResult } from '@contractual/types'; +import { assembleResult } from '@contractual/differs.core'; +import { resolveOpenApiSpec } from './resolver.js'; +import { diffStructural } from './structural.js'; + +/** + * Diff two OpenAPI specification files + * + * @param oldSpecPath - Path to the old/base OpenAPI spec file + * @param newSpecPath - Path to the new/changed OpenAPI spec file + * @returns DiffResult with classified changes and suggested bump + */ +export async function diffOpenApi(oldSpecPath: string, newSpecPath: string): Promise { + const oldSpec = await resolveOpenApiSpec(oldSpecPath); + const newSpec = await resolveOpenApiSpec(newSpecPath); + + return diffOpenApiObjects(oldSpec, newSpec); +} + +/** + * Diff two resolved OpenAPI spec objects directly + * + * @param oldSpec - The old/base OpenAPI spec object (fully resolved) + * @param newSpec - The new/changed OpenAPI spec object (fully resolved) + * @returns DiffResult with classified changes and suggested bump + */ +export function diffOpenApiObjects( + oldSpec: Record, + newSpec: Record +): DiffResult { + const rawChanges = diffStructural(oldSpec, newSpec); + return assembleResult(rawChanges, newSpec); +} diff --git a/packages/differs.openapi/src/index.ts b/packages/differs.openapi/src/index.ts new file mode 100644 index 0000000..8ae0302 --- /dev/null +++ b/packages/differs.openapi/src/index.ts @@ -0,0 +1,3 @@ +export { diffOpenApi, diffOpenApiObjects } from './differ.js'; +export { resolveOpenApiSpec } from './resolver.js'; +export { diffStructural } from './structural.js'; diff --git a/packages/differs.openapi/src/resolver.ts b/packages/differs.openapi/src/resolver.ts new file mode 100644 index 0000000..4978e7a --- /dev/null +++ b/packages/differs.openapi/src/resolver.ts @@ -0,0 +1,42 @@ +/** + * OpenAPI spec resolver using @redocly/openapi-core + * + * Handles both OpenAPI 3.0 and 3.1 specifications. + * Uses Redocly to bundle external refs into a single document, + * then uses the core ref-resolver to dereference internal $refs + * (which handles circular references safely). + */ + +import { resolveRefs } from '@contractual/differs.core'; + +/** + * Resolve an OpenAPI spec file to a fully dereferenced object + * + * @param specPath - Absolute or relative path to the OpenAPI spec file + * @returns The fully resolved OpenAPI document as a plain object + */ +export async function resolveOpenApiSpec(specPath: string): Promise> { + let bundle: typeof import('@redocly/openapi-core').bundle; + let loadConfig: typeof import('@redocly/openapi-core').loadConfig; + + try { + const redocly = await import('@redocly/openapi-core'); + bundle = redocly.bundle; + loadConfig = redocly.loadConfig; + } catch { + throw new Error( + 'OpenAPI diffing requires @redocly/openapi-core. ' + + 'Install it with: npm install @redocly/openapi-core' + ); + } + + // Bundle only (no dereference) β€” resolves external refs into one document + // but keeps internal $refs intact to avoid circular object references + const config = await loadConfig(); + const result = await bundle({ ref: specPath, config }); + const bundled = result.bundle.parsed as Record; + + // Use our own ref-resolver which handles circular refs safely + const resolved = resolveRefs(bundled); + return resolved.schema as Record; +} diff --git a/packages/differs.openapi/src/structural.ts b/packages/differs.openapi/src/structural.ts new file mode 100644 index 0000000..49ce505 --- /dev/null +++ b/packages/differs.openapi/src/structural.ts @@ -0,0 +1,317 @@ +/** + * OpenAPI structural differ + * + * Compares the structural layer of two OpenAPI specs: + * paths, operations, parameters, request bodies, responses. + * Schema-level diffing is delegated to @contractual/differs.core walker. + */ + +import type { RawChange, ChangeType } from '@contractual/types'; +import { walk, escapeJsonPointer } from '@contractual/differs.core'; + +const HTTP_METHODS = ['get', 'put', 'post', 'delete', 'patch', 'options', 'head', 'trace'] as const; + +type PathsMap = Record>; +type OperationMap = Record; + +/** + * Diff the structural layer of two resolved OpenAPI specs + */ +export function diffStructural( + oldSpec: Record, + newSpec: Record +): RawChange[] { + const changes: RawChange[] = []; + + // Diff top-level info metadata + const oldInfo = (oldSpec.info ?? {}) as Record; + const newInfo = (newSpec.info ?? {}) as Record; + + if (oldInfo.description !== newInfo.description) { + changes.push({ + path: '/info/description', + type: 'description-changed' as ChangeType, + oldValue: oldInfo.description, + newValue: newInfo.description, + }); + } + + if (oldInfo.title !== newInfo.title) { + changes.push({ + path: '/info/title', + type: 'title-changed' as ChangeType, + oldValue: oldInfo.title, + newValue: newInfo.title, + }); + } + + const oldPaths = (oldSpec.paths ?? {}) as PathsMap; + const newPaths = (newSpec.paths ?? {}) as PathsMap; + + // Diff paths + for (const path of Object.keys(oldPaths)) { + const escapedPath = escapeJsonPointer(path); + + if (!(path in newPaths)) { + changes.push({ + path: `/paths/${escapedPath}`, + type: 'path-removed' as ChangeType, + oldValue: path, + }); + continue; + } + + // Diff operations within matching paths + changes.push(...diffOperations(oldPaths[path], newPaths[path], path)); + } + + for (const path of Object.keys(newPaths)) { + if (!(path in oldPaths)) { + const escapedPath = escapeJsonPointer(path); + changes.push({ + path: `/paths/${escapedPath}`, + type: 'path-added' as ChangeType, + newValue: path, + }); + } + } + + return changes; +} + +/** + * Diff operations (HTTP methods) within a path + */ +function diffOperations( + oldPathItem: Record, + newPathItem: Record, + apiPath: string +): RawChange[] { + const changes: RawChange[] = []; + const escapedPath = escapeJsonPointer(apiPath); + + for (const method of HTTP_METHODS) { + const oldOp = oldPathItem[method] as OperationMap | undefined; + const newOp = newPathItem[method] as OperationMap | undefined; + + if (oldOp && !newOp) { + changes.push({ + path: `/paths/${escapedPath}/${method}`, + type: 'operation-removed' as ChangeType, + oldValue: `${method.toUpperCase()} ${apiPath}`, + }); + } else if (!oldOp && newOp) { + changes.push({ + path: `/paths/${escapedPath}/${method}`, + type: 'operation-added' as ChangeType, + newValue: `${method.toUpperCase()} ${apiPath}`, + }); + } else if (oldOp && newOp) { + // Both exist β€” diff metadata, parameters, request body, responses + const basePath = `/paths/${escapedPath}/${method}`; + changes.push(...diffOperationMetadata(oldOp, newOp, basePath)); + changes.push(...diffParameters(oldOp, newOp, basePath)); + changes.push(...diffRequestBody(oldOp, newOp, basePath)); + changes.push(...diffResponses(oldOp, newOp, basePath)); + } + } + + return changes; +} + +/** + * Diff operation-level metadata (description, summary, tags, deprecated) + */ +function diffOperationMetadata( + oldOp: OperationMap, + newOp: OperationMap, + basePath: string +): RawChange[] { + const changes: RawChange[] = []; + + // Description changed (patch) + if (oldOp.description !== newOp.description) { + changes.push({ + path: `${basePath}/description`, + type: 'description-changed' as ChangeType, + oldValue: oldOp.description, + newValue: newOp.description, + }); + } + + // Summary changed (patch β€” treated as title) + if (oldOp.summary !== newOp.summary) { + changes.push({ + path: `${basePath}/summary`, + type: 'title-changed' as ChangeType, + oldValue: oldOp.summary, + newValue: newOp.summary, + }); + } + + // Deprecated changed (patch) + if (oldOp.deprecated !== newOp.deprecated) { + changes.push({ + path: `${basePath}/deprecated`, + type: 'deprecated-changed' as ChangeType, + oldValue: oldOp.deprecated, + newValue: newOp.deprecated, + }); + } + + return changes; +} + +/** + * Diff parameters between two operations + */ +function diffParameters(oldOp: OperationMap, newOp: OperationMap, basePath: string): RawChange[] { + const changes: RawChange[] = []; + const oldParams = (oldOp.parameters ?? []) as Array>; + const newParams = (newOp.parameters ?? []) as Array>; + + // Index by "in+name" for matching + const oldByKey = new Map(oldParams.map((p) => [`${p.in}:${p.name}`, p])); + const newByKey = new Map(newParams.map((p) => [`${p.in}:${p.name}`, p])); + + for (const [key, oldParam] of oldByKey) { + if (!newByKey.has(key)) { + changes.push({ + path: `${basePath}/parameters/${oldParam.name}`, + type: 'parameter-removed' as ChangeType, + oldValue: key, + }); + } else { + const newParam = newByKey.get(key)!; + + // Check required change + if (oldParam.required !== newParam.required) { + changes.push({ + path: `${basePath}/parameters/${oldParam.name}/required`, + type: 'parameter-required-changed' as ChangeType, + oldValue: oldParam.required, + newValue: newParam.required, + }); + } + + // Diff parameter schemas using core walker + if (oldParam.schema && newParam.schema) { + const schemaChanges = walk( + oldParam.schema, + newParam.schema, + `${basePath}/parameters/${oldParam.name}/schema` + ); + changes.push(...schemaChanges); + } + } + } + + for (const [key, newParam] of newByKey) { + if (!oldByKey.has(key)) { + // Required parameter added = breaking, optional = non-breaking + // Store required flag in newValue so classifiers can check + changes.push({ + path: `${basePath}/parameters/${newParam.name}`, + type: newParam.required + ? ('parameter-required-added' as ChangeType) + : ('parameter-added' as ChangeType), + newValue: key, + }); + } + } + + return changes; +} + +/** + * Extract the primary schema from a content map (prefers application/json) + */ +function extractSchema(content: Record | undefined): unknown { + if (!content) return undefined; + + // Prefer application/json, then first available + const jsonContent = content['application/json'] as Record | undefined; + if (jsonContent?.schema) return jsonContent.schema; + + for (const mediaType of Object.values(content)) { + const mt = mediaType as Record; + if (mt?.schema) return mt.schema; + } + + return undefined; +} + +/** + * Diff request bodies between two operations + */ +function diffRequestBody(oldOp: OperationMap, newOp: OperationMap, basePath: string): RawChange[] { + const changes: RawChange[] = []; + const oldBody = oldOp.requestBody as Record | undefined; + const newBody = newOp.requestBody as Record | undefined; + + if (oldBody && !newBody) { + changes.push({ + path: `${basePath}/requestBody`, + type: 'request-body-removed' as ChangeType, + }); + } else if (!oldBody && newBody) { + changes.push({ + path: `${basePath}/requestBody`, + type: 'request-body-added' as ChangeType, + }); + } else if (oldBody && newBody) { + const oldSchema = extractSchema(oldBody.content as Record); + const newSchema = extractSchema(newBody.content as Record); + + if (oldSchema && newSchema) { + const schemaChanges = walk(oldSchema, newSchema, `${basePath}/requestBody/content/schema`); + changes.push(...schemaChanges); + } + } + + return changes; +} + +/** + * Diff responses between two operations + */ +function diffResponses(oldOp: OperationMap, newOp: OperationMap, basePath: string): RawChange[] { + const changes: RawChange[] = []; + const oldResponses = (oldOp.responses ?? {}) as Record>; + const newResponses = (newOp.responses ?? {}) as Record>; + + for (const statusCode of Object.keys(oldResponses)) { + if (!(statusCode in newResponses)) { + changes.push({ + path: `${basePath}/responses/${statusCode}`, + type: 'response-removed' as ChangeType, + oldValue: statusCode, + }); + } else { + // Diff response schemas + const oldSchema = extractSchema(oldResponses[statusCode].content as Record); + const newSchema = extractSchema(newResponses[statusCode].content as Record); + + if (oldSchema && newSchema) { + const schemaChanges = walk( + oldSchema, + newSchema, + `${basePath}/responses/${statusCode}/content/schema` + ); + changes.push(...schemaChanges); + } + } + } + + for (const statusCode of Object.keys(newResponses)) { + if (!(statusCode in oldResponses)) { + changes.push({ + path: `${basePath}/responses/${statusCode}`, + type: 'response-added' as ChangeType, + newValue: statusCode, + }); + } + } + + return changes; +} diff --git a/packages/differs.openapi/tsconfig.build.json b/packages/differs.openapi/tsconfig.build.json new file mode 100644 index 0000000..2333a75 --- /dev/null +++ b/packages/differs.openapi/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "exclude": ["node_modules", "dist", "tests", "**/*.test.ts"] +} diff --git a/packages/differs.openapi/tsconfig.json b/packages/differs.openapi/tsconfig.json new file mode 100644 index 0000000..e666b16 --- /dev/null +++ b/packages/differs.openapi/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "tests"] +} diff --git a/packages/generators/contract/.eslintrc b/packages/generators/contract/.eslintrc deleted file mode 100644 index 3280263..0000000 --- a/packages/generators/contract/.eslintrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../.eslintrc" -} \ No newline at end of file diff --git a/packages/generators/contract/contract.template.hbs b/packages/generators/contract/contract.template.hbs deleted file mode 100644 index 4a96b12..0000000 --- a/packages/generators/contract/contract.template.hbs +++ /dev/null @@ -1,56 +0,0 @@ -import { z } from 'zod'; -import type { AppRouter } from '@ts-rest/core'; -import { initContract } from '@ts-rest/core'; - -{{#if imports}} - {{#each imports}} - import { {{{@key}}} } from "./{{{this}}}" - {{/each}} -{{/if}} - -{{#if schemas}} - {{#each schemas}} - export const {{@key}} = {{{this}}}; - - {{/each}} -{{/if}} - -export const ApiContract = { -{{#each endpoints}} - {{alias}}: { - method: '{{toUpperCase method}}' as const, - path: "{{path}}", - {{#if description}} - description: `{{description}}`, - {{/if}} - {{#if parameters}} - {{#includesType parameters "Path"}} - pathParams: z.object({ - {{#each parameters}}{{#ifeq type "Path"}}'{{name}}': {{{schema}}},{{/ifeq}}{{/each}} - }), - {{/includesType}} - {{#includesType parameters "Query"}} - query: z.object({ - {{#each parameters}}{{#ifeq type "Query"}}'{{name}}': {{{schema}}},{{/ifeq}}{{/each}} - }), - {{/includesType}} - {{#includesType parameters "Body"}} - body: {{#each parameters}}{{#ifeq type "Body"}}{{{schema}}}{{/ifeq}}{{/each}}, - {{/includesType}} - {{/if}} - responses: { - {{#each responses}} - [{{statusCode}}]: {{{schema}}}, - {{/each}} - }, - }, -{{/each}} -} satisfies AppRouter; - -export const ApiOperations = { -{{#each endpoints}} - '{{toPlainWords alias}}': '{{alias}}', -{{/each}} -} satisfies Record; - -export const contract = initContract().router(ApiContract); diff --git a/packages/generators/contract/index.ts b/packages/generators/contract/index.ts deleted file mode 100644 index 8420b10..0000000 --- a/packages/generators/contract/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './src'; diff --git a/packages/generators/contract/package.json b/packages/generators/contract/package.json deleted file mode 100644 index 8f12758..0000000 --- a/packages/generators/contract/package.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "name": "@contractual/generators.contract", - "private": false, - "version": "0.0.0", - "license": "MIT", - "type": "module", - "module": "dist/index.js", - "main": "dist/index.js", - "repository": { - "type": "git", - "url": "https://github.com/contractual-dev/contractual.git", - "directory": "packages/generators/client" - }, - "exports": { - ".": { - "import": "./dist/index.js", - "require": "./dist/index.js" - } - }, - "homepage": "https://contractual.dev", - "bugs": { - "url": "https://github.com/contractual-dev/contractual/issues" - }, - "contributors": [ - { - "name": "Omer Morad", - "email": "omer.moradd@gmail.com" - } - ], - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/contractual-dev" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/contractual-dev" - } - ], - "engines": { - "node": ">=18.12.0" - }, - "scripts": { - "prebuild": "pnpm rimraf dist", - "build": "tsc -p tsconfig.build.json", - "build:watch": "tsc -p tsconfig.build.json --watch", - "tester": "jest --coverage --verbose", - "lint": "eslint '{src,test}/**/*.ts'" - }, - "files": [ - "client.template.hbs", - "dist", - "README.md" - ], - "dependencies": { - "@apidevtools/swagger-parser": "^10.1.1", - "chalk": "^5.4.1", - "handlebars": "^4.7.8", - "openapi-types": "^12.1.3", - "openapi-zod-client": "^1.18.2", - "openapi3-ts": "^4.4.0" - }, - "devDependencies": { - "ora": "^8.1.1", - "typescript": "~5.7.2" - }, - "peerDependencies": { - "typescript": ">=5.x" - }, - "publishConfig": { - "access": "public", - "provenance": true - } -} diff --git a/packages/generators/contract/src/command.ts b/packages/generators/contract/src/command.ts deleted file mode 100644 index 05ac4aa..0000000 --- a/packages/generators/contract/src/command.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type ora from 'ora'; -import { ContractCommandHandler, ContractFileGenerator, FileSystemHandler, SpinnerFactory } from './generator.js'; -import * as fs from 'node:fs'; -import SwaggerParser from '@apidevtools/swagger-parser'; -import { generateZodClientFromOpenAPI } from 'openapi-zod-client'; -import { createProgram } from 'typescript'; -import type chalk from 'chalk'; - -export const createContractCommandHandler = ( - spinner: typeof ora, - chalker: typeof chalk, - logger: Console, - apiSpecPath: string -) => { - const spinnerFactory = new SpinnerFactory(spinner); - - const fileGenerator = new ContractFileGenerator( - spinnerFactory, - generateZodClientFromOpenAPI, - createProgram, - new FileSystemHandler(fs) - ); - - return new ContractCommandHandler( - spinnerFactory, - new FileSystemHandler(fs), - SwaggerParser, - fileGenerator, - logger, - chalker, - apiSpecPath - ); -}; diff --git a/packages/generators/contract/src/generator.ts b/packages/generators/contract/src/generator.ts deleted file mode 100644 index d98173c..0000000 --- a/packages/generators/contract/src/generator.ts +++ /dev/null @@ -1,174 +0,0 @@ -import type SwaggerParser from '@apidevtools/swagger-parser'; -import * as path from 'node:path'; -import type { generateZodClientFromOpenAPI } from 'openapi-zod-client'; -import type { OpenAPIObject } from 'openapi3-ts/oas30'; -import * as process from 'node:process'; -import type { createProgram } from 'typescript'; -import { ModuleKind, ModuleResolutionKind, ScriptTarget } from 'typescript'; -import { createHandlebars } from './handlebars-helpers.js'; -import type ora from 'ora'; -import type * as fs from 'node:fs'; -import type chalk from 'chalk'; - -export class SpinnerFactory { - public constructor(private readonly spinner: typeof ora) {} - - public createSpinner(text: string) { - return this.spinner(text); - } -} - -export class FileSystemHandler { - public constructor(private readonly fileSystem: typeof fs) {} - - public exists(path: string): boolean { - return this.fileSystem.existsSync(path); - } - - public removeFile(path: string): void { - this.fileSystem.rmSync(path); - } -} - -export class ContractFileGenerator { - public constructor( - private readonly spinnerFactory: SpinnerFactory, - private readonly zodGenerator: typeof generateZodClientFromOpenAPI, - private readonly programFactory: typeof createProgram, - private readonly fileSystemHandler: FileSystemHandler - ) {} - - public async generateFiles(openapiDocument: OpenAPIObject, destinationPath: string) { - const spinner = this.spinnerFactory.createSpinner('Generating contract files..').start(); - const indexTsDestinationPath = path.resolve(destinationPath, 'index.ts'); - - try { - await this.zodGenerator({ - distPath: indexTsDestinationPath, - openApiDoc: openapiDocument, - templatePath: path.resolve( - path.dirname(new URL(import.meta.url).pathname), - '..', - 'contract.template.hbs' - ), - prettierConfig: { - tabWidth: 2, - semi: true, - singleQuote: true, - trailingComma: 'all', - bracketSpacing: true, - arrowParens: 'always', - }, - options: { - additionalPropertiesDefaultValue: false, - withAllResponses: true, - withAlias: true, - withDescription: false, - withDefaultValues: false, - }, - handlebars: createHandlebars(), - }); - - spinner.succeed('Generated Contractual contract'); - return indexTsDestinationPath; - } catch (error) { - spinner.fail('Failed to generate contract files.'); - throw error; - } - } - - public async compileFiles(destinationPath: string) { - const spinner = this.spinnerFactory.createSpinner('Compiling contract..').start(); - const indexTsDestinationPath = path.resolve(destinationPath, 'index.ts'); - - try { - this.programFactory([indexTsDestinationPath], { - module: ModuleKind.ESNext, - target: ScriptTarget.ESNext, - skipLibCheck: true, - declaration: true, - noImplicitAny: true, - moduleResolution: ModuleResolutionKind.Bundler, - outDir: destinationPath, - }).emit(); - - spinner.succeed('Compilation completed successfully.'); - this.fileSystemHandler.removeFile(indexTsDestinationPath); - } catch (error) { - spinner.fail('Failed to compile contract.'); - throw error; - } - } -} - -export class ContractCommandHandler { - public constructor( - private readonly spinnerFactory: SpinnerFactory, - private readonly fileSystemHandler: FileSystemHandler, - private readonly swaggerParser: typeof SwaggerParser, - private readonly fileGenerator: ContractFileGenerator, - private readonly logger: Console, - private readonly chalker: typeof chalk, - private readonly apiSpecPathToReadFrom: string - ) {} - - public async handle() { - try { - if (!this.fileSystemHandler.exists(this.apiSpecPathToReadFrom)) { - this.logError( - 'Could not find Contractual schema that is required for this command. You can provide it with `--spec` argument' - ); - - return; - } - - this.logger.log( - this.chalker.gray(`Contractual schema loaded from ${this.apiSpecPathToReadFrom}`) - ); - - const openapiDocument = await this.parseSpec(this.apiSpecPathToReadFrom); - - const destinationPath = path.resolve( - path.dirname(new URL(import.meta.url).pathname), - '../../..', - 'contract/contract' - ); - - await this.fileGenerator.generateFiles(openapiDocument, destinationPath); - await this.fileGenerator.compileFiles(destinationPath); - - this.logger.log(this.chalker.green('Done')); - } catch (error) { - this.logError('Error occurred during contract generation.', error); - } - } - - private async parseSpec(apiSpecPath: string): Promise { - const spinner = this.spinnerFactory.createSpinner('Parsing TypeSpec file..').start(); - - return this.swaggerParser - .parse(apiSpecPath) - .then((document) => { - if (!('openapi' in document)) { - throw new Error('Invalid OpenAPI document: Missing "openapi" property.'); - } - - spinner.succeed('TypeSpec file parsed successfully.'); - - return document as OpenAPIObject; - }) - .catch((error) => { - spinner.fail('Failed to parse TypeSpec file.'); - - throw error instanceof Error ? new Error(error.message) : error; - }); - } - - private logError(message: string, error?: unknown) { - this.logger.log(`${this.chalker.red('Error')}: ${message}`); - - if (error) { - this.logger.error('Error details:', error); - } - } -} diff --git a/packages/generators/contract/src/handlebars-helpers.ts b/packages/generators/contract/src/handlebars-helpers.ts deleted file mode 100644 index 506c2e3..0000000 --- a/packages/generators/contract/src/handlebars-helpers.ts +++ /dev/null @@ -1,51 +0,0 @@ -import handlebars from 'handlebars'; - -const { create } = handlebars; - -export function createHandlebars() { - const instance = create(); - - instance.registerHelper('ifNotEmptyObj', function (obj, options) { - if (typeof obj === 'object' && Object.keys(obj).length > 0) { - return options.fn(this); - } - - return options.inverse(this); - }); - - instance.registerHelper('toUpperCase', (input: string) => input.toUpperCase()); - - instance.registerHelper('ifNotEq', function (a, b, options) { - if (a !== b) { - return options.fn(this); - } - - return options.inverse(this); - }); - - instance.registerHelper('ifeq', function (a, b, options) { - if (a === b) { - return options.fn(this); - } - - return options.inverse(this); - }); - - instance.registerHelper('includesType', function (arr, val, options) { - if (Array.isArray(arr) && arr.length > 0 && arr.some((v) => v.type === val)) { - return options.fn(this); - } - - return options.inverse(this); - }); - - instance.registerHelper('toPlainWords', function (str) { - return str.replace(/([A-Z])/g, ' $1').toLowerCase(); - }); - - instance.registerHelper('toDashes', function (str) { - return str.replace(/([A-Z])/g, '-$1').toLowerCase(); - }); - - return instance; -} diff --git a/packages/generators/contract/src/index.ts b/packages/generators/contract/src/index.ts deleted file mode 100644 index 4e2ea87..0000000 --- a/packages/generators/contract/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './command.js'; diff --git a/packages/generators/contract/tsconfig.build.json b/packages/generators/contract/tsconfig.build.json deleted file mode 100644 index 0bc65ae..0000000 --- a/packages/generators/contract/tsconfig.build.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "../../../tsconfig.build.json", - "compilerOptions": { - "rootDir": "src", - "baseUrl": ".", - "outDir": "dist", - "skipLibCheck": true - }, - "exclude": [ - "dist", - "index.ts", - "node_modules", - "test", - "**/*.spec.ts", - "jest.config.ts" - ] -} \ No newline at end of file diff --git a/packages/generators/contract/tsconfig.json b/packages/generators/contract/tsconfig.json deleted file mode 100644 index b027bb4..0000000 --- a/packages/generators/contract/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../tsconfig.json" -} \ No newline at end of file diff --git a/packages/generators/diff/.eslintrc b/packages/generators/diff/.eslintrc deleted file mode 100644 index 3280263..0000000 --- a/packages/generators/diff/.eslintrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../.eslintrc" -} \ No newline at end of file diff --git a/packages/generators/diff/index.ts b/packages/generators/diff/index.ts deleted file mode 100644 index 8420b10..0000000 --- a/packages/generators/diff/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './src'; diff --git a/packages/generators/diff/package.json b/packages/generators/diff/package.json deleted file mode 100644 index f09cb42..0000000 --- a/packages/generators/diff/package.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "name": "@contractual/generators.diff", - "private": false, - "version": "0.0.0", - "license": "MIT", - "type": "module", - "module": "dist/index.js", - "main": "dist/index.js", - "repository": { - "type": "git", - "url": "https://github.com/contractual-dev/contractual.git", - "directory": "packages/generators/diff" - }, - "exports": { - ".": { - "import": "./dist/index.js", - "require": "./dist/index.js" - } - }, - "homepage": "https://contractual.dev", - "bugs": { - "url": "https://github.com/contractual-dev/contractual/issues" - }, - "contributors": [ - { - "name": "Omer Morad", - "email": "omer.moradd@gmail.com" - } - ], - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/contractual-dev" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/contractual-dev" - } - ], - "engines": { - "node": ">=18.12.0" - }, - "scripts": { - "prebuild": "pnpm rimraf dist", - "build": "tsc -p tsconfig.build.json", - "test": "pnpm vitest run --watch", - "build:watch": "tsc -p tsconfig.build.json --watch", - "lint": "eslint '{src,test}/**/*.ts'" - }, - "files": [ - "dist", - "README.md" - ], - "publishConfig": { - "access": "public", - "provenance": true - }, - "dependencies": { - "chalk": "^5.4.1", - "openapi-diff": "^0.23.7", - "openapi-types": "^12.1.3", - "semver": "^7.6.3", - "table": "^6.9.0" - }, - "devDependencies": { - "@types/semver": "^7.5.8", - "@vitest/coverage-c8": "^0.33.0", - "typescript": "~5.7.2", - "vitest": "^3.0.3" - } -} diff --git a/packages/generators/diff/src/diff-highlighter.ts b/packages/generators/diff/src/diff-highlighter.ts deleted file mode 100644 index 58a281b..0000000 --- a/packages/generators/diff/src/diff-highlighter.ts +++ /dev/null @@ -1,77 +0,0 @@ -import chalk from 'chalk'; -import { table } from 'table'; -import type OpenApiDiff from 'openapi-diff'; -import { type DiffOutcome } from 'openapi-diff'; -import type { TableUserConfig } from 'table/dist/src/types/api.js'; - -async function printOpenApiDiff(diffOutcome: DiffOutcome) { - try { - if ('breakingDifferencesFound' in diffOutcome && diffOutcome.breakingDifferencesFound) { - console.log(chalk.red.bold('\nBreaking Differences Found:\n')); - console.log(formatDiffs(diffOutcome.breakingDifferences, chalk.red)); - } - - if (diffOutcome.nonBreakingDifferences.length > 0) { - console.log(chalk.green.bold('\nNon-Breaking Differences:\n')); - console.log(formatDiffs(diffOutcome.nonBreakingDifferences, chalk.green)); - } - - if (diffOutcome.unclassifiedDifferences.length > 0) { - console.log(chalk.yellow.bold('\nUnclassified Differences:\n')); - console.log(formatDiffs(diffOutcome.unclassifiedDifferences, chalk.yellow)); - } - - if ( - !('breakingDifferencesFound' in diffOutcome) || - (!diffOutcome.breakingDifferencesFound && - diffOutcome.nonBreakingDifferences.length === 0 && - diffOutcome.unclassifiedDifferences.length === 0) - ) { - console.log( - chalk.green.bold('\nNo Differences Found! Specifications are identical or compatible.\n') - ); - } - } catch (error) { - console.error(chalk.red.bold('\nError performing diff:\n')); - console.error(error); - } -} - -function formatDiffs( - diffs: OpenApiDiff.DiffResult[], - colorFn: (text: string) => string -): string { - const formattedDiffs = diffs.map((diff) => [ - colorFn(diff.code), - colorFn(diff.entity), - chalk.bold(diff.action === 'add' ? 'βž• Add' : '❌ Remove'), - diff.sourceSpecEntityDetails.map((d) => `${d.location}`).join(', '), - diff.destinationSpecEntityDetails.map((d) => `${d.location}`).join(', '), - ]); - - const tableConfig: TableUserConfig = { - columns: { - 0: { alignment: 'left', width: 20 }, - 1: { alignment: 'left', width: 20 }, - 2: { alignment: 'center', width: 10 }, - 3: { alignment: 'left', width: 30 }, - 4: { alignment: 'left', width: 30 }, - }, - }; - - return table( - [ - [ - chalk.underline.bold('Code'), - chalk.underline.bold('Entity'), - chalk.underline.bold('Action'), - chalk.underline.bold('Source Location'), - chalk.underline.bold('Destination Location'), - ], - ...formattedDiffs, - ], - tableConfig - ); -} - -export { printOpenApiDiff }; diff --git a/packages/generators/diff/src/generator.spec.ts b/packages/generators/diff/src/generator.spec.ts deleted file mode 100644 index 324ad41..0000000 --- a/packages/generators/diff/src/generator.spec.ts +++ /dev/null @@ -1,886 +0,0 @@ -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; -import * as fs from 'node:fs'; -import * as path from 'node:path'; -import { diffSpecs } from './generator'; - -const dummyOpenApiSpecBreaking = ` -openapi: 3.0.0 -info: - title: Petstore API - description: A simple API for managing a pet store. - version: 0.0.0 -tags: [] -paths: - /pet: - post: - operationId: addPet - description: Add a new pet to the store. - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/CreatePetResponse' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AddPetBody' - /pet/{petId}: - get: - operationId: getPetById - description: Retrieve a pet by ID. - parameters: - - name: petId - in: path - required: true - schema: - type: integer - format: int32 - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/GetPetResponse' - /store/order: - post: - operationId: placeOrder - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/CreateOrderResponse' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOrderBody' - /store/order/{orderId}: - get: - operationId: getOrderById - description: Retrieve an order by ID. - parameters: - - name: orderId - in: path - required: true - schema: - type: integer - format: int32 - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/GetOrderResponse' - /user: - post: - operationId: createUser - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/CreateUserResponse' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateUserBody' - /user/{username}: - get: - operationId: getUserByUsername - description: Retrieve a user by username. - parameters: - - name: username - in: path - required: true - schema: - type: string - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/GetUserResponse' -components: - schemas: - AddPetBody: - type: object - required: - - name - properties: - name: - type: string - description: Name of the pet. - category: - type: string - description: Category of the pet (e.g., dog, cat, bird). - tags: - type: array - items: - type: string - description: List of tags associated with the pet. - status: - type: string - enum: - - available - - pending - - sold - description: The current status of the pet in the store. - description: Request body for adding a new pet. - ApiResponse: - type: object - required: - - code - - type - - message - properties: - code: - type: integer - format: int32 - description: Status code of the response. - type: - type: string - description: The type of response (e.g., success, error). - message: - type: string - description: Detailed message about the response. - description: Represents a standard API response for messages or errors. - CreateOrderResponse: - type: object - required: - - id - properties: - id: - type: integer - format: int32 - description: Unique identifier of the created order. - description: Response for creating an order. - CreatePetResponse: - type: object - required: - - id - properties: - id: - type: integer - format: int32 - description: Unique identifier of the created pet. - description: Response for creating a pet. - CreateUserBody: - type: object - required: - - username - - firstName - - lastName - - email - - password - - phone - properties: - username: - type: string - description: Username of the user. - firstName: - type: string - description: First name of the user. - lastName: - type: string - description: Last name of the user. - email: - type: string - description: Email address of the user. - password: - type: string - description: Password for the user account. - phone: - type: string - description: Phone number of the user. - userStatus: - type: integer - format: int32 - description: User status (e.g., 1 for active, 0 for inactive). - description: Create a new user. - CreateUserResponse: - type: object - required: - - id - properties: - id: - type: integer - format: int32 - description: Unique identifier of the created user. - description: Response for creating a user. - GetOrderResponse: - type: object - required: - - id - - petId - - status - - quantity - properties: - id: - type: integer - format: int32 - description: Unique identifier of the order. - petId: - type: integer - format: int32 - description: Identifier of the pet associated with the order. - status: - type: string - enum: - - placed - - approved - - delivered - description: The current status of the order. - quantity: - type: integer - format: int32 - description: Quantity of pets ordered. - description: Response for retrieving an order. - GetPetResponse: - type: object - required: - - id - - name - - status - properties: - id: - type: integer - format: int32 - description: Unique identifier of the pet. - name: - type: string - description: Name of the pet. - status: - type: string - enum: - - available - - pending - - sold - description: The current status of the pet in the store. - description: Response for retrieving a pet. - GetUserResponse: - type: object - required: - - id - - username - - email - - phone - properties: - id: - type: integer - format: int32 - description: Unique identifier of the user. - username: - type: string - description: Username associated with the user. - email: - type: string - description: Email address of the user. - phone: - type: string - description: Phone number of the user. - description: Response for retrieving a user. - Order: - type: object - required: - - id - - petId - - quantity - - shipDate - - status - properties: - id: - type: integer - format: int32 - description: Unique identifier for the order. - petId: - type: integer - format: int32 - description: ID of the pet being ordered. - quantity: - type: integer - format: int32 - description: Quantity of pets ordered. - shipDate: - type: string - description: Shipping date for the order. - status: - type: string - enum: - - placed - - approved - - delivered - description: The current status of the order. - complete: - type: boolean - description: Indicates whether the order is complete. - description: Represents an order for purchasing a pet. - Pet: - type: object - required: - - id - - name - properties: - id: - type: integer - format: int32 - description: Unique identifier for the pet. - name: - type: string - description: Name of the pet. - category: - type: string - description: Category of the pet (e.g., dog, cat, bird). - tags: - type: array - items: - type: string - description: List of tags associated with the pet. - status: - type: string - enum: - - available - - pending - - sold - description: The current status of the pet in the store. - description: Represents a pet in the store. - PlaceOrderBody: - type: object - required: - - petId - - quantity - - shipDate - - status - properties: - petId: - type: integer - format: int32 - description: ID of the pet being ordered. - quantity: - type: integer - format: int32 - description: Quantity of pets ordered. - shipDate: - type: string - description: Shipping date for the order. - status: - type: string - enum: - - placed - - approved - - delivered - description: The current status of the order. - complete: - type: boolean - description: Indicates whether the order is complete. - description: Place an order for a pet. - User: - type: object - required: - - id - - username - - firstName - - lastName - - email - - password - - phone - properties: - id: - type: integer - format: int32 - description: Unique identifier for the user. - username: - type: string - description: Username of the user. - firstName: - type: string - description: First name of the user. - lastName: - type: string - description: Last name of the user. - email: - type: string - description: Email address of the user. - password: - type: string - description: Password for the user account. - phone: - type: string - description: Phone number of the user. - userStatus: - type: integer - format: int32 - description: User status (e.g., 1 for active, 0 for inactive). - description: Represents a user of the pet store. -`; - -const dummyOpenApiSpecMinor = ` -openapi: 3.0.0 -info: - title: Petstore API - description: A simple API for managing a pet store. - version: 0.0.0 -tags: [] -paths: - /pet: - post: - operationId: addPet - description: Add a new pet to the store. - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/CreatePetResponse' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AddPetBody' - /pet/{petId}: - get: - operationId: getPetById - description: Retrieve a pet by ID. - parameters: - - name: petId - in: path - required: true - schema: - type: integer - format: int32 - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/GetPetResponse' - /store/order: - post: - operationId: placeOrder - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/CreateOrderResponse' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOrderBody' - /store/order/{orderId}: - get: - operationId: getOrderById - description: Retrieve an order by ID. - parameters: - - name: orderId - in: path - required: true - schema: - type: integer - format: int32 - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/GetOrderResponse' - /user: - post: - operationId: createUser - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/CreateUserResponse' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateUserBody' - /user/{username}: - get: - operationId: getUserByUsername - description: Retrieve a user by username. - parameters: - - name: username - in: path - required: true - schema: - type: string - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/GetUserResponse' -components: - schemas: - AddPetBody: - type: object - required: - - name - properties: - name: - type: string - description: Name of the pet. - category: - type: string - description: Category of the pet (e.g., dog, cat, bird). - tags: - type: array - items: - type: string - description: List of tags associated with the pet. - status: - type: string - enum: - - available - - pending - - sold - description: The current status of the pet in the store. - description: Request body for adding a new pet. - ApiResponse: - type: object - required: - - code - - type - - message - properties: - code: - type: integer - format: int32 - description: Status code of the response. - type: - type: string - description: The type of response (e.g., success, error). - message: - type: string - description: Detailed message about the response. - description: Represents a standard API response for messages or errors. - CreateOrderResponse: - type: object - required: - - id - properties: - id: - type: integer - format: int32 - description: Unique identifier of the created order. - description: Response for creating an order. - CreatePetResponse: - type: object - required: - - id - properties: - id: - type: integer - format: int32 - description: Unique identifier of the created pet. - description: Response for creating a pet. - CreateUserBody: - type: object - required: - - username - - firstName - - lastName - - email - - password - - phone - properties: - username: - type: string - description: Username of the user. - firstName: - type: string - description: First name of the user. - lastName: - type: string - description: Last name of the user. - email: - type: string - description: Email address of the user. - password: - type: string - description: Password for the user account. - phone: - type: string - description: Phone number of the user. - userStatus: - type: integer - format: int32 - description: User status (e.g., 1 for active, 0 for inactive). - description: Create a new user. - CreateUserResponse: - type: object - required: - - id - properties: - id: - type: integer - format: int32 - description: Unique identifier of the created user. - description: Response for creating a user. - GetOrderResponse: - type: object - required: - - id - - petId - - status - - quantity - properties: - id: - type: integer - format: int32 - description: Unique identifier of the order. - petId: - type: integer - format: int32 - description: Identifier of the pet associated with the order. - status: - type: string - enum: - - placed - - approved - - delivered - description: The current status of the order. - quantity: - type: integer - format: int32 - description: Quantity of pets ordered. - description: Response for retrieving an order. - GetPetResponse: - type: object - required: - - id - - name - - status - properties: - id: - type: integer - format: int32 - description: Unique identifier of the pet. - name: - type: string - description: Name of the pet. - status: - type: string - enum: - - available - - pending - - sold - description: The current status of the pet in the store. - category: - type: string - description: Category of the pet (e.g., dog, cat, bird). - nickname: - type: string - description: Category of the pet (e.g., dog, cat, bird). - description: Response for retrieving a pet. - GetUserResponse: - type: object - required: - - id - - username - - email - - phone - properties: - id: - type: integer - format: int32 - description: Unique identifier of the user. - username: - type: string - description: Username associated with the user. - email: - type: string - description: Email address of the user. - phone: - type: string - description: Phone number of the user. - description: Response for retrieving a user. - Order: - type: object - required: - - id - - petId - - quantity - - shipDate - - status - properties: - id: - type: integer - format: int32 - description: Unique identifier for the order. - petId: - type: integer - format: int32 - description: ID of the pet being ordered. - quantity: - type: integer - format: int32 - description: Quantity of pets ordered. - shipDate: - type: string - description: Shipping date for the order. - status: - type: string - enum: - - placed - - approved - - delivered - description: The current status of the order. - complete: - type: boolean - description: Indicates whether the order is complete. - description: Represents an order for purchasing a pet. - Pet: - type: object - required: - - id - - name - properties: - id: - type: integer - format: int32 - description: Unique identifier for the pet. - name: - type: string - description: Name of the pet. - category: - type: string - description: Category of the pet (e.g., dog, cat, bird). - tags: - type: array - items: - type: string - description: List of tags associated with the pet. - status: - type: string - enum: - - available - - pending - - sold - description: The current status of the pet in the store. - description: Represents a pet in the store. - PlaceOrderBody: - type: object - required: - - petId - - quantity - - shipDate - - status - properties: - petId: - type: integer - format: int32 - description: ID of the pet being ordered. - quantity: - type: integer - format: int32 - description: Quantity of pets ordered. - shipDate: - type: string - description: Shipping date for the order. - status: - type: string - enum: - - placed - - approved - - delivered - description: The current status of the order. - complete: - type: boolean - description: Indicates whether the order is complete. - description: Place an order for a pet. - User: - type: object - required: - - id - - username - - firstName - - lastName - - email - - password - - phone - properties: - id: - type: integer - format: int32 - description: Unique identifier for the user. - username: - type: string - description: Username of the user. - firstName: - type: string - description: First name of the user. - lastName: - type: string - description: Last name of the user. - email: - type: string - description: Email address of the user. - password: - type: string - description: Password for the user account. - phone: - type: string - description: Phone number of the user. - userStatus: - type: integer - format: int32 - description: User status (e.g., 1 for active, 0 for inactive). - description: Represents a user of the pet store. -`; - -describe('diffSpecs', () => { - const tempDir = path.resolve(new URL('.', import.meta.url).pathname, 'temp'); - const spec1Path = path.join(tempDir, 'spec1.yaml'); - const spec2Path = path.join(tempDir, 'spec2.yaml'); - - beforeAll(() => { - if (!fs.existsSync(tempDir)) { - fs.mkdirSync(tempDir); - } - }); - - afterAll(() => { - fs.rmSync(tempDir, { force: true, recursive: true }); - }); - - it.only('should detect no changes between identical specs', async () => { - fs.writeFileSync(spec1Path, dummyOpenApiSpecBreaking); - fs.writeFileSync(spec2Path, dummyOpenApiSpecMinor); - const result = await diffSpecs(spec2Path, spec1Path); - expect(result.version).toBe('minor'); - expect(result.diff).toEqual([]); - }); -}); diff --git a/packages/generators/diff/src/generator.ts b/packages/generators/diff/src/generator.ts deleted file mode 100644 index 97635f7..0000000 --- a/packages/generators/diff/src/generator.ts +++ /dev/null @@ -1,35 +0,0 @@ -import * as fs from 'node:fs'; -import openapiDiff, { type DiffOutcome } from 'openapi-diff'; - -interface DiffResult { - version: 'minor' | 'major' | 'patch' | 'unchanged'; - diff: DiffOutcome | null; -} - -export async function diffSpecs( - destinationSpecPath: string, - sourceSpecPath: string -): Promise { - const diff = await openapiDiff.diffSpecs({ - destinationSpec: { - content: fs.readFileSync(destinationSpecPath, 'utf-8'), - location: destinationSpecPath, - format: 'openapi3', - }, - sourceSpec: { - content: fs.readFileSync(sourceSpecPath, 'utf-8'), - location: sourceSpecPath, - format: 'openapi3', - }, - }); - - if (diff.breakingDifferencesFound) { - return { version: 'major', diff: diff }; - } - - if (diff.unclassifiedDifferences.length === 0 && diff.nonBreakingDifferences.length === 0) { - return { version: 'unchanged', diff: null }; - } - - return { version: 'minor', diff: diff }; -} diff --git a/packages/generators/diff/src/index.ts b/packages/generators/diff/src/index.ts deleted file mode 100644 index ff776ac..0000000 --- a/packages/generators/diff/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './generator.js'; -export * from './diff-highlighter.js'; diff --git a/packages/generators/diff/tsconfig.build.json b/packages/generators/diff/tsconfig.build.json deleted file mode 100644 index 0bc65ae..0000000 --- a/packages/generators/diff/tsconfig.build.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "../../../tsconfig.build.json", - "compilerOptions": { - "rootDir": "src", - "baseUrl": ".", - "outDir": "dist", - "skipLibCheck": true - }, - "exclude": [ - "dist", - "index.ts", - "node_modules", - "test", - "**/*.spec.ts", - "jest.config.ts" - ] -} \ No newline at end of file diff --git a/packages/generators/diff/tsconfig.json b/packages/generators/diff/tsconfig.json deleted file mode 100644 index b027bb4..0000000 --- a/packages/generators/diff/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../tsconfig.json" -} \ No newline at end of file diff --git a/packages/generators/spec/.eslintrc b/packages/generators/spec/.eslintrc deleted file mode 100644 index 3280263..0000000 --- a/packages/generators/spec/.eslintrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../.eslintrc" -} \ No newline at end of file diff --git a/packages/generators/spec/index.ts b/packages/generators/spec/index.ts deleted file mode 100644 index 8420b10..0000000 --- a/packages/generators/spec/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './src'; diff --git a/packages/generators/spec/package.json b/packages/generators/spec/package.json deleted file mode 100644 index 825caca..0000000 --- a/packages/generators/spec/package.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "name": "@contractual/generators.spec", - "private": false, - "version": "0.0.0", - "license": "MIT", - "type": "module", - "module": "dist/index.js", - "main": "dist/index.js", - "repository": { - "type": "git", - "url": "https://github.com/contractual-dev/contractual.git", - "directory": "packages/generators/spec" - }, - "exports": { - ".": { - "import": "./dist/index.js", - "require": "./dist/index.js" - } - }, - "homepage": "https://contractual.dev", - "bugs": { - "url": "https://github.com/contractual-dev/contractual/issues" - }, - "contributors": [ - { - "name": "Omer Morad", - "email": "omer.moradd@gmail.com" - } - ], - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/contractual-dev" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/contractual-dev" - } - ], - "engines": { - "node": ">=18.12.0" - }, - "scripts": { - "prebuild": "pnpm rimraf dist", - "build": "tsc -p tsconfig.build.json", - "build:watch": "tsc -p tsconfig.build.json --watch", - "tester": "jest --coverage --verbose", - "lint": "eslint '{src,test}/**/*.ts'" - }, - "files": [ - "dist", - "README.md" - ], - "publishConfig": { - "access": "public", - "provenance": true - }, - "dependencies": { - "@contractual/generators.diff": "workspace:*", - "@typespec/compiler": "^0.63.0", - "@typespec/http": "^0.63.0", - "@typespec/openapi": "^0.63.0", - "@typespec/openapi3": "^0.63.0", - "@typespec/rest": "^0.63.1", - "@typespec/versioning": "^0.63.0", - "semver": "^7.6.3", - "yaml": "^2.7.0" - }, - "devDependencies": { - "@types/semver": "^7.5.8", - "chalk": "^5.4.1", - "inquirer": "^12.3.2", - "openapi-types": "^12.1.3", - "ora": "^8.1.1", - "typescript": "~5.7.2" - }, - "peerDependencies": { - "typescript": ">=5.x" - } -} diff --git a/packages/generators/spec/src/generator.ts b/packages/generators/spec/src/generator.ts deleted file mode 100644 index 415c74a..0000000 --- a/packages/generators/spec/src/generator.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { compile, logDiagnostics, NodeHost } from '@typespec/compiler'; -import * as fs from 'node:fs'; -import * as path from 'node:path'; -import * as process from 'node:process'; -import { parse, stringify } from 'yaml'; -import { inc } from 'semver'; -import type inquirer from 'inquirer'; -import ora from 'ora'; -import chalk from 'chalk'; -import { diffSpecs, printOpenApiDiff } from '@contractual/generators.diff'; - -export function initializePaths() { - const rootPath = path.resolve(process.cwd(), 'contractual'); - const configFilePath = path.resolve(rootPath, 'api-lock.yaml'); - const snapshotsPath = path.resolve(rootPath, 'specs'); - const currentPath = path.resolve(path.dirname(new URL(import.meta.url).pathname)); - const specPath = path.resolve(rootPath, 'api.tsp'); - const tempSpecPath = path.resolve(currentPath, '@typespec', 'openapi3', 'openapi.yaml'); - - return { rootPath, configFilePath, snapshotsPath, currentPath, specPath, tempSpecPath }; -} - -function checkFileExists(filePath: string, errorMessage: string): boolean { - if (!fs.existsSync(filePath)) { - console.error(errorMessage, filePath); - return false; - } - - return true; -} - -async function compileSpecification(specPath: string, outputPath: string) { - const program = await compile(NodeHost, specPath, { - emit: ['@typespec/openapi3'], - additionalImports: ['@typespec/openapi', '@typespec/openapi3', '@typespec/http'], - outputDir: outputPath, - ignoreDeprecated: true, - warningAsError: false, - }); - - if (program.hasError()) { - logDiagnostics( - program.diagnostics.filter(({ severity }) => severity === 'error'), - NodeHost.logSink - ); - - return null; - } - - return program; -} - -async function checkSpecificationDifferences( - tempSpecPath: string, - snapshotsPath: string, - version: string -) { - return diffSpecs(tempSpecPath, path.resolve(snapshotsPath, `openapi-v${version}.yaml`)); -} - -function updateVersionAndSnapshot( - configPath: string, - snapshotsPath: string, - tempSpecPath: string, - currentVersion: string -) { - const newVersion = inc(currentVersion, 'minor'); - const newConfigContent = stringify({ version: { latest: newVersion } }); - - fs.writeFileSync(configPath, newConfigContent); - fs.copyFileSync(tempSpecPath, path.resolve(snapshotsPath, `openapi-v${newVersion}.yaml`)); -} - -export function getLatestVersion(configPath: string) { - const configContent = parse(fs.readFileSync(configPath, 'utf-8')); - return configContent.version.latest; -} - -export async function generateSpecification(inquirerDep: typeof inquirer) { - const paths = initializePaths(); - - if (!checkFileExists(paths.rootPath, `'contractual' directory not found`)) { - return; - } - - if (!fs.existsSync(paths.snapshotsPath)) { - fs.mkdirSync(paths.snapshotsPath); - } - - if (!checkFileExists(paths.specPath, 'specification file not found')) { - process.exit(1); - } - - const latest = getLatestVersion(paths.configFilePath); - - console.log(chalk.gray(`Latest version is ${latest}`)); - - const spinner = ora('Compiling TypeSpec API specification..').start(); - - const program = await compileSpecification(paths.specPath, paths.currentPath); - - if (!program) { - spinner.fail('Compilation failed due to compilation errors'); - return; - } - - if (!checkFileExists(paths.tempSpecPath, 'openapi.yaml not found')) { - spinner.fail('Compilation failed due to missing temp openapi.yaml'); - return; - } - - spinner.succeed('TypeSpec API specification compiled successfully'); - - if (latest === 'unversioned') { - const { initialVersion } = await inquirerDep.prompt([ - { - type: 'input', - name: 'initialVersion', - message: 'Please provide the initial version (e.g., 1.0.0):', - default: '1.0.0', - validate: (input) => - /^\d+\.\d+\.\d+$/.test(input) || - 'Invalid version format. Please use semantic versioning format (e.g., 1.0.0).', - }, - ]); - - const updateSpinner = ora('Creating new version..').start(); - - const destinationPath = path.resolve(paths.snapshotsPath, `openapi-v${initialVersion}.yaml`); - - fs.copyFileSync(paths.tempSpecPath, destinationPath); - - updateSpinner.info(`New version ${initialVersion} created successfully`); - - const newConfigContent = stringify({ version: { latest: initialVersion } }); - - fs.writeFileSync(paths.configFilePath, newConfigContent); - - updateSpinner.info(`Updated to new version: ${initialVersion}`); - - updateSpinner.succeed('Specification generated successfully'); - - return; - } - - const diffSpinner = ora('Checking for API diff..').start(); - - const differences = await checkSpecificationDifferences( - paths.tempSpecPath, - paths.snapshotsPath, - latest - ); - - if (differences.version === 'unchanged') { - diffSpinner.info('No differences found. Specifications are identical or compatible.'); - return; - } - - diffSpinner.info('Differences found'); - - await printOpenApiDiff(differences.diff!); - - const { confirmUpdate } = await inquirerDep.prompt([ - { - type: 'confirm', - name: 'confirmUpdate', - message: 'Do you want to update the API version?', - default: true, - }, - ]); - - if (!confirmUpdate) { - diffSpinner.info('API version not updated'); - return; - } - - diffSpinner.start('Updating API version..'); - - updateVersionAndSnapshot(paths.configFilePath, paths.snapshotsPath, paths.tempSpecPath, latest); - - diffSpinner.succeed('API version updated successfully'); -} diff --git a/packages/generators/spec/src/index.ts b/packages/generators/spec/src/index.ts deleted file mode 100644 index 9f33372..0000000 --- a/packages/generators/spec/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './generator.js'; diff --git a/packages/generators/spec/tsconfig.build.json b/packages/generators/spec/tsconfig.build.json deleted file mode 100644 index 0bc65ae..0000000 --- a/packages/generators/spec/tsconfig.build.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "../../../tsconfig.build.json", - "compilerOptions": { - "rootDir": "src", - "baseUrl": ".", - "outDir": "dist", - "skipLibCheck": true - }, - "exclude": [ - "dist", - "index.ts", - "node_modules", - "test", - "**/*.spec.ts", - "jest.config.ts" - ] -} \ No newline at end of file diff --git a/packages/generators/spec/tsconfig.json b/packages/generators/spec/tsconfig.json deleted file mode 100644 index b027bb4..0000000 --- a/packages/generators/spec/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../tsconfig.json" -} \ No newline at end of file diff --git a/packages/governance/differs/index.ts b/packages/governance/differs/index.ts new file mode 100644 index 0000000..d41479f --- /dev/null +++ b/packages/governance/differs/index.ts @@ -0,0 +1,12 @@ +/** + * Differs + * + * Contract differ implementations for detecting structural changes + * between two versions of a contract specification. + * + * All differs are pure Node.js - no binary dependencies. + */ + +export { diffOpenApi, diffOpenApiObjects, resolveOpenApiSpec } from '@contractual/differs.openapi'; + +export { diffJsonSchema } from './json-schema/index.js'; diff --git a/packages/governance/differs/json-schema/index.ts b/packages/governance/differs/json-schema/index.ts new file mode 100644 index 0000000..8fc70fa --- /dev/null +++ b/packages/governance/differs/json-schema/index.ts @@ -0,0 +1,9 @@ +/** + * JSON Schema Differ + * + * Re-exports from @contractual/differs.json-schema package. + * This module provides JSON Schema structural diffing capabilities. + */ + +// Re-export everything from the json-schema-differ package +export * from '@contractual/differs.json-schema'; diff --git a/packages/governance/index.ts b/packages/governance/index.ts new file mode 100644 index 0000000..5014918 --- /dev/null +++ b/packages/governance/index.ts @@ -0,0 +1,106 @@ +/** + * Governance Engines + * + * Central module for all linting and diffing capabilities. + * Registers all built-in engines with the registry at import time. + */ + +import { registerLinter, registerDiffer } from './registry.js'; +import { lintOpenAPI } from './linters/openapi-redocly.js'; +import { lintJsonSchema } from './linters/json-schema-ajv.js'; +import { diffOpenApi } from '@contractual/differs.openapi'; +import { diffJsonSchema } from '@contractual/differs.json-schema'; + +// Re-export registry functions +export { + registerLinter, + registerDiffer, + getLinter, + getDiffer, + hasLinter, + hasDiffer, + getRegisteredLinterTypes, + getRegisteredDifferTypes, +} from './registry.js'; + +// Re-export types from @contractual/types +export type { + Change, + ChangeSeverity, + DiffResult, + DiffSummary, + SuggestedBump, + LintResult, + LintIssue, + LintSeverity, + LintFn, + DiffFn, + ContractType, + RawChange, + ChangeType, +} from '@contractual/types'; + +// Re-export linters +export { lintOpenAPI } from './linters/openapi-redocly.js'; +export { lintJsonSchema } from './linters/json-schema-ajv.js'; + +// Re-export OpenAPI differ +export { diffOpenApi, diffOpenApiObjects, resolveOpenApiSpec } from '@contractual/differs.openapi'; + +// Re-export everything from JSON Schema differ package +export { + diffJsonSchema, + diffJsonSchemaObjects, + compareSchemas, + checkCompatibility, + classify, + classifyPropertyAdded, + classifyAll, + CLASSIFICATION_SETS, + resolveRefs, + hasUnresolvedRefs, + extractRefs, + validateRefs, + walk, + formatChangeMessage, +} from '@contractual/differs.json-schema'; + +export type { + CompareResult, + CompareOptions, + StrandsTrace, + StrandsCompatibility, + StrandsVersion, + SemanticVersion, + JsonSchemaDraft, + ResolvedSchema, + ResolveResult, +} from '@contractual/differs.json-schema'; + +// Re-export runner for custom commands +export { executeCustomCommand, parseCustomLintOutput, parseCustomDiffOutput } from './runner.js'; + +/** + * Register all built-in governance engines + * + * Call this function at CLI startup to make all engines available + * through the registry. + */ +export function registerAllEngines(): void { + // Register linters + registerLinter('openapi', lintOpenAPI); + registerLinter('json-schema', lintJsonSchema); + + // Register differs + registerDiffer('openapi', diffOpenApi); + registerDiffer('json-schema', diffJsonSchema); + + // Phase 2: + // registerLinter('asyncapi', lintAsyncAPI); + // registerLinter('odcs', lintODCS); + // registerDiffer('asyncapi', diffAsyncAPI); + // registerDiffer('odcs', diffODCS); +} + +// Auto-register on import +registerAllEngines(); diff --git a/packages/governance/linters/index.ts b/packages/governance/linters/index.ts new file mode 100644 index 0000000..42dc1a1 --- /dev/null +++ b/packages/governance/linters/index.ts @@ -0,0 +1,20 @@ +/** + * Linters - Export all linter implementations + */ + +export { lintOpenAPI } from './openapi-redocly.js'; +export { lintJsonSchema, lintJsonSchemaObject } from './json-schema-ajv.js'; +export type { JsonSchemaLintOptions } from './json-schema-ajv.js'; + +// JSON Schema lint rules (for programmatic access) +export { + runLintRules, + getAvailableRules, + getRuleDescription, + LINT_RULES, +} from './json-schema-rules.js'; +export type { LintRule, SchemaNode } from './json-schema-rules.js'; + +// Phase 2: +// export { lintAsyncAPI } from './asyncapi-parser.js'; +// export { lintODCS } from './odcs-ajv.js'; diff --git a/packages/governance/linters/json-schema-ajv.ts b/packages/governance/linters/json-schema-ajv.ts new file mode 100644 index 0000000..defa954 --- /dev/null +++ b/packages/governance/linters/json-schema-ajv.ts @@ -0,0 +1,189 @@ +/** + * JSON Schema Linter using ajv + custom rules + * + * Two-phase linting: + * 1. Meta-validation: Validates that the schema is itself valid JSON Schema (ajv) + * 2. Style rules: Checks for best practices, anti-patterns, and quality (custom rules) + * + * @see https://github.com/sourcemeta/jsonschema + * @see https://github.com/orgs/json-schema-org/discussions/323 + */ + +import { readFile } from 'node:fs/promises'; +import Ajv from 'ajv'; +import addFormats from 'ajv-formats'; +import type { LintResult, LintIssue, LintOptions } from '@contractual/types'; +import { runLintRules } from './json-schema-rules.js'; +import type { SchemaNode } from './json-schema-rules.js'; + +const AjvConstructor = Ajv.default ?? Ajv; +const addFormatsFunc = addFormats.default ?? addFormats; + +/** + * Options for JSON Schema linting (extends base LintOptions) + */ +export interface JsonSchemaLintOptions extends LintOptions { + /** Rules to enable (if not specified, all rules are enabled) */ + enabledRules?: Set; + /** Rules to disable */ + disabledRules?: Set; + /** Skip meta-validation (ajv) */ + skipMetaValidation?: boolean; + /** Skip style rules */ + skipStyleRules?: boolean; +} + +/** + * Lint a JSON Schema using ajv meta-validation and custom style rules + * + * @param specPath - Path to the JSON Schema file + * @param options - Linting options + * @returns Lint result with errors and warnings + */ +export async function lintJsonSchema( + specPath: string, + options: JsonSchemaLintOptions = {} +): Promise { + const errors: LintIssue[] = []; + const warnings: LintIssue[] = []; + + let schema: SchemaNode; + + try { + const content = await readFile(specPath, 'utf-8'); + schema = JSON.parse(content) as SchemaNode; + } catch (error: unknown) { + const message = error instanceof Error ? error.message : 'Unknown error reading schema'; + + errors.push({ + path: '/', + message: `Failed to read or parse schema: ${message}`, + rule: 'parse-error', + severity: 'error', + }); + + return { contract: '', specPath, errors, warnings }; + } + + // Phase 1: Meta-validation using ajv + if (!options.skipMetaValidation) { + const ajv = new AjvConstructor({ + allErrors: true, + strict: false, + validateSchema: true, + }); + addFormatsFunc(ajv); + + const valid = ajv.validateSchema(schema); + + if (!valid && ajv.errors) { + for (const err of ajv.errors) { + errors.push({ + path: err.instancePath || '/', + message: `${err.keyword}: ${err.message}`, + rule: `meta:${err.keyword}`, + severity: 'error', + }); + } + } + } + + // Phase 2: Style rules + if (!options.skipStyleRules) { + const styleIssues = runLintRules(schema, options.enabledRules, options.disabledRules); + + // Partition into errors and warnings + for (const issue of styleIssues) { + if (issue.severity === 'error') { + errors.push(issue); + } else { + warnings.push(issue); + } + } + } + + // Additional meta-level warnings + + // Warn about unknown $schema versions + if (schema.$schema && typeof schema.$schema === 'string') { + const schemaUri = schema.$schema; + const supportedDrafts = ['draft-04', 'draft-06', 'draft-07', 'draft/2019-09', 'draft/2020-12']; + const isKnown = supportedDrafts.some( + (draft) => schemaUri.includes(draft) || schemaUri.includes(draft.replace('draft-', 'draft/')) + ); + + if (!isKnown && !schemaUri.includes('json-schema.org')) { + warnings.push({ + path: '/$schema', + message: `Unknown schema draft: ${schemaUri}. Validation may be incomplete.`, + rule: 'unknown-draft', + severity: 'warning', + }); + } + } + + return { + contract: '', + specPath, + errors, + warnings, + }; +} + +/** + * Lint a JSON Schema object directly (no file I/O) + * + * @param schema - JSON Schema object + * @param options - Linting options + * @returns Lint result with errors and warnings + */ +export async function lintJsonSchemaObject( + schema: SchemaNode, + options: JsonSchemaLintOptions = {} +): Promise { + const errors: LintIssue[] = []; + const warnings: LintIssue[] = []; + + // Phase 1: Meta-validation using ajv + if (!options.skipMetaValidation) { + const ajv = new AjvConstructor({ + allErrors: true, + strict: false, + validateSchema: true, + }); + addFormatsFunc(ajv); + + const valid = ajv.validateSchema(schema); + + if (!valid && ajv.errors) { + for (const err of ajv.errors) { + errors.push({ + path: err.instancePath || '/', + message: `${err.keyword}: ${err.message}`, + rule: `meta:${err.keyword}`, + severity: 'error', + }); + } + } + } + + // Phase 2: Style rules + if (!options.skipStyleRules) { + const styleIssues = runLintRules(schema, options.enabledRules, options.disabledRules); + + for (const issue of styleIssues) { + if (issue.severity === 'error') { + errors.push(issue); + } else { + warnings.push(issue); + } + } + } + + return { + contract: '', + specPath: '', + errors, + warnings, + }; +} diff --git a/packages/governance/linters/json-schema-rules.ts b/packages/governance/linters/json-schema-rules.ts new file mode 100644 index 0000000..489b027 --- /dev/null +++ b/packages/governance/linters/json-schema-rules.ts @@ -0,0 +1,835 @@ +/** + * JSON Schema Linting Rules + * + * Native TypeScript implementation of linting rules for JSON Schema. + * Based on best practices from Sourcemeta, JSON Schema org, and community discussions. + * + * @see https://github.com/sourcemeta/jsonschema + * @see https://github.com/orgs/json-schema-org/discussions/323 + */ + +import type { LintIssue, LintSeverity } from '@contractual/types'; +import { type ResolvedSchema, isSchemaObject } from '@contractual/differs.json-schema'; + +// Re-export ResolvedSchema as SchemaNode for linting API +export type { ResolvedSchema as SchemaNode } from '@contractual/differs.json-schema'; + +/** + * Lint rule definition + */ +export interface LintRule { + /** Rule identifier */ + id: string; + /** Human-readable description */ + description: string; + /** Default severity */ + severity: LintSeverity; + /** Check function - returns issues found at this schema node */ + check: (schema: ResolvedSchema, path: string, root: ResolvedSchema) => LintIssue[]; +} + +/** + * Keywords that only apply to specific types + */ +const TYPE_SPECIFIC_KEYWORDS: Record = { + string: ['minLength', 'maxLength', 'pattern', 'format'], + number: ['minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf'], + integer: ['minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf'], + array: [ + 'items', + 'additionalItems', + 'prefixItems', + 'contains', + 'minItems', + 'maxItems', + 'uniqueItems', + 'minContains', + 'maxContains', + ], + object: [ + 'properties', + 'additionalProperties', + 'required', + 'minProperties', + 'maxProperties', + 'patternProperties', + 'propertyNames', + ], + boolean: [], + null: [], +}; + +/** + * Known JSON Schema format values + */ +const KNOWN_FORMATS = new Set([ + // Dates and times (RFC 3339) + 'date-time', + 'date', + 'time', + 'duration', + // Email (RFC 5321/5322) + 'email', + 'idn-email', + // Hostnames (RFC 1123/5891) + 'hostname', + 'idn-hostname', + // IP addresses (RFC 2673/4291) + 'ipv4', + 'ipv6', + // URIs (RFC 3986/3987) + 'uri', + 'uri-reference', + 'iri', + 'iri-reference', + 'uri-template', + // JSON Pointer (RFC 6901) + 'json-pointer', + 'relative-json-pointer', + // Regex (ECMA 262) + 'regex', + // UUID (RFC 4122) + 'uuid', +]); + +/** + * All built-in linting rules + */ +export const LINT_RULES: LintRule[] = [ + // ========================================== + // Schema Declaration Rules + // ========================================== + { + id: 'missing-schema', + description: 'Root schema should declare $schema', + severity: 'warning', + check: (schema, path, root) => { + // Only check at root + if (path !== '/' || schema !== root) return []; + if (!schema.$schema) { + return [ + { + path: '/', + message: + 'No $schema declared. Consider adding "$schema": "https://json-schema.org/draft/2020-12/schema"', + rule: 'missing-schema', + severity: 'warning', + }, + ]; + } + return []; + }, + }, + + { + id: 'schema-not-at-root', + description: '$schema should only appear at resource root (with $id) or document root', + severity: 'warning', + check: (schema, path, root) => { + if (path === '/' || schema === root) return []; + if (schema.$schema && !schema.$id) { + return [ + { + path, + message: + '$schema without $id in non-root location. $schema should only appear at resource roots.', + rule: 'schema-not-at-root', + severity: 'warning', + }, + ]; + } + return []; + }, + }, + + // ========================================== + // Metadata Rules + // ========================================== + { + id: 'missing-title', + description: 'Root schema should have a title', + severity: 'warning', + check: (schema, path, root) => { + if (path !== '/' || schema !== root) return []; + if (!schema.title) { + return [ + { + path: '/', + message: 'Root schema has no title. Consider adding one for documentation.', + rule: 'missing-title', + severity: 'warning', + }, + ]; + } + return []; + }, + }, + + { + id: 'missing-description', + description: 'Root schema should have a description', + severity: 'warning', + check: (schema, path, root) => { + if (path !== '/' || schema !== root) return []; + if (!schema.description) { + return [ + { + path: '/', + message: 'Root schema has no description. Consider adding one for documentation.', + rule: 'missing-description', + severity: 'warning', + }, + ]; + } + return []; + }, + }, + + // ========================================== + // Enum/Const Rules + // ========================================== + { + id: 'enum-to-const', + description: 'An enum with a single value should use const instead', + severity: 'warning', + check: (schema, path) => { + if (schema.enum && Array.isArray(schema.enum) && schema.enum.length === 1) { + return [ + { + path, + message: `An 'enum' with a single value can be expressed as 'const'. Use: "const": ${JSON.stringify(schema.enum[0])}`, + rule: 'enum-to-const', + severity: 'warning', + }, + ]; + } + return []; + }, + }, + + { + id: 'enum-with-type', + description: 'Using type with enum is redundant when enum values are all the same type', + severity: 'warning', + check: (schema, path) => { + if (!schema.enum || !schema.type) return []; + + const enumValues = schema.enum; + if (!Array.isArray(enumValues) || enumValues.length === 0) return []; + + // Determine types of all enum values + const enumTypes = new Set( + enumValues.map((v) => { + if (v === null) return 'null'; + if (Array.isArray(v)) return 'array'; + return typeof v; + }) + ); + + // Map JS types to JSON Schema types + const jsToSchema: Record = { + string: 'string', + number: 'number', + boolean: 'boolean', + object: 'object', + null: 'null', + array: 'array', + }; + + const schemaTypes = new Set([...enumTypes].map((t) => jsToSchema[t] || t)); + const declaredType = new Set( + Array.isArray(schema.type) ? schema.type : [schema.type as string] + ); + + // Check if type declaration matches enum value types exactly + const typesMatch = + schemaTypes.size === declaredType.size && + [...schemaTypes].every((t) => declaredType.has(t)); + + if (typesMatch) { + return [ + { + path, + message: "'type' is redundant when 'enum' values already constrain the type", + rule: 'enum-with-type', + severity: 'warning', + }, + ]; + } + return []; + }, + }, + + { + id: 'const-with-type', + description: 'Using type with const is redundant when const value determines the type', + severity: 'warning', + check: (schema, path) => { + if (schema.const === undefined || !schema.type) return []; + + const constValue = schema.const; + let constType: string; + + if (constValue === null) { + constType = 'null'; + } else if (Array.isArray(constValue)) { + constType = 'array'; + } else { + constType = typeof constValue; + } + + // Map JS type to JSON Schema type + const jsToSchema: Record = { + string: 'string', + number: 'number', + boolean: 'boolean', + object: 'object', + null: 'null', + array: 'array', + }; + const schemaConstType = jsToSchema[constType] || constType; + + const declaredType = Array.isArray(schema.type) ? schema.type : [schema.type]; + + if (declaredType.length === 1 && declaredType[0] === schemaConstType) { + return [ + { + path, + message: "'type' is redundant when 'const' value already determines the type", + rule: 'const-with-type', + severity: 'warning', + }, + ]; + } + return []; + }, + }, + + // ========================================== + // Conditional Schema Rules + // ========================================== + { + id: 'if-without-then-else', + description: 'if without then or else is unnecessary', + severity: 'warning', + check: (schema, path) => { + if (schema.if && !schema.then && !schema.else) { + return [ + { + path, + message: "'if' without 'then' or 'else' has no effect and can be removed", + rule: 'if-without-then-else', + severity: 'warning', + }, + ]; + } + return []; + }, + }, + + { + id: 'then-else-without-if', + description: 'then or else without if is unnecessary', + severity: 'warning', + check: (schema, path) => { + const issues: LintIssue[] = []; + if (schema.then && !schema.if) { + issues.push({ + path, + message: "'then' without 'if' has no effect and can be removed", + rule: 'then-else-without-if', + severity: 'warning', + }); + } + if (schema.else && !schema.if) { + issues.push({ + path, + message: "'else' without 'if' has no effect and can be removed", + rule: 'then-else-without-if', + severity: 'warning', + }); + } + return issues; + }, + }, + + // ========================================== + // Array Constraint Rules + // ========================================== + { + id: 'additional-items-redundant', + description: 'additionalItems is ignored when items is a schema (not tuple)', + severity: 'warning', + check: (schema, path) => { + // additionalItems only matters when items is an array (tuple validation) + // When items is a schema, additionalItems is ignored + if (schema.additionalItems !== undefined && schema.items && !Array.isArray(schema.items)) { + return [ + { + path, + message: + "'additionalItems' is ignored when 'items' is a schema (not a tuple). Remove 'additionalItems' or use tuple validation.", + rule: 'additional-items-redundant', + severity: 'warning', + }, + ]; + } + return []; + }, + }, + + { + id: 'contains-required', + description: 'minContains or maxContains without contains is unnecessary', + severity: 'warning', + check: (schema, path) => { + const issues: LintIssue[] = []; + if (schema.minContains !== undefined && !schema.contains) { + issues.push({ + path, + message: "'minContains' without 'contains' has no effect", + rule: 'contains-required', + severity: 'warning', + }); + } + if (schema.maxContains !== undefined && !schema.contains) { + issues.push({ + path, + message: "'maxContains' without 'contains' has no effect", + rule: 'contains-required', + severity: 'warning', + }); + } + return issues; + }, + }, + + // ========================================== + // Type Compatibility Rules + // ========================================== + { + id: 'type-incompatible-keywords', + description: 'Validation keywords that do not apply to the declared type', + severity: 'warning', + check: (schema, path) => { + if (!schema.type || Array.isArray(schema.type)) return []; + + const declaredType = schema.type as string; + const applicableKeywords = TYPE_SPECIFIC_KEYWORDS[declaredType] || []; + const issues: LintIssue[] = []; + + // Check for keywords that apply to other types + for (const [type, keywords] of Object.entries(TYPE_SPECIFIC_KEYWORDS)) { + if (type === declaredType) continue; + + for (const keyword of keywords) { + if (schema[keyword] !== undefined && !applicableKeywords.includes(keyword)) { + issues.push({ + path, + message: `'${keyword}' applies to type '${type}' but schema declares type '${declaredType}'`, + rule: 'type-incompatible-keywords', + severity: 'warning', + }); + } + } + } + + return issues; + }, + }, + + // ========================================== + // Range Validation Rules + // ========================================== + { + id: 'invalid-numeric-range', + description: 'maximum should be greater than or equal to minimum', + severity: 'error', + check: (schema, path) => { + const issues: LintIssue[] = []; + + if (typeof schema.minimum === 'number' && typeof schema.maximum === 'number') { + if (schema.maximum < schema.minimum) { + issues.push({ + path, + message: `'maximum' (${schema.maximum}) is less than 'minimum' (${schema.minimum})`, + rule: 'invalid-numeric-range', + severity: 'error', + }); + } + } + + // Handle exclusive bounds (draft-06+ where they are numbers) + if ( + typeof schema.exclusiveMinimum === 'number' && + typeof schema.exclusiveMaximum === 'number' + ) { + if (schema.exclusiveMaximum <= schema.exclusiveMinimum) { + issues.push({ + path, + message: `'exclusiveMaximum' (${schema.exclusiveMaximum}) is not greater than 'exclusiveMinimum' (${schema.exclusiveMinimum})`, + rule: 'invalid-numeric-range', + severity: 'error', + }); + } + } + + return issues; + }, + }, + + { + id: 'invalid-length-range', + description: 'maxLength should be greater than or equal to minLength', + severity: 'error', + check: (schema, path) => { + if (typeof schema.minLength === 'number' && typeof schema.maxLength === 'number') { + if (schema.maxLength < schema.minLength) { + return [ + { + path, + message: `'maxLength' (${schema.maxLength}) is less than 'minLength' (${schema.minLength})`, + rule: 'invalid-length-range', + severity: 'error', + }, + ]; + } + } + return []; + }, + }, + + { + id: 'invalid-items-range', + description: 'maxItems should be greater than or equal to minItems', + severity: 'error', + check: (schema, path) => { + if (typeof schema.minItems === 'number' && typeof schema.maxItems === 'number') { + if (schema.maxItems < schema.minItems) { + return [ + { + path, + message: `'maxItems' (${schema.maxItems}) is less than 'minItems' (${schema.minItems})`, + rule: 'invalid-items-range', + severity: 'error', + }, + ]; + } + } + return []; + }, + }, + + { + id: 'invalid-properties-range', + description: 'maxProperties should be greater than or equal to minProperties', + severity: 'error', + check: (schema, path) => { + if (typeof schema.minProperties === 'number' && typeof schema.maxProperties === 'number') { + if (schema.maxProperties < schema.minProperties) { + return [ + { + path, + message: `'maxProperties' (${schema.maxProperties}) is less than 'minProperties' (${schema.minProperties})`, + rule: 'invalid-properties-range', + severity: 'error', + }, + ]; + } + } + return []; + }, + }, + + // ========================================== + // Format Rules + // ========================================== + { + id: 'unknown-format', + description: 'Unknown format value may not be validated', + severity: 'warning', + check: (schema, path) => { + if (typeof schema.format === 'string' && !KNOWN_FORMATS.has(schema.format)) { + return [ + { + path, + message: `Unknown format '${schema.format}'. Custom formats may not be validated by all implementations.`, + rule: 'unknown-format', + severity: 'warning', + }, + ]; + } + return []; + }, + }, + + // ========================================== + // Empty Schema Rules + // ========================================== + { + id: 'empty-enum', + description: 'Empty enum matches nothing and is likely an error', + severity: 'error', + check: (schema, path) => { + if (schema.enum && Array.isArray(schema.enum) && schema.enum.length === 0) { + return [ + { + path, + message: "Empty 'enum' array will never validate any value", + rule: 'empty-enum', + severity: 'error', + }, + ]; + } + return []; + }, + }, + + { + id: 'empty-required', + description: 'Empty required array is unnecessary', + severity: 'warning', + check: (schema, path) => { + if (schema.required && Array.isArray(schema.required) && schema.required.length === 0) { + return [ + { + path, + message: "Empty 'required' array can be removed", + rule: 'empty-required', + severity: 'warning', + }, + ]; + } + return []; + }, + }, + + { + id: 'empty-allof-anyof-oneof', + description: 'Empty allOf/anyOf/oneOf is likely an error', + severity: 'error', + check: (schema, path) => { + const issues: LintIssue[] = []; + for (const keyword of ['allOf', 'anyOf', 'oneOf'] as const) { + const value = schema[keyword]; + if (value && Array.isArray(value) && value.length === 0) { + issues.push({ + path, + message: `Empty '${keyword}' array ${keyword === 'anyOf' || keyword === 'oneOf' ? 'will never validate' : 'is redundant'}`, + rule: 'empty-allof-anyof-oneof', + severity: keyword === 'allOf' ? 'warning' : 'error', + }); + } + } + return issues; + }, + }, + + // ========================================== + // Required Properties Rules + // ========================================== + { + id: 'required-undefined-property', + description: 'Required property is not defined in properties', + severity: 'warning', + check: (schema, path) => { + if (!schema.required || !schema.properties) return []; + + const issues: LintIssue[] = []; + const definedProps = new Set(Object.keys(schema.properties)); + + for (const requiredProp of schema.required) { + if (!definedProps.has(requiredProp)) { + issues.push({ + path, + message: `Required property '${requiredProp}' is not defined in 'properties'`, + rule: 'required-undefined-property', + severity: 'warning', + }); + } + } + return issues; + }, + }, + + { + id: 'duplicate-required', + description: 'Duplicate entries in required array', + severity: 'warning', + check: (schema, path) => { + if (!schema.required || !Array.isArray(schema.required)) return []; + + const seen = new Set(); + const duplicates = new Set(); + + for (const prop of schema.required) { + if (seen.has(prop)) { + duplicates.add(prop); + } + seen.add(prop); + } + + if (duplicates.size > 0) { + return [ + { + path, + message: `Duplicate entries in 'required': ${[...duplicates].join(', ')}`, + rule: 'duplicate-required', + severity: 'warning', + }, + ]; + } + return []; + }, + }, +]; + +/** + * Run all lint rules against a schema, recursively walking all subschemas + */ +export function runLintRules( + schema: ResolvedSchema, + enabledRules?: Set, + disabledRules?: Set +): LintIssue[] { + const issues: LintIssue[] = []; + const activeRules = LINT_RULES.filter((rule) => { + if (disabledRules?.has(rule.id)) return false; + if (enabledRules && !enabledRules.has(rule.id)) return false; + return true; + }); + + walkSchema(schema, '/', schema, (node, path, root) => { + for (const rule of activeRules) { + const ruleIssues = rule.check(node, path, root); + issues.push(...ruleIssues); + } + }); + + return issues; +} + +/** + * Walk all subschemas in a JSON Schema document + */ +function walkSchema( + schema: ResolvedSchema, + path: string, + root: ResolvedSchema, + visitor: (node: ResolvedSchema, path: string, root: ResolvedSchema) => void +): void { + if (!isSchemaObject(schema)) return; + + visitor(schema, path, root); + + // Properties + if (schema.properties) { + for (const [key, value] of Object.entries(schema.properties)) { + if (isSchemaObject(value)) { + walkSchema(value, `${path}/properties/${key}`, root, visitor); + } + } + } + + // Additional properties + if (isSchemaObject(schema.additionalProperties)) { + walkSchema(schema.additionalProperties, `${path}/additionalProperties`, root, visitor); + } + + // Pattern properties + if (schema.patternProperties) { + for (const [pattern, value] of Object.entries(schema.patternProperties)) { + if (isSchemaObject(value)) { + walkSchema( + value, + `${path}/patternProperties/${encodeURIComponent(pattern)}`, + root, + visitor + ); + } + } + } + + // Property names + if (isSchemaObject(schema.propertyNames)) { + walkSchema(schema.propertyNames, `${path}/propertyNames`, root, visitor); + } + + // Items (array or single schema) + if (schema.items) { + if (Array.isArray(schema.items)) { + schema.items.forEach((item, i) => { + if (isSchemaObject(item)) { + walkSchema(item, `${path}/items/${i}`, root, visitor); + } + }); + } else if (isSchemaObject(schema.items)) { + walkSchema(schema.items, `${path}/items`, root, visitor); + } + } + + // Prefix items (2020-12) + if (schema.prefixItems && Array.isArray(schema.prefixItems)) { + schema.prefixItems.forEach((item, i) => { + if (isSchemaObject(item)) { + walkSchema(item, `${path}/prefixItems/${i}`, root, visitor); + } + }); + } + + // Contains + if (isSchemaObject(schema.contains)) { + walkSchema(schema.contains, `${path}/contains`, root, visitor); + } + + // Conditional + if (isSchemaObject(schema.if)) { + walkSchema(schema.if, `${path}/if`, root, visitor); + } + if (isSchemaObject(schema.then)) { + walkSchema(schema.then, `${path}/then`, root, visitor); + } + if (isSchemaObject(schema.else)) { + walkSchema(schema.else, `${path}/else`, root, visitor); + } + + // Composition + for (const keyword of ['allOf', 'anyOf', 'oneOf'] as const) { + const arr = schema[keyword]; + if (arr && Array.isArray(arr)) { + arr.forEach((item, i) => { + if (isSchemaObject(item)) { + walkSchema(item, `${path}/${keyword}/${i}`, root, visitor); + } + }); + } + } + + // Not + if (isSchemaObject(schema.not)) { + walkSchema(schema.not, `${path}/not`, root, visitor); + } + + // Definitions ($defs) + if (schema.$defs) { + for (const [key, value] of Object.entries(schema.$defs)) { + if (isSchemaObject(value)) { + walkSchema(value, `${path}/$defs/${key}`, root, visitor); + } + } + } +} + +/** + * Get all available rule IDs + */ +export function getAvailableRules(): string[] { + return LINT_RULES.map((r) => r.id); +} + +/** + * Get rule description by ID + */ +export function getRuleDescription(ruleId: string): string | undefined { + return LINT_RULES.find((r) => r.id === ruleId)?.description; +} diff --git a/packages/governance/linters/openapi-redocly.ts b/packages/governance/linters/openapi-redocly.ts new file mode 100644 index 0000000..fc6862e --- /dev/null +++ b/packages/governance/linters/openapi-redocly.ts @@ -0,0 +1,78 @@ +/** + * OpenAPI Linter using @redocly/openapi-core + * + * Wraps Redocly's OpenAPI linting library to validate OpenAPI specs. + * Users can place a .redocly.yaml in their repo for custom rules. + */ + +import type { LintResult, LintIssue } from '@contractual/types'; + +/** + * Lint an OpenAPI specification using Redocly + * + * @param specPath - Path to the OpenAPI spec file + * @returns Lint result with errors and warnings + */ +export async function lintOpenAPI(specPath: string): Promise { + // Dynamic import to handle the library being optional + let lint: typeof import('@redocly/openapi-core').lint; + let loadConfig: typeof import('@redocly/openapi-core').loadConfig; + + try { + const redocly = await import('@redocly/openapi-core'); + lint = redocly.lint; + loadConfig = redocly.loadConfig; + } catch { + throw new Error( + 'OpenAPI linting requires @redocly/openapi-core. ' + + 'Install it with: npm install @redocly/openapi-core' + ); + } + + const errors: LintIssue[] = []; + const warnings: LintIssue[] = []; + + try { + // Load config from .redocly.yaml if present, or use defaults + const config = await loadConfig(); + + // Run the linter + const results = await lint({ + ref: specPath, + config, + }); + + // Process problems + for (const problem of results) { + const issue: LintIssue = { + path: problem.location?.[0]?.pointer ?? '', + message: problem.message, + rule: problem.ruleId, + severity: problem.severity === 'error' ? 'error' : 'warning', + }; + + if (issue.severity === 'error') { + errors.push(issue); + } else { + warnings.push(issue); + } + } + } catch (error: unknown) { + // Handle parse errors or file not found + const message = error instanceof Error ? error.message : 'Unknown error during linting'; + + errors.push({ + path: '/', + message, + rule: 'parse-error', + severity: 'error', + }); + } + + return { + contract: '', // Filled by caller + specPath, + errors, + warnings, + }; +} diff --git a/packages/governance/package.json b/packages/governance/package.json new file mode 100644 index 0000000..454023e --- /dev/null +++ b/packages/governance/package.json @@ -0,0 +1,59 @@ +{ + "name": "@contractual/governance", + "private": false, + "version": "0.1.0-dev.5", + "license": "MIT", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./linters": { + "types": "./dist/linters/index.d.ts", + "import": "./dist/linters/index.js" + }, + "./differs": { + "types": "./dist/differs/index.d.ts", + "import": "./dist/differs/index.js" + } + }, + "repository": { + "type": "git", + "url": "https://github.com/contractual-dev/contractual.git", + "directory": "packages/governance" + }, + "homepage": "https://contractual.dev", + "bugs": { + "url": "https://github.com/contractual-dev/contractual/issues" + }, + "engines": { + "node": ">=20.0.0" + }, + "scripts": { + "prebuild": "pnpm rimraf dist", + "build": "tsc -p tsconfig.build.json", + "build:watch": "tsc -p tsconfig.build.json --watch", + "lint": "eslint \"linters/**/*.ts\" \"differs/**/*.ts\" \"*.ts\"" + }, + "files": [ + "dist", + "README.md" + ], + "dependencies": { + "@contractual/differs.json-schema": "workspace:*", + "@contractual/differs.openapi": "workspace:*", + "@contractual/types": "workspace:*", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1" + }, + "devDependencies": { + "@redocly/openapi-core": "^1.0.0" + }, + "publishConfig": { + "access": "public", + "provenance": true + } +} diff --git a/packages/governance/registry.ts b/packages/governance/registry.ts new file mode 100644 index 0000000..2ed1f78 --- /dev/null +++ b/packages/governance/registry.ts @@ -0,0 +1,198 @@ +/** + * Governance Registry + * + * Central registry for linters and differs. CLI Core uses this to resolve + * the correct engine for each contract type. + */ + +import type { LintFn, DiffFn, ContractType } from '@contractual/types'; + +const linters = new Map(); +const differs = new Map(); + +/** + * Register a linter for a contract type + */ +export function registerLinter(type: ContractType, linter: LintFn): void { + linters.set(type, linter); +} + +/** + * Register a differ for a contract type + */ +export function registerDiffer(type: ContractType, differ: DiffFn): void { + differs.set(type, differ); +} + +/** + * Get the linter for a contract type + * + * @param type - Contract type (openapi, json-schema, etc.) + * @param override - Optional override: built-in name or custom command + * @returns Linter function or null if not found + */ +export function getLinter(type: ContractType, override?: string | false): LintFn | null { + // If override is explicitly false, disable linting + if (override === false) { + return null; + } + + // If override is a custom command (contains {spec}), create custom executor + if (override && override.includes('{spec}')) { + return createCustomLinter(override); + } + + // If override is a known built-in name, resolve it + if (override) { + const builtIn = resolveBuiltInLinter(override); + if (builtIn) return builtIn; + } + + // Return default for type + return linters.get(type) ?? null; +} + +/** + * Get the differ for a contract type + * + * @param type - Contract type (openapi, json-schema, etc.) + * @param override - Optional override: built-in name or custom command + * @returns Differ function or null if not found + */ +export function getDiffer(type: ContractType, override?: string | false): DiffFn | null { + // If override is explicitly false, disable diffing + if (override === false) { + return null; + } + + // If override is a custom command (contains {old} or {new}), create custom executor + if (override && (override.includes('{old}') || override.includes('{new}'))) { + return createCustomDiffer(override); + } + + // If override is a known built-in name, resolve it + if (override) { + const builtIn = resolveBuiltInDiffer(override); + if (builtIn) return builtIn; + } + + // Return default for type + return differs.get(type) ?? null; +} + +/** + * Valid contract type values for type checking + */ +const VALID_CONTRACT_TYPES: readonly ContractType[] = [ + 'openapi', + 'json-schema', + 'asyncapi', + 'odcs', +] as const; + +/** + * Type guard for ContractType + */ +function isContractType(value: string): value is ContractType { + return VALID_CONTRACT_TYPES.includes(value as ContractType); +} + +/** + * Resolve a built-in linter by name + */ +function resolveBuiltInLinter(name: string): LintFn | null { + // Check if name is a valid contract type + if (isContractType(name)) { + const linter = linters.get(name); + if (linter) return linter; + } + + // Special named overrides + switch (name.toLowerCase()) { + case 'redocly': + case 'openapi-redocly': + return linters.get('openapi') ?? null; + case 'spectral': + // Spectral is an alternative OpenAPI linter - could add support later + return linters.get('openapi') ?? null; + case 'ajv': + case 'json-schema-ajv': + return linters.get('json-schema') ?? null; + default: + return null; + } +} + +/** + * Resolve a built-in differ by name + */ +function resolveBuiltInDiffer(name: string): DiffFn | null { + // Check if name is a valid contract type + if (isContractType(name)) { + const differ = differs.get(name); + if (differ) return differ; + } + + // Special named overrides + switch (name.toLowerCase()) { + case 'oasdiff': + case 'openapi-oasdiff': + return differs.get('openapi') ?? null; + case 'json-schema-differ': + return differs.get('json-schema') ?? null; + default: + return null; + } +} + +/** + * Create a custom linter from a command template + */ +function createCustomLinter(commandTemplate: string): LintFn { + return async (specPath: string) => { + const { executeCustomCommand, parseCustomLintOutput } = await import('./runner.js'); + const command = commandTemplate.replace('{spec}', specPath); + const output = await executeCustomCommand(command); + return parseCustomLintOutput(output); + }; +} + +/** + * Create a custom differ from a command template + */ +function createCustomDiffer(commandTemplate: string): DiffFn { + return async (oldSpecPath: string, newSpecPath: string) => { + const { executeCustomCommand, parseCustomDiffOutput } = await import('./runner.js'); + const command = commandTemplate.replace('{old}', oldSpecPath).replace('{new}', newSpecPath); + const output = await executeCustomCommand(command); + return parseCustomDiffOutput(output); + }; +} + +/** + * Check if a linter is registered for a contract type + */ +export function hasLinter(type: ContractType): boolean { + return linters.has(type); +} + +/** + * Check if a differ is registered for a contract type + */ +export function hasDiffer(type: ContractType): boolean { + return differs.has(type); +} + +/** + * Get all registered contract types for linting + */ +export function getRegisteredLinterTypes(): ContractType[] { + return Array.from(linters.keys()); +} + +/** + * Get all registered contract types for diffing + */ +export function getRegisteredDifferTypes(): ContractType[] { + return Array.from(differs.keys()); +} diff --git a/packages/governance/runner.ts b/packages/governance/runner.ts new file mode 100644 index 0000000..0812b07 --- /dev/null +++ b/packages/governance/runner.ts @@ -0,0 +1,366 @@ +/** + * Custom Command Runner + * + * Executes user-defined governance commands and parses their output. + */ + +import { execSync } from 'node:child_process'; +import type { LintResult, DiffResult, LintIssue, Change } from '@contractual/types'; + +/** + * Shape of exec error with additional properties + */ +interface ExecError extends Error { + stdout?: string; + stderr?: string; + status?: number; + signal?: string; +} + +/** + * Type guard for ExecError + */ +function isExecError(error: unknown): error is ExecError { + return error instanceof Error; +} + +/** + * Execute a custom command and return its output + * + * Note: This function is async for API consistency with other governance functions, + * even though the underlying execSync is synchronous. This allows for future + * migration to async child_process.spawn if needed. + * + * @param command - Command to execute + * @param timeout - Timeout in milliseconds (default: 60000) + * @returns Command stdout + * @throws Error if command fails + */ +export async function executeCustomCommand(command: string, timeout = 60000): Promise { + try { + const stdout = execSync(command, { + encoding: 'utf-8', + timeout, + stdio: ['pipe', 'pipe', 'pipe'], + }); + return stdout; + } catch (error: unknown) { + // Type-safe error handling + if (isExecError(error)) { + // Some tools exit with non-zero when issues found - return stdout if available + if (error.stdout) { + return error.stdout; + } + + throw new Error( + `Custom command failed: ${command}\n` + + `Exit code: ${error.status ?? 'unknown'}\n` + + `Stderr: ${error.stderr ?? error.message}` + ); + } + + throw new Error(`Custom command failed: ${command}\n${String(error)}`); + } +} + +/** + * Parse custom lint command output into LintResult + * + * Attempts to parse as JSON first, then falls back to line-based parsing. + */ +export function parseCustomLintOutput(output: string): LintResult { + const trimmed = output.trim(); + + // Try JSON parsing first + if (trimmed.startsWith('[') || trimmed.startsWith('{')) { + try { + const parsed = JSON.parse(trimmed); + return normalizeJsonLintOutput(parsed); + } catch { + // Fall through to line-based parsing + } + } + + // Line-based parsing for text output + return parseLineLintOutput(trimmed); +} + +/** + * Parse custom diff command output into DiffResult + * + * Attempts to parse as JSON first, then falls back to line-based parsing. + */ +export function parseCustomDiffOutput(output: string): DiffResult { + const trimmed = output.trim(); + + // Try JSON parsing first + if (trimmed.startsWith('[') || trimmed.startsWith('{')) { + try { + const parsed = JSON.parse(trimmed); + return normalizeJsonDiffOutput(parsed); + } catch { + // Fall through to line-based parsing + } + } + + // Line-based parsing for text output + return parseLineDiffOutput(trimmed); +} + +/** + * Normalize JSON lint output from various tools + */ +function normalizeJsonLintOutput(parsed: unknown): LintResult { + const errors: LintIssue[] = []; + const warnings: LintIssue[] = []; + + if (Array.isArray(parsed)) { + for (const item of parsed) { + const issue = normalizeIssue(item); + if (issue.severity === 'error') { + errors.push(issue); + } else { + warnings.push(issue); + } + } + } else if (typeof parsed === 'object' && parsed !== null) { + // Handle object with errors/warnings arrays + const obj = parsed as Record; + if (Array.isArray(obj.errors)) { + errors.push(...obj.errors.map((e) => normalizeIssue(e, 'error'))); + } + if (Array.isArray(obj.warnings)) { + warnings.push(...obj.warnings.map((w) => normalizeIssue(w, 'warning'))); + } + if (Array.isArray(obj.problems)) { + for (const p of obj.problems) { + const issue = normalizeIssue(p); + if (issue.severity === 'error') { + errors.push(issue); + } else { + warnings.push(issue); + } + } + } + } + + return { contract: '', specPath: '', errors, warnings }; +} + +/** + * Normalize a single issue from various formats + */ +function normalizeIssue(item: unknown, defaultSeverity: 'error' | 'warning' = 'error'): LintIssue { + if (typeof item === 'string') { + return { + path: '', + message: item, + severity: defaultSeverity, + }; + } + + if (typeof item !== 'object' || item === null) { + return { + path: '', + message: String(item), + severity: defaultSeverity, + }; + } + + const obj = item as Record; + + // Common field mappings + const path = String(obj.path ?? obj.location ?? obj.pointer ?? obj.instancePath ?? ''); + const message = String(obj.message ?? obj.text ?? obj.description ?? obj.msg ?? ''); + const rule = obj.rule ?? obj.ruleId ?? obj.code ?? obj.id; + const severity = normalizeSeverity(obj.severity ?? obj.level ?? defaultSeverity); + + return { + path, + message, + rule: rule ? String(rule) : undefined, + severity, + }; +} + +/** + * Normalize severity from various formats + */ +function normalizeSeverity(value: unknown): 'error' | 'warning' { + if (typeof value === 'number') { + // Common: 0=error, 1=warning, 2=info (Spectral) + // Or: 1=error, 2=warning, 3=info (some tools) + return value <= 1 ? 'error' : 'warning'; + } + if (typeof value === 'string') { + const lower = value.toLowerCase(); + if (lower === 'error' || lower === 'err' || lower === 'fatal') { + return 'error'; + } + } + return 'warning'; +} + +/** + * Parse line-based lint output + */ +function parseLineLintOutput(output: string): LintResult { + const errors: LintIssue[] = []; + const warnings: LintIssue[] = []; + + const lines = output.split('\n').filter((line) => line.trim()); + + for (const line of lines) { + // Common patterns: + // "error: message" or "warning: message" + // "path:line:col: error message" + // "[ERROR] message" + + const lowerLine = line.toLowerCase(); + const isError = + lowerLine.includes('error') || lowerLine.includes('[err]') || lowerLine.startsWith('e '); + const isWarning = + lowerLine.includes('warning') || lowerLine.includes('[warn]') || lowerLine.startsWith('w '); + + if (isError || isWarning || line.includes(':')) { + const issue: LintIssue = { + path: '', + message: line.trim(), + severity: isError ? 'error' : 'warning', + }; + + if (isError) { + errors.push(issue); + } else { + warnings.push(issue); + } + } + } + + return { contract: '', specPath: '', errors, warnings }; +} + +/** + * Normalize JSON diff output from various tools + */ +function normalizeJsonDiffOutput(parsed: unknown): DiffResult { + const changes: Change[] = []; + + if (Array.isArray(parsed)) { + for (const item of parsed) { + changes.push(normalizeChange(item)); + } + } else if (typeof parsed === 'object' && parsed !== null) { + const obj = parsed as Record; + // Handle object with changes/items array + const items = obj.changes ?? obj.items ?? obj.differences ?? []; + if (Array.isArray(items)) { + for (const item of items) { + changes.push(normalizeChange(item)); + } + } + } + + const summary = { + breaking: changes.filter((c) => c.severity === 'breaking').length, + nonBreaking: changes.filter((c) => c.severity === 'non-breaking').length, + patch: changes.filter((c) => c.severity === 'patch').length, + unknown: changes.filter((c) => c.severity === 'unknown').length, + }; + + const suggestedBump = + summary.breaking > 0 + ? 'major' + : summary.nonBreaking > 0 + ? 'minor' + : summary.patch > 0 + ? 'patch' + : 'none'; + + return { contract: '', changes, summary, suggestedBump }; +} + +/** + * Normalize a single change from various formats + */ +function normalizeChange(item: unknown): Change { + if (typeof item !== 'object' || item === null) { + return { + path: '', + severity: 'unknown', + category: 'unknown-change', + message: String(item), + }; + } + + const obj = item as Record; + + const path = String(obj.path ?? obj.location ?? obj.pointer ?? ''); + const message = String(obj.message ?? obj.text ?? obj.description ?? ''); + const category = String(obj.category ?? obj.code ?? obj.id ?? 'unknown-change'); + + // Determine severity + let severity: Change['severity'] = 'unknown'; + if (obj.severity) { + const sev = String(obj.severity).toLowerCase(); + if (sev === 'breaking' || sev === 'major') { + severity = 'breaking'; + } else if (sev === 'non-breaking' || sev === 'minor') { + severity = 'non-breaking'; + } else if (sev === 'patch') { + severity = 'patch'; + } + } else if (obj.level !== undefined) { + // oasdiff style: level 3 = breaking + const level = Number(obj.level); + if (level === 3) severity = 'breaking'; + else if (level === 2) severity = 'non-breaking'; + else if (level === 1) severity = 'patch'; + } else if (obj.breaking === true) { + severity = 'breaking'; + } + + return { + path, + severity, + category, + message, + oldValue: obj.oldValue ?? obj.old ?? obj.from, + newValue: obj.newValue ?? obj.new ?? obj.to, + }; +} + +/** + * Parse line-based diff output + */ +function parseLineDiffOutput(output: string): DiffResult { + const changes: Change[] = []; + + const lines = output.split('\n').filter((line) => line.trim()); + + for (const line of lines) { + const lowerLine = line.toLowerCase(); + const isBreaking = + lowerLine.includes('breaking') || + lowerLine.includes('removed') || + lowerLine.includes('major'); + + changes.push({ + path: '', + severity: isBreaking ? 'breaking' : 'unknown', + category: 'unknown-change', + message: line.trim(), + }); + } + + const summary = { + breaking: changes.filter((c) => c.severity === 'breaking').length, + nonBreaking: 0, + patch: 0, + unknown: changes.filter((c) => c.severity === 'unknown').length, + }; + + const suggestedBump = summary.breaking > 0 ? 'major' : 'none'; + + return { contract: '', changes, summary, suggestedBump }; +} diff --git a/packages/governance/tsconfig.build.json b/packages/governance/tsconfig.build.json new file mode 100644 index 0000000..8ed7148 --- /dev/null +++ b/packages/governance/tsconfig.build.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "composite": true + }, + "references": [ + { "path": "../types/tsconfig.build.json" } + ], + "exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"] +} diff --git a/packages/governance/tsconfig.json b/packages/governance/tsconfig.json new file mode 100644 index 0000000..3bdc553 --- /dev/null +++ b/packages/governance/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "./dist", + "strict": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/types/config.ts b/packages/types/config.ts new file mode 100644 index 0000000..7edb48d --- /dev/null +++ b/packages/types/config.ts @@ -0,0 +1,164 @@ +/** + * Supported contract types in Contractual. + * + * @remarks + * - `openapi` - OpenAPI 3.x specifications + * - `json-schema` - JSON Schema (Draft-07, 2019-09, 2020-12) + * - `asyncapi` - AsyncAPI 2.x/3.x specifications + * - `odcs` - Open Data Contract Standard v3.x + */ +export type ContractType = 'openapi' | 'json-schema' | 'asyncapi' | 'odcs'; + +/** + * All valid contract type values as a readonly array. + * Useful for runtime validation and iteration. + */ +export const CONTRACT_TYPES = [ + 'openapi', + 'json-schema', + 'asyncapi', + 'odcs', +] as const satisfies readonly ContractType[]; + +/** + * Definition of a single contract in the configuration file. + * + * @example + * ```yaml + * contracts: + * - name: orders-api + * type: openapi + * path: ./specs/orders.openapi.yaml + * ``` + */ +export interface ContractDefinition { + /** Unique identifier for the contract, used in changeset files and version history */ + name: string; + /** Type of schema format */ + type: ContractType; + /** Path to spec file, relative to config (can be glob pattern) */ + path: string; + /** Override linter: tool name, custom command with {spec} placeholder, or false to disable */ + lint?: string | false; + /** Override differ: tool name, custom command with {old} {new} placeholders, or false to disable */ + breaking?: string | false; + /** Sync version field inside the spec file on version bump (default: true). Set to false to skip. */ + syncVersion?: boolean; + /** Output generation commands (Phase 2) */ + generate?: string[]; +} + +/** + * Changeset behavior configuration. + */ +export interface ChangesetConfig { + /** Auto-detect change classifications from structural diff (default: true) */ + autoDetect?: boolean; + /** Require changeset for spec changes in PRs (default: true) */ + requireOnPR?: boolean; +} + +/** + * AI provider types supported by Contractual. + */ +export type AIProvider = 'anthropic'; + +/** + * AI feature toggles. + */ +export interface AIFeatures { + /** Enable PR change explanations */ + explain?: boolean; + /** Enable AI-enriched changelog entries */ + changelog?: boolean; + /** Enable spec metadata enhancement (auto-fill descriptions, examples) */ + enhance?: boolean; +} + +/** + * AI/LLM integration configuration. + * + * @remarks + * All AI features gracefully degrade when no API key is provided. + */ +export interface AIConfig { + /** AI provider (only 'anthropic' supported currently) */ + provider?: AIProvider; + /** Model identifier to use */ + model?: string; + /** Feature toggles */ + features?: AIFeatures; +} + +/** + * Versioning mode for contracts. + * + * - `independent` - Each contract has its own version (like Lerna independent mode) + * - `fixed` - All contracts share the same version + */ +export type VersioningMode = 'independent' | 'fixed'; + +/** + * Versioning configuration. + */ +export interface VersioningConfig { + /** Versioning mode (default: 'independent') */ + mode: VersioningMode; +} + +/** + * Root configuration for contractual.yaml. + * + * @example + * ```yaml + * contracts: + * - name: orders-api + * type: openapi + * path: ./specs/orders.openapi.yaml + * + * ai: + * features: + * explain: true + * changelog: true + * ``` + */ +export interface ContractualConfig { + /** List of contract definitions */ + contracts: ContractDefinition[]; + /** Versioning configuration */ + versioning?: VersioningConfig; + /** Changeset behavior configuration */ + changeset?: ChangesetConfig; + /** AI/LLM integration configuration */ + ai?: AIConfig; +} + +/** + * Resolved contract with absolute paths and validated data. + */ +export interface ResolvedContract extends ContractDefinition { + /** Absolute path to the spec file */ + absolutePath: string; +} + +/** + * Fully resolved configuration with absolute paths. + */ +export interface ResolvedConfig extends Omit { + /** Resolved contracts with absolute paths */ + contracts: ResolvedContract[]; + /** Directory containing contractual.yaml */ + configDir: string; + /** Absolute path to contractual.yaml */ + configPath: string; +} + +/** + * Type guard to check if a string is a valid ContractType. + * + * @param value - The value to check + * @returns True if the value is a valid ContractType + */ +export function isContractType(value: unknown): value is ContractType { + return typeof value === 'string' && CONTRACT_TYPES.includes(value as ContractType); +} diff --git a/packages/types/governance.ts b/packages/types/governance.ts new file mode 100644 index 0000000..a6c4db9 --- /dev/null +++ b/packages/types/governance.ts @@ -0,0 +1,374 @@ +/** + * Severity classification for detected changes between spec versions. + * + * @remarks + * - `breaking` - Changes that will break existing consumers (major bump) + * - `non-breaking` - Additive changes that are backwards compatible (minor bump) + * - `patch` - Metadata-only changes (patch bump) + * - `unknown` - Complex changes requiring manual review + */ +export type ChangeSeverity = 'breaking' | 'non-breaking' | 'patch' | 'unknown'; + +/** + * A single change detected between two spec versions. + * + * @example + * ```typescript + * const change: Change = { + * path: '/properties/amount/type', + * severity: 'breaking', + * category: 'type-changed', + * message: "Changed type of field 'amount': string -> number", + * oldValue: 'string', + * newValue: 'number' + * }; + * ``` + */ +export interface Change { + /** JSON Pointer path to the changed element (e.g., "/properties/amount/type") */ + path: string; + /** Severity classification determining version bump */ + severity: ChangeSeverity; + /** Category key for grouping similar changes (e.g., "field-removed", "type-changed") */ + category: string; + /** Human-readable description of the change */ + message: string; + /** Value in the old spec (undefined if added) */ + oldValue?: unknown; + /** Value in the new spec (undefined if removed) */ + newValue?: unknown; +} + +/** + * Summary counts of changes by severity. + */ +export interface DiffSummary { + /** Number of breaking changes detected */ + breaking: number; + /** Number of non-breaking (additive) changes detected */ + nonBreaking: number; + /** Number of patch-level (metadata) changes detected */ + patch: number; + /** Number of unclassifiable changes requiring manual review */ + unknown: number; +} + +/** + * Suggested semver bump based on detected changes. + * + * @remarks + * - `major` - Breaking changes detected + * - `minor` - Non-breaking structural changes + * - `patch` - Metadata-only changes + * - `none` - No changes detected + */ +export type SuggestedBump = 'major' | 'minor' | 'patch' | 'none'; + +/** + * Result of comparing two spec versions. + */ +export interface DiffResult { + /** Contract name from config */ + contract: string; + /** List of all detected changes */ + changes: Change[]; + /** Aggregated summary counts by severity */ + summary: DiffSummary; + /** Recommended semver bump based on highest severity change */ + suggestedBump: SuggestedBump; +} + +/** + * Severity of a lint diagnostic. + */ +export type LintSeverity = 'error' | 'warning'; + +/** + * A single lint issue found in a spec. + */ +export interface LintIssue { + /** Location in spec (JSON Pointer path or line:col) */ + path: string; + /** Human-readable issue description */ + message: string; + /** Rule identifier if available (e.g., "operation-operationId") */ + rule?: string; + /** Issue severity */ + severity: LintSeverity; +} + +/** + * Result of linting a spec file. + */ +export interface LintResult { + /** Contract name from config */ + contract: string; + /** Absolute path to the spec file that was linted */ + specPath: string; + /** List of error-level issues (cause lint failure) */ + errors: LintIssue[]; + /** List of warning-level issues (informational) */ + warnings: LintIssue[]; +} + +/** + * Options for linter functions. + */ +export interface LintOptions { + /** Custom ruleset configuration */ + ruleset?: string; + /** Additional tool-specific options */ + [key: string]: unknown; +} + +/** + * Function signature for linter implementations. + * + * @param specPath - Absolute path to the spec file to lint + * @param options - Optional linter configuration + * @returns Promise resolving to lint results + */ +export type LintFn = (specPath: string, options?: LintOptions) => Promise; + +/** + * Options for differ functions. + */ +export interface DiffOptions { + /** Include metadata-only (patch) changes in results */ + includeMetadata?: boolean; + /** Additional tool-specific options */ + [key: string]: unknown; +} + +/** + * Function signature for differ implementations. + * + * @param oldSpecPath - Absolute path to the old (baseline) spec + * @param newSpecPath - Absolute path to the new (current) spec + * @param options - Optional differ configuration + * @returns Promise resolving to diff results + */ +export type DiffFn = ( + oldSpecPath: string, + newSpecPath: string, + options?: DiffOptions +) => Promise; + +/** + * All possible structural change types for classification. + * + * @remarks + * These categories are used internally by differs to classify raw changes + * before mapping to severity levels. + */ +export type ChangeType = + // Property-level changes + | 'property-added' + | 'property-removed' + | 'required-added' + | 'required-removed' + // Type-level changes + | 'type-changed' + | 'type-narrowed' + | 'type-widened' + // Enum-level changes + | 'enum-value-added' + | 'enum-value-removed' + | 'enum-added' + | 'enum-removed' + // Constraint-level changes + | 'constraint-tightened' + | 'constraint-loosened' + | 'format-changed' + | 'format-added' + | 'format-removed' + // Object-level changes + | 'additional-properties-denied' + | 'additional-properties-allowed' + | 'additional-properties-changed' + | 'property-names-changed' + | 'dependent-required-added' + | 'dependent-required-removed' + | 'dependent-schemas-changed' + | 'unevaluated-properties-changed' + // Array-level changes + | 'items-changed' + | 'min-items-increased' + | 'max-items-decreased' + | 'min-contains-changed' + | 'max-contains-changed' + | 'unevaluated-items-changed' + // Ref-level changes + | 'ref-target-changed' + // Metadata-level changes (patch) + | 'description-changed' + | 'title-changed' + | 'default-changed' + | 'examples-changed' + // Annotation changes (patch per Strands API) + | 'deprecated-changed' + | 'read-only-changed' + | 'write-only-changed' + // Content keyword changes (patch per Strands API) + | 'content-encoding-changed' + | 'content-media-type-changed' + | 'content-schema-changed' + // Granular composition changes (replacing generic composition-changed) + | 'anyof-option-added' + | 'anyof-option-removed' + | 'oneof-option-added' + | 'oneof-option-removed' + | 'allof-member-added' + | 'allof-member-removed' + | 'not-schema-changed' + | 'if-then-else-changed' + // Legacy composition (for backward compat) + | 'composition-changed' + // OpenAPI structural changes + | 'path-added' + | 'path-removed' + | 'operation-added' + | 'operation-removed' + | 'parameter-added' + | 'parameter-required-added' + | 'parameter-removed' + | 'parameter-required-changed' + | 'parameter-schema-changed' + | 'request-body-added' + | 'request-body-removed' + | 'response-added' + | 'response-removed' + | 'response-schema-changed' + | 'security-changed' + | 'server-changed' + // Catch-all for unrecognized changes + | 'unknown-change'; + +/** + * Raw change detected by a differ before severity classification. + * + * @remarks + * This is an internal type used by differ implementations. + * Raw changes are classified into {@link Change} with severity. + */ +export interface RawChange { + /** JSON Pointer path to the change location */ + path: string; + /** Structural change type for classification */ + type: ChangeType; + /** Value in old spec (undefined if added) */ + oldValue?: unknown; + /** Value in new spec (undefined if removed) */ + newValue?: unknown; +} + +/** + * Mapping from change types to their severity classification. + * + * @remarks + * This constant provides the default classification rules aligned with Strands API. + * Breaking changes cause major bumps, non-breaking cause minor, etc. + */ +export const CHANGE_TYPE_SEVERITY: Record = { + // Breaking changes (major) + 'property-removed': 'breaking', + 'required-added': 'breaking', + 'type-changed': 'breaking', + 'type-narrowed': 'breaking', + 'enum-value-removed': 'breaking', + 'enum-added': 'breaking', + 'constraint-tightened': 'breaking', + 'additional-properties-denied': 'breaking', + 'items-changed': 'breaking', + 'min-items-increased': 'breaking', + 'max-items-decreased': 'breaking', + 'ref-target-changed': 'breaking', + 'dependent-required-added': 'breaking', + // Composition breaking changes (per Strands API) + 'anyof-option-added': 'breaking', + 'oneof-option-added': 'breaking', + 'allof-member-added': 'breaking', + 'not-schema-changed': 'breaking', + + // Non-breaking changes (minor) + 'property-added': 'non-breaking', + 'required-removed': 'non-breaking', + 'type-widened': 'non-breaking', + 'enum-value-added': 'non-breaking', + 'enum-removed': 'non-breaking', + 'constraint-loosened': 'non-breaking', + 'additional-properties-allowed': 'non-breaking', + 'additional-properties-changed': 'non-breaking', + 'dependent-required-removed': 'non-breaking', + // Composition non-breaking changes (per Strands API) + 'anyof-option-removed': 'non-breaking', + 'oneof-option-removed': 'non-breaking', + 'allof-member-removed': 'non-breaking', + + // Patch-level changes (per Strands API - format is annotation) + 'format-added': 'patch', + 'format-removed': 'patch', + 'format-changed': 'patch', + 'description-changed': 'patch', + 'title-changed': 'patch', + 'default-changed': 'patch', + 'examples-changed': 'patch', + // Annotation changes (patch per Strands API) + 'deprecated-changed': 'patch', + 'read-only-changed': 'patch', + 'write-only-changed': 'patch', + // Content keyword changes (patch per Strands API) + 'content-encoding-changed': 'patch', + 'content-media-type-changed': 'patch', + 'content-schema-changed': 'patch', + + // Requires manual review (unknown) + 'property-names-changed': 'unknown', + 'dependent-schemas-changed': 'unknown', + 'unevaluated-properties-changed': 'unknown', + 'unevaluated-items-changed': 'unknown', + 'min-contains-changed': 'unknown', + 'max-contains-changed': 'unknown', + 'if-then-else-changed': 'unknown', + 'composition-changed': 'unknown', + // OpenAPI structural changes + 'path-removed': 'breaking', + 'operation-removed': 'breaking', + 'parameter-added': 'non-breaking', + 'parameter-required-added': 'breaking', + 'parameter-removed': 'breaking', + 'parameter-required-changed': 'breaking', + 'request-body-added': 'breaking', + 'response-removed': 'breaking', + 'security-changed': 'breaking', + 'path-added': 'non-breaking', + 'operation-added': 'non-breaking', + 'request-body-removed': 'non-breaking', + 'response-added': 'non-breaking', + 'server-changed': 'non-breaking', + 'parameter-schema-changed': 'unknown', + 'response-schema-changed': 'unknown', + 'unknown-change': 'unknown', +}; + +/** + * Type guard to check if a value is a valid ChangeSeverity. + * + * @param value - The value to check + * @returns True if the value is a valid ChangeSeverity + */ +export function isChangeSeverity(value: unknown): value is ChangeSeverity { + return ( + typeof value === 'string' && ['breaking', 'non-breaking', 'patch', 'unknown'].includes(value) + ); +} + +/** + * Type guard to check if a value is a valid ChangeType. + * + * @param value - The value to check + * @returns True if the value is a valid ChangeType + */ +export function isChangeType(value: unknown): value is ChangeType { + return typeof value === 'string' && value in CHANGE_TYPE_SEVERITY; +} diff --git a/packages/types/index.ts b/packages/types/index.ts new file mode 100644 index 0000000..c23ccde --- /dev/null +++ b/packages/types/index.ts @@ -0,0 +1,3 @@ +export * from './config.js'; +export * from './governance.js'; +export * from './versioning.js'; diff --git a/packages/types/package.json b/packages/types/package.json new file mode 100644 index 0000000..d596426 --- /dev/null +++ b/packages/types/package.json @@ -0,0 +1,51 @@ +{ + "name": "@contractual/types", + "private": false, + "version": "0.1.0-dev.5", + "description": "TypeScript type definitions for Contractual - schema contract lifecycle orchestration", + "license": "MIT", + "type": "module", + "sideEffects": false, + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "repository": { + "type": "git", + "url": "https://github.com/contractual-dev/contractual.git", + "directory": "packages/types" + }, + "homepage": "https://contractual.dev", + "bugs": { + "url": "https://github.com/contractual-dev/contractual/issues" + }, + "keywords": [ + "contractual", + "types", + "typescript", + "openapi", + "json-schema", + "asyncapi", + "odcs", + "schema", + "contract" + ], + "engines": { + "node": ">=20.0.0" + }, + "scripts": { + "prebuild": "pnpm rimraf dist", + "build": "tsc -p tsconfig.build.json", + "typecheck": "tsc -p tsconfig.build.json --noEmit" + }, + "files": [ + "dist" + ], + "publishConfig": { + "access": "public", + "provenance": true + } +} diff --git a/packages/types/tsconfig.build.json b/packages/types/tsconfig.build.json new file mode 100644 index 0000000..973b05e --- /dev/null +++ b/packages/types/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "composite": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + } +} diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json new file mode 100644 index 0000000..5e75f83 --- /dev/null +++ b/packages/types/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "./dist", + "strict": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "noPropertyAccessFromIndexSignature": true, + "emitDecoratorMetadata": false, + "experimentalDecorators": false + }, + "include": ["*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/types/versioning.ts b/packages/types/versioning.ts new file mode 100644 index 0000000..8afb1cc --- /dev/null +++ b/packages/types/versioning.ts @@ -0,0 +1,206 @@ +import type { DiffResult, LintResult } from './governance.js'; + +/** + * Semver bump type for version increments. + */ +export type BumpType = 'major' | 'minor' | 'patch'; + +/** + * Version entry for a single contract in the history. + */ +export interface VersionEntry { + /** Semver version string (e.g., "1.2.0") */ + version: string; + /** ISO 8601 timestamp of when this version was released */ + released: string; + /** Changeset filenames that contributed to this version */ + changesets?: string[]; +} + +/** + * Contents of versions.json file for a single contract. + * + * @example + * ```json + * { + * "current": "1.2.0", + * "versions": [ + * { "version": "0.1.0", "released": "2026-02-01T10:00:00Z" }, + * { "version": "1.0.0", "released": "2026-02-05T14:30:00Z", "changesets": ["brave-tigers-fly"] } + * ] + * } + * ``` + */ +export interface ContractVersionsFile { + /** Current (latest) version */ + current: string; + /** History of all versions */ + versions: VersionEntry[]; +} + +/** + * Simple version entry used in the legacy VersionsFile format. + * Contains only the essential version tracking fields. + */ +export interface SimpleVersionEntry { + /** Current version */ + version: string; + /** ISO 8601 timestamp of last release */ + released: string; +} + +/** + * Aggregated versions.json file mapping contract names to their version info. + * + * @remarks + * This is the legacy format used for simple version tracking. + * For richer history tracking, use ContractVersionsFile per contract. + */ +export interface VersionsFile { + [contractName: string]: SimpleVersionEntry; +} + +/** + * Parsed changeset file data. + * + * @example + * ```markdown + * --- + * "orders-api": major + * "order-schema": minor + * --- + * + * ## orders-api + * + * **Breaking changes:** + * - Removed endpoint GET /orders/{id}/details + * ``` + */ +export interface ChangesetFile { + /** Filename without path (e.g., "brave-tigers-fly.md") */ + filename: string; + /** Full path to the changeset file */ + path: string; + /** Map of contract name to bump type */ + bumps: Record; + /** Markdown body with change descriptions */ + body: string; +} + +/** + * Result of applying a version bump to a single contract. + */ +export interface BumpResult { + /** Contract name */ + contract: string; + /** Version before bump */ + oldVersion: string; + /** Version after bump */ + newVersion: string; + /** Type of bump applied */ + bumpType: BumpType; + /** Markdown section describing changes from changeset body */ + changes: string; +} + +/** + * Result of the `contractual version` command. + */ +export interface VersionCommandResult { + /** All version bumps that were applied */ + bumps: BumpResult[]; + /** Filenames of changesets that were consumed */ + consumedChangesets: string[]; + /** Updated changelog content (if generated) */ + changelog?: string; +} + +/** + * Result of the `contractual lint` command. + */ +export interface LintCommandResult { + /** Whether all contracts passed linting (no errors) */ + success: boolean; + /** Individual lint results per contract */ + results: LintResult[]; + /** Total error count across all contracts */ + errorCount: number; + /** Total warning count across all contracts */ + warningCount: number; +} + +/** + * Result of the `contractual breaking` command. + */ +export interface BreakingCommandResult { + /** Whether any breaking changes were detected */ + hasBreaking: boolean; + /** Individual diff results per contract */ + results: DiffResult[]; + /** Suggested overall bump type based on highest severity */ + suggestedBump: BumpType | 'none'; +} + +/** + * Result of the `contractual changeset` command. + */ +export interface ChangesetCommandResult { + /** Whether a changeset was created */ + created: boolean; + /** Path to the created changeset file (if created) */ + path?: string; + /** Filename of the created changeset (if created) */ + filename?: string; + /** Map of contract names to their detected bump types */ + bumps: Record; +} + +/** + * Result of the `contractual status` command. + */ +export interface StatusCommandResult { + /** Current contract versions */ + versions: Record; + /** Pending changesets awaiting consumption */ + pendingChangesets: string[]; + /** Predicted version bumps from pending changesets */ + pendingBumps: Record; + /** Whether there are uncommitted spec changes without changesets */ + hasUncommittedChanges: boolean; +} + +/** + * Type guard to check if a value is a valid BumpType. + * + * @param value - The value to check + * @returns True if the value is a valid BumpType + */ +export function isBumpType(value: unknown): value is BumpType { + return ( + typeof value === 'string' && + ['major', 'minor', 'patch'].includes(value) + ); +} + +/** + * Pre-release state stored in .contractual/pre.json + * + * @example + * ```json + * { + * "tag": "beta", + * "enteredAt": "2026-03-10T10:00:00Z", + * "initialVersions": { + * "orders-api": "1.2.0" + * } + * } + * ``` + */ +export interface PreReleaseState { + /** Pre-release tag (e.g., "alpha", "beta", "rc") */ + tag: string; + /** ISO 8601 timestamp when pre-release mode was entered */ + enteredAt: string; + /** Versions of contracts when pre-release mode was entered */ + initialVersions: Record; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc4a422..71a4cfe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,1327 +5,1512 @@ settings: excludeLinksFromLockfile: false importers: - .: - dependencies: - '@manypkg/cli': - specifier: ^0.21.4 - version: 0.21.4 + devDependencies: '@types/node': specifier: ^22.10.2 - version: 22.10.7 + version: 22.19.11 '@typescript-eslint/eslint-plugin': specifier: ^6.7.5 version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3) '@typescript-eslint/parser': specifier: ^6.7.5 version: 6.21.0(eslint@8.57.1)(typescript@5.7.3) - '@vitest/coverage-c8': - specifier: ^0.33.0 - version: 0.33.0(vitest@3.0.3(@types/node@22.10.7)(yaml@2.7.0)) '@vitest/coverage-v8': - specifier: 3.0.3 - version: 3.0.3(vitest@3.0.3(@types/node@22.10.7)(yaml@2.7.0)) - braces: - specifier: 3.0.3 - version: 3.0.3 + specifier: ^3.0.3 + version: 3.2.4(vitest@3.2.4(@types/node@22.19.11)(yaml@2.8.2)) eslint: specifier: ^8.57.0 version: 8.57.1 eslint-config-prettier: specifier: ^9.0.0 - version: 9.1.0(eslint@8.57.1) + version: 9.1.2(eslint@8.57.1) eslint-import-resolver-typescript: specifier: ^3.6.1 - version: 3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1) + version: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) eslint-plugin-import: specifier: ^2.29.1 - version: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1) + version: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-prettier: specifier: ^5.0.1 - version: 5.2.3(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.4.2) - follow-redirects: - specifier: 1.15.6 - version: 1.15.6 - ip: - specifier: 2.0.1 - version: 2.0.1 + version: 5.5.5(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.8.1) + husky: + specifier: ^8.0.3 + version: 8.0.3 + json-schema-typed: + specifier: ^8.0.2 + version: 8.0.2 lerna: - specifier: ^7.3.1 - version: 7.4.2(encoding@0.1.13) + specifier: ^8.2.3 + version: 8.2.4(@types/node@22.19.11)(encoding@0.1.13) lint-staged: specifier: ^14.0.1 version: 14.0.1(enquirer@2.3.6) - madge: - specifier: ^7.0.0 - version: 7.0.0(typescript@5.7.3) - micromatch: - specifier: 4.0.8 - version: 4.0.8 prettier: specifier: ^3.2.5 - version: 3.4.2 + version: 3.8.1 rimraf: specifier: ^5.0.5 version: 5.0.10 - rxjs: - specifier: ^7.8.1 - version: 7.8.1 - tar: - specifier: 6.2.0 - version: 6.2.0 - ts-jest: - specifier: ^29.1.3 - version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)))(typescript@5.7.3) - ts-node: - specifier: ^10.9.1 - version: 10.9.2(@types/node@22.10.7)(typescript@5.7.3) typescript: specifier: ~5.7.2 version: 5.7.3 vitest: specifier: ^3.0.3 - version: 3.0.3(@types/node@22.10.7)(yaml@2.7.0) + version: 3.2.4(@types/node@22.19.11)(yaml@2.8.2) + yaml: + specifier: ^2.8.2 + version: 2.8.2 + + packages/changesets: + dependencies: + '@contractual/types': + specifier: workspace:* + version: link:../types + semver: + specifier: ^7.7.1 + version: 7.7.4 + yaml: + specifier: ^2.7.0 + version: 2.8.2 devDependencies: - husky: - specifier: ^8.0.3 - version: 8.0.3 + '@types/semver': + specifier: ^7.5.8 + version: 7.7.1 packages/cli: dependencies: - '@contractual/generators.contract': + '@contractual/changesets': specifier: workspace:* - version: link:../generators/contract - '@contractual/generators.diff': + version: link:../changesets + '@contractual/governance': specifier: workspace:* - version: link:../generators/diff - '@contractual/generators.spec': + version: link:../governance + '@contractual/types': specifier: workspace:* - version: link:../generators/spec + version: link:../types + '@inquirer/prompts': + specifier: ^8.1.0 + version: 8.1.0(@types/node@22.19.11) + ajv: + specifier: ^8.17.1 + version: 8.18.0 + ajv-formats: + specifier: ^3.0.1 + version: 3.0.1(ajv@8.18.0) chalk: specifier: ^5.4.1 - version: 5.4.1 + version: 5.6.2 commander: specifier: ^12.1.0 version: 12.1.0 - inquirer: - specifier: ^12.3.2 - version: 12.3.2(@types/node@22.10.7) + fast-glob: + specifier: ^3.3.3 + version: 3.3.3 ora: specifier: ^8.1.1 - version: 8.1.1 + version: 8.2.0 + yaml: + specifier: ^2.7.0 + version: 2.8.2 devDependencies: - '@vitest/coverage-c8': - specifier: ^0.33.0 - version: 0.33.0(vitest@3.0.3(@types/node@22.10.7)(yaml@2.7.0)) + '@vitest/coverage-v8': + specifier: ^3.0.0 + version: 3.2.4(vitest@3.2.4(@types/node@22.19.11)(yaml@2.8.2)) vitest: specifier: ^3.0.3 - version: 3.0.3(@types/node@22.10.7)(yaml@2.7.0) + version: 3.2.4(@types/node@22.19.11)(yaml@2.8.2) - packages/contract: + packages/differs.core: dependencies: - '@ts-rest/core': - specifier: ^3.51.0 - version: 3.51.0(@types/node@22.10.7)(zod@3.24.1) - axios: - specifier: ^1.7.9 - version: 1.7.9 - zod: - specifier: ^3.24.1 - version: 3.24.1 - - packages/generators/contract: - dependencies: - '@apidevtools/swagger-parser': - specifier: ^10.1.1 - version: 10.1.1(openapi-types@12.1.3) - chalk: - specifier: ^5.4.1 - version: 5.4.1 - handlebars: - specifier: ^4.7.8 - version: 4.7.8 - openapi-types: - specifier: ^12.1.3 - version: 12.1.3 - openapi-zod-client: - specifier: ^1.18.2 - version: 1.18.2 - openapi3-ts: - specifier: ^4.4.0 - version: 4.4.0 + '@contractual/types': + specifier: workspace:* + version: link:../types devDependencies: - ora: - specifier: ^8.1.1 - version: 8.1.1 - typescript: - specifier: ~5.7.2 - version: 5.7.3 + rimraf: + specifier: ^5.0.5 + version: 5.0.10 - packages/generators/diff: + packages/differs.json-schema: dependencies: - chalk: - specifier: ^5.4.1 - version: 5.4.1 - openapi-diff: - specifier: ^0.23.7 - version: 0.23.7(openapi-types@12.1.3) - openapi-types: - specifier: ^12.1.3 - version: 12.1.3 - semver: - specifier: ^7.6.3 - version: 7.6.3 - table: - specifier: ^6.9.0 - version: 6.9.0 + '@contractual/differs.core': + specifier: workspace:* + version: link:../differs.core + '@contractual/types': + specifier: workspace:* + version: link:../types devDependencies: - '@types/semver': - specifier: ^7.5.8 - version: 7.5.8 - '@vitest/coverage-c8': - specifier: ^0.33.0 - version: 0.33.0(vitest@3.0.3(@types/node@22.10.7)(yaml@2.7.0)) - typescript: - specifier: ~5.7.2 - version: 5.7.3 + rimraf: + specifier: ^5.0.5 + version: 5.0.10 vitest: specifier: ^3.0.3 - version: 3.0.3(@types/node@22.10.7)(yaml@2.7.0) + version: 3.2.4(@types/node@22.19.11)(yaml@2.8.2) - packages/generators/spec: + packages/differs.openapi: dependencies: - '@contractual/generators.diff': + '@contractual/differs.core': specifier: workspace:* - version: link:../diff - '@typespec/compiler': - specifier: ^0.63.0 - version: 0.63.0 - '@typespec/http': - specifier: ^0.63.0 - version: 0.63.0(@typespec/compiler@0.63.0) - '@typespec/openapi': - specifier: ^0.63.0 - version: 0.63.0(@typespec/compiler@0.63.0)(@typespec/http@0.63.0(@typespec/compiler@0.63.0)) - '@typespec/openapi3': - specifier: ^0.63.0 - version: 0.63.0(@typespec/compiler@0.63.0)(@typespec/http@0.63.0(@typespec/compiler@0.63.0))(@typespec/openapi@0.63.0(@typespec/compiler@0.63.0)(@typespec/http@0.63.0(@typespec/compiler@0.63.0)))(@typespec/versioning@0.63.0(@typespec/compiler@0.63.0)) - '@typespec/rest': - specifier: ^0.63.1 - version: 0.63.1(@typespec/compiler@0.63.0)(@typespec/http@0.63.0(@typespec/compiler@0.63.0)) - '@typespec/versioning': - specifier: ^0.63.0 - version: 0.63.0(@typespec/compiler@0.63.0) - semver: - specifier: ^7.6.3 - version: 7.6.3 - yaml: - specifier: ^2.7.0 - version: 2.7.0 + version: link:../differs.core + '@contractual/types': + specifier: workspace:* + version: link:../types + '@redocly/openapi-core': + specifier: ^1.0.0 + version: 1.34.6 devDependencies: - '@types/semver': - specifier: ^7.5.8 - version: 7.5.8 - chalk: - specifier: ^5.4.1 - version: 5.4.1 - inquirer: - specifier: ^12.3.2 - version: 12.3.2(@types/node@22.10.7) - openapi-types: - specifier: ^12.1.3 - version: 12.1.3 - ora: - specifier: ^8.1.1 - version: 8.1.1 - typescript: - specifier: ~5.7.2 - version: 5.7.3 - -packages: - - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - - '@apidevtools/json-schema-ref-parser@11.7.2': - resolution: {integrity: sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==} - engines: {node: '>= 16'} - - '@apidevtools/json-schema-ref-parser@9.0.9': - resolution: {integrity: sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==} - - '@apidevtools/json-schema-ref-parser@9.1.2': - resolution: {integrity: sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==} - - '@apidevtools/openapi-schemas@2.1.0': - resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==} - engines: {node: '>=10'} - - '@apidevtools/swagger-methods@3.0.2': - resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==} - - '@apidevtools/swagger-parser@10.0.3': - resolution: {integrity: sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==} - peerDependencies: - openapi-types: '>=7' - - '@apidevtools/swagger-parser@10.1.1': - resolution: {integrity: sha512-u/kozRnsPO/x8QtKYJOqoGtC4kH6yg1lfYkB9Au0WhYB0FNLpyFusttQtvhlwjtG3rOwiRz4D8DnnXa8iEpIKA==} - peerDependencies: - openapi-types: '>=7' - - '@babel/code-frame@7.25.9': - resolution: {integrity: sha512-z88xeGxnzehn2sqZ8UdGQEvYErF1odv2CftxInpSYJt6uHuPe9YjahKZITGs3l5LeI9d2ROG+obuDAoSlqbNfQ==} - engines: {node: '>=6.9.0'} - - '@babel/code-frame@7.26.2': - resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.26.5': - resolution: {integrity: sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.26.0': - resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.26.5': - resolution: {integrity: sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.26.5': - resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.25.9': - resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.26.0': - resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-plugin-utils@7.26.5': - resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.25.9': - resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.25.9': - resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.25.9': - resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} - engines: {node: '>=6.9.0'} + rimraf: + specifier: ^5.0.5 + version: 5.0.10 - '@babel/helpers@7.26.0': - resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} - engines: {node: '>=6.9.0'} + packages/governance: + dependencies: + '@contractual/differs.json-schema': + specifier: workspace:* + version: link:../differs.json-schema + '@contractual/differs.openapi': + specifier: workspace:* + version: link:../differs.openapi + '@contractual/types': + specifier: workspace:* + version: link:../types + ajv: + specifier: ^8.17.1 + version: 8.18.0 + ajv-formats: + specifier: ^3.0.1 + version: 3.0.1(ajv@8.18.0) + devDependencies: + '@redocly/openapi-core': + specifier: ^1.0.0 + version: 1.34.6 - '@babel/highlight@7.25.9': - resolution: {integrity: sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==} - engines: {node: '>=6.9.0'} + packages/types: {} - '@babel/parser@7.26.5': - resolution: {integrity: sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==} - engines: {node: '>=6.0.0'} +packages: + '@ampproject/remapping@2.3.0': + resolution: + { + integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==, + } + engines: { node: '>=6.0.0' } + + '@babel/code-frame@7.29.0': + resolution: + { + integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-string-parser@7.27.1': + resolution: + { + integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-validator-identifier@7.28.5': + resolution: + { + integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==, + } + engines: { node: '>=6.9.0' } + + '@babel/parser@7.29.0': + resolution: + { + integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==, + } + engines: { node: '>=6.0.0' } hasBin: true - '@babel/plugin-syntax-async-generators@7.8.4': - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-bigint@7.8.3': - resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-class-properties@7.12.13': - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-class-static-block@7.14.5': - resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-attributes@7.26.0': - resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-meta@7.10.4': - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-json-strings@7.8.3': - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-jsx@7.25.9': - resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-logical-assignment-operators@7.10.4': - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-numeric-separator@7.10.4': - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-object-rest-spread@7.8.3': - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-optional-catch-binding@7.8.3': - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-optional-chaining@7.8.3': - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-private-property-in-object@7.14.5': - resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-top-level-await@7.14.5': - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-typescript@7.25.9': - resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/runtime@7.26.0': - resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} - engines: {node: '>=6.9.0'} - - '@babel/template@7.25.9': - resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.26.5': - resolution: {integrity: sha512-rkOSPOw+AXbgtwUga3U4u8RpoK9FEFWBNAlTpcnkLFjL5CT+oyHNuUUC/xx6XefEJ16r38r8Bc/lfp6rYuHeJQ==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.26.5': - resolution: {integrity: sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==} - engines: {node: '>=6.9.0'} - - '@bcoe/v8-coverage@0.2.3': - resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@babel/types@7.29.0': + resolution: + { + integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==, + } + engines: { node: '>=6.9.0' } '@bcoe/v8-coverage@1.0.2': - resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} - engines: {node: '>=18'} - - '@cspotcode/source-map-support@0.8.1': - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - - '@dependents/detective-less@4.1.0': - resolution: {integrity: sha512-KrkT6qO5NxqNfy68sBl6CTSoJ4SNDIS5iQArkibhlbGU4LaDukZ3q2HIkh8aUKDio6o4itU4xDR7t82Y2eP1Bg==} - engines: {node: '>=14'} - - '@esbuild/aix-ppc64@0.24.2': - resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==, + } + engines: { node: '>=18' } + + '@emnapi/core@1.8.1': + resolution: + { + integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==, + } + + '@emnapi/runtime@1.8.1': + resolution: + { + integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==, + } + + '@emnapi/wasi-threads@1.1.0': + resolution: + { + integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==, + } + + '@esbuild/aix-ppc64@0.27.3': + resolution: + { + integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==, + } + engines: { node: '>=18' } cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.24.2': - resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} - engines: {node: '>=18'} + '@esbuild/android-arm64@0.27.3': + resolution: + { + integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==, + } + engines: { node: '>=18' } cpu: [arm64] os: [android] - '@esbuild/android-arm@0.24.2': - resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} - engines: {node: '>=18'} + '@esbuild/android-arm@0.27.3': + resolution: + { + integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==, + } + engines: { node: '>=18' } cpu: [arm] os: [android] - '@esbuild/android-x64@0.24.2': - resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} - engines: {node: '>=18'} + '@esbuild/android-x64@0.27.3': + resolution: + { + integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==, + } + engines: { node: '>=18' } cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.24.2': - resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} - engines: {node: '>=18'} + '@esbuild/darwin-arm64@0.27.3': + resolution: + { + integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==, + } + engines: { node: '>=18' } cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.24.2': - resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} - engines: {node: '>=18'} + '@esbuild/darwin-x64@0.27.3': + resolution: + { + integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==, + } + engines: { node: '>=18' } cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.24.2': - resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} - engines: {node: '>=18'} + '@esbuild/freebsd-arm64@0.27.3': + resolution: + { + integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==, + } + engines: { node: '>=18' } cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.24.2': - resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} - engines: {node: '>=18'} + '@esbuild/freebsd-x64@0.27.3': + resolution: + { + integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==, + } + engines: { node: '>=18' } cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.24.2': - resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} - engines: {node: '>=18'} + '@esbuild/linux-arm64@0.27.3': + resolution: + { + integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==, + } + engines: { node: '>=18' } cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.24.2': - resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} - engines: {node: '>=18'} + '@esbuild/linux-arm@0.27.3': + resolution: + { + integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==, + } + engines: { node: '>=18' } cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.24.2': - resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} - engines: {node: '>=18'} + '@esbuild/linux-ia32@0.27.3': + resolution: + { + integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==, + } + engines: { node: '>=18' } cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.24.2': - resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} - engines: {node: '>=18'} + '@esbuild/linux-loong64@0.27.3': + resolution: + { + integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==, + } + engines: { node: '>=18' } cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.24.2': - resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} - engines: {node: '>=18'} + '@esbuild/linux-mips64el@0.27.3': + resolution: + { + integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==, + } + engines: { node: '>=18' } cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.24.2': - resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} - engines: {node: '>=18'} + '@esbuild/linux-ppc64@0.27.3': + resolution: + { + integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==, + } + engines: { node: '>=18' } cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.24.2': - resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} - engines: {node: '>=18'} + '@esbuild/linux-riscv64@0.27.3': + resolution: + { + integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==, + } + engines: { node: '>=18' } cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.24.2': - resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} - engines: {node: '>=18'} + '@esbuild/linux-s390x@0.27.3': + resolution: + { + integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==, + } + engines: { node: '>=18' } cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.24.2': - resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} - engines: {node: '>=18'} + '@esbuild/linux-x64@0.27.3': + resolution: + { + integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==, + } + engines: { node: '>=18' } cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.24.2': - resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} - engines: {node: '>=18'} + '@esbuild/netbsd-arm64@0.27.3': + resolution: + { + integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==, + } + engines: { node: '>=18' } cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.24.2': - resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} - engines: {node: '>=18'} + '@esbuild/netbsd-x64@0.27.3': + resolution: + { + integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==, + } + engines: { node: '>=18' } cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.24.2': - resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} - engines: {node: '>=18'} + '@esbuild/openbsd-arm64@0.27.3': + resolution: + { + integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==, + } + engines: { node: '>=18' } cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.24.2': - resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} - engines: {node: '>=18'} + '@esbuild/openbsd-x64@0.27.3': + resolution: + { + integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==, + } + engines: { node: '>=18' } cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.24.2': - resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} - engines: {node: '>=18'} + '@esbuild/openharmony-arm64@0.27.3': + resolution: + { + integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.3': + resolution: + { + integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==, + } + engines: { node: '>=18' } cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.24.2': - resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} - engines: {node: '>=18'} + '@esbuild/win32-arm64@0.27.3': + resolution: + { + integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==, + } + engines: { node: '>=18' } cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.24.2': - resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} - engines: {node: '>=18'} + '@esbuild/win32-ia32@0.27.3': + resolution: + { + integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==, + } + engines: { node: '>=18' } cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.24.2': - resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} - engines: {node: '>=18'} + '@esbuild/win32-x64@0.27.3': + resolution: + { + integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==, + } + engines: { node: '>=18' } cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.4.1': - resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint-community/eslint-utils@4.9.1': + resolution: + { + integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==, + } + 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.1': - resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.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/eslintrc@2.1.4': - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + resolution: + { + integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } '@eslint/js@8.57.1': - resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@gar/promisify@1.1.3': - resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} + resolution: + { + integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } '@humanwhocodes/config-array@0.13.0': - resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead + resolution: + { + integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==, + } + engines: { node: '>=10.10.0' } '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/momoa@2.0.4': - resolution: {integrity: sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA==} - engines: {node: '>=10.10.0'} + resolution: + { + integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==, + } + engines: { node: '>=12.22' } '@humanwhocodes/object-schema@2.0.3': - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead + resolution: + { + integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==, + } '@hutson/parse-repository-url@3.0.2': - resolution: {integrity: sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==} - engines: {node: '>=6.9.0'} - - '@inquirer/checkbox@4.0.6': - resolution: {integrity: sha512-PgP35JfmGjHU0LSXOyRew0zHuA9N6OJwOlos1fZ20b7j8ISeAdib3L+n0jIxBtX958UeEpte6xhG/gxJ5iUqMw==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==, + } + engines: { node: '>=6.9.0' } + + '@inquirer/ansi@2.0.2': + resolution: + { + integrity: sha512-SYLX05PwJVnW+WVegZt1T4Ip1qba1ik+pNJPDiqvk6zS5Y/i8PhRzLpGEtVd7sW0G8cMtkD8t4AZYhQwm8vnww==, + } + engines: { node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0' } + + '@inquirer/checkbox@5.0.3': + resolution: + { + integrity: sha512-xtQP2eXMFlOcAhZ4ReKP2KZvDIBb1AnCfZ81wWXG3DXLVH0f0g4obE0XDPH+ukAEMRcZT0kdX2AS1jrWGXbpxw==, + } + engines: { node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0' } peerDependencies: '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - '@inquirer/confirm@5.1.3': - resolution: {integrity: sha512-fuF9laMmHoOgWapF9h9hv6opA5WvmGFHsTYGCmuFxcghIhEhb3dN0CdQR4BUMqa2H506NCj8cGX4jwMsE4t6dA==} - engines: {node: '>=18'} + '@inquirer/confirm@6.0.3': + resolution: + { + integrity: sha512-lyEvibDFL+NA5R4xl8FUmNhmu81B+LDL9L/MpKkZlQDJZXzG8InxiqYxiAlQYa9cqLLhYqKLQwZqXmSTqCLjyw==, + } + engines: { node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0' } peerDependencies: '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - '@inquirer/core@10.1.4': - resolution: {integrity: sha512-5y4/PUJVnRb4bwWY67KLdebWOhOc7xj5IP2J80oWXa64mVag24rwQ1VAdnj7/eDY/odhguW0zQ1Mp1pj6fO/2w==} - engines: {node: '>=18'} - - '@inquirer/editor@4.2.3': - resolution: {integrity: sha512-S9KnIOJuTZpb9upeRSBBhoDZv7aSV3pG9TECrBj0f+ZsFwccz886hzKBrChGrXMJwd4NKY+pOA9Vy72uqnd6Eg==} - engines: {node: '>=18'} + '@inquirer/core@11.1.0': + resolution: + { + integrity: sha512-+jD/34T1pK8M5QmZD/ENhOfXdl9Zr+BrQAUc5h2anWgi7gggRq15ZbiBeLoObj0TLbdgW7TAIQRU2boMc9uOKQ==, + } + engines: { node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0' } peerDependencies: '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - '@inquirer/expand@4.0.6': - resolution: {integrity: sha512-TRTfi1mv1GeIZGyi9PQmvAaH65ZlG4/FACq6wSzs7Vvf1z5dnNWsAAXBjWMHt76l+1hUY8teIqJFrWBk5N6gsg==} - engines: {node: '>=18'} + '@inquirer/editor@5.0.3': + resolution: + { + integrity: sha512-wYyQo96TsAqIciP/r5D3cFeV8h4WqKQ/YOvTg5yOfP2sqEbVVpbxPpfV3LM5D0EP4zUI3EZVHyIUIllnoIa8OQ==, + } + engines: { node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0' } peerDependencies: '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - '@inquirer/figures@1.0.9': - resolution: {integrity: sha512-BXvGj0ehzrngHTPTDqUoDT3NXL8U0RxUk2zJm2A66RhCEIWdtU1v6GuUqNAgArW4PQ9CinqIWyHdQgdwOj06zQ==} - engines: {node: '>=18'} - - '@inquirer/input@4.1.3': - resolution: {integrity: sha512-zeo++6f7hxaEe7OjtMzdGZPHiawsfmCZxWB9X1NpmYgbeoyerIbWemvlBxxl+sQIlHC0WuSAG19ibMq3gbhaqQ==} - engines: {node: '>=18'} + '@inquirer/expand@5.0.3': + resolution: + { + integrity: sha512-2oINvuL27ujjxd95f6K2K909uZOU2x1WiAl7Wb1X/xOtL8CgQ1kSxzykIr7u4xTkXkXOAkCuF45T588/YKee7w==, + } + engines: { node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0' } peerDependencies: '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - '@inquirer/number@3.0.6': - resolution: {integrity: sha512-xO07lftUHk1rs1gR0KbqB+LJPhkUNkyzV/KhH+937hdkMazmAYHLm1OIrNKpPelppeV1FgWrgFDjdUD8mM+XUg==} - engines: {node: '>=18'} + '@inquirer/external-editor@1.0.3': + resolution: + { + integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==, + } + engines: { node: '>=18' } peerDependencies: '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - '@inquirer/password@4.0.6': - resolution: {integrity: sha512-QLF0HmMpHZPPMp10WGXh6F+ZPvzWE7LX6rNoccdktv/Rov0B+0f+eyXkAcgqy5cH9V+WSpbLxu2lo3ysEVK91w==} - engines: {node: '>=18'} + '@inquirer/external-editor@2.0.2': + resolution: + { + integrity: sha512-X/fMXK7vXomRWEex1j8mnj7s1mpnTeP4CO/h2gysJhHLT2WjBnLv4ZQEGpm/kcYI8QfLZ2fgW+9kTKD+jeopLg==, + } + engines: { node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0' } peerDependencies: '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - '@inquirer/prompts@7.2.3': - resolution: {integrity: sha512-hzfnm3uOoDySDXfDNOm9usOuYIaQvTgKp/13l1uJoe6UNY+Zpcn2RYt0jXz3yA+yemGHvDOxVzqWl3S5sQq53Q==} - engines: {node: '>=18'} + '@inquirer/figures@2.0.2': + resolution: + { + integrity: sha512-qXm6EVvQx/FmnSrCWCIGtMHwqeLgxABP8XgcaAoywsL0NFga9gD5kfG0gXiv80GjK9Hsoz4pgGwF/+CjygyV9A==, + } + engines: { node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0' } + + '@inquirer/input@5.0.3': + resolution: + { + integrity: sha512-4R0TdWl53dtp79Vs6Df2OHAtA2FVNqya1hND1f5wjHWxZJxwDMSNB1X5ADZJSsQKYAJ5JHCTO+GpJZ42mK0Otw==, + } + engines: { node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0' } peerDependencies: '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - '@inquirer/rawlist@4.0.6': - resolution: {integrity: sha512-QoE4s1SsIPx27FO4L1b1mUjVcoHm1pWE/oCmm4z/Hl+V1Aw5IXl8FYYzGmfXaBT0l/sWr49XmNSiq7kg3Kd/Lg==} - engines: {node: '>=18'} + '@inquirer/number@4.0.3': + resolution: + { + integrity: sha512-TjQLe93GGo5snRlu83JxE38ZPqj5ZVggL+QqqAF2oBA5JOJoxx25GG3EGH/XN/Os5WOmKfO8iLVdCXQxXRZIMQ==, + } + engines: { node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0' } peerDependencies: '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - '@inquirer/search@3.0.6': - resolution: {integrity: sha512-eFZ2hiAq0bZcFPuFFBmZEtXU1EarHLigE+ENCtpO+37NHCl4+Yokq1P/d09kUblObaikwfo97w+0FtG/EXl5Ng==} - engines: {node: '>=18'} + '@inquirer/password@5.0.3': + resolution: + { + integrity: sha512-rCozGbUMAHedTeYWEN8sgZH4lRCdgG/WinFkit6ZPsp8JaNg2T0g3QslPBS5XbpORyKP/I+xyBO81kFEvhBmjA==, + } + engines: { node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0' } peerDependencies: '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - '@inquirer/select@4.0.6': - resolution: {integrity: sha512-yANzIiNZ8fhMm4NORm+a74+KFYHmf7BZphSOBovIzYPVLquseTGEkU5l2UTnBOf5k0VLmTgPighNDLE9QtbViQ==} - engines: {node: '>=18'} + '@inquirer/prompts@8.1.0': + resolution: + { + integrity: sha512-LsZMdKcmRNF5LyTRuZE5nWeOjganzmN3zwbtNfcs6GPh3I2TsTtF1UYZlbxVfhxd+EuUqLGs/Lm3Xt4v6Az1wA==, + } + engines: { node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0' } peerDependencies: '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - '@inquirer/type@3.0.2': - resolution: {integrity: sha512-ZhQ4TvhwHZF+lGhQ2O/rsjo80XoZR5/5qhOY3t6FJuX5XBg5Be8YzYTvaUGJnc12AUGI2nr4QSUE4PhKSigx7g==} - engines: {node: '>=18'} + '@inquirer/rawlist@5.1.0': + resolution: + { + integrity: sha512-yUCuVh0jW026Gr2tZlG3kHignxcrLKDR3KBp+eUgNz+BAdSeZk0e18yt2gyBr+giYhj/WSIHCmPDOgp1mT2niQ==, + } + engines: { node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0' } peerDependencies: '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@istanbuljs/load-nyc-config@1.1.0': - resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} - engines: {node: '>=8'} - - '@istanbuljs/schema@0.1.3': - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} - - '@jest/console@29.7.0': - resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/core@29.7.0': - resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@inquirer/search@4.0.3': + resolution: + { + integrity: sha512-lzqVw0YwuKYetk5VwJ81Ba+dyVlhseHPx9YnRKQgwXdFS0kEavCz2gngnNhnMIxg8+j1N/rUl1t5s1npwa7bqg==, + } + engines: { node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0' } peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + '@types/node': '>=18' peerDependenciesMeta: - node-notifier: + '@types/node': optional: true - '@jest/environment@29.7.0': - resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/expect-utils@29.7.0': - resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/expect@29.7.0': - resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/fake-timers@29.7.0': - resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/globals@29.7.0': - resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/reporters@29.7.0': - resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@inquirer/select@5.0.3': + resolution: + { + integrity: sha512-M+ynbwS0ecQFDYMFrQrybA0qL8DV0snpc4kKevCCNaTpfghsRowRY7SlQBeIYNzHqXtiiz4RG9vTOeb/udew7w==, + } + engines: { node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0' } peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + '@types/node': '>=18' peerDependenciesMeta: - node-notifier: + '@types/node': optional: true - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/source-map@29.6.3': - resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/test-result@29.7.0': - resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/test-sequencer@29.7.0': - resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@inquirer/type@4.0.2': + resolution: + { + integrity: sha512-cae7mzluplsjSdgFA6ACLygb5jC8alO0UUnFPyu0E7tNRPrL+q/f8VcSXp+cjZQ7l5CMpDpi2G1+IQvkOiL1Lw==, + } + engines: { node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0' } + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - '@jest/transform@29.7.0': - resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@isaacs/cliui@8.0.2': + resolution: + { + integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==, + } + engines: { node: '>=12' } + + '@isaacs/string-locale-compare@1.1.0': + resolution: + { + integrity: sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==, + } - '@jest/types@29.6.3': - resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@istanbuljs/schema@0.1.3': + resolution: + { + integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==, + } + engines: { node: '>=8' } - '@jridgewell/gen-mapping@0.3.8': - resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} - engines: {node: '>=6.0.0'} + '@jest/schemas@29.6.3': + resolution: + { + integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + '@jridgewell/gen-mapping@0.3.13': + resolution: + { + integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==, + } '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - - '@jsdevtools/ono@7.1.3': - resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} - - '@lerna/child-process@7.4.2': - resolution: {integrity: sha512-je+kkrfcvPcwL5Tg8JRENRqlbzjdlZXyaR88UcnCdNW0AJ1jX9IfHRys1X7AwSroU2ug8ESNC+suoBw1vX833Q==} - engines: {node: '>=16.0.0'} - - '@lerna/create@7.4.2': - resolution: {integrity: sha512-1wplFbQ52K8E/unnqB0Tq39Z4e+NEoNrpovEnl6GpsTUrC6WDp8+w0Le2uCBV0hXyemxChduCkLz4/y1H1wTeg==} - engines: {node: '>=16.0.0'} - - '@liuli-util/fs-extra@0.1.0': - resolution: {integrity: sha512-eaAyDyMGT23QuRGbITVY3SOJff3G9ekAAyGqB9joAnTBmqvFN+9a1FazOdO70G6IUqgpKV451eBHYSRcOJ/FNQ==} - - '@manypkg/cli@0.21.4': - resolution: {integrity: sha512-EACxxb+c/t6G0l1FrlyewZeBnyR5V1cLkXjnBfsay5TN1UgbilFpG6POglzn+lVJet9NqnEKe3RLHABzkIDZ0Q==} - engines: {node: '>=14.18.0'} - hasBin: true - - '@manypkg/find-root@2.2.3': - resolution: {integrity: sha512-jtEZKczWTueJYHjGpxU3KJQ08Gsrf4r6Q2GjmPp/RGk5leeYAA1eyDADSAF+KVCsQ6EwZd/FMcOFCoMhtqdCtQ==} - engines: {node: '>=14.18.0'} - - '@manypkg/get-packages@2.2.2': - resolution: {integrity: sha512-3+Zd8kLZmsyJFmWTBtY0MAuCErI7yKB2cjMBlujvSVKZ2R/BMXi0kjCXu2dtRlSq/ML86t1FkumT0yreQ3n8OQ==} - engines: {node: '>=14.18.0'} - - '@manypkg/tools@1.1.2': - resolution: {integrity: sha512-3lBouSuF7CqlseLB+FKES0K4FQ02JrbEoRtJhxnsyB1s5v4AP03gsoohN8jp7DcOImhaR9scYdztq3/sLfk/qQ==} - engines: {node: '>=14.18.0'} + resolution: + { + integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==, + } + engines: { node: '>=6.0.0' } + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: + { + integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==, + } + + '@jridgewell/trace-mapping@0.3.31': + resolution: + { + integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==, + } + + '@lerna/create@8.2.4': + resolution: + { + integrity: sha512-A8AlzetnS2WIuhijdAzKUyFpR5YbLLfV3luQ4lzBgIBgRfuoBDZeF+RSZPhra+7A6/zTUlrbhKZIOi/MNhqgvQ==, + } + engines: { node: '>=18.0.0' } + + '@napi-rs/wasm-runtime@0.2.12': + resolution: + { + integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==, + } + + '@napi-rs/wasm-runtime@0.2.4': + resolution: + { + integrity: sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==, + } '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} + resolution: + { + integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==, + } + engines: { node: '>= 8' } '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} + resolution: + { + integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==, + } + engines: { node: '>= 8' } '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} + resolution: + { + integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==, + } + engines: { node: '>= 8' } '@nolyfill/is-core-module@1.0.39': - resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} - engines: {node: '>=12.4.0'} - - '@npmcli/fs@2.1.2': - resolution: {integrity: sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + resolution: + { + integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==, + } + engines: { node: '>=12.4.0' } + + '@npmcli/agent@2.2.2': + resolution: + { + integrity: sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==, + } + engines: { node: ^16.14.0 || >=18.0.0 } + + '@npmcli/arborist@7.5.4': + resolution: + { + integrity: sha512-nWtIc6QwwoUORCRNzKx4ypHqCk3drI+5aeYdMTQQiRCcn4lOOgfQh7WyZobGYTxXPSq1VwV53lkpN/BRlRk08g==, + } + engines: { node: ^16.14.0 || >=18.0.0 } + hasBin: true '@npmcli/fs@3.1.1': - resolution: {integrity: sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - '@npmcli/git@4.1.0': - resolution: {integrity: sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + resolution: + { + integrity: sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + + '@npmcli/git@5.0.8': + resolution: + { + integrity: sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==, + } + engines: { node: ^16.14.0 || >=18.0.0 } '@npmcli/installed-package-contents@2.1.0': - resolution: {integrity: sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + resolution: + { + integrity: sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } hasBin: true - '@npmcli/move-file@2.0.1': - resolution: {integrity: sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This functionality has been moved to @npmcli/fs + '@npmcli/map-workspaces@3.0.6': + resolution: + { + integrity: sha512-tkYs0OYnzQm6iIRdfy+LcLBjcKuQCeE5YLb8KnrIlutJfheNaPvPpgoFEyEFgbjzl5PLZ3IA/BWAwRU0eHuQDA==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + + '@npmcli/metavuln-calculator@7.1.1': + resolution: + { + integrity: sha512-Nkxf96V0lAx3HCpVda7Vw4P23RILgdi/5K1fmj2tZkWIYLpXAN8k2UVVOsW16TsS5F8Ws2I7Cm+PU1/rsVF47g==, + } + engines: { node: ^16.14.0 || >=18.0.0 } + + '@npmcli/name-from-folder@2.0.0': + resolution: + { + integrity: sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } '@npmcli/node-gyp@3.0.0': - resolution: {integrity: sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - '@npmcli/promise-spawn@6.0.2': - resolution: {integrity: sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - '@npmcli/run-script@6.0.2': - resolution: {integrity: sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - '@nrwl/devkit@16.10.0': - resolution: {integrity: sha512-fRloARtsDQoQgQ7HKEy0RJiusg/HSygnmg4gX/0n/Z+SUS+4KoZzvHjXc6T5ZdEiSjvLypJ+HBM8dQzIcVACPQ==} - - '@nrwl/tao@16.10.0': - resolution: {integrity: sha512-QNAanpINbr+Pod6e1xNgFbzK1x5wmZl+jMocgiEFXZ67KHvmbD6MAQQr0MMz+GPhIu7EE4QCTLTyCEMlAG+K5Q==} - hasBin: true - - '@nx/devkit@16.10.0': - resolution: {integrity: sha512-IvKQqRJFDDiaj33SPfGd3ckNHhHi6ceEoqCbAP4UuMXOPPVOX6H0KVk+9tknkPb48B7jWIw6/AgOeWkBxPRO5w==} + resolution: + { + integrity: sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + + '@npmcli/package-json@5.2.0': + resolution: + { + integrity: sha512-qe/kiqqkW0AGtvBjL8TJKZk/eBBSpnJkUWvHdQ9jM2lKHXRYYJuyNpJPlJw3c8QjC2ow6NZYiLExhUaeJelbxQ==, + } + engines: { node: ^16.14.0 || >=18.0.0 } + + '@npmcli/promise-spawn@7.0.2': + resolution: + { + integrity: sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==, + } + engines: { node: ^16.14.0 || >=18.0.0 } + + '@npmcli/query@3.1.0': + resolution: + { + integrity: sha512-C/iR0tk7KSKGldibYIB9x8GtO/0Bd0I2mhOaDb8ucQL/bQVTmGoeREaFj64Z5+iCBRf3dQfed0CjJL7I8iTkiQ==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + + '@npmcli/redact@2.0.1': + resolution: + { + integrity: sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==, + } + engines: { node: ^16.14.0 || >=18.0.0 } + + '@npmcli/run-script@8.1.0': + resolution: + { + integrity: sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==, + } + engines: { node: ^16.14.0 || >=18.0.0 } + + '@nx/devkit@20.8.2': + resolution: + { + integrity: sha512-rr9p2/tZDQivIpuBUpZaFBK6bZ+b5SAjZk75V4tbCUqGW3+5OPuVvBPm+X+7PYwUF6rwSpewxkjWNeGskfCe+Q==, + } peerDependencies: - nx: '>= 15 <= 17' - - '@nx/nx-darwin-arm64@16.10.0': - resolution: {integrity: sha512-YF+MIpeuwFkyvM5OwgY/rTNRpgVAI/YiR0yTYCZR+X3AAvP775IVlusNgQ3oedTBRUzyRnI4Tknj1WniENFsvQ==} - engines: {node: '>= 10'} + nx: '>= 19 <= 21' + + '@nx/nx-darwin-arm64@20.8.2': + resolution: + { + integrity: sha512-t+bmCn6sRPNGU6hnSyWNvbQYA/KgsxGZKYlaCLRwkNhI2akModcBUqtktJzCKd1XHDqs6EkEFBWjFr8/kBEkSg==, + } + engines: { node: '>= 10' } cpu: [arm64] os: [darwin] - '@nx/nx-darwin-x64@16.10.0': - resolution: {integrity: sha512-ypi6YxwXgb0kg2ixKXE3pwf5myVNUgWf1CsV5OzVccCM8NzheMO51KDXTDmEpXdzUsfT0AkO1sk5GZeCjhVONg==} - engines: {node: '>= 10'} + '@nx/nx-darwin-x64@20.8.2': + resolution: + { + integrity: sha512-pt/wmDLM31Es8/EzazlyT5U+ou2l60rfMNFGCLqleHEQ0JUTc0KWnOciBLbHIQFiPsCQZJFEKyfV5V/ncePmmw==, + } + engines: { node: '>= 10' } cpu: [x64] os: [darwin] - '@nx/nx-freebsd-x64@16.10.0': - resolution: {integrity: sha512-UeEYFDmdbbDkTQamqvtU8ibgu5jQLgFF1ruNb/U4Ywvwutw2d4ruOMl2e0u9hiNja9NFFAnDbvzrDcMo7jYqYw==} - engines: {node: '>= 10'} + '@nx/nx-freebsd-x64@20.8.2': + resolution: + { + integrity: sha512-joZxFbgJfkHkB9uMIJr73Gpnm9pnpvr0XKGbWC409/d2x7q1qK77tKdyhGm+A3+kaZFwstNVPmCUtUwJYyU6LA==, + } + engines: { node: '>= 10' } cpu: [x64] os: [freebsd] - '@nx/nx-linux-arm-gnueabihf@16.10.0': - resolution: {integrity: sha512-WV3XUC2DB6/+bz1sx+d1Ai9q2Cdr+kTZRN50SOkfmZUQyEBaF6DRYpx/a4ahhxH3ktpNfyY8Maa9OEYxGCBkQA==} - engines: {node: '>= 10'} + '@nx/nx-linux-arm-gnueabihf@20.8.2': + resolution: + { + integrity: sha512-98O/qsxn4vIMPY/FyzvmVrl7C5yFhCUVk0/4PF+PA2SvtQ051L1eMRY6bq/lb69qfN6szJPZ41PG5mPx0NeLZw==, + } + engines: { node: '>= 10' } cpu: [arm] os: [linux] - '@nx/nx-linux-arm64-gnu@16.10.0': - resolution: {integrity: sha512-aWIkOUw995V3ItfpAi5FuxQ+1e9EWLS1cjWM1jmeuo+5WtaKToJn5itgQOkvSlPz+HSLgM3VfXMvOFALNk125g==} - engines: {node: '>= 10'} + '@nx/nx-linux-arm64-gnu@20.8.2': + resolution: + { + integrity: sha512-h6a+HxwfSpxsi4KpxGgPh9GDBmD2E+XqGCdfYpobabxqEBvlnIlJyuDhlRR06cTWpuNXHpRdrVogmV6m/YbtDg==, + } + engines: { node: '>= 10' } cpu: [arm64] os: [linux] - '@nx/nx-linux-arm64-musl@16.10.0': - resolution: {integrity: sha512-uO6Gg+irqpVcCKMcEPIQcTFZ+tDI02AZkqkP7koQAjniLEappd8DnUBSQdcn53T086pHpdc264X/ZEpXFfrKWQ==} - engines: {node: '>= 10'} + '@nx/nx-linux-arm64-musl@20.8.2': + resolution: + { + integrity: sha512-4Ev+jM0VAxDHV/dFgMXjQTCXS4I8W4oMe7FSkXpG8RUn6JK659DC8ExIDPoGIh+Cyqq6r6mw1CSia+ciQWICWQ==, + } + engines: { node: '>= 10' } cpu: [arm64] os: [linux] - '@nx/nx-linux-x64-gnu@16.10.0': - resolution: {integrity: sha512-134PW/u/arNFAQKpqMJniC7irbChMPz+W+qtyKPAUXE0XFKPa7c1GtlI/wK2dvP9qJDZ6bKf0KtA0U/m2HMUOA==} - engines: {node: '>= 10'} + '@nx/nx-linux-x64-gnu@20.8.2': + resolution: + { + integrity: sha512-nR0ev+wxu+nQYRd7bhqggOxK7UfkV6h+Ko1mumUFyrM5GvPpz/ELhjJFSnMcOkOMcvH0b6G5uTBJvN1XWCkbmg==, + } + engines: { node: '>= 10' } cpu: [x64] os: [linux] - '@nx/nx-linux-x64-musl@16.10.0': - resolution: {integrity: sha512-q8sINYLdIJxK/iUx9vRk5jWAWb/2O0PAbOJFwv4qkxBv4rLoN7y+otgCZ5v0xfx/zztFgk/oNY4lg5xYjIso2Q==} - engines: {node: '>= 10'} + '@nx/nx-linux-x64-musl@20.8.2': + resolution: + { + integrity: sha512-ost41l5yc2aq2Gc9bMMpaPi/jkXqbXEMEPHrxWKuKmaek3K2zbVDQzvBBNcQKxf/mlCsrqN4QO0mKYSRRqag5A==, + } + engines: { node: '>= 10' } cpu: [x64] os: [linux] - '@nx/nx-win32-arm64-msvc@16.10.0': - resolution: {integrity: sha512-moJkL9kcqxUdJSRpG7dET3UeLIciwrfP08mzBQ12ewo8K8FzxU8ZUsTIVVdNrwt01CXOdXoweGfdQLjJ4qTURA==} - engines: {node: '>= 10'} + '@nx/nx-win32-arm64-msvc@20.8.2': + resolution: + { + integrity: sha512-0SEOqT/daBG5WtM9vOGilrYaAuf1tiALdrFavY62+/arXYxXemUKmRI5qoKDTnvoLMBGkJs6kxhMO5b7aUXIvQ==, + } + engines: { node: '>= 10' } cpu: [arm64] os: [win32] - '@nx/nx-win32-x64-msvc@16.10.0': - resolution: {integrity: sha512-5iV2NKZnzxJwZZ4DM5JVbRG/nkhAbzEskKaLBB82PmYGKzaDHuMHP1lcPoD/rtYMlowZgNA/RQndfKvPBPwmXA==} - engines: {node: '>= 10'} + '@nx/nx-win32-x64-msvc@20.8.2': + resolution: + { + integrity: sha512-iIsY+tVqes/NOqTbJmggL9Juie/iaDYlWgXA9IUv88FE9thqWKhVj4/tCcPjsOwzD+1SVna3YISEEFsx5UV4ew==, + } + engines: { node: '>= 10' } cpu: [x64] os: [win32] - '@octokit/auth-token@3.0.4': - resolution: {integrity: sha512-TWFX7cZF2LXoCvdmJWY7XVPi74aSY0+FfBZNSXEXFkMpjcqsQwDSYVv5FhRFaI0V1ECnwbz4j59T/G+rXNWaIQ==} - engines: {node: '>= 14'} - - '@octokit/core@4.2.4': - resolution: {integrity: sha512-rYKilwgzQ7/imScn3M9/pFfUf4I1AZEH3KhyJmtPdE2zfaXAn2mFfUy4FbKewzc2We5y/LlKLj36fWJLKC2SIQ==} - engines: {node: '>= 14'} - - '@octokit/endpoint@7.0.6': - resolution: {integrity: sha512-5L4fseVRUsDFGR00tMWD/Trdeeihn999rTMGRMC1G/Ldi1uWlWJzI98H4Iak5DB/RVvQuyMYKqSK/R6mbSOQyg==} - engines: {node: '>= 14'} - - '@octokit/graphql@5.0.6': - resolution: {integrity: sha512-Fxyxdy/JH0MnIB5h+UQ3yCoh1FG4kWXfFKkpWqjZHw/p+Kc8Y44Hu/kCgNBT6nU1shNumEchmW/sUO1JuQnPcw==} - engines: {node: '>= 14'} - - '@octokit/openapi-types@18.1.1': - resolution: {integrity: sha512-VRaeH8nCDtF5aXWnjPuEMIYf1itK/s3JYyJcWFJT8X9pSNnBtriDf7wlEWsGuhPLl4QIH4xM8fqTXDwJ3Mu6sw==} + '@octokit/auth-token@4.0.0': + resolution: + { + integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==, + } + engines: { node: '>= 18' } + + '@octokit/core@5.2.2': + resolution: + { + integrity: sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==, + } + engines: { node: '>= 18' } + + '@octokit/endpoint@9.0.6': + resolution: + { + integrity: sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==, + } + engines: { node: '>= 18' } + + '@octokit/graphql@7.1.1': + resolution: + { + integrity: sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==, + } + engines: { node: '>= 18' } + + '@octokit/openapi-types@24.2.0': + resolution: + { + integrity: sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==, + } '@octokit/plugin-enterprise-rest@6.0.1': - resolution: {integrity: sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw==} - - '@octokit/plugin-paginate-rest@6.1.2': - resolution: {integrity: sha512-qhrmtQeHU/IivxucOV1bbI/xZyC/iOBhclokv7Sut5vnejAIAEXVcGQeRpQlU39E0WwK9lNvJHphHri/DB6lbQ==} - engines: {node: '>= 14'} + resolution: + { + integrity: sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw==, + } + + '@octokit/plugin-paginate-rest@11.4.4-cjs.2': + resolution: + { + integrity: sha512-2dK6z8fhs8lla5PaOTgqfCGBxgAv/le+EhPs27KklPhm1bKObpu6lXzwfUEQ16ajXzqNrKMujsFyo9K2eaoISw==, + } + engines: { node: '>= 18' } peerDependencies: - '@octokit/core': '>=4' - - '@octokit/plugin-request-log@1.0.4': - resolution: {integrity: sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==} + '@octokit/core': '5' + + '@octokit/plugin-request-log@4.0.1': + resolution: + { + integrity: sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==, + } + engines: { node: '>= 18' } peerDependencies: - '@octokit/core': '>=3' - - '@octokit/plugin-rest-endpoint-methods@7.2.3': - resolution: {integrity: sha512-I5Gml6kTAkzVlN7KCtjOM+Ruwe/rQppp0QU372K1GP7kNOYEKe8Xn5BW4sE62JAHdwpq95OQK/qGNyKQMUzVgA==} - engines: {node: '>= 14'} + '@octokit/core': '5' + + '@octokit/plugin-rest-endpoint-methods@13.3.2-cjs.1': + resolution: + { + integrity: sha512-VUjIjOOvF2oELQmiFpWA1aOPdawpyaCUqcEBc/UOUnj3Xp6DJGrJ1+bjUIIDzdHjnFNO6q57ODMfdEZnoBkCwQ==, + } + engines: { node: '>= 18' } peerDependencies: - '@octokit/core': '>=3' - - '@octokit/request-error@3.0.3': - resolution: {integrity: sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ==} - engines: {node: '>= 14'} - - '@octokit/request@6.2.8': - resolution: {integrity: sha512-ow4+pkVQ+6XVVsekSYBzJC0VTVvh/FCTUUgTsboGq+DTeWdyIFV8WSCdo0RIxk6wSkBTHqIK1mYuY7nOBXOchw==} - engines: {node: '>= 14'} - - '@octokit/rest@19.0.11': - resolution: {integrity: sha512-m2a9VhaP5/tUw8FwfnW2ICXlXpLPIqxtg3XcAiGMLj/Xhw3RSBfZ8le/466ktO1Gcjr8oXudGnHhxV1TXJgFxw==} - engines: {node: '>= 14'} - - '@octokit/tsconfig@1.0.2': - resolution: {integrity: sha512-I0vDR0rdtP8p2lGMzvsJzbhdOWy405HcGovrspJ8RRibHnyRgggUSNO5AIox5LmqiwmatHKYsvj6VGFHkqS7lA==} - - '@octokit/types@10.0.0': - resolution: {integrity: sha512-Vm8IddVmhCgU1fxC1eyinpwqzXPEYu0NrYzD3YZjlGjyftdLBTeqNblRC0jmJmgxbJIsQlyogVeGnrNaaMVzIg==} - - '@octokit/types@9.3.2': - resolution: {integrity: sha512-D4iHGTdAnEEVsB8fl95m1hiz7D5YiRdQ9b/OEb3BYRVwbLsGHcRVPz+u+BgRLNk0Q0/4iZCBqDN96j2XNxfXrA==} - - '@parcel/watcher@2.0.4': - resolution: {integrity: sha512-cTDi+FUDBIUOBKEtj+nhiJ71AZVlkAsQFuGQTun5tV9mwQBQgZvhCzG+URPQc8myeN32yRVZEfVAPCs1RW+Jvg==} - engines: {node: '>= 10.0.0'} + '@octokit/core': ^5 + + '@octokit/request-error@5.1.1': + resolution: + { + integrity: sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==, + } + engines: { node: '>= 18' } + + '@octokit/request@8.4.1': + resolution: + { + integrity: sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==, + } + engines: { node: '>= 18' } + + '@octokit/rest@20.1.2': + resolution: + { + integrity: sha512-GmYiltypkHHtihFwPRxlaorG5R9VAHuk/vbszVoRTGXnAsY60wYLkh/E2XiFmdZmqrisw+9FaazS1i5SbdWYgA==, + } + engines: { node: '>= 18' } + + '@octokit/types@13.10.0': + resolution: + { + integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==, + } '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - - '@pkgr/core@0.1.1': - resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - - '@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@2.3.1': - resolution: {integrity: sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==} - engines: {node: '>=12'} - - '@readme/better-ajv-errors@1.6.0': - resolution: {integrity: sha512-9gO9rld84Jgu13kcbKRU+WHseNhaVt76wYMeRDGsUGYxwJtI3RmEJ9LY9dZCYQGI8eUZLuxb5qDja0nqklpFjQ==} - engines: {node: '>=14'} - peerDependencies: - ajv: 4.11.8 - 8 - - '@readme/json-schema-ref-parser@1.2.0': - resolution: {integrity: sha512-Bt3QVovFSua4QmHa65EHUmh2xS0XJ3rgTEUPH998f4OW4VVJke3BuS16f+kM0ZLOGdvIrzrPRqwihuv5BAjtrA==} - - '@readme/openapi-parser@2.6.0': - resolution: {integrity: sha512-pyFJXezWj9WI1O+gdp95CoxfY+i+Uq3kKk4zXIFuRAZi9YnHpHOpjumWWr67wkmRTw19Hskh9spyY0Iyikf3fA==} - engines: {node: '>=18'} - peerDependencies: - openapi-types: '>=7' - - '@readme/openapi-schemas@3.1.0': - resolution: {integrity: sha512-9FC/6ho8uFa8fV50+FPy/ngWN53jaUu4GRXlAjcxIRrzhltJnpKkBG2Tp0IDraFJeWrOpk84RJ9EMEEYzaI1Bw==} - engines: {node: '>=18'} - - '@rollup/rollup-android-arm-eabi@4.31.0': - resolution: {integrity: sha512-9NrR4033uCbUBRgvLcBrJofa2KY9DzxL2UKZ1/4xA/mnTNyhZCWBuD8X3tPm1n4KxcgaraOYgrFKSgwjASfmlA==} + resolution: + { + integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==, + } + engines: { node: '>=14' } + + '@pkgr/core@0.2.9': + resolution: + { + integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==, + } + engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + + '@redocly/ajv@8.17.4': + resolution: + { + integrity: sha512-BieiCML/IgP6x99HZByJSt7fJE4ipgzO7KAFss92Bs+PEI35BhY7vGIysFXLT+YmS7nHtQjZjhOQyPPEf7xGHA==, + } + + '@redocly/config@0.22.2': + resolution: + { + integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==, + } + + '@redocly/openapi-core@1.34.6': + resolution: + { + integrity: sha512-2+O+riuIUgVSuLl3Lyh5AplWZyVMNuG2F98/o6NrutKJfW4/GTZdPpZlIphS0HGgcOHgmWcCSHj+dWFlZaGSHw==, + } + engines: { node: '>=18.17.0', npm: '>=9.5.0' } + + '@rollup/rollup-android-arm-eabi@4.57.1': + resolution: + { + integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==, + } cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.31.0': - resolution: {integrity: sha512-iBbODqT86YBFHajxxF8ebj2hwKm1k8PTBQSojSt3d1FFt1gN+xf4CowE47iN0vOSdnd+5ierMHBbu/rHc7nq5g==} + '@rollup/rollup-android-arm64@4.57.1': + resolution: + { + integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==, + } cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.31.0': - resolution: {integrity: sha512-WHIZfXgVBX30SWuTMhlHPXTyN20AXrLH4TEeH/D0Bolvx9PjgZnn4H677PlSGvU6MKNsjCQJYczkpvBbrBnG6g==} + '@rollup/rollup-darwin-arm64@4.57.1': + resolution: + { + integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==, + } cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.31.0': - resolution: {integrity: sha512-hrWL7uQacTEF8gdrQAqcDy9xllQ0w0zuL1wk1HV8wKGSGbKPVjVUv/DEwT2+Asabf8Dh/As+IvfdU+H8hhzrQQ==} + '@rollup/rollup-darwin-x64@4.57.1': + resolution: + { + integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==, + } cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.31.0': - resolution: {integrity: sha512-S2oCsZ4hJviG1QjPY1h6sVJLBI6ekBeAEssYKad1soRFv3SocsQCzX6cwnk6fID6UQQACTjeIMB+hyYrFacRew==} + '@rollup/rollup-freebsd-arm64@4.57.1': + resolution: + { + integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==, + } cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.31.0': - resolution: {integrity: sha512-pCANqpynRS4Jirn4IKZH4tnm2+2CqCNLKD7gAdEjzdLGbH1iO0zouHz4mxqg0uEMpO030ejJ0aA6e1PJo2xrPA==} + '@rollup/rollup-freebsd-x64@4.57.1': + resolution: + { + integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==, + } cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.31.0': - resolution: {integrity: sha512-0O8ViX+QcBd3ZmGlcFTnYXZKGbFu09EhgD27tgTdGnkcYXLat4KIsBBQeKLR2xZDCXdIBAlWLkiXE1+rJpCxFw==} + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + resolution: + { + integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==, + } cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.31.0': - resolution: {integrity: sha512-w5IzG0wTVv7B0/SwDnMYmbr2uERQp999q8FMkKG1I+j8hpPX2BYFjWe69xbhbP6J9h2gId/7ogesl9hwblFwwg==} + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + resolution: + { + integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==, + } cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.31.0': - resolution: {integrity: sha512-JyFFshbN5xwy6fulZ8B/8qOqENRmDdEkcIMF0Zz+RsfamEW+Zabl5jAb0IozP/8UKnJ7g2FtZZPEUIAlUSX8cA==} + '@rollup/rollup-linux-arm64-gnu@4.57.1': + resolution: + { + integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==, + } cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.31.0': - resolution: {integrity: sha512-kpQXQ0UPFeMPmPYksiBL9WS/BDiQEjRGMfklVIsA0Sng347H8W2iexch+IEwaR7OVSKtr2ZFxggt11zVIlZ25g==} + '@rollup/rollup-linux-arm64-musl@4.57.1': + resolution: + { + integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==, + } cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.31.0': - resolution: {integrity: sha512-pMlxLjt60iQTzt9iBb3jZphFIl55a70wexvo8p+vVFK+7ifTRookdoXX3bOsRdmfD+OKnMozKO6XM4zR0sHRrQ==} + '@rollup/rollup-linux-loong64-gnu@4.57.1': + resolution: + { + integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==, + } cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.31.0': - resolution: {integrity: sha512-D7TXT7I/uKEuWiRkEFbed1UUYZwcJDU4vZQdPTcepK7ecPhzKOYk4Er2YR4uHKme4qDeIh6N3XrLfpuM7vzRWQ==} + '@rollup/rollup-linux-loong64-musl@4.57.1': + resolution: + { + integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==, + } + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + resolution: + { + integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==, + } + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + resolution: + { + integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==, + } cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.31.0': - resolution: {integrity: sha512-wal2Tc8O5lMBtoePLBYRKj2CImUCJ4UNGJlLwspx7QApYny7K1cUYlzQ/4IGQBLmm+y0RS7dwc3TDO/pmcneTw==} + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + resolution: + { + integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==, + } cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.31.0': - resolution: {integrity: sha512-O1o5EUI0+RRMkK9wiTVpk2tyzXdXefHtRTIjBbmFREmNMy7pFeYXCFGbhKFwISA3UOExlo5GGUuuj3oMKdK6JQ==} + '@rollup/rollup-linux-riscv64-musl@4.57.1': + resolution: + { + integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==, + } + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.57.1': + resolution: + { + integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==, + } cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.31.0': - resolution: {integrity: sha512-zSoHl356vKnNxwOWnLd60ixHNPRBglxpv2g7q0Cd3Pmr561gf0HiAcUBRL3S1vPqRC17Zo2CX/9cPkqTIiai1g==} + '@rollup/rollup-linux-x64-gnu@4.57.1': + resolution: + { + integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==, + } cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.31.0': - resolution: {integrity: sha512-ypB/HMtcSGhKUQNiFwqgdclWNRrAYDH8iMYH4etw/ZlGwiTVxBz2tDrGRrPlfZu6QjXwtd+C3Zib5pFqID97ZA==} + '@rollup/rollup-linux-x64-musl@4.57.1': + resolution: + { + integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==, + } cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.31.0': - resolution: {integrity: sha512-JuhN2xdI/m8Hr+aVO3vspO7OQfUFO6bKLIRTAy0U15vmWjnZDLrEgCZ2s6+scAYaQVpYSh9tZtRijApw9IXyMw==} + '@rollup/rollup-openbsd-x64@4.57.1': + resolution: + { + integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==, + } + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.57.1': + resolution: + { + integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==, + } + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + resolution: + { + integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==, + } cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.31.0': - resolution: {integrity: sha512-U1xZZXYkvdf5MIWmftU8wrM5PPXzyaY1nGCI4KI4BFfoZxHamsIe+BtnPLIvvPykvQWlVbqUXdLa4aJUuilwLQ==} + '@rollup/rollup-win32-ia32-msvc@4.57.1': + resolution: + { + integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==, + } cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.31.0': - resolution: {integrity: sha512-ul8rnCsUumNln5YWwz0ted2ZHFhzhRRnkpBZ+YRuHoRAlUji9KChpOUOndY7uykrPEPXVbHLlsdo6v5yXo/TXw==} + '@rollup/rollup-win32-x64-gnu@4.57.1': + resolution: + { + integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==, + } cpu: [x64] os: [win32] - '@rtsao/scc@1.1.0': - resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - - '@sigstore/bundle@1.1.0': - resolution: {integrity: sha512-PFutXEy0SmQxYI4texPw3dd2KewuNqv7OuK1ZFtY2fM754yhvG2KdgwIhRnoEE2uHdtdGNQ8s0lb94dW9sELog==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - '@sigstore/protobuf-specs@0.2.1': - resolution: {integrity: sha512-XTWVxnWJu+c1oCshMLwnKvz8ZQJJDVOlciMfgpJBQbThVjKTCG8dwyhgLngBD2KN0ap9F/gOV8rFDEx8uh7R2A==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - '@sigstore/sign@1.0.0': - resolution: {integrity: sha512-INxFVNQteLtcfGmcoldzV6Je0sbbfh9I16DM4yJPw3j5+TFP8X6uIiA18mvpEa9yyeycAKgPmOA3X9hVdVTPUA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + '@rollup/rollup-win32-x64-msvc@4.57.1': + resolution: + { + integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==, + } + cpu: [x64] + os: [win32] - '@sigstore/tuf@1.0.3': - resolution: {integrity: sha512-2bRovzs0nJZFlCN3rXirE4gwxCn97JNjMmwpecqlbgV9WcxX7WRuIrgzx/X7Ib7MYRbyUTpBYE0s2x6AmZXnlg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + '@rtsao/scc@1.1.0': + resolution: + { + integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==, + } + + '@sigstore/bundle@2.3.2': + resolution: + { + integrity: sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==, + } + engines: { node: ^16.14.0 || >=18.0.0 } + + '@sigstore/core@1.1.0': + resolution: + { + integrity: sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==, + } + engines: { node: ^16.14.0 || >=18.0.0 } + + '@sigstore/protobuf-specs@0.3.3': + resolution: + { + integrity: sha512-RpacQhBlwpBWd7KEJsRKcBQalbV28fvkxwTOJIqhIuDysMMaJW47V4OqW30iJB9uRpqOSxxEAQFdr8tTattReQ==, + } + engines: { node: ^18.17.0 || >=20.5.0 } + + '@sigstore/sign@2.3.2': + resolution: + { + integrity: sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==, + } + engines: { node: ^16.14.0 || >=18.0.0 } + + '@sigstore/tuf@2.3.4': + resolution: + { + integrity: sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==, + } + engines: { node: ^16.14.0 || >=18.0.0 } + + '@sigstore/verify@1.2.1': + resolution: + { + integrity: sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==, + } + engines: { node: ^16.14.0 || >=18.0.0 } '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - - '@sindresorhus/is@5.6.0': - resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} - engines: {node: '>=14.16'} - - '@sindresorhus/merge-streams@2.3.0': - resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} - engines: {node: '>=18'} - - '@sinonjs/commons@3.0.1': - resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} - - '@sinonjs/fake-timers@10.3.0': - resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} - - '@szmarczak/http-timer@5.0.1': - resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} - engines: {node: '>=14.16'} - - '@tootallnate/once@2.0.0': - resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} - engines: {node: '>= 10'} - - '@ts-rest/core@3.51.0': - resolution: {integrity: sha512-v6lnWEcpZj1UgN9wb84XQ+EORP1QEtncFumoXMJjno5ZUV6vdjKze3MYcQN0C6vjBpIJPQEaI/gab2jr4/0KzQ==} - peerDependencies: - '@types/node': ^18.18.7 || >=20.8.4 - zod: ^3.22.3 - peerDependenciesMeta: - '@types/node': - optional: true - zod: - optional: true - - '@tsconfig/node10@1.0.11': - resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} - - '@tsconfig/node12@1.0.11': - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - - '@tsconfig/node14@1.0.3': - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - - '@tsconfig/node16@1.0.4': - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - - '@tufjs/canonical-json@1.0.0': - resolution: {integrity: sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - '@tufjs/models@1.0.4': - resolution: {integrity: sha512-qaGV9ltJP0EO25YfFUPhxRVK0evXFIAGicsVXuRim4Ed9cjPxYhNnNJ49SFmbeLgtxpslIkX317IgpfcHPVj/A==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.6.8': - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} - - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - - '@types/babel__traverse@7.20.6': - resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - - '@types/estree@1.0.6': - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - - '@types/fs-extra@9.0.13': - resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} - - '@types/graceful-fs@4.1.9': - resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} - - '@types/http-cache-semantics@4.0.4': - resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} - - '@types/istanbul-lib-coverage@2.0.6': - resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} - - '@types/istanbul-lib-report@3.0.3': - resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} - - '@types/istanbul-reports@3.0.4': - resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + resolution: + { + integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==, + } + + '@tufjs/canonical-json@2.0.0': + resolution: + { + integrity: sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==, + } + engines: { node: ^16.14.0 || >=18.0.0 } + + '@tufjs/models@2.0.1': + resolution: + { + integrity: sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==, + } + engines: { node: ^16.14.0 || >=18.0.0 } + + '@tybys/wasm-util@0.10.1': + resolution: + { + integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==, + } + + '@tybys/wasm-util@0.9.0': + resolution: + { + integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==, + } + + '@types/chai@5.2.3': + resolution: + { + integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==, + } + + '@types/deep-eql@4.0.2': + resolution: + { + integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==, + } + + '@types/estree@1.0.8': + resolution: + { + integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==, + } '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + resolution: + { + integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, + } '@types/json5@0.0.29': - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + resolution: + { + integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==, + } '@types/minimatch@3.0.5': - resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} + resolution: + { + integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==, + } '@types/minimist@1.2.5': - resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + resolution: + { + integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==, + } - '@types/node@22.10.7': - resolution: {integrity: sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==} + '@types/node@22.19.11': + resolution: + { + integrity: sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==, + } '@types/normalize-package-data@2.4.4': - resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - - '@types/semver@6.2.7': - resolution: {integrity: sha512-blctEWbzUFzQx799RZjzzIdBJOXmE37YYEyDtKkx5Dg+V7o/zyyAxLPiI98A2jdTtDgxZleMdfV+7p8WbRJ1OQ==} + resolution: + { + integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==, + } - '@types/semver@7.5.8': - resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} - - '@types/stack-utils@2.0.3': - resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - - '@types/yargs-parser@21.0.3': - resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - - '@types/yargs@17.0.33': - resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} + '@types/semver@7.7.1': + resolution: + { + integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==, + } '@typescript-eslint/eslint-plugin@6.21.0': - resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} - engines: {node: ^16.0.0 || >=18.0.0} + resolution: + { + integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==, + } + engines: { node: ^16.0.0 || >=18.0.0 } peerDependencies: '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha eslint: ^7.0.0 || ^8.0.0 @@ -1335,8 +1520,11 @@ packages: optional: true '@typescript-eslint/parser@6.21.0': - resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} - engines: {node: ^16.0.0 || >=18.0.0} + resolution: + { + integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==, + } + engines: { node: ^16.0.0 || >=18.0.0 } peerDependencies: eslint: ^7.0.0 || ^8.0.0 typescript: '*' @@ -1345,12 +1533,18 @@ packages: optional: true '@typescript-eslint/scope-manager@6.21.0': - resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} - engines: {node: ^16.0.0 || >=18.0.0} + resolution: + { + integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==, + } + engines: { node: ^16.0.0 || >=18.0.0 } '@typescript-eslint/type-utils@6.21.0': - resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} - engines: {node: ^16.0.0 || >=18.0.0} + resolution: + { + integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==, + } + engines: { node: ^16.0.0 || >=18.0.0 } peerDependencies: eslint: ^7.0.0 || ^8.0.0 typescript: '*' @@ -1358,26 +1552,19 @@ packages: typescript: optional: true - '@typescript-eslint/types@5.62.0': - resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@typescript-eslint/types@6.21.0': - resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} - engines: {node: ^16.0.0 || >=18.0.0} - - '@typescript-eslint/typescript-estree@5.62.0': - resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + resolution: + { + integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==, + } + engines: { node: ^16.0.0 || >=18.0.0 } '@typescript-eslint/typescript-estree@6.21.0': - resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} - engines: {node: ^16.0.0 || >=18.0.0} + resolution: + { + integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==, + } + engines: { node: ^16.0.0 || >=18.0.0 } peerDependencies: typescript: '*' peerDependenciesMeta: @@ -1385,726 +1572,1035 @@ packages: optional: true '@typescript-eslint/utils@6.21.0': - resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} - engines: {node: ^16.0.0 || >=18.0.0} + resolution: + { + integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==, + } + engines: { node: ^16.0.0 || >=18.0.0 } peerDependencies: eslint: ^7.0.0 || ^8.0.0 - '@typescript-eslint/visitor-keys@5.62.0': - resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@typescript-eslint/visitor-keys@6.21.0': - resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} - engines: {node: ^16.0.0 || >=18.0.0} + resolution: + { + integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==, + } + engines: { node: ^16.0.0 || >=18.0.0 } + + '@ungap/structured-clone@1.3.0': + resolution: + { + integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==, + } + + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + resolution: + { + integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==, + } + cpu: [arm] + os: [android] - '@typespec/compiler@0.63.0': - resolution: {integrity: sha512-cC3YniwbFghn1fASX3r1IgNjMrwaY4gmzznkHT4f/NxE+HK4XoXWn4EG7287QgVMCaHUykzJCIfW9k7kIleW5A==} - engines: {node: '>=18.0.0'} - hasBin: true + '@unrs/resolver-binding-android-arm64@1.11.1': + resolution: + { + integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==, + } + cpu: [arm64] + os: [android] - '@typespec/http@0.63.0': - resolution: {integrity: sha512-SYVbBmLPAPdWZfdMs0QlbpTnFREDnkINu2FR+0kRX12qzbRgpRbLsdhg59qx4TfKoh4IAPgSV+Fq84w7BWGsyQ==} - engines: {node: '>=18.0.0'} - peerDependencies: - '@typespec/compiler': ~0.63.0 - '@typespec/streams': ~0.63.0 - peerDependenciesMeta: - '@typespec/streams': - optional: true + '@unrs/resolver-binding-darwin-arm64@1.11.1': + resolution: + { + integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==, + } + cpu: [arm64] + os: [darwin] - '@typespec/openapi3@0.63.0': - resolution: {integrity: sha512-HC8VeakPznXNn7euAyAxUFNsOcfSzM8tQwYPNUMWs0qGJqGgb6vjf5rShQmfgrCe5Y6zcMM2PPBuxaFV3xXYLw==} - engines: {node: '>=18.0.0'} - hasBin: true - peerDependencies: - '@typespec/compiler': ~0.63.0 - '@typespec/http': ~0.63.0 - '@typespec/openapi': ~0.63.0 - '@typespec/versioning': ~0.63.0 - '@typespec/xml': '*' - peerDependenciesMeta: - '@typespec/xml': - optional: true + '@unrs/resolver-binding-darwin-x64@1.11.1': + resolution: + { + integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==, + } + cpu: [x64] + os: [darwin] - '@typespec/openapi@0.63.0': - resolution: {integrity: sha512-/KzR60mj3P/LnNWd/QfH0KTN/If4+mjrsWNSB7/uab6c8Qu/lNsGlZDkmWq4EFiwBR7VmpdFz9FP7d/m3O+tGw==} - engines: {node: '>=18.0.0'} - peerDependencies: - '@typespec/compiler': ~0.63.0 - '@typespec/http': ~0.63.0 + '@unrs/resolver-binding-freebsd-x64@1.11.1': + resolution: + { + integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==, + } + cpu: [x64] + os: [freebsd] - '@typespec/rest@0.63.1': - resolution: {integrity: sha512-RQbTM+HGjCaNIWC0v72m5ulnuvLjuRigb7pH4QeRCvFtGPHos+WBv5SImkGrbYx3353OGR8dIi7lWe7aNwiDcQ==} - engines: {node: '>=18.0.0'} - peerDependencies: - '@typespec/compiler': ~0.63.0 - '@typespec/http': ~0.63.0 + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + resolution: + { + integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==, + } + cpu: [arm] + os: [linux] - '@typespec/versioning@0.63.0': - resolution: {integrity: sha512-BPvmPL+g20yEmSA8XRfbIHdToNOjssq4QfwOU6D7kKLLXnZHFb1hmuwW0tf0Wa/lYgoaUC60ONAeoXgNT1ZOIQ==} - engines: {node: '>=18.0.0'} - peerDependencies: - '@typespec/compiler': ~0.63.0 + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + resolution: + { + integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==, + } + cpu: [arm] + os: [linux] - '@ungap/structured-clone@1.2.1': - resolution: {integrity: sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==} + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + resolution: + { + integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==, + } + cpu: [arm64] + os: [linux] - '@vitest/coverage-c8@0.33.0': - resolution: {integrity: sha512-DaF1zJz4dcOZS4k/neiQJokmOWqsGXwhthfmUdPGorXIQHjdPvV6JQSYhQDI41MyI8c+IieQUdIDs5XAMHtDDw==} - deprecated: v8 coverage is moved to @vitest/coverage-v8 package - peerDependencies: - vitest: '>=0.30.0 <1' + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + resolution: + { + integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==, + } + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + resolution: + { + integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==, + } + cpu: [ppc64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + resolution: + { + integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==, + } + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + resolution: + { + integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==, + } + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + resolution: + { + integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==, + } + cpu: [s390x] + os: [linux] + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + resolution: + { + integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==, + } + cpu: [x64] + os: [linux] - '@vitest/coverage-v8@3.0.3': - resolution: {integrity: sha512-uVbJ/xhImdNtzPnLyxCZJMTeTIYdgcC2nWtBBBpR1H6z0w8m7D+9/zrDIx2nNxgMg9r+X8+RY2qVpUDeW2b3nw==} + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + resolution: + { + integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==, + } + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-wasm32-wasi@1.11.1': + resolution: + { + integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==, + } + engines: { node: '>=14.0.0' } + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + resolution: + { + integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==, + } + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + resolution: + { + integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==, + } + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + resolution: + { + integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==, + } + cpu: [x64] + os: [win32] + + '@vitest/coverage-v8@3.2.4': + resolution: + { + integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==, + } peerDependencies: - '@vitest/browser': 3.0.3 - vitest: 3.0.3 + '@vitest/browser': 3.2.4 + vitest: 3.2.4 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@3.0.3': - resolution: {integrity: sha512-SbRCHU4qr91xguu+dH3RUdI5dC86zm8aZWydbp961aIR7G8OYNN6ZiayFuf9WAngRbFOfdrLHCGgXTj3GtoMRQ==} - - '@vitest/mocker@3.0.3': - resolution: {integrity: sha512-XT2XBc4AN9UdaxJAeIlcSZ0ILi/GzmG5G8XSly4gaiqIvPV3HMTSIDZWJVX6QRJ0PX1m+W8Cy0K9ByXNb/bPIA==} + '@vitest/expect@3.2.4': + resolution: + { + integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==, + } + + '@vitest/mocker@3.2.4': + resolution: + { + integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==, + } peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/pretty-format@3.0.3': - resolution: {integrity: sha512-gCrM9F7STYdsDoNjGgYXKPq4SkSxwwIU5nkaQvdUxiQ0EcNlez+PdKOVIsUJvh9P9IeIFmjn4IIREWblOBpP2Q==} - - '@vitest/runner@3.0.3': - resolution: {integrity: sha512-Rgi2kOAk5ZxWZlwPguRJFOBmWs6uvvyAAR9k3MvjRvYrG7xYvKChZcmnnpJCS98311CBDMqsW9MzzRFsj2gX3g==} - - '@vitest/snapshot@3.0.3': - resolution: {integrity: sha512-kNRcHlI4txBGztuJfPEJ68VezlPAXLRT1u5UCx219TU3kOG2DplNxhWLwDf2h6emwmTPogzLnGVwP6epDaJN6Q==} - - '@vitest/spy@3.0.3': - resolution: {integrity: sha512-7/dgux8ZBbF7lEIKNnEqQlyRaER9nkAL9eTmdKJkDO3hS8p59ATGwKOCUDHcBLKr7h/oi/6hP+7djQk8049T2A==} - - '@vitest/utils@3.0.3': - resolution: {integrity: sha512-f+s8CvyzPtMFY1eZKkIHGhPsQgYo5qCm6O8KZoim9qm1/jT64qBgGpO5tHscNH6BzRHM+edLNOP+3vO8+8pE/A==} + '@vitest/pretty-format@3.2.4': + resolution: + { + integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==, + } + + '@vitest/runner@3.2.4': + resolution: + { + integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==, + } + + '@vitest/snapshot@3.2.4': + resolution: + { + integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==, + } + + '@vitest/spy@3.2.4': + resolution: + { + integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==, + } + + '@vitest/utils@3.2.4': + resolution: + { + integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==, + } '@yarnpkg/lockfile@1.1.0': - resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} - - '@yarnpkg/parsers@3.0.0-rc.46': - resolution: {integrity: sha512-aiATs7pSutzda/rq8fnuPwTglyVwjM22bNnK2ZgjrpAjQHSSl3lztd2f9evst1W/qnC58DRz7T7QndUDumAR4Q==} - engines: {node: '>=14.15.0'} - - '@zkochan/js-yaml@0.0.6': - resolution: {integrity: sha512-nzvgl3VfhcELQ8LyVrYOru+UtAy1nrygk2+AGbTm8a5YcO6o8lSjAT+pfg3vJWxIoZKOUhrK6UU7xW/+00kQrg==} + resolution: + { + integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==, + } + + '@yarnpkg/parsers@3.0.2': + resolution: + { + integrity: sha512-/HcYgtUSiJiot/XWGLOlGxPYUG65+/31V8oqk17vZLW1xlCoR4PampyePljOxY2n8/3jz9+tIFzICsyGujJZoA==, + } + engines: { node: '>=18.12.0' } + + '@zkochan/js-yaml@0.0.7': + resolution: + { + integrity: sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==, + } hasBin: true - '@zodios/core@10.9.6': - resolution: {integrity: sha512-aH4rOdb3AcezN7ws8vDgBfGboZMk2JGGzEq/DtW65MhnRxyTGRuLJRWVQ/2KxDgWvV2F5oTkAS+5pnjKbl0n+A==} - peerDependencies: - axios: ^0.x || ^1.0.0 - zod: ^3.x - JSONStream@1.3.5: - resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} + resolution: + { + integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==, + } hasBin: true - abbrev@1.1.1: - resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + abbrev@2.0.0: + resolution: + { + integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + resolution: + { + integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, + } peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-walk@8.3.4: - resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} - engines: {node: '>=0.4.0'} - - acorn@8.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} - engines: {node: '>=0.4.0'} + acorn@8.15.0: + resolution: + { + integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==, + } + engines: { node: '>=0.4.0' } hasBin: true add-stream@1.0.0: - resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} - - agent-base@6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} - - agentkeepalive@4.6.0: - resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} - engines: {node: '>= 8.0.0'} + resolution: + { + integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==, + } + + agent-base@7.1.4: + resolution: + { + integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==, + } + engines: { node: '>= 14' } aggregate-error@3.1.0: - resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} - engines: {node: '>=8'} - - ajv-draft-04@1.0.0: - resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + resolution: + { + integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==, + } + engines: { node: '>=8' } + + ajv-formats@3.0.1: + resolution: + { + integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==, + } peerDependencies: - ajv: ^8.5.0 + ajv: ^8.0.0 peerDependenciesMeta: ajv: optional: true ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + resolution: + { + integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==, + } - ajv@8.17.1: - resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + ajv@8.18.0: + resolution: + { + integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==, + } ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==, + } + engines: { node: '>=6' } ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==, + } + engines: { node: '>=8' } ansi-escapes@5.0.0: - resolution: {integrity: sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==, + } + engines: { node: '>=12' } ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.1.0: - resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} - engines: {node: '>=12'} - - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==, + } + engines: { node: '>=8' } + + ansi-regex@6.2.2: + resolution: + { + integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==, + } + engines: { node: '>=12' } ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, + } + engines: { node: '>=8' } ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - app-module-path@2.2.0: - resolution: {integrity: sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==} + resolution: + { + integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==, + } + engines: { node: '>=10' } + + ansi-styles@6.2.3: + resolution: + { + integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==, + } + engines: { node: '>=12' } aproba@2.0.0: - resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} - - are-we-there-yet@3.0.1: - resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This package is no longer supported. - - arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + resolution: + { + integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==, + } argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + resolution: + { + integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==, + } argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + resolution: + { + integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, + } array-buffer-byte-length@1.0.2: - resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==, + } + engines: { node: '>= 0.4' } array-differ@3.0.0: - resolution: {integrity: sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==, + } + engines: { node: '>=8' } array-ify@1.0.0: - resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} - - array-includes@3.1.8: - resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==, + } + + array-includes@3.1.9: + resolution: + { + integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==, + } + engines: { node: '>= 0.4' } array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - - array.prototype.findlastindex@1.2.5: - resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==, + } + engines: { node: '>=8' } + + array.prototype.findlastindex@1.2.6: + resolution: + { + integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==, + } + engines: { node: '>= 0.4' } array.prototype.flat@1.3.3: - resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==, + } + engines: { node: '>= 0.4' } array.prototype.flatmap@1.3.3: - resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==, + } + engines: { node: '>= 0.4' } arraybuffer.prototype.slice@1.0.4: - resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==, + } + engines: { node: '>= 0.4' } arrify@1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==, + } + engines: { node: '>=0.10.0' } arrify@2.0.1: - resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} - engines: {node: '>=8'} - - assert-plus@1.0.0: - resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} - engines: {node: '>=0.8'} + resolution: + { + integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==, + } + engines: { node: '>=8' } assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - - ast-module-types@5.0.0: - resolution: {integrity: sha512-JvqziE0Wc0rXQfma0HZC/aY7URXHFuZV84fJRtP8u+lhp0JYCNd5wJzVXP45t0PH0Mej3ynlzvdyITYIu0G4LQ==} - engines: {node: '>=14'} - - astral-regex@2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==, + } + engines: { node: '>=12' } + + ast-v8-to-istanbul@0.3.11: + resolution: + { + integrity: sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==, + } + + async-function@1.0.0: + resolution: + { + integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==, + } + engines: { node: '>= 0.4' } async@3.2.6: - resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + resolution: + { + integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==, + } asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + resolution: + { + integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==, + } available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} - - axios@1.7.9: - resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} - - babel-jest@29.7.0: - resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.8.0 - - babel-plugin-istanbul@6.1.1: - resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} - engines: {node: '>=8'} - - babel-plugin-jest-hoist@29.6.3: - resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - babel-preset-current-node-syntax@1.1.0: - resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==} - peerDependencies: - '@babel/core': ^7.0.0 - - babel-preset-jest@29.6.3: - resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.0.0 + resolution: + { + integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==, + } + engines: { node: '>= 0.4' } + + axios@1.13.5: + resolution: + { + integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==, + } balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + resolution: + { + integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, + } base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + resolution: + { + integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==, + } before-after-hook@2.2.3: - resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} + resolution: + { + integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==, + } + + bin-links@4.0.4: + resolution: + { + integrity: sha512-cMtq4W5ZsEwcutJrVId+a/tjt8GSbS+h0oNkdl6+6rBuEv8Ot33Bevj5KPm40t309zuhVic8NjpuL42QCiJWWA==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + resolution: + { + integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==, + } + + brace-expansion@1.1.12: + resolution: + { + integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==, + } + + brace-expansion@2.0.2: + resolution: + { + integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==, + } braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - browserslist@4.24.4: - resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - bs-logger@0.2.6: - resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} - engines: {node: '>= 6'} - - bser@2.1.1: - resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + resolution: + { + integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==, + } + engines: { node: '>=8' } buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + resolution: + { + integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==, + } buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - - builtins@1.0.3: - resolution: {integrity: sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ==} - - builtins@5.1.0: - resolution: {integrity: sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==} + resolution: + { + integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==, + } byte-size@8.1.1: - resolution: {integrity: sha512-tUkzZWK0M/qdoLEqikxBWe4kumyuwjl3HO6zHTr4yEI23EojPtLYXdG1+AQY7MN0cGyNDvEaJ8wiYQm6P2bPxg==} - engines: {node: '>=12.17'} - - c8@7.14.0: - resolution: {integrity: sha512-i04rtkkcNcCf7zsQcSv/T9EbUn4RXQ6mropeMcjFOsQXQ0iGLAr/xT6TImQg4+U9hmNpN9XdvPkjUL1IzbgxJw==} - engines: {node: '>=10.12.0'} - hasBin: true + resolution: + { + integrity: sha512-tUkzZWK0M/qdoLEqikxBWe4kumyuwjl3HO6zHTr4yEI23EojPtLYXdG1+AQY7MN0cGyNDvEaJ8wiYQm6P2bPxg==, + } + engines: { node: '>=12.17' } cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - cacache@16.1.3: - resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - - cacache@17.1.4: - resolution: {integrity: sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - cacheable-lookup@7.0.0: - resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} - engines: {node: '>=14.16'} - - cacheable-request@10.2.14: - resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==} - engines: {node: '>=14.16'} - - call-bind-apply-helpers@1.0.1: - resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==, + } + engines: { node: '>=8' } + + cacache@18.0.4: + resolution: + { + integrity: sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==, + } + engines: { node: ^16.14.0 || >=18.0.0 } + + call-bind-apply-helpers@1.0.2: + resolution: + { + integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==, + } + engines: { node: '>= 0.4' } call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} - engines: {node: '>= 0.4'} - - call-bound@1.0.3: - resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} - engines: {node: '>= 0.4'} - - call-me-maybe@1.0.2: - resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} + resolution: + { + integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==, + } + 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'} + resolution: + { + integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, + } + engines: { node: '>=6' } camelcase-keys@6.2.2: - resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==, + } + engines: { node: '>=8' } camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - - caniuse-lite@1.0.30001695: - resolution: {integrity: sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==} - - chai@5.1.2: - resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} - engines: {node: '>=12'} - - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==, + } + engines: { node: '>=6' } + + chai@5.3.3: + resolution: + { + integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==, + } + engines: { node: '>=18' } chalk@4.1.0: - resolution: {integrity: sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==, + } + engines: { node: '>=10' } chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, + } + engines: { node: '>=10' } chalk@5.3.0: - resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - - chalk@5.4.1: - resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - - change-case@5.4.4: - resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} - - char-regex@1.0.2: - resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} - engines: {node: '>=10'} - - chardet@0.7.0: - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - - check-error@2.1.1: - resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} - engines: {node: '>= 16'} + resolution: + { + integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==, + } + engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } + + chalk@5.6.2: + resolution: + { + integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==, + } + engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } + + chardet@2.1.1: + resolution: + { + integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==, + } + + check-error@2.1.3: + resolution: + { + integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==, + } + engines: { node: '>= 16' } chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==, + } + engines: { node: '>=10' } ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} - engines: {node: '>=8'} - - cjs-module-lexer@1.4.1: - resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==} + resolution: + { + integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==, + } + engines: { node: '>=8' } + + ci-info@4.4.0: + resolution: + { + integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==, + } + engines: { node: '>=8' } clean-stack@2.2.0: - resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==, + } + engines: { node: '>=6' } cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==, + } + engines: { node: '>=8' } cli-cursor@4.0.0: - resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + resolution: + { + integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } cli-cursor@5.0.0: - resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==, + } + engines: { node: '>=18' } cli-spinners@2.6.1: - resolution: {integrity: sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==, + } + engines: { node: '>=6' } cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==, + } + engines: { node: '>=6' } cli-truncate@3.1.0: - resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + resolution: + { + integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } cli-width@3.0.0: - resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} - engines: {node: '>= 10'} + resolution: + { + integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==, + } + engines: { node: '>= 10' } cli-width@4.1.0: - resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} - engines: {node: '>= 12'} + resolution: + { + integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==, + } + engines: { node: '>= 12' } cliui@7.0.4: - resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + resolution: + { + integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==, + } cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==, + } + engines: { node: '>=12' } clone-deep@4.0.1: - resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==, + } + engines: { node: '>=6' } clone@1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} - engines: {node: '>=0.8'} - - cmd-shim@6.0.1: - resolution: {integrity: sha512-S9iI9y0nKR4hwEQsVWpyxld/6kRfGepGfzff83FcaiEBpmvlbA2nnGe7Cylgrx2f/p1P5S5wpRm9oL8z1PbS3Q==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - co@4.6.0: - resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - - code-error-fragment@0.0.230: - resolution: {integrity: sha512-cadkfKp6932H8UkhzE/gcUqhRMNf8jHzkAN7+5Myabswaghu4xABTgPHDCjW+dBAJxj/SpkTYokpzDqY4pCzQw==} - engines: {node: '>= 4'} - - collect-v8-coverage@1.0.2: - resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} - - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + resolution: + { + integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==, + } + engines: { node: '>=0.8' } + + cmd-shim@6.0.3: + resolution: + { + integrity: sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } 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==} + resolution: + { + integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, + } + engines: { node: '>=7.0.0' } color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + resolution: + { + integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, + } color-support@1.1.3: - resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + resolution: + { + integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==, + } hasBin: true + colorette@1.4.0: + resolution: + { + integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==, + } + colorette@2.0.20: - resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + resolution: + { + integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==, + } columnify@1.6.0: - resolution: {integrity: sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q==} - engines: {node: '>=8.0.0'} + resolution: + { + integrity: sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q==, + } + engines: { node: '>=8.0.0' } combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - - commander@10.0.1: - resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} - engines: {node: '>=14'} + resolution: + { + integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==, + } + engines: { node: '>= 0.8' } commander@11.0.0: - resolution: {integrity: sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==} - engines: {node: '>=16'} + resolution: + { + integrity: sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==, + } + engines: { node: '>=16' } commander@12.1.0: - resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} - engines: {node: '>=18'} - - commander@7.2.0: - resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} - engines: {node: '>= 10'} - - commander@8.3.0: - resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} - engines: {node: '>= 12'} - - commander@9.5.0: - resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} - engines: {node: ^12.20.0 || >=14} - - commondir@1.0.1: - resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + resolution: + { + integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==, + } + engines: { node: '>=18' } + + common-ancestor-path@1.0.1: + resolution: + { + integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==, + } compare-func@2.0.0: - resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + resolution: + { + integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==, + } concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + resolution: + { + integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, + } concat-stream@2.0.0: - resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} - engines: {'0': node >= 6.0} - - config-chain@1.1.13: - resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + resolution: + { + integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==, + } + engines: { '0': node >= 6.0 } console-control-strings@1.1.0: - resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + resolution: + { + integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==, + } conventional-changelog-angular@7.0.0: - resolution: {integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==} - engines: {node: '>=16'} + resolution: + { + integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==, + } + engines: { node: '>=16' } conventional-changelog-core@5.0.1: - resolution: {integrity: sha512-Rvi5pH+LvgsqGwZPZ3Cq/tz4ty7mjijhr3qR4m9IBXNbxGGYgTVVO+duXzz9aArmHxFtwZ+LRkrNIMDQzgoY4A==} - engines: {node: '>=14'} + resolution: + { + integrity: sha512-Rvi5pH+LvgsqGwZPZ3Cq/tz4ty7mjijhr3qR4m9IBXNbxGGYgTVVO+duXzz9aArmHxFtwZ+LRkrNIMDQzgoY4A==, + } + engines: { node: '>=14' } conventional-changelog-preset-loader@3.0.0: - resolution: {integrity: sha512-qy9XbdSLmVnwnvzEisjxdDiLA4OmV3o8db+Zdg4WiFw14fP3B6XNz98X0swPPpkTd/pc1K7+adKgEDM1JCUMiA==} - engines: {node: '>=14'} + resolution: + { + integrity: sha512-qy9XbdSLmVnwnvzEisjxdDiLA4OmV3o8db+Zdg4WiFw14fP3B6XNz98X0swPPpkTd/pc1K7+adKgEDM1JCUMiA==, + } + engines: { node: '>=14' } conventional-changelog-writer@6.0.1: - resolution: {integrity: sha512-359t9aHorPw+U+nHzUXHS5ZnPBOizRxfQsWT5ZDHBfvfxQOAik+yfuhKXG66CN5LEWPpMNnIMHUTCKeYNprvHQ==} - engines: {node: '>=14'} + resolution: + { + integrity: sha512-359t9aHorPw+U+nHzUXHS5ZnPBOizRxfQsWT5ZDHBfvfxQOAik+yfuhKXG66CN5LEWPpMNnIMHUTCKeYNprvHQ==, + } + engines: { node: '>=14' } hasBin: true conventional-commits-filter@3.0.0: - resolution: {integrity: sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q==} - engines: {node: '>=14'} + resolution: + { + integrity: sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q==, + } + engines: { node: '>=14' } conventional-commits-parser@4.0.0: - resolution: {integrity: sha512-WRv5j1FsVM5FISJkoYMR6tPk07fkKT0UodruX4je86V4owk451yjXAKzKAPOs9l7y59E2viHUS9eQ+dfUA9NSg==} - engines: {node: '>=14'} + resolution: + { + integrity: sha512-WRv5j1FsVM5FISJkoYMR6tPk07fkKT0UodruX4je86V4owk451yjXAKzKAPOs9l7y59E2viHUS9eQ+dfUA9NSg==, + } + engines: { node: '>=14' } hasBin: true conventional-recommended-bump@7.0.1: - resolution: {integrity: sha512-Ft79FF4SlOFvX4PkwFDRnaNiIVX7YbmqGU0RwccUaiGvgp3S0a8ipR2/Qxk31vclDNM+GSdJOVs2KrsUCjblVA==} - engines: {node: '>=14'} + resolution: + { + integrity: sha512-Ft79FF4SlOFvX4PkwFDRnaNiIVX7YbmqGU0RwccUaiGvgp3S0a8ipR2/Qxk31vclDNM+GSdJOVs2KrsUCjblVA==, + } + engines: { node: '>=14' } hasBin: true - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - convict@6.2.4: - resolution: {integrity: sha512-qN60BAwdMVdofckX7AlohVJ2x9UvjTNoKVXCL2LxFk1l7757EJqf1nySdMkPQer0bt8kQ5lQiyZ9/2NvrFBuwQ==} - engines: {node: '>=6'} - core-util-is@1.0.2: - resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} - - core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - - cosmiconfig@8.3.6: - resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} - engines: {node: '>=14'} + resolution: + { + integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==, + } + + cosmiconfig@9.0.0: + resolution: + { + integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==, + } + engines: { node: '>=14' } peerDependencies: typescript: '>=4.9.5' peerDependenciesMeta: typescript: optional: true - create-jest@29.7.0: - resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - - create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - - cross-spawn@5.1.0: - resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} + resolution: + { + integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==, + } + engines: { node: '>= 8' } + + cssesc@3.0.0: + resolution: + { + integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==, + } + engines: { node: '>=4' } + hasBin: true dargs@7.0.0: - resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==, + } + engines: { node: '>=8' } data-view-buffer@1.0.2: - resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==, + } + engines: { node: '>= 0.4' } data-view-byte-length@1.0.2: - resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==, + } + engines: { node: '>= 0.4' } data-view-byte-offset@1.0.1: - resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==, + } + engines: { node: '>= 0.4' } dateformat@3.0.3: - resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} + resolution: + { + integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==, + } debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + resolution: + { + integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==, + } peerDependencies: supports-color: '*' peerDependenciesMeta: @@ -2112,17 +2608,23 @@ packages: optional: true debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} + resolution: + { + integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==, + } + engines: { node: '>=6.0' } peerDependencies: supports-color: '*' peerDependenciesMeta: supports-color: optional: true - debug@4.4.0: - resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} - engines: {node: '>=6.0'} + debug@4.4.3: + resolution: + { + integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==, + } + engines: { node: '>=6.0' } peerDependencies: supports-color: '*' peerDependenciesMeta: @@ -2130,22 +2632,24 @@ packages: optional: true decamelize-keys@1.1.1: - resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==, + } + engines: { node: '>=0.10.0' } decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - - decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} - - dedent@0.7.0: - resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} + resolution: + { + integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==, + } + engines: { node: '>=0.10.0' } dedent@1.5.3: - resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} + resolution: + { + integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==, + } peerDependencies: babel-plugin-macros: ^3.1.0 peerDependenciesMeta: @@ -2153,259 +2657,304 @@ packages: optional: true deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} - - deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} + resolution: + { + integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==, + } + engines: { node: '>=6' } deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - - deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, + } defaults@1.0.4: - resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} - - defer-to-connect@2.0.1: - resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==, + } define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==, + } + engines: { node: '>= 0.4' } define-lazy-prop@2.0.0: - resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==, + } + engines: { node: '>=8' } define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==, + } + engines: { node: '>= 0.4' } delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - - delegates@1.0.0: - resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} - - dependency-tree@10.0.9: - resolution: {integrity: sha512-dwc59FRIsht+HfnTVM0BCjJaEWxdq2YAvEDy4/Hn6CwS3CBWMtFnL3aZGAkQn3XCYxk/YcTDE4jX2Q7bFTwCjA==} - engines: {node: '>=14'} - hasBin: true + resolution: + { + integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==, + } + engines: { node: '>=0.4.0' } deprecation@2.3.1: - resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} + resolution: + { + integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==, + } detect-indent@5.0.0: - resolution: {integrity: sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g==} - engines: {node: '>=4'} - - detect-indent@6.1.0: - resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} - engines: {node: '>=8'} - - detect-newline@3.1.0: - resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} - engines: {node: '>=8'} - - detective-amd@5.0.2: - resolution: {integrity: sha512-XFd/VEQ76HSpym80zxM68ieB77unNuoMwopU2TFT/ErUk5n4KvUTwW4beafAVUugrjV48l4BmmR0rh2MglBaiA==} - engines: {node: '>=14'} - hasBin: true - - detective-cjs@5.0.1: - resolution: {integrity: sha512-6nTvAZtpomyz/2pmEmGX1sXNjaqgMplhQkskq2MLrar0ZAIkHMrDhLXkRiK2mvbu9wSWr0V5/IfiTrZqAQMrmQ==} - engines: {node: '>=14'} - - detective-es6@4.0.1: - resolution: {integrity: sha512-k3Z5tB4LQ8UVHkuMrFOlvb3GgFWdJ9NqAa2YLUU/jTaWJIm+JJnEh4PsMc+6dfT223Y8ACKOaC0qcj7diIhBKw==} - engines: {node: '>=14'} - - detective-postcss@6.1.3: - resolution: {integrity: sha512-7BRVvE5pPEvk2ukUWNQ+H2XOq43xENWbH0LcdCE14mwgTBEAMoAx+Fc1rdp76SmyZ4Sp48HlV7VedUnP6GA1Tw==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - detective-sass@5.0.3: - resolution: {integrity: sha512-YsYT2WuA8YIafp2RVF5CEfGhhyIVdPzlwQgxSjK+TUm3JoHP+Tcorbk3SfG0cNZ7D7+cYWa0ZBcvOaR0O8+LlA==} - engines: {node: '>=14'} - - detective-scss@4.0.3: - resolution: {integrity: sha512-VYI6cHcD0fLokwqqPFFtDQhhSnlFWvU614J42eY6G0s8c+MBhi9QAWycLwIOGxlmD8I/XvGSOUV1kIDhJ70ZPg==} - engines: {node: '>=14'} - - detective-stylus@4.0.0: - resolution: {integrity: sha512-TfPotjhszKLgFBzBhTOxNHDsutIxx9GTWjrL5Wh7Qx/ydxKhwUrlSFeLIn+ZaHPF+h0siVBkAQSuy6CADyTxgQ==} - engines: {node: '>=14'} - - detective-typescript@11.2.0: - resolution: {integrity: sha512-ARFxjzizOhPqs1fYC/2NMC3N4jrQ6HvVflnXBTRqNEqJuXwyKLRr9CrJwkRcV/SnZt1sNXgsF6FPm0x57Tq0rw==} - engines: {node: ^14.14.0 || >=16.0.0} + resolution: + { + integrity: sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g==, + } + engines: { node: '>=4' } diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} - engines: {node: '>=0.3.1'} + resolution: + { + integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==, + } + engines: { node: '>=8' } doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==, + } + engines: { node: '>=0.10.0' } doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} + resolution: + { + integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==, + } + engines: { node: '>=6.0.0' } dot-prop@5.3.0: - resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} - engines: {node: '>=8'} - - dotenv-expand@10.0.0: - resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} - engines: {node: '>=12'} - - dotenv@16.3.2: - resolution: {integrity: sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==, + } + engines: { node: '>=8' } + + dotenv-expand@11.0.7: + resolution: + { + integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==, + } + engines: { node: '>=12' } + + dotenv@16.4.7: + resolution: + { + integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==, + } + engines: { node: '>=12' } dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - duplexer@0.1.2: - resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + resolution: + { + integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==, + } + engines: { node: '>= 0.4' } eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + resolution: + { + integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==, + } ejs@3.1.10: - resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==, + } + engines: { node: '>=0.10.0' } hasBin: true - electron-to-chromium@1.5.83: - resolution: {integrity: sha512-LcUDPqSt+V0QmI47XLzZrz5OqILSMGsPFkDYus22rIbgorSvBYEFqq854ltTmUdHkY92FSdAAvsh4jWEULMdfQ==} - - emittery@0.13.1: - resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} - engines: {node: '>=12'} - - emoji-regex@10.4.0: - resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} + emoji-regex@10.6.0: + resolution: + { + integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==, + } emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + resolution: + { + integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, + } emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + resolution: + { + integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, + } encoding@0.1.13: - resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + resolution: + { + integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==, + } - end-of-stream@1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - - enhanced-resolve@5.18.0: - resolution: {integrity: sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==} - engines: {node: '>=10.13.0'} + end-of-stream@1.4.5: + resolution: + { + integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==, + } enquirer@2.3.6: - resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} - engines: {node: '>=8.6'} + resolution: + { + integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==, + } + engines: { node: '>=8.6' } env-paths@2.2.1: - resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} - engines: {node: '>=6'} - - envinfo@7.8.1: - resolution: {integrity: sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==, + } + engines: { node: '>=6' } + + envinfo@7.13.0: + resolution: + { + integrity: sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==, + } + engines: { node: '>=4' } hasBin: true err-code@2.0.3: - resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} - - error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - - es-abstract@1.23.9: - resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==, + } + + error-ex@1.3.4: + resolution: + { + integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==, + } + + es-abstract@1.24.1: + resolution: + { + integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==, + } + engines: { node: '>= 0.4' } es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} + 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@1.6.0: - resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} + resolution: + { + integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==, + } + engines: { node: '>= 0.4' } + + es-module-lexer@1.7.0: + resolution: + { + integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==, + } es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==, + } + engines: { node: '>= 0.4' } es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} - - es-shim-unscopables@1.0.2: - resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} + resolution: + { + integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==, + } + engines: { node: '>= 0.4' } + + es-shim-unscopables@1.1.0: + resolution: + { + integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==, + } + engines: { node: '>= 0.4' } es-to-primitive@1.3.0: - resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} - engines: {node: '>= 0.4'} - - esbuild@0.24.2: - resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==, + } + engines: { node: '>= 0.4' } + + esbuild@0.27.3: + resolution: + { + integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==, + } + engines: { node: '>=18' } hasBin: true escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} + 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@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==, + } + engines: { node: '>=0.8.0' } escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - escodegen@2.1.0: - resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} - engines: {node: '>=6.0'} - hasBin: true - - eslint-config-prettier@9.1.0: - resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + resolution: + { + integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==, + } + engines: { node: '>=10' } + + eslint-config-prettier@9.1.2: + resolution: + { + integrity: sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==, + } hasBin: true peerDependencies: eslint: '>=7.0.0' eslint-import-resolver-node@0.3.9: - resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} - - eslint-import-resolver-typescript@3.7.0: - resolution: {integrity: sha512-Vrwyi8HHxY97K5ebydMtffsWAn1SCR9eol49eCd5fJS4O1WV7PaAjbcjmbfJJSMz/t4Mal212Uz/fQZrOB8mow==} - engines: {node: ^14.18.0 || >=16.0.0} + resolution: + { + integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==, + } + + eslint-import-resolver-typescript@3.10.1: + resolution: + { + integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==, + } + engines: { node: ^14.18.0 || >=16.0.0 } peerDependencies: eslint: '*' eslint-plugin-import: '*' @@ -2416,9 +2965,12 @@ packages: eslint-plugin-import-x: optional: true - eslint-module-utils@2.12.0: - resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} - engines: {node: '>=4'} + eslint-module-utils@2.12.1: + resolution: + { + integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==, + } + engines: { node: '>=4' } peerDependencies: '@typescript-eslint/parser': '*' eslint: '*' @@ -2437,9 +2989,12 @@ packages: eslint-import-resolver-webpack: optional: true - eslint-plugin-import@2.31.0: - resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} - engines: {node: '>=4'} + eslint-plugin-import@2.32.0: + resolution: + { + integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==, + } + engines: { node: '>=4' } peerDependencies: '@typescript-eslint/parser': '*' eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 @@ -2447,13 +3002,16 @@ packages: '@typescript-eslint/parser': optional: true - eslint-plugin-prettier@5.2.3: - resolution: {integrity: sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==} - engines: {node: ^14.18.0 || >=16.0.0} + eslint-plugin-prettier@5.5.5: + resolution: + { + integrity: sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==, + } + engines: { node: ^14.18.0 || >=16.0.0 } peerDependencies: '@types/eslint': '>=8.0.0' eslint: '>=8.0.0' - eslint-config-prettier: '*' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' prettier: '>=3.0.0' peerDependenciesMeta: '@types/eslint': @@ -2462,1086 +3020,1418 @@ packages: optional: true eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + resolution: + { + integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } 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} + resolution: + { + integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } eslint@8.57.1: - resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + resolution: + { + integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } hasBin: true espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + resolution: + { + integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==, + } + engines: { node: '>=4' } hasBin: true - esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} - engines: {node: '>=0.10'} + 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'} + 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'} + resolution: + { + integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==, + } + engines: { node: '>=4.0' } estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + resolution: + { + integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==, + } esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - - eval-estree-expression@2.0.3: - resolution: {integrity: sha512-6zXgUV+NHvx6PwHxPsIQ8T4cCUgsnhaH6ZyYF1OSKZIrkcAzvSvZgHAbdj72GlNm8eH6c8FI8ywcwqm42Xq1aQ==} + resolution: + { + integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==, + } + engines: { node: '>=0.10.0' } eventemitter3@4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + resolution: + { + integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==, + } - eventemitter3@5.0.1: - resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + eventemitter3@5.0.4: + resolution: + { + integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==, + } execa@5.0.0: - resolution: {integrity: sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==} - engines: {node: '>=10'} - - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==, + } + engines: { node: '>=10' } execa@7.2.0: - resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} - engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} - - exit@0.1.2: - resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} - engines: {node: '>= 0.8.0'} - - expect-type@1.1.0: - resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} - engines: {node: '>=12.0.0'} - - expect@29.7.0: - resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - exponential-backoff@3.1.1: - resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} - - external-editor@3.1.0: - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} - engines: {node: '>=4'} - - extsprintf@1.4.1: - resolution: {integrity: sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==} - engines: {'0': node >=0.6.0} + resolution: + { + integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==, + } + engines: { node: ^14.18.0 || ^16.14.0 || >=18.0.0 } + + expect-type@1.3.0: + resolution: + { + integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==, + } + engines: { node: '>=12.0.0' } + + exponential-backoff@3.1.3: + resolution: + { + integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==, + } fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + resolution: + { + integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, + } fast-diff@1.3.0: - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + resolution: + { + integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==, + } fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} + resolution: + { + integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==, + } + engines: { node: '>=8.6.0' } fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + resolution: + { + integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, + } fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - - fast-uri@3.0.6: - resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} - - fastq@1.18.0: - resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==} - - fb-watchman@2.0.2: - resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + resolution: + { + integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, + } + + fast-uri@3.1.0: + resolution: + { + integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==, + } + + fastq@1.20.1: + resolution: + { + integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==, + } + + fdir@6.5.0: + resolution: + { + integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==, + } + engines: { node: '>=12.0.0' } + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true figures@3.2.0: - resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==, + } + engines: { node: '>=8' } file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} + resolution: + { + integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==, + } + engines: { node: ^10.12.0 || >=12.0.0 } filelist@1.0.4: - resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} - - filing-cabinet@4.2.0: - resolution: {integrity: sha512-YZ21ryzRcyqxpyKggdYSoXx//d3sCJzM3lsYoaeg/FyXdADGJrUl+BW1KIglaVLJN5BBcMtWylkygY8zBp2MrQ==} - engines: {node: '>=14'} - hasBin: true + resolution: + { + integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==, + } fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==, + } + engines: { node: '>=8' } find-up@2.1.0: - resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==, + } + engines: { node: '>=4' } find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==, + } + engines: { node: '>=8' } find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==, + } + engines: { node: '>=10' } flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} + resolution: + { + integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==, + } + engines: { node: ^10.12.0 || >=12.0.0 } flat@5.0.2: - resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + resolution: + { + integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==, + } hasBin: true - flatted@3.3.2: - resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} - - follow-redirects@1.15.6: - resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} - engines: {node: '>=4.0'} + flatted@3.3.3: + resolution: + { + integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==, + } + + follow-redirects@1.15.11: + resolution: + { + integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==, + } + engines: { node: '>=4.0' } peerDependencies: debug: '*' peerDependenciesMeta: debug: optional: true - for-each@0.3.3: - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} - - foreground-child@2.0.0: - resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==} - engines: {node: '>=8.0.0'} - - foreground-child@3.3.0: - resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} - engines: {node: '>=14'} - - form-data-encoder@2.1.4: - resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} - engines: {node: '>= 14.17'} - - form-data@4.0.1: - resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} - engines: {node: '>= 6'} + for-each@0.3.5: + resolution: + { + integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==, + } + engines: { node: '>= 0.4' } + + foreground-child@3.3.1: + resolution: + { + integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==, + } + engines: { node: '>=14' } + + form-data@4.0.5: + resolution: + { + integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==, + } + engines: { node: '>= 6' } + + front-matter@4.0.2: + resolution: + { + integrity: sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==, + } fs-constants@1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - - fs-extra@10.1.0: - resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} - engines: {node: '>=12'} - - fs-extra@11.3.0: - resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} - engines: {node: '>=14.14'} - - fs-extra@8.1.0: - resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} - engines: {node: '>=6 <7 || >=8'} + resolution: + { + integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==, + } + + fs-extra@11.3.3: + resolution: + { + integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==, + } + engines: { node: '>=14.14' } fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} + resolution: + { + integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==, + } + engines: { node: '>= 8' } fs-minipass@3.0.3: - resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + resolution: + { + integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + resolution: + { + integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==, + } fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + 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==} + resolution: + { + integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==, + } function.prototype.name@1.1.8: - resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==, + } + engines: { node: '>= 0.4' } functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - - gauge@4.0.4: - resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This package is no longer supported. - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - get-amd-module-type@5.0.1: - resolution: {integrity: sha512-jb65zDeHyDjFR1loOVk0HQGM5WNwoGB8aLWy3LKCieMKol0/ProHkhO2X1JxojuN10vbz1qNn09MJ7tNp7qMzw==} - engines: {node: '>=14'} + resolution: + { + integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==, + } + + generator-function@2.0.1: + resolution: + { + integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==, + } + engines: { node: '>= 0.4' } get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - get-east-asian-width@1.3.0: - resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} - engines: {node: '>=18'} - - get-intrinsic@1.2.7: - resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} - engines: {node: '>= 0.4'} - - get-own-enumerable-property-symbols@3.0.2: - resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} - - get-package-type@0.1.0: - resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} - engines: {node: '>=8.0.0'} + resolution: + { + integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==, + } + engines: { node: 6.* || 8.* || >= 10.* } + + get-east-asian-width@1.4.0: + resolution: + { + integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==, + } + engines: { node: '>=18' } + + get-intrinsic@1.3.0: + resolution: + { + integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==, + } + engines: { node: '>= 0.4' } get-pkg-repo@4.2.1: - resolution: {integrity: sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==} - engines: {node: '>=6.9.0'} + resolution: + { + integrity: sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==, + } + engines: { node: '>=6.9.0' } hasBin: true get-port@5.1.1: - resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==, + } + engines: { node: '>=8' } get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==, + } + engines: { node: '>= 0.4' } get-stream@6.0.0: - resolution: {integrity: sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==, + } + engines: { node: '>=10' } get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==, + } + engines: { node: '>=10' } get-symbol-description@1.1.0: - resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} - engines: {node: '>= 0.4'} - - get-tsconfig@4.9.0: - resolution: {integrity: sha512-52n24W52sIueosRe0XZ8Ex5Yle+WbhfCKnV/gWXpbVR8FXNTfqdKEKUSypKso66VRHTvvcQxL44UTZbJRlCTnw==} + resolution: + { + integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==, + } + engines: { node: '>= 0.4' } + + get-tsconfig@4.13.6: + resolution: + { + integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==, + } git-raw-commits@3.0.0: - resolution: {integrity: sha512-b5OHmZ3vAgGrDn/X0kS+9qCfNKWe4K/jFnhwzVWWg0/k5eLa3060tZShrRg8Dja5kPc+YjS0Gc6y7cRr44Lpjw==} - engines: {node: '>=14'} + resolution: + { + integrity: sha512-b5OHmZ3vAgGrDn/X0kS+9qCfNKWe4K/jFnhwzVWWg0/k5eLa3060tZShrRg8Dja5kPc+YjS0Gc6y7cRr44Lpjw==, + } + engines: { node: '>=14' } hasBin: true git-remote-origin-url@2.0.0: - resolution: {integrity: sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==, + } + engines: { node: '>=4' } git-semver-tags@5.0.1: - resolution: {integrity: sha512-hIvOeZwRbQ+7YEUmCkHqo8FOLQZCEn18yevLHADlFPZY02KJGsu5FZt9YW/lybfK2uhWFI7Qg/07LekJiTv7iA==} - engines: {node: '>=14'} + resolution: + { + integrity: sha512-hIvOeZwRbQ+7YEUmCkHqo8FOLQZCEn18yevLHADlFPZY02KJGsu5FZt9YW/lybfK2uhWFI7Qg/07LekJiTv7iA==, + } + engines: { node: '>=14' } hasBin: true git-up@7.0.0: - resolution: {integrity: sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==} + resolution: + { + integrity: sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==, + } - git-url-parse@13.1.0: - resolution: {integrity: sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA==} + git-url-parse@14.0.0: + resolution: + { + integrity: sha512-NnLweV+2A4nCvn4U/m2AoYu0pPKlsmhK9cknG7IMwsjFY1S2jxM+mAhsDxyxfCIGfGaD+dozsyX4b6vkYc83yQ==, + } gitconfiglocal@1.0.0: - resolution: {integrity: sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==} + resolution: + { + integrity: sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==, + } glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} + resolution: + { + integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==, + } + engines: { node: '>= 6' } glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + resolution: + { + integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==, + } + engines: { node: '>=10.13.0' } + + glob@10.5.0: + resolution: + { + integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==, + } hasBin: true - glob@7.1.4: - resolution: {integrity: sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==} - deprecated: Glob versions prior to v9 are no longer supported - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - - glob@8.1.0: - resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} - engines: {node: '>=12'} - deprecated: Glob versions prior to v9 are no longer supported + resolution: + { + integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==, + } glob@9.3.5: - resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==} - engines: {node: '>=16 || 14 >=14.17'} - - globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==, + } + engines: { node: '>=16 || 14 >=14.17' } globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==, + } + engines: { node: '>=8' } globalthis@1.0.4: - resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==, + } + engines: { node: '>= 0.4' } globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - - globby@14.0.2: - resolution: {integrity: sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==} - engines: {node: '>=18'} - - gonzales-pe@4.3.0: - resolution: {integrity: sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==} - engines: {node: '>=0.6.0'} - hasBin: true + resolution: + { + integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==, + } + engines: { node: '>=10' } gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - - got@12.6.1: - resolution: {integrity: sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==} - engines: {node: '>=14.16'} - - graceful-fs@4.2.10: - resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + resolution: + { + integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==, + } + engines: { node: '>= 0.4' } graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - grapheme-splitter@1.0.4: - resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + resolution: + { + integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==, + } graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + resolution: + { + integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==, + } handlebars@4.7.8: - resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} - engines: {node: '>=0.4.7'} + resolution: + { + integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==, + } + engines: { node: '>=0.4.7' } hasBin: true hard-rejection@2.1.0: - resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==, + } + engines: { node: '>=6' } has-bigints@1.1.0: - resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} - engines: {node: '>= 0.4'} - - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==, + } + engines: { node: '>= 0.4' } has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, + } + engines: { node: '>=8' } has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + resolution: + { + integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==, + } has-proto@1.2.0: - resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==, + } + engines: { node: '>= 0.4' } has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==, + } + engines: { node: '>= 0.4' } has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==, + } + engines: { node: '>= 0.4' } has-unicode@2.0.1: - resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + resolution: + { + integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==, + } hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==, + } + engines: { node: '>= 0.4' } hosted-git-info@2.8.9: - resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} - - hosted-git-info@3.0.8: - resolution: {integrity: sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==, + } hosted-git-info@4.1.0: - resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} - engines: {node: '>=10'} - - hosted-git-info@6.1.3: - resolution: {integrity: sha512-HVJyzUrLIL1c0QmviVh5E8VGyUS7xCFPS6yydaVd1UegW+ibV/CohqTH9MkOLDp5o+rb82DMo77PTuc9F/8GKw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + resolution: + { + integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==, + } + engines: { node: '>=10' } + + hosted-git-info@7.0.2: + resolution: + { + integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==, + } + engines: { node: ^16.14.0 || >=18.0.0 } html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - - http-cache-semantics@4.1.1: - resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} - - http-proxy-agent@5.0.0: - resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} - engines: {node: '>= 6'} - - http2-wrapper@2.2.1: - resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} - engines: {node: '>=10.19.0'} - - https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} + resolution: + { + integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==, + } + + http-cache-semantics@4.2.0: + resolution: + { + integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==, + } + + http-proxy-agent@7.0.2: + resolution: + { + integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==, + } + engines: { node: '>= 14' } + + https-proxy-agent@7.0.6: + resolution: + { + integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==, + } + engines: { node: '>= 14' } human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} + resolution: + { + integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==, + } + engines: { node: '>=10.17.0' } human-signals@4.3.1: - resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} - engines: {node: '>=14.18.0'} - - humanize-ms@1.2.1: - resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + resolution: + { + integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==, + } + engines: { node: '>=14.18.0' } husky@8.0.3: - resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} - engines: {node: '>=14'} + resolution: + { + integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==, + } + engines: { node: '>=14' } hasBin: true - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==, + } + engines: { node: '>=0.10.0' } + + iconv-lite@0.7.2: + resolution: + { + integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==, + } + engines: { node: '>=0.10.0' } ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - ignore-walk@5.0.1: - resolution: {integrity: sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + resolution: + { + integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==, + } ignore-walk@6.0.5: - resolution: {integrity: sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + resolution: + { + integrity: sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - - import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==, + } + engines: { node: '>= 4' } + + import-fresh@3.3.1: + resolution: + { + integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==, + } + engines: { node: '>=6' } import-local@3.1.0: - resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} - engines: {node: '>=8'} - hasBin: true - - import-local@3.2.0: - resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==, + } + engines: { node: '>=8' } hasBin: true imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} + resolution: + { + integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==, + } + engines: { node: '>=0.8.19' } indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - - infer-owner@1.0.4: - resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} + resolution: + { + integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==, + } + engines: { node: '>=8' } inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + resolution: + { + integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==, + } inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + resolution: + { + integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==, + } ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - - init-package-json@5.0.0: - resolution: {integrity: sha512-kBhlSheBfYmq3e0L1ii+VKe3zBTLL5lDCDWR+f9dLmEGSB3MqLlMlsolubSsyI88Bg6EA+BIMlomAnQ1SwgQBw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - inquirer@12.3.2: - resolution: {integrity: sha512-YjQCIcDd3yyDuQrbII0FBtm/ZqNoWtvaC71yeCnd5Vbg4EgzsAGaemzfpzmqfvIZEp2roSwuZZKdM0C65hA43g==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - - inquirer@8.2.6: - resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} - engines: {node: '>=12.0.0'} + resolution: + { + integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==, + } + + ini@4.1.3: + resolution: + { + integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + + init-package-json@6.0.3: + resolution: + { + integrity: sha512-Zfeb5ol+H+eqJWHTaGca9BovufyGeIfr4zaaBorPmJBMrJ+KBnN+kQx2ZtXdsotUTgldHmHQV44xvUWOUA7E2w==, + } + engines: { node: ^16.14.0 || >=18.0.0 } + + inquirer@8.2.7: + resolution: + { + integrity: sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==, + } + engines: { node: '>=12.0.0' } internal-slot@1.1.0: - resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} - engines: {node: '>= 0.4'} - - ip-address@9.0.5: - resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} - engines: {node: '>= 12'} - - ip@2.0.1: - resolution: {integrity: sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==} + resolution: + { + integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==, + } + engines: { node: '>= 0.4' } + + ip-address@10.1.0: + resolution: + { + integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==, + } + engines: { node: '>= 12' } is-array-buffer@3.0.5: - resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==, + } + engines: { node: '>= 0.4' } is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - - is-async-function@2.1.0: - resolution: {integrity: sha512-GExz9MtyhlZyXYLxzlJRj5WUCE661zhDa1Yna52CN57AJsymh+DvXXjyveSioqSRdxvUrdKdvqB1b5cVKsNpWQ==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==, + } + + is-async-function@2.1.1: + resolution: + { + integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==, + } + engines: { node: '>= 0.4' } is-bigint@1.1.0: - resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} - engines: {node: '>= 0.4'} - - is-boolean-object@1.2.1: - resolution: {integrity: sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==} - engines: {node: '>= 0.4'} - - is-bun-module@1.3.0: - resolution: {integrity: sha512-DgXeu5UWI0IsMQundYb5UAOzm6G2eVnarJ0byP6Tm55iZNKceD59LNPA2L4VvsScTtHcw0yEkVwSf7PC+QoLSA==} + resolution: + { + integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==, + } + engines: { node: '>= 0.4' } + + is-boolean-object@1.2.2: + resolution: + { + integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==, + } + engines: { node: '>= 0.4' } + + is-bun-module@2.0.0: + resolution: + { + integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==, + } is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==, + } + engines: { node: '>= 0.4' } is-ci@3.0.1: - resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} + resolution: + { + integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==, + } hasBin: true is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==, + } + engines: { node: '>= 0.4' } is-data-view@1.0.2: - resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==, + } + engines: { node: '>= 0.4' } is-date-object@1.1.0: - resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==, + } + engines: { node: '>= 0.4' } is-docker@2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==, + } + engines: { node: '>=8' } hasBin: true is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==, + } + engines: { node: '>=0.10.0' } is-finalizationregistry@1.1.1: - resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==, + } + engines: { node: '>= 0.4' } is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==, + } + engines: { node: '>=8' } is-fullwidth-code-point@4.0.0: - resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} - engines: {node: '>=12'} - - is-generator-fn@2.1.0: - resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} - engines: {node: '>=6'} - - is-generator-function@1.1.0: - resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==, + } + engines: { node: '>=12' } + + is-generator-function@1.1.2: + resolution: + { + integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==, + } + engines: { node: '>= 0.4' } is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==, + } + engines: { node: '>=0.10.0' } is-interactive@1.0.0: - resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==, + } + engines: { node: '>=8' } is-interactive@2.0.0: - resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==, + } + engines: { node: '>=12' } is-lambda@1.0.1: - resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} + resolution: + { + integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==, + } is-map@2.0.3: - resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==, + } + engines: { node: '>= 0.4' } + + is-negative-zero@2.0.3: + resolution: + { + integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==, + } + engines: { node: '>= 0.4' } is-number-object@1.1.1: - resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==, + } + engines: { node: '>= 0.4' } is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-obj@1.0.1: - resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} - engines: {node: '>=0.10.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'} + resolution: + { + integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==, + } + engines: { node: '>=8' } is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==, + } + engines: { node: '>=8' } is-plain-obj@1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==, + } + engines: { node: '>=0.10.0' } is-plain-object@2.0.4: - resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} - engines: {node: '>=0.10.0'} - - is-plain-object@5.0.0: - resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==, + } + engines: { node: '>=0.10.0' } is-regex@1.2.1: - resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} - engines: {node: '>= 0.4'} - - is-regexp@1.0.0: - resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} - engines: {node: '>=0.10.0'} - - is-relative-path@1.0.2: - resolution: {integrity: sha512-i1h+y50g+0hRbBD+dbnInl3JlJ702aar58snAeX+MxBAPvzXGej7sYoPMhlnykabt0ZzCJNBEyzMlekuQZN7fA==} + resolution: + { + integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==, + } + engines: { node: '>= 0.4' } is-set@2.0.3: - resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==, + } + engines: { node: '>= 0.4' } is-shared-array-buffer@1.0.4: - resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} - engines: {node: '>= 0.4'} - - is-ssh@1.4.0: - resolution: {integrity: sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==} + resolution: + { + integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==, + } + engines: { node: '>= 0.4' } + + is-ssh@1.4.1: + resolution: + { + integrity: sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==, + } is-stream@2.0.0: - resolution: {integrity: sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==} - engines: {node: '>=8'} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==, + } + engines: { node: '>=8' } is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + resolution: + { + integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } is-string@1.1.1: - resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==, + } + engines: { node: '>= 0.4' } is-symbol@1.1.1: - resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==, + } + engines: { node: '>= 0.4' } is-text-path@1.0.1: - resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==, + } + engines: { node: '>=0.10.0' } is-typed-array@1.1.15: - resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==, + } + engines: { node: '>= 0.4' } is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==, + } + engines: { node: '>=10' } is-unicode-supported@1.3.0: - resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==, + } + engines: { node: '>=12' } is-unicode-supported@2.1.0: - resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} - engines: {node: '>=18'} - - is-url-superb@4.0.0: - resolution: {integrity: sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA==} - engines: {node: '>=10'} - - is-url@1.2.4: - resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} + resolution: + { + integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==, + } + engines: { node: '>=18' } is-weakmap@2.0.2: - resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} - engines: {node: '>= 0.4'} - - is-weakref@1.1.0: - resolution: {integrity: sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==, + } + engines: { node: '>= 0.4' } + + is-weakref@1.1.1: + resolution: + { + integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==, + } + engines: { node: '>= 0.4' } is-weakset@2.0.4: - resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==, + } + engines: { node: '>= 0.4' } is-wsl@2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==, + } + engines: { node: '>=8' } isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + resolution: + { + integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==, + } isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + resolution: + { + integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==, + } isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + resolution: + { + integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, + } + + isexe@3.1.5: + resolution: + { + integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==, + } + engines: { node: '>=18' } isobject@3.0.1: - resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==, + } + engines: { node: '>=0.10.0' } istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} - - istanbul-lib-instrument@5.2.1: - resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} - engines: {node: '>=8'} - - istanbul-lib-instrument@6.0.3: - resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} - engines: {node: '>=10'} + 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-lib-source-maps@4.0.1: - resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==, + } + engines: { node: '>=10' } istanbul-lib-source-maps@5.0.6: - resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} - engines: {node: '>=10'} - - istanbul-reports@3.1.7: - resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==, + } + engines: { node: '>=10' } + + istanbul-reports@3.2.0: + resolution: + { + integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==, + } + engines: { node: '>=8' } jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - - jake@10.9.2: - resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==} - engines: {node: '>=10'} - hasBin: true - - jest-changed-files@29.7.0: - resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-circus@29.7.0: - resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-cli@29.7.0: - resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + resolution: + { + integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==, + } + + jake@10.9.4: + resolution: + { + integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==, + } + engines: { node: '>=10' } hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - jest-config@29.7.0: - resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@types/node': '*' - ts-node: '>=9.0.0' - peerDependenciesMeta: - '@types/node': - optional: true - ts-node: - optional: true jest-diff@29.7.0: - resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-docblock@29.7.0: - resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-each@29.7.0: - resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-environment-node@29.7.0: - resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + resolution: + { + integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } jest-get-type@29.6.3: - resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-haste-map@29.7.0: - resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-leak-detector@29.7.0: - resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-matcher-utils@29.7.0: - resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-message-util@29.7.0: - resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-mock@29.7.0: - resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-pnp-resolver@1.2.3: - resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} - engines: {node: '>=6'} - peerDependencies: - jest-resolve: '*' - peerDependenciesMeta: - jest-resolve: - optional: true - - jest-regex-util@29.6.3: - resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-resolve-dependencies@29.7.0: - resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-resolve@29.7.0: - resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-runner@29.7.0: - resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-runtime@29.7.0: - resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-snapshot@29.7.0: - resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-util@29.7.0: - resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-validate@29.7.0: - resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-watcher@29.7.0: - resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-worker@29.7.0: - resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest@29.7.0: - resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - jju@1.4.0: - resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + resolution: + { + integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + js-levenshtein@1.1.6: + resolution: + { + integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==, + } + engines: { node: '>=0.10.0' } + + 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@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + resolution: + { + integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, + } + + js-tokens@9.0.1: + resolution: + { + integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==, + } + + js-yaml@3.14.2: + resolution: + { + integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==, + } hasBin: true js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + resolution: + { + integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==, + } hasBin: true - jsbn@1.1.0: - resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} + js-yaml@4.1.1: + resolution: + { + integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==, + } hasBin: true json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + resolution: + { + integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==, + } json-parse-better-errors@1.0.2: - resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + resolution: + { + integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==, + } json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + resolution: + { + integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==, + } json-parse-even-better-errors@3.0.2: - resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - json-schema-diff@0.18.1: - resolution: {integrity: sha512-lLP/kbwXN85yKWwBGtraxVrJnK/v31D7UZIkSg68BrO+Qeai+aeW1u9b5H1h9uF/Uzzsa8PeaQPTYRaUcdAgWQ==} - engines: {node: '>=v10.24.1'} - hasBin: true - - json-schema-ref-parser@9.0.9: - resolution: {integrity: sha512-qcP2lmGy+JUoQJ4DOQeLaZDqH9qSkeGCK3suKWxJXS82dg728Mn3j97azDMaOUmJAN4uCq91LdPx4K7E8F1a7Q==} - engines: {node: '>=10'} - deprecated: Please switch to @apidevtools/json-schema-ref-parser - - json-schema-spec-types@0.1.2: - resolution: {integrity: sha512-MDl8fA8ONckmQOm2+eXKJaFJNvxk7eGin+XFofNjS3q3PRKSoEvgMVb0ehOpCAYkUiLoMiqdU7obV7AmzAmyLw==} + resolution: + { + integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + resolution: + { + integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==, + } json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + resolution: + { + integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==, + } + + json-schema-typed@8.0.2: + resolution: + { + integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==, + } json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + resolution: + { + integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, + } - json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + json-stringify-nice@1.1.4: + resolution: + { + integrity: sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==, + } - json-to-ast@2.1.0: - resolution: {integrity: sha512-W9Lq347r8tA1DfMvAGn9QNcgYm4Wm7Yc+k8e6vezpMnRT+NHbtlxgNBXRVjXe9YM6eTn6+p/MKOlV/aABJcSnQ==} - engines: {node: '>= 4'} + json-stringify-safe@5.0.1: + resolution: + { + integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==, + } json5@1.0.2: - resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + resolution: + { + integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==, + } hasBin: true json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==, + } + engines: { node: '>=6' } hasBin: true jsonc-parser@3.2.0: - resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + resolution: + { + integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==, + } - jsonfile@4.0.0: - resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} - - jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + jsonfile@6.2.0: + resolution: + { + integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==, + } jsonparse@1.3.1: - resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} - engines: {'0': node >= 0.2.0} - - jsonpointer@5.0.1: - resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==, + } + engines: { '0': node >= 0.2.0 } + + just-diff-apply@5.5.0: + resolution: + { + integrity: sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw==, + } + + just-diff@6.0.2: + resolution: + { + integrity: sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==, + } keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + resolution: + { + integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, + } kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} - - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - - lerna@7.4.2: - resolution: {integrity: sha512-gxavfzHfJ4JL30OvMunmlm4Anw7d7Tq6tdVHzUukLdS9nWnxCN/QB21qR+VJYp5tcyXogHKbdUEGh6qmeyzxSA==} - engines: {node: '>=16.0.0'} + resolution: + { + integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==, + } + engines: { node: '>=0.10.0' } + + lerna@8.2.4: + resolution: + { + integrity: sha512-0gaVWDIVT7fLfprfwpYcQajb7dBJv3EGavjG7zvJ+TmGx3/wovl5GklnSwM2/WeE0Z2wrIz7ndWhBcDUHVjOcQ==, + } + engines: { node: '>=18.0.0' } hasBin: true - leven@3.1.0: - resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} - engines: {node: '>=6'} - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - - libnpmaccess@7.0.2: - resolution: {integrity: sha512-vHBVMw1JFMTgEk15zRsJuSAg7QtGGHpUSEfnbcRL1/gTBag9iEfJbyjpDmdJmwMhvpoLoNBtdAUCdGnaP32hhw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - libnpmpublish@7.3.0: - resolution: {integrity: sha512-fHUxw5VJhZCNSls0KLNEG0mCD2PN1i14gH5elGOgiVnU3VgTcRahagYP2LKI1m0tFCJ+XrAm0zVYyF5RCbXzcg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + resolution: + { + integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==, + } + engines: { node: '>= 0.8.0' } + + libnpmaccess@8.0.6: + resolution: + { + integrity: sha512-uM8DHDEfYG6G5gVivVl+yQd4pH3uRclHC59lzIbSvy7b5FEwR+mU49Zq1jEyRtRFv7+M99mUW9S0wL/4laT4lw==, + } + engines: { node: ^16.14.0 || >=18.0.0 } + + libnpmpublish@9.0.9: + resolution: + { + integrity: sha512-26zzwoBNAvX9AWOPiqqF6FG4HrSCPsHFkQm7nT+xU1ggAujL/eae81RnCv4CJ2In9q9fh10B88sYSzKCUh/Ghg==, + } + engines: { node: ^16.14.0 || >=18.0.0 } lilconfig@2.1.0: - resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==, + } + engines: { node: '>=10' } lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - lines-and-columns@2.0.4: - resolution: {integrity: sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + resolution: + { + integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==, + } + + lines-and-columns@2.0.3: + resolution: + { + integrity: sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } lint-staged@14.0.1: - resolution: {integrity: sha512-Mw0cL6HXnHN1ag0mN/Dg4g6sr8uf8sn98w2Oc1ECtFto9tvRF7nkXGJRbx8gPlHyoR0pLyBr2lQHbWwmUHe1Sw==} - engines: {node: ^16.14.0 || >=18.0.0} + resolution: + { + integrity: sha512-Mw0cL6HXnHN1ag0mN/Dg4g6sr8uf8sn98w2Oc1ECtFto9tvRF7nkXGJRbx8gPlHyoR0pLyBr2lQHbWwmUHe1Sw==, + } + engines: { node: ^16.14.0 || >=18.0.0 } hasBin: true listr2@6.6.1: - resolution: {integrity: sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==} - engines: {node: '>=16.0.0'} + resolution: + { + integrity: sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==, + } + engines: { node: '>=16.0.0' } peerDependencies: enquirer: '>= 2.3.0 < 3' peerDependenciesMeta: @@ -3549,440 +4439,565 @@ packages: optional: true load-json-file@4.0.0: - resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==, + } + engines: { node: '>=4' } load-json-file@6.2.0: - resolution: {integrity: sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ==, + } + engines: { node: '>=8' } locate-path@2.0.0: - resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==, + } + engines: { node: '>=4' } locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==, + } + engines: { node: '>=8' } locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - lodash.clonedeep@4.5.0: - resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} - - lodash.get@4.4.2: - resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} - - lodash.isequal@4.5.0: - resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + resolution: + { + integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==, + } + engines: { node: '>=10' } lodash.ismatch@4.4.0: - resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==} - - lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + resolution: + { + integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==, + } lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + resolution: + { + integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, + } - lodash.truncate@4.4.2: - resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} - - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lodash@4.17.23: + resolution: + { + integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==, + } log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==, + } + engines: { node: '>=10' } log-symbols@6.0.0: - resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==, + } + engines: { node: '>=18' } log-update@5.0.1: - resolution: {integrity: sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - loupe@3.1.2: - resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} - - lowercase-keys@3.0.0: - resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + resolution: + { + integrity: sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + loupe@3.2.1: + resolution: + { + integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==, + } lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - - lru-cache@4.1.5: - resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + resolution: + { + integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==, + } lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - - lru-cache@7.18.3: - resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} - engines: {node: '>=12'} - - madge@7.0.0: - resolution: {integrity: sha512-x9eHkBWoCJ2B8yGesWf8LRucarkbH5P3lazqgvmxe4xn5U2Meyfu906iG9mBB1RnY/f4D+gtELWdiz1k6+jAZA==} - engines: {node: '>=14'} - hasBin: true - peerDependencies: - typescript: ^3.9.5 || ^4.9.5 || ^5 - peerDependenciesMeta: - typescript: - optional: true - - magic-string@0.30.17: - resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + resolution: + { + integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==, + } + engines: { node: '>=10' } + + magic-string@0.30.21: + resolution: + { + integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==, + } magicast@0.3.5: - resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + resolution: + { + integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==, + } make-dir@2.1.0: - resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==, + } + engines: { node: '>=6' } make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} - - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - - make-fetch-happen@10.2.1: - resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - - make-fetch-happen@11.1.1: - resolution: {integrity: sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - makeerror@1.0.12: - resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + resolution: + { + integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==, + } + engines: { node: '>=10' } + + make-fetch-happen@13.0.1: + resolution: + { + integrity: sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==, + } + engines: { node: ^16.14.0 || >=18.0.0 } map-obj@1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==, + } + engines: { node: '>=0.10.0' } map-obj@4.3.0: - resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==, + } + engines: { node: '>=8' } math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==, + } + engines: { node: '>= 0.4' } meow@8.1.2: - resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==, + } + engines: { node: '>=10' } merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + resolution: + { + integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==, + } merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} + resolution: + { + integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==, + } + engines: { node: '>= 8' } micromatch@4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} + resolution: + { + integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==, + } + engines: { node: '>=8.6' } micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} + resolution: + { + integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==, + } + engines: { node: '>=8.6' } mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} + resolution: + { + integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==, + } + engines: { node: '>= 0.6' } mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} + resolution: + { + integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==, + } + engines: { node: '>= 0.6' } mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==, + } + engines: { node: '>=6' } mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==, + } + engines: { node: '>=12' } mimic-function@5.0.1: - resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} - engines: {node: '>=18'} - - mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - - mimic-response@4.0.0: - resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + resolution: + { + integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==, + } + engines: { node: '>=18' } min-indent@1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==, + } + engines: { node: '>=4' } minimatch@3.0.5: - resolution: {integrity: sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==} + resolution: + { + integrity: sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==, + } minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + resolution: + { + integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==, + } minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==, + } + engines: { node: '>=10' } minimatch@8.0.4: - resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==} - engines: {node: '>=16 || 14 >=14.17'} + resolution: + { + integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==, + } + engines: { node: '>=16 || 14 >=14.17' } minimatch@9.0.3: - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} - engines: {node: '>=16 || 14 >=14.17'} + resolution: + { + integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==, + } + engines: { node: '>=16 || 14 >=14.17' } minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} + resolution: + { + integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==, + } + engines: { node: '>=16 || 14 >=14.17' } minimist-options@4.1.0: - resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} - engines: {node: '>= 6'} + resolution: + { + integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==, + } + engines: { node: '>= 6' } minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - minipass-collect@1.0.2: - resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} - engines: {node: '>= 8'} - - minipass-fetch@2.1.2: - resolution: {integrity: sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + resolution: + { + integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==, + } + + minipass-collect@2.0.1: + resolution: + { + integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==, + } + engines: { node: '>=16 || 14 >=14.17' } minipass-fetch@3.0.5: - resolution: {integrity: sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + resolution: + { + integrity: sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } minipass-flush@1.0.5: - resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} - engines: {node: '>= 8'} - - minipass-json-stream@1.0.2: - resolution: {integrity: sha512-myxeeTm57lYs8pH2nxPzmEEg8DGIgW+9mv6D4JZD2pa81I/OBjeU7PtICXV6c9eRGTA5JMDsuIPUZRCyBMYNhg==} + resolution: + { + integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==, + } + engines: { node: '>= 8' } minipass-pipeline@1.2.4: - resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==, + } + engines: { node: '>=8' } minipass-sized@1.0.3: - resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==, + } + engines: { node: '>=8' } minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==, + } + engines: { node: '>=8' } minipass@4.2.8: - resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==, + } + engines: { node: '>=8' } minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==, + } + engines: { node: '>=8' } minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} + resolution: + { + integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==, + } + engines: { node: '>=16 || 14 >=14.17' } minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} + resolution: + { + integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==, + } + engines: { node: '>= 8' } mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==, + } + engines: { node: '>=10' } hasBin: true modify-values@1.0.1: - resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} - engines: {node: '>=0.10.0'} - - module-definition@5.0.1: - resolution: {integrity: sha512-kvw3B4G19IXk+BOXnYq/D/VeO9qfHaapMeuS7w7sNUqmGaA6hywdFHMi+VWeR9wUScXM7XjoryTffCZ5B0/8IA==} - engines: {node: '>=14'} - hasBin: true - - module-lookup-amd@8.0.5: - resolution: {integrity: sha512-vc3rYLjDo5Frjox8NZpiyLXsNWJ5BWshztc/5KSOMzpg9k5cHH652YsJ7VKKmtM4SvaxuE9RkrYGhiSjH3Ehow==} - engines: {node: '>=14'} - hasBin: true + resolution: + { + integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==, + } + engines: { node: '>=0.10.0' } ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + resolution: + { + integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==, + } ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + resolution: + { + integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, + } multimatch@5.0.0: - resolution: {integrity: sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==} - engines: {node: '>=10'} - - mustache@4.2.0: - resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} - hasBin: true + resolution: + { + integrity: sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==, + } + engines: { node: '>=10' } mute-stream@0.0.8: - resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + resolution: + { + integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==, + } mute-stream@1.0.0: - resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - mute-stream@2.0.0: - resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} - engines: {node: ^18.17.0 || >=20.5.0} + resolution: + { + integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + + mute-stream@3.0.0: + resolution: + { + integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==, + } + engines: { node: ^20.17.0 || >=22.9.0 } + + nanoid@3.3.11: + resolution: + { + integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==, + } + engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + hasBin: true - nanoid@3.3.8: - resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + napi-postinstall@0.3.4: + resolution: + { + integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==, + } + engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } hasBin: true natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + resolution: + { + integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, + } negotiator@0.6.4: - resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} - engines: {node: '>= 0.6'} + resolution: + { + integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==, + } + engines: { node: '>= 0.6' } neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - - node-addon-api@3.2.1: - resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} + resolution: + { + integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==, + } node-fetch@2.6.7: - resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} - engines: {node: 4.x || >=6.0.0} + resolution: + { + integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==, + } + engines: { node: 4.x || >=6.0.0 } peerDependencies: encoding: ^0.1.0 peerDependenciesMeta: encoding: optional: true - node-gyp-build@4.8.4: - resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} - hasBin: true - - node-gyp@9.4.1: - resolution: {integrity: sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==} - engines: {node: ^12.13 || ^14.13 || >=16} + node-gyp@10.3.1: + resolution: + { + integrity: sha512-Pp3nFHBThHzVtNY7U6JfPjvT/DTE8+o/4xKsLQtBoU+j2HLsGlhcfzflAoUreaJbNmYnX+LlLi0qjV8kpyO6xQ==, + } + engines: { node: ^16.14.0 || >=18.0.0 } hasBin: true - node-int64@0.4.0: - resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - node-machine-id@1.1.12: - resolution: {integrity: sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==} - - node-releases@2.0.19: - resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} - - node-source-walk@6.0.2: - resolution: {integrity: sha512-jn9vOIK/nfqoFCcpK89/VCVaLg1IHE6UVfDOzvqmANaJ/rWCTEdH8RZ1V278nv2jr36BJdyQXIAavBLXpzdlag==} - engines: {node: '>=14'} - - nopt@6.0.0: - resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + resolution: + { + integrity: sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==, + } + + nopt@7.2.1: + resolution: + { + integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } hasBin: true normalize-package-data@2.5.0: - resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + resolution: + { + integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==, + } normalize-package-data@3.0.3: - resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} - engines: {node: '>=10'} - - normalize-package-data@5.0.0: - resolution: {integrity: sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - normalize-url@8.0.1: - resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==} - engines: {node: '>=14.16'} - - npm-bundled@1.1.2: - resolution: {integrity: sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==} + resolution: + { + integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==, + } + engines: { node: '>=10' } + + normalize-package-data@6.0.2: + resolution: + { + integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==, + } + engines: { node: ^16.14.0 || >=18.0.0 } npm-bundled@3.0.1: - resolution: {integrity: sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + resolution: + { + integrity: sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } npm-install-checks@6.3.0: - resolution: {integrity: sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - npm-normalize-package-bin@1.0.1: - resolution: {integrity: sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==} + resolution: + { + integrity: sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } npm-normalize-package-bin@3.0.1: - resolution: {integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - npm-package-arg@10.1.0: - resolution: {integrity: sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - npm-package-arg@8.1.1: - resolution: {integrity: sha512-CsP95FhWQDwNqiYS+Q0mZ7FAEDytDZAkNxQqea6IaAFJTAY9Lhhqyl0irU/6PMc7BGfUmnsbHcqxJD7XuVM/rg==} - engines: {node: '>=10'} - - npm-packlist@5.1.1: - resolution: {integrity: sha512-UfpSvQ5YKwctmodvPPkK6Fwk603aoVsf8AEbmVKAEECrfvL8SSe1A2YIwrJ6xmTHAITKPwwZsWo7WwEbNk0kxw==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - hasBin: true - - npm-packlist@7.0.4: - resolution: {integrity: sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - npm-pick-manifest@8.0.2: - resolution: {integrity: sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - npm-registry-fetch@14.0.5: - resolution: {integrity: sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + resolution: + { + integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + + npm-package-arg@11.0.2: + resolution: + { + integrity: sha512-IGN0IAwmhDJwy13Wc8k+4PEbTPhpJnMtfR53ZbOyjkvmEcLS4nCwp6mvMWjS5sUjeiW3mpx6cHmuhKEu9XmcQw==, + } + engines: { node: ^16.14.0 || >=18.0.0 } + + npm-packlist@8.0.2: + resolution: + { + integrity: sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + + npm-pick-manifest@9.1.0: + resolution: + { + integrity: sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==, + } + engines: { node: ^16.14.0 || >=18.0.0 } + + npm-registry-fetch@17.1.0: + resolution: + { + integrity: sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA==, + } + engines: { node: ^16.14.0 || >=18.0.0 } npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==, + } + engines: { node: '>=8' } npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - npmlog@6.0.2: - resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This package is no longer supported. - - nx@16.10.0: - resolution: {integrity: sha512-gZl4iCC0Hx0Qe1VWmO4Bkeul2nttuXdPpfnlcDKSACGu3ZIo+uySqwOF8yBAxSTIf8xe2JRhgzJN1aFkuezEBg==} + resolution: + { + integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + nx@20.8.2: + resolution: + { + integrity: sha512-mDKpbH3vEpUFDx0rrLh+tTqLq1PYU8KiD/R7OVZGd1FxQxghx2HOl32MiqNsfPcw6AvKlXhslbwIESV+N55FLQ==, + } hasBin: true peerDependencies: - '@swc-node/register': ^1.6.7 + '@swc-node/register': ^1.8.0 '@swc/core': ^1.3.85 peerDependenciesMeta: '@swc-node/register': @@ -3990,354 +5005,515 @@ packages: '@swc/core': optional: true - object-inspect@1.13.3: - resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==} - engines: {node: '>= 0.4'} + object-inspect@1.13.4: + resolution: + { + integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==, + } + engines: { node: '>= 0.4' } object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==, + } + engines: { node: '>= 0.4' } object.assign@4.1.7: - resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==, + } + engines: { node: '>= 0.4' } object.fromentries@2.0.8: - resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==, + } + engines: { node: '>= 0.4' } object.groupby@1.0.3: - resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==, + } + engines: { node: '>= 0.4' } object.values@1.2.1: - resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==, + } + engines: { node: '>= 0.4' } once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + resolution: + { + integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==, + } onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==, + } + engines: { node: '>=6' } onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==, + } + engines: { node: '>=12' } onetime@7.0.0: - resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==, + } + engines: { node: '>=18' } open@8.4.2: - resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} - engines: {node: '>=12'} - - openapi-diff@0.23.7: - resolution: {integrity: sha512-ABXJ7gTYyawmEC4Z3MiLu7CoD2fLSVfid+ttP9yiuDpytC2PDsc26OTNhYUV9y/z2/ama4CKbszkq3LMd80R6Q==} - engines: {node: '>=6.11.4'} - hasBin: true - - openapi-types@12.1.3: - resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} - - openapi-zod-client@1.18.2: - resolution: {integrity: sha512-mfqBxwnGbnfK1CwQb6TBmu8CqVUlHD013Aw82JhDf0iGZsd5oemlPzO8QtteLAaAE6cmLNmSG/tQeBjQV0vB9g==} - hasBin: true - - openapi3-ts@2.0.2: - resolution: {integrity: sha512-TxhYBMoqx9frXyOgnRHufjQfPXomTIHYKhSKJ6jHfj13kS8OEIhvmE8CTuQyKtjjWttAjX5DPxM1vmalEpo8Qw==} - - openapi3-ts@3.1.0: - resolution: {integrity: sha512-1qKTvCCVoV0rkwUh1zq5o8QyghmwYPuhdvtjv1rFjuOnJToXhQyF8eGjNETQ8QmGjr9Jz/tkAKLITIl2s7dw3A==} - - openapi3-ts@4.4.0: - resolution: {integrity: sha512-9asTNB9IkKEzWMcHmVZE7Ts3kC9G7AFHfs8i7caD8HbI76gEjdkId4z/AkP83xdZsH7PLAnnbl47qZkXuxpArw==} + resolution: + { + integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==, + } + engines: { node: '>=12' } optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} + resolution: + { + integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==, + } + engines: { node: '>= 0.8.0' } + + ora@5.3.0: + resolution: + { + integrity: sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g==, + } + engines: { node: '>=10' } ora@5.4.1: - resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} - engines: {node: '>=10'} - - ora@8.1.1: - resolution: {integrity: sha512-YWielGi1XzG1UTvOaCFaNgEnuhZVMSHYkW/FQ7UX8O26PtlpdM84c0f7wLPlkvx2RfiQmnzd61d/MGxmpQeJPw==} - engines: {node: '>=18'} - - os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==, + } + engines: { node: '>=10' } + + ora@8.2.0: + resolution: + { + integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==, + } + engines: { node: '>=18' } own-keys@1.0.1: - resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} - engines: {node: '>= 0.4'} - - p-cancelable@3.0.0: - resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} - engines: {node: '>=12.20'} + resolution: + { + integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==, + } + engines: { node: '>= 0.4' } p-finally@1.0.0: - resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==, + } + engines: { node: '>=4' } p-limit@1.3.0: - resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==, + } + engines: { node: '>=4' } p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==, + } + engines: { node: '>=6' } p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==, + } + engines: { node: '>=10' } p-locate@2.0.0: - resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==, + } + engines: { node: '>=4' } p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==, + } + engines: { node: '>=8' } p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==, + } + engines: { node: '>=10' } p-map-series@2.1.0: - resolution: {integrity: sha512-RpYIIK1zXSNEOdwxcfe7FdvGcs7+y5n8rifMhMNWvaxRNMPINJHF5GDeuVxWqnfrcHPSCnp7Oo5yNXHId9Av2Q==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-RpYIIK1zXSNEOdwxcfe7FdvGcs7+y5n8rifMhMNWvaxRNMPINJHF5GDeuVxWqnfrcHPSCnp7Oo5yNXHId9Av2Q==, + } + engines: { node: '>=8' } p-map@4.0.0: - resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==, + } + engines: { node: '>=10' } p-pipe@3.1.0: - resolution: {integrity: sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw==, + } + engines: { node: '>=8' } p-queue@6.6.2: - resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==, + } + engines: { node: '>=8' } p-reduce@2.1.0: - resolution: {integrity: sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==, + } + engines: { node: '>=8' } p-timeout@3.2.0: - resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==, + } + engines: { node: '>=8' } p-try@1.0.0: - resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==, + } + engines: { node: '>=4' } p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==, + } + engines: { node: '>=6' } p-waterfall@2.1.1: - resolution: {integrity: sha512-RRTnDb2TBG/epPRI2yYXsimO0v3BXC8Yd3ogr1545IaqKK17VGhbWVeGGN+XfCm/08OK8635nH31c8bATkHuSw==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-RRTnDb2TBG/epPRI2yYXsimO0v3BXC8Yd3ogr1545IaqKK17VGhbWVeGGN+XfCm/08OK8635nH31c8bATkHuSw==, + } + engines: { node: '>=8' } package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - package-json@8.1.1: - resolution: {integrity: sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==} - engines: {node: '>=14.16'} - - pacote@15.2.0: - resolution: {integrity: sha512-rJVZeIwHTUta23sIZgEIM62WYwbmGbThdbnkt81ravBplQv+HjyroqnLRNH2+sLJHcGZmLRmhPwACqhfTcOmnA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + resolution: + { + integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==, + } + + pacote@18.0.6: + resolution: + { + integrity: sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==, + } + engines: { node: ^16.14.0 || >=18.0.0 } hasBin: true parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - parse-github-url@1.0.3: - resolution: {integrity: sha512-tfalY5/4SqGaV/GIGzWyHnFjlpTPTNpENR9Ea2lLldSJ8EWXMsvacWucqY3m3I4YPtas15IxTLQVQ5NSYXPrww==} - engines: {node: '>= 0.10'} - hasBin: true + resolution: + { + integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, + } + engines: { node: '>=6' } + + parse-conflict-json@3.0.1: + resolution: + { + integrity: sha512-01TvEktc68vwbJOtWZluyWeVGWjP+bZwXtPDMQVbBKzbJ/vZBif0L69KH1+cHv1SZ6e0FKLvjyHe8mqsIqYOmw==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } parse-json@4.0.0: - resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} - engines: {node: '>=4'} + 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-ms@2.1.0: - resolution: {integrity: sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==} - engines: {node: '>=6'} - - parse-path@7.0.0: - resolution: {integrity: sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==} + resolution: + { + integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==, + } + engines: { node: '>=8' } + + parse-path@7.1.0: + resolution: + { + integrity: sha512-EuCycjZtfPcjWk7KTksnJ5xPMvWGA/6i4zrLYhRG0hGvC3GPU/jGUj3Cy+ZR0v30duV3e23R95T1lE2+lsndSw==, + } parse-url@8.1.0: - resolution: {integrity: sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==} - - pastable@2.2.1: - resolution: {integrity: sha512-K4ClMxRKpgN4sXj6VIPPrvor/TMp2yPNCGtfhvV106C73SwefQ3FuegURsH7AQHpqu0WwbvKXRl1HQxF6qax9w==} - engines: {node: '>=14.x'} - peerDependencies: - react: '>=17' - xstate: '>=4.32.1' - peerDependenciesMeta: - react: - optional: true - xstate: - optional: true + resolution: + { + integrity: sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==, + } path-exists@3.0.0: - resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==, + } + engines: { node: '>=4' } path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, + } + engines: { node: '>=8' } path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==, + } + engines: { node: '>=0.10.0' } path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, + } + engines: { node: '>=8' } path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==, + } + engines: { node: '>=12' } path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + resolution: + { + integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, + } path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} + resolution: + { + integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==, + } + engines: { node: '>=16 || 14 >=14.18' } path-type@3.0.0: - resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==, + } + engines: { node: '>=4' } path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - - path-type@5.0.0: - resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} - engines: {node: '>=12'} - - pathe@2.0.2: - resolution: {integrity: sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==} - - pathval@2.0.0: - resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} - engines: {node: '>= 14.16'} + resolution: + { + integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==, + } + engines: { node: '>=8' } + + pathe@2.0.3: + resolution: + { + integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==, + } + + pathval@2.0.1: + resolution: + { + integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==, + } + engines: { node: '>= 14.16' } picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + resolution: + { + integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, + } picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} + resolution: + { + integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==, + } + engines: { node: '>=8.6' } + + picomatch@4.0.3: + resolution: + { + integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==, + } + engines: { node: '>=12' } pidtree@0.6.0: - resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} - engines: {node: '>=0.10'} + resolution: + { + integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==, + } + engines: { node: '>=0.10' } hasBin: true pify@2.3.0: - resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==, + } + engines: { node: '>=0.10.0' } pify@3.0.0: - resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==, + } + engines: { node: '>=4' } pify@4.0.1: - resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==, + } + engines: { node: '>=6' } pify@5.0.0: - resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} - engines: {node: '>=10'} - - pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} - engines: {node: '>= 6'} + resolution: + { + integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==, + } + engines: { node: '>=10' } pkg-dir@4.2.0: - resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==, + } + engines: { node: '>=8' } pluralize@8.0.0: - resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} - engines: {node: '>=4'} - - possible-typed-array-names@1.0.0: - resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} - engines: {node: '>= 0.4'} - - postcss-values-parser@6.0.2: - resolution: {integrity: sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw==} - engines: {node: '>=10'} - peerDependencies: - postcss: ^8.2.9 - - postcss@8.5.1: - resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==} - engines: {node: ^10 || ^12 || >=14} - - precinct@11.0.5: - resolution: {integrity: sha512-oHSWLC8cL/0znFhvln26D14KfCQFFn4KOLSw6hmLhd+LQ2SKt9Ljm89but76Pc7flM9Ty1TnXyrA2u16MfRV3w==} - engines: {node: ^14.14.0 || >=16.0.0} - hasBin: true + resolution: + { + integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==, + } + engines: { node: '>=4' } + + possible-typed-array-names@1.1.0: + resolution: + { + integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==, + } + engines: { node: '>= 0.4' } + + postcss-selector-parser@6.1.2: + resolution: + { + integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==, + } + engines: { node: '>=4' } + + postcss@8.5.6: + resolution: + { + integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==, + } + engines: { node: ^10 || ^12 || >=14 } prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - - prettier-linter-helpers@1.0.0: - resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} - engines: {node: '>=6.0.0'} - - prettier@2.8.8: - resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} - engines: {node: '>=10.13.0'} - hasBin: true - - prettier@3.3.3: - resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} - engines: {node: '>=14'} - hasBin: true - - prettier@3.4.2: - resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==} - engines: {node: '>=14'} + resolution: + { + integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==, + } + engines: { node: '>= 0.8.0' } + + prettier-linter-helpers@1.0.1: + resolution: + { + integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==, + } + engines: { node: '>=6.0.0' } + + prettier@3.8.1: + resolution: + { + integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==, + } + engines: { node: '>=14' } hasBin: true pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - pretty-ms@7.0.1: - resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==} - engines: {node: '>=10'} - - proc-log@3.0.0: - resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + resolution: + { + integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + proc-log@4.2.0: + resolution: + { + integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + resolution: + { + integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==, + } + + proggy@2.0.0: + resolution: + { + integrity: sha512-69agxLtnI8xBs9gUGqEnK26UfiexpHy+KUpBQWabiytQjnn5wFY8rklAi7GRfABIuPNnQ/ik48+LGLkYYJcy4A==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + + promise-all-reject-late@1.0.1: + resolution: + { + integrity: sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==, + } + + promise-call-limit@3.0.2: + resolution: + { + integrity: sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw==, + } promise-inflight@1.0.1: - resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} + resolution: + { + integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==, + } peerDependencies: bluebird: '*' peerDependenciesMeta: @@ -4345,907 +5521,1114 @@ packages: optional: true promise-retry@2.0.1: - resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} - engines: {node: '>=10'} - - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} + resolution: + { + integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==, + } + engines: { node: '>=10' } promzard@1.0.2: - resolution: {integrity: sha512-2FPputGL+mP3jJ3UZg/Dl9YOkovB7DX0oOr+ck5QbZ5MtORtds8k/BZdn+02peDLI8/YWbmzx34k5fA+fHvCVQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - proto-list@1.2.4: - resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} - - protocols@2.0.1: - resolution: {integrity: sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==} + resolution: + { + integrity: sha512-2FPputGL+mP3jJ3UZg/Dl9YOkovB7DX0oOr+ck5QbZ5MtORtds8k/BZdn+02peDLI8/YWbmzx34k5fA+fHvCVQ==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + + protocols@2.0.2: + resolution: + { + integrity: sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==, + } proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - - pseudomap@1.0.2: - resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} + resolution: + { + integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==, + } punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - pure-rand@6.1.0: - resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + resolution: + { + integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==, + } + engines: { node: '>=6' } queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + resolution: + { + integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, + } quick-lru@4.0.1: - resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} - engines: {node: '>=8'} - - quick-lru@5.1.1: - resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} - engines: {node: '>=10'} - - quote-unquote@1.0.0: - resolution: {integrity: sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==} - - rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true + resolution: + { + integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==, + } + engines: { node: '>=8' } react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + resolution: + { + integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==, + } read-cmd-shim@4.0.0: - resolution: {integrity: sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + resolution: + { + integrity: sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } read-package-json-fast@3.0.2: - resolution: {integrity: sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - read-package-json@6.0.4: - resolution: {integrity: sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - deprecated: This package is no longer supported. Please use @npmcli/package-json instead. + resolution: + { + integrity: sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } read-pkg-up@3.0.0: - resolution: {integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==, + } + engines: { node: '>=4' } read-pkg-up@7.0.1: - resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==, + } + engines: { node: '>=8' } read-pkg@3.0.0: - resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==, + } + engines: { node: '>=4' } read-pkg@5.2.0: - resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} - engines: {node: '>=8'} - - read@2.1.0: - resolution: {integrity: sha512-bvxi1QLJHcaywCAEsAk4DG3nVoqiY2Csps3qzWalhj5hFqRn1d/OixkFXtLO1PrgHUcAP0FNaSY/5GYNfENFFQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + resolution: + { + integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==, + } + engines: { node: '>=8' } read@3.0.1: - resolution: {integrity: sha512-SLBrDU/Srs/9EoWhU5GdbAoxG1GzpQHo/6qiGItaoLJ1thmYpcNIM1qISEUvyHBzfGlWIyd6p2DNi1oV1VmAuw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + resolution: + { + integrity: sha512-SLBrDU/Srs/9EoWhU5GdbAoxG1GzpQHo/6qiGItaoLJ1thmYpcNIM1qISEUvyHBzfGlWIyd6p2DNi1oV1VmAuw==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + resolution: + { + integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==, + } readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} + resolution: + { + integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==, + } + engines: { node: '>= 6' } redent@3.0.0: - resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==, + } + engines: { node: '>=8' } reflect.getprototypeof@1.0.10: - resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} - engines: {node: '>= 0.4'} - - regenerator-runtime@0.14.1: - resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + resolution: + { + integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==, + } + engines: { node: '>= 0.4' } regexp.prototype.flags@1.5.4: - resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} - engines: {node: '>= 0.4'} - - registry-auth-token@5.0.3: - resolution: {integrity: sha512-1bpc9IyC+e+CNFRaWyn77tk4xGG4PPUyfakSmA6F6cvUDjrm58dfyJ3II+9yb10EDkHoy1LaPSmHaWLOH3m6HA==} - engines: {node: '>=14'} - - registry-url@6.0.1: - resolution: {integrity: sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==, + } + engines: { node: '>= 0.4' } require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} + 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'} - - requirejs-config-file@4.0.0: - resolution: {integrity: sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw==} - engines: {node: '>=10.13.0'} - - requirejs@2.3.7: - resolution: {integrity: sha512-DouTG8T1WanGok6Qjg2SXuCMzszOo0eHeH9hDZ5Y4x8Je+9JB38HdTLT4/VA8OaUhBa0JPVHJ0pyBkM1z+pDsw==} - engines: {node: '>=0.4.0'} - hasBin: true - - resolve-alpn@1.2.1: - resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + resolution: + { + integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==, + } + engines: { node: '>=0.10.0' } resolve-cwd@3.0.0: - resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} - engines: {node: '>=8'} - - resolve-dependency-path@3.0.2: - resolution: {integrity: sha512-Tz7zfjhLfsvR39ADOSk9us4421J/1ztVBo4rWUkF38hgHK5m0OCZ3NxFVpqHRkjctnwVa15igEUHFJp8MCS7vA==} - engines: {node: '>=14'} + resolution: + { + integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==, + } + engines: { node: '>=8' } resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, + } + engines: { node: '>=4' } resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==, + } + engines: { node: '>=8' } resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolution: + { + integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==, + } resolve.exports@2.0.3: - resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} - engines: {node: '>=10'} - - resolve@1.22.10: - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==, + } + engines: { node: '>=10' } + + resolve@1.22.11: + resolution: + { + integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==, + } + engines: { node: '>= 0.4' } hasBin: true - responselike@3.0.0: - resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==} - engines: {node: '>=14.16'} - restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==, + } + engines: { node: '>=8' } restore-cursor@4.0.0: - resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + resolution: + { + integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } restore-cursor@5.1.0: - resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==, + } + engines: { node: '>=18' } retry@0.12.0: - resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} - engines: {node: '>= 4'} - - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + resolution: + { + integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==, + } + engines: { node: '>= 4' } + + reusify@1.1.0: + resolution: + { + integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==, + } + engines: { iojs: '>=1.0.0', node: '>=0.10.0' } rfdc@1.4.1: - resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + resolution: + { + integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==, + } rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported + resolution: + { + integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==, + } hasBin: true rimraf@4.4.1: - resolution: {integrity: sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==} - engines: {node: '>=14'} + resolution: + { + integrity: sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==, + } + engines: { node: '>=14' } hasBin: true rimraf@5.0.10: - resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} + resolution: + { + integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==, + } hasBin: true - rollup@4.31.0: - resolution: {integrity: sha512-9cCE8P4rZLx9+PjoyqHLs31V9a9Vpvfo4qNcs6JCiGWYhw2gijSetFbH6SSy1whnkgcefnUwr8sad7tgqsGvnw==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} + rollup@4.57.1: + resolution: + { + integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==, + } + engines: { node: '>=18.0.0', npm: '>=8.0.0' } hasBin: true run-async@2.4.1: - resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} - engines: {node: '>=0.12.0'} - - run-async@3.0.0: - resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} - engines: {node: '>=0.12.0'} + resolution: + { + integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==, + } + engines: { node: '>=0.12.0' } run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + resolution: + { + integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, + } - rxjs@7.8.1: - resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + rxjs@7.8.2: + resolution: + { + integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==, + } safe-array-concat@1.1.3: - resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} - engines: {node: '>=0.4'} + resolution: + { + integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==, + } + engines: { node: '>=0.4' } safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + resolution: + { + integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==, + } safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + resolution: + { + integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==, + } safe-push-apply@1.0.0: - resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==, + } + engines: { node: '>= 0.4' } safe-regex-test@1.1.0: - resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==, + } + engines: { node: '>= 0.4' } safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - sass-lookup@5.0.1: - resolution: {integrity: sha512-t0X5PaizPc2H4+rCwszAqHZRtr4bugo4pgiCvrBFvIX0XFxnr29g77LJcpyj9A0DcKf7gXMLcgvRjsonYI6x4g==} - engines: {node: '>=14'} - hasBin: true - - sembear@0.5.2: - resolution: {integrity: sha512-Ij1vCAdFgWABd7zTg50Xw1/p0JgESNxuLlneEAsmBrKishA06ulTTL/SHGmNy2Zud7+rKrHTKNI6moJsn1ppAQ==} + resolution: + { + integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==, + } semver@5.7.2: - resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + resolution: + { + integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==, + } hasBin: true semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - semver@7.5.3: - resolution: {integrity: sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==, + } hasBin: true - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} - engines: {node: '>=10'} + semver@7.7.4: + resolution: + { + integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==, + } + engines: { node: '>=10' } hasBin: true set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + resolution: + { + integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==, + } set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==, + } + engines: { node: '>= 0.4' } set-function-name@2.0.2: - resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==, + } + engines: { node: '>= 0.4' } set-proto@1.0.0: - resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==, + } + engines: { node: '>= 0.4' } shallow-clone@3.0.1: - resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} - engines: {node: '>=8'} - - shebang-command@1.2.0: - resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==, + } + engines: { node: '>=8' } shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@1.0.0: - resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} - engines: {node: '>=0.10.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'} + resolution: + { + integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, + } + engines: { node: '>=8' } side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==, + } + engines: { node: '>= 0.4' } side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} + 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'} + resolution: + { + integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==, + } + engines: { node: '>= 0.4' } side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==, + } + engines: { node: '>= 0.4' } siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + resolution: + { + integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==, + } signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + resolution: + { + integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==, + } signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - sigstore@1.9.0: - resolution: {integrity: sha512-0Zjz0oe37d08VeOtBIuB6cRriqXse2e8w+7yIy2XSXjshRKxbc2KkhXjL229jXSxEm7UbcjS76wcJDGQddVI9A==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - hasBin: true - - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + resolution: + { + integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==, + } + engines: { node: '>=14' } + + sigstore@2.3.1: + resolution: + { + integrity: sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==, + } + engines: { node: ^16.14.0 || >=18.0.0 } slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - - slash@5.1.0: - resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} - engines: {node: '>=14.16'} - - slice-ansi@4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==, + } + engines: { node: '>=8' } slice-ansi@5.0.0: - resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==, + } + engines: { node: '>=12' } smart-buffer@4.2.0: - resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} - engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - - socks-proxy-agent@7.0.0: - resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==} - engines: {node: '>= 10'} - - socks@2.8.3: - resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==} - engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + resolution: + { + integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==, + } + engines: { node: '>= 6.0.0', npm: '>= 3.0.0' } + + socks-proxy-agent@8.0.5: + resolution: + { + integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==, + } + engines: { node: '>= 14' } + + socks@2.8.7: + resolution: + { + integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==, + } + engines: { node: '>= 10.0.0', npm: '>= 3.0.0' } sort-keys@2.0.0: - resolution: {integrity: sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==, + } + engines: { node: '>=4' } source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - source-map-support@0.5.13: - resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + 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'} - - spawndamnit@2.0.0: - resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==} + resolution: + { + integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==, + } + engines: { node: '>=0.10.0' } spdx-correct@3.2.0: - resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + resolution: + { + integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==, + } spdx-exceptions@2.5.0: - resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + resolution: + { + integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==, + } spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + resolution: + { + integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==, + } - spdx-license-ids@3.0.21: - resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==} + spdx-license-ids@3.0.22: + resolution: + { + integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==, + } split2@3.2.2: - resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} + resolution: + { + integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==, + } split@1.0.1: - resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} + resolution: + { + integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==, + } sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - - sprintf-js@1.1.3: - resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + resolution: + { + integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==, + } ssri@10.0.6: - resolution: {integrity: sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - ssri@9.0.1: - resolution: {integrity: sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - - stable-hash@0.0.4: - resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==} - - stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + + stable-hash@0.0.5: + resolution: + { + integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==, + } stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + resolution: + { + integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==, + } - std-env@3.8.0: - resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + std-env@3.10.0: + resolution: + { + integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==, + } stdin-discarder@0.2.2: - resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} - engines: {node: '>=18'} - - stream-to-array@2.3.0: - resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==} + resolution: + { + integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==, + } + engines: { node: '>=18' } + + stop-iteration-iterator@1.1.0: + resolution: + { + integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==, + } + engines: { node: '>= 0.4' } string-argv@0.3.2: - resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} - engines: {node: '>=0.6.19'} - - string-length@4.0.2: - resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} - engines: {node: '>=10'} + 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'} + resolution: + { + integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==, + } + engines: { node: '>=8' } string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==, + } + engines: { node: '>=12' } string-width@7.2.0: - resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==, + } + engines: { node: '>=18' } string.prototype.trim@1.2.10: - resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==, + } + engines: { node: '>= 0.4' } string.prototype.trimend@1.0.9: - resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==, + } + engines: { node: '>= 0.4' } string.prototype.trimstart@1.0.8: - resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==, + } + engines: { node: '>= 0.4' } string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + resolution: + { + integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==, + } string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - stringify-object@3.3.0: - resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==, + } strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, + } + engines: { node: '>=8' } + + strip-ansi@7.1.2: + resolution: + { + integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==, + } + engines: { node: '>=12' } strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==, + } + engines: { node: '>=4' } strip-bom@4.0.0: - resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==, + } + engines: { node: '>=8' } strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==, + } + engines: { node: '>=6' } strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==, + } + engines: { node: '>=12' } strip-indent@3.0.0: - resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} - engines: {node: '>=8'} - - strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==, + } + engines: { node: '>=8' } strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - - strong-log-transformer@2.1.0: - resolution: {integrity: sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA==} - engines: {node: '>=4'} - hasBin: true - - stylus-lookup@5.0.1: - resolution: {integrity: sha512-tLtJEd5AGvnVy4f9UHQMw4bkJJtaAcmo54N+ovQBjDY3DuWyK9Eltxzr5+KG0q4ew6v2EHyuWWNnHeiw/Eo7rQ==} - engines: {node: '>=14'} - hasBin: true - - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==, + } + engines: { node: '>=8' } + + strip-literal@3.1.0: + resolution: + { + integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==, + } supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, + } + engines: { node: '>=8' } supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - swagger-parser@10.0.3: - resolution: {integrity: sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==} - engines: {node: '>=10'} - - synckit@0.9.2: - resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==} - engines: {node: ^14.18.0 || >=16.0.0} - - table@6.9.0: - resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} - engines: {node: '>=10.0.0'} - - tanu@0.1.13: - resolution: {integrity: sha512-UbRmX7ccZ4wMVOY/Uw+7ji4VOkEYSYJG1+I4qzbnn4qh/jtvVbrm6BFnF12NQQ4+jGv21wKmjb1iFyUSVnBWcQ==} - - tapable@2.2.1: - resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==, + } + engines: { node: '>= 0.4' } + + synckit@0.11.12: + resolution: + { + integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==, + } + engines: { node: ^14.18.0 || >=16.0.0 } tar-stream@2.2.0: - resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} - engines: {node: '>=6'} - - tar@6.1.11: - resolution: {integrity: sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==} - engines: {node: '>= 10'} - - tar@6.2.0: - resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==, + } + engines: { node: '>=6' } + + tar@6.2.1: + resolution: + { + integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==, + } + engines: { node: '>=10' } temp-dir@1.0.0: - resolution: {integrity: sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==} - engines: {node: '>=4'} - - temporal-polyfill@0.2.5: - resolution: {integrity: sha512-ye47xp8Cb0nDguAhrrDS1JT1SzwEV9e26sSsrWzVu+yPZ7LzceEcH0i2gci9jWfOfSCCgM3Qv5nOYShVUUFUXA==} - - temporal-spec@0.2.4: - resolution: {integrity: sha512-lDMFv4nKQrSjlkHKAlHVqKrBG4DyFfa9F74cmBZ3Iy3ed8yvWnlWSIdi4IKfSqwmazAohBNwiN64qGx4y5Q3IQ==} - - test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==, + } + engines: { node: '>=4' } test-exclude@7.0.1: - resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} - engines: {node: '>=18'} + resolution: + { + integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==, + } + engines: { node: '>=18' } text-extensions@1.9.0: - resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} - engines: {node: '>=0.10'} + resolution: + { + integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==, + } + engines: { node: '>=0.10' } text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + resolution: + { + integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==, + } through2@2.0.5: - resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + resolution: + { + integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==, + } through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + resolution: + { + integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==, + } tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + resolution: + { + integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==, + } tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - - tinypool@1.0.2: - resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} - engines: {node: ^18.0.0 || >=20.0.0} + resolution: + { + integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==, + } + + tinyglobby@0.2.12: + resolution: + { + integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==, + } + engines: { node: '>=12.0.0' } + + tinyglobby@0.2.15: + resolution: + { + integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==, + } + engines: { node: '>=12.0.0' } + + tinypool@1.1.1: + resolution: + { + integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==, + } + engines: { node: ^18.0.0 || >=20.0.0 } tinyrainbow@2.0.0: - resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} - engines: {node: '>=14.0.0'} - - tinyspy@3.0.2: - resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} - engines: {node: '>=14.0.0'} - - tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} - - tmp@0.2.3: - resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} - engines: {node: '>=14.14'} - - tmpl@1.0.5: - resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + resolution: + { + integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==, + } + engines: { node: '>=14.0.0' } + + tinyspy@4.0.4: + resolution: + { + integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==, + } + engines: { node: '>=14.0.0' } + + tmp@0.2.5: + resolution: + { + integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==, + } + engines: { node: '>=14.14' } to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} + resolution: + { + integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==, + } + engines: { node: '>=8.0' } tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + resolution: + { + integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==, + } + + treeverse@3.0.0: + resolution: + { + integrity: sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } trim-newlines@3.0.1: - resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==, + } + engines: { node: '>=8' } ts-api-utils@1.4.3: - resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} - engines: {node: '>=16'} + resolution: + { + integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==, + } + engines: { node: '>=16' } peerDependencies: typescript: '>=4.2.0' - ts-graphviz@1.8.2: - resolution: {integrity: sha512-5YhbFoHmjxa7pgQLkB07MtGnGJ/yhvjmc9uhsnDBEICME6gkPf83SBwLDQqGDoCa3XzUMWLk1AU2Wn1u1naDtA==} - engines: {node: '>=14.16'} - - ts-jest@29.2.5: - resolution: {integrity: sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==} - engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@babel/core': '>=7.0.0-beta.0 <8' - '@jest/transform': ^29.0.0 - '@jest/types': ^29.0.0 - babel-jest: ^29.0.0 - esbuild: '*' - jest: ^29.0.0 - typescript: '>=4.3 <6' - peerDependenciesMeta: - '@babel/core': - optional: true - '@jest/transform': - optional: true - '@jest/types': - optional: true - babel-jest: - optional: true - esbuild: - optional: true - - ts-node@10.9.2: - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - - ts-pattern@5.6.2: - resolution: {integrity: sha512-d4IxJUXROL5NCa3amvMg6VQW2HVtZYmUTPfvVtO7zJWGYLJ+mry9v2OmYm+z67aniQoQ8/yFNadiEwtNS9qQiw==} - - ts-toolbelt@9.6.0: - resolution: {integrity: sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==} - tsconfig-paths@3.15.0: - resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + resolution: + { + integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==, + } tsconfig-paths@4.2.0: - resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} - engines: {node: '>=6'} - - tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + resolution: + { + integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==, + } + engines: { node: '>=6' } tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tsutils@3.21.0: - resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} - engines: {node: '>= 6'} - peerDependencies: - typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' - - tuf-js@1.1.7: - resolution: {integrity: sha512-i3P9Kgw3ytjELUfpuKVDNBJvk4u5bXL6gskv572mcevPbSKCV3zt3djhmlEQ65yERjIbOSncy7U4cQJaB1CBCg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + resolution: + { + integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, + } + + tuf-js@2.2.1: + resolution: + { + integrity: sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==, + } + engines: { node: ^16.14.0 || >=18.0.0 } type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - - type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==, + } + engines: { node: '>= 0.8.0' } type-fest@0.18.1: - resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==, + } + engines: { node: '>=10' } type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==, + } + engines: { node: '>=10' } type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==, + } + engines: { node: '>=10' } type-fest@0.4.1: - resolution: {integrity: sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw==, + } + engines: { node: '>=6' } type-fest@0.6.0: - resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==, + } + engines: { node: '>=8' } type-fest@0.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==, + } + engines: { node: '>=8' } type-fest@1.4.0: - resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} - engines: {node: '>=10'} - - type-fest@3.13.1: - resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} - engines: {node: '>=14.16'} + resolution: + { + integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==, + } + engines: { node: '>=10' } typed-array-buffer@1.0.3: - resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==, + } + engines: { node: '>= 0.4' } typed-array-byte-length@1.0.3: - resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==, + } + engines: { node: '>= 0.4' } typed-array-byte-offset@1.0.4: - resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==, + } + engines: { node: '>= 0.4' } typed-array-length@1.0.7: - resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==, + } + engines: { node: '>= 0.4' } typedarray@0.0.6: - resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - - typescript@4.9.5: - resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} - engines: {node: '>=4.2.0'} - hasBin: true + resolution: + { + integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==, + } typescript@5.7.3: - resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} - engines: {node: '>=14.17'} + resolution: + { + integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==, + } + 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'} + resolution: + { + integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==, + } + engines: { node: '>=0.8.0' } hasBin: true unbox-primitive@1.1.0: - resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} - engines: {node: '>= 0.4'} - - undici-types@6.20.0: - resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} - - unicorn-magic@0.1.0: - resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} - engines: {node: '>=18'} - - unique-filename@2.0.1: - resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + resolution: + { + integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==, + } + engines: { node: '>= 0.4' } + + undici-types@6.21.0: + resolution: + { + integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==, + } unique-filename@3.0.0: - resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - unique-slug@3.0.0: - resolution: {integrity: sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + resolution: + { + integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } unique-slug@4.0.0: - resolution: {integrity: sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + resolution: + { + integrity: sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } universal-user-agent@6.0.1: - resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==} - - universalify@0.1.2: - resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} - engines: {node: '>= 4.0.0'} + resolution: + { + integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==, + } universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} + resolution: + { + integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==, + } + engines: { node: '>= 10.0.0' } + + unrs-resolver@1.11.1: + resolution: + { + integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==, + } upath@2.0.1: - resolution: {integrity: sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==} - engines: {node: '>=4'} - - update-browserslist-db@1.1.2: - resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' + resolution: + { + integrity: sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==, + } + engines: { node: '>=4' } uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + resolution: + { + integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, + } util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + resolution: + { + integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, + } + + uuid@10.0.0: + resolution: + { + integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==, + } hasBin: true - v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - - v8-compile-cache@2.3.0: - resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} - - v8-to-istanbul@9.3.0: - resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} - engines: {node: '>=10.12.0'} - validate-npm-package-license@3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - - validate-npm-package-name@3.0.0: - resolution: {integrity: sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw==} - - validate-npm-package-name@5.0.0: - resolution: {integrity: sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - validator@13.12.0: - resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==} - engines: {node: '>= 0.10'} - - verror@1.10.1: - resolution: {integrity: sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==} - engines: {node: '>=0.6.0'} - - vite-node@3.0.3: - resolution: {integrity: sha512-0sQcwhwAEw/UJGojbhOrnq3HtiZ3tC7BzpAa0lx3QaTX0S3YX70iGcik25UBdB96pmdwjyY2uyKNYruxCDmiEg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + 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 } + + vite-node@3.2.4: + resolution: + { + integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==, + } + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } hasBin: true - vite@6.0.11: - resolution: {integrity: sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + vite@7.3.1: + resolution: + { + integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==, + } + engines: { node: ^20.19.0 || >=22.12.0 } hasBin: true peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@types/node': ^20.19.0 || >=22.12.0 jiti: '>=1.21.0' - less: '*' + less: ^4.0.0 lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' + 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 @@ -5273,20 +6656,26 @@ packages: yaml: optional: true - vitest@3.0.3: - resolution: {integrity: sha512-dWdwTFUW9rcnL0LyF2F+IfvNQWB0w9DERySCk8VMG75F8k25C7LsZoh6XfCjPvcR8Nb+Lqi9JKr6vnzH7HSrpQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + vitest@3.2.4: + resolution: + { + integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==, + } + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } hasBin: true peerDependencies: '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.0.3 - '@vitest/ui': 3.0.3 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 happy-dom: '*' jsdom: '*' peerDependenciesMeta: '@edge-runtime/vm': optional: true + '@types/debug': + optional: true '@types/node': optional: true '@vitest/browser': @@ -5298,554 +6687,372 @@ packages: jsdom: optional: true - vscode-jsonrpc@8.2.0: - resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} - engines: {node: '>=14.0.0'} - - vscode-languageserver-protocol@3.17.5: - resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} - - vscode-languageserver-textdocument@1.0.12: - resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} - - vscode-languageserver-types@3.17.5: - resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} - - vscode-languageserver@9.0.1: - resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} - hasBin: true - - walkdir@0.4.1: - resolution: {integrity: sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==} - engines: {node: '>=6.0.0'} - - walker@1.0.8: - resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + walk-up-path@3.0.1: + resolution: + { + integrity: sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==, + } wcwidth@1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + resolution: + { + integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==, + } webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + resolution: + { + integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==, + } whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - - whence@2.0.1: - resolution: {integrity: sha512-VtcCE1Pe3BKofF/k+P5xcpuoqQ0f1NJY6TmdUw5kInl9/pEr1ZEFD9+ZOUicf52tvpTbhMS93aWXriu2IQYTTw==} - engines: {node: '>=14'} + resolution: + { + integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==, + } which-boxed-primitive@1.1.1: - resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==, + } + engines: { node: '>= 0.4' } which-builtin-type@1.2.1: - resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} - engines: {node: '>= 0.4'} + resolution: + { + integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==, + } + engines: { node: '>= 0.4' } which-collection@1.0.2: - resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} - engines: {node: '>= 0.4'} - - which-typed-array@1.1.18: - resolution: {integrity: sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==} - engines: {node: '>= 0.4'} - - which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true + resolution: + { + integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==, + } + engines: { node: '>= 0.4' } + + which-typed-array@1.1.20: + resolution: + { + integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==, + } + engines: { node: '>= 0.4' } which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} + resolution: + { + integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, + } + engines: { node: '>= 8' } hasBin: true - which@3.0.1: - resolution: {integrity: sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + which@4.0.0: + resolution: + { + integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==, + } + engines: { node: ^16.13.0 || >=18.0.0 } hasBin: true why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==, + } + engines: { node: '>=8' } hasBin: true wide-align@1.1.5: - resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + resolution: + { + integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==, + } word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==, + } + engines: { node: '>=0.10.0' } wordwrap@1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + resolution: + { + integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==, + } wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==, + } + engines: { node: '>=8' } wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==, + } + engines: { node: '>=10' } wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==, + } + engines: { node: '>=12' } + + wrap-ansi@9.0.2: + resolution: + { + integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==, + } + engines: { node: '>=18' } wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + resolution: + { + integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==, + } write-file-atomic@2.4.3: - resolution: {integrity: sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==} - - write-file-atomic@4.0.2: - resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + resolution: + { + integrity: sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==, + } write-file-atomic@5.0.1: - resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + resolution: + { + integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } write-json-file@3.2.0: - resolution: {integrity: sha512-3xZqT7Byc2uORAatYiP3DHUUAVEkNOswEWNs9H5KXiicRTvzYzYqKjYc4G7p+8pltvAw641lVByKVtMpf+4sYQ==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-3xZqT7Byc2uORAatYiP3DHUUAVEkNOswEWNs9H5KXiicRTvzYzYqKjYc4G7p+8pltvAw641lVByKVtMpf+4sYQ==, + } + engines: { node: '>=6' } write-pkg@4.0.0: - resolution: {integrity: sha512-v2UQ+50TNf2rNHJ8NyWttfm/EJUBWMJcx6ZTYZr6Qp52uuegWw/lBkCtCbnYZEmPRNL61m+u67dAmGxo+HTULA==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-v2UQ+50TNf2rNHJ8NyWttfm/EJUBWMJcx6ZTYZr6Qp52uuegWw/lBkCtCbnYZEmPRNL61m+u67dAmGxo+HTULA==, + } + engines: { node: '>=8' } xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} + 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'} - - yallist@2.1.2: - resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} - - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + resolution: + { + integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==, + } + engines: { node: '>=10' } yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + resolution: + { + integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==, + } - yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} + yaml-ast-parser@0.0.43: + resolution: + { + integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==, + } yaml@2.3.1: - resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==} - engines: {node: '>= 14'} - - yaml@2.5.1: - resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==} - engines: {node: '>= 14'} - hasBin: true - - yaml@2.7.0: - resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==} - engines: {node: '>= 14'} + resolution: + { + integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==, + } + engines: { node: '>= 14' } + + yaml@2.8.2: + resolution: + { + integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==, + } + engines: { node: '>= 14.6' } hasBin: true - yargs-parser@20.2.4: - resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} - engines: {node: '>=10'} - yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==, + } + engines: { node: '>=10' } yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} + resolution: + { + integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==, + } + engines: { node: '>=12' } yargs@16.2.0: - resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==, + } + engines: { node: '>=10' } yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - - yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==, + } + engines: { node: '>=12' } yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - - yoctocolors-cjs@2.1.2: - resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} - engines: {node: '>=18'} - - z-schema@5.0.5: - resolution: {integrity: sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==} - engines: {node: '>=8.0.0'} - hasBin: true - - zod@3.24.1: - resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} + resolution: + { + integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==, + } + engines: { node: '>=10' } snapshots: - '@ampproject/remapping@2.3.0': dependencies: - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 - - '@apidevtools/json-schema-ref-parser@11.7.2': - dependencies: - '@jsdevtools/ono': 7.1.3 - '@types/json-schema': 7.0.15 - js-yaml: 4.1.0 - - '@apidevtools/json-schema-ref-parser@9.0.9': - dependencies: - '@jsdevtools/ono': 7.1.3 - '@types/json-schema': 7.0.15 - call-me-maybe: 1.0.2 - js-yaml: 4.1.0 - - '@apidevtools/json-schema-ref-parser@9.1.2': - dependencies: - '@jsdevtools/ono': 7.1.3 - '@types/json-schema': 7.0.15 - call-me-maybe: 1.0.2 - js-yaml: 4.1.0 - - '@apidevtools/openapi-schemas@2.1.0': {} - - '@apidevtools/swagger-methods@3.0.2': {} - - '@apidevtools/swagger-parser@10.0.3(openapi-types@12.1.3)': - dependencies: - '@apidevtools/json-schema-ref-parser': 9.1.2 - '@apidevtools/openapi-schemas': 2.1.0 - '@apidevtools/swagger-methods': 3.0.2 - '@jsdevtools/ono': 7.1.3 - call-me-maybe: 1.0.2 - openapi-types: 12.1.3 - z-schema: 5.0.5 - - '@apidevtools/swagger-parser@10.1.1(openapi-types@12.1.3)': - dependencies: - '@apidevtools/json-schema-ref-parser': 11.7.2 - '@apidevtools/openapi-schemas': 2.1.0 - '@apidevtools/swagger-methods': 3.0.2 - '@jsdevtools/ono': 7.1.3 - ajv: 8.17.1 - ajv-draft-04: 1.0.0(ajv@8.17.1) - call-me-maybe: 1.0.2 - openapi-types: 12.1.3 - - '@babel/code-frame@7.25.9': - dependencies: - '@babel/highlight': 7.25.9 - picocolors: 1.1.1 - - '@babel/code-frame@7.26.2': - dependencies: - '@babel/helper-validator-identifier': 7.25.9 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.26.5': {} - - '@babel/core@7.26.0': - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.5 - '@babel/helper-compilation-targets': 7.26.5 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) - '@babel/helpers': 7.26.0 - '@babel/parser': 7.26.5 - '@babel/template': 7.25.9 - '@babel/traverse': 7.26.5 - '@babel/types': 7.26.5 - convert-source-map: 2.0.0 - debug: 4.4.0 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.26.5': - dependencies: - '@babel/parser': 7.26.5 - '@babel/types': 7.26.5 - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 3.1.0 - - '@babel/helper-compilation-targets@7.26.5': - dependencies: - '@babel/compat-data': 7.26.5 - '@babel/helper-validator-option': 7.25.9 - browserslist: 4.24.4 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-module-imports@7.25.9': - dependencies: - '@babel/traverse': 7.26.5 - '@babel/types': 7.26.5 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-imports': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.26.5 - transitivePeerDependencies: - - supports-color - - '@babel/helper-plugin-utils@7.26.5': {} - - '@babel/helper-string-parser@7.25.9': {} - - '@babel/helper-validator-identifier@7.25.9': {} - - '@babel/helper-validator-option@7.25.9': {} - - '@babel/helpers@7.26.0': - dependencies: - '@babel/template': 7.25.9 - '@babel/types': 7.26.5 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 - '@babel/highlight@7.25.9': + '@babel/code-frame@7.29.0': dependencies: - '@babel/helper-validator-identifier': 7.25.9 - chalk: 2.4.2 + '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/parser@7.26.5': - dependencies: - '@babel/types': 7.26.5 - - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.26.5 - - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.26.5 - - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.26.5 - - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.26.5 - - '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.26.5 - - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.26.5 - - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.26.5 - - '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.26.5 - - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.26.5 - - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.26.5 - - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.26.5 - - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.26.5 - - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.26.5 - - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.26.5 - - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-string-parser@7.27.1': {} - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-validator-identifier@7.28.5': {} - '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0)': + '@babel/parser@7.29.0': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/types': 7.29.0 - '@babel/runtime@7.26.0': + '@babel/types@7.29.0': dependencies: - regenerator-runtime: 0.14.1 + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 - '@babel/template@7.25.9': - dependencies: - '@babel/code-frame': 7.26.2 - '@babel/parser': 7.26.5 - '@babel/types': 7.26.5 + '@bcoe/v8-coverage@1.0.2': {} - '@babel/traverse@7.26.5': + '@emnapi/core@1.8.1': dependencies: - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.5 - '@babel/parser': 7.26.5 - '@babel/template': 7.25.9 - '@babel/types': 7.26.5 - debug: 4.4.0 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 - '@babel/types@7.26.5': + '@emnapi/runtime@1.8.1': dependencies: - '@babel/helper-string-parser': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 - - '@bcoe/v8-coverage@0.2.3': {} - - '@bcoe/v8-coverage@1.0.2': {} + tslib: 2.8.1 - '@cspotcode/source-map-support@0.8.1': + '@emnapi/wasi-threads@1.1.0': dependencies: - '@jridgewell/trace-mapping': 0.3.9 + tslib: 2.8.1 - '@dependents/detective-less@4.1.0': - dependencies: - gonzales-pe: 4.3.0 - node-source-walk: 6.0.2 + '@esbuild/aix-ppc64@0.27.3': + optional: true - '@esbuild/aix-ppc64@0.24.2': + '@esbuild/android-arm64@0.27.3': optional: true - '@esbuild/android-arm64@0.24.2': + '@esbuild/android-arm@0.27.3': optional: true - '@esbuild/android-arm@0.24.2': + '@esbuild/android-x64@0.27.3': optional: true - '@esbuild/android-x64@0.24.2': + '@esbuild/darwin-arm64@0.27.3': optional: true - '@esbuild/darwin-arm64@0.24.2': + '@esbuild/darwin-x64@0.27.3': optional: true - '@esbuild/darwin-x64@0.24.2': + '@esbuild/freebsd-arm64@0.27.3': optional: true - '@esbuild/freebsd-arm64@0.24.2': + '@esbuild/freebsd-x64@0.27.3': optional: true - '@esbuild/freebsd-x64@0.24.2': + '@esbuild/linux-arm64@0.27.3': optional: true - '@esbuild/linux-arm64@0.24.2': + '@esbuild/linux-arm@0.27.3': optional: true - '@esbuild/linux-arm@0.24.2': + '@esbuild/linux-ia32@0.27.3': optional: true - '@esbuild/linux-ia32@0.24.2': + '@esbuild/linux-loong64@0.27.3': optional: true - '@esbuild/linux-loong64@0.24.2': + '@esbuild/linux-mips64el@0.27.3': optional: true - '@esbuild/linux-mips64el@0.24.2': + '@esbuild/linux-ppc64@0.27.3': optional: true - '@esbuild/linux-ppc64@0.24.2': + '@esbuild/linux-riscv64@0.27.3': optional: true - '@esbuild/linux-riscv64@0.24.2': + '@esbuild/linux-s390x@0.27.3': optional: true - '@esbuild/linux-s390x@0.24.2': + '@esbuild/linux-x64@0.27.3': optional: true - '@esbuild/linux-x64@0.24.2': + '@esbuild/netbsd-arm64@0.27.3': optional: true - '@esbuild/netbsd-arm64@0.24.2': + '@esbuild/netbsd-x64@0.27.3': optional: true - '@esbuild/netbsd-x64@0.24.2': + '@esbuild/openbsd-arm64@0.27.3': optional: true - '@esbuild/openbsd-arm64@0.24.2': + '@esbuild/openbsd-x64@0.27.3': optional: true - '@esbuild/openbsd-x64@0.24.2': + '@esbuild/openharmony-arm64@0.27.3': optional: true - '@esbuild/sunos-x64@0.24.2': + '@esbuild/sunos-x64@0.27.3': optional: true - '@esbuild/win32-arm64@0.24.2': + '@esbuild/win32-arm64@0.27.3': optional: true - '@esbuild/win32-ia32@0.24.2': + '@esbuild/win32-ia32@0.27.3': optional: true - '@esbuild/win32-x64@0.24.2': + '@esbuild/win32-x64@0.27.3': optional: true - '@eslint-community/eslint-utils@4.4.1(eslint@8.57.1)': + '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': dependencies: eslint: 8.57.1 eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.12.1': {} + '@eslint-community/regexpp@4.12.2': {} '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.4.0 + debug: 4.4.3 espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 - import-fresh: 3.3.0 - js-yaml: 4.1.0 + import-fresh: 3.3.1 + js-yaml: 4.1.1 minimatch: 3.1.2 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -5853,452 +7060,271 @@ snapshots: '@eslint/js@8.57.1': {} - '@gar/promisify@1.1.3': {} - - '@humanwhocodes/config-array@0.13.0': - dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.0 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/momoa@2.0.4': {} - - '@humanwhocodes/object-schema@2.0.3': {} - - '@hutson/parse-repository-url@3.0.2': {} - - '@inquirer/checkbox@4.0.6(@types/node@22.10.7)': - dependencies: - '@inquirer/core': 10.1.4(@types/node@22.10.7) - '@inquirer/figures': 1.0.9 - '@inquirer/type': 3.0.2(@types/node@22.10.7) - '@types/node': 22.10.7 - ansi-escapes: 4.3.2 - yoctocolors-cjs: 2.1.2 - - '@inquirer/confirm@5.1.3(@types/node@22.10.7)': - dependencies: - '@inquirer/core': 10.1.4(@types/node@22.10.7) - '@inquirer/type': 3.0.2(@types/node@22.10.7) - '@types/node': 22.10.7 - - '@inquirer/core@10.1.4(@types/node@22.10.7)': - dependencies: - '@inquirer/figures': 1.0.9 - '@inquirer/type': 3.0.2(@types/node@22.10.7) - ansi-escapes: 4.3.2 - cli-width: 4.1.0 - mute-stream: 2.0.0 - signal-exit: 4.1.0 - strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.2 - transitivePeerDependencies: - - '@types/node' - - '@inquirer/editor@4.2.3(@types/node@22.10.7)': + '@humanwhocodes/config-array@0.13.0': dependencies: - '@inquirer/core': 10.1.4(@types/node@22.10.7) - '@inquirer/type': 3.0.2(@types/node@22.10.7) - '@types/node': 22.10.7 - external-editor: 3.1.0 + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color - '@inquirer/expand@4.0.6(@types/node@22.10.7)': - dependencies: - '@inquirer/core': 10.1.4(@types/node@22.10.7) - '@inquirer/type': 3.0.2(@types/node@22.10.7) - '@types/node': 22.10.7 - yoctocolors-cjs: 2.1.2 + '@humanwhocodes/module-importer@1.0.1': {} - '@inquirer/figures@1.0.9': {} + '@humanwhocodes/object-schema@2.0.3': {} - '@inquirer/input@4.1.3(@types/node@22.10.7)': - dependencies: - '@inquirer/core': 10.1.4(@types/node@22.10.7) - '@inquirer/type': 3.0.2(@types/node@22.10.7) - '@types/node': 22.10.7 + '@hutson/parse-repository-url@3.0.2': {} - '@inquirer/number@3.0.6(@types/node@22.10.7)': - dependencies: - '@inquirer/core': 10.1.4(@types/node@22.10.7) - '@inquirer/type': 3.0.2(@types/node@22.10.7) - '@types/node': 22.10.7 + '@inquirer/ansi@2.0.2': {} - '@inquirer/password@4.0.6(@types/node@22.10.7)': + '@inquirer/checkbox@5.0.3(@types/node@22.19.11)': dependencies: - '@inquirer/core': 10.1.4(@types/node@22.10.7) - '@inquirer/type': 3.0.2(@types/node@22.10.7) - '@types/node': 22.10.7 - ansi-escapes: 4.3.2 - - '@inquirer/prompts@7.2.3(@types/node@22.10.7)': - dependencies: - '@inquirer/checkbox': 4.0.6(@types/node@22.10.7) - '@inquirer/confirm': 5.1.3(@types/node@22.10.7) - '@inquirer/editor': 4.2.3(@types/node@22.10.7) - '@inquirer/expand': 4.0.6(@types/node@22.10.7) - '@inquirer/input': 4.1.3(@types/node@22.10.7) - '@inquirer/number': 3.0.6(@types/node@22.10.7) - '@inquirer/password': 4.0.6(@types/node@22.10.7) - '@inquirer/rawlist': 4.0.6(@types/node@22.10.7) - '@inquirer/search': 3.0.6(@types/node@22.10.7) - '@inquirer/select': 4.0.6(@types/node@22.10.7) - '@types/node': 22.10.7 - - '@inquirer/rawlist@4.0.6(@types/node@22.10.7)': - dependencies: - '@inquirer/core': 10.1.4(@types/node@22.10.7) - '@inquirer/type': 3.0.2(@types/node@22.10.7) - '@types/node': 22.10.7 - yoctocolors-cjs: 2.1.2 - - '@inquirer/search@3.0.6(@types/node@22.10.7)': - dependencies: - '@inquirer/core': 10.1.4(@types/node@22.10.7) - '@inquirer/figures': 1.0.9 - '@inquirer/type': 3.0.2(@types/node@22.10.7) - '@types/node': 22.10.7 - yoctocolors-cjs: 2.1.2 - - '@inquirer/select@4.0.6(@types/node@22.10.7)': - dependencies: - '@inquirer/core': 10.1.4(@types/node@22.10.7) - '@inquirer/figures': 1.0.9 - '@inquirer/type': 3.0.2(@types/node@22.10.7) - '@types/node': 22.10.7 - ansi-escapes: 4.3.2 - yoctocolors-cjs: 2.1.2 + '@inquirer/ansi': 2.0.2 + '@inquirer/core': 11.1.0(@types/node@22.19.11) + '@inquirer/figures': 2.0.2 + '@inquirer/type': 4.0.2(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 - '@inquirer/type@3.0.2(@types/node@22.10.7)': + '@inquirer/confirm@6.0.3(@types/node@22.19.11)': dependencies: - '@types/node': 22.10.7 + '@inquirer/core': 11.1.0(@types/node@22.19.11) + '@inquirer/type': 4.0.2(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 - '@isaacs/cliui@8.0.2': + '@inquirer/core@11.1.0(@types/node@22.19.11)': dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 + '@inquirer/ansi': 2.0.2 + '@inquirer/figures': 2.0.2 + '@inquirer/type': 4.0.2(@types/node@22.19.11) + cli-width: 4.1.0 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + wrap-ansi: 9.0.2 + optionalDependencies: + '@types/node': 22.19.11 - '@istanbuljs/load-nyc-config@1.1.0': + '@inquirer/editor@5.0.3(@types/node@22.19.11)': dependencies: - camelcase: 5.3.1 - find-up: 4.1.0 - get-package-type: 0.1.0 - js-yaml: 3.14.1 - resolve-from: 5.0.0 - - '@istanbuljs/schema@0.1.3': {} + '@inquirer/core': 11.1.0(@types/node@22.19.11) + '@inquirer/external-editor': 2.0.2(@types/node@22.19.11) + '@inquirer/type': 4.0.2(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 - '@jest/console@29.7.0': + '@inquirer/expand@5.0.3(@types/node@22.19.11)': dependencies: - '@jest/types': 29.6.3 - '@types/node': 22.10.7 - chalk: 4.1.2 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - slash: 3.0.0 + '@inquirer/core': 11.1.0(@types/node@22.19.11) + '@inquirer/type': 4.0.2(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 - '@jest/core@29.7.0(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3))': + '@inquirer/external-editor@1.0.3(@types/node@22.19.11)': dependencies: - '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.10.7 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.9.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)) - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - jest-watcher: 29.7.0 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - ts-node + chardet: 2.1.1 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 22.19.11 - '@jest/environment@29.7.0': + '@inquirer/external-editor@2.0.2(@types/node@22.19.11)': dependencies: - '@jest/fake-timers': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.10.7 - jest-mock: 29.7.0 + chardet: 2.1.1 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 22.19.11 - '@jest/expect-utils@29.7.0': - dependencies: - jest-get-type: 29.6.3 + '@inquirer/figures@2.0.2': {} - '@jest/expect@29.7.0': + '@inquirer/input@5.0.3(@types/node@22.19.11)': dependencies: - expect: 29.7.0 - jest-snapshot: 29.7.0 - transitivePeerDependencies: - - supports-color + '@inquirer/core': 11.1.0(@types/node@22.19.11) + '@inquirer/type': 4.0.2(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 - '@jest/fake-timers@29.7.0': + '@inquirer/number@4.0.3(@types/node@22.19.11)': dependencies: - '@jest/types': 29.6.3 - '@sinonjs/fake-timers': 10.3.0 - '@types/node': 22.10.7 - jest-message-util: 29.7.0 - jest-mock: 29.7.0 - jest-util: 29.7.0 + '@inquirer/core': 11.1.0(@types/node@22.19.11) + '@inquirer/type': 4.0.2(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 - '@jest/globals@29.7.0': + '@inquirer/password@5.0.3(@types/node@22.19.11)': dependencies: - '@jest/environment': 29.7.0 - '@jest/expect': 29.7.0 - '@jest/types': 29.6.3 - jest-mock: 29.7.0 - transitivePeerDependencies: - - supports-color + '@inquirer/ansi': 2.0.2 + '@inquirer/core': 11.1.0(@types/node@22.19.11) + '@inquirer/type': 4.0.2(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/prompts@8.1.0(@types/node@22.19.11)': + dependencies: + '@inquirer/checkbox': 5.0.3(@types/node@22.19.11) + '@inquirer/confirm': 6.0.3(@types/node@22.19.11) + '@inquirer/editor': 5.0.3(@types/node@22.19.11) + '@inquirer/expand': 5.0.3(@types/node@22.19.11) + '@inquirer/input': 5.0.3(@types/node@22.19.11) + '@inquirer/number': 4.0.3(@types/node@22.19.11) + '@inquirer/password': 5.0.3(@types/node@22.19.11) + '@inquirer/rawlist': 5.1.0(@types/node@22.19.11) + '@inquirer/search': 4.0.3(@types/node@22.19.11) + '@inquirer/select': 5.0.3(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 - '@jest/reporters@29.7.0': + '@inquirer/rawlist@5.1.0(@types/node@22.19.11)': dependencies: - '@bcoe/v8-coverage': 0.2.3 - '@jest/console': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 22.10.7 - chalk: 4.1.2 - collect-v8-coverage: 1.0.2 - exit: 0.1.2 - glob: 7.2.3 - graceful-fs: 4.2.11 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-instrument: 6.0.3 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.1.7 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - jest-worker: 29.7.0 - slash: 3.0.0 - string-length: 4.0.2 - strip-ansi: 6.0.1 - v8-to-istanbul: 9.3.0 - transitivePeerDependencies: - - supports-color + '@inquirer/core': 11.1.0(@types/node@22.19.11) + '@inquirer/type': 4.0.2(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 - '@jest/schemas@29.6.3': + '@inquirer/search@4.0.3(@types/node@22.19.11)': dependencies: - '@sinclair/typebox': 0.27.8 + '@inquirer/core': 11.1.0(@types/node@22.19.11) + '@inquirer/figures': 2.0.2 + '@inquirer/type': 4.0.2(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 - '@jest/source-map@29.6.3': + '@inquirer/select@5.0.3(@types/node@22.19.11)': dependencies: - '@jridgewell/trace-mapping': 0.3.25 - callsites: 3.1.0 - graceful-fs: 4.2.11 + '@inquirer/ansi': 2.0.2 + '@inquirer/core': 11.1.0(@types/node@22.19.11) + '@inquirer/figures': 2.0.2 + '@inquirer/type': 4.0.2(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 - '@jest/test-result@29.7.0': - dependencies: - '@jest/console': 29.7.0 - '@jest/types': 29.6.3 - '@types/istanbul-lib-coverage': 2.0.6 - collect-v8-coverage: 1.0.2 + '@inquirer/type@4.0.2(@types/node@22.19.11)': + optionalDependencies: + '@types/node': 22.19.11 - '@jest/test-sequencer@29.7.0': + '@isaacs/cliui@8.0.2': dependencies: - '@jest/test-result': 29.7.0 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - slash: 3.0.0 + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 - '@jest/transform@29.7.0': - dependencies: - '@babel/core': 7.26.0 - '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.25 - babel-plugin-istanbul: 6.1.1 - chalk: 4.1.2 - convert-source-map: 2.0.0 - fast-json-stable-stringify: 2.1.0 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - jest-regex-util: 29.6.3 - jest-util: 29.7.0 - micromatch: 4.0.8 - pirates: 4.0.6 - slash: 3.0.0 - write-file-atomic: 4.0.2 - transitivePeerDependencies: - - supports-color + '@isaacs/string-locale-compare@1.1.0': {} + + '@istanbuljs/schema@0.1.3': {} - '@jest/types@29.6.3': + '@jest/schemas@29.6.3': dependencies: - '@jest/schemas': 29.6.3 - '@types/istanbul-lib-coverage': 2.0.6 - '@types/istanbul-reports': 3.0.4 - '@types/node': 22.10.7 - '@types/yargs': 17.0.33 - chalk: 4.1.2 + '@sinclair/typebox': 0.27.8 - '@jridgewell/gen-mapping@0.3.8': + '@jridgewell/gen-mapping@0.3.13': dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/set-array@1.2.1': {} - - '@jridgewell/sourcemap-codec@1.5.0': {} - - '@jridgewell/trace-mapping@0.3.25': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.9': + '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 - - '@jsdevtools/ono@7.1.3': {} - - '@lerna/child-process@7.4.2': - dependencies: - chalk: 4.1.2 - execa: 5.0.0 - strong-log-transformer: 2.1.0 + '@jridgewell/sourcemap-codec': 1.5.5 - '@lerna/create@7.4.2(encoding@0.1.13)(typescript@5.7.3)': + '@lerna/create@8.2.4(@types/node@22.19.11)(encoding@0.1.13)(typescript@5.7.3)': dependencies: - '@lerna/child-process': 7.4.2 - '@npmcli/run-script': 6.0.2 - '@nx/devkit': 16.10.0(nx@16.10.0) + '@npmcli/arborist': 7.5.4 + '@npmcli/package-json': 5.2.0 + '@npmcli/run-script': 8.1.0 + '@nx/devkit': 20.8.2(nx@20.8.2) '@octokit/plugin-enterprise-rest': 6.0.1 - '@octokit/rest': 19.0.11(encoding@0.1.13) + '@octokit/rest': 20.1.2 + aproba: 2.0.0 byte-size: 8.1.1 chalk: 4.1.0 clone-deep: 4.0.1 - cmd-shim: 6.0.1 + cmd-shim: 6.0.3 + color-support: 1.1.3 columnify: 1.6.0 + console-control-strings: 1.1.0 conventional-changelog-core: 5.0.1 conventional-recommended-bump: 7.0.1 - cosmiconfig: 8.3.6(typescript@5.7.3) - dedent: 0.7.0 + cosmiconfig: 9.0.0(typescript@5.7.3) + dedent: 1.5.3 execa: 5.0.0 - fs-extra: 11.3.0 + fs-extra: 11.3.3 get-stream: 6.0.0 - git-url-parse: 13.1.0 - glob-parent: 5.1.2 - globby: 11.1.0 + git-url-parse: 14.0.0 + glob-parent: 6.0.2 graceful-fs: 4.2.11 has-unicode: 2.0.1 ini: 1.3.8 - init-package-json: 5.0.0 - inquirer: 8.2.6 + init-package-json: 6.0.3 + inquirer: 8.2.7(@types/node@22.19.11) is-ci: 3.0.1 is-stream: 2.0.0 js-yaml: 4.1.0 - libnpmpublish: 7.3.0 + libnpmpublish: 9.0.9 load-json-file: 6.2.0 - lodash: 4.17.21 make-dir: 4.0.0 minimatch: 3.0.5 multimatch: 5.0.0 node-fetch: 2.6.7(encoding@0.1.13) - npm-package-arg: 8.1.1 - npm-packlist: 5.1.1 - npm-registry-fetch: 14.0.5 - npmlog: 6.0.2 - nx: 16.10.0 + npm-package-arg: 11.0.2 + npm-packlist: 8.0.2 + npm-registry-fetch: 17.1.0 + nx: 20.8.2 p-map: 4.0.0 p-map-series: 2.1.0 p-queue: 6.6.2 p-reduce: 2.1.0 - pacote: 15.2.0 + pacote: 18.0.6 pify: 5.0.0 read-cmd-shim: 4.0.0 - read-package-json: 6.0.4 resolve-from: 5.0.0 rimraf: 4.4.1 - semver: 7.6.3 + semver: 7.7.4 + set-blocking: 2.0.0 signal-exit: 3.0.7 slash: 3.0.0 - ssri: 9.0.1 - strong-log-transformer: 2.1.0 - tar: 6.1.11 + ssri: 10.0.6 + string-width: 4.2.3 + tar: 6.2.1 temp-dir: 1.0.0 + through: 2.3.8 + tinyglobby: 0.2.12 upath: 2.0.1 - uuid: 9.0.1 + uuid: 10.0.0 validate-npm-package-license: 3.0.4 - validate-npm-package-name: 5.0.0 + validate-npm-package-name: 5.0.1 + wide-align: 1.1.5 write-file-atomic: 5.0.1 write-pkg: 4.0.0 - yargs: 16.2.0 - yargs-parser: 20.2.4 + yargs: 17.7.2 + yargs-parser: 21.1.1 transitivePeerDependencies: - '@swc-node/register' - '@swc/core' + - '@types/node' + - babel-plugin-macros - bluebird - debug - encoding - supports-color - typescript - '@liuli-util/fs-extra@0.1.0': - dependencies: - '@types/fs-extra': 9.0.13 - fs-extra: 10.1.0 - - '@manypkg/cli@0.21.4': - dependencies: - '@manypkg/get-packages': 2.2.2 - chalk: 2.4.2 - detect-indent: 6.1.0 - find-up: 4.1.0 - fs-extra: 8.1.0 - normalize-path: 3.0.0 - p-limit: 2.3.0 - package-json: 8.1.1 - parse-github-url: 1.0.3 - sembear: 0.5.2 - semver: 6.3.1 - spawndamnit: 2.0.0 - validate-npm-package-name: 3.0.0 - - '@manypkg/find-root@2.2.3': - dependencies: - '@manypkg/tools': 1.1.2 - - '@manypkg/get-packages@2.2.2': + '@napi-rs/wasm-runtime@0.2.12': dependencies: - '@manypkg/find-root': 2.2.3 - '@manypkg/tools': 1.1.2 + '@emnapi/core': 1.8.1 + '@emnapi/runtime': 1.8.1 + '@tybys/wasm-util': 0.10.1 + optional: true - '@manypkg/tools@1.1.2': + '@napi-rs/wasm-runtime@0.2.4': dependencies: - fast-glob: 3.3.3 - jju: 1.4.0 - js-yaml: 4.1.0 + '@emnapi/core': 1.8.1 + '@emnapi/runtime': 1.8.1 + '@tybys/wasm-util': 0.9.0 '@nodelib/fs.scandir@2.1.5': dependencies: @@ -6310,29 +7336,76 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.18.0 + fastq: 1.20.1 '@nolyfill/is-core-module@1.0.39': {} - '@npmcli/fs@2.1.2': + '@npmcli/agent@2.2.2': + dependencies: + agent-base: 7.1.4 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 10.4.3 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + '@npmcli/arborist@7.5.4': dependencies: - '@gar/promisify': 1.1.3 - semver: 7.6.3 + '@isaacs/string-locale-compare': 1.1.0 + '@npmcli/fs': 3.1.1 + '@npmcli/installed-package-contents': 2.1.0 + '@npmcli/map-workspaces': 3.0.6 + '@npmcli/metavuln-calculator': 7.1.1 + '@npmcli/name-from-folder': 2.0.0 + '@npmcli/node-gyp': 3.0.0 + '@npmcli/package-json': 5.2.0 + '@npmcli/query': 3.1.0 + '@npmcli/redact': 2.0.1 + '@npmcli/run-script': 8.1.0 + bin-links: 4.0.4 + cacache: 18.0.4 + common-ancestor-path: 1.0.1 + hosted-git-info: 7.0.2 + json-parse-even-better-errors: 3.0.2 + json-stringify-nice: 1.1.4 + lru-cache: 10.4.3 + minimatch: 9.0.5 + nopt: 7.2.1 + npm-install-checks: 6.3.0 + npm-package-arg: 11.0.2 + npm-pick-manifest: 9.1.0 + npm-registry-fetch: 17.1.0 + pacote: 18.0.6 + parse-conflict-json: 3.0.1 + proc-log: 4.2.0 + proggy: 2.0.0 + promise-all-reject-late: 1.0.1 + promise-call-limit: 3.0.2 + read-package-json-fast: 3.0.2 + semver: 7.7.4 + ssri: 10.0.6 + treeverse: 3.0.0 + walk-up-path: 3.0.1 + transitivePeerDependencies: + - bluebird + - supports-color '@npmcli/fs@3.1.1': dependencies: - semver: 7.6.3 + semver: 7.7.4 - '@npmcli/git@4.1.0': + '@npmcli/git@5.0.8': dependencies: - '@npmcli/promise-spawn': 6.0.2 - lru-cache: 7.18.3 - npm-pick-manifest: 8.0.2 - proc-log: 3.0.0 + '@npmcli/promise-spawn': 7.0.2 + ini: 4.1.3 + lru-cache: 10.4.3 + npm-pick-manifest: 9.1.0 + proc-log: 4.2.0 promise-inflight: 1.0.1 promise-retry: 2.0.1 - semver: 7.6.3 - which: 3.0.1 + semver: 7.7.4 + which: 4.0.0 transitivePeerDependencies: - bluebird @@ -6341,383 +7414,332 @@ snapshots: npm-bundled: 3.0.1 npm-normalize-package-bin: 3.0.1 - '@npmcli/move-file@2.0.1': - dependencies: - mkdirp: 1.0.4 - rimraf: 3.0.2 - - '@npmcli/node-gyp@3.0.0': {} - - '@npmcli/promise-spawn@6.0.2': + '@npmcli/map-workspaces@3.0.6': dependencies: - which: 3.0.1 + '@npmcli/name-from-folder': 2.0.0 + glob: 10.5.0 + minimatch: 9.0.5 + read-package-json-fast: 3.0.2 - '@npmcli/run-script@6.0.2': + '@npmcli/metavuln-calculator@7.1.1': dependencies: - '@npmcli/node-gyp': 3.0.0 - '@npmcli/promise-spawn': 6.0.2 - node-gyp: 9.4.1 - read-package-json-fast: 3.0.2 - which: 3.0.1 + cacache: 18.0.4 + json-parse-even-better-errors: 3.0.2 + pacote: 18.0.6 + proc-log: 4.2.0 + semver: 7.7.4 transitivePeerDependencies: - bluebird - supports-color - '@nrwl/devkit@16.10.0(nx@16.10.0)': + '@npmcli/name-from-folder@2.0.0': {} + + '@npmcli/node-gyp@3.0.0': {} + + '@npmcli/package-json@5.2.0': dependencies: - '@nx/devkit': 16.10.0(nx@16.10.0) + '@npmcli/git': 5.0.8 + glob: 10.5.0 + hosted-git-info: 7.0.2 + json-parse-even-better-errors: 3.0.2 + normalize-package-data: 6.0.2 + proc-log: 4.2.0 + semver: 7.7.4 transitivePeerDependencies: - - nx + - bluebird - '@nrwl/tao@16.10.0': + '@npmcli/promise-spawn@7.0.2': dependencies: - nx: 16.10.0 - tslib: 2.8.1 + which: 4.0.0 + + '@npmcli/query@3.1.0': + dependencies: + postcss-selector-parser: 6.1.2 + + '@npmcli/redact@2.0.1': {} + + '@npmcli/run-script@8.1.0': + dependencies: + '@npmcli/node-gyp': 3.0.0 + '@npmcli/package-json': 5.2.0 + '@npmcli/promise-spawn': 7.0.2 + node-gyp: 10.3.1 + proc-log: 4.2.0 + which: 4.0.0 transitivePeerDependencies: - - '@swc-node/register' - - '@swc/core' - - debug + - bluebird + - supports-color - '@nx/devkit@16.10.0(nx@16.10.0)': + '@nx/devkit@20.8.2(nx@20.8.2)': dependencies: - '@nrwl/devkit': 16.10.0(nx@16.10.0) ejs: 3.1.10 enquirer: 2.3.6 ignore: 5.3.2 - nx: 16.10.0 - semver: 7.5.3 - tmp: 0.2.3 + minimatch: 9.0.3 + nx: 20.8.2 + semver: 7.7.4 + tmp: 0.2.5 tslib: 2.8.1 + yargs-parser: 21.1.1 - '@nx/nx-darwin-arm64@16.10.0': + '@nx/nx-darwin-arm64@20.8.2': optional: true - '@nx/nx-darwin-x64@16.10.0': + '@nx/nx-darwin-x64@20.8.2': optional: true - '@nx/nx-freebsd-x64@16.10.0': + '@nx/nx-freebsd-x64@20.8.2': optional: true - '@nx/nx-linux-arm-gnueabihf@16.10.0': + '@nx/nx-linux-arm-gnueabihf@20.8.2': optional: true - '@nx/nx-linux-arm64-gnu@16.10.0': + '@nx/nx-linux-arm64-gnu@20.8.2': optional: true - '@nx/nx-linux-arm64-musl@16.10.0': + '@nx/nx-linux-arm64-musl@20.8.2': optional: true - '@nx/nx-linux-x64-gnu@16.10.0': + '@nx/nx-linux-x64-gnu@20.8.2': optional: true - '@nx/nx-linux-x64-musl@16.10.0': + '@nx/nx-linux-x64-musl@20.8.2': optional: true - '@nx/nx-win32-arm64-msvc@16.10.0': + '@nx/nx-win32-arm64-msvc@20.8.2': optional: true - '@nx/nx-win32-x64-msvc@16.10.0': + '@nx/nx-win32-x64-msvc@20.8.2': optional: true - '@octokit/auth-token@3.0.4': {} + '@octokit/auth-token@4.0.0': {} - '@octokit/core@4.2.4(encoding@0.1.13)': + '@octokit/core@5.2.2': dependencies: - '@octokit/auth-token': 3.0.4 - '@octokit/graphql': 5.0.6(encoding@0.1.13) - '@octokit/request': 6.2.8(encoding@0.1.13) - '@octokit/request-error': 3.0.3 - '@octokit/types': 9.3.2 + '@octokit/auth-token': 4.0.0 + '@octokit/graphql': 7.1.1 + '@octokit/request': 8.4.1 + '@octokit/request-error': 5.1.1 + '@octokit/types': 13.10.0 before-after-hook: 2.2.3 universal-user-agent: 6.0.1 - transitivePeerDependencies: - - encoding - '@octokit/endpoint@7.0.6': + '@octokit/endpoint@9.0.6': dependencies: - '@octokit/types': 9.3.2 - is-plain-object: 5.0.0 + '@octokit/types': 13.10.0 universal-user-agent: 6.0.1 - '@octokit/graphql@5.0.6(encoding@0.1.13)': + '@octokit/graphql@7.1.1': dependencies: - '@octokit/request': 6.2.8(encoding@0.1.13) - '@octokit/types': 9.3.2 + '@octokit/request': 8.4.1 + '@octokit/types': 13.10.0 universal-user-agent: 6.0.1 - transitivePeerDependencies: - - encoding - '@octokit/openapi-types@18.1.1': {} + '@octokit/openapi-types@24.2.0': {} '@octokit/plugin-enterprise-rest@6.0.1': {} - '@octokit/plugin-paginate-rest@6.1.2(@octokit/core@4.2.4(encoding@0.1.13))': + '@octokit/plugin-paginate-rest@11.4.4-cjs.2(@octokit/core@5.2.2)': dependencies: - '@octokit/core': 4.2.4(encoding@0.1.13) - '@octokit/tsconfig': 1.0.2 - '@octokit/types': 9.3.2 + '@octokit/core': 5.2.2 + '@octokit/types': 13.10.0 - '@octokit/plugin-request-log@1.0.4(@octokit/core@4.2.4(encoding@0.1.13))': + '@octokit/plugin-request-log@4.0.1(@octokit/core@5.2.2)': dependencies: - '@octokit/core': 4.2.4(encoding@0.1.13) + '@octokit/core': 5.2.2 - '@octokit/plugin-rest-endpoint-methods@7.2.3(@octokit/core@4.2.4(encoding@0.1.13))': + '@octokit/plugin-rest-endpoint-methods@13.3.2-cjs.1(@octokit/core@5.2.2)': dependencies: - '@octokit/core': 4.2.4(encoding@0.1.13) - '@octokit/types': 10.0.0 + '@octokit/core': 5.2.2 + '@octokit/types': 13.10.0 - '@octokit/request-error@3.0.3': + '@octokit/request-error@5.1.1': dependencies: - '@octokit/types': 9.3.2 + '@octokit/types': 13.10.0 deprecation: 2.3.1 once: 1.4.0 - '@octokit/request@6.2.8(encoding@0.1.13)': + '@octokit/request@8.4.1': dependencies: - '@octokit/endpoint': 7.0.6 - '@octokit/request-error': 3.0.3 - '@octokit/types': 9.3.2 - is-plain-object: 5.0.0 - node-fetch: 2.6.7(encoding@0.1.13) + '@octokit/endpoint': 9.0.6 + '@octokit/request-error': 5.1.1 + '@octokit/types': 13.10.0 universal-user-agent: 6.0.1 - transitivePeerDependencies: - - encoding - - '@octokit/rest@19.0.11(encoding@0.1.13)': - dependencies: - '@octokit/core': 4.2.4(encoding@0.1.13) - '@octokit/plugin-paginate-rest': 6.1.2(@octokit/core@4.2.4(encoding@0.1.13)) - '@octokit/plugin-request-log': 1.0.4(@octokit/core@4.2.4(encoding@0.1.13)) - '@octokit/plugin-rest-endpoint-methods': 7.2.3(@octokit/core@4.2.4(encoding@0.1.13)) - transitivePeerDependencies: - - encoding - - '@octokit/tsconfig@1.0.2': {} - - '@octokit/types@10.0.0': - dependencies: - '@octokit/openapi-types': 18.1.1 - '@octokit/types@9.3.2': + '@octokit/rest@20.1.2': dependencies: - '@octokit/openapi-types': 18.1.1 + '@octokit/core': 5.2.2 + '@octokit/plugin-paginate-rest': 11.4.4-cjs.2(@octokit/core@5.2.2) + '@octokit/plugin-request-log': 4.0.1(@octokit/core@5.2.2) + '@octokit/plugin-rest-endpoint-methods': 13.3.2-cjs.1(@octokit/core@5.2.2) - '@parcel/watcher@2.0.4': + '@octokit/types@13.10.0': dependencies: - node-addon-api: 3.2.1 - node-gyp-build: 4.8.4 + '@octokit/openapi-types': 24.2.0 '@pkgjs/parseargs@0.11.0': optional: true - '@pkgr/core@0.1.1': {} - - '@pnpm/config.env-replace@1.1.0': {} + '@pkgr/core@0.2.9': {} - '@pnpm/network.ca-file@1.0.2': + '@redocly/ajv@8.17.4': dependencies: - graceful-fs: 4.2.10 + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 - '@pnpm/npm-conf@2.3.1': - dependencies: - '@pnpm/config.env-replace': 1.1.0 - '@pnpm/network.ca-file': 1.0.2 - config-chain: 1.1.13 + '@redocly/config@0.22.2': {} - '@readme/better-ajv-errors@1.6.0(ajv@8.17.1)': + '@redocly/openapi-core@1.34.6': dependencies: - '@babel/code-frame': 7.26.2 - '@babel/runtime': 7.26.0 - '@humanwhocodes/momoa': 2.0.4 - ajv: 8.17.1 - chalk: 4.1.2 - json-to-ast: 2.1.0 - jsonpointer: 5.0.1 - leven: 3.1.0 + '@redocly/ajv': 8.17.4 + '@redocly/config': 0.22.2 + colorette: 1.4.0 + https-proxy-agent: 7.0.6 + js-levenshtein: 1.1.6 + js-yaml: 4.1.1 + minimatch: 5.1.6 + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - supports-color - '@readme/json-schema-ref-parser@1.2.0': - dependencies: - '@jsdevtools/ono': 7.1.3 - '@types/json-schema': 7.0.15 - call-me-maybe: 1.0.2 - js-yaml: 4.1.0 + '@rollup/rollup-android-arm-eabi@4.57.1': + optional: true - '@readme/openapi-parser@2.6.0(openapi-types@12.1.3)': - dependencies: - '@apidevtools/swagger-methods': 3.0.2 - '@jsdevtools/ono': 7.1.3 - '@readme/better-ajv-errors': 1.6.0(ajv@8.17.1) - '@readme/json-schema-ref-parser': 1.2.0 - '@readme/openapi-schemas': 3.1.0 - ajv: 8.17.1 - ajv-draft-04: 1.0.0(ajv@8.17.1) - call-me-maybe: 1.0.2 - openapi-types: 12.1.3 + '@rollup/rollup-android-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-x64@4.57.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.57.1': + optional: true - '@readme/openapi-schemas@3.1.0': {} + '@rollup/rollup-freebsd-x64@4.57.1': + optional: true - '@rollup/rollup-android-arm-eabi@4.31.0': + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': optional: true - '@rollup/rollup-android-arm64@4.31.0': + '@rollup/rollup-linux-arm-musleabihf@4.57.1': optional: true - '@rollup/rollup-darwin-arm64@4.31.0': + '@rollup/rollup-linux-arm64-gnu@4.57.1': optional: true - '@rollup/rollup-darwin-x64@4.31.0': + '@rollup/rollup-linux-arm64-musl@4.57.1': optional: true - '@rollup/rollup-freebsd-arm64@4.31.0': + '@rollup/rollup-linux-loong64-gnu@4.57.1': optional: true - '@rollup/rollup-freebsd-x64@4.31.0': + '@rollup/rollup-linux-loong64-musl@4.57.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.31.0': + '@rollup/rollup-linux-ppc64-gnu@4.57.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.31.0': + '@rollup/rollup-linux-ppc64-musl@4.57.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.31.0': + '@rollup/rollup-linux-riscv64-gnu@4.57.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.31.0': + '@rollup/rollup-linux-riscv64-musl@4.57.1': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.31.0': + '@rollup/rollup-linux-s390x-gnu@4.57.1': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.31.0': + '@rollup/rollup-linux-x64-gnu@4.57.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.31.0': + '@rollup/rollup-linux-x64-musl@4.57.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.31.0': + '@rollup/rollup-openbsd-x64@4.57.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.31.0': + '@rollup/rollup-openharmony-arm64@4.57.1': optional: true - '@rollup/rollup-linux-x64-musl@4.31.0': + '@rollup/rollup-win32-arm64-msvc@4.57.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.31.0': + '@rollup/rollup-win32-ia32-msvc@4.57.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.31.0': + '@rollup/rollup-win32-x64-gnu@4.57.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.31.0': + '@rollup/rollup-win32-x64-msvc@4.57.1': optional: true '@rtsao/scc@1.1.0': {} - '@sigstore/bundle@1.1.0': + '@sigstore/bundle@2.3.2': dependencies: - '@sigstore/protobuf-specs': 0.2.1 + '@sigstore/protobuf-specs': 0.3.3 - '@sigstore/protobuf-specs@0.2.1': {} + '@sigstore/core@1.1.0': {} - '@sigstore/sign@1.0.0': + '@sigstore/protobuf-specs@0.3.3': {} + + '@sigstore/sign@2.3.2': dependencies: - '@sigstore/bundle': 1.1.0 - '@sigstore/protobuf-specs': 0.2.1 - make-fetch-happen: 11.1.1 + '@sigstore/bundle': 2.3.2 + '@sigstore/core': 1.1.0 + '@sigstore/protobuf-specs': 0.3.3 + make-fetch-happen: 13.0.1 + proc-log: 4.2.0 + promise-retry: 2.0.1 transitivePeerDependencies: - supports-color - '@sigstore/tuf@1.0.3': + '@sigstore/tuf@2.3.4': dependencies: - '@sigstore/protobuf-specs': 0.2.1 - tuf-js: 1.1.7 + '@sigstore/protobuf-specs': 0.3.3 + tuf-js: 2.2.1 transitivePeerDependencies: - supports-color - '@sinclair/typebox@0.27.8': {} - - '@sindresorhus/is@5.6.0': {} - - '@sindresorhus/merge-streams@2.3.0': {} - - '@sinonjs/commons@3.0.1': - dependencies: - type-detect: 4.0.8 - - '@sinonjs/fake-timers@10.3.0': + '@sigstore/verify@1.2.1': dependencies: - '@sinonjs/commons': 3.0.1 - - '@szmarczak/http-timer@5.0.1': - dependencies: - defer-to-connect: 2.0.1 - - '@tootallnate/once@2.0.0': {} - - '@ts-rest/core@3.51.0(@types/node@22.10.7)(zod@3.24.1)': - optionalDependencies: - '@types/node': 22.10.7 - zod: 3.24.1 - - '@tsconfig/node10@1.0.11': {} - - '@tsconfig/node12@1.0.11': {} - - '@tsconfig/node14@1.0.3': {} + '@sigstore/bundle': 2.3.2 + '@sigstore/core': 1.1.0 + '@sigstore/protobuf-specs': 0.3.3 - '@tsconfig/node16@1.0.4': {} + '@sinclair/typebox@0.27.8': {} - '@tufjs/canonical-json@1.0.0': {} + '@tufjs/canonical-json@2.0.0': {} - '@tufjs/models@1.0.4': + '@tufjs/models@2.0.1': dependencies: - '@tufjs/canonical-json': 1.0.0 + '@tufjs/canonical-json': 2.0.0 minimatch: 9.0.5 - '@types/babel__core@7.20.5': - dependencies: - '@babel/parser': 7.26.5 - '@babel/types': 7.26.5 - '@types/babel__generator': 7.6.8 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.20.6 - - '@types/babel__generator@7.6.8': - dependencies: - '@babel/types': 7.26.5 - - '@types/babel__template@7.4.4': - dependencies: - '@babel/parser': 7.26.5 - '@babel/types': 7.26.5 - - '@types/babel__traverse@7.20.6': + '@tybys/wasm-util@0.10.1': dependencies: - '@babel/types': 7.26.5 - - '@types/estree@1.0.6': {} + tslib: 2.8.1 + optional: true - '@types/fs-extra@9.0.13': + '@tybys/wasm-util@0.9.0': dependencies: - '@types/node': 22.10.7 + tslib: 2.8.1 - '@types/graceful-fs@4.1.9': + '@types/chai@5.2.3': dependencies: - '@types/node': 22.10.7 - - '@types/http-cache-semantics@4.0.4': {} - - '@types/istanbul-lib-coverage@2.0.6': {} + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 - '@types/istanbul-lib-report@3.0.3': - dependencies: - '@types/istanbul-lib-coverage': 2.0.6 + '@types/deep-eql@4.0.2': {} - '@types/istanbul-reports@3.0.4': - dependencies: - '@types/istanbul-lib-report': 3.0.3 + '@types/estree@1.0.8': {} '@types/json-schema@7.0.15': {} @@ -6727,38 +7749,28 @@ snapshots: '@types/minimist@1.2.5': {} - '@types/node@22.10.7': + '@types/node@22.19.11': dependencies: - undici-types: 6.20.0 + undici-types: 6.21.0 '@types/normalize-package-data@2.4.4': {} - '@types/semver@6.2.7': {} - - '@types/semver@7.5.8': {} - - '@types/stack-utils@2.0.3': {} - - '@types/yargs-parser@21.0.3': {} - - '@types/yargs@17.0.33': - dependencies: - '@types/yargs-parser': 21.0.3 + '@types/semver@7.7.1': {} '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3)': dependencies: - '@eslint-community/regexpp': 4.12.1 + '@eslint-community/regexpp': 4.12.2 '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.7.3) '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.7.3) '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.7.3) '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.0 + debug: 4.4.3 eslint: 8.57.1 graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - semver: 7.6.3 + semver: 7.7.4 ts-api-utils: 1.4.3(typescript@5.7.3) optionalDependencies: typescript: 5.7.3 @@ -6771,7 +7783,7 @@ snapshots: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.7.3) '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.0 + debug: 4.4.3 eslint: 8.57.1 optionalDependencies: typescript: 5.7.3 @@ -6787,7 +7799,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.7.3) '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.7.3) - debug: 4.4.0 + debug: 4.4.3 eslint: 8.57.1 ts-api-utils: 1.4.3(typescript@5.7.3) optionalDependencies: @@ -6795,33 +7807,17 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@5.62.0': {} - '@typescript-eslint/types@6.21.0': {} - '@typescript-eslint/typescript-estree@5.62.0(typescript@5.7.3)': - dependencies: - '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.4.0 - globby: 11.1.0 - is-glob: 4.0.3 - semver: 7.6.3 - tsutils: 3.21.0(typescript@5.7.3) - optionalDependencies: - typescript: 5.7.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/typescript-estree@6.21.0(typescript@5.7.3)': dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.0 + debug: 4.4.3 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.6.3 + semver: 7.7.4 ts-api-utils: 1.4.3(typescript@5.7.3) optionalDependencies: typescript: 5.7.3 @@ -6830,195 +7826,181 @@ snapshots: '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.7.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) '@types/json-schema': 7.0.15 - '@types/semver': 7.5.8 + '@types/semver': 7.7.1 '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.7.3) eslint: 8.57.1 - semver: 7.6.3 + semver: 7.7.4 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/visitor-keys@5.62.0': - dependencies: - '@typescript-eslint/types': 5.62.0 - eslint-visitor-keys: 3.4.3 - '@typescript-eslint/visitor-keys@6.21.0': dependencies: '@typescript-eslint/types': 6.21.0 eslint-visitor-keys: 3.4.3 - '@typespec/compiler@0.63.0': - dependencies: - '@babel/code-frame': 7.25.9 - ajv: 8.17.1 - change-case: 5.4.4 - globby: 14.0.2 - mustache: 4.2.0 - picocolors: 1.1.1 - prettier: 3.3.3 - prompts: 2.4.2 - semver: 7.6.3 - temporal-polyfill: 0.2.5 - vscode-languageserver: 9.0.1 - vscode-languageserver-textdocument: 1.0.12 - yaml: 2.5.1 - yargs: 17.7.2 + '@ungap/structured-clone@1.3.0': {} - '@typespec/http@0.63.0(@typespec/compiler@0.63.0)': - dependencies: - '@typespec/compiler': 0.63.0 + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + optional: true - '@typespec/openapi3@0.63.0(@typespec/compiler@0.63.0)(@typespec/http@0.63.0(@typespec/compiler@0.63.0))(@typespec/openapi@0.63.0(@typespec/compiler@0.63.0)(@typespec/http@0.63.0(@typespec/compiler@0.63.0)))(@typespec/versioning@0.63.0(@typespec/compiler@0.63.0))': - dependencies: - '@readme/openapi-parser': 2.6.0(openapi-types@12.1.3) - '@typespec/compiler': 0.63.0 - '@typespec/http': 0.63.0(@typespec/compiler@0.63.0) - '@typespec/openapi': 0.63.0(@typespec/compiler@0.63.0)(@typespec/http@0.63.0(@typespec/compiler@0.63.0)) - '@typespec/versioning': 0.63.0(@typespec/compiler@0.63.0) - openapi-types: 12.1.3 - yaml: 2.5.1 + '@unrs/resolver-binding-android-arm64@1.11.1': + optional: true - '@typespec/openapi@0.63.0(@typespec/compiler@0.63.0)(@typespec/http@0.63.0(@typespec/compiler@0.63.0))': - dependencies: - '@typespec/compiler': 0.63.0 - '@typespec/http': 0.63.0(@typespec/compiler@0.63.0) + '@unrs/resolver-binding-darwin-arm64@1.11.1': + optional: true - '@typespec/rest@0.63.1(@typespec/compiler@0.63.0)(@typespec/http@0.63.0(@typespec/compiler@0.63.0))': - dependencies: - '@typespec/compiler': 0.63.0 - '@typespec/http': 0.63.0(@typespec/compiler@0.63.0) + '@unrs/resolver-binding-darwin-x64@1.11.1': + optional: true - '@typespec/versioning@0.63.0(@typespec/compiler@0.63.0)': - dependencies: - '@typespec/compiler': 0.63.0 + '@unrs/resolver-binding-freebsd-x64@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + optional: true - '@ungap/structured-clone@1.2.1': {} + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + optional: true - '@vitest/coverage-c8@0.33.0(vitest@3.0.3(@types/node@22.10.7)(yaml@2.7.0))': + '@unrs/resolver-binding-wasm32-wasi@1.11.1': dependencies: - '@ampproject/remapping': 2.3.0 - c8: 7.14.0 - magic-string: 0.30.17 - picocolors: 1.1.1 - std-env: 3.8.0 - vitest: 3.0.3(@types/node@22.10.7)(yaml@2.7.0) + '@napi-rs/wasm-runtime': 0.2.12 + optional: true - '@vitest/coverage-v8@3.0.3(vitest@3.0.3(@types/node@22.10.7)(yaml@2.7.0))': + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + optional: true + + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/node@22.19.11)(yaml@2.8.2))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 - debug: 4.4.0 + ast-v8-to-istanbul: 0.3.11 + debug: 4.4.3 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 - istanbul-reports: 3.1.7 - magic-string: 0.30.17 + istanbul-reports: 3.2.0 + magic-string: 0.30.21 magicast: 0.3.5 - std-env: 3.8.0 + std-env: 3.10.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.0.3(@types/node@22.10.7)(yaml@2.7.0) + vitest: 3.2.4(@types/node@22.19.11)(yaml@2.8.2) transitivePeerDependencies: - supports-color - '@vitest/expect@3.0.3': + '@vitest/expect@3.2.4': dependencies: - '@vitest/spy': 3.0.3 - '@vitest/utils': 3.0.3 - chai: 5.1.2 + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.0.3(vite@6.0.11(@types/node@22.10.7)(yaml@2.7.0))': + '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@22.19.11)(yaml@2.8.2))': dependencies: - '@vitest/spy': 3.0.3 + '@vitest/spy': 3.2.4 estree-walker: 3.0.3 - magic-string: 0.30.17 + magic-string: 0.30.21 optionalDependencies: - vite: 6.0.11(@types/node@22.10.7)(yaml@2.7.0) + vite: 7.3.1(@types/node@22.19.11)(yaml@2.8.2) - '@vitest/pretty-format@3.0.3': + '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 - '@vitest/runner@3.0.3': + '@vitest/runner@3.2.4': dependencies: - '@vitest/utils': 3.0.3 - pathe: 2.0.2 + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.1.0 - '@vitest/snapshot@3.0.3': + '@vitest/snapshot@3.2.4': dependencies: - '@vitest/pretty-format': 3.0.3 - magic-string: 0.30.17 - pathe: 2.0.2 + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.21 + pathe: 2.0.3 - '@vitest/spy@3.0.3': + '@vitest/spy@3.2.4': dependencies: - tinyspy: 3.0.2 + tinyspy: 4.0.4 - '@vitest/utils@3.0.3': + '@vitest/utils@3.2.4': dependencies: - '@vitest/pretty-format': 3.0.3 - loupe: 3.1.2 + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 tinyrainbow: 2.0.0 '@yarnpkg/lockfile@1.1.0': {} - '@yarnpkg/parsers@3.0.0-rc.46': + '@yarnpkg/parsers@3.0.2': dependencies: - js-yaml: 3.14.1 + js-yaml: 3.14.2 tslib: 2.8.1 - '@zkochan/js-yaml@0.0.6': + '@zkochan/js-yaml@0.0.7': dependencies: argparse: 2.0.1 - '@zodios/core@10.9.6(axios@1.7.9)(zod@3.24.1)': - dependencies: - axios: 1.7.9 - zod: 3.24.1 - JSONStream@1.3.5: dependencies: jsonparse: 1.3.1 through: 2.3.8 - abbrev@1.1.1: {} + abbrev@2.0.0: {} - acorn-jsx@5.3.2(acorn@8.14.0): + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: - acorn: 8.14.0 + acorn: 8.15.0 - acorn-walk@8.3.4: - dependencies: - acorn: 8.14.0 - - acorn@8.14.0: {} + acorn@8.15.0: {} add-stream@1.0.0: {} - agent-base@6.0.2: - dependencies: - debug: 4.4.0 - transitivePeerDependencies: - - supports-color - - agentkeepalive@4.6.0: - dependencies: - humanize-ms: 1.2.1 + agent-base@7.1.4: {} aggregate-error@3.1.0: dependencies: clean-stack: 2.2.0 indent-string: 4.0.0 - ajv-draft-04@1.0.0(ajv@8.17.1): + ajv-formats@3.0.1(ajv@8.18.0): optionalDependencies: - ajv: 8.17.1 + ajv: 8.18.0 ajv@6.12.6: dependencies: @@ -7027,10 +8009,10 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.17.1: + ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.0.6 + fast-uri: 3.1.0 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -7046,11 +8028,7 @@ snapshots: ansi-regex@5.0.1: {} - ansi-regex@6.1.0: {} - - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 + ansi-regex@6.2.2: {} ansi-styles@4.3.0: dependencies: @@ -7058,26 +8036,10 @@ snapshots: ansi-styles@5.2.0: {} - ansi-styles@6.2.1: {} - - any-promise@1.3.0: {} - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - - app-module-path@2.2.0: {} + ansi-styles@6.2.3: {} aproba@2.0.0: {} - are-we-there-yet@3.0.1: - dependencies: - delegates: 1.0.0 - readable-stream: 3.6.2 - - arg@4.1.3: {} - argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -7086,68 +8048,73 @@ snapshots: array-buffer-byte-length@1.0.2: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 is-array-buffer: 3.0.5 array-differ@3.0.0: {} array-ify@1.0.0: {} - array-includes@3.1.8: + array-includes@3.1.9: dependencies: call-bind: 1.0.8 + call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.1 es-object-atoms: 1.1.1 - get-intrinsic: 1.2.7 + get-intrinsic: 1.3.0 is-string: 1.1.1 + math-intrinsics: 1.1.0 array-union@2.1.0: {} - array.prototype.findlastindex@1.2.5: + array.prototype.findlastindex@1.2.6: dependencies: call-bind: 1.0.8 + call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.1 es-errors: 1.3.0 es-object-atoms: 1.1.1 - es-shim-unscopables: 1.0.2 + es-shim-unscopables: 1.1.0 array.prototype.flat@1.3.3: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 - es-shim-unscopables: 1.0.2 + es-abstract: 1.24.1 + es-shim-unscopables: 1.1.0 array.prototype.flatmap@1.3.3: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 - es-shim-unscopables: 1.0.2 + es-abstract: 1.24.1 + es-shim-unscopables: 1.1.0 arraybuffer.prototype.slice@1.0.4: dependencies: array-buffer-byte-length: 1.0.2 call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.1 es-errors: 1.3.0 - get-intrinsic: 1.2.7 + get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 arrify@1.0.1: {} arrify@2.0.1: {} - assert-plus@1.0.0: {} - assertion-error@2.0.1: {} - ast-module-types@5.0.0: {} + ast-v8-to-istanbul@0.3.11: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 - astral-regex@2.0.0: {} + async-function@1.0.0: {} async@3.2.6: {} @@ -7155,89 +8122,41 @@ snapshots: available-typed-arrays@1.0.7: dependencies: - possible-typed-array-names: 1.0.0 + possible-typed-array-names: 1.1.0 - axios@1.7.9: + axios@1.13.5: dependencies: - follow-redirects: 1.15.6 - form-data: 4.0.1 + follow-redirects: 1.15.11 + form-data: 4.0.5 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug - babel-jest@29.7.0(@babel/core@7.26.0): - dependencies: - '@babel/core': 7.26.0 - '@jest/transform': 29.7.0 - '@types/babel__core': 7.20.5 - babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.26.0) - chalk: 4.1.2 - graceful-fs: 4.2.11 - slash: 3.0.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-istanbul@6.1.1: - dependencies: - '@babel/helper-plugin-utils': 7.26.5 - '@istanbuljs/load-nyc-config': 1.1.0 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-instrument: 5.2.1 - test-exclude: 6.0.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-jest-hoist@29.6.3: - dependencies: - '@babel/template': 7.25.9 - '@babel/types': 7.26.5 - '@types/babel__core': 7.20.5 - '@types/babel__traverse': 7.20.6 - - babel-preset-current-node-syntax@1.1.0(@babel/core@7.26.0): - dependencies: - '@babel/core': 7.26.0 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.26.0) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.26.0) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.26.0) - '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.0) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.26.0) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.26.0) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.26.0) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.26.0) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.0) - - babel-preset-jest@29.6.3(@babel/core@7.26.0): - dependencies: - '@babel/core': 7.26.0 - babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.0) - balanced-match@1.0.2: {} base64-js@1.5.1: {} before-after-hook@2.2.3: {} + bin-links@4.0.4: + dependencies: + cmd-shim: 6.0.3 + npm-normalize-package-bin: 3.0.1 + read-cmd-shim: 4.0.0 + write-file-atomic: 5.0.1 + bl@4.1.0: dependencies: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.2 - brace-expansion@1.1.11: + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.1: + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -7245,21 +8164,6 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.24.4: - dependencies: - caniuse-lite: 1.0.30001695 - electron-to-chromium: 1.5.83 - node-releases: 2.0.19 - update-browserslist-db: 1.1.2(browserslist@4.24.4) - - bs-logger@0.2.6: - dependencies: - fast-json-stable-stringify: 2.1.0 - - bser@2.1.1: - dependencies: - node-int64: 0.4.0 - buffer-from@1.1.2: {} buffer@5.7.1: @@ -7267,99 +8171,41 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - builtins@1.0.3: {} - - builtins@5.1.0: - dependencies: - semver: 7.6.3 - byte-size@8.1.1: {} - c8@7.14.0: - dependencies: - '@bcoe/v8-coverage': 0.2.3 - '@istanbuljs/schema': 0.1.3 - find-up: 5.0.0 - foreground-child: 2.0.0 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-reports: 3.1.7 - rimraf: 3.0.2 - test-exclude: 6.0.0 - v8-to-istanbul: 9.3.0 - yargs: 16.2.0 - yargs-parser: 20.2.9 - cac@6.7.14: {} - cacache@16.1.3: - dependencies: - '@npmcli/fs': 2.1.2 - '@npmcli/move-file': 2.0.1 - chownr: 2.0.0 - fs-minipass: 2.1.0 - glob: 8.1.0 - infer-owner: 1.0.4 - lru-cache: 7.18.3 - minipass: 3.3.6 - minipass-collect: 1.0.2 - minipass-flush: 1.0.5 - minipass-pipeline: 1.2.4 - mkdirp: 1.0.4 - p-map: 4.0.0 - promise-inflight: 1.0.1 - rimraf: 3.0.2 - ssri: 9.0.1 - tar: 6.2.0 - unique-filename: 2.0.1 - transitivePeerDependencies: - - bluebird - - cacache@17.1.4: + cacache@18.0.4: dependencies: '@npmcli/fs': 3.1.1 fs-minipass: 3.0.3 - glob: 10.4.5 - lru-cache: 7.18.3 + glob: 10.5.0 + lru-cache: 10.4.3 minipass: 7.1.2 - minipass-collect: 1.0.2 + minipass-collect: 2.0.1 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 p-map: 4.0.0 ssri: 10.0.6 - tar: 6.2.0 + tar: 6.2.1 unique-filename: 3.0.0 - cacheable-lookup@7.0.0: {} - - cacheable-request@10.2.14: - dependencies: - '@types/http-cache-semantics': 4.0.4 - get-stream: 6.0.1 - http-cache-semantics: 4.1.1 - keyv: 4.5.4 - mimic-response: 4.0.0 - normalize-url: 8.0.1 - responselike: 3.0.0 - - call-bind-apply-helpers@1.0.1: + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 call-bind@1.0.8: dependencies: - call-bind-apply-helpers: 1.0.1 + call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 - get-intrinsic: 1.2.7 + get-intrinsic: 1.3.0 set-function-length: 1.2.2 - call-bound@1.0.3: + call-bound@1.0.4: dependencies: - call-bind-apply-helpers: 1.0.1 - get-intrinsic: 1.2.7 - - call-me-maybe@1.0.2: {} + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 callsites@3.1.0: {} @@ -7371,23 +8217,13 @@ snapshots: camelcase@5.3.1: {} - camelcase@6.3.0: {} - - caniuse-lite@1.0.30001695: {} - - chai@5.1.2: + chai@5.3.3: dependencies: assertion-error: 2.0.1 - check-error: 2.1.1 + check-error: 2.1.3 deep-eql: 5.0.2 - loupe: 3.1.2 - pathval: 2.0.0 - - chalk@2.4.2: - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 + loupe: 3.2.1 + pathval: 2.0.1 chalk@4.1.0: dependencies: @@ -7401,21 +8237,17 @@ snapshots: chalk@5.3.0: {} - chalk@5.4.1: {} + chalk@5.6.2: {} - change-case@5.4.4: {} + chardet@2.1.1: {} - char-regex@1.0.2: {} - - chardet@0.7.0: {} - - check-error@2.1.1: {} + check-error@2.1.3: {} chownr@2.0.0: {} ci-info@3.9.0: {} - cjs-module-lexer@1.4.1: {} + ci-info@4.4.0: {} clean-stack@2.2.0: {} @@ -7464,28 +8296,18 @@ snapshots: clone@1.0.4: {} - cmd-shim@6.0.1: {} - - co@4.6.0: {} - - code-error-fragment@0.0.230: {} - - collect-v8-coverage@1.0.2: {} - - color-convert@1.9.3: - dependencies: - color-name: 1.1.3 + cmd-shim@6.0.3: {} color-convert@2.0.1: dependencies: color-name: 1.1.4 - color-name@1.1.3: {} - color-name@1.1.4: {} color-support@1.1.3: {} + colorette@1.4.0: {} + colorette@2.0.20: {} columnify@1.6.0: @@ -7497,20 +8319,11 @@ snapshots: dependencies: delayed-stream: 1.0.0 - commander@10.0.1: {} - commander@11.0.0: {} commander@12.1.0: {} - commander@7.2.0: {} - - commander@8.3.0: {} - - commander@9.5.0: - optional: true - - commondir@1.0.1: {} + common-ancestor-path@1.0.1: {} compare-func@2.0.0: dependencies: @@ -7526,11 +8339,6 @@ snapshots: readable-stream: 3.6.2 typedarray: 0.0.6 - config-chain@1.1.13: - dependencies: - ini: 1.3.8 - proto-list: 1.2.4 - console-control-strings@1.1.0: {} conventional-changelog-angular@7.0.0: @@ -7560,7 +8368,7 @@ snapshots: handlebars: 4.7.8 json-stringify-safe: 5.0.1 meow: 8.1.2 - semver: 7.6.3 + semver: 7.7.4 split: 1.0.1 conventional-commits-filter@3.0.0: @@ -7585,72 +8393,42 @@ snapshots: git-semver-tags: 5.0.1 meow: 8.1.2 - convert-source-map@2.0.0: {} - - convict@6.2.4: - dependencies: - lodash.clonedeep: 4.5.0 - yargs-parser: 20.2.9 - core-util-is@1.0.2: {} - core-util-is@1.0.3: {} - - cosmiconfig@8.3.6(typescript@5.7.3): + cosmiconfig@9.0.0(typescript@5.7.3): dependencies: - import-fresh: 3.3.0 - js-yaml: 4.1.0 + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.1.1 parse-json: 5.2.0 - path-type: 4.0.0 optionalDependencies: typescript: 5.7.3 - create-jest@29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)): - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)) - jest-util: 29.7.0 - prompts: 2.4.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - create-require@1.1.1: {} - - cross-spawn@5.1.0: - dependencies: - lru-cache: 4.1.5 - shebang-command: 1.2.0 - which: 1.3.1 - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + cssesc@3.0.0: {} + dargs@7.0.0: {} data-view-buffer@1.0.2: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 es-errors: 1.3.0 is-data-view: 1.0.2 data-view-byte-length@1.0.2: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 es-errors: 1.3.0 is-data-view: 1.0.2 data-view-byte-offset@1.0.1: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 es-errors: 1.3.0 is-data-view: 1.0.2 @@ -7664,7 +8442,7 @@ snapshots: dependencies: ms: 2.1.2 - debug@4.4.0: + debug@4.4.3: dependencies: ms: 2.1.3 @@ -7675,28 +8453,16 @@ snapshots: decamelize@1.2.0: {} - decompress-response@6.0.0: - dependencies: - mimic-response: 3.1.0 - - dedent@0.7.0: {} - dedent@1.5.3: {} deep-eql@5.0.2: {} - deep-extend@0.6.0: {} - deep-is@0.1.4: {} - deepmerge@4.3.1: {} - defaults@1.0.4: dependencies: clone: 1.0.4 - defer-to-connect@2.0.1: {} - define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -7713,72 +8479,12 @@ snapshots: delayed-stream@1.0.0: {} - delegates@1.0.0: {} - - dependency-tree@10.0.9: - dependencies: - commander: 10.0.1 - filing-cabinet: 4.2.0 - precinct: 11.0.5 - typescript: 5.7.3 - transitivePeerDependencies: - - supports-color - deprecation@2.3.1: {} detect-indent@5.0.0: {} - detect-indent@6.1.0: {} - - detect-newline@3.1.0: {} - - detective-amd@5.0.2: - dependencies: - ast-module-types: 5.0.0 - escodegen: 2.1.0 - get-amd-module-type: 5.0.1 - node-source-walk: 6.0.2 - - detective-cjs@5.0.1: - dependencies: - ast-module-types: 5.0.0 - node-source-walk: 6.0.2 - - detective-es6@4.0.1: - dependencies: - node-source-walk: 6.0.2 - - detective-postcss@6.1.3: - dependencies: - is-url: 1.2.4 - postcss: 8.5.1 - postcss-values-parser: 6.0.2(postcss@8.5.1) - - detective-sass@5.0.3: - dependencies: - gonzales-pe: 4.3.0 - node-source-walk: 6.0.2 - - detective-scss@4.0.3: - dependencies: - gonzales-pe: 4.3.0 - node-source-walk: 6.0.2 - - detective-stylus@4.0.0: {} - - detective-typescript@11.2.0: - dependencies: - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.7.3) - ast-module-types: 5.0.0 - node-source-walk: 6.0.2 - typescript: 5.7.3 - transitivePeerDependencies: - - supports-color - diff-sequences@29.6.3: {} - diff@4.0.2: {} - dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -7795,29 +8501,25 @@ snapshots: dependencies: is-obj: 2.0.0 - dotenv-expand@10.0.0: {} + dotenv-expand@11.0.7: + dependencies: + dotenv: 16.4.7 - dotenv@16.3.2: {} + dotenv@16.4.7: {} dunder-proto@1.0.1: dependencies: - call-bind-apply-helpers: 1.0.1 + call-bind-apply-helpers: 1.0.2 es-errors: 1.3.0 gopd: 1.2.0 - duplexer@0.1.2: {} - eastasianwidth@0.2.0: {} ejs@3.1.10: dependencies: - jake: 10.9.2 + jake: 10.9.4 - electron-to-chromium@1.5.83: {} - - emittery@0.13.1: {} - - emoji-regex@10.4.0: {} + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} @@ -7828,36 +8530,31 @@ snapshots: iconv-lite: 0.6.3 optional: true - end-of-stream@1.4.4: + end-of-stream@1.4.5: dependencies: once: 1.4.0 - enhanced-resolve@5.18.0: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.2.1 - enquirer@2.3.6: dependencies: ansi-colors: 4.1.3 env-paths@2.2.1: {} - envinfo@7.8.1: {} + envinfo@7.13.0: {} err-code@2.0.3: {} - error-ex@1.3.2: + error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 - es-abstract@1.23.9: + es-abstract@1.24.1: dependencies: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 available-typed-arrays: 1.0.7 call-bind: 1.0.8 - call-bound: 1.0.3 + call-bound: 1.0.4 data-view-buffer: 1.0.2 data-view-byte-length: 1.0.2 data-view-byte-offset: 1.0.1 @@ -7867,7 +8564,7 @@ snapshots: es-set-tostringtag: 2.1.0 es-to-primitive: 1.3.0 function.prototype.name: 1.1.8 - get-intrinsic: 1.2.7 + get-intrinsic: 1.3.0 get-proto: 1.0.1 get-symbol-description: 1.1.0 globalthis: 1.0.4 @@ -7880,13 +8577,15 @@ snapshots: is-array-buffer: 3.0.5 is-callable: 1.2.7 is-data-view: 1.0.2 + is-negative-zero: 2.0.3 is-regex: 1.2.1 + is-set: 2.0.3 is-shared-array-buffer: 1.0.4 is-string: 1.1.1 is-typed-array: 1.1.15 - is-weakref: 1.1.0 + is-weakref: 1.1.1 math-intrinsics: 1.1.0 - object-inspect: 1.13.3 + object-inspect: 1.13.4 object-keys: 1.1.1 object.assign: 4.1.7 own-keys: 1.0.1 @@ -7895,6 +8594,7 @@ snapshots: safe-push-apply: 1.0.0 safe-regex-test: 1.1.0 set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 string.prototype.trim: 1.2.10 string.prototype.trimend: 1.0.9 string.prototype.trimstart: 1.0.8 @@ -7903,13 +8603,13 @@ snapshots: typed-array-byte-offset: 1.0.4 typed-array-length: 1.0.7 unbox-primitive: 1.1.0 - which-typed-array: 1.1.18 + which-typed-array: 1.1.20 es-define-property@1.0.1: {} es-errors@1.3.0: {} - es-module-lexer@1.6.0: {} + es-module-lexer@1.7.0: {} es-object-atoms@1.1.1: dependencies: @@ -7918,11 +8618,11 @@ snapshots: es-set-tostringtag@2.1.0: dependencies: es-errors: 1.3.0 - get-intrinsic: 1.2.7 + get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 hasown: 2.0.2 - es-shim-unscopables@1.0.2: + es-shim-unscopables@1.1.0: dependencies: hasown: 2.0.2 @@ -7932,51 +8632,42 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - esbuild@0.24.2: + esbuild@0.27.3: optionalDependencies: - '@esbuild/aix-ppc64': 0.24.2 - '@esbuild/android-arm': 0.24.2 - '@esbuild/android-arm64': 0.24.2 - '@esbuild/android-x64': 0.24.2 - '@esbuild/darwin-arm64': 0.24.2 - '@esbuild/darwin-x64': 0.24.2 - '@esbuild/freebsd-arm64': 0.24.2 - '@esbuild/freebsd-x64': 0.24.2 - '@esbuild/linux-arm': 0.24.2 - '@esbuild/linux-arm64': 0.24.2 - '@esbuild/linux-ia32': 0.24.2 - '@esbuild/linux-loong64': 0.24.2 - '@esbuild/linux-mips64el': 0.24.2 - '@esbuild/linux-ppc64': 0.24.2 - '@esbuild/linux-riscv64': 0.24.2 - '@esbuild/linux-s390x': 0.24.2 - '@esbuild/linux-x64': 0.24.2 - '@esbuild/netbsd-arm64': 0.24.2 - '@esbuild/netbsd-x64': 0.24.2 - '@esbuild/openbsd-arm64': 0.24.2 - '@esbuild/openbsd-x64': 0.24.2 - '@esbuild/sunos-x64': 0.24.2 - '@esbuild/win32-arm64': 0.24.2 - '@esbuild/win32-ia32': 0.24.2 - '@esbuild/win32-x64': 0.24.2 + '@esbuild/aix-ppc64': 0.27.3 + '@esbuild/android-arm': 0.27.3 + '@esbuild/android-arm64': 0.27.3 + '@esbuild/android-x64': 0.27.3 + '@esbuild/darwin-arm64': 0.27.3 + '@esbuild/darwin-x64': 0.27.3 + '@esbuild/freebsd-arm64': 0.27.3 + '@esbuild/freebsd-x64': 0.27.3 + '@esbuild/linux-arm': 0.27.3 + '@esbuild/linux-arm64': 0.27.3 + '@esbuild/linux-ia32': 0.27.3 + '@esbuild/linux-loong64': 0.27.3 + '@esbuild/linux-mips64el': 0.27.3 + '@esbuild/linux-ppc64': 0.27.3 + '@esbuild/linux-riscv64': 0.27.3 + '@esbuild/linux-s390x': 0.27.3 + '@esbuild/linux-x64': 0.27.3 + '@esbuild/netbsd-arm64': 0.27.3 + '@esbuild/netbsd-x64': 0.27.3 + '@esbuild/openbsd-arm64': 0.27.3 + '@esbuild/openbsd-x64': 0.27.3 + '@esbuild/openharmony-arm64': 0.27.3 + '@esbuild/sunos-x64': 0.27.3 + '@esbuild/win32-arm64': 0.27.3 + '@esbuild/win32-ia32': 0.27.3 + '@esbuild/win32-x64': 0.27.3 escalade@3.2.0: {} escape-string-regexp@1.0.5: {} - escape-string-regexp@2.0.0: {} - escape-string-regexp@4.0.0: {} - escodegen@2.1.0: - dependencies: - esprima: 4.0.1 - estraverse: 5.3.0 - esutils: 2.0.3 - optionalDependencies: - source-map: 0.6.1 - - eslint-config-prettier@9.1.0(eslint@8.57.1): + eslint-config-prettier@9.1.2(eslint@8.57.1): dependencies: eslint: 8.57.1 @@ -7984,49 +8675,48 @@ snapshots: dependencies: debug: 3.2.7 is-core-module: 2.16.1 - resolve: 1.22.10 + resolve: 1.22.11 transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.0 - enhanced-resolve: 5.18.0 + debug: 4.4.3 eslint: 8.57.1 - fast-glob: 3.3.3 - get-tsconfig: 4.9.0 - is-bun-module: 1.3.0 - is-glob: 4.0.3 - stable-hash: 0.0.4 + get-tsconfig: 4.13.6 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.15 + unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1): + eslint-module-utils@2.12.1(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.7.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 - array-includes: 3.1.8 - array.prototype.findlastindex: 1.2.5 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 array.prototype.flat: 1.3.3 array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -8044,14 +8734,14 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-prettier@5.2.3(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.4.2): + eslint-plugin-prettier@5.5.5(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.8.1): dependencies: eslint: 8.57.1 - prettier: 3.4.2 - prettier-linter-helpers: 1.0.0 - synckit: 0.9.2 + prettier: 3.8.1 + prettier-linter-helpers: 1.0.1 + synckit: 0.11.12 optionalDependencies: - eslint-config-prettier: 9.1.0(eslint@8.57.1) + eslint-config-prettier: 9.1.2(eslint@8.57.1) eslint-scope@7.2.2: dependencies: @@ -8062,24 +8752,24 @@ snapshots: eslint@8.57.1: dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) - '@eslint-community/regexpp': 4.12.1 + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.2 '@eslint/eslintrc': 2.1.4 '@eslint/js': 8.57.1 '@humanwhocodes/config-array': 0.13.0 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.2.1 + '@ungap/structured-clone': 1.3.0 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.0 + debug: 4.4.3 doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 espree: 9.6.1 - esquery: 1.6.0 + esquery: 1.7.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 6.0.1 @@ -8091,7 +8781,7 @@ snapshots: imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 - js-yaml: 4.1.0 + js-yaml: 4.1.1 json-stable-stringify-without-jsonify: 1.0.1 levn: 0.4.1 lodash.merge: 4.6.2 @@ -8105,13 +8795,13 @@ snapshots: espree@9.6.1: dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 3.4.3 esprima@4.0.1: {} - esquery@1.6.0: + esquery@1.7.0: dependencies: estraverse: 5.3.0 @@ -8123,34 +8813,20 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 esutils@2.0.3: {} - eval-estree-expression@2.0.3: {} - eventemitter3@4.0.7: {} - eventemitter3@5.0.1: {} + eventemitter3@5.0.4: {} execa@5.0.0: - dependencies: - cross-spawn: 7.0.6 - get-stream: 6.0.0 - human-signals: 2.1.0 - is-stream: 2.0.0 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - - execa@5.1.1: dependencies: cross-spawn: 7.0.6 get-stream: 6.0.1 human-signals: 2.1.0 - is-stream: 2.0.1 + is-stream: 2.0.0 merge-stream: 2.0.0 npm-run-path: 4.0.1 onetime: 5.1.2 @@ -8169,27 +8845,9 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 3.0.0 - exit@0.1.2: {} - - expect-type@1.1.0: {} - - expect@29.7.0: - dependencies: - '@jest/expect-utils': 29.7.0 - jest-get-type: 29.6.3 - jest-matcher-utils: 29.7.0 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - - exponential-backoff@3.1.1: {} - - external-editor@3.1.0: - dependencies: - chardet: 0.7.0 - iconv-lite: 0.4.24 - tmp: 0.0.33 + expect-type@1.3.0: {} - extsprintf@1.4.1: {} + exponential-backoff@3.1.3: {} fast-deep-equal@3.1.3: {} @@ -8207,15 +8865,15 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-uri@3.0.6: {} + fast-uri@3.1.0: {} - fastq@1.18.0: + fastq@1.20.1: dependencies: - reusify: 1.0.4 + reusify: 1.1.0 - fb-watchman@2.0.2: - dependencies: - bser: 2.1.1 + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 figures@3.2.0: dependencies: @@ -8226,23 +8884,8 @@ snapshots: flat-cache: 3.2.0 filelist@1.0.4: - dependencies: - minimatch: 5.1.6 - - filing-cabinet@4.2.0: - dependencies: - app-module-path: 2.2.0 - commander: 10.0.1 - enhanced-resolve: 5.18.0 - is-relative-path: 1.0.2 - module-definition: 5.0.1 - module-lookup-amd: 8.0.5 - resolve: 1.22.10 - resolve-dependency-path: 3.0.2 - sass-lookup: 5.0.1 - stylus-lookup: 5.0.1 - tsconfig-paths: 4.2.0 - typescript: 5.7.3 + dependencies: + minimatch: 5.1.6 fill-range@7.1.1: dependencies: @@ -8264,57 +8907,44 @@ snapshots: flat-cache@3.2.0: dependencies: - flatted: 3.3.2 + flatted: 3.3.3 keyv: 4.5.4 rimraf: 3.0.2 flat@5.0.2: {} - flatted@3.3.2: {} + flatted@3.3.3: {} - follow-redirects@1.15.6: {} + follow-redirects@1.15.11: {} - for-each@0.3.3: + for-each@0.3.5: dependencies: is-callable: 1.2.7 - foreground-child@2.0.0: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 3.0.7 - - foreground-child@3.3.0: + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 - form-data-encoder@2.1.4: {} - - form-data@4.0.1: + form-data@4.0.5: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 mime-types: 2.1.35 - fs-constants@1.0.0: {} - - fs-extra@10.1.0: + front-matter@4.0.2: dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.1 + js-yaml: 3.14.2 - fs-extra@11.3.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.1 + fs-constants@1.0.0: {} - fs-extra@8.1.0: + fs-extra@11.3.3: dependencies: graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 + jsonfile: 6.2.0 + universalify: 2.0.1 fs-minipass@2.1.0: dependencies: @@ -8334,7 +8964,7 @@ snapshots: function.prototype.name@1.1.8: dependencies: call-bind: 1.0.8 - call-bound: 1.0.3 + call-bound: 1.0.4 define-properties: 1.2.1 functions-have-names: 1.2.3 hasown: 2.0.2 @@ -8342,31 +8972,15 @@ snapshots: functions-have-names@1.2.3: {} - gauge@4.0.4: - dependencies: - aproba: 2.0.0 - color-support: 1.1.3 - console-control-strings: 1.1.0 - has-unicode: 2.0.1 - signal-exit: 3.0.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wide-align: 1.1.5 - - gensync@1.0.0-beta.2: {} - - get-amd-module-type@5.0.1: - dependencies: - ast-module-types: 5.0.0 - node-source-walk: 6.0.2 + generator-function@2.0.1: {} get-caller-file@2.0.5: {} - get-east-asian-width@1.3.0: {} + get-east-asian-width@1.4.0: {} - get-intrinsic@1.2.7: + get-intrinsic@1.3.0: dependencies: - call-bind-apply-helpers: 1.0.1 + call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 es-errors: 1.3.0 es-object-atoms: 1.1.1 @@ -8377,10 +8991,6 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 - get-own-enumerable-property-symbols@3.0.2: {} - - get-package-type@0.1.0: {} - get-pkg-repo@4.2.1: dependencies: '@hutson/parse-repository-url': 3.0.2 @@ -8401,11 +9011,11 @@ snapshots: get-symbol-description@1.1.0: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 es-errors: 1.3.0 - get-intrinsic: 1.2.7 + get-intrinsic: 1.3.0 - get-tsconfig@4.9.0: + get-tsconfig@4.13.6: dependencies: resolve-pkg-maps: 1.0.0 @@ -8423,14 +9033,14 @@ snapshots: git-semver-tags@5.0.1: dependencies: meow: 8.1.2 - semver: 7.6.3 + semver: 7.7.4 git-up@7.0.0: dependencies: - is-ssh: 1.4.0 + is-ssh: 1.4.1 parse-url: 8.1.0 - git-url-parse@13.1.0: + git-url-parse@14.0.0: dependencies: git-up: 7.0.0 @@ -8446,24 +9056,15 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.4.5: + glob@10.5.0: dependencies: - foreground-child: 3.3.0 + foreground-child: 3.3.1 jackspeak: 3.4.3 minimatch: 9.0.5 minipass: 7.1.2 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - glob@7.1.4: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.0.5 - once: 1.4.0 - path-is-absolute: 1.0.1 - glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -8473,14 +9074,6 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 - glob@8.1.0: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 5.1.6 - once: 1.4.0 - glob@9.3.5: dependencies: fs.realpath: 1.0.0 @@ -8488,8 +9081,6 @@ snapshots: minipass: 4.2.8 path-scurry: 1.11.1 - globals@11.12.0: {} - globals@13.24.0: dependencies: type-fest: 0.20.2 @@ -8508,41 +9099,10 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 - globby@14.0.2: - dependencies: - '@sindresorhus/merge-streams': 2.3.0 - fast-glob: 3.3.3 - ignore: 5.3.2 - path-type: 5.0.0 - slash: 5.1.0 - unicorn-magic: 0.1.0 - - gonzales-pe@4.3.0: - dependencies: - minimist: 1.2.8 - gopd@1.2.0: {} - got@12.6.1: - dependencies: - '@sindresorhus/is': 5.6.0 - '@szmarczak/http-timer': 5.0.1 - cacheable-lookup: 7.0.0 - cacheable-request: 10.2.14 - decompress-response: 6.0.0 - form-data-encoder: 2.1.4 - get-stream: 6.0.1 - http2-wrapper: 2.2.1 - lowercase-keys: 3.0.0 - p-cancelable: 3.0.0 - responselike: 3.0.0 - - graceful-fs@4.2.10: {} - graceful-fs@4.2.11: {} - grapheme-splitter@1.0.4: {} - graphemer@1.4.0: {} handlebars@4.7.8: @@ -8558,8 +9118,6 @@ snapshots: has-bigints@1.1.0: {} - has-flag@3.0.0: {} - has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -8584,39 +9142,29 @@ snapshots: hosted-git-info@2.8.9: {} - hosted-git-info@3.0.8: - dependencies: - lru-cache: 6.0.0 - hosted-git-info@4.1.0: dependencies: lru-cache: 6.0.0 - hosted-git-info@6.1.3: + hosted-git-info@7.0.2: dependencies: - lru-cache: 7.18.3 + lru-cache: 10.4.3 html-escaper@2.0.2: {} - http-cache-semantics@4.1.1: {} + http-cache-semantics@4.2.0: {} - http-proxy-agent@5.0.0: + http-proxy-agent@7.0.2: dependencies: - '@tootallnate/once': 2.0.0 - agent-base: 6.0.2 - debug: 4.4.0 + agent-base: 7.1.4 + debug: 4.4.3 transitivePeerDependencies: - supports-color - http2-wrapper@2.2.1: - dependencies: - quick-lru: 5.1.1 - resolve-alpn: 1.2.1 - - https-proxy-agent@5.0.1: + https-proxy-agent@7.0.6: dependencies: - agent-base: 6.0.2 - debug: 4.4.0 + agent-base: 7.1.4 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -8624,34 +9172,26 @@ snapshots: human-signals@4.3.1: {} - humanize-ms@1.2.1: - dependencies: - ms: 2.1.3 - husky@8.0.3: {} - iconv-lite@0.4.24: + iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 + optional: true - iconv-lite@0.6.3: + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 - optional: true ieee754@1.2.1: {} - ignore-walk@5.0.1: - dependencies: - minimatch: 5.1.6 - ignore-walk@6.0.5: dependencies: minimatch: 9.0.5 ignore@5.3.2: {} - import-fresh@3.3.0: + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 @@ -8661,17 +9201,10 @@ snapshots: pkg-dir: 4.2.0 resolve-cwd: 3.0.0 - import-local@3.2.0: - dependencies: - pkg-dir: 4.2.0 - resolve-cwd: 3.0.0 - imurmurhash@0.1.4: {} indent-string@4.0.0: {} - infer-owner@1.0.4: {} - inflight@1.0.6: dependencies: once: 1.4.0 @@ -8681,44 +9214,39 @@ snapshots: ini@1.3.8: {} - init-package-json@5.0.0: + ini@4.1.3: {} + + init-package-json@6.0.3: dependencies: - npm-package-arg: 10.1.0 + '@npmcli/package-json': 5.2.0 + npm-package-arg: 11.0.2 promzard: 1.0.2 - read: 2.1.0 - read-package-json: 6.0.4 - semver: 7.6.3 + read: 3.0.1 + semver: 7.7.4 validate-npm-package-license: 3.0.4 - validate-npm-package-name: 5.0.0 - - inquirer@12.3.2(@types/node@22.10.7): - dependencies: - '@inquirer/core': 10.1.4(@types/node@22.10.7) - '@inquirer/prompts': 7.2.3(@types/node@22.10.7) - '@inquirer/type': 3.0.2(@types/node@22.10.7) - '@types/node': 22.10.7 - ansi-escapes: 4.3.2 - mute-stream: 2.0.0 - run-async: 3.0.0 - rxjs: 7.8.1 + validate-npm-package-name: 5.0.1 + transitivePeerDependencies: + - bluebird - inquirer@8.2.6: + inquirer@8.2.7(@types/node@22.19.11): dependencies: + '@inquirer/external-editor': 1.0.3(@types/node@22.19.11) ansi-escapes: 4.3.2 chalk: 4.1.2 cli-cursor: 3.1.0 cli-width: 3.0.0 - external-editor: 3.1.0 figures: 3.2.0 - lodash: 4.17.21 + lodash: 4.17.23 mute-stream: 0.0.8 ora: 5.4.1 run-async: 2.4.1 - rxjs: 7.8.1 + rxjs: 7.8.2 string-width: 4.2.3 strip-ansi: 6.0.1 through: 2.3.8 wrap-ansi: 6.2.0 + transitivePeerDependencies: + - '@types/node' internal-slot@1.1.0: dependencies: @@ -8726,24 +9254,20 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 - ip-address@9.0.5: - dependencies: - jsbn: 1.1.0 - sprintf-js: 1.1.3 - - ip@2.0.1: {} + ip-address@10.1.0: {} is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 - call-bound: 1.0.3 - get-intrinsic: 1.2.7 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 is-arrayish@0.2.1: {} - is-async-function@2.1.0: + is-async-function@2.1.1: dependencies: - call-bound: 1.0.3 + async-function: 1.0.0 + call-bound: 1.0.4 get-proto: 1.0.1 has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 @@ -8752,14 +9276,14 @@ snapshots: dependencies: has-bigints: 1.1.0 - is-boolean-object@1.2.1: + is-boolean-object@1.2.2: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-bun-module@1.3.0: + is-bun-module@2.0.0: dependencies: - semver: 7.6.3 + semver: 7.7.4 is-callable@1.2.7: {} @@ -8773,13 +9297,13 @@ snapshots: is-data-view@1.0.2: dependencies: - call-bound: 1.0.3 - get-intrinsic: 1.2.7 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 is-typed-array: 1.1.15 is-date-object@1.1.0: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 has-tostringtag: 1.0.2 is-docker@2.2.1: {} @@ -8788,17 +9312,16 @@ snapshots: is-finalizationregistry@1.1.1: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 is-fullwidth-code-point@3.0.0: {} is-fullwidth-code-point@4.0.0: {} - is-generator-fn@2.1.0: {} - - is-generator-function@1.1.0: + is-generator-function@1.1.2: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 + generator-function: 2.0.1 get-proto: 1.0.1 has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 @@ -8815,482 +9338,147 @@ snapshots: is-map@2.0.3: {} + is-negative-zero@2.0.3: {} + is-number-object@1.1.1: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 has-tostringtag: 1.0.2 is-number@7.0.0: {} - is-obj@1.0.1: {} - - is-obj@2.0.0: {} - - is-path-inside@3.0.3: {} - - is-plain-obj@1.1.0: {} - - is-plain-object@2.0.4: - dependencies: - isobject: 3.0.1 - - is-plain-object@5.0.0: {} - - is-regex@1.2.1: - dependencies: - call-bound: 1.0.3 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - - is-regexp@1.0.0: {} - - is-relative-path@1.0.2: {} - - is-set@2.0.3: {} - - is-shared-array-buffer@1.0.4: - dependencies: - call-bound: 1.0.3 - - is-ssh@1.4.0: - dependencies: - protocols: 2.0.1 - - is-stream@2.0.0: {} - - is-stream@2.0.1: {} - - is-stream@3.0.0: {} - - is-string@1.1.1: - dependencies: - call-bound: 1.0.3 - has-tostringtag: 1.0.2 - - is-symbol@1.1.1: - dependencies: - call-bound: 1.0.3 - has-symbols: 1.1.0 - safe-regex-test: 1.1.0 - - is-text-path@1.0.1: - dependencies: - text-extensions: 1.9.0 - - is-typed-array@1.1.15: - dependencies: - which-typed-array: 1.1.18 - - is-unicode-supported@0.1.0: {} - - is-unicode-supported@1.3.0: {} - - is-unicode-supported@2.1.0: {} - - is-url-superb@4.0.0: {} - - is-url@1.2.4: {} - - is-weakmap@2.0.2: {} - - is-weakref@1.1.0: - dependencies: - call-bound: 1.0.3 - - is-weakset@2.0.4: - dependencies: - call-bound: 1.0.3 - get-intrinsic: 1.2.7 - - is-wsl@2.2.0: - dependencies: - is-docker: 2.2.1 - - isarray@1.0.0: {} - - isarray@2.0.5: {} - - isexe@2.0.0: {} - - isobject@3.0.1: {} - - istanbul-lib-coverage@3.2.2: {} - - istanbul-lib-instrument@5.2.1: - dependencies: - '@babel/core': 7.26.0 - '@babel/parser': 7.26.5 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-coverage: 3.2.2 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - istanbul-lib-instrument@6.0.3: - dependencies: - '@babel/core': 7.26.0 - '@babel/parser': 7.26.5 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-coverage: 3.2.2 - semver: 7.6.3 - transitivePeerDependencies: - - supports-color - - istanbul-lib-report@3.0.1: - dependencies: - istanbul-lib-coverage: 3.2.2 - make-dir: 4.0.0 - supports-color: 7.2.0 - - istanbul-lib-source-maps@4.0.1: - dependencies: - debug: 4.4.0 - istanbul-lib-coverage: 3.2.2 - source-map: 0.6.1 - transitivePeerDependencies: - - supports-color - - istanbul-lib-source-maps@5.0.6: - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - debug: 4.4.0 - istanbul-lib-coverage: 3.2.2 - transitivePeerDependencies: - - supports-color - - istanbul-reports@3.1.7: - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 - - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - - jake@10.9.2: - dependencies: - async: 3.2.6 - chalk: 4.1.2 - filelist: 1.0.4 - minimatch: 3.1.2 - - jest-changed-files@29.7.0: - dependencies: - execa: 5.1.1 - jest-util: 29.7.0 - p-limit: 3.1.0 - - jest-circus@29.7.0: - dependencies: - '@jest/environment': 29.7.0 - '@jest/expect': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.10.7 - chalk: 4.1.2 - co: 4.6.0 - dedent: 1.5.3 - is-generator-fn: 2.1.0 - jest-each: 29.7.0 - jest-matcher-utils: 29.7.0 - jest-message-util: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - p-limit: 3.1.0 - pretty-format: 29.7.0 - pure-rand: 6.1.0 - slash: 3.0.0 - stack-utils: 2.0.6 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-cli@29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)): - dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)) - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)) - exit: 0.1.2 - import-local: 3.2.0 - jest-config: 29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)) - jest-util: 29.7.0 - jest-validate: 29.7.0 - yargs: 17.7.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - jest-config@29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)): - dependencies: - '@babel/core': 7.26.0 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.0) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 22.10.7 - ts-node: 10.9.2(@types/node@22.10.7)(typescript@5.7.3) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-diff@29.7.0: + is-obj@2.0.0: {} + + is-path-inside@3.0.3: {} + + is-plain-obj@1.1.0: {} + + is-plain-object@2.0.4: dependencies: - chalk: 4.1.2 - diff-sequences: 29.6.3 - jest-get-type: 29.6.3 - pretty-format: 29.7.0 + isobject: 3.0.1 - jest-docblock@29.7.0: + is-regex@1.2.1: dependencies: - detect-newline: 3.1.0 + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} - jest-each@29.7.0: + is-shared-array-buffer@1.0.4: dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - jest-get-type: 29.6.3 - jest-util: 29.7.0 - pretty-format: 29.7.0 + call-bound: 1.0.4 - jest-environment-node@29.7.0: + is-ssh@1.4.1: dependencies: - '@jest/environment': 29.7.0 - '@jest/fake-timers': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.10.7 - jest-mock: 29.7.0 - jest-util: 29.7.0 + protocols: 2.0.2 - jest-get-type@29.6.3: {} + is-stream@2.0.0: {} - jest-haste-map@29.7.0: - dependencies: - '@jest/types': 29.6.3 - '@types/graceful-fs': 4.1.9 - '@types/node': 22.10.7 - anymatch: 3.1.3 - fb-watchman: 2.0.2 - graceful-fs: 4.2.11 - jest-regex-util: 29.6.3 - jest-util: 29.7.0 - jest-worker: 29.7.0 - micromatch: 4.0.8 - walker: 1.0.8 - optionalDependencies: - fsevents: 2.3.3 + is-stream@3.0.0: {} - jest-leak-detector@29.7.0: + is-string@1.1.1: dependencies: - jest-get-type: 29.6.3 - pretty-format: 29.7.0 + call-bound: 1.0.4 + has-tostringtag: 1.0.2 - jest-matcher-utils@29.7.0: + is-symbol@1.1.1: dependencies: - chalk: 4.1.2 - jest-diff: 29.7.0 - jest-get-type: 29.6.3 - pretty-format: 29.7.0 + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 - jest-message-util@29.7.0: + is-text-path@1.0.1: dependencies: - '@babel/code-frame': 7.26.2 - '@jest/types': 29.6.3 - '@types/stack-utils': 2.0.3 - chalk: 4.1.2 - graceful-fs: 4.2.11 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - stack-utils: 2.0.6 + text-extensions: 1.9.0 - jest-mock@29.7.0: + is-typed-array@1.1.15: dependencies: - '@jest/types': 29.6.3 - '@types/node': 22.10.7 - jest-util: 29.7.0 + which-typed-array: 1.1.20 - jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): - optionalDependencies: - jest-resolve: 29.7.0 + is-unicode-supported@0.1.0: {} + + is-unicode-supported@1.3.0: {} + + is-unicode-supported@2.1.0: {} - jest-regex-util@29.6.3: {} + is-weakmap@2.0.2: {} - jest-resolve-dependencies@29.7.0: + is-weakref@1.1.1: dependencies: - jest-regex-util: 29.6.3 - jest-snapshot: 29.7.0 - transitivePeerDependencies: - - supports-color + call-bound: 1.0.4 - jest-resolve@29.7.0: + is-weakset@2.0.4: dependencies: - chalk: 4.1.2 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) - jest-util: 29.7.0 - jest-validate: 29.7.0 - resolve: 1.22.10 - resolve.exports: 2.0.3 - slash: 3.0.0 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 - jest-runner@29.7.0: + is-wsl@2.2.0: dependencies: - '@jest/console': 29.7.0 - '@jest/environment': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.10.7 - chalk: 4.1.2 - emittery: 0.13.1 - graceful-fs: 4.2.11 - jest-docblock: 29.7.0 - jest-environment-node: 29.7.0 - jest-haste-map: 29.7.0 - jest-leak-detector: 29.7.0 - jest-message-util: 29.7.0 - jest-resolve: 29.7.0 - jest-runtime: 29.7.0 - jest-util: 29.7.0 - jest-watcher: 29.7.0 - jest-worker: 29.7.0 - p-limit: 3.1.0 - source-map-support: 0.5.13 - transitivePeerDependencies: - - supports-color + is-docker: 2.2.1 + + isarray@1.0.0: {} + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + isexe@3.1.5: {} + + isobject@3.0.1: {} - jest-runtime@29.7.0: + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: dependencies: - '@jest/environment': 29.7.0 - '@jest/fake-timers': 29.7.0 - '@jest/globals': 29.7.0 - '@jest/source-map': 29.6.3 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.10.7 - chalk: 4.1.2 - cjs-module-lexer: 1.4.1 - collect-v8-coverage: 1.0.2 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-mock: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - slash: 3.0.0 - strip-bom: 4.0.0 - transitivePeerDependencies: - - supports-color + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 - jest-snapshot@29.7.0: - dependencies: - '@babel/core': 7.26.0 - '@babel/generator': 7.26.5 - '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) - '@babel/types': 7.26.5 - '@jest/expect-utils': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.0) - chalk: 4.1.2 - expect: 29.7.0 - graceful-fs: 4.2.11 - jest-diff: 29.7.0 - jest-get-type: 29.6.3 - jest-matcher-utils: 29.7.0 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - natural-compare: 1.4.0 - pretty-format: 29.7.0 - semver: 7.6.3 + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color - jest-util@29.7.0: + istanbul-reports@3.2.0: dependencies: - '@jest/types': 29.6.3 - '@types/node': 22.10.7 - chalk: 4.1.2 - ci-info: 3.9.0 - graceful-fs: 4.2.11 - picomatch: 2.3.1 + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 - jest-validate@29.7.0: + jackspeak@3.4.3: dependencies: - '@jest/types': 29.6.3 - camelcase: 6.3.0 - chalk: 4.1.2 - jest-get-type: 29.6.3 - leven: 3.1.0 - pretty-format: 29.7.0 + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 - jest-watcher@29.7.0: + jake@10.9.4: dependencies: - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.10.7 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - emittery: 0.13.1 - jest-util: 29.7.0 - string-length: 4.0.2 + async: 3.2.6 + filelist: 1.0.4 + picocolors: 1.1.1 - jest-worker@29.7.0: + jest-diff@29.7.0: dependencies: - '@types/node': 22.10.7 - jest-util: 29.7.0 - merge-stream: 2.0.0 - supports-color: 8.1.1 + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 - jest@29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)): - dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)) - '@jest/types': 29.6.3 - import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node + jest-get-type@29.6.3: {} - jju@1.4.0: {} + js-levenshtein@1.1.6: {} + + js-tokens@10.0.0: {} js-tokens@4.0.0: {} - js-yaml@3.14.1: + js-tokens@9.0.1: {} + + js-yaml@3.14.2: dependencies: argparse: 1.0.10 esprima: 4.0.1 @@ -9299,9 +9487,9 @@ snapshots: dependencies: argparse: 2.0.1 - jsbn@1.1.0: {} - - jsesc@3.1.0: {} + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 json-buffer@3.0.1: {} @@ -9311,34 +9499,17 @@ snapshots: json-parse-even-better-errors@3.0.2: {} - json-schema-diff@0.18.1: - dependencies: - ajv: 8.17.1 - commander: 10.0.1 - convict: 6.2.4 - json-schema-ref-parser: 9.0.9 - json-schema-spec-types: 0.1.2 - lodash: 4.17.21 - verror: 1.10.1 - - json-schema-ref-parser@9.0.9: - dependencies: - '@apidevtools/json-schema-ref-parser': 9.0.9 - - json-schema-spec-types@0.1.2: {} - json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} + json-schema-typed@8.0.2: {} + json-stable-stringify-without-jsonify@1.0.1: {} - json-stringify-safe@5.0.1: {} + json-stringify-nice@1.1.4: {} - json-to-ast@2.1.0: - dependencies: - code-error-fragment: 0.0.230 - grapheme-splitter: 1.0.4 + json-stringify-safe@5.0.1: {} json5@1.0.2: dependencies: @@ -9348,11 +9519,7 @@ snapshots: jsonc-parser@3.2.0: {} - jsonfile@4.0.0: - optionalDependencies: - graceful-fs: 4.2.11 - - jsonfile@6.1.0: + jsonfile@6.2.0: dependencies: universalify: 2.0.1 optionalDependencies: @@ -9360,7 +9527,9 @@ snapshots: jsonparse@1.3.1: {} - jsonpointer@5.0.1: {} + just-diff-apply@5.5.0: {} + + just-diff@6.0.2: {} keyv@4.5.4: dependencies: @@ -9368,116 +9537,118 @@ snapshots: kind-of@6.0.3: {} - kleur@3.0.3: {} - - lerna@7.4.2(encoding@0.1.13): + lerna@8.2.4(@types/node@22.19.11)(encoding@0.1.13): dependencies: - '@lerna/child-process': 7.4.2 - '@lerna/create': 7.4.2(encoding@0.1.13)(typescript@5.7.3) - '@npmcli/run-script': 6.0.2 - '@nx/devkit': 16.10.0(nx@16.10.0) + '@lerna/create': 8.2.4(@types/node@22.19.11)(encoding@0.1.13)(typescript@5.7.3) + '@npmcli/arborist': 7.5.4 + '@npmcli/package-json': 5.2.0 + '@npmcli/run-script': 8.1.0 + '@nx/devkit': 20.8.2(nx@20.8.2) '@octokit/plugin-enterprise-rest': 6.0.1 - '@octokit/rest': 19.0.11(encoding@0.1.13) + '@octokit/rest': 20.1.2 + aproba: 2.0.0 byte-size: 8.1.1 chalk: 4.1.0 clone-deep: 4.0.1 - cmd-shim: 6.0.1 + cmd-shim: 6.0.3 + color-support: 1.1.3 columnify: 1.6.0 + console-control-strings: 1.1.0 conventional-changelog-angular: 7.0.0 conventional-changelog-core: 5.0.1 conventional-recommended-bump: 7.0.1 - cosmiconfig: 8.3.6(typescript@5.7.3) - dedent: 0.7.0 - envinfo: 7.8.1 + cosmiconfig: 9.0.0(typescript@5.7.3) + dedent: 1.5.3 + envinfo: 7.13.0 execa: 5.0.0 - fs-extra: 11.3.0 + fs-extra: 11.3.3 get-port: 5.1.1 get-stream: 6.0.0 - git-url-parse: 13.1.0 - glob-parent: 5.1.2 - globby: 11.1.0 + git-url-parse: 14.0.0 + glob-parent: 6.0.2 graceful-fs: 4.2.11 has-unicode: 2.0.1 import-local: 3.1.0 ini: 1.3.8 - init-package-json: 5.0.0 - inquirer: 8.2.6 + init-package-json: 6.0.3 + inquirer: 8.2.7(@types/node@22.19.11) is-ci: 3.0.1 is-stream: 2.0.0 jest-diff: 29.7.0 js-yaml: 4.1.0 - libnpmaccess: 7.0.2 - libnpmpublish: 7.3.0 + libnpmaccess: 8.0.6 + libnpmpublish: 9.0.9 load-json-file: 6.2.0 - lodash: 4.17.21 make-dir: 4.0.0 minimatch: 3.0.5 multimatch: 5.0.0 node-fetch: 2.6.7(encoding@0.1.13) - npm-package-arg: 8.1.1 - npm-packlist: 5.1.1 - npm-registry-fetch: 14.0.5 - npmlog: 6.0.2 - nx: 16.10.0 + npm-package-arg: 11.0.2 + npm-packlist: 8.0.2 + npm-registry-fetch: 17.1.0 + nx: 20.8.2 p-map: 4.0.0 p-map-series: 2.1.0 p-pipe: 3.1.0 p-queue: 6.6.2 p-reduce: 2.1.0 p-waterfall: 2.1.1 - pacote: 15.2.0 + pacote: 18.0.6 pify: 5.0.0 read-cmd-shim: 4.0.0 - read-package-json: 6.0.4 resolve-from: 5.0.0 rimraf: 4.4.1 - semver: 7.6.3 + semver: 7.7.4 + set-blocking: 2.0.0 signal-exit: 3.0.7 slash: 3.0.0 - ssri: 9.0.1 - strong-log-transformer: 2.1.0 - tar: 6.1.11 + ssri: 10.0.6 + string-width: 4.2.3 + tar: 6.2.1 temp-dir: 1.0.0 + through: 2.3.8 + tinyglobby: 0.2.12 typescript: 5.7.3 upath: 2.0.1 - uuid: 9.0.1 + uuid: 10.0.0 validate-npm-package-license: 3.0.4 - validate-npm-package-name: 5.0.0 + validate-npm-package-name: 5.0.1 + wide-align: 1.1.5 write-file-atomic: 5.0.1 write-pkg: 4.0.0 - yargs: 16.2.0 - yargs-parser: 20.2.4 + yargs: 17.7.2 + yargs-parser: 21.1.1 transitivePeerDependencies: - '@swc-node/register' - '@swc/core' + - '@types/node' + - babel-plugin-macros - bluebird - debug - encoding - supports-color - leven@3.1.0: {} - levn@0.4.1: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 - libnpmaccess@7.0.2: + libnpmaccess@8.0.6: dependencies: - npm-package-arg: 10.1.0 - npm-registry-fetch: 14.0.5 + npm-package-arg: 11.0.2 + npm-registry-fetch: 17.1.0 transitivePeerDependencies: - supports-color - libnpmpublish@7.3.0: + libnpmpublish@9.0.9: dependencies: - ci-info: 3.9.0 - normalize-package-data: 5.0.0 - npm-package-arg: 10.1.0 - npm-registry-fetch: 14.0.5 - proc-log: 3.0.0 - semver: 7.6.3 - sigstore: 1.9.0 + ci-info: 4.4.0 + normalize-package-data: 6.0.2 + npm-package-arg: 11.0.2 + npm-registry-fetch: 17.1.0 + proc-log: 4.2.0 + semver: 7.7.4 + sigstore: 2.3.1 ssri: 10.0.6 transitivePeerDependencies: - supports-color @@ -9486,7 +9657,7 @@ snapshots: lines-and-columns@1.2.4: {} - lines-and-columns@2.0.4: {} + lines-and-columns@2.0.3: {} lint-staged@14.0.1(enquirer@2.3.6): dependencies: @@ -9508,7 +9679,7 @@ snapshots: dependencies: cli-truncate: 3.1.0 colorette: 2.0.20 - eventemitter3: 5.0.1 + eventemitter3: 5.0.4 log-update: 5.0.1 rfdc: 1.4.1 wrap-ansi: 8.1.0 @@ -9542,21 +9713,11 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash.clonedeep@4.5.0: {} - - lodash.get@4.4.2: {} - - lodash.isequal@4.5.0: {} - lodash.ismatch@4.4.0: {} - lodash.memoize@4.1.2: {} - lodash.merge@4.6.2: {} - lodash.truncate@4.4.2: {} - - lodash@4.17.21: {} + lodash@4.17.23: {} log-symbols@4.1.0: dependencies: @@ -9565,7 +9726,7 @@ snapshots: log-symbols@6.0.0: dependencies: - chalk: 5.4.1 + chalk: 5.6.2 is-unicode-supported: 1.3.0 log-update@5.0.1: @@ -9573,58 +9734,25 @@ snapshots: ansi-escapes: 5.0.0 cli-cursor: 4.0.0 slice-ansi: 5.0.0 - strip-ansi: 7.1.0 + strip-ansi: 7.1.2 wrap-ansi: 8.1.0 - loupe@3.1.2: {} - - lowercase-keys@3.0.0: {} + loupe@3.2.1: {} lru-cache@10.4.3: {} - lru-cache@4.1.5: - dependencies: - pseudomap: 1.0.2 - yallist: 2.1.2 - - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - lru-cache@6.0.0: dependencies: yallist: 4.0.0 - lru-cache@7.18.3: {} - - madge@7.0.0(typescript@5.7.3): - dependencies: - chalk: 4.1.2 - commander: 7.2.0 - commondir: 1.0.1 - debug: 4.4.0 - dependency-tree: 10.0.9 - ora: 5.4.1 - pluralize: 8.0.0 - precinct: 11.0.5 - pretty-ms: 7.0.1 - rc: 1.2.8 - stream-to-array: 2.3.0 - ts-graphviz: 1.8.2 - walkdir: 0.4.1 - optionalDependencies: - typescript: 5.7.3 - transitivePeerDependencies: - - supports-color - - magic-string@0.30.17: + magic-string@0.30.21: dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.5 magicast@0.3.5: dependencies: - '@babel/parser': 7.26.5 - '@babel/types': 7.26.5 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 source-map-js: 1.2.1 make-dir@2.1.0: @@ -9634,56 +9762,25 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.6.3 - - make-error@1.3.6: {} - - make-fetch-happen@10.2.1: - dependencies: - agentkeepalive: 4.6.0 - cacache: 16.1.3 - http-cache-semantics: 4.1.1 - http-proxy-agent: 5.0.0 - https-proxy-agent: 5.0.1 - is-lambda: 1.0.1 - lru-cache: 7.18.3 - minipass: 3.3.6 - minipass-collect: 1.0.2 - minipass-fetch: 2.1.2 - minipass-flush: 1.0.5 - minipass-pipeline: 1.2.4 - negotiator: 0.6.4 - promise-retry: 2.0.1 - socks-proxy-agent: 7.0.0 - ssri: 9.0.1 - transitivePeerDependencies: - - bluebird - - supports-color + semver: 7.7.4 - make-fetch-happen@11.1.1: + make-fetch-happen@13.0.1: dependencies: - agentkeepalive: 4.6.0 - cacache: 17.1.4 - http-cache-semantics: 4.1.1 - http-proxy-agent: 5.0.0 - https-proxy-agent: 5.0.1 + '@npmcli/agent': 2.2.2 + cacache: 18.0.4 + http-cache-semantics: 4.2.0 is-lambda: 1.0.1 - lru-cache: 7.18.3 - minipass: 5.0.0 + minipass: 7.1.2 minipass-fetch: 3.0.5 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 negotiator: 0.6.4 + proc-log: 4.2.0 promise-retry: 2.0.1 - socks-proxy-agent: 7.0.0 ssri: 10.0.6 transitivePeerDependencies: - supports-color - makeerror@1.0.12: - dependencies: - tmpl: 1.0.5 - map-obj@1.0.1: {} map-obj@4.3.0: {} @@ -9730,35 +9827,31 @@ snapshots: mimic-function@5.0.1: {} - mimic-response@3.1.0: {} - - mimic-response@4.0.0: {} - min-indent@1.0.1: {} minimatch@3.0.5: dependencies: - brace-expansion: 1.1.11 + brace-expansion: 1.1.12 minimatch@3.1.2: dependencies: - brace-expansion: 1.1.11 + brace-expansion: 1.1.12 minimatch@5.1.6: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minimatch@8.0.4: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minimatch@9.0.3: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minimatch@9.0.5: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minimist-options@4.1.0: dependencies: @@ -9768,17 +9861,9 @@ snapshots: minimist@1.2.8: {} - minipass-collect@1.0.2: + minipass-collect@2.0.1: dependencies: - minipass: 3.3.6 - - minipass-fetch@2.1.2: - dependencies: - minipass: 3.3.6 - minipass-sized: 1.0.3 - minizlib: 2.1.2 - optionalDependencies: - encoding: 0.1.13 + minipass: 7.1.2 minipass-fetch@3.0.5: dependencies: @@ -9792,11 +9877,6 @@ snapshots: dependencies: minipass: 3.3.6 - minipass-json-stream@1.0.2: - dependencies: - jsonparse: 1.3.1 - minipass: 3.3.6 - minipass-pipeline@1.2.4: dependencies: minipass: 3.3.6 @@ -9824,18 +9904,6 @@ snapshots: modify-values@1.0.1: {} - module-definition@5.0.1: - dependencies: - ast-module-types: 5.0.0 - node-source-walk: 6.0.2 - - module-lookup-amd@8.0.5: - dependencies: - commander: 10.0.1 - glob: 7.2.3 - requirejs: 2.3.7 - requirejs-config-file: 4.0.0 - ms@2.1.2: {} ms@2.1.3: {} @@ -9846,17 +9914,17 @@ snapshots: array-differ: 3.0.0 array-union: 2.1.0 arrify: 2.0.1 - minimatch: 3.0.5 - - mustache@4.2.0: {} + minimatch: 3.1.2 mute-stream@0.0.8: {} mute-stream@1.0.0: {} - mute-stream@2.0.0: {} + mute-stream@3.0.0: {} + + nanoid@3.3.11: {} - nanoid@3.3.8: {} + napi-postinstall@0.3.4: {} natural-compare@1.4.0: {} @@ -9864,51 +9932,37 @@ snapshots: neo-async@2.6.2: {} - node-addon-api@3.2.1: {} - node-fetch@2.6.7(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 optionalDependencies: encoding: 0.1.13 - node-gyp-build@4.8.4: {} - - node-gyp@9.4.1: + node-gyp@10.3.1: dependencies: env-paths: 2.2.1 - exponential-backoff: 3.1.1 - glob: 7.2.3 + exponential-backoff: 3.1.3 + glob: 10.5.0 graceful-fs: 4.2.11 - make-fetch-happen: 10.2.1 - nopt: 6.0.0 - npmlog: 6.0.2 - rimraf: 3.0.2 - semver: 7.6.3 - tar: 6.2.0 - which: 2.0.2 + make-fetch-happen: 13.0.1 + nopt: 7.2.1 + proc-log: 4.2.0 + semver: 7.7.4 + tar: 6.2.1 + which: 4.0.0 transitivePeerDependencies: - - bluebird - supports-color - node-int64@0.4.0: {} - node-machine-id@1.1.12: {} - node-releases@2.0.19: {} - - node-source-walk@6.0.2: - dependencies: - '@babel/parser': 7.26.5 - - nopt@6.0.0: + nopt@7.2.1: dependencies: - abbrev: 1.1.1 + abbrev: 2.0.0 normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.10 + resolve: 1.22.11 semver: 5.7.2 validate-npm-package-license: 3.0.4 @@ -9916,76 +9970,53 @@ snapshots: dependencies: hosted-git-info: 4.1.0 is-core-module: 2.16.1 - semver: 7.6.3 + semver: 7.7.4 validate-npm-package-license: 3.0.4 - normalize-package-data@5.0.0: + normalize-package-data@6.0.2: dependencies: - hosted-git-info: 6.1.3 - is-core-module: 2.16.1 - semver: 7.6.3 + hosted-git-info: 7.0.2 + semver: 7.7.4 validate-npm-package-license: 3.0.4 - normalize-path@3.0.0: {} - - normalize-url@8.0.1: {} - - npm-bundled@1.1.2: - dependencies: - npm-normalize-package-bin: 1.0.1 - npm-bundled@3.0.1: dependencies: npm-normalize-package-bin: 3.0.1 npm-install-checks@6.3.0: dependencies: - semver: 7.6.3 - - npm-normalize-package-bin@1.0.1: {} + semver: 7.7.4 npm-normalize-package-bin@3.0.1: {} - npm-package-arg@10.1.0: - dependencies: - hosted-git-info: 6.1.3 - proc-log: 3.0.0 - semver: 7.6.3 - validate-npm-package-name: 5.0.0 - - npm-package-arg@8.1.1: + npm-package-arg@11.0.2: dependencies: - hosted-git-info: 3.0.8 - semver: 7.6.3 - validate-npm-package-name: 3.0.0 + hosted-git-info: 7.0.2 + proc-log: 4.2.0 + semver: 7.7.4 + validate-npm-package-name: 5.0.1 - npm-packlist@5.1.1: - dependencies: - glob: 8.1.0 - ignore-walk: 5.0.1 - npm-bundled: 1.1.2 - npm-normalize-package-bin: 1.0.1 - - npm-packlist@7.0.4: + npm-packlist@8.0.2: dependencies: ignore-walk: 6.0.5 - npm-pick-manifest@8.0.2: + npm-pick-manifest@9.1.0: dependencies: npm-install-checks: 6.3.0 npm-normalize-package-bin: 3.0.1 - npm-package-arg: 10.1.0 - semver: 7.6.3 + npm-package-arg: 11.0.2 + semver: 7.7.4 - npm-registry-fetch@14.0.5: + npm-registry-fetch@17.1.0: dependencies: - make-fetch-happen: 11.1.1 - minipass: 5.0.0 + '@npmcli/redact': 2.0.1 + jsonparse: 1.3.1 + make-fetch-happen: 13.0.1 + minipass: 7.1.2 minipass-fetch: 3.0.5 - minipass-json-stream: 1.0.2 minizlib: 2.1.2 - npm-package-arg: 10.1.0 - proc-log: 3.0.0 + npm-package-arg: 11.0.2 + proc-log: 4.2.0 transitivePeerDependencies: - supports-color @@ -9995,75 +10026,66 @@ snapshots: npm-run-path@5.3.0: dependencies: - path-key: 4.0.0 - - npmlog@6.0.2: - dependencies: - are-we-there-yet: 3.0.1 - console-control-strings: 1.1.0 - gauge: 4.0.4 - set-blocking: 2.0.0 + path-key: 4.0.0 - nx@16.10.0: + nx@20.8.2: dependencies: - '@nrwl/tao': 16.10.0 - '@parcel/watcher': 2.0.4 + '@napi-rs/wasm-runtime': 0.2.4 '@yarnpkg/lockfile': 1.1.0 - '@yarnpkg/parsers': 3.0.0-rc.46 - '@zkochan/js-yaml': 0.0.6 - axios: 1.7.9 + '@yarnpkg/parsers': 3.0.2 + '@zkochan/js-yaml': 0.0.7 + axios: 1.13.5 chalk: 4.1.2 cli-cursor: 3.1.0 cli-spinners: 2.6.1 cliui: 8.0.1 - dotenv: 16.3.2 - dotenv-expand: 10.0.0 + dotenv: 16.4.7 + dotenv-expand: 11.0.7 enquirer: 2.3.6 figures: 3.2.0 flat: 5.0.2 - fs-extra: 11.3.0 - glob: 7.1.4 + front-matter: 4.0.2 ignore: 5.3.2 jest-diff: 29.7.0 - js-yaml: 4.1.0 jsonc-parser: 3.2.0 - lines-and-columns: 2.0.4 - minimatch: 3.0.5 + lines-and-columns: 2.0.3 + minimatch: 9.0.3 node-machine-id: 1.1.12 npm-run-path: 4.0.1 open: 8.4.2 - semver: 7.5.3 + ora: 5.3.0 + resolve.exports: 2.0.3 + semver: 7.7.4 string-width: 4.2.3 - strong-log-transformer: 2.1.0 tar-stream: 2.2.0 - tmp: 0.2.3 + tmp: 0.2.5 tsconfig-paths: 4.2.0 tslib: 2.8.1 - v8-compile-cache: 2.3.0 + yaml: 2.8.2 yargs: 17.7.2 yargs-parser: 21.1.1 optionalDependencies: - '@nx/nx-darwin-arm64': 16.10.0 - '@nx/nx-darwin-x64': 16.10.0 - '@nx/nx-freebsd-x64': 16.10.0 - '@nx/nx-linux-arm-gnueabihf': 16.10.0 - '@nx/nx-linux-arm64-gnu': 16.10.0 - '@nx/nx-linux-arm64-musl': 16.10.0 - '@nx/nx-linux-x64-gnu': 16.10.0 - '@nx/nx-linux-x64-musl': 16.10.0 - '@nx/nx-win32-arm64-msvc': 16.10.0 - '@nx/nx-win32-x64-msvc': 16.10.0 + '@nx/nx-darwin-arm64': 20.8.2 + '@nx/nx-darwin-x64': 20.8.2 + '@nx/nx-freebsd-x64': 20.8.2 + '@nx/nx-linux-arm-gnueabihf': 20.8.2 + '@nx/nx-linux-arm64-gnu': 20.8.2 + '@nx/nx-linux-arm64-musl': 20.8.2 + '@nx/nx-linux-x64-gnu': 20.8.2 + '@nx/nx-linux-x64-musl': 20.8.2 + '@nx/nx-win32-arm64-msvc': 20.8.2 + '@nx/nx-win32-x64-msvc': 20.8.2 transitivePeerDependencies: - debug - object-inspect@1.13.3: {} + object-inspect@1.13.4: {} object-keys@1.1.1: {} object.assign@4.1.7: dependencies: call-bind: 1.0.8 - call-bound: 1.0.3 + call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 has-symbols: 1.1.0 @@ -10073,19 +10095,19 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.1 es-object-atoms: 1.1.1 object.groupby@1.0.3: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.1 object.values@1.2.1: dependencies: call-bind: 1.0.8 - call-bound: 1.0.3 + call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 @@ -10111,57 +10133,6 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 - openapi-diff@0.23.7(openapi-types@12.1.3): - dependencies: - axios: 1.7.9 - commander: 8.3.0 - js-yaml: 4.1.0 - json-schema-diff: 0.18.1 - jsonpointer: 5.0.1 - lodash: 4.17.21 - openapi3-ts: 2.0.2 - swagger-parser: 10.0.3(openapi-types@12.1.3) - verror: 1.10.1 - transitivePeerDependencies: - - debug - - openapi-types - - openapi-types@12.1.3: {} - - openapi-zod-client@1.18.2: - dependencies: - '@apidevtools/swagger-parser': 10.1.1(openapi-types@12.1.3) - '@liuli-util/fs-extra': 0.1.0 - '@zodios/core': 10.9.6(axios@1.7.9)(zod@3.24.1) - axios: 1.7.9 - cac: 6.7.14 - handlebars: 4.7.8 - openapi-types: 12.1.3 - openapi3-ts: 3.1.0 - pastable: 2.2.1 - prettier: 2.8.8 - tanu: 0.1.13 - ts-pattern: 5.6.2 - whence: 2.0.1 - zod: 3.24.1 - transitivePeerDependencies: - - debug - - react - - supports-color - - xstate - - openapi3-ts@2.0.2: - dependencies: - yaml: 1.10.2 - - openapi3-ts@3.1.0: - dependencies: - yaml: 2.7.0 - - openapi3-ts@4.4.0: - dependencies: - yaml: 2.7.0 - optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -10171,6 +10142,17 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + ora@5.3.0: + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + ora@5.4.1: dependencies: bl: 4.1.0 @@ -10183,9 +10165,9 @@ snapshots: strip-ansi: 6.0.1 wcwidth: 1.0.1 - ora@8.1.1: + ora@8.2.0: dependencies: - chalk: 5.4.1 + chalk: 5.6.2 cli-cursor: 5.0.0 cli-spinners: 2.9.2 is-interactive: 2.0.0 @@ -10193,18 +10175,14 @@ snapshots: log-symbols: 6.0.0 stdin-discarder: 0.2.2 string-width: 7.2.0 - strip-ansi: 7.1.0 - - os-tmpdir@1.0.2: {} + strip-ansi: 7.1.2 own-keys@1.0.1: dependencies: - get-intrinsic: 1.2.7 + get-intrinsic: 1.3.0 object-keys: 1.1.1 safe-push-apply: 1.0.0 - p-cancelable@3.0.0: {} - p-finally@1.0.0: {} p-limit@1.3.0: @@ -10260,33 +10238,25 @@ snapshots: package-json-from-dist@1.0.1: {} - package-json@8.1.1: - dependencies: - got: 12.6.1 - registry-auth-token: 5.0.3 - registry-url: 6.0.1 - semver: 7.6.3 - - pacote@15.2.0: + pacote@18.0.6: dependencies: - '@npmcli/git': 4.1.0 + '@npmcli/git': 5.0.8 '@npmcli/installed-package-contents': 2.1.0 - '@npmcli/promise-spawn': 6.0.2 - '@npmcli/run-script': 6.0.2 - cacache: 17.1.4 + '@npmcli/package-json': 5.2.0 + '@npmcli/promise-spawn': 7.0.2 + '@npmcli/run-script': 8.1.0 + cacache: 18.0.4 fs-minipass: 3.0.3 - minipass: 5.0.0 - npm-package-arg: 10.1.0 - npm-packlist: 7.0.4 - npm-pick-manifest: 8.0.2 - npm-registry-fetch: 14.0.5 - proc-log: 3.0.0 + minipass: 7.1.2 + npm-package-arg: 11.0.2 + npm-packlist: 8.0.2 + npm-pick-manifest: 9.1.0 + npm-registry-fetch: 17.1.0 + proc-log: 4.2.0 promise-retry: 2.0.1 - read-package-json: 6.0.4 - read-package-json-fast: 3.0.2 - sigstore: 1.9.0 + sigstore: 2.3.1 ssri: 10.0.6 - tar: 6.2.0 + tar: 6.2.1 transitivePeerDependencies: - bluebird - supports-color @@ -10295,37 +10265,31 @@ snapshots: dependencies: callsites: 3.1.0 - parse-github-url@1.0.3: {} + parse-conflict-json@3.0.1: + dependencies: + json-parse-even-better-errors: 3.0.2 + just-diff: 6.0.2 + just-diff-apply: 5.5.0 parse-json@4.0.0: dependencies: - error-ex: 1.3.2 + error-ex: 1.3.4 json-parse-better-errors: 1.0.2 parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.26.2 - error-ex: 1.3.2 + '@babel/code-frame': 7.29.0 + error-ex: 1.3.4 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - parse-ms@2.1.0: {} - - parse-path@7.0.0: + parse-path@7.1.0: dependencies: - protocols: 2.0.1 + protocols: 2.0.2 parse-url@8.1.0: dependencies: - parse-path: 7.0.0 - - pastable@2.2.1: - dependencies: - '@babel/core': 7.26.0 - ts-toolbelt: 9.6.0 - type-fest: 3.13.1 - transitivePeerDependencies: - - supports-color + parse-path: 7.1.0 path-exists@3.0.0: {} @@ -10350,16 +10314,16 @@ snapshots: path-type@4.0.0: {} - path-type@5.0.0: {} + pathe@2.0.3: {} - pathe@2.0.2: {} - - pathval@2.0.0: {} + pathval@2.0.1: {} picocolors@1.1.1: {} picomatch@2.3.1: {} + picomatch@4.0.3: {} + pidtree@0.6.0: {} pify@2.3.0: {} @@ -10370,57 +10334,32 @@ snapshots: pify@5.0.0: {} - pirates@4.0.6: {} - pkg-dir@4.2.0: dependencies: find-up: 4.1.0 pluralize@8.0.0: {} - possible-typed-array-names@1.0.0: {} + possible-typed-array-names@1.1.0: {} - postcss-values-parser@6.0.2(postcss@8.5.1): + postcss-selector-parser@6.1.2: dependencies: - color-name: 1.1.4 - is-url-superb: 4.0.0 - postcss: 8.5.1 - quote-unquote: 1.0.0 + cssesc: 3.0.0 + util-deprecate: 1.0.2 - postcss@8.5.1: + postcss@8.5.6: dependencies: - nanoid: 3.3.8 + nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 - precinct@11.0.5: - dependencies: - '@dependents/detective-less': 4.1.0 - commander: 10.0.1 - detective-amd: 5.0.2 - detective-cjs: 5.0.1 - detective-es6: 4.0.1 - detective-postcss: 6.1.3 - detective-sass: 5.0.3 - detective-scss: 4.0.3 - detective-stylus: 4.0.0 - detective-typescript: 11.2.0 - module-definition: 5.0.1 - node-source-walk: 6.0.2 - transitivePeerDependencies: - - supports-color - prelude-ls@1.2.1: {} - prettier-linter-helpers@1.0.0: + prettier-linter-helpers@1.0.1: dependencies: fast-diff: 1.3.0 - prettier@2.8.8: {} - - prettier@3.3.3: {} - - prettier@3.4.2: {} + prettier@3.8.1: {} pretty-format@29.7.0: dependencies: @@ -10428,14 +10367,16 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 - pretty-ms@7.0.1: - dependencies: - parse-ms: 2.1.0 - - proc-log@3.0.0: {} + proc-log@4.2.0: {} process-nextick-args@2.0.1: {} + proggy@2.0.0: {} + + promise-all-reject-late@1.0.1: {} + + promise-call-limit@3.0.2: {} + promise-inflight@1.0.1: {} promise-retry@2.0.1: @@ -10443,42 +10384,20 @@ snapshots: err-code: 2.0.3 retry: 0.12.0 - prompts@2.4.2: - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 - promzard@1.0.2: dependencies: read: 3.0.1 - proto-list@1.2.4: {} - - protocols@2.0.1: {} + protocols@2.0.2: {} proxy-from-env@1.1.0: {} - pseudomap@1.0.2: {} - punycode@2.3.1: {} - pure-rand@6.1.0: {} - queue-microtask@1.2.3: {} quick-lru@4.0.1: {} - quick-lru@5.1.1: {} - - quote-unquote@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 - react-is@18.3.1: {} read-cmd-shim@4.0.0: {} @@ -10488,13 +10407,6 @@ snapshots: json-parse-even-better-errors: 3.0.2 npm-normalize-package-bin: 3.0.1 - read-package-json@6.0.4: - dependencies: - glob: 10.4.5 - json-parse-even-better-errors: 3.0.2 - normalize-package-data: 5.0.0 - npm-normalize-package-bin: 3.0.1 - read-pkg-up@3.0.0: dependencies: find-up: 2.1.0 @@ -10519,17 +10431,13 @@ snapshots: parse-json: 5.2.0 type-fest: 0.6.0 - read@2.1.0: - dependencies: - mute-stream: 1.0.0 - read@3.0.1: dependencies: mute-stream: 1.0.0 readable-stream@2.3.8: dependencies: - core-util-is: 1.0.3 + core-util-is: 1.0.2 inherits: 2.0.4 isarray: 1.0.0 process-nextick-args: 2.0.1 @@ -10552,15 +10460,13 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.1 es-errors: 1.3.0 es-object-atoms: 1.1.1 - get-intrinsic: 1.2.7 + get-intrinsic: 1.3.0 get-proto: 1.0.1 which-builtin-type: 1.2.1 - regenerator-runtime@0.14.1: {} - regexp.prototype.flags@1.5.4: dependencies: call-bind: 1.0.8 @@ -10570,33 +10476,14 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 - registry-auth-token@5.0.3: - dependencies: - '@pnpm/npm-conf': 2.3.1 - - registry-url@6.0.1: - dependencies: - rc: 1.2.8 - require-directory@2.1.1: {} require-from-string@2.0.2: {} - requirejs-config-file@4.0.0: - dependencies: - esprima: 4.0.1 - stringify-object: 3.3.0 - - requirejs@2.3.7: {} - - resolve-alpn@1.2.1: {} - resolve-cwd@3.0.0: dependencies: resolve-from: 5.0.0 - resolve-dependency-path@3.0.2: {} - resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -10605,16 +10492,12 @@ snapshots: resolve.exports@2.0.3: {} - resolve@1.22.10: + resolve@1.22.11: dependencies: is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - responselike@3.0.0: - dependencies: - lowercase-keys: 3.0.0 - restore-cursor@3.1.0: dependencies: onetime: 5.1.2 @@ -10632,7 +10515,7 @@ snapshots: retry@0.12.0: {} - reusify@1.0.4: {} + reusify@1.1.0: {} rfdc@1.4.1: {} @@ -10646,50 +10529,54 @@ snapshots: rimraf@5.0.10: dependencies: - glob: 10.4.5 + glob: 10.5.0 - rollup@4.31.0: + rollup@4.57.1: dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.31.0 - '@rollup/rollup-android-arm64': 4.31.0 - '@rollup/rollup-darwin-arm64': 4.31.0 - '@rollup/rollup-darwin-x64': 4.31.0 - '@rollup/rollup-freebsd-arm64': 4.31.0 - '@rollup/rollup-freebsd-x64': 4.31.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.31.0 - '@rollup/rollup-linux-arm-musleabihf': 4.31.0 - '@rollup/rollup-linux-arm64-gnu': 4.31.0 - '@rollup/rollup-linux-arm64-musl': 4.31.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.31.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.31.0 - '@rollup/rollup-linux-riscv64-gnu': 4.31.0 - '@rollup/rollup-linux-s390x-gnu': 4.31.0 - '@rollup/rollup-linux-x64-gnu': 4.31.0 - '@rollup/rollup-linux-x64-musl': 4.31.0 - '@rollup/rollup-win32-arm64-msvc': 4.31.0 - '@rollup/rollup-win32-ia32-msvc': 4.31.0 - '@rollup/rollup-win32-x64-msvc': 4.31.0 + '@rollup/rollup-android-arm-eabi': 4.57.1 + '@rollup/rollup-android-arm64': 4.57.1 + '@rollup/rollup-darwin-arm64': 4.57.1 + '@rollup/rollup-darwin-x64': 4.57.1 + '@rollup/rollup-freebsd-arm64': 4.57.1 + '@rollup/rollup-freebsd-x64': 4.57.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 + '@rollup/rollup-linux-arm-musleabihf': 4.57.1 + '@rollup/rollup-linux-arm64-gnu': 4.57.1 + '@rollup/rollup-linux-arm64-musl': 4.57.1 + '@rollup/rollup-linux-loong64-gnu': 4.57.1 + '@rollup/rollup-linux-loong64-musl': 4.57.1 + '@rollup/rollup-linux-ppc64-gnu': 4.57.1 + '@rollup/rollup-linux-ppc64-musl': 4.57.1 + '@rollup/rollup-linux-riscv64-gnu': 4.57.1 + '@rollup/rollup-linux-riscv64-musl': 4.57.1 + '@rollup/rollup-linux-s390x-gnu': 4.57.1 + '@rollup/rollup-linux-x64-gnu': 4.57.1 + '@rollup/rollup-linux-x64-musl': 4.57.1 + '@rollup/rollup-openbsd-x64': 4.57.1 + '@rollup/rollup-openharmony-arm64': 4.57.1 + '@rollup/rollup-win32-arm64-msvc': 4.57.1 + '@rollup/rollup-win32-ia32-msvc': 4.57.1 + '@rollup/rollup-win32-x64-gnu': 4.57.1 + '@rollup/rollup-win32-x64-msvc': 4.57.1 fsevents: 2.3.3 run-async@2.4.1: {} - run-async@3.0.0: {} - run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 - rxjs@7.8.1: + rxjs@7.8.2: dependencies: tslib: 2.8.1 safe-array-concat@1.1.3: dependencies: call-bind: 1.0.8 - call-bound: 1.0.3 - get-intrinsic: 1.2.7 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 has-symbols: 1.1.0 isarray: 2.0.5 @@ -10704,30 +10591,17 @@ snapshots: safe-regex-test@1.1.0: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 es-errors: 1.3.0 is-regex: 1.2.1 safer-buffer@2.1.2: {} - sass-lookup@5.0.1: - dependencies: - commander: 10.0.1 - - sembear@0.5.2: - dependencies: - '@types/semver': 6.2.7 - semver: 6.3.1 - semver@5.7.2: {} semver@6.3.1: {} - semver@7.5.3: - dependencies: - lru-cache: 6.0.0 - - semver@7.6.3: {} + semver@7.7.4: {} set-blocking@2.0.0: {} @@ -10736,7 +10610,7 @@ snapshots: define-data-property: 1.1.4 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.7 + get-intrinsic: 1.3.0 gopd: 1.2.0 has-property-descriptors: 1.0.2 @@ -10757,42 +10631,36 @@ snapshots: dependencies: kind-of: 6.0.3 - shebang-command@1.2.0: - dependencies: - shebang-regex: 1.0.0 - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 - shebang-regex@1.0.0: {} - shebang-regex@3.0.0: {} side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 - object-inspect: 1.13.3 + object-inspect: 1.13.4 side-channel-map@1.0.1: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 es-errors: 1.3.0 - get-intrinsic: 1.2.7 - object-inspect: 1.13.3 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 side-channel-weakmap@1.0.2: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 es-errors: 1.3.0 - get-intrinsic: 1.2.7 - object-inspect: 1.13.3 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 side-channel-map: 1.0.1 side-channel@1.1.0: dependencies: es-errors: 1.3.0 - object-inspect: 1.13.3 + object-inspect: 1.13.4 side-channel-list: 1.0.0 side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 @@ -10803,46 +10671,37 @@ snapshots: signal-exit@4.1.0: {} - sigstore@1.9.0: + sigstore@2.3.1: dependencies: - '@sigstore/bundle': 1.1.0 - '@sigstore/protobuf-specs': 0.2.1 - '@sigstore/sign': 1.0.0 - '@sigstore/tuf': 1.0.3 - make-fetch-happen: 11.1.1 + '@sigstore/bundle': 2.3.2 + '@sigstore/core': 1.1.0 + '@sigstore/protobuf-specs': 0.3.3 + '@sigstore/sign': 2.3.2 + '@sigstore/tuf': 2.3.4 + '@sigstore/verify': 1.2.1 transitivePeerDependencies: - supports-color - sisteransi@1.0.5: {} - slash@3.0.0: {} - slash@5.1.0: {} - - slice-ansi@4.0.0: - dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 - slice-ansi@5.0.0: dependencies: - ansi-styles: 6.2.1 + ansi-styles: 6.2.3 is-fullwidth-code-point: 4.0.0 smart-buffer@4.2.0: {} - socks-proxy-agent@7.0.0: + socks-proxy-agent@8.0.5: dependencies: - agent-base: 6.0.2 - debug: 4.4.0 - socks: 2.8.3 + agent-base: 7.1.4 + debug: 4.4.3 + socks: 2.8.7 transitivePeerDependencies: - supports-color - socks@2.8.3: + socks@2.8.7: dependencies: - ip-address: 9.0.5 + ip-address: 10.1.0 smart-buffer: 4.2.0 sort-keys@2.0.0: @@ -10851,31 +10710,21 @@ snapshots: source-map-js@1.2.1: {} - source-map-support@0.5.13: - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - source-map@0.6.1: {} - spawndamnit@2.0.0: - dependencies: - cross-spawn: 5.1.0 - signal-exit: 3.0.7 - spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.21 + spdx-license-ids: 3.0.22 spdx-exceptions@2.5.0: {} spdx-expression-parse@3.0.1: dependencies: spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.21 + spdx-license-ids: 3.0.22 - spdx-license-ids@3.0.21: {} + spdx-license-ids@3.0.22: {} split2@3.2.2: dependencies: @@ -10887,39 +10736,25 @@ snapshots: sprintf-js@1.0.3: {} - sprintf-js@1.1.3: {} - ssri@10.0.6: dependencies: minipass: 7.1.2 - ssri@9.0.1: - dependencies: - minipass: 3.3.6 - - stable-hash@0.0.4: {} - - stack-utils@2.0.6: - dependencies: - escape-string-regexp: 2.0.0 + stable-hash@0.0.5: {} stackback@0.0.2: {} - std-env@3.8.0: {} + std-env@3.10.0: {} stdin-discarder@0.2.2: {} - stream-to-array@2.3.0: + stop-iteration-iterator@1.1.0: dependencies: - any-promise: 1.3.0 + es-errors: 1.3.0 + internal-slot: 1.1.0 string-argv@0.3.2: {} - string-length@4.0.2: - dependencies: - char-regex: 1.0.2 - strip-ansi: 6.0.1 - string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -10930,28 +10765,28 @@ snapshots: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 - strip-ansi: 7.1.0 + strip-ansi: 7.1.2 string-width@7.2.0: dependencies: - emoji-regex: 10.4.0 - get-east-asian-width: 1.3.0 - strip-ansi: 7.1.0 + emoji-regex: 10.6.0 + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 string.prototype.trim@1.2.10: dependencies: call-bind: 1.0.8 - call-bound: 1.0.3 + call-bound: 1.0.4 define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.1 es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 string.prototype.trimend@1.0.9: dependencies: call-bind: 1.0.8 - call-bound: 1.0.3 + call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 @@ -10969,19 +10804,13 @@ snapshots: dependencies: safe-buffer: 5.2.1 - stringify-object@3.3.0: - dependencies: - get-own-enumerable-property-symbols: 3.0.2 - is-obj: 1.0.1 - is-regexp: 1.0.0 - strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.0: + strip-ansi@7.1.2: dependencies: - ansi-regex: 6.1.0 + ansi-regex: 6.2.2 strip-bom@3.0.0: {} @@ -10995,78 +10824,31 @@ snapshots: dependencies: min-indent: 1.0.1 - strip-json-comments@2.0.1: {} - strip-json-comments@3.1.1: {} - strong-log-transformer@2.1.0: + strip-literal@3.1.0: dependencies: - duplexer: 0.1.2 - minimist: 1.2.8 - through: 2.3.8 - - stylus-lookup@5.0.1: - dependencies: - commander: 10.0.1 - - supports-color@5.5.0: - dependencies: - has-flag: 3.0.0 + js-tokens: 9.0.1 supports-color@7.2.0: dependencies: has-flag: 4.0.0 - supports-color@8.1.1: - dependencies: - has-flag: 4.0.0 - supports-preserve-symlinks-flag@1.0.0: {} - swagger-parser@10.0.3(openapi-types@12.1.3): - dependencies: - '@apidevtools/swagger-parser': 10.0.3(openapi-types@12.1.3) - transitivePeerDependencies: - - openapi-types - - synckit@0.9.2: - dependencies: - '@pkgr/core': 0.1.1 - tslib: 2.8.1 - - table@6.9.0: - dependencies: - ajv: 8.17.1 - lodash.truncate: 4.4.2 - slice-ansi: 4.0.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - tanu@0.1.13: + synckit@0.11.12: dependencies: - tslib: 2.8.1 - typescript: 4.9.5 - - tapable@2.2.1: {} + '@pkgr/core': 0.2.9 tar-stream@2.2.0: dependencies: bl: 4.1.0 - end-of-stream: 1.4.4 + end-of-stream: 1.4.5 fs-constants: 1.0.0 inherits: 2.0.4 readable-stream: 3.6.2 - tar@6.1.11: - dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 3.3.6 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 - - tar@6.2.0: + tar@6.2.1: dependencies: chownr: 2.0.0 fs-minipass: 2.1.0 @@ -11077,22 +10859,10 @@ snapshots: temp-dir@1.0.0: {} - temporal-polyfill@0.2.5: - dependencies: - temporal-spec: 0.2.4 - - temporal-spec@0.2.4: {} - - test-exclude@6.0.0: - dependencies: - '@istanbuljs/schema': 0.1.3 - glob: 7.2.3 - minimatch: 3.1.2 - test-exclude@7.0.1: dependencies: '@istanbuljs/schema': 0.1.3 - glob: 10.4.5 + glob: 10.5.0 minimatch: 9.0.5 text-extensions@1.9.0: {} @@ -11110,19 +10880,23 @@ snapshots: tinyexec@0.3.2: {} - tinypool@1.0.2: {} + tinyglobby@0.2.12: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 - tinyrainbow@2.0.0: {} + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 - tinyspy@3.0.2: {} + tinypool@1.1.1: {} - tmp@0.0.33: - dependencies: - os-tmpdir: 1.0.2 + tinyrainbow@2.0.0: {} - tmp@0.2.3: {} + tinyspy@4.0.4: {} - tmpl@1.0.5: {} + tmp@0.2.5: {} to-regex-range@5.0.1: dependencies: @@ -11130,55 +10904,14 @@ snapshots: tr46@0.0.3: {} + treeverse@3.0.0: {} + trim-newlines@3.0.1: {} ts-api-utils@1.4.3(typescript@5.7.3): dependencies: typescript: 5.7.3 - ts-graphviz@1.8.2: {} - - ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)))(typescript@5.7.3): - dependencies: - bs-logger: 0.2.6 - ejs: 3.1.10 - fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)) - jest-util: 29.7.0 - json5: 2.2.3 - lodash.memoize: 4.1.2 - make-error: 1.3.6 - semver: 7.6.3 - typescript: 5.7.3 - yargs-parser: 21.1.1 - optionalDependencies: - '@babel/core': 7.26.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.0) - - ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.11 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 22.10.7 - acorn: 8.14.0 - acorn-walk: 8.3.4 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 5.7.3 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - - ts-pattern@5.6.2: {} - - ts-toolbelt@9.6.0: {} - tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 @@ -11192,20 +10925,13 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 - tslib@1.14.1: {} - tslib@2.8.1: {} - tsutils@3.21.0(typescript@5.7.3): + tuf-js@2.2.1: dependencies: - tslib: 1.14.1 - typescript: 5.7.3 - - tuf-js@1.1.7: - dependencies: - '@tufjs/models': 1.0.4 - debug: 4.4.0 - make-fetch-happen: 11.1.1 + '@tufjs/models': 2.0.1 + debug: 4.4.3 + make-fetch-happen: 13.0.1 transitivePeerDependencies: - supports-color @@ -11213,8 +10939,6 @@ snapshots: dependencies: prelude-ls: 1.2.1 - type-detect@4.0.8: {} - type-fest@0.18.1: {} type-fest@0.20.2: {} @@ -11229,18 +10953,16 @@ snapshots: type-fest@1.4.0: {} - type-fest@3.13.1: {} - typed-array-buffer@1.0.3: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 es-errors: 1.3.0 is-typed-array: 1.1.15 typed-array-byte-length@1.0.3: dependencies: call-bind: 1.0.8 - for-each: 0.3.3 + for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 @@ -11249,7 +10971,7 @@ snapshots: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.8 - for-each: 0.3.3 + for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 @@ -11258,16 +10980,14 @@ snapshots: typed-array-length@1.0.7: dependencies: call-bind: 1.0.8 - for-each: 0.3.3 + for-each: 0.3.5 gopd: 1.2.0 is-typed-array: 1.1.15 - possible-typed-array-names: 1.0.0 + possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 typedarray@0.0.6: {} - typescript@4.9.5: {} - typescript@5.7.3: {} uglify-js@3.19.3: @@ -11275,44 +10995,50 @@ snapshots: unbox-primitive@1.1.0: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 has-bigints: 1.1.0 has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 - undici-types@6.20.0: {} - - unicorn-magic@0.1.0: {} - - unique-filename@2.0.1: - dependencies: - unique-slug: 3.0.0 + undici-types@6.21.0: {} unique-filename@3.0.0: dependencies: unique-slug: 4.0.0 - unique-slug@3.0.0: - dependencies: - imurmurhash: 0.1.4 - unique-slug@4.0.0: dependencies: imurmurhash: 0.1.4 universal-user-agent@6.0.1: {} - universalify@0.1.2: {} - universalify@2.0.1: {} - upath@2.0.1: {} - - update-browserslist-db@1.1.2(browserslist@4.24.4): + unrs-resolver@1.11.1: dependencies: - browserslist: 4.24.4 - escalade: 3.2.0 - picocolors: 1.1.1 + napi-postinstall: 0.3.4 + optionalDependencies: + '@unrs/resolver-binding-android-arm-eabi': 1.11.1 + '@unrs/resolver-binding-android-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-x64': 1.11.1 + '@unrs/resolver-binding-freebsd-x64': 1.11.1 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-arm64-musl': 1.11.1 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1 + '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-musl': 1.11.1 + '@unrs/resolver-binding-wasm32-wasi': 1.11.1 + '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1 + '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 + '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + + upath@2.0.1: {} uri-js@4.4.1: dependencies: @@ -11320,46 +11046,22 @@ snapshots: util-deprecate@1.0.2: {} - uuid@9.0.1: {} - - v8-compile-cache-lib@3.0.1: {} - - v8-compile-cache@2.3.0: {} - - v8-to-istanbul@9.3.0: - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - '@types/istanbul-lib-coverage': 2.0.6 - convert-source-map: 2.0.0 + uuid@10.0.0: {} validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - validate-npm-package-name@3.0.0: - dependencies: - builtins: 1.0.3 - - validate-npm-package-name@5.0.0: - dependencies: - builtins: 5.1.0 - - validator@13.12.0: {} + validate-npm-package-name@5.0.1: {} - verror@1.10.1: - dependencies: - assert-plus: 1.0.0 - core-util-is: 1.0.2 - extsprintf: 1.4.1 - - vite-node@3.0.3(@types/node@22.10.7)(yaml@2.7.0): + vite-node@3.2.4(@types/node@22.19.11)(yaml@2.8.2): dependencies: cac: 6.7.14 - debug: 4.4.0 - es-module-lexer: 1.6.0 - pathe: 2.0.2 - vite: 6.0.11(@types/node@22.10.7)(yaml@2.7.0) + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.3.1(@types/node@22.19.11)(yaml@2.8.2) transitivePeerDependencies: - '@types/node' - jiti @@ -11374,40 +11076,46 @@ snapshots: - tsx - yaml - vite@6.0.11(@types/node@22.10.7)(yaml@2.7.0): + vite@7.3.1(@types/node@22.19.11)(yaml@2.8.2): dependencies: - esbuild: 0.24.2 - postcss: 8.5.1 - rollup: 4.31.0 + esbuild: 0.27.3 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.57.1 + tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 22.10.7 + '@types/node': 22.19.11 fsevents: 2.3.3 - yaml: 2.7.0 - - vitest@3.0.3(@types/node@22.10.7)(yaml@2.7.0): - dependencies: - '@vitest/expect': 3.0.3 - '@vitest/mocker': 3.0.3(vite@6.0.11(@types/node@22.10.7)(yaml@2.7.0)) - '@vitest/pretty-format': 3.0.3 - '@vitest/runner': 3.0.3 - '@vitest/snapshot': 3.0.3 - '@vitest/spy': 3.0.3 - '@vitest/utils': 3.0.3 - chai: 5.1.2 - debug: 4.4.0 - expect-type: 1.1.0 - magic-string: 0.30.17 - pathe: 2.0.2 - std-env: 3.8.0 + yaml: 2.8.2 + + vitest@3.2.4(@types/node@22.19.11)(yaml@2.8.2): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@22.19.11)(yaml@2.8.2)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 tinybench: 2.9.0 tinyexec: 0.3.2 - tinypool: 1.0.2 + tinyglobby: 0.2.15 + tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 6.0.11(@types/node@22.10.7)(yaml@2.7.0) - vite-node: 3.0.3(@types/node@22.10.7)(yaml@2.7.0) + vite: 7.3.1(@types/node@22.19.11)(yaml@2.8.2) + vite-node: 3.2.4(@types/node@22.19.11)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.10.7 + '@types/node': 22.19.11 transitivePeerDependencies: - jiti - less @@ -11422,26 +11130,7 @@ snapshots: - tsx - yaml - vscode-jsonrpc@8.2.0: {} - - vscode-languageserver-protocol@3.17.5: - dependencies: - vscode-jsonrpc: 8.2.0 - vscode-languageserver-types: 3.17.5 - - vscode-languageserver-textdocument@1.0.12: {} - - vscode-languageserver-types@3.17.5: {} - - vscode-languageserver@9.0.1: - dependencies: - vscode-languageserver-protocol: 3.17.5 - - walkdir@0.4.1: {} - - walker@1.0.8: - dependencies: - makeerror: 1.0.12 + walk-up-path@3.0.1: {} wcwidth@1.0.1: dependencies: @@ -11454,34 +11143,29 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 - whence@2.0.1: - dependencies: - '@babel/parser': 7.26.5 - eval-estree-expression: 2.0.3 - which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 - is-boolean-object: 1.2.1 + is-boolean-object: 1.2.2 is-number-object: 1.1.1 is-string: 1.1.1 is-symbol: 1.1.1 which-builtin-type@1.2.1: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 function.prototype.name: 1.1.8 has-tostringtag: 1.0.2 - is-async-function: 2.1.0 + is-async-function: 2.1.1 is-date-object: 1.1.0 is-finalizationregistry: 1.1.1 - is-generator-function: 1.1.0 + is-generator-function: 1.1.2 is-regex: 1.2.1 - is-weakref: 1.1.0 + is-weakref: 1.1.1 isarray: 2.0.5 which-boxed-primitive: 1.1.1 which-collection: 1.0.2 - which-typed-array: 1.1.18 + which-typed-array: 1.1.20 which-collection@1.0.2: dependencies: @@ -11490,26 +11174,23 @@ snapshots: is-weakmap: 2.0.2 is-weakset: 2.0.4 - which-typed-array@1.1.18: + which-typed-array@1.1.20: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.8 - call-bound: 1.0.3 - for-each: 0.3.3 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 gopd: 1.2.0 has-tostringtag: 1.0.2 - which@1.3.1: - dependencies: - isexe: 2.0.0 - which@2.0.2: dependencies: isexe: 2.0.0 - which@3.0.1: + which@4.0.0: dependencies: - isexe: 2.0.0 + isexe: 3.1.5 why-is-node-running@2.3.0: dependencies: @@ -11538,9 +11219,15 @@ snapshots: wrap-ansi@8.1.0: dependencies: - ansi-styles: 6.2.1 + ansi-styles: 6.2.3 string-width: 5.1.2 - strip-ansi: 7.1.0 + strip-ansi: 7.1.2 + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.1.2 wrappy@1.0.2: {} @@ -11550,11 +11237,6 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 3.0.7 - write-file-atomic@4.0.2: - dependencies: - imurmurhash: 0.1.4 - signal-exit: 3.0.7 - write-file-atomic@5.0.1: dependencies: imurmurhash: 0.1.4 @@ -11579,21 +11261,13 @@ snapshots: y18n@5.0.8: {} - yallist@2.1.2: {} - - yallist@3.1.1: {} - yallist@4.0.0: {} - yaml@1.10.2: {} + yaml-ast-parser@0.0.43: {} yaml@2.3.1: {} - yaml@2.5.1: {} - - yaml@2.7.0: {} - - yargs-parser@20.2.4: {} + yaml@2.8.2: {} yargs-parser@20.2.9: {} @@ -11619,18 +11293,4 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 - yn@3.1.1: {} - yocto-queue@0.1.0: {} - - yoctocolors-cjs@2.1.2: {} - - z-schema@5.0.5: - dependencies: - lodash.get: 4.4.2 - lodash.isequal: 4.5.0 - validator: 13.12.0 - optionalDependencies: - commander: 9.5.0 - - zod@3.24.1: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 281197a..dee51e9 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,2 @@ packages: - "packages/*" - - "packages/generators/*" - - "packages/providers/*" - - "packages/types/*" diff --git a/spec-graduate.gif b/spec-graduate.gif deleted file mode 100644 index 3a040a5..0000000 Binary files a/spec-graduate.gif and /dev/null differ diff --git a/tests/e2e/01-init.test.ts b/tests/e2e/01-init.test.ts new file mode 100644 index 0000000..5ba98ae --- /dev/null +++ b/tests/e2e/01-init.test.ts @@ -0,0 +1,133 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + fileExists, + readYAML, + readJSON, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +describe('contractual init', () => { + test('detects OpenAPI spec and scaffolds config', () => { + const { dir, cleanup } = createTempRepo(); + try { + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/api.openapi.yaml')); + + const result = run('init', dir); + + // Assert: config created + expect(fileExists(dir, 'contractual.yaml')).toBe(true); + const config = readYAML(dir, 'contractual.yaml') as { contracts: Array<{ type: string; path: string }> }; + expect(config.contracts).toHaveLength(1); + expect(config.contracts[0].type).toBe('openapi'); + expect(config.contracts[0].path).toContain('api.openapi.yaml'); + + // Assert: .contractual directory created + expect(fileExists(dir, '.contractual/versions.json')).toBe(true); + expect(fileExists(dir, '.contractual/changesets')).toBe(true); + expect(fileExists(dir, '.contractual/snapshots')).toBe(true); + + // Assert: versions.json is populated with initial version + const versions = readJSON(dir, '.contractual/versions.json') as Record; + const contractName = Object.keys(versions)[0]; + expect(contractName).toBeDefined(); + expect(versions[contractName].version).toBe('0.0.0'); + + // Assert: stdout confirms detection + expect(result.stdout).toMatch(/found|detected|initialized/i); + } finally { + cleanup(); + } + }); + + test('detects JSON Schema by extension', () => { + const { dir, cleanup } = createTempRepo(); + try { + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.schema.json')); + + run('init', dir); + + const config = readYAML(dir, 'contractual.yaml') as { contracts: Array<{ type: string }> }; + expect(config.contracts).toHaveLength(1); + expect(config.contracts[0].type).toBe('json-schema'); + } finally { + cleanup(); + } + }); + + test('detects multiple spec types in one repo', () => { + const { dir, cleanup } = createTempRepo(); + try { + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/api.openapi.yaml')); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.schema.json')); + + run('init', dir); + + const config = readYAML(dir, 'contractual.yaml') as { contracts: Array<{ type: string }> }; + expect(config.contracts).toHaveLength(2); + + const types = config.contracts.map((c) => c.type).sort(); + expect(types).toEqual(['json-schema', 'openapi']); + } finally { + cleanup(); + } + }); + + test('handles already initialized gracefully', () => { + const { dir, cleanup } = createTempRepo(); + try { + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/api.openapi.yaml')); + + run('init', dir); + + // Second init should succeed with informational message (use --force to reinitialize) + const result = run('init', dir); + expect(result.exitCode).toBe(0); + expect(result.stdout + result.stderr).toMatch(/already initialized|all contracts have snapshots/i); + } finally { + cleanup(); + } + }); + + test('handles repo with no specs', () => { + const { dir, cleanup } = createTempRepo(); + try { + const result = run('init', dir); + + // Should still complete (maybe with a warning) + expect(result.exitCode).toBe(0); + + // Config may or may not be created with empty contracts + if (fileExists(dir, 'contractual.yaml')) { + const config = readYAML(dir, 'contractual.yaml') as { contracts: Array }; + expect(config.contracts).toHaveLength(0); + } + } finally { + cleanup(); + } + }); + + test('uses parent directory name for generic spec names', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Use "openapi.yaml" (generic name) in "orders" directory + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'orders/openapi.yaml')); + + run('init', dir); + + const config = readYAML(dir, 'contractual.yaml') as { contracts: Array<{ name: string }> }; + expect(config.contracts).toHaveLength(1); + // Should use "orders" as the name, not "openapi" + expect(config.contracts[0].name).toBe('orders'); + } finally { + cleanup(); + } + }); +}); diff --git a/tests/e2e/02-lint.test.ts b/tests/e2e/02-lint.test.ts new file mode 100644 index 0000000..993fabe --- /dev/null +++ b/tests/e2e/02-lint.test.ts @@ -0,0 +1,192 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + setupRepoWithConfig, + writeFile, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +describe('contractual lint', () => { + // Note: No built-in linters are registered yet in linterRegistry. + // All contracts are skipped with "no linter available for {type}" message. + // These tests verify the CLI handles this gracefully (exit 0, no errors). + + test('OpenAPI spec linted with Spectral returns errors for petstore fixture', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + }, + ]); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/petstore.yaml')); + + // OpenAPI linter (Spectral) is registered and finds errors in petstore fixture + const result = run('lint --format json', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + const parsed = JSON.parse(result.stdout); + expect(parsed.results).toHaveLength(1); + expect(parsed.results[0].errors.length).toBeGreaterThan(0); + } finally { + cleanup(); + } + }); + + test('JSON Schema without registered linter exits successfully', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + // No linter registered = contract is skipped, exit 0 + const result = run('lint', dir); + expect(result.exitCode).toBe(0); + } finally { + cleanup(); + } + }); + + // Skip: No linters are registered, so invalid schemas cannot be validated + test.skip('lint reports errors for invalid JSON Schema and exits 1', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'bad-schema', + type: 'json-schema', + path: 'schemas/bad.json', + }, + ]); + // Write an intentionally invalid JSON Schema + writeFile( + dir, + 'schemas/bad.json', + JSON.stringify({ + type: 'objekt', // typo - invalid type value + properties: { + id: { type: 123 }, // type should be string, not number + }, + }) + ); + + const result = run('lint', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + expect(result.stdout + result.stderr).toMatch(/error/i); + } finally { + cleanup(); + } + }); + + test('lint --format json outputs valid JSON', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + const result = run('lint --format json', dir); + const parsed = JSON.parse(result.stdout); + expect(parsed).toHaveProperty('results'); + expect(Array.isArray(parsed.results)).toBe(true); + } finally { + cleanup(); + } + }); + + test('lint with disabled linter skips contract', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + lint: false, + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + // With --format json, disabled contracts are not included in results + const result = run('lint --format json', dir); + expect(result.exitCode).toBe(0); + const parsed = JSON.parse(result.stdout); + // Results should be empty since contract is disabled + expect(parsed.results).toHaveLength(0); + } finally { + cleanup(); + } + }); + + test('lint --contract filters to single contract', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'api', type: 'openapi', path: 'specs/api.yaml' }, + { name: 'schema', type: 'json-schema', path: 'schemas/order.json' }, + ]); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/api.yaml')); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + // OpenAPI linter runs for 'api' contract, petstore has lint errors + const result = run('lint --contract api --format json', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + const parsed = JSON.parse(result.stdout); + // Should have exactly 1 result (the filtered 'api' contract) + expect(parsed.results).toHaveLength(1); + expect(parsed.results[0].contract).toBe('api'); + } finally { + cleanup(); + } + }); + + test('lint reports error for non-existent contract name', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'api', type: 'openapi', path: 'specs/api.yaml' }, + ]); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/api.yaml')); + + const result = run('lint --contract nonexistent', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + expect(result.stdout + result.stderr).toMatch(/not found/i); + } finally { + cleanup(); + } + }); + + test('lint fails with empty contracts array due to schema validation', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Config schema requires minItems: 1 for contracts array + setupRepoWithConfig(dir, []); + + const result = run('lint', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + // Should fail config validation + expect(result.stdout + result.stderr).toMatch(/contracts|validation|invalid/i); + } finally { + cleanup(); + } + }); +}); diff --git a/tests/e2e/03-breaking.test.ts b/tests/e2e/03-breaking.test.ts new file mode 100644 index 0000000..bfff362 --- /dev/null +++ b/tests/e2e/03-breaking.test.ts @@ -0,0 +1,274 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + setupRepoWithConfig, + writeFile, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +describe('contractual breaking', () => { + test('no snapshot β†’ reports first version, no breaking changes', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + const result = run('breaking', dir); + expect(result.exitCode).toBe(0); + // First version with no snapshot shows "No changes detected" + expect(result.stdout).toMatch(/no changes detected/i); + } finally { + cleanup(); + } + }); + + test('detects breaking change in JSON Schema (field removed)', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + // Put base as snapshot (simulating a previous release) + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + // Put breaking variant as current spec + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + + const result = run('breaking', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + expect(result.stdout).toMatch(/breaking/i); + } finally { + cleanup(); + } + }); + + test('detects non-breaking change (optional field added)', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + copyFixture( + 'json-schema/order-optional-field-added.json', + path.join(dir, 'schemas/order.json') + ); + + const result = run('breaking', dir); + // Non-breaking = exit 0 (only breaking exits 1) + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/non-breaking|minor|no breaking/i); + } finally { + cleanup(); + } + }); + + test('no changes detected when spec is identical', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('breaking', dir); + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/no.*change|no breaking/i); + } finally { + cleanup(); + } + }); + + test('--format json returns structured output', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('breaking --format json', dir, { expectFail: true }); + const parsed = JSON.parse(result.stdout); + expect(parsed).toHaveProperty('results'); + expect(parsed).toHaveProperty('hasBreaking'); + expect(parsed.hasBreaking).toBe(true); + expect(Array.isArray(parsed.results)).toBe(true); + expect(parsed.results[0]).toHaveProperty('changes'); + expect(parsed.results[0]).toHaveProperty('summary'); + } finally { + cleanup(); + } + }); + + test('detects type change as breaking', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + copyFixture('json-schema/order-type-changed.json', path.join(dir, 'schemas/order.json')); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('breaking', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + expect(result.stdout).toMatch(/breaking|type/i); + } finally { + cleanup(); + } + }); + + test('--contract filters to single contract', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'order-schema', type: 'json-schema', path: 'schemas/order.json' }, + { name: 'user-schema', type: 'json-schema', path: 'schemas/user.json' }, + ]); + + // Set up snapshots for both + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/user-schema.json') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + 'user-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Breaking change in order, no change in user + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/user.json')); + + // Check only user-schema - should pass + const result = run('breaking --contract user-schema', dir); + expect(result.exitCode).toBe(0); + } finally { + cleanup(); + } + }); + + test('breaking detection disabled for contract skips it', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + breaking: false, + }, + ]); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('breaking', dir); + expect(result.exitCode).toBe(0); + // Disabled contracts show "No changes detected" + expect(result.stdout).toMatch(/no changes detected/i); + } finally { + cleanup(); + } + }); +}); diff --git a/tests/e2e/04-changeset.test.ts b/tests/e2e/04-changeset.test.ts new file mode 100644 index 0000000..cbd87c4 --- /dev/null +++ b/tests/e2e/04-changeset.test.ts @@ -0,0 +1,362 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + setupRepoWithConfig, + writeFile, + readFile, + listFiles, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +describe('contractual changeset', () => { + test('auto-generates changeset from detected breaking changes (major bump)', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + // Put base as snapshot (simulating a previous release) + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Put breaking variant as current spec (field removed) + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + + const result = run('changeset', dir); + expect(result.exitCode).toBe(0); + + // A changeset file should exist + const changesets = listFiles(dir, '.contractual/changesets').filter((f) => f.endsWith('.md')); + expect(changesets.length).toBeGreaterThanOrEqual(1); + + // Parse the changeset + const content = readFile(dir, `.contractual/changesets/${changesets[0]}`); + + // Frontmatter should have major bump + expect(content).toMatch(/"order-schema":\s*major/); + + // Body should contain the contract name section + expect(content).toMatch(/##\s*order-schema/); + + // Body should mention BREAKING + expect(content).toMatch(/\*\*\[BREAKING\]\*\*/); + } finally { + cleanup(); + } + }); + + test('auto-generates minor changeset for non-breaking changes', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + // Put base as snapshot + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Put non-breaking variant as current spec (optional field added) + copyFixture( + 'json-schema/order-optional-field-added.json', + path.join(dir, 'schemas/order.json') + ); + + const result = run('changeset', dir); + expect(result.exitCode).toBe(0); + + const changesets = listFiles(dir, '.contractual/changesets').filter((f) => f.endsWith('.md')); + expect(changesets.length).toBeGreaterThanOrEqual(1); + + const content = readFile(dir, `.contractual/changesets/${changesets[0]}`); + + // Frontmatter should have minor bump (not major) + expect(content).toMatch(/"order-schema":\s*minor/); + + // Body should contain the contract section + expect(content).toMatch(/##\s*order-schema/); + + // Body should mention minor or non-breaking change + expect(content).toMatch(/\*\*\[minor\]\*\*/); + } finally { + cleanup(); + } + }); + + test('no changes -> no changeset created, clean exit', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + // Put base as snapshot + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + // Put identical spec as current + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('changeset', dir); + expect(result.exitCode).toBe(0); + + // Output should indicate no changes + expect(result.stdout).toMatch(/no changes/i); + + // No changeset files should be created (except .gitkeep if present) + const changesets = listFiles(dir, '.contractual/changesets').filter((f) => f.endsWith('.md')); + expect(changesets).toHaveLength(0); + } finally { + cleanup(); + } + }); + + test('changeset for multiple contracts in one file', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'order-schema', type: 'json-schema', path: 'schemas/order.json' }, + { name: 'user-schema', type: 'json-schema', path: 'schemas/user.json' }, + ]); + + // Both have snapshots at v1 (using order-base as template for both) + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/user-schema.json') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + 'user-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Breaking change to order (field removed), non-breaking to user (optional field added) + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + copyFixture( + 'json-schema/order-optional-field-added.json', + path.join(dir, 'schemas/user.json') + ); + + const result = run('changeset', dir); + expect(result.exitCode).toBe(0); + + const changesets = listFiles(dir, '.contractual/changesets').filter((f) => f.endsWith('.md')); + expect(changesets.length).toBeGreaterThanOrEqual(1); + + const content = readFile(dir, `.contractual/changesets/${changesets[0]}`); + + // Should have both contracts in the frontmatter + expect(content).toMatch(/"order-schema":\s*major/); + expect(content).toMatch(/"user-schema":\s*minor/); + + // Should have sections for both contracts + expect(content).toMatch(/##\s*order-schema/); + expect(content).toMatch(/##\s*user-schema/); + } finally { + cleanup(); + } + }); + + test('first version contract (no snapshot) is skipped', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + // No snapshot exists - first version + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + // Empty versions + writeFile(dir, '.contractual/versions.json', '{}'); + + const result = run('changeset', dir); + expect(result.exitCode).toBe(0); + + // No changes to detect without a snapshot + expect(result.stdout).toMatch(/no changes/i); + + const changesets = listFiles(dir, '.contractual/changesets').filter((f) => f.endsWith('.md')); + expect(changesets).toHaveLength(0); + } finally { + cleanup(); + } + }); + + test('changeset with type change (breaking)', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Type changed (breaking) + copyFixture('json-schema/order-type-changed.json', path.join(dir, 'schemas/order.json')); + + const result = run('changeset', dir); + expect(result.exitCode).toBe(0); + + const changesets = listFiles(dir, '.contractual/changesets').filter((f) => f.endsWith('.md')); + expect(changesets.length).toBeGreaterThanOrEqual(1); + + const content = readFile(dir, `.contractual/changesets/${changesets[0]}`); + + // Should be major bump + expect(content).toMatch(/"order-schema":\s*major/); + } finally { + cleanup(); + } + }); + + test('contract with breaking detection disabled is skipped', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + breaking: false, + }, + ]); + + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Breaking change that would normally be detected + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + + const result = run('changeset', dir); + expect(result.exitCode).toBe(0); + + // No changeset should be created since breaking detection is disabled + expect(result.stdout).toMatch(/no changes/i); + + const changesets = listFiles(dir, '.contractual/changesets').filter((f) => f.endsWith('.md')); + expect(changesets).toHaveLength(0); + } finally { + cleanup(); + } + }); + + test('changeset output indicates created file path', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + + const result = run('changeset', dir); + expect(result.exitCode).toBe(0); + + // Output should mention the changeset file was created + expect(result.stdout).toMatch(/created.*changeset/i); + expect(result.stdout).toMatch(/\.contractual\/changesets/); + expect(result.stdout).toMatch(/\.md/); + } finally { + cleanup(); + } + }); +}); diff --git a/tests/e2e/05-version.test.ts b/tests/e2e/05-version.test.ts new file mode 100644 index 0000000..3fc73c3 --- /dev/null +++ b/tests/e2e/05-version.test.ts @@ -0,0 +1,488 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + setupRepoWithConfig, + writeFile, + readFile, + readJSON, + listFiles, + fileExists, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +describe('contractual version', () => { + test('consumes changeset, bumps version, updates snapshot, writes changelog', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Setup repo with a contract + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + // Set initial version + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Create a changeset + writeFile( + dir, + '.contractual/changesets/add-notes-field.md', + `--- +"order-schema": minor +--- +## order-schema +- Added optional notes field to order +` + ); + + const result = run('version', dir); + + // Assert: command succeeds + expect(result.exitCode).toBe(0); + + // Assert: version bumped to 1.1.0 (minor bump from 1.0.0) + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string; released: string } + >; + expect(versions['order-schema'].version).toBe('1.1.0'); + expect(versions['order-schema'].released).toBeDefined(); + + // Assert: snapshot created/updated + expect(fileExists(dir, '.contractual/snapshots/order-schema.json')).toBe(true); + + // Assert: changelog created with entry + expect(fileExists(dir, 'CHANGELOG.md')).toBe(true); + const changelog = readFile(dir, 'CHANGELOG.md'); + expect(changelog).toContain('# Changelog'); + expect(changelog).toContain('[order-schema] v1.1.0'); + expect(changelog).toContain('Added optional notes field'); + + // Assert: changeset file removed + const changesetFiles = listFiles(dir, '.contractual/changesets'); + expect(changesetFiles).not.toContain('add-notes-field.md'); + + // Assert: stdout confirms version bump + expect(result.stdout).toMatch(/1\.0\.0.*->.*1\.1\.0/); + } finally { + cleanup(); + } + }); + + test('multiple changesets: highest bump wins (major > minor > patch)', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'api-schema', + type: 'json-schema', + path: 'schemas/api.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/api.json')); + + // Set initial version + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'api-schema': { version: '2.5.3', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Create multiple changesets with different bump types + // Changeset 1: patch bump + writeFile( + dir, + '.contractual/changesets/fix-typo.md', + `--- +"api-schema": patch +--- +## api-schema +- Fixed typo in description +` + ); + + // Changeset 2: minor bump + writeFile( + dir, + '.contractual/changesets/add-field.md', + `--- +"api-schema": minor +--- +## api-schema +- Added new optional field +` + ); + + // Changeset 3: major bump (should win) + writeFile( + dir, + '.contractual/changesets/breaking-change.md', + `--- +"api-schema": major +--- +## api-schema +- Removed deprecated endpoint (BREAKING) +` + ); + + const result = run('version', dir); + + // Assert: command succeeds + expect(result.exitCode).toBe(0); + + // Assert: major bump applied (2.5.3 -> 3.0.0) + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['api-schema'].version).toBe('3.0.0'); + + // Assert: all changesets consumed + const changesetFiles = listFiles(dir, '.contractual/changesets'); + expect(changesetFiles).toHaveLength(0); + + // Assert: changelog contains all changes + const changelog = readFile(dir, 'CHANGELOG.md'); + expect(changelog).toContain('v3.0.0'); + expect(changelog).toContain('Fixed typo'); + expect(changelog).toContain('Added new optional field'); + expect(changelog).toContain('Removed deprecated endpoint'); + } finally { + cleanup(); + } + }); + + test('no changesets does nothing', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + // Set version but no changesets + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('version', dir); + + // Assert: command succeeds (no-op) + expect(result.exitCode).toBe(0); + + // Assert: version unchanged + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['order-schema'].version).toBe('1.0.0'); + + // Assert: no changelog created + expect(fileExists(dir, 'CHANGELOG.md')).toBe(false); + + // Assert: stdout indicates nothing to do + expect(result.stdout).toMatch(/no.*changeset|nothing/i); + } finally { + cleanup(); + } + }); + + test('first version: 0.0.0 -> 1.0.0 on major', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'new-api', + type: 'json-schema', + path: 'schemas/new-api.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/new-api.json')); + + // No initial version (first release) + // versions.json is empty (setup creates empty object) + + // Create changeset with major bump + writeFile( + dir, + '.contractual/changesets/initial-release.md', + `--- +"new-api": major +--- +## new-api +- Initial release of the API +` + ); + + const result = run('version', dir); + + // Assert: command succeeds + expect(result.exitCode).toBe(0); + + // Assert: version is 1.0.0 (major bump from implicit 0.0.0) + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['new-api'].version).toBe('1.0.0'); + + // Assert: snapshot created + expect(fileExists(dir, '.contractual/snapshots/new-api.json')).toBe(true); + + // Assert: changelog created + expect(fileExists(dir, 'CHANGELOG.md')).toBe(true); + const changelog = readFile(dir, 'CHANGELOG.md'); + expect(changelog).toContain('[new-api] v1.0.0'); + expect(changelog).toContain('Initial release'); + + // Assert: stdout shows bump from 0.0.0 + expect(result.stdout).toMatch(/0\.0\.0.*->.*1\.0\.0/); + } finally { + cleanup(); + } + }); + + test('changelog appends, does not overwrite existing entries', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'api', + type: 'json-schema', + path: 'schemas/api.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/api.json')); + + // Set initial version + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + api: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Create existing changelog with previous entries + writeFile( + dir, + 'CHANGELOG.md', + `# Changelog + +## [api] v1.0.0 - 2026-01-01 + +- Initial release with core features +- Added authentication support +` + ); + + // Create a new changeset + writeFile( + dir, + '.contractual/changesets/add-feature.md', + `--- +"api": minor +--- +## api +- Added pagination support +` + ); + + const result = run('version', dir); + + // Assert: command succeeds + expect(result.exitCode).toBe(0); + + // Assert: version bumped + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['api'].version).toBe('1.1.0'); + + // Assert: changelog preserved old entries AND has new entry + const changelog = readFile(dir, 'CHANGELOG.md'); + + // New entry should be present + expect(changelog).toContain('[api] v1.1.0'); + expect(changelog).toContain('Added pagination support'); + + // Old entries should still be present + expect(changelog).toContain('[api] v1.0.0'); + expect(changelog).toContain('Initial release with core features'); + expect(changelog).toContain('Added authentication support'); + + // New entry should come before old entry (prepended) + const v110Index = changelog.indexOf('[api] v1.1.0'); + const v100Index = changelog.indexOf('[api] v1.0.0'); + expect(v110Index).toBeLessThan(v100Index); + } finally { + cleanup(); + } + }); + + test('handles multiple contracts in one changeset', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'orders-api', + type: 'json-schema', + path: 'schemas/orders.json', + }, + { + name: 'users-api', + type: 'json-schema', + path: 'schemas/users.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/orders.json')); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/users.json')); + + // Set initial versions + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'orders-api': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + 'users-api': { version: '2.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Create changeset affecting both contracts + writeFile( + dir, + '.contractual/changesets/shared-update.md', + `--- +"orders-api": minor +"users-api": patch +--- +## orders-api +- Added bulk operations endpoint + +## users-api +- Fixed validation message +` + ); + + const result = run('version', dir); + + // Assert: command succeeds + expect(result.exitCode).toBe(0); + + // Assert: both versions bumped appropriately + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['orders-api'].version).toBe('1.1.0'); + expect(versions['users-api'].version).toBe('2.0.1'); + + // Assert: both snapshots created + expect(fileExists(dir, '.contractual/snapshots/orders-api.json')).toBe(true); + expect(fileExists(dir, '.contractual/snapshots/users-api.json')).toBe(true); + + // Assert: changelog has entries for both + const changelog = readFile(dir, 'CHANGELOG.md'); + expect(changelog).toContain('[orders-api] v1.1.0'); + expect(changelog).toContain('[users-api] v2.0.1'); + expect(changelog).toContain('Added bulk operations endpoint'); + expect(changelog).toContain('Fixed validation message'); + } finally { + cleanup(); + } + }); + + test('skips contracts not found in config', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'existing-api', + type: 'json-schema', + path: 'schemas/existing.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/existing.json')); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'existing-api': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Create changeset referencing a non-existent contract + writeFile( + dir, + '.contractual/changesets/mixed-update.md', + `--- +"existing-api": minor +"nonexistent-api": major +--- +## existing-api +- Updated existing API + +## nonexistent-api +- This contract does not exist +` + ); + + const result = run('version', dir); + + // Assert: command succeeds (processes what it can) + expect(result.exitCode).toBe(0); + + // Assert: existing contract was bumped + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['existing-api'].version).toBe('1.1.0'); + + // Assert: nonexistent contract was not added to versions + expect(versions['nonexistent-api']).toBeUndefined(); + + // Assert: changeset was still consumed + const changesetFiles = listFiles(dir, '.contractual/changesets'); + expect(changesetFiles).toHaveLength(0); + + // Assert: changelog only contains the valid contract + const changelog = readFile(dir, 'CHANGELOG.md'); + expect(changelog).toContain('[existing-api] v1.1.0'); + expect(changelog).toContain('Updated existing API'); + // The nonexistent-api changes should NOT appear in changelog (no version bumped for it) + expect(changelog).not.toContain('[nonexistent-api]'); + } finally { + cleanup(); + } + }); +}); diff --git a/tests/e2e/06-status.test.ts b/tests/e2e/06-status.test.ts new file mode 100644 index 0000000..38c1ba2 --- /dev/null +++ b/tests/e2e/06-status.test.ts @@ -0,0 +1,250 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + setupRepoWithConfig, + writeFile, + readFile, + listFiles, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +describe('contractual status', () => { + test('shows current versions from versions.json', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + { + name: 'user-schema', + type: 'json-schema', + path: 'schemas/user.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/user.json')); + + // Set up versions + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.2.3', released: '2026-01-15T10:00:00Z' }, + 'user-schema': { version: '2.0.0', released: '2026-02-01T12:00:00Z' }, + }) + ); + + const result = run('status', dir); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/order-schema/); + expect(result.stdout).toMatch(/1\.2\.3/); + expect(result.stdout).toMatch(/user-schema/); + expect(result.stdout).toMatch(/2\.0\.0/); + } finally { + cleanup(); + } + }); + + test('shows pending changesets', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + // Set up version + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Create a pending changeset + const changesetContent = `--- +"order-schema": minor +--- + +## order-schema + +- **[minor]** Added optional tracking_number field +`; + writeFile(dir, '.contractual/changesets/friendly-tiger.md', changesetContent); + + const result = run('status', dir); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/pending/i); + expect(result.stdout).toMatch(/friendly-tiger\.md/); + expect(result.stdout).toMatch(/order-schema/); + expect(result.stdout).toMatch(/minor/i); + } finally { + cleanup(); + } + }); + + test('shows projected bumps based on pending changesets', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + { + name: 'user-schema', + type: 'json-schema', + path: 'schemas/user.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/user.json')); + + // Set up versions + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + 'user-schema': { version: '1.5.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Create a changeset with major bump for order-schema + const changesetContent = `--- +"order-schema": major +"user-schema": patch +--- + +## order-schema + +- **[BREAKING]** Removed customer_email field + +## user-schema + +- **[patch]** Updated description +`; + writeFile(dir, '.contractual/changesets/breaking-change.md', changesetContent); + + const result = run('status', dir); + + expect(result.exitCode).toBe(0); + // Should show projected version 2.0.0 for order-schema (major bump from 1.0.0) + expect(result.stdout).toMatch(/2\.0\.0/); + // Should show projected version 1.5.1 for user-schema (patch bump from 1.5.0) + expect(result.stdout).toMatch(/1\.5\.1/); + // Should indicate major and patch bumps + expect(result.stdout).toMatch(/major/i); + expect(result.stdout).toMatch(/patch/i); + } finally { + cleanup(); + } + }); + + test('handles empty state (no versions, no changesets)', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + // versions.json is empty (created by setupRepoWithConfig) + const result = run('status', dir); + + expect(result.exitCode).toBe(0); + // Should show 0.0.0 or unreleased + expect(result.stdout).toMatch(/0\.0\.0|unreleased/i); + // Should indicate no pending changesets + expect(result.stdout).toMatch(/no pending changeset/i); + } finally { + cleanup(); + } + }); + + test('aggregates multiple changesets correctly (highest bump wins)', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + // Set up version + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Create multiple changesets - patch and minor for same contract + const patchChangeset = `--- +"order-schema": patch +--- + +Fixed typo in description +`; + const minorChangeset = `--- +"order-schema": minor +--- + +Added optional field +`; + writeFile(dir, '.contractual/changesets/fix-typo.md', patchChangeset); + writeFile(dir, '.contractual/changesets/add-field.md', minorChangeset); + + const result = run('status', dir); + + expect(result.exitCode).toBe(0); + // Should show 2 pending changesets + expect(result.stdout).toMatch(/2.*changeset/i); + // Should project to 1.1.0 (minor wins over patch) + expect(result.stdout).toMatch(/1\.1\.0/); + } finally { + cleanup(); + } + }); + + test('reports error when not initialized', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Don't run init or setupRepoWithConfig - no .contractual directory + + const result = run('status', dir, { expectFail: true }); + + expect(result.exitCode).toBe(1); + // Error message contains "No contractual.yaml found" + expect(result.stdout + result.stderr).toMatch(/contractual\.yaml|init/i); + } finally { + cleanup(); + } + }); +}); diff --git a/tests/e2e/07-full-lifecycle.test.ts b/tests/e2e/07-full-lifecycle.test.ts new file mode 100644 index 0000000..f7844a0 --- /dev/null +++ b/tests/e2e/07-full-lifecycle.test.ts @@ -0,0 +1,372 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + setupRepoWithConfig, + writeFile, + readFile, + readJSON, + fileExists, + listFiles, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +describe('contractual full lifecycle', () => { + test('complete cycle: init -> first release -> changes -> changeset -> second release', () => { + const { dir, cleanup } = createTempRepo(); + try { + // ===== PHASE 1: Setup with a spec using setupRepoWithConfig ===== + setupRepoWithConfig(dir, [ + { + name: 'order', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + // ===== PHASE 2: Create initial changeset manually, run version (1.0.0) ===== + const initialChangeset = `--- +"order": major +--- + +## order + +- **[major]** Initial release of order schema +`; + writeFile(dir, '.contractual/changesets/initial-release.md', initialChangeset); + + // Run version to consume the changeset + const versionResult1 = run('version', dir); + expect(versionResult1.exitCode).toBe(0); + expect(versionResult1.stdout).toMatch(/1\.0\.0/); + + // Verify versions.json was updated + const versions1 = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions1['order']).toBeDefined(); + expect(versions1['order'].version).toBe('1.0.0'); + + // Verify changeset was consumed + const changesetsAfterV1 = listFiles(dir, '.contractual/changesets'); + expect(changesetsAfterV1.filter((f) => f.endsWith('.md'))).toHaveLength(0); + + // Verify snapshot was created + expect(fileExists(dir, '.contractual/snapshots/order.json')).toBe(true); + + // Verify CHANGELOG.md was created + expect(fileExists(dir, 'CHANGELOG.md')).toBe(true); + const changelog1 = readFile(dir, 'CHANGELOG.md'); + expect(changelog1).toMatch(/1\.0\.0/); + + // ===== PHASE 3: Make breaking change to spec ===== + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + + // ===== PHASE 4: Run changeset command (auto-detect) ===== + const changesetResult = run('changeset', dir); + expect(changesetResult.exitCode).toBe(0); + expect(changesetResult.stdout).toMatch(/created changeset/i); + + // Verify a changeset file was created + const changesetsAfterDetect = listFiles(dir, '.contractual/changesets'); + const mdFiles = changesetsAfterDetect.filter((f) => f.endsWith('.md')); + expect(mdFiles.length).toBeGreaterThan(0); + + // Verify the changeset contains major bump (breaking change) + const changesetFile = readFile(dir, `.contractual/changesets/${mdFiles[0]}`); + expect(changesetFile).toMatch(/major/i); + + // ===== PHASE 5: Run status (shows projected bump) ===== + const statusResult = run('status', dir); + expect(statusResult.exitCode).toBe(0); + expect(statusResult.stdout).toMatch(/1\.0\.0/); // Current version + expect(statusResult.stdout).toMatch(/2\.0\.0/); // Projected version + expect(statusResult.stdout).toMatch(/major/i); + expect(statusResult.stdout).toMatch(/pending/i); + + // ===== PHASE 6: Run version (bumps to 2.0.0) ===== + const versionResult2 = run('version', dir); + expect(versionResult2.exitCode).toBe(0); + expect(versionResult2.stdout).toMatch(/2\.0\.0/); + + // Verify versions.json was updated + const versions2 = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions2['order'].version).toBe('2.0.0'); + + // Verify CHANGELOG.md was updated + const changelog2 = readFile(dir, 'CHANGELOG.md'); + expect(changelog2).toMatch(/2\.0\.0/); + expect(changelog2).toMatch(/1\.0\.0/); // Previous version still there + + // ===== PHASE 7: Verify breaking shows no changes after version ===== + const breakingResult = run('breaking', dir); + expect(breakingResult.exitCode).toBe(0); + expect(breakingResult.stdout).toMatch(/no.*change|no breaking/i); + } finally { + cleanup(); + } + }); + + test('multiple releases accumulate in changelog (1.0.0 -> 1.1.0 -> 1.1.1)', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Setup initial state + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + // Create snapshot (simulating a previous version) + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + // ===== Release 1.0.0 ===== + const release1Changeset = `--- +"order-schema": major +--- + +## order-schema + +- **[major]** Initial release +`; + writeFile(dir, '.contractual/changesets/release-1.md', release1Changeset); + + const version1 = run('version', dir); + expect(version1.exitCode).toBe(0); + + const versions1 = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions1['order-schema'].version).toBe('1.0.0'); + + // ===== Release 1.1.0 (minor bump - add optional field) ===== + copyFixture( + 'json-schema/order-optional-field-added.json', + path.join(dir, 'schemas/order.json') + ); + + // Auto-detect changes + const changeset2 = run('changeset', dir); + expect(changeset2.exitCode).toBe(0); + + const version2 = run('version', dir); + expect(version2.exitCode).toBe(0); + + const versions2 = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions2['order-schema'].version).toBe('1.1.0'); + + // ===== Release 1.1.1 (patch bump - description change) ===== + copyFixture( + 'json-schema/order-description-changed.json', + path.join(dir, 'schemas/order.json') + ); + + // Create manual patch changeset + const patchChangeset = `--- +"order-schema": patch +--- + +## order-schema + +- **[patch]** Updated description text +`; + writeFile(dir, '.contractual/changesets/patch-desc.md', patchChangeset); + + const version3 = run('version', dir); + expect(version3.exitCode).toBe(0); + + const versions3 = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions3['order-schema'].version).toBe('1.1.1'); + + // ===== Verify changelog has all three versions ===== + const changelog = readFile(dir, 'CHANGELOG.md'); + expect(changelog).toMatch(/1\.0\.0/); + expect(changelog).toMatch(/1\.1\.0/); + expect(changelog).toMatch(/1\.1\.1/); + + // Verify order in changelog (newest first typically) + const v100Index = changelog.indexOf('1.0.0'); + const v110Index = changelog.indexOf('1.1.0'); + const v111Index = changelog.indexOf('1.1.1'); + + // Newest versions should appear first in changelog + expect(v111Index).toBeLessThan(v110Index); + expect(v110Index).toBeLessThan(v100Index); + } finally { + cleanup(); + } + }); + + test('handles multiple contracts with independent versioning', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + { + name: 'user-schema', + type: 'json-schema', + path: 'schemas/user.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/user.json')); + + // ===== Release both contracts at 1.0.0 ===== + const initialChangeset = `--- +"order-schema": major +"user-schema": major +--- + +Initial release of both schemas +`; + writeFile(dir, '.contractual/changesets/initial.md', initialChangeset); + + const version1 = run('version', dir); + expect(version1.exitCode).toBe(0); + + const versions1 = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions1['order-schema'].version).toBe('1.0.0'); + expect(versions1['user-schema'].version).toBe('1.0.0'); + + // ===== Bump only order-schema to 2.0.0 ===== + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + + const orderChangeset = `--- +"order-schema": major +--- + +## order-schema + +- **[BREAKING]** Removed customer_email field +`; + writeFile(dir, '.contractual/changesets/order-breaking.md', orderChangeset); + + const version2 = run('version', dir); + expect(version2.exitCode).toBe(0); + + const versions2 = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions2['order-schema'].version).toBe('2.0.0'); + expect(versions2['user-schema'].version).toBe('1.0.0'); // Unchanged + + // ===== Verify status shows correct versions ===== + const status = run('status', dir); + expect(status.exitCode).toBe(0); + expect(status.stdout).toMatch(/order-schema/); + expect(status.stdout).toMatch(/2\.0\.0/); + expect(status.stdout).toMatch(/user-schema/); + } finally { + cleanup(); + } + }); + + test('version command with no changesets does nothing', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // No changesets - just run version + const result = run('version', dir); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/no pending|nothing to version/i); + + // Version should remain unchanged + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['order-schema'].version).toBe('1.0.0'); + } finally { + cleanup(); + } + }); + + test('changeset command with no changes does nothing', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + // Create snapshot matching current spec + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Spec matches snapshot - no changes + const result = run('changeset', dir); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/no change/i); + + // No changeset should be created + const changesets = listFiles(dir, '.contractual/changesets'); + expect(changesets.filter((f) => f.endsWith('.md'))).toHaveLength(0); + } finally { + cleanup(); + } + }); +}); diff --git a/tests/e2e/08-multi-contract.test.ts b/tests/e2e/08-multi-contract.test.ts new file mode 100644 index 0000000..da31b53 --- /dev/null +++ b/tests/e2e/08-multi-contract.test.ts @@ -0,0 +1,337 @@ +import { describe, test, expect, beforeAll, beforeEach, afterEach } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + setupRepoWithConfig, + writeFile, + readFile, + readJSON, + fileExists, + listFiles, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +describe('multi-contract scenarios', () => { + let tempRepo: { dir: string; cleanup: () => void }; + + beforeEach(() => { + tempRepo = createTempRepo(); + }); + + afterEach(() => { + tempRepo.cleanup(); + }); + + test('lint works with multiple contracts of different types', () => { + const { dir } = tempRepo; + + setupRepoWithConfig(dir, [ + { name: 'petstore-api', type: 'openapi', path: 'specs/petstore.yaml' }, + { name: 'order-schema', type: 'json-schema', path: 'schemas/order.json' }, + { name: 'user-schema', type: 'json-schema', path: 'schemas/user.json' }, + ]); + + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/petstore.yaml')); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/user.json')); + + // OpenAPI linter (Spectral) runs and finds errors in petstore fixture + // JSON Schema linter validates and passes for valid schemas + const result = run('lint --format json', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); // petstore has lint errors + + const parsed = JSON.parse(result.stdout); + // All 3 contracts have results + expect(parsed.results).toHaveLength(3); + // petstore has errors, JSON schemas pass + const petstoreResult = parsed.results.find((r: { contract: string }) => r.contract === 'petstore-api'); + expect(petstoreResult.errors.length).toBeGreaterThan(0); + }); + + test('breaking detects changes in multiple contracts', () => { + const { dir } = tempRepo; + + // Use only json-schema contracts for reliable testing + // (openapi requires oasdiff binary which may not be available) + setupRepoWithConfig(dir, [ + { name: 'order-schema', type: 'json-schema', path: 'schemas/order.json' }, + { name: 'user-schema', type: 'json-schema', path: 'schemas/user.json' }, + ]); + + // Set up snapshots for json-schema contracts + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/user-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + 'user-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Current specs: breaking change in order-schema, no change in user + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/user.json')); + + const result = run('breaking --format json', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + + const parsed = JSON.parse(result.stdout); + expect(parsed.hasBreaking).toBe(true); + expect(parsed.results).toHaveLength(2); + + // Find results by contract name + const orderResult = parsed.results.find( + (r: { contract: string }) => r.contract === 'order-schema' + ); + const userResult = parsed.results.find( + (r: { contract: string }) => r.contract === 'user-schema' + ); + + expect(orderResult.summary.breaking).toBeGreaterThan(0); + expect(userResult.summary.breaking).toBe(0); + expect(userResult.summary.nonBreaking).toBe(0); + }); + + test('changeset includes all changed contracts', () => { + const { dir } = tempRepo; + + // Use only json-schema contracts since openapi requires oasdiff binary + setupRepoWithConfig(dir, [ + { name: 'order-schema', type: 'json-schema', path: 'schemas/order.json' }, + { name: 'user-schema', type: 'json-schema', path: 'schemas/user.json' }, + ]); + + // Set up snapshots + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/user-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + 'user-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Current specs with changes - only order-schema changed + copyFixture( + 'json-schema/order-optional-field-added.json', + path.join(dir, 'schemas/order.json') + ); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/user.json')); // No change + + const result = run('changeset', dir); + expect(result.exitCode).toBe(0); + + // Check that changeset file was created + const changesetFiles = listFiles(dir, '.contractual/changesets').filter(f => f.endsWith('.md')); + expect(changesetFiles.length).toBeGreaterThan(0); + + // Read the changeset file (markdown with YAML frontmatter) + const changesetPath = `.contractual/changesets/${changesetFiles[0]}`; + const changesetContent = readFile(dir, changesetPath); + + // Changeset should include order-schema (which changed) + expect(changesetContent).toContain('order-schema'); + // user-schema should not be included (no changes) + expect(changesetContent).not.toContain('user-schema'); + }); + + test('version bumps multiple contracts independently', () => { + const { dir } = tempRepo; + + setupRepoWithConfig(dir, [ + { name: 'order-schema', type: 'json-schema', path: 'schemas/order.json' }, + { name: 'user-schema', type: 'json-schema', path: 'schemas/user.json' }, + ]); + + // Current specs + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/user.json')); + + // Set up existing versions + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '2.0.0', released: '2026-01-01T00:00:00Z' }, + 'user-schema': { version: '1.0.5', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Create a changeset with different bump types - using markdown format with YAML frontmatter + const changesetId = `changeset-${Date.now()}`; + writeFile( + dir, + `.contractual/changesets/${changesetId}.md`, + `--- +"order-schema": major +"user-schema": patch +--- + +Multiple contracts bumped independently +` + ); + + // Run version command + const result = run('version', dir); + expect(result.exitCode).toBe(0); + + // Check versions were bumped correctly + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + + expect(versions['order-schema'].version).toBe('3.0.0'); // major bump: 2.0.0 -> 3.0.0 + expect(versions['user-schema'].version).toBe('1.0.6'); // patch bump: 1.0.5 -> 1.0.6 + + // Verify snapshots were updated + expect(fileExists(dir, '.contractual/snapshots/order-schema.json')).toBe(true); + expect(fileExists(dir, '.contractual/snapshots/user-schema.json')).toBe(true); + + // Changeset should be consumed (removed) + expect(fileExists(dir, `.contractual/changesets/${changesetId}.md`)).toBe(false); + }); + + test('lint reports errors for invalid specs', () => { + const { dir } = tempRepo; + + // Use lint: false for OpenAPI to avoid petstore errors, focus on JSON Schema validation + setupRepoWithConfig(dir, [ + { name: 'valid-api', type: 'openapi', path: 'specs/valid.yaml', lint: false }, + { name: 'invalid-schema', type: 'json-schema', path: 'schemas/invalid.json' }, + { name: 'another-valid', type: 'json-schema', path: 'schemas/valid.json' }, + ]); + + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/valid.yaml')); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/valid.json')); + // Write invalid JSON Schema + writeFile( + dir, + 'schemas/invalid.json', + JSON.stringify({ + type: 'objekt', // Invalid type value + properties: { + id: { type: 123 }, // type should be string + }, + }) + ); + + // JSON Schema linter finds errors in invalid-schema, so exit code is 1 + const result = run('lint --format json', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + const parsed = JSON.parse(result.stdout); + // Should have 2 results (JSON Schema contracts only, OpenAPI has lint: false) + expect(parsed.results).toHaveLength(2); + // invalid-schema should have errors + const invalidResult = parsed.results.find((r: { contract: string }) => r.contract === 'invalid-schema'); + expect(invalidResult.errors.length).toBeGreaterThan(0); + }); + + test('breaking --contract filters to single contract in multi-contract repo', () => { + const { dir } = tempRepo; + + setupRepoWithConfig(dir, [ + { name: 'petstore-api', type: 'openapi', path: 'specs/petstore.yaml' }, + { name: 'order-schema', type: 'json-schema', path: 'schemas/order.json' }, + ]); + + // Set up snapshots + copyFixture( + 'openapi/petstore-base.yaml', + path.join(dir, '.contractual/snapshots/petstore-api.yaml') + ); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'petstore-api': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Both have breaking changes + copyFixture( + 'openapi/petstore-breaking-endpoint-removed.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + + // Filter to only check order-schema + const result = run('breaking --contract order-schema --format json', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + + const parsed = JSON.parse(result.stdout); + expect(parsed.results).toHaveLength(1); + expect(parsed.results[0].contract).toBe('order-schema'); + expect(parsed.results[0].summary.breaking).toBeGreaterThan(0); + }); + + test('status shows all contracts with their current versions', () => { + const { dir } = tempRepo; + + setupRepoWithConfig(dir, [ + { name: 'petstore-api', type: 'openapi', path: 'specs/petstore.yaml' }, + { name: 'order-schema', type: 'json-schema', path: 'schemas/order.json' }, + { name: 'user-schema', type: 'json-schema', path: 'schemas/user.json' }, + ]); + + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/petstore.yaml')); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/user.json')); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'petstore-api': { version: '1.2.0', released: '2026-01-01T00:00:00Z' }, + 'order-schema': { version: '2.0.0', released: '2026-01-01T00:00:00Z' }, + 'user-schema': { version: '1.0.5', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Status command only outputs text format (no --format json option) + const result = run('status', dir); + expect(result.exitCode).toBe(0); + + // Check for text output containing contract info + expect(result.stdout).toMatch(/petstore-api/); + expect(result.stdout).toMatch(/openapi/); + expect(result.stdout).toMatch(/1\.2\.0/); + + expect(result.stdout).toMatch(/order-schema/); + expect(result.stdout).toMatch(/json-schema/); + expect(result.stdout).toMatch(/2\.0\.0/); + + expect(result.stdout).toMatch(/user-schema/); + expect(result.stdout).toMatch(/1\.0\.5/); + }); +}); diff --git a/tests/e2e/09-differs.json-schema.test.ts b/tests/e2e/09-differs.json-schema.test.ts new file mode 100644 index 0000000..c0a2de1 --- /dev/null +++ b/tests/e2e/09-differs.json-schema.test.ts @@ -0,0 +1,756 @@ +import { describe, test, expect, beforeAll, beforeEach, afterEach } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + setupRepoWithConfig, + writeFile, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +interface BreakingChange { + category: string; + severity: string; + path: string; + message: string; +} + +interface BreakingResult { + contract: string; + changes: BreakingChange[]; + summary: { + breaking: number; + nonBreaking: number; + patch: number; + }; +} + +interface BreakingOutput { + hasBreaking: boolean; + results: BreakingResult[]; +} + +describe('JSON Schema differ exhaustive tests', () => { + let tempRepo: { dir: string; cleanup: () => void }; + + beforeEach(() => { + tempRepo = createTempRepo(); + }); + + afterEach(() => { + tempRepo.cleanup(); + }); + + /** + * Helper to set up a JSON Schema diff test scenario + */ + function setupJsonSchemaDiff(baseFixture: string, currentFixture: string): void { + const { dir } = tempRepo; + + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + // Set up base as snapshot (previous release) + copyFixture(baseFixture, path.join(dir, '.contractual/snapshots/order-schema.json')); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Set up current spec + copyFixture(currentFixture, path.join(dir, 'schemas/order.json')); + } + + describe('breaking changes (exit 1, major)', () => { + test('field removed -> category: property-removed', () => { + setupJsonSchemaDiff('json-schema/order-base.json', 'json-schema/order-field-removed.json'); + + const result = run('breaking --format json', tempRepo.dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + + const parsed: BreakingOutput = JSON.parse(result.stdout); + expect(parsed.hasBreaking).toBe(true); + + const changes = parsed.results[0].changes; + const propertyRemovedChange = changes.find((c) => c.category === 'property-removed'); + expect(propertyRemovedChange).toBeDefined(); + expect(propertyRemovedChange!.severity).toBe('breaking'); + expect(propertyRemovedChange!.path).toContain('customer_email'); + }); + + test('required added -> category: required-added', () => { + setupJsonSchemaDiff('json-schema/order-base.json', 'json-schema/order-required-added.json'); + + const result = run('breaking --format json', tempRepo.dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + + const parsed: BreakingOutput = JSON.parse(result.stdout); + expect(parsed.hasBreaking).toBe(true); + + const changes = parsed.results[0].changes; + const requiredAddedChange = changes.find((c) => c.category === 'required-added'); + expect(requiredAddedChange).toBeDefined(); + expect(requiredAddedChange!.severity).toBe('breaking'); + // The path is /required, but the field name appears in the message + expect(requiredAddedChange!.path).toBe('/required'); + expect(requiredAddedChange!.message).toContain('customer_email'); + }); + + test('type changed -> category: type-changed', () => { + setupJsonSchemaDiff('json-schema/order-base.json', 'json-schema/order-type-changed.json'); + + const result = run('breaking --format json', tempRepo.dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + + const parsed: BreakingOutput = JSON.parse(result.stdout); + expect(parsed.hasBreaking).toBe(true); + + const changes = parsed.results[0].changes; + const typeChangedChange = changes.find((c) => c.category === 'type-changed'); + expect(typeChangedChange).toBeDefined(); + expect(typeChangedChange!.severity).toBe('breaking'); + expect(typeChangedChange!.path).toContain('amount'); + expect(typeChangedChange!.message).toMatch(/string.*number|type/i); + }); + + test('enum value removed -> category: enum-value-removed', () => { + setupJsonSchemaDiff( + 'json-schema/order-base.json', + 'json-schema/order-enum-value-removed.json' + ); + + const result = run('breaking --format json', tempRepo.dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + + const parsed: BreakingOutput = JSON.parse(result.stdout); + expect(parsed.hasBreaking).toBe(true); + + const changes = parsed.results[0].changes; + const enumRemovedChange = changes.find((c) => c.category === 'enum-value-removed'); + expect(enumRemovedChange).toBeDefined(); + expect(enumRemovedChange!.severity).toBe('breaking'); + expect(enumRemovedChange!.path).toContain('status'); + expect(enumRemovedChange!.message).toMatch(/delivered/i); + }); + + test('constraint tightened -> category: constraint-tightened', () => { + setupJsonSchemaDiff( + 'json-schema/order-base.json', + 'json-schema/order-constraint-tightened.json' + ); + + const result = run('breaking --format json', tempRepo.dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + + const parsed: BreakingOutput = JSON.parse(result.stdout); + expect(parsed.hasBreaking).toBe(true); + + const changes = parsed.results[0].changes; + const constraintChange = changes.find((c) => c.category === 'constraint-tightened'); + expect(constraintChange).toBeDefined(); + expect(constraintChange!.severity).toBe('breaking'); + // Should detect maxLength reduction in zip (10 -> 5) or notes (500 -> 200) + expect(constraintChange!.message).toMatch(/maxLength|constraint/i); + }); + + test('additionalProperties denied -> category: additional-properties-denied', () => { + setupJsonSchemaDiff( + 'json-schema/order-base.json', + 'json-schema/order-additional-props-denied.json' + ); + + const result = run('breaking --format json', tempRepo.dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + + const parsed: BreakingOutput = JSON.parse(result.stdout); + expect(parsed.hasBreaking).toBe(true); + + const changes = parsed.results[0].changes; + const additionalPropsChange = changes.find( + (c) => c.category === 'additional-properties-denied' + ); + expect(additionalPropsChange).toBeDefined(); + expect(additionalPropsChange!.severity).toBe('breaking'); + expect(additionalPropsChange!.path).toContain('metadata'); + }); + }); + + describe('non-breaking changes (exit 0, minor)', () => { + test('optional field added -> category: property-added, severity: non-breaking', () => { + setupJsonSchemaDiff( + 'json-schema/order-base.json', + 'json-schema/order-optional-field-added.json' + ); + + const result = run('breaking --format json', tempRepo.dir); + expect(result.exitCode).toBe(0); + + const parsed: BreakingOutput = JSON.parse(result.stdout); + expect(parsed.hasBreaking).toBe(false); + + const changes = parsed.results[0].changes; + const propertyAddedChange = changes.find((c) => c.category === 'property-added'); + expect(propertyAddedChange).toBeDefined(); + expect(propertyAddedChange!.severity).toBe('non-breaking'); + expect(propertyAddedChange!.path).toContain('tracking_number'); + + // Summary should show non-breaking changes + expect(parsed.results[0].summary.nonBreaking).toBeGreaterThan(0); + expect(parsed.results[0].summary.breaking).toBe(0); + }); + + test('enum value added -> category: enum-value-added', () => { + setupJsonSchemaDiff('json-schema/order-base.json', 'json-schema/order-enum-value-added.json'); + + const result = run('breaking --format json', tempRepo.dir); + expect(result.exitCode).toBe(0); + + const parsed: BreakingOutput = JSON.parse(result.stdout); + expect(parsed.hasBreaking).toBe(false); + + const changes = parsed.results[0].changes; + const enumAddedChange = changes.find((c) => c.category === 'enum-value-added'); + expect(enumAddedChange).toBeDefined(); + expect(enumAddedChange!.severity).toBe('non-breaking'); + expect(enumAddedChange!.path).toContain('status'); + expect(enumAddedChange!.message).toMatch(/cancelled/i); + }); + }); + + describe('patch changes (exit 0, patch)', () => { + test('description changed -> category: description-changed', () => { + setupJsonSchemaDiff( + 'json-schema/order-base.json', + 'json-schema/order-description-changed.json' + ); + + const result = run('breaking --format json', tempRepo.dir); + expect(result.exitCode).toBe(0); + + const parsed: BreakingOutput = JSON.parse(result.stdout); + expect(parsed.hasBreaking).toBe(false); + + const changes = parsed.results[0].changes; + const descriptionChange = changes.find((c) => c.category === 'description-changed'); + expect(descriptionChange).toBeDefined(); + expect(descriptionChange!.severity).toBe('patch'); + + // Summary should show patch changes + expect(parsed.results[0].summary.patch).toBeGreaterThan(0); + expect(parsed.results[0].summary.breaking).toBe(0); + }); + }); + + describe('no changes', () => { + test('identical schema -> no changes detected', () => { + setupJsonSchemaDiff('json-schema/order-base.json', 'json-schema/order-identical.json'); + + const result = run('breaking --format json', tempRepo.dir); + expect(result.exitCode).toBe(0); + + const parsed: BreakingOutput = JSON.parse(result.stdout); + expect(parsed.hasBreaking).toBe(false); + expect(parsed.results[0].changes).toHaveLength(0); + expect(parsed.results[0].summary.breaking).toBe(0); + expect(parsed.results[0].summary.nonBreaking).toBe(0); + expect(parsed.results[0].summary.patch).toBe(0); + }); + }); + + describe('multiple changes in single diff', () => { + test('detects multiple breaking changes in one schema update', () => { + const { dir } = tempRepo; + + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + // Use base as snapshot + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Create a schema with multiple breaking changes + const multiBreakingSchema = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: 'https://example.com/order.schema.json', + title: 'Order', + description: 'An order in the system', + type: 'object', + properties: { + id: { + type: 'number', // Type changed from string + description: 'Unique order identifier', + }, + status: { + type: 'string', + enum: ['pending', 'processing'], // Removed 'shipped' and 'delivered' + description: 'Current order status', + }, + // 'amount' property removed + currency: { + type: 'string', + format: 'iso-4217', + description: 'Currency code (ISO 4217)', + }, + items: { + type: 'array', + description: 'Line items in the order', + items: { + type: 'object', + properties: { + sku: { type: 'string' }, + quantity: { type: 'integer', minimum: 1 }, + price: { type: 'string' }, + }, + required: ['sku', 'quantity', 'price'], + }, + }, + created_at: { + type: 'string', + format: 'date-time', + description: 'Order creation timestamp', + }, + }, + required: ['id', 'status', 'currency', 'items', 'created_at', 'customer_email'], // Added required field + additionalProperties: false, + }; + + writeFile(dir, 'schemas/order.json', JSON.stringify(multiBreakingSchema, null, 2)); + + const result = run('breaking --format json', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + + const parsed: BreakingOutput = JSON.parse(result.stdout); + expect(parsed.hasBreaking).toBe(true); + + const changes = parsed.results[0].changes; + const breakingChanges = changes.filter((c) => c.severity === 'breaking'); + + // Should detect multiple breaking changes + expect(breakingChanges.length).toBeGreaterThanOrEqual(2); + + // Check for specific categories + const categories = breakingChanges.map((c) => c.category); + expect(categories).toContain('type-changed'); + expect( + categories.includes('enum-value-removed') || categories.includes('property-removed') + ).toBe(true); + }); + + test('detects mix of breaking and non-breaking changes', () => { + const { dir } = tempRepo; + + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Create a schema with both breaking and non-breaking changes + const mixedChangesSchema = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: 'https://example.com/order.schema.json', + title: 'Order', + description: 'An order in the e-commerce system', // Description changed (patch) + type: 'object', + properties: { + id: { + type: 'number', // Type changed (breaking) + description: 'Unique order identifier', + }, + status: { + type: 'string', + enum: ['pending', 'processing', 'shipped', 'delivered', 'cancelled'], // Added enum value (non-breaking) + description: 'Current order status', + }, + amount: { + type: 'string', + description: 'Order total amount as string', + }, + currency: { + type: 'string', + format: 'iso-4217', + description: 'Currency code (ISO 4217)', + }, + items: { + type: 'array', + description: 'Line items in the order', + items: { + type: 'object', + properties: { + sku: { type: 'string' }, + quantity: { type: 'integer', minimum: 1 }, + price: { type: 'string' }, + }, + required: ['sku', 'quantity', 'price'], + }, + }, + shipping_address: { + type: 'object', + description: 'Shipping address', + properties: { + street: { type: 'string' }, + city: { type: 'string' }, + zip: { type: 'string', maxLength: 10 }, + country: { type: 'string' }, + }, + required: ['street', 'city', 'zip', 'country'], + }, + customer_email: { + type: 'string', + format: 'email', + description: 'Customer email address', + }, + notes: { + type: 'string', + description: 'Optional order notes', + maxLength: 500, + }, + created_at: { + type: 'string', + format: 'date-time', + description: 'Order creation timestamp', + }, + metadata: { + type: 'object', + description: 'Additional metadata', + additionalProperties: true, + }, + tracking_number: { + // New optional property (non-breaking) + type: 'string', + description: 'Shipment tracking number', + }, + }, + required: ['id', 'status', 'amount', 'currency', 'items', 'created_at'], + additionalProperties: false, + }; + + writeFile(dir, 'schemas/order.json', JSON.stringify(mixedChangesSchema, null, 2)); + + const result = run('breaking --format json', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); // Exit 1 because there are breaking changes + + const parsed: BreakingOutput = JSON.parse(result.stdout); + expect(parsed.hasBreaking).toBe(true); + + const { summary } = parsed.results[0]; + expect(summary.breaking).toBeGreaterThan(0); + expect(summary.nonBreaking).toBeGreaterThan(0); + }); + }); + + describe('nested property changes', () => { + test('detects changes in nested object properties', () => { + const { dir } = tempRepo; + + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Create schema with nested property changes (shipping_address.zip maxLength changed) + const nestedChangeSchema = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: 'https://example.com/order.schema.json', + title: 'Order', + description: 'An order in the system', + type: 'object', + properties: { + id: { type: 'string', description: 'Unique order identifier' }, + status: { + type: 'string', + enum: ['pending', 'processing', 'shipped', 'delivered'], + description: 'Current order status', + }, + amount: { type: 'string', description: 'Order total amount as string' }, + currency: { + type: 'string', + format: 'iso-4217', + description: 'Currency code (ISO 4217)', + }, + items: { + type: 'array', + description: 'Line items in the order', + items: { + type: 'object', + properties: { + sku: { type: 'string' }, + quantity: { type: 'integer', minimum: 1 }, + price: { type: 'string' }, + }, + required: ['sku', 'quantity', 'price'], + }, + }, + shipping_address: { + type: 'object', + description: 'Shipping address', + properties: { + street: { type: 'string' }, + city: { type: 'string' }, + zip: { type: 'string', maxLength: 5 }, // Changed from 10 to 5 (breaking) + country: { type: 'string' }, + }, + required: ['street', 'city', 'zip', 'country'], + }, + customer_email: { + type: 'string', + format: 'email', + description: 'Customer email address', + }, + notes: { type: 'string', description: 'Optional order notes', maxLength: 500 }, + created_at: { + type: 'string', + format: 'date-time', + description: 'Order creation timestamp', + }, + metadata: { + type: 'object', + description: 'Additional metadata', + additionalProperties: true, + }, + }, + required: ['id', 'status', 'amount', 'currency', 'items', 'created_at'], + additionalProperties: false, + }; + + writeFile(dir, 'schemas/order.json', JSON.stringify(nestedChangeSchema, null, 2)); + + const result = run('breaking --format json', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + + const parsed: BreakingOutput = JSON.parse(result.stdout); + expect(parsed.hasBreaking).toBe(true); + + const changes = parsed.results[0].changes; + const constraintChange = changes.find((c) => c.category === 'constraint-tightened'); + expect(constraintChange).toBeDefined(); + expect(constraintChange!.path).toContain('shipping_address'); + expect(constraintChange!.path).toContain('zip'); + }); + + test('detects changes in array item schema', () => { + const { dir } = tempRepo; + + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Create schema with array item changes (items[].sku type changed) + const arrayItemChangeSchema = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: 'https://example.com/order.schema.json', + title: 'Order', + description: 'An order in the system', + type: 'object', + properties: { + id: { type: 'string', description: 'Unique order identifier' }, + status: { + type: 'string', + enum: ['pending', 'processing', 'shipped', 'delivered'], + description: 'Current order status', + }, + amount: { type: 'string', description: 'Order total amount as string' }, + currency: { + type: 'string', + format: 'iso-4217', + description: 'Currency code (ISO 4217)', + }, + items: { + type: 'array', + description: 'Line items in the order', + items: { + type: 'object', + properties: { + sku: { type: 'number' }, // Changed from string to number (breaking) + quantity: { type: 'integer', minimum: 1 }, + price: { type: 'string' }, + }, + required: ['sku', 'quantity', 'price'], + }, + }, + shipping_address: { + type: 'object', + description: 'Shipping address', + properties: { + street: { type: 'string' }, + city: { type: 'string' }, + zip: { type: 'string', maxLength: 10 }, + country: { type: 'string' }, + }, + required: ['street', 'city', 'zip', 'country'], + }, + customer_email: { + type: 'string', + format: 'email', + description: 'Customer email address', + }, + notes: { type: 'string', description: 'Optional order notes', maxLength: 500 }, + created_at: { + type: 'string', + format: 'date-time', + description: 'Order creation timestamp', + }, + metadata: { + type: 'object', + description: 'Additional metadata', + additionalProperties: true, + }, + }, + required: ['id', 'status', 'amount', 'currency', 'items', 'created_at'], + additionalProperties: false, + }; + + writeFile(dir, 'schemas/order.json', JSON.stringify(arrayItemChangeSchema, null, 2)); + + const result = run('breaking --format json', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + + const parsed: BreakingOutput = JSON.parse(result.stdout); + expect(parsed.hasBreaking).toBe(true); + + const changes = parsed.results[0].changes; + const typeChange = changes.find((c) => c.category === 'type-changed'); + expect(typeChange).toBeDefined(); + expect(typeChange!.path).toContain('items'); + expect(typeChange!.path).toContain('sku'); + }); + }); + + describe('edge cases', () => { + test('handles first version (no snapshot)', () => { + const { dir } = tempRepo; + + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + // No snapshot exists - first version + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + const result = run('breaking --format json', dir); + expect(result.exitCode).toBe(0); + + const parsed = JSON.parse(result.stdout); + expect(parsed.hasBreaking).toBe(false); + // First version with no snapshot shows the contract with no changes + if (parsed.results.length > 0) { + expect(parsed.results[0].changes).toHaveLength(0); + } + }); + + test('handles contract with breaking detection disabled', () => { + const { dir } = tempRepo; + + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + breaking: false, + }, + ]); + + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Even with breaking changes, should pass because breaking detection is disabled + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + + const result = run('breaking', dir); + expect(result.exitCode).toBe(0); + // When breaking detection is disabled, shows "No changes detected" + expect(result.stdout).toMatch(/no changes detected/i); + }); + }); +}); diff --git a/tests/e2e/10-openapi-differ.test.ts b/tests/e2e/10-openapi-differ.test.ts new file mode 100644 index 0000000..984ec81 --- /dev/null +++ b/tests/e2e/10-openapi-differ.test.ts @@ -0,0 +1,247 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + setupRepoWithConfig, + writeFile, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +describe('OpenAPI differ (native)', () => { + test('detects breaking change when endpoint is removed', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + }, + ]); + + // Set up base spec as snapshot (simulating previous release) + copyFixture( + 'openapi/petstore-base.yaml', + path.join(dir, '.contractual/snapshots/petstore.yaml') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + petstore: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Current spec has endpoint removed (breaking change) + copyFixture( + 'openapi/petstore-breaking-endpoint-removed.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + const result = run('breaking', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + expect(result.stdout).toMatch(/breaking/i); + } finally { + cleanup(); + } + }); + + test('detects non-breaking change when endpoint is added', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + }, + ]); + + // Set up base spec as snapshot + copyFixture( + 'openapi/petstore-base.yaml', + path.join(dir, '.contractual/snapshots/petstore.yaml') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + petstore: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Current spec has new endpoint added (non-breaking change) + copyFixture( + 'openapi/petstore-nonbreaking-endpoint-added.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + const result = run('breaking', dir); + // Non-breaking changes should exit 0 + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/non-breaking|minor|no breaking/i); + } finally { + cleanup(); + } + }); + + test('reports no changes when specs are identical', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + }, + ]); + + // Set up base spec as snapshot + copyFixture( + 'openapi/petstore-base.yaml', + path.join(dir, '.contractual/snapshots/petstore.yaml') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + petstore: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Current spec is identical to snapshot + copyFixture('openapi/petstore-identical.yaml', path.join(dir, 'specs/petstore.yaml')); + + const result = run('breaking', dir); + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/no.*change|no breaking|identical/i); + } finally { + cleanup(); + } + }); + + test('detects breaking change when response type is changed', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + }, + ]); + + // Set up base spec as snapshot + copyFixture( + 'openapi/petstore-base.yaml', + path.join(dir, '.contractual/snapshots/petstore.yaml') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + petstore: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Current spec has type change (breaking) + copyFixture( + 'openapi/petstore-breaking-type-changed.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + const result = run('breaking', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + expect(result.stdout).toMatch(/breaking/i); + } finally { + cleanup(); + } + }); + + test('detects non-breaking change when only description is updated', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + }, + ]); + + // Set up base spec as snapshot + copyFixture( + 'openapi/petstore-base.yaml', + path.join(dir, '.contractual/snapshots/petstore.yaml') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + petstore: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Current spec has only description changed (non-breaking) + copyFixture( + 'openapi/petstore-nonbreaking-description.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + const result = run('breaking', dir); + expect(result.exitCode).toBe(0); + } finally { + cleanup(); + } + }); + + test('returns JSON output with --format json flag', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + }, + ]); + + // Set up base spec as snapshot + copyFixture( + 'openapi/petstore-base.yaml', + path.join(dir, '.contractual/snapshots/petstore.yaml') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + petstore: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Current spec has breaking change + copyFixture( + 'openapi/petstore-breaking-endpoint-removed.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + const result = run('breaking --format json', dir, { expectFail: true }); + const parsed = JSON.parse(result.stdout); + + expect(parsed).toHaveProperty('results'); + expect(parsed).toHaveProperty('hasBreaking'); + expect(parsed.hasBreaking).toBe(true); + expect(Array.isArray(parsed.results)).toBe(true); + expect(parsed.results.length).toBeGreaterThan(0); + expect(parsed.results[0]).toHaveProperty('contract', 'petstore'); + } finally { + cleanup(); + } + }); +}); diff --git a/tests/e2e/11-custom-commands.test.ts b/tests/e2e/11-custom-commands.test.ts new file mode 100644 index 0000000..8edeb72 --- /dev/null +++ b/tests/e2e/11-custom-commands.test.ts @@ -0,0 +1,330 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + setupRepoWithConfig, + writeFile, + fileExists, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +describe('custom command overrides', () => { + describe('custom lint command', () => { + test('uses custom lint command with {spec} placeholder', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Create a custom lint script that creates a marker file + writeFile( + dir, + 'custom-lint.sh', + `#!/bin/bash +echo "Custom lint executed for: $1" +touch "${dir}/lint-marker.txt" +exit 0 +` + ); + + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + lint: `bash ${dir}/custom-lint.sh {spec}`, + }, + ]); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/petstore.yaml')); + + const result = run('lint', dir); + expect(result.exitCode).toBe(0); + // Custom command was executed - marker file should exist + expect(fileExists(dir, 'lint-marker.txt')).toBe(true); + } finally { + cleanup(); + } + }); + + test('custom lint command without {spec} uses default linter', () => { + const { dir, cleanup } = createTempRepo(); + try { + // A lint override that doesn't contain {spec} falls through to default linter + // The petstore fixture has lint errors, so the default linter will exit 1 + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + lint: 'exit 1', // No {spec} - falls through to default linter + }, + ]); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/petstore.yaml')); + + // Default OpenAPI linter runs and finds errors in the petstore fixture + const result = run('lint', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + // Should show lint errors from the default linter + expect(result.stdout).toMatch(/error/i); + } finally { + cleanup(); + } + }); + + test('lint: false disables linting for contract', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + lint: false, + }, + ]); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/petstore.yaml')); + + const result = run('lint', dir); + expect(result.exitCode).toBe(0); + // Spinner output goes to TTY, not captured in stdout + // JSON format shows empty results when linting is disabled + const jsonResult = run('lint --format json', dir); + const parsed = JSON.parse(jsonResult.stdout); + expect(parsed.results).toHaveLength(0); + expect(parsed.errors).toBe(0); + } finally { + cleanup(); + } + }); + + test('lint: false with invalid spec still passes (linting skipped)', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'bad-spec', + type: 'openapi', + path: 'specs/bad.yaml', + lint: false, + }, + ]); + // Write intentionally invalid OpenAPI + writeFile( + dir, + 'specs/bad.yaml', + `openapi: 3.0.0 +info: + title: Invalid + # Missing version field - normally invalid +paths: [] # paths should be object, not array +` + ); + + const result = run('lint', dir); + expect(result.exitCode).toBe(0); + // Verify via JSON that the contract was skipped (not in results) + const jsonResult = run('lint --format json', dir); + const parsed = JSON.parse(jsonResult.stdout); + expect(parsed.results).toHaveLength(0); + } finally { + cleanup(); + } + }); + }); + + describe('custom breaking command', () => { + test('uses custom breaking command with {old} and {new} placeholders', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Create a custom breaking detection script + writeFile( + dir, + 'custom-breaking.sh', + `#!/bin/bash +echo "Comparing old: $1 with new: $2" +touch "${dir}/breaking-marker.txt" +exit 0 +` + ); + + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + breaking: `bash ${dir}/custom-breaking.sh {old} {new}`, + }, + ]); + + // Set up snapshot + copyFixture( + 'openapi/petstore-base.yaml', + path.join(dir, '.contractual/snapshots/petstore.yaml') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + petstore: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Current spec with changes + copyFixture( + 'openapi/petstore-nonbreaking-endpoint-added.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + const result = run('breaking', dir); + expect(result.exitCode).toBe(0); + // Custom command was executed - marker file should exist + expect(fileExists(dir, 'breaking-marker.txt')).toBe(true); + } finally { + cleanup(); + } + }); + + test('custom breaking command without placeholders uses default differ', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Custom command without {old} or {new} falls through to default differ + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + breaking: 'echo "Breaking change detected" && exit 1', + }, + ]); + + // Set up snapshot + copyFixture( + 'openapi/petstore-base.yaml', + path.join(dir, '.contractual/snapshots/petstore.yaml') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + petstore: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + // Same spec as snapshot - no changes, default differ will pass + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/petstore.yaml')); + + // Default differ runs - with identical specs, should pass (no breaking changes) + const result = run('breaking', dir); + expect(result.exitCode).toBe(0); + } finally { + cleanup(); + } + }); + + test('breaking: false disables breaking detection for contract', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + breaking: false, + }, + ]); + + // Set up snapshot with breaking change in current + copyFixture( + 'openapi/petstore-base.yaml', + path.join(dir, '.contractual/snapshots/petstore.yaml') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + petstore: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + // Even with a breaking change, it should pass because breaking detection is disabled + copyFixture( + 'openapi/petstore-breaking-endpoint-removed.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + const result = run('breaking', dir); + expect(result.exitCode).toBe(0); + // When breaking detection is disabled, shows "No changes detected" + expect(result.stdout).toMatch(/no changes detected/i); + } finally { + cleanup(); + } + }); + }); + + describe('mixed custom and default commands', () => { + test('can mix custom lint with default breaking', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Custom lint command with {spec} - will be executed + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + lint: 'echo "Custom lint for {spec}" && exit 0', + // No custom breaking - uses default + }, + ]); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/petstore.yaml')); + + // Custom lint command with {spec} runs and exits 0 + const lintResult = run('lint', dir); + expect(lintResult.exitCode).toBe(0); + + // Default breaking (no snapshot) - exits 0 + const breakingResult = run('breaking', dir); + expect(breakingResult.exitCode).toBe(0); + } finally { + cleanup(); + } + }); + + test('can have different settings per contract', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'api-v1', + type: 'openapi', + path: 'specs/api-v1.yaml', + lint: false, // Disable lint for v1 + breaking: false, // Disable breaking for v1 + }, + { + name: 'api-v2', + type: 'openapi', + path: 'specs/api-v2.yaml', + // Use defaults for v2 - will use default OpenAPI linter + }, + ]); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/api-v1.yaml')); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/api-v2.yaml')); + + // The petstore fixture has lint errors, so api-v2 will cause exit 1 + const result = run('lint --format json', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + const parsed = JSON.parse(result.stdout); + + // api-v1 is skipped (lint: false), api-v2 is linted with default linter + expect(parsed.results).toHaveLength(1); + expect(parsed.results[0].contract).toBe('api-v2'); + // api-v2 has lint errors from the petstore fixture + expect(parsed.errors).toBeGreaterThan(0); + } finally { + cleanup(); + } + }); + }); +}); diff --git a/tests/e2e/11-openapi-31-differ.test.ts b/tests/e2e/11-openapi-31-differ.test.ts new file mode 100644 index 0000000..7b9c241 --- /dev/null +++ b/tests/e2e/11-openapi-31-differ.test.ts @@ -0,0 +1,204 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + setupRepoWithConfig, + writeFile, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +describe('OpenAPI 3.1 differ', () => { + test('detects breaking change when endpoint is removed (3.1 spec)', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + }, + ]); + + copyFixture( + 'openapi/petstore-31-base.yaml', + path.join(dir, '.contractual/snapshots/petstore.yaml') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + petstore: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + copyFixture( + 'openapi/petstore-31-breaking-endpoint-removed.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + const result = run('breaking', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + expect(result.stdout).toMatch(/breaking/i); + } finally { + cleanup(); + } + }); + + test('detects breaking change when type is narrowed (3.1 type arrays)', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + }, + ]); + + copyFixture( + 'openapi/petstore-31-base.yaml', + path.join(dir, '.contractual/snapshots/petstore.yaml') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + petstore: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // tag field narrowed from ["string", "null"] to "string" + copyFixture( + 'openapi/petstore-31-breaking-type-narrowed.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + const result = run('breaking', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + expect(result.stdout).toMatch(/breaking/i); + } finally { + cleanup(); + } + }); + + test('detects non-breaking change when type is widened (3.1 type arrays)', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + }, + ]); + + copyFixture( + 'openapi/petstore-31-base.yaml', + path.join(dir, '.contractual/snapshots/petstore.yaml') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + petstore: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // id field widened from integer to [integer, string] + copyFixture( + 'openapi/petstore-31-nonbreaking-type-widened.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + const result = run('breaking', dir); + expect(result.exitCode).toBe(0); + } finally { + cleanup(); + } + }); + + test('reports no changes when 3.1 specs are identical', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + }, + ]); + + copyFixture( + 'openapi/petstore-31-base.yaml', + path.join(dir, '.contractual/snapshots/petstore.yaml') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + petstore: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Same spec as snapshot + copyFixture( + 'openapi/petstore-31-base.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + const result = run('breaking', dir); + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/no.*change|no breaking|identical/i); + } finally { + cleanup(); + } + }); + + test('returns JSON output for 3.1 spec with --format json', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + }, + ]); + + copyFixture( + 'openapi/petstore-31-base.yaml', + path.join(dir, '.contractual/snapshots/petstore.yaml') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + petstore: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + copyFixture( + 'openapi/petstore-31-breaking-endpoint-removed.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + const result = run('breaking --format json', dir, { expectFail: true }); + const parsed = JSON.parse(result.stdout); + + expect(parsed).toHaveProperty('results'); + expect(parsed).toHaveProperty('hasBreaking'); + expect(parsed.hasBreaking).toBe(true); + expect(Array.isArray(parsed.results)).toBe(true); + expect(parsed.results.length).toBeGreaterThan(0); + expect(parsed.results[0]).toHaveProperty('contract', 'petstore'); + } finally { + cleanup(); + } + }); +}); diff --git a/tests/e2e/12-edge-cases.test.ts b/tests/e2e/12-edge-cases.test.ts new file mode 100644 index 0000000..b4eeda0 --- /dev/null +++ b/tests/e2e/12-edge-cases.test.ts @@ -0,0 +1,507 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + setupRepoWithConfig, + writeFile, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +describe('error handling and edge cases', () => { + describe('missing contractual.yaml', () => { + test('exits with error suggesting init when no config exists', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Run lint without any config + const result = run('lint', dir, { expectFail: true }); + expect(result.exitCode).not.toBe(0); + expect(result.stdout + result.stderr).toMatch(/contractual\.yaml|not found|init/i); + } finally { + cleanup(); + } + }); + + test('breaking command suggests init when no config exists', () => { + const { dir, cleanup } = createTempRepo(); + try { + const result = run('breaking', dir, { expectFail: true }); + expect(result.exitCode).not.toBe(0); + expect(result.stdout + result.stderr).toMatch(/contractual\.yaml|not found|init/i); + } finally { + cleanup(); + } + }); + + test('status command suggests init when no config exists', () => { + const { dir, cleanup } = createTempRepo(); + try { + const result = run('status', dir, { expectFail: true }); + expect(result.exitCode).not.toBe(0); + expect(result.stdout + result.stderr).toMatch(/contractual\.yaml|not found|init/i); + } finally { + cleanup(); + } + }); + }); + + describe('invalid YAML configuration', () => { + test('exits with parse error for malformed YAML', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Write invalid YAML syntax + writeFile( + dir, + 'contractual.yaml', + `contracts: + - name: api + type: openapi + path: specs/api.yaml + - name: broken + type: [this is invalid yaml + path: nowhere +` + ); + + const result = run('lint', dir, { expectFail: true }); + expect(result.exitCode).not.toBe(0); + expect(result.stdout + result.stderr).toMatch(/parse|yaml|syntax|invalid/i); + } finally { + cleanup(); + } + }); + + test('exits with error for invalid config structure', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Write valid YAML but invalid config structure + writeFile( + dir, + 'contractual.yaml', + `contracts: "this should be an array" +` + ); + + const result = run('lint', dir, { expectFail: true }); + expect(result.exitCode).not.toBe(0); + expect(result.stdout + result.stderr).toMatch(/invalid|error|array/i); + } finally { + cleanup(); + } + }); + + test('handles empty config file gracefully', () => { + const { dir, cleanup } = createTempRepo(); + try { + writeFile(dir, 'contractual.yaml', ''); + + const result = run('lint', dir, { expectFail: true }); + expect(result.exitCode).not.toBe(0); + expect(result.stdout + result.stderr).toMatch(/empty|invalid|error/i); + } finally { + cleanup(); + } + }); + }); + + describe('spec file not found', () => { + test('lint reports error when spec file does not exist', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'missing-api', + type: 'openapi', + path: 'specs/nonexistent.yaml', + }, + ]); + + // Config loader warns about missing files but continues + // Linter will fail when trying to read the missing file + const result = run('lint', dir, { expectFail: true }); + expect(result.exitCode).not.toBe(0); + expect(result.stdout + result.stderr).toMatch(/warning|not found|no such file|ENOENT/i); + } finally { + cleanup(); + } + }); + + test('breaking command handles missing spec file gracefully', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'missing-api', + type: 'openapi', + path: 'specs/nonexistent.yaml', + }, + ]); + // Set up snapshot but no current spec + copyFixture( + 'openapi/petstore-base.yaml', + path.join(dir, '.contractual/snapshots/missing-api.yaml') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'missing-api': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Config loader warns about missing file but continues + // Breaking command may fail during diff (if oasdiff available) or report no differ + const result = run('breaking', dir, { expectFail: true }); + // Should mention the missing file or failed check in output + expect(result.stdout + result.stderr).toMatch(/warning|not found|failed|error|no contracts/i); + } finally { + cleanup(); + } + }); + + test('lints valid contracts and reports error for missing spec', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'valid-api', + type: 'openapi', + path: 'specs/valid.yaml', + }, + { + name: 'missing-api', + type: 'openapi', + path: 'specs/nonexistent.yaml', + }, + ]); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/valid.yaml')); + + // Config loader emits a warning about missing files but continues + // Lint will process valid contract and fail on missing one + const result = run('lint', dir, { expectFail: true }); + // Should mention the missing file + expect(result.stdout + result.stderr).toMatch(/nonexistent|not found|ENOENT/i); + } finally { + cleanup(); + } + }); + }); + + describe('corrupt versions.json', () => { + test('status handles corrupt/invalid JSON in versions.json gracefully', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + }, + ]); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/petstore.yaml')); + + // Write corrupt JSON to versions.json + writeFile(dir, '.contractual/versions.json', '{ this is not valid json }}}'); + + // The readVersions function in status.command.ts returns {} on parse error + // So the status command should succeed, treating all contracts as unreleased + const result = run('status', dir); + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/unreleased|0\.0\.0/i); + } finally { + cleanup(); + } + }); + + test('status handles versions.json with wrong structure gracefully', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + }, + ]); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/petstore.yaml')); + + // Write valid JSON but wrong structure (array instead of object) + // This will be treated as invalid, and status will show contracts as unreleased + writeFile(dir, '.contractual/versions.json', JSON.stringify(['not', 'an', 'object'])); + + const result = run('status', dir); + // Status command should succeed - it may treat versions as empty + expect(result.exitCode).toBe(0); + } finally { + cleanup(); + } + }); + + test('breaking handles missing snapshot gracefully', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + }, + ]); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/petstore.yaml')); + + // No snapshot exists for this contract - first version + const result = run('breaking', dir); + // Should handle gracefully - exits with 0 + expect(result.exitCode).toBe(0); + // When no snapshot exists, shows "No changes detected" + expect(result.stdout).toMatch(/no changes detected/i); + } finally { + cleanup(); + } + }); + }); + + describe('changeset references unknown contract', () => { + test('status shows changeset referencing unknown contract', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + }, + ]); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/petstore.yaml')); + + // Create a changeset that references an unknown contract + // Format: YAML frontmatter with "contract-name": bump-type + writeFile( + dir, + '.contractual/changesets/orphan-change.md', + `--- +"unknown-contract": minor +--- + +This references a contract that does not exist +` + ); + + // Status command should succeed - it shows the changeset + // but the projected version won't be shown for an unknown contract + const result = run('status', dir); + expect(result.exitCode).toBe(0); + // Should list the changeset + expect(result.stdout).toMatch(/changeset|pending/i); + } finally { + cleanup(); + } + }); + + test('version command handles changeset with unknown contract', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + }, + ]); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/petstore.yaml')); + + // Create a valid changeset for known contract + writeFile( + dir, + '.contractual/changesets/valid-change.md', + `--- +"petstore": patch +--- + +A valid change +` + ); + + // Create a changeset for unknown contract + writeFile( + dir, + '.contractual/changesets/orphan-change.md', + `--- +"ghost-api": major +--- + +This references a non-existent contract +` + ); + + const result = run('version', dir); + // Should process changesets - the unknown one will be in the aggregation + expect(result.exitCode).toBe(0); + } finally { + cleanup(); + } + }); + }); + + describe('empty contracts array', () => { + test('lint rejects empty contracts array per schema validation', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, []); + + // Empty contracts array is invalid per config schema (minItems: 1) + const result = run('lint', dir, { expectFail: true }); + expect(result.exitCode).not.toBe(0); + expect(result.stdout + result.stderr).toMatch(/invalid|error|contract|minItems/i); + } finally { + cleanup(); + } + }); + + test('breaking rejects empty contracts array per schema validation', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, []); + + // Empty contracts array is invalid per config schema (minItems: 1) + const result = run('breaking', dir, { expectFail: true }); + expect(result.exitCode).not.toBe(0); + expect(result.stdout + result.stderr).toMatch(/invalid|error|contract|minItems/i); + } finally { + cleanup(); + } + }); + + test('status rejects empty contracts array per schema validation', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, []); + + // Empty contracts array is invalid per config schema (minItems: 1) + const result = run('status', dir, { expectFail: true }); + expect(result.exitCode).not.toBe(0); + expect(result.stdout + result.stderr).toMatch(/invalid|error|contract|minItems/i); + } finally { + cleanup(); + } + }); + }); + + describe('special characters in paths', () => { + test('handles spec path with spaces', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'spaced-api', + type: 'openapi', + path: 'specs/my api spec.yaml', + lint: false, // Disable linting to avoid errors from fixture spec + }, + ]); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/my api spec.yaml')); + + const result = run('lint', dir); + expect(result.exitCode).toBe(0); + } finally { + cleanup(); + } + }); + + test('handles contract name with dashes and numbers', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Contract names must match pattern: ^[a-z][a-z0-9-]*$ + // So dashes and numbers are allowed, but not dots + setupRepoWithConfig(dir, [ + { + name: 'api-v2-beta', + type: 'openapi', + path: 'specs/api.yaml', + lint: false, // Disable linting to avoid errors from fixture spec + }, + ]); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/api.yaml')); + + const result = run('lint', dir); + expect(result.exitCode).toBe(0); + } finally { + cleanup(); + } + }); + }); + + describe('permission and file system errors', () => { + test('lint reports permission error when spec file is unreadable', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + }, + ]); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/petstore.yaml')); + + // Make file unreadable (only works on Unix-like systems) + try { + require('node:fs').chmodSync(path.join(dir, 'specs/petstore.yaml'), 0o000); + } catch { + // Skip this test on systems where chmod doesn't work + return; + } + + try { + // Linter will fail when trying to read the file + const result = run('lint', dir, { expectFail: true }); + expect(result.exitCode).not.toBe(0); + expect(result.stdout + result.stderr).toMatch(/permission|EACCES|denied|error/i); + } finally { + // Restore permissions for cleanup + require('node:fs').chmodSync(path.join(dir, 'specs/petstore.yaml'), 0o644); + } + } finally { + cleanup(); + } + }); + }); + + describe('concurrent operations', () => { + test('handles being run in parallel (no race conditions)', async () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + lint: false, // Disable linting to avoid errors from fixture spec + breaking: false, // Disable breaking to avoid needing snapshot + }, + ]); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/petstore.yaml')); + + // Run multiple commands in parallel + // Note: status command does not have --format flag + const results = await Promise.all([ + Promise.resolve(run('lint', dir)), + Promise.resolve(run('status', dir)), + Promise.resolve(run('breaking', dir)), + ]); + + // All should succeed + for (const result of results) { + expect(result.exitCode).toBe(0); + } + } finally { + cleanup(); + } + }); + }); +}); diff --git a/tests/e2e/12-openapi-circular-refs.test.ts b/tests/e2e/12-openapi-circular-refs.test.ts new file mode 100644 index 0000000..fb94d65 --- /dev/null +++ b/tests/e2e/12-openapi-circular-refs.test.ts @@ -0,0 +1,91 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + setupRepoWithConfig, + writeFile, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +describe('OpenAPI differ with circular $ref schemas', () => { + test('handles identical specs with circular refs without stack overflow', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'circular-api', + type: 'openapi', + path: 'specs/api.yaml', + }, + ]); + + copyFixture( + 'openapi/circular-refs-base.yaml', + path.join(dir, '.contractual/snapshots/circular-api.yaml') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'circular-api': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Same spec β€” should produce no changes, not a stack overflow + copyFixture( + 'openapi/circular-refs-base.yaml', + path.join(dir, 'specs/api.yaml') + ); + + // 10s timeout β€” stack overflow would hang/crash without it + const result = run('breaking', dir, { timeout: 10_000 }); + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/no.*change|no breaking|identical/i); + } finally { + cleanup(); + } + }); + + test('detects breaking change in schema with circular refs', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'circular-api', + type: 'openapi', + path: 'specs/api.yaml', + }, + ]); + + copyFixture( + 'openapi/circular-refs-base.yaml', + path.join(dir, '.contractual/snapshots/circular-api.yaml') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'circular-api': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Category.id changed from integer to string β€” breaking + copyFixture( + 'openapi/circular-refs-breaking.yaml', + path.join(dir, 'specs/api.yaml') + ); + + const result = run('breaking', dir, { expectFail: true, timeout: 10_000 }); + expect(result.exitCode).toBe(1); + expect(result.stdout).toMatch(/breaking/i); + } finally { + cleanup(); + } + }); +}); diff --git a/tests/e2e/13-config-variations.test.ts b/tests/e2e/13-config-variations.test.ts new file mode 100644 index 0000000..b091bb6 --- /dev/null +++ b/tests/e2e/13-config-variations.test.ts @@ -0,0 +1,448 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + setupRepoWithConfig, + writeFile, + readYAML, + fileExists, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +describe('config variations', () => { + test('minimal config (just contracts array) works', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Write minimal config with only required fields + // Use lint: false to avoid petstore fixture lint errors + writeFile( + dir, + 'contractual.yaml', + `contracts: + - name: api + type: openapi + path: specs/api.yaml + lint: false +` + ); + + // Create .contractual directory structure + writeFile(dir, '.contractual/versions.json', '{}'); + + // Copy the spec file + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/api.yaml')); + + // Lint should work with minimal config (lint disabled) + const result = run('lint', dir); + expect(result.exitCode).toBe(0); + + // Verify config was parsed correctly + const config = readYAML(dir, 'contractual.yaml') as { contracts: Array<{ name: string; type: string; path: string }> }; + expect(config.contracts).toHaveLength(1); + expect(config.contracts[0].name).toBe('api'); + expect(config.contracts[0].type).toBe('openapi'); + } finally { + cleanup(); + } + }); + + test('config with changeset options (autoDetect, requireOnPR)', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Write config with changeset options + // Use lint: false to avoid petstore fixture lint errors + writeFile( + dir, + 'contractual.yaml', + `contracts: + - name: api + type: openapi + path: specs/api.yaml + lint: false +changeset: + autoDetect: true + requireOnPR: false +` + ); + + writeFile(dir, '.contractual/versions.json', '{}'); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/api.yaml')); + + // Config should be valid and lint should work + const result = run('lint', dir); + expect(result.exitCode).toBe(0); + + // Verify changeset options are preserved + const config = readYAML(dir, 'contractual.yaml') as { + contracts: Array; + changeset: { autoDetect: boolean; requireOnPR: boolean }; + }; + expect(config.changeset).toBeDefined(); + expect(config.changeset.autoDetect).toBe(true); + expect(config.changeset.requireOnPR).toBe(false); + } finally { + cleanup(); + } + }); + + test('config with AI options (disabled by default)', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Write config with AI options - all features disabled + // Use lint: false to avoid petstore fixture lint errors + writeFile( + dir, + 'contractual.yaml', + `contracts: + - name: api + type: openapi + path: specs/api.yaml + lint: false +ai: + provider: anthropic + model: claude-3-sonnet + features: + explain: false + changelog: false + enhance: false +` + ); + + writeFile(dir, '.contractual/versions.json', '{}'); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/api.yaml')); + + // Config should be valid + const result = run('lint', dir); + expect(result.exitCode).toBe(0); + + // Verify AI options are preserved + const config = readYAML(dir, 'contractual.yaml') as { + contracts: Array; + ai: { + provider: string; + model: string; + features: { explain: boolean; changelog: boolean; enhance: boolean }; + }; + }; + expect(config.ai).toBeDefined(); + expect(config.ai.provider).toBe('anthropic'); + expect(config.ai.model).toBe('claude-3-sonnet'); + expect(config.ai.features.explain).toBe(false); + expect(config.ai.features.changelog).toBe(false); + expect(config.ai.features.enhance).toBe(false); + } finally { + cleanup(); + } + }); + + test('glob patterns in contract path work', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Write config with glob pattern and lint disabled + writeFile( + dir, + 'contractual.yaml', + `contracts: + - name: all-schemas + type: json-schema + path: schemas/*.json + lint: false +` + ); + + writeFile(dir, '.contractual/versions.json', '{}'); + + // Create multiple schema files matching the glob + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + // Lint should work with glob pattern (lint disabled) + const result = run('lint', dir); + expect(result.exitCode).toBe(0); + + // Verify the file exists at the expected location + expect(fileExists(dir, 'schemas/order.json')).toBe(true); + } finally { + cleanup(); + } + }); + + test('contract with all optional fields specified', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Write config with all optional contract fields + writeFile( + dir, + 'contractual.yaml', + `contracts: + - name: full-contract + type: openapi + path: specs/api.yaml + lint: spectral + breaking: oasdiff + generate: + - openapi-generator generate -i specs/api.yaml -g typescript-axios -o ./client +` + ); + + writeFile(dir, '.contractual/versions.json', '{}'); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/api.yaml')); + + // Use status command to verify config is valid (lint would fail due to petstore fixture errors) + const result = run('status', dir); + expect(result.exitCode).toBe(0); + + // Verify all optional fields are preserved + const config = readYAML(dir, 'contractual.yaml') as { + contracts: Array<{ + name: string; + type: string; + path: string; + lint: string; + breaking: string; + generate: string[]; + }>; + }; + expect(config.contracts[0].lint).toBe('spectral'); + expect(config.contracts[0].breaking).toBe('oasdiff'); + expect(config.contracts[0].generate).toEqual([ + 'openapi-generator generate -i specs/api.yaml -g typescript-axios -o ./client', + ]); + } finally { + cleanup(); + } + }); + + test('config with multiple contract types and mixed options', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Write complex config with multiple contracts and various options + writeFile( + dir, + 'contractual.yaml', + `contracts: + - name: api + type: openapi + path: specs/api.yaml + lint: spectral + - name: events + type: json-schema + path: schemas/events.json + lint: false + breaking: false +changeset: + autoDetect: true + requireOnPR: true +ai: + provider: anthropic + features: + explain: true + changelog: false +` + ); + + writeFile(dir, '.contractual/versions.json', '{}'); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/api.yaml')); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/events.json')); + + // Use status command to verify config is valid (lint would fail due to petstore fixture errors) + const result = run('status', dir); + expect(result.exitCode).toBe(0); + + // Verify config structure + const config = readYAML(dir, 'contractual.yaml') as { + contracts: Array<{ name: string; lint?: string | boolean; breaking?: string | boolean }>; + changeset: { autoDetect: boolean; requireOnPR: boolean }; + ai: { provider: string; features: { explain: boolean; changelog: boolean } }; + }; + expect(config.contracts).toHaveLength(2); + expect(config.contracts[0].lint).toBe('spectral'); + expect(config.contracts[1].lint).toBe(false); + expect(config.contracts[1].breaking).toBe(false); + expect(config.changeset.autoDetect).toBe(true); + expect(config.ai.features.explain).toBe(true); + } finally { + cleanup(); + } + }); + + test('config with nested directory structure for specs', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Write config pointing to deeply nested spec, with lint disabled + writeFile( + dir, + 'contractual.yaml', + `contracts: + - name: nested-api + type: openapi + path: src/contracts/api/v1/openapi.yaml + lint: false +` + ); + + writeFile(dir, '.contractual/versions.json', '{}'); + copyFixture( + 'openapi/petstore-base.yaml', + path.join(dir, 'src/contracts/api/v1/openapi.yaml') + ); + + // Lint should work with nested paths (lint disabled) + const result = run('lint', dir); + expect(result.exitCode).toBe(0); + + // Verify nested file exists + expect(fileExists(dir, 'src/contracts/api/v1/openapi.yaml')).toBe(true); + } finally { + cleanup(); + } + }); + + test('config with only changeset options (no ai)', () => { + const { dir, cleanup } = createTempRepo(); + try { + writeFile( + dir, + 'contractual.yaml', + `contracts: + - name: api + type: openapi + path: specs/api.yaml + lint: false +changeset: + autoDetect: false + requireOnPR: true +` + ); + + writeFile(dir, '.contractual/versions.json', '{}'); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/api.yaml')); + + const result = run('lint', dir); + expect(result.exitCode).toBe(0); + + const config = readYAML(dir, 'contractual.yaml') as { + contracts: Array; + changeset: { autoDetect: boolean; requireOnPR: boolean }; + ai?: unknown; + }; + expect(config.changeset.autoDetect).toBe(false); + expect(config.changeset.requireOnPR).toBe(true); + expect(config.ai).toBeUndefined(); + } finally { + cleanup(); + } + }); + + test('config with only ai options (no changeset)', () => { + const { dir, cleanup } = createTempRepo(); + try { + writeFile( + dir, + 'contractual.yaml', + `contracts: + - name: api + type: openapi + path: specs/api.yaml + lint: false +ai: + provider: anthropic + features: + explain: true +` + ); + + writeFile(dir, '.contractual/versions.json', '{}'); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/api.yaml')); + + const result = run('lint', dir); + expect(result.exitCode).toBe(0); + + const config = readYAML(dir, 'contractual.yaml') as { + contracts: Array; + ai: { provider: string; features: { explain: boolean } }; + changeset?: unknown; + }; + expect(config.ai.provider).toBe('anthropic'); + expect(config.ai.features.explain).toBe(true); + expect(config.changeset).toBeUndefined(); + } finally { + cleanup(); + } + }); + + test('asyncapi contract type is accepted', () => { + const { dir, cleanup } = createTempRepo(); + try { + writeFile( + dir, + 'contractual.yaml', + `contracts: + - name: events + type: asyncapi + path: specs/events.yaml +` + ); + + writeFile(dir, '.contractual/versions.json', '{}'); + // Use a basic YAML file as placeholder (asyncapi would have similar structure) + writeFile( + dir, + 'specs/events.yaml', + `asyncapi: 2.6.0 +info: + title: Events API + version: 1.0.0 +channels: {} +` + ); + + // Config validation should pass (even if lint may not have asyncapi support yet) + const config = readYAML(dir, 'contractual.yaml') as { + contracts: Array<{ name: string; type: string }>; + }; + expect(config.contracts[0].type).toBe('asyncapi'); + } finally { + cleanup(); + } + }); + + test('odcs contract type is accepted', () => { + const { dir, cleanup } = createTempRepo(); + try { + writeFile( + dir, + 'contractual.yaml', + `contracts: + - name: data-contract + type: odcs + path: contracts/data.yaml +` + ); + + writeFile(dir, '.contractual/versions.json', '{}'); + // Use a basic YAML file as placeholder for ODCS + writeFile( + dir, + 'contracts/data.yaml', + `dataContractSpecification: 1.0.0 +info: + title: Data Contract + version: 1.0.0 +` + ); + + const config = readYAML(dir, 'contractual.yaml') as { + contracts: Array<{ name: string; type: string }>; + }; + expect(config.contracts[0].type).toBe('odcs'); + } finally { + cleanup(); + } + }); +}); diff --git a/tests/e2e/13-openapi-params-and-metadata.test.ts b/tests/e2e/13-openapi-params-and-metadata.test.ts new file mode 100644 index 0000000..b69e486 --- /dev/null +++ b/tests/e2e/13-openapi-params-and-metadata.test.ts @@ -0,0 +1,120 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + setupRepoWithConfig, + writeFile, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +function setupPetstore31(dir: string) { + setupRepoWithConfig(dir, [ + { name: 'petstore', type: 'openapi', path: 'specs/petstore.yaml' }, + ]); + copyFixture( + 'openapi/petstore-31-base.yaml', + path.join(dir, '.contractual/snapshots/petstore.yaml') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + petstore: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); +} + +describe('OpenAPI parameter classification', () => { + test('adding optional parameter is non-breaking', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupPetstore31(dir); + copyFixture( + 'openapi/petstore-31-nonbreaking-optional-param.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + const result = run('breaking', dir); + expect(result.exitCode).toBe(0); + } finally { + cleanup(); + } + }); + + test('adding required parameter is breaking', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupPetstore31(dir); + copyFixture( + 'openapi/petstore-31-breaking-required-param.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + const result = run('breaking', dir, { expectFail: true }); + expect(result.exitCode).toBe(1); + expect(result.stdout).toMatch(/breaking/i); + } finally { + cleanup(); + } + }); +}); + +describe('OpenAPI metadata change detection', () => { + test('description/summary changes are detected as patch (not breaking)', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupPetstore31(dir); + copyFixture( + 'openapi/petstore-31-description-changed.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + // Description changes should not be breaking + const result = run('breaking', dir); + expect(result.exitCode).toBe(0); + } finally { + cleanup(); + } + }); + + test('description changes create a changeset', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupPetstore31(dir); + copyFixture( + 'openapi/petstore-31-description-changed.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + const result = run('changeset', dir); + expect(result.exitCode).toBe(0); + // Should create a changeset (not "No changes detected") + expect(result.stdout).toMatch(/created changeset/i); + } finally { + cleanup(); + } + }); + + test('description changes detected in diff command', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupPetstore31(dir); + copyFixture( + 'openapi/petstore-31-description-changed.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + const result = run('diff', dir); + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/change|patch|description/i); + } finally { + cleanup(); + } + }); +}); diff --git a/tests/e2e/14-openapi-message-formatting.test.ts b/tests/e2e/14-openapi-message-formatting.test.ts new file mode 100644 index 0000000..41898e4 --- /dev/null +++ b/tests/e2e/14-openapi-message-formatting.test.ts @@ -0,0 +1,181 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + setupRepoWithConfig, + writeFile, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +function setupPetstore31(dir: string) { + setupRepoWithConfig(dir, [ + { name: 'petstore', type: 'openapi', path: 'specs/petstore.yaml' }, + ]); + copyFixture( + 'openapi/petstore-31-base.yaml', + path.join(dir, '.contractual/snapshots/petstore.yaml') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + petstore: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); +} + +describe('OpenAPI change message formatting', () => { + test('path-added shows a readable message (not "Unknown change")', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'petstore', type: 'openapi', path: 'specs/petstore.yaml' }, + ]); + copyFixture( + 'openapi/petstore-base.yaml', + path.join(dir, '.contractual/snapshots/petstore.yaml') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + petstore: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + copyFixture( + 'openapi/petstore-nonbreaking-endpoint-added.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + const result = run('diff --format json', dir); + expect(result.exitCode).toBe(0); + const parsed = JSON.parse(result.stdout); + const changes = parsed.contracts?.petstore?.changes ?? []; + + // Find the path-added change + const pathAdded = changes.find((c: { category: string }) => c.category === 'path-added'); + expect(pathAdded).toBeDefined(); + expect(pathAdded.message).not.toMatch(/unknown change/i); + expect(pathAdded.message).toMatch(/new path added/i); + } finally { + cleanup(); + } + }); + + test('path paths in messages are decoded from JSON Pointer', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'petstore', type: 'openapi', path: 'specs/petstore.yaml' }, + ]); + copyFixture( + 'openapi/petstore-base.yaml', + path.join(dir, '.contractual/snapshots/petstore.yaml') + ); + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + petstore: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + copyFixture( + 'openapi/petstore-breaking-endpoint-removed.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + const result = run('diff --format json', dir, { expectFail: false }); + expect(result.exitCode).toBe(0); + const parsed = JSON.parse(result.stdout); + const changes = parsed.contracts?.petstore?.changes ?? []; + + // All messages should be decoded (no ~1 or ~0 escapes) + for (const change of changes) { + expect(change.message).not.toMatch(/~1/); + expect(change.message).not.toMatch(/~0/); + } + } finally { + cleanup(); + } + }); + + test('operation-added shows readable message', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupPetstore31(dir); + copyFixture( + 'openapi/petstore-31-nonbreaking-optional-param.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + const result = run('diff --format json', dir); + expect(result.exitCode).toBe(0); + const parsed = JSON.parse(result.stdout); + const changes = parsed.contracts?.petstore?.changes ?? []; + + // parameter-added case + const paramAdded = changes.find( + (c: { category: string }) => c.category === 'parameter-added' + ); + expect(paramAdded).toBeDefined(); + expect(paramAdded.message).not.toMatch(/unknown change/i); + expect(paramAdded.message).toMatch(/optional parameter added/i); + } finally { + cleanup(); + } + }); + + test('required parameter added shows specific message', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupPetstore31(dir); + copyFixture( + 'openapi/petstore-31-breaking-required-param.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + const result = run('diff --format json', dir); + expect(result.exitCode).toBe(0); + const parsed = JSON.parse(result.stdout); + const changes = parsed.contracts?.petstore?.changes ?? []; + + const reqParam = changes.find( + (c: { category: string }) => c.category === 'parameter-required-added' + ); + expect(reqParam).toBeDefined(); + expect(reqParam.message).not.toMatch(/unknown change/i); + expect(reqParam.message).toMatch(/required parameter added/i); + } finally { + cleanup(); + } + }); + + test('no "Unknown change" messages for any OpenAPI structural change', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupPetstore31(dir); + copyFixture( + 'openapi/petstore-31-description-changed.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + const result = run('diff --format json', dir); + expect(result.exitCode).toBe(0); + const parsed = JSON.parse(result.stdout); + const changes = parsed.contracts?.petstore?.changes ?? []; + + expect(changes.length).toBeGreaterThan(0); + for (const change of changes) { + expect(change.message).not.toMatch(/unknown change/i); + } + } finally { + cleanup(); + } + }); +}); diff --git a/tests/e2e/14-strands-conformance.test.ts b/tests/e2e/14-strands-conformance.test.ts new file mode 100644 index 0000000..9da366a --- /dev/null +++ b/tests/e2e/14-strands-conformance.test.ts @@ -0,0 +1,607 @@ +/** + * Strands API Conformance Tests + * + * Tests the compareSchemas function against expected Strands API behavior. + * Based on the Strands API at https://strands.octue.com/api/compare-schemas + */ + +import { describe, test, expect } from 'vitest'; +import { + compareSchemas, + checkCompatibility, +} from '../../packages/differs.json-schema/src/compare.js'; +import type { + CompareResult, + CompareOptions, + StrandsTrace, + StrandsCompatibility, + StrandsVersion, + SemanticVersion, + ResolvedSchema, +} from '../../packages/differs.json-schema/src/types.js'; + +describe('Strands API Conformance', () => { + describe('Basic comparison results', () => { + test('identical schemas return equal version', () => { + const schema: ResolvedSchema = { + type: 'object', + properties: { + name: { type: 'string' }, + }, + }; + + const result: CompareResult = compareSchemas(schema, schema); + expect(result.version).toBe('equal'); + expect(result.traces).toHaveLength(0); + }); + + test('no changes returns equal with currentVersion', () => { + const schema: ResolvedSchema = { type: 'string' }; + const options: CompareOptions = { currentVersion: '1.0.0' }; + const result: CompareResult = compareSchemas(schema, schema, options); + + expect(result.version).toBe('equal'); + const expectedVersion: SemanticVersion = { + major: 1, + minor: 0, + patch: 0, + version: '1.0.0', + }; + expect(result.newVersion).toEqual(expectedVersion); + }); + }); + + describe('Format changes are patch (not breaking)', () => { + test('format-added is patch', () => { + const source: ResolvedSchema = { type: 'string' }; + const target: ResolvedSchema = { type: 'string', format: 'email' }; + + const result: CompareResult = compareSchemas(source, target, { currentVersion: '1.0.0' }); + expect(result.version).toBe('patch'); + expect(result.newVersion?.version).toBe('1.0.1'); + }); + + test('format-removed is patch', () => { + const source: ResolvedSchema = { type: 'string', format: 'email' }; + const target: ResolvedSchema = { type: 'string' }; + + const result: CompareResult = compareSchemas(source, target, { currentVersion: '1.0.0' }); + expect(result.version).toBe('patch'); + }); + + test('format-changed is patch', () => { + const source: ResolvedSchema = { type: 'string', format: 'email' }; + const target: ResolvedSchema = { type: 'string', format: 'uri' }; + + const result: CompareResult = compareSchemas(source, target, { currentVersion: '1.0.0' }); + expect(result.version).toBe('patch'); + }); + }); + + describe('Type changes classification', () => { + test('type change is breaking (major)', () => { + const source: ResolvedSchema = { type: 'string' }; + const target: ResolvedSchema = { type: 'number' }; + + const result: CompareResult = compareSchemas(source, target, { currentVersion: '1.0.0' }); + expect(result.version).toBe('major'); + expect(result.newVersion?.version).toBe('2.0.0'); + }); + + test('type narrowed is breaking', () => { + const source: ResolvedSchema = { type: ['string', 'number'] }; + const target: ResolvedSchema = { type: 'string' }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('major'); + }); + + test('type widened is non-breaking (minor)', () => { + const source: ResolvedSchema = { type: 'string' }; + const target: ResolvedSchema = { type: ['string', 'number'] }; + + const result: CompareResult = compareSchemas(source, target, { currentVersion: '1.0.0' }); + expect(result.version).toBe('minor'); + expect(result.newVersion?.version).toBe('1.1.0'); + }); + }); + + describe('Property changes classification', () => { + test('property-added is non-breaking (minor)', () => { + const source: ResolvedSchema = { + type: 'object', + properties: { name: { type: 'string' } }, + }; + const target: ResolvedSchema = { + type: 'object', + properties: { + name: { type: 'string' }, + email: { type: 'string' }, + }, + }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('minor'); + }); + + test('property-removed is breaking (major)', () => { + const source: ResolvedSchema = { + type: 'object', + properties: { + name: { type: 'string' }, + email: { type: 'string' }, + }, + }; + const target: ResolvedSchema = { + type: 'object', + properties: { name: { type: 'string' } }, + }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('major'); + }); + + test('required-added is breaking', () => { + const source: ResolvedSchema = { + type: 'object', + properties: { name: { type: 'string' } }, + }; + const target: ResolvedSchema = { + type: 'object', + properties: { name: { type: 'string' } }, + required: ['name'], + }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('major'); + }); + + test('required-removed is non-breaking', () => { + const source: ResolvedSchema = { + type: 'object', + properties: { name: { type: 'string' } }, + required: ['name'], + }; + const target: ResolvedSchema = { + type: 'object', + properties: { name: { type: 'string' } }, + }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('minor'); + }); + }); + + describe('Enum changes classification', () => { + test('enum-value-added is non-breaking', () => { + const source: ResolvedSchema = { type: 'string', enum: ['a', 'b'] }; + const target: ResolvedSchema = { type: 'string', enum: ['a', 'b', 'c'] }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('minor'); + }); + + test('enum-value-removed is breaking', () => { + const source: ResolvedSchema = { type: 'string', enum: ['a', 'b', 'c'] }; + const target: ResolvedSchema = { type: 'string', enum: ['a', 'b'] }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('major'); + }); + + test('enum-added is breaking', () => { + const source: ResolvedSchema = { type: 'string' }; + const target: ResolvedSchema = { type: 'string', enum: ['a', 'b'] }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('major'); + }); + + test('enum-removed is non-breaking', () => { + const source: ResolvedSchema = { type: 'string', enum: ['a', 'b'] }; + const target: ResolvedSchema = { type: 'string' }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('minor'); + }); + }); + + describe('Constraint changes classification', () => { + test('constraint-tightened is breaking', () => { + const source: ResolvedSchema = { type: 'number', minimum: 0 }; + const target: ResolvedSchema = { type: 'number', minimum: 10 }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('major'); + }); + + test('constraint-loosened is non-breaking', () => { + const source: ResolvedSchema = { type: 'number', minimum: 10 }; + const target: ResolvedSchema = { type: 'number', minimum: 0 }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('minor'); + }); + + test('minItems-increased is breaking', () => { + const source: ResolvedSchema = { type: 'array', items: { type: 'string' }, minItems: 1 }; + const target: ResolvedSchema = { type: 'array', items: { type: 'string' }, minItems: 5 }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('major'); + }); + + test('maxItems-decreased is breaking', () => { + const source: ResolvedSchema = { type: 'array', items: { type: 'string' }, maxItems: 10 }; + const target: ResolvedSchema = { type: 'array', items: { type: 'string' }, maxItems: 5 }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('major'); + }); + }); + + describe('additionalProperties changes', () => { + test('additionalProperties denied is breaking', () => { + const source: ResolvedSchema = { type: 'object', properties: { name: { type: 'string' } } }; + const target: ResolvedSchema = { + type: 'object', + properties: { name: { type: 'string' } }, + additionalProperties: false, + }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('major'); + }); + + test('additionalProperties allowed is non-breaking', () => { + const source: ResolvedSchema = { + type: 'object', + properties: { name: { type: 'string' } }, + additionalProperties: false, + }; + const target: ResolvedSchema = { type: 'object', properties: { name: { type: 'string' } } }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('minor'); + }); + }); + + describe('Metadata changes are patch', () => { + test('description-changed is patch', () => { + const source: ResolvedSchema = { type: 'string', description: 'Old description' }; + const target: ResolvedSchema = { type: 'string', description: 'New description' }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('patch'); + }); + + test('title-changed is patch', () => { + const source: ResolvedSchema = { type: 'string', title: 'Old title' }; + const target: ResolvedSchema = { type: 'string', title: 'New title' }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('patch'); + }); + + test('default-changed is patch', () => { + const source: ResolvedSchema = { type: 'string', default: 'old' }; + const target: ResolvedSchema = { type: 'string', default: 'new' }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('patch'); + }); + + test('examples-changed is patch', () => { + const source: ResolvedSchema = { type: 'string', examples: ['old'] }; + const target: ResolvedSchema = { type: 'string', examples: ['new'] }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('patch'); + }); + }); + + describe('Annotation changes are patch (Draft 2019-09+)', () => { + test('deprecated-changed is patch', () => { + const source: ResolvedSchema = { type: 'string' }; + const target: ResolvedSchema = { type: 'string', deprecated: true }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('patch'); + }); + + test('readOnly-changed is patch', () => { + const source: ResolvedSchema = { type: 'string' }; + const target: ResolvedSchema = { type: 'string', readOnly: true }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('patch'); + }); + + test('writeOnly-changed is patch', () => { + const source: ResolvedSchema = { type: 'string' }; + const target: ResolvedSchema = { type: 'string', writeOnly: true }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('patch'); + }); + }); + + describe('Composition changes (anyOf/oneOf/allOf)', () => { + test('anyOf option added is breaking', () => { + const source: ResolvedSchema = { + anyOf: [{ type: 'string' }, { type: 'number' }], + }; + const target: ResolvedSchema = { + anyOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }], + }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('major'); + }); + + test('anyOf option removed is non-breaking', () => { + const source: ResolvedSchema = { + anyOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }], + }; + const target: ResolvedSchema = { + anyOf: [{ type: 'string' }, { type: 'number' }], + }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('minor'); + }); + + test('oneOf option added is breaking', () => { + const source: ResolvedSchema = { + oneOf: [{ type: 'string' }, { type: 'number' }], + }; + const target: ResolvedSchema = { + oneOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }], + }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('major'); + }); + + test('oneOf option removed is non-breaking', () => { + const source: ResolvedSchema = { + oneOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }], + }; + const target: ResolvedSchema = { + oneOf: [{ type: 'string' }, { type: 'number' }], + }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('minor'); + }); + + test('allOf member added is breaking', () => { + const source: ResolvedSchema = { + allOf: [{ type: 'object', properties: { a: { type: 'string' } } }], + }; + const target: ResolvedSchema = { + allOf: [ + { type: 'object', properties: { a: { type: 'string' } } }, + { type: 'object', properties: { b: { type: 'number' } } }, + ], + }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('major'); + }); + + test('allOf member removed is non-breaking', () => { + const source: ResolvedSchema = { + allOf: [ + { type: 'object', properties: { a: { type: 'string' } } }, + { type: 'object', properties: { b: { type: 'number' } } }, + ], + }; + const target: ResolvedSchema = { + allOf: [{ type: 'object', properties: { a: { type: 'string' } } }], + }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('minor'); + }); + + test('not schema changed is breaking', () => { + const source: ResolvedSchema = { not: { type: 'string' } }; + const target: ResolvedSchema = { not: { type: 'number' } }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('major'); + }); + }); + + describe('Draft 2020-12 keywords', () => { + test('dependentRequired-added is breaking', () => { + const source: ResolvedSchema = { + type: 'object', + properties: { + creditCard: { type: 'string' }, + billingAddress: { type: 'string' }, + }, + }; + const target: ResolvedSchema = { + type: 'object', + properties: { + creditCard: { type: 'string' }, + billingAddress: { type: 'string' }, + }, + dependentRequired: { + creditCard: ['billingAddress'], + }, + }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('major'); + }); + + test('dependentRequired-removed is non-breaking', () => { + const source: ResolvedSchema = { + type: 'object', + properties: { + creditCard: { type: 'string' }, + billingAddress: { type: 'string' }, + }, + dependentRequired: { + creditCard: ['billingAddress'], + }, + }; + const target: ResolvedSchema = { + type: 'object', + properties: { + creditCard: { type: 'string' }, + billingAddress: { type: 'string' }, + }, + }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.version).toBe('minor'); + }); + }); + + describe('Strands trace format', () => { + test('traces have correct format', () => { + const source: ResolvedSchema = { type: 'string' }; + const target: ResolvedSchema = { type: 'number' }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.traces.length).toBeGreaterThan(0); + + const validCompatibilities: StrandsCompatibility[] = [ + 'incompatible', + 'compatible', + 'unknown', + ]; + for (const trace of result.traces) { + // Verify trace conforms to StrandsTrace interface + const typedTrace: StrandsTrace = trace; + expect(typedTrace).toHaveProperty('compatibility'); + expect(typedTrace).toHaveProperty('left'); + expect(typedTrace).toHaveProperty('right'); + expect(validCompatibilities).toContain(typedTrace.compatibility); + } + }); + + test('breaking changes have incompatible traces', () => { + const source: ResolvedSchema = { type: 'string' }; + const target: ResolvedSchema = { type: 'number' }; + + const result: CompareResult = compareSchemas(source, target); + const hasIncompatible = result.traces.some( + (t: StrandsTrace) => t.compatibility === 'incompatible' + ); + expect(hasIncompatible).toBe(true); + }); + + test('non-breaking changes have compatible traces', () => { + const source: ResolvedSchema = { type: 'string' }; + const target: ResolvedSchema = { type: ['string', 'number'] }; + + const result: CompareResult = compareSchemas(source, target); + const hasCompatible = result.traces.some( + (t: StrandsTrace) => t.compatibility === 'compatible' + ); + expect(hasCompatible).toBe(true); + }); + }); + + describe('newVersion computation', () => { + test('computes newVersion correctly for major bump', () => { + const source: ResolvedSchema = { type: 'string' }; + const target: ResolvedSchema = { type: 'number' }; + const options: CompareOptions = { currentVersion: '1.2.3' }; + + const result: CompareResult = compareSchemas(source, target, options); + const expectedVersion: SemanticVersion = { + major: 2, + minor: 0, + patch: 0, + version: '2.0.0', + }; + expect(result.newVersion).toEqual(expectedVersion); + }); + + test('computes newVersion correctly for minor bump', () => { + const source: ResolvedSchema = { type: 'string' }; + const target: ResolvedSchema = { type: ['string', 'number'] }; + const options: CompareOptions = { currentVersion: '1.2.3' }; + + const result: CompareResult = compareSchemas(source, target, options); + const expectedVersion: SemanticVersion = { + major: 1, + minor: 3, + patch: 0, + version: '1.3.0', + }; + expect(result.newVersion).toEqual(expectedVersion); + }); + + test('computes newVersion correctly for patch bump', () => { + const source: ResolvedSchema = { type: 'string', description: 'old' }; + const target: ResolvedSchema = { type: 'string', description: 'new' }; + const options: CompareOptions = { currentVersion: '1.2.3' }; + + const result: CompareResult = compareSchemas(source, target, options); + const expectedVersion: SemanticVersion = { + major: 1, + minor: 2, + patch: 4, + version: '1.2.4', + }; + expect(result.newVersion).toEqual(expectedVersion); + }); + + test('handles v-prefix in currentVersion', () => { + const source: ResolvedSchema = { type: 'string' }; + const target: ResolvedSchema = { type: 'number' }; + const options: CompareOptions = { currentVersion: 'v1.2.3' }; + + const result: CompareResult = compareSchemas(source, target, options); + expect(result.newVersion?.version).toBe('2.0.0'); + }); + + test('newVersion is null when currentVersion not provided', () => { + const source: ResolvedSchema = { type: 'string' }; + const target: ResolvedSchema = { type: 'number' }; + + const result: CompareResult = compareSchemas(source, target); + expect(result.newVersion).toBeNull(); + }); + }); + + describe('checkCompatibility helper', () => { + test('returns incompatible for breaking changes', () => { + const source: ResolvedSchema = { type: 'string' }; + const target: ResolvedSchema = { type: 'number' }; + + const compatibility: StrandsCompatibility = checkCompatibility(source, target); + expect(compatibility).toBe('incompatible'); + }); + + test('returns compatible for non-breaking changes', () => { + const source: ResolvedSchema = { type: 'string' }; + const target: ResolvedSchema = { type: ['string', 'number'] }; + + const compatibility: StrandsCompatibility = checkCompatibility(source, target); + expect(compatibility).toBe('compatible'); + }); + + test('returns compatible for patch changes', () => { + const source: ResolvedSchema = { type: 'string', description: 'old' }; + const target: ResolvedSchema = { type: 'string', description: 'new' }; + + const compatibility: StrandsCompatibility = checkCompatibility(source, target); + expect(compatibility).toBe('compatible'); + }); + + test('returns compatible for identical schemas', () => { + const schema: ResolvedSchema = { type: 'string' }; + + const compatibility: StrandsCompatibility = checkCompatibility(schema, schema); + expect(compatibility).toBe('compatible'); + }); + }); +}); diff --git a/tests/e2e/15-json-schema-lint-rules.test.ts b/tests/e2e/15-json-schema-lint-rules.test.ts new file mode 100644 index 0000000..a9165d6 --- /dev/null +++ b/tests/e2e/15-json-schema-lint-rules.test.ts @@ -0,0 +1,838 @@ +/** + * JSON Schema Lint Rules Tests + * + * Tests the linting rules for JSON Schema quality and best practices. + * Based on Sourcemeta and JSON Schema community guidelines. + * + * @see https://github.com/sourcemeta/jsonschema + * @see https://github.com/orgs/json-schema-org/discussions/323 + */ + +import { describe, test, expect } from 'vitest'; +import { + runLintRules, + getAvailableRules, + getRuleDescription, + LINT_RULES, +} from '../../packages/governance/linters/json-schema-rules.js'; +import { lintJsonSchemaObject } from '../../packages/governance/linters/json-schema-ajv.js'; +import type { SchemaNode } from '../../packages/governance/linters/json-schema-rules.js'; +import type { LintIssue } from '../../packages/types/governance.js'; + +describe('JSON Schema Lint Rules', () => { + describe('Rule registry', () => { + test('all rules are registered', () => { + const ruleIds = getAvailableRules(); + expect(ruleIds.length).toBeGreaterThan(15); + }); + + test('each rule has a description', () => { + for (const rule of LINT_RULES) { + expect(rule.description).toBeTruthy(); + expect(getRuleDescription(rule.id)).toBe(rule.description); + } + }); + + test('each rule has a valid severity', () => { + for (const rule of LINT_RULES) { + expect(['error', 'warning']).toContain(rule.severity); + } + }); + }); + + describe('Schema declaration rules', () => { + test('missing-schema: warns when $schema is missing at root', () => { + const schema: SchemaNode = { + type: 'object', + properties: { name: { type: 'string' } }, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'missing-schema'); + expect(issue).toBeDefined(); + expect(issue?.severity).toBe('warning'); + expect(issue?.path).toBe('/'); + }); + + test('missing-schema: no warning when $schema is present', () => { + const schema: SchemaNode = { + $schema: 'https://json-schema.org/draft/2020-12/schema', + type: 'string', + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'missing-schema'); + expect(issue).toBeUndefined(); + }); + + test('schema-not-at-root: warns when $schema appears in subschema without $id', () => { + const schema: SchemaNode = { + $schema: 'https://json-schema.org/draft/2020-12/schema', + type: 'object', + properties: { + nested: { + $schema: 'https://json-schema.org/draft/2020-12/schema', + type: 'string', + }, + }, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'schema-not-at-root'); + expect(issue).toBeDefined(); + expect(issue?.path).toContain('nested'); + }); + + test('schema-not-at-root: no warning when subschema has $id', () => { + const schema: SchemaNode = { + $schema: 'https://json-schema.org/draft/2020-12/schema', + type: 'object', + properties: { + nested: { + $id: 'https://example.com/nested', + $schema: 'https://json-schema.org/draft/2020-12/schema', + type: 'string', + }, + }, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'schema-not-at-root'); + expect(issue).toBeUndefined(); + }); + }); + + describe('Metadata rules', () => { + test('missing-title: warns when root schema has no title', () => { + const schema: SchemaNode = { + $schema: 'https://json-schema.org/draft/2020-12/schema', + type: 'string', + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'missing-title'); + expect(issue).toBeDefined(); + expect(issue?.severity).toBe('warning'); + }); + + test('missing-title: no warning when title is present', () => { + const schema: SchemaNode = { + $schema: 'https://json-schema.org/draft/2020-12/schema', + title: 'My Schema', + type: 'string', + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'missing-title'); + expect(issue).toBeUndefined(); + }); + + test('missing-description: warns when root schema has no description', () => { + const schema: SchemaNode = { + $schema: 'https://json-schema.org/draft/2020-12/schema', + type: 'string', + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'missing-description'); + expect(issue).toBeDefined(); + }); + + test('missing-description: no warning when description is present', () => { + const schema: SchemaNode = { + $schema: 'https://json-schema.org/draft/2020-12/schema', + description: 'A string value', + type: 'string', + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'missing-description'); + expect(issue).toBeUndefined(); + }); + }); + + describe('Enum/Const rules', () => { + test('enum-to-const: warns when enum has single value', () => { + const schema: SchemaNode = { + type: 'string', + enum: ['only-value'], + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'enum-to-const'); + expect(issue).toBeDefined(); + expect(issue?.message).toContain('const'); + }); + + test('enum-to-const: no warning when enum has multiple values', () => { + const schema: SchemaNode = { + type: 'string', + enum: ['a', 'b', 'c'], + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'enum-to-const'); + expect(issue).toBeUndefined(); + }); + + test('enum-with-type: warns when type is redundant with enum', () => { + const schema: SchemaNode = { + type: 'string', + enum: ['a', 'b', 'c'], + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'enum-with-type'); + expect(issue).toBeDefined(); + expect(issue?.message).toContain('redundant'); + }); + + test('enum-with-type: no warning when enum has mixed types', () => { + const schema: SchemaNode = { + type: ['string', 'number'], + enum: ['a', 1, 'b', 2], + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'enum-with-type'); + // Should still warn because enum constrains to the same types + expect(issue).toBeDefined(); + }); + + test('const-with-type: warns when type is redundant with const', () => { + const schema: SchemaNode = { + type: 'string', + const: 'fixed-value', + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'const-with-type'); + expect(issue).toBeDefined(); + expect(issue?.message).toContain('redundant'); + }); + + test('const-with-type: no warning when type differs from const', () => { + const schema: SchemaNode = { + type: ['string', 'null'], + const: 'fixed-value', + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'const-with-type'); + expect(issue).toBeUndefined(); + }); + }); + + describe('Conditional schema rules', () => { + test('if-without-then-else: warns when if has no then or else', () => { + const schema: SchemaNode = { + type: 'object', + if: { properties: { type: { const: 'a' } } }, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'if-without-then-else'); + expect(issue).toBeDefined(); + expect(issue?.message).toContain('no effect'); + }); + + test('if-without-then-else: no warning when if has then', () => { + const schema: SchemaNode = { + type: 'object', + if: { properties: { type: { const: 'a' } } }, + then: { required: ['extra'] }, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'if-without-then-else'); + expect(issue).toBeUndefined(); + }); + + test('if-without-then-else: no warning when if has else', () => { + const schema: SchemaNode = { + type: 'object', + if: { properties: { type: { const: 'a' } } }, + else: { required: ['other'] }, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'if-without-then-else'); + expect(issue).toBeUndefined(); + }); + + test('then-else-without-if: warns when then appears without if', () => { + const schema: SchemaNode = { + type: 'object', + then: { required: ['extra'] }, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'then-else-without-if'); + expect(issue).toBeDefined(); + expect(issue?.message).toContain('then'); + }); + + test('then-else-without-if: warns when else appears without if', () => { + const schema: SchemaNode = { + type: 'object', + else: { required: ['other'] }, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'then-else-without-if'); + expect(issue).toBeDefined(); + expect(issue?.message).toContain('else'); + }); + }); + + describe('Array constraint rules', () => { + test('additional-items-redundant: warns when additionalItems with schema items', () => { + const schema: SchemaNode = { + type: 'array', + items: { type: 'string' }, + additionalItems: { type: 'number' }, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'additional-items-redundant'); + expect(issue).toBeDefined(); + expect(issue?.message).toContain('ignored'); + }); + + test('additional-items-redundant: no warning when items is tuple', () => { + const schema: SchemaNode = { + type: 'array', + items: [{ type: 'string' }, { type: 'number' }], + additionalItems: { type: 'boolean' }, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'additional-items-redundant'); + expect(issue).toBeUndefined(); + }); + + test('contains-required: warns when minContains without contains', () => { + const schema: SchemaNode = { + type: 'array', + minContains: 2, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'contains-required'); + expect(issue).toBeDefined(); + expect(issue?.message).toContain('minContains'); + }); + + test('contains-required: warns when maxContains without contains', () => { + const schema: SchemaNode = { + type: 'array', + maxContains: 5, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'contains-required'); + expect(issue).toBeDefined(); + expect(issue?.message).toContain('maxContains'); + }); + + test('contains-required: no warning when contains is present', () => { + const schema: SchemaNode = { + type: 'array', + contains: { type: 'string' }, + minContains: 2, + maxContains: 5, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'contains-required'); + expect(issue).toBeUndefined(); + }); + }); + + describe('Type compatibility rules', () => { + test('type-incompatible-keywords: warns when string keywords on number type', () => { + const schema: SchemaNode = { + type: 'number', + minLength: 5, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'type-incompatible-keywords'); + expect(issue).toBeDefined(); + expect(issue?.message).toContain('minLength'); + expect(issue?.message).toContain('string'); + }); + + test('type-incompatible-keywords: warns when array keywords on object type', () => { + const schema: SchemaNode = { + type: 'object', + minItems: 1, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'type-incompatible-keywords'); + expect(issue).toBeDefined(); + expect(issue?.message).toContain('minItems'); + expect(issue?.message).toContain('array'); + }); + + test('type-incompatible-keywords: warns when numeric keywords on string type', () => { + const schema: SchemaNode = { + type: 'string', + minimum: 0, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'type-incompatible-keywords'); + expect(issue).toBeDefined(); + expect(issue?.message).toContain('minimum'); + }); + + test('type-incompatible-keywords: no warning when keywords match type', () => { + const schema: SchemaNode = { + type: 'string', + minLength: 1, + maxLength: 100, + pattern: '^[a-z]+$', + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'type-incompatible-keywords'); + expect(issue).toBeUndefined(); + }); + + test('type-incompatible-keywords: no check when type is array (multiple types)', () => { + const schema: SchemaNode = { + type: ['string', 'number'], + minLength: 1, + minimum: 0, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'type-incompatible-keywords'); + expect(issue).toBeUndefined(); + }); + }); + + describe('Range validation rules', () => { + test('invalid-numeric-range: errors when maximum < minimum', () => { + const schema: SchemaNode = { + type: 'number', + minimum: 100, + maximum: 10, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'invalid-numeric-range'); + expect(issue).toBeDefined(); + expect(issue?.severity).toBe('error'); + expect(issue?.message).toContain('maximum'); + expect(issue?.message).toContain('minimum'); + }); + + test('invalid-numeric-range: no error when maximum >= minimum', () => { + const schema: SchemaNode = { + type: 'number', + minimum: 0, + maximum: 100, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'invalid-numeric-range'); + expect(issue).toBeUndefined(); + }); + + test('invalid-numeric-range: errors when exclusiveMaximum <= exclusiveMinimum', () => { + const schema: SchemaNode = { + type: 'number', + exclusiveMinimum: 10, + exclusiveMaximum: 10, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'invalid-numeric-range'); + expect(issue).toBeDefined(); + expect(issue?.message).toContain('exclusiveMaximum'); + }); + + test('invalid-length-range: errors when maxLength < minLength', () => { + const schema: SchemaNode = { + type: 'string', + minLength: 10, + maxLength: 5, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'invalid-length-range'); + expect(issue).toBeDefined(); + expect(issue?.severity).toBe('error'); + }); + + test('invalid-items-range: errors when maxItems < minItems', () => { + const schema: SchemaNode = { + type: 'array', + minItems: 10, + maxItems: 5, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'invalid-items-range'); + expect(issue).toBeDefined(); + expect(issue?.severity).toBe('error'); + }); + + test('invalid-properties-range: errors when maxProperties < minProperties', () => { + const schema: SchemaNode = { + type: 'object', + minProperties: 10, + maxProperties: 5, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'invalid-properties-range'); + expect(issue).toBeDefined(); + expect(issue?.severity).toBe('error'); + }); + }); + + describe('Format rules', () => { + test('unknown-format: warns for unknown format values', () => { + const schema: SchemaNode = { + type: 'string', + format: 'custom-format', + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'unknown-format'); + expect(issue).toBeDefined(); + expect(issue?.message).toContain('custom-format'); + }); + + test('unknown-format: no warning for known formats', () => { + const knownFormats = [ + 'date-time', + 'date', + 'time', + 'email', + 'uri', + 'uuid', + 'ipv4', + 'ipv6', + 'hostname', + 'regex', + ]; + + for (const format of knownFormats) { + const schema: SchemaNode = { type: 'string', format }; + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'unknown-format'); + expect(issue).toBeUndefined(); + } + }); + }); + + describe('Empty schema rules', () => { + test('empty-enum: errors when enum is empty array', () => { + const schema: SchemaNode = { + type: 'string', + enum: [], + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'empty-enum'); + expect(issue).toBeDefined(); + expect(issue?.severity).toBe('error'); + expect(issue?.message).toContain('never validate'); + }); + + test('empty-required: warns when required is empty array', () => { + const schema: SchemaNode = { + type: 'object', + properties: { name: { type: 'string' } }, + required: [], + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'empty-required'); + expect(issue).toBeDefined(); + expect(issue?.severity).toBe('warning'); + }); + + test('empty-allof-anyof-oneof: errors when anyOf is empty', () => { + const schema: SchemaNode = { + anyOf: [], + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'empty-allof-anyof-oneof'); + expect(issue).toBeDefined(); + expect(issue?.severity).toBe('error'); + }); + + test('empty-allof-anyof-oneof: errors when oneOf is empty', () => { + const schema: SchemaNode = { + oneOf: [], + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'empty-allof-anyof-oneof'); + expect(issue).toBeDefined(); + expect(issue?.severity).toBe('error'); + }); + + test('empty-allof-anyof-oneof: warns when allOf is empty', () => { + const schema: SchemaNode = { + allOf: [], + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'empty-allof-anyof-oneof'); + expect(issue).toBeDefined(); + // allOf empty is just redundant, not invalid + expect(issue?.severity).toBe('warning'); + }); + }); + + describe('Required properties rules', () => { + test('required-undefined-property: warns when required property not in properties', () => { + const schema: SchemaNode = { + type: 'object', + properties: { + name: { type: 'string' }, + }, + required: ['name', 'email'], + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'required-undefined-property'); + expect(issue).toBeDefined(); + expect(issue?.message).toContain('email'); + }); + + test('required-undefined-property: no warning when all required are defined', () => { + const schema: SchemaNode = { + type: 'object', + properties: { + name: { type: 'string' }, + email: { type: 'string' }, + }, + required: ['name', 'email'], + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'required-undefined-property'); + expect(issue).toBeUndefined(); + }); + + test('duplicate-required: warns when required has duplicates', () => { + const schema: SchemaNode = { + type: 'object', + properties: { + name: { type: 'string' }, + }, + required: ['name', 'name'], + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'duplicate-required'); + expect(issue).toBeDefined(); + expect(issue?.message).toContain('name'); + }); + + test('duplicate-required: no warning when required has no duplicates', () => { + const schema: SchemaNode = { + type: 'object', + properties: { + name: { type: 'string' }, + email: { type: 'string' }, + }, + required: ['name', 'email'], + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'duplicate-required'); + expect(issue).toBeUndefined(); + }); + }); + + describe('Nested schema linting', () => { + test('rules apply to nested properties', () => { + const schema: SchemaNode = { + $schema: 'https://json-schema.org/draft/2020-12/schema', + title: 'Root', + description: 'Root schema', + type: 'object', + properties: { + nested: { + type: 'object', + properties: { + value: { + type: 'number', + minimum: 100, + maximum: 10, // Invalid range + }, + }, + }, + }, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'invalid-numeric-range'); + expect(issue).toBeDefined(); + expect(issue?.path).toContain('nested'); + expect(issue?.path).toContain('value'); + }); + + test('rules apply to items schema', () => { + const schema: SchemaNode = { + $schema: 'https://json-schema.org/draft/2020-12/schema', + title: 'Root', + description: 'Root schema', + type: 'array', + items: { + type: 'string', + enum: ['only'], // Should suggest const + }, + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'enum-to-const'); + expect(issue).toBeDefined(); + expect(issue?.path).toContain('items'); + }); + + test('rules apply to allOf/anyOf/oneOf schemas', () => { + const schema: SchemaNode = { + $schema: 'https://json-schema.org/draft/2020-12/schema', + title: 'Root', + description: 'Root schema', + anyOf: [ + { type: 'string', minLength: 10, maxLength: 5 }, // Invalid range + { type: 'number' }, + ], + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'invalid-length-range'); + expect(issue).toBeDefined(); + expect(issue?.path).toContain('anyOf'); + }); + + test('rules apply to $defs schemas', () => { + const schema: SchemaNode = { + $schema: 'https://json-schema.org/draft/2020-12/schema', + title: 'Root', + description: 'Root schema', + $defs: { + BadDef: { + type: 'array', + minItems: 10, + maxItems: 5, // Invalid range + }, + }, + $ref: '#/$defs/BadDef', + }; + + const issues = runLintRules(schema); + const issue = issues.find((i) => i.rule === 'invalid-items-range'); + expect(issue).toBeDefined(); + expect(issue?.path).toContain('$defs'); + }); + }); + + describe('Rule filtering', () => { + test('enabledRules filters to only specified rules', () => { + const schema: SchemaNode = { + type: 'string', + enum: ['only'], + minLength: 10, + maxLength: 5, + }; + + const enabledRules = new Set(['enum-to-const']); + const issues = runLintRules(schema, enabledRules); + + expect(issues.length).toBe(1); + expect(issues[0].rule).toBe('enum-to-const'); + }); + + test('disabledRules excludes specified rules', () => { + const schema: SchemaNode = { + type: 'string', + enum: ['only'], + }; + + const disabledRules = new Set(['enum-to-const', 'enum-with-type']); + const issues = runLintRules(schema, undefined, disabledRules); + + const enumIssues = issues.filter( + (i) => i.rule === 'enum-to-const' || i.rule === 'enum-with-type' + ); + expect(enumIssues).toHaveLength(0); + }); + }); + + describe('Full linter integration (lintJsonSchemaObject)', () => { + test('combines meta-validation and style rules', async () => { + const schema: SchemaNode = { + type: 'objekt', // Invalid type value (meta-validation error) + enum: ['only'], // Style warning + }; + + const result = await lintJsonSchemaObject(schema); + + // Should have meta-validation error + expect(result.errors.length).toBeGreaterThan(0); + + // Should have style warnings + expect(result.warnings.length).toBeGreaterThan(0); + }); + + test('skipMetaValidation option works', async () => { + const schema: SchemaNode = { + type: 'objekt', // Invalid but should be skipped + }; + + const result = await lintJsonSchemaObject(schema, { skipMetaValidation: true }); + + // Should not have meta-validation errors + const metaErrors = result.errors.filter((e) => e.rule?.startsWith('meta:')); + expect(metaErrors).toHaveLength(0); + }); + + test('skipStyleRules option works', async () => { + const schema: SchemaNode = { + type: 'string', + enum: ['only'], // Should be skipped + }; + + const result = await lintJsonSchemaObject(schema, { skipStyleRules: true }); + + // Should not have style rule warnings + const styleWarnings = result.warnings.filter((w) => w.rule === 'enum-to-const'); + expect(styleWarnings).toHaveLength(0); + }); + + test('valid schema with best practices passes all checks', async () => { + const schema: SchemaNode = { + $schema: 'http://json-schema.org/draft-07/schema#', + title: 'User', + description: 'A user object', + type: 'object', + properties: { + id: { type: 'string', format: 'uuid' }, + name: { type: 'string', minLength: 1, maxLength: 100 }, + email: { type: 'string', format: 'email' }, + age: { type: 'integer', minimum: 0, maximum: 150 }, + }, + required: ['id', 'name', 'email'], + additionalProperties: false, + }; + + const result = await lintJsonSchemaObject(schema); + + expect(result.errors).toHaveLength(0); + // May have some warnings (like missing-description on nested props) + }); + }); +}); diff --git a/tests/e2e/16-diff-command.test.ts b/tests/e2e/16-diff-command.test.ts new file mode 100644 index 0000000..3a217de --- /dev/null +++ b/tests/e2e/16-diff-command.test.ts @@ -0,0 +1,633 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + setupRepoWithConfig, + writeFile, + readFile, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +describe('contractual diff', () => { + describe('basic functionality', () => { + test('shows "no changes" when specs match snapshots', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + // Create snapshot matching current spec + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('diff', dir); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/no changes/i); + } finally { + cleanup(); + } + }); + + test('shows classified changes when specs differ from snapshots', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + // Current spec has a field removed (breaking change) + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + + // Snapshot is the base version + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('diff', dir); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/order-schema/i); + expect(result.stdout).toMatch(/breaking/i); + } finally { + cleanup(); + } + }); + + test('always exits 0 on success regardless of breaking changes', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + // Breaking change: field removed + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('diff', dir); + + // diff always exits 0 (unlike breaking which exits 1) + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/breaking/i); + } finally { + cleanup(); + } + }); + }); + + describe('options', () => { + test('--contract filters to single contract', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + { + name: 'user-schema', + type: 'json-schema', + path: 'schemas/user.json', + }, + ]); + + // Both have changes + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + copyFixture( + 'json-schema/order-optional-field-added.json', + path.join(dir, 'schemas/user.json') + ); + + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/user-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + 'user-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('diff --contract order-schema', dir); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/order-schema/i); + expect(result.stdout).not.toMatch(/user-schema/i); + } finally { + cleanup(); + } + }); + + test('--format json outputs valid JSON', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('diff --format json', dir); + + expect(result.exitCode).toBe(0); + + const output = JSON.parse(result.stdout); + expect(output).toHaveProperty('contracts'); + expect(typeof output.contracts).toBe('object'); + + const contractNames = Object.keys(output.contracts); + if (contractNames.length > 0) { + const firstContract = output.contracts[contractNames[0]]; + expect(firstContract).toHaveProperty('changes'); + expect(firstContract).toHaveProperty('summary'); + expect(firstContract).toHaveProperty('suggestedBump'); + } + } finally { + cleanup(); + } + }); + + test('--severity breaking filters to only breaking changes', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + // Use a spec that has both breaking and non-breaking changes + // For this we need a spec with field removed (breaking) and description changed (patch) + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('diff --severity breaking --format json', dir); + + expect(result.exitCode).toBe(0); + + const output = JSON.parse(result.stdout); + // All changes in contracts should be breaking + for (const contractName in output.contracts) { + const contract = output.contracts[contractName]; + for (const change of contract.changes) { + expect(change.severity).toBe('breaking'); + } + } + } finally { + cleanup(); + } + }); + + test('--severity non-breaking filters to only non-breaking changes', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + // Non-breaking: optional field added + copyFixture( + 'json-schema/order-optional-field-added.json', + path.join(dir, 'schemas/order.json') + ); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('diff --severity non-breaking --format json', dir); + + expect(result.exitCode).toBe(0); + + const output = JSON.parse(result.stdout); + // All changes should be non-breaking + for (const contractName in output.contracts) { + const contract = output.contracts[contractName]; + for (const change of contract.changes) { + expect(change.severity).toBe('non-breaking'); + } + } + } finally { + cleanup(); + } + }); + + test('--verbose shows JSON Pointer paths', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('diff --verbose', dir); + + expect(result.exitCode).toBe(0); + // Verbose output should include "path:" lines + expect(result.stdout).toMatch(/path:/i); + } finally { + cleanup(); + } + }); + }); + + describe('edge cases', () => { + test('shows "no changes" for first version (no snapshot)', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + // No snapshot exists - first version + + const result = run('diff', dir); + + expect(result.exitCode).toBe(0); + // Should indicate first version or no changes + expect(result.stdout).toMatch(/no changes|first version|no snapshot/i); + } finally { + cleanup(); + } + }); + + test('exits with error on configuration error', () => { + const { dir, cleanup } = createTempRepo(); + try { + // No contractual.yaml - should fail + + const result = run('diff', dir, { expectFail: true }); + + expect(result.exitCode).toBeGreaterThan(0); + expect(result.stdout + result.stderr).toMatch(/init|config|not found/i); + } finally { + cleanup(); + } + }); + + test('handles contract with breaking detection disabled', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + breaking: false, // Disabled + }, + ]); + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('diff', dir); + + expect(result.exitCode).toBe(0); + // Should indicate skipped or disabled + expect(result.stdout).toMatch(/disabled|skipped|no changes/i); + } finally { + cleanup(); + } + }); + + test('reports error for non-existent contract name', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + const result = run('diff --contract nonexistent', dir, { expectFail: true }); + + expect(result.exitCode).toBeGreaterThan(0); + expect(result.stdout + result.stderr).toMatch(/not found|nonexistent/i); + } finally { + cleanup(); + } + }); + }); + + describe('OpenAPI contracts', () => { + test('shows changes for OpenAPI specs', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore-api', + type: 'openapi', + path: 'specs/petstore.yaml', + lint: false, // Disable linting for petstore fixture + }, + ]); + + // Current spec has endpoint removed (breaking) + copyFixture( + 'openapi/petstore-breaking-endpoint-removed.yaml', + path.join(dir, 'specs/petstore.yaml') + ); + + // Snapshot is base version + copyFixture( + 'openapi/petstore-base.yaml', + path.join(dir, '.contractual/snapshots/petstore-api.yaml') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'petstore-api': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('diff', dir); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/petstore-api/i); + expect(result.stdout).toMatch(/breaking/i); + } finally { + cleanup(); + } + }); + }); +}); + +describe('breaking command after refactor', () => { + test('still exits 1 on breaking changes', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('breaking', dir, { expectFail: true }); + + expect(result.exitCode).toBe(1); + } finally { + cleanup(); + } + }); + + test('still exits 0 when no breaking changes', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + // Non-breaking change only + copyFixture( + 'json-schema/order-optional-field-added.json', + path.join(dir, 'schemas/order.json') + ); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('breaking', dir); + + expect(result.exitCode).toBe(0); + } finally { + cleanup(); + } + }); +}); + +describe('changeset command after refactor', () => { + test('still generates changeset from detected changes', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('changeset', dir); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/created changeset/i); + } finally { + cleanup(); + } + }); + + test('still shows "no changes" when specs match', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('changeset', dir); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/no change/i); + } finally { + cleanup(); + } + }); +}); diff --git a/tests/e2e/17-pre-release.test.ts b/tests/e2e/17-pre-release.test.ts new file mode 100644 index 0000000..76069b2 --- /dev/null +++ b/tests/e2e/17-pre-release.test.ts @@ -0,0 +1,403 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + setupRepoWithConfig, + writeFile, + readFile, + readJSON, + fileExists, + listFiles, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +describe('contractual pre', () => { + describe('pre enter', () => { + test('enters pre-release mode with tag', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const result = run('pre enter beta', dir); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/entered pre-release mode/i); + expect(result.stdout).toMatch(/beta/i); + expect(fileExists(dir, '.contractual/pre.json')).toBe(true); + + const preState = readJSON(dir, '.contractual/pre.json') as { + tag: string; + enteredAt: string; + initialVersions: Record; + }; + expect(preState.tag).toBe('beta'); + expect(preState.initialVersions['order-schema']).toBe('1.0.0'); + } finally { + cleanup(); + } + }); + + test('creates pre.json with correct structure', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'api', + type: 'json-schema', + path: 'schemas/api.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/api.json')); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + api: { version: '2.5.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + run('pre enter alpha', dir); + + const preState = readJSON(dir, '.contractual/pre.json') as { + tag: string; + enteredAt: string; + initialVersions: Record; + }; + + expect(preState.tag).toBe('alpha'); + expect(preState.enteredAt).toBeDefined(); + expect(new Date(preState.enteredAt).getTime()).not.toBeNaN(); + expect(preState.initialVersions).toEqual({ api: '2.5.0' }); + } finally { + cleanup(); + } + }); + + test('fails if already in pre-release mode', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Enter pre-release mode first + run('pre enter beta', dir); + + // Try to enter again + const result = run('pre enter alpha', dir, { expectFail: true }); + + expect(result.exitCode).toBe(1); + expect(result.stdout).toMatch(/already in pre-release mode/i); + } finally { + cleanup(); + } + }); + + test('fails if not initialized', () => { + const { dir, cleanup } = createTempRepo(); + try { + // No contractual.yaml or .contractual directory + + const result = run('pre enter beta', dir, { expectFail: true }); + + expect(result.exitCode).toBe(1); + expect(result.stdout + result.stderr).toMatch(/no .contractual directory|init/i); + } finally { + cleanup(); + } + }); + }); + + describe('pre exit', () => { + test('exits pre-release mode', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Enter and then exit + run('pre enter beta', dir); + expect(fileExists(dir, '.contractual/pre.json')).toBe(true); + + const result = run('pre exit', dir); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/exited pre-release mode/i); + expect(fileExists(dir, '.contractual/pre.json')).toBe(false); + } finally { + cleanup(); + } + }); + + test('shows message if not in pre-release mode', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + const result = run('pre exit', dir); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/not in pre-release mode/i); + } finally { + cleanup(); + } + }); + + test('fails if not initialized', () => { + const { dir, cleanup } = createTempRepo(); + try { + const result = run('pre exit', dir, { expectFail: true }); + + expect(result.exitCode).toBe(1); + expect(result.stdout + result.stderr).toMatch(/no .contractual directory|init/i); + } finally { + cleanup(); + } + }); + }); + + describe('pre status', () => { + test('shows current pre-release tag and entry time', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + run('pre enter rc', dir); + + const result = run('pre status', dir); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/rc/i); + expect(result.stdout).toMatch(/tag|pre-release/i); + } finally { + cleanup(); + } + }); + + test('shows "not in pre-release mode" when inactive', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + const result = run('pre status', dir); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/not in pre-release mode/i); + } finally { + cleanup(); + } + }); + + test('fails if not initialized', () => { + const { dir, cleanup } = createTempRepo(); + try { + const result = run('pre status', dir, { expectFail: true }); + + expect(result.exitCode).toBe(1); + expect(result.stdout + result.stderr).toMatch(/no .contractual directory|init/i); + } finally { + cleanup(); + } + }); + }); + + describe('version with pre-release', () => { + test('bumps to pre-release version', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + // Setup initial state + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + copyFixture('json-schema/order-field-removed.json', path.join(dir, 'schemas/order.json')); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Enter pre-release mode + run('pre enter beta', dir); + + // Create changeset + const changeset = `--- +"order-schema": major +--- + +Breaking change for beta testing +`; + writeFile(dir, '.contractual/changesets/breaking.md', changeset); + + // Run version + const result = run('version --yes', dir); + + expect(result.exitCode).toBe(0); + + // Check version is pre-release format + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['order-schema'].version).toMatch(/2\.0\.0-beta/); + } finally { + cleanup(); + } + }); + + test('increments pre-release number on subsequent versions', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + + // Start with a pre-release version already + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + copyFixture( + 'json-schema/order-optional-field-added.json', + path.join(dir, 'schemas/order.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '2.0.0-beta.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Create pre.json to simulate being in pre-release mode + writeFile( + dir, + '.contractual/pre.json', + JSON.stringify({ + tag: 'beta', + enteredAt: '2026-01-01T00:00:00Z', + initialVersions: { 'order-schema': '1.0.0' }, + }) + ); + + // Create changeset + const changeset = `--- +"order-schema": minor +--- + +Another beta change +`; + writeFile(dir, '.contractual/changesets/minor.md', changeset); + + // Run version + const result = run('version --yes', dir); + + expect(result.exitCode).toBe(0); + + // Check version incremented pre-release number + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + // Should be 2.1.0-beta.0 or 2.0.0-beta.1 depending on implementation + expect(versions['order-schema'].version).toMatch(/beta/); + } finally { + cleanup(); + } + }); + }); +}); diff --git a/tests/e2e/18-contract-management.test.ts b/tests/e2e/18-contract-management.test.ts new file mode 100644 index 0000000..c7305a4 --- /dev/null +++ b/tests/e2e/18-contract-management.test.ts @@ -0,0 +1,312 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + setupRepoWithConfig, + writeFile, + readFile, + readJSON, + readYAML, + fileExists, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +describe('contractual contract', () => { + describe('contract add', () => { + test('adds contract to existing config', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Initialize with one contract + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + // Add a new spec file + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/user.json')); + + // Add contract using CLI + const result = run( + 'contract add --name user-schema --type json-schema --path schemas/user.json --initial-version 0.0.0 -y', + dir + ); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/added.*user-schema/i); + + // Verify config was updated + const config = readYAML(dir, 'contractual.yaml') as { + contracts: Array<{ name: string; type: string; path: string }>; + }; + expect(config.contracts).toHaveLength(2); + expect(config.contracts.find((c) => c.name === 'user-schema')).toBeDefined(); + } finally { + cleanup(); + } + }); + + test('creates snapshot and updates versions.json', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Add new contract + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/new-api.json')); + + const result = run( + 'contract add --name new-api --type json-schema --path schemas/new-api.json --initial-version 0.5.0 -y', + dir + ); + + expect(result.exitCode).toBe(0); + + // Verify snapshot created + expect(fileExists(dir, '.contractual/snapshots/new-api.json')).toBe(true); + + // Verify versions.json updated + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['new-api'].version).toBe('0.5.0'); + expect(versions['order-schema'].version).toBe('1.0.0'); + } finally { + cleanup(); + } + }); + + test('validates spec file type', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + // Create a non-matching spec file (JSON Schema file but claim it's OpenAPI) + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/fake-api.json')); + + const result = run( + 'contract add --name fake-api --type openapi --path schemas/fake-api.json -y', + dir, + { expectFail: true } + ); + + expect(result.exitCode).toBe(1); + expect(result.stdout).toMatch(/type mismatch|invalid|detected/i); + } finally { + cleanup(); + } + }); + + test('--skip-validation bypasses type check', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + // Create file + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/custom.json')); + + const result = run( + 'contract add --name custom-api --type openapi --path schemas/custom.json --skip-validation -y', + dir + ); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/added.*custom-api/i); + } finally { + cleanup(); + } + }); + + test('fails if not initialized', () => { + const { dir, cleanup } = createTempRepo(); + try { + // No contractual.yaml + + const result = run( + 'contract add --name test --type json-schema --path test.json -y', + dir, + { expectFail: true } + ); + + expect(result.exitCode).toBe(1); + expect(result.stdout + result.stderr).toMatch(/not initialized|init/i); + } finally { + cleanup(); + } + }); + + test('fails if contract name already exists', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + // Try to add with same name + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/other.json')); + + const result = run( + 'contract add --name order-schema --type json-schema --path schemas/other.json -y', + dir, + { expectFail: true } + ); + + expect(result.exitCode).toBe(1); + expect(result.stdout + result.stderr).toMatch(/contract exists|already defined|duplicate/i); + } finally { + cleanup(); + } + }); + }); + + describe('contract list', () => { + test('lists all contracts', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + { + name: 'user-api', + type: 'openapi', + path: 'specs/user.yaml', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/user.yaml')); + + const result = run('contract list', dir); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/order-schema/); + expect(result.stdout).toMatch(/user-api/); + expect(result.stdout).toMatch(/json-schema/); + expect(result.stdout).toMatch(/openapi/); + } finally { + cleanup(); + } + }); + + test('filters by exact name', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + { + name: 'user-schema', + type: 'json-schema', + path: 'schemas/user.json', + }, + { + name: 'product-api', + type: 'openapi', + path: 'specs/product.yaml', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/user.json')); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/product.yaml')); + + const result = run('contract list order-schema', dir); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/order-schema/); + expect(result.stdout).not.toMatch(/user-schema/); + expect(result.stdout).not.toMatch(/product-api/); + } finally { + cleanup(); + } + }); + + test('--json outputs structured JSON', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + const result = run('contract list --json', dir); + + expect(result.exitCode).toBe(0); + + const output = JSON.parse(result.stdout); + expect(Array.isArray(output)).toBe(true); + expect(output[0].name).toBe('order-schema'); + expect(output[0].type).toBe('json-schema'); + } finally { + cleanup(); + } + }); + + test('shows message when no contracts (schema requires at least one)', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Schema validation requires at least one contract, so empty array fails + writeFile(dir, 'contractual.yaml', 'contracts: []\n'); + + const result = run('contract list', dir, { expectFail: true }); + + // Schema validation fails on empty contracts array + expect(result.exitCode).toBeGreaterThan(0); + expect(result.stdout + result.stderr).toMatch(/must NOT have fewer than 1 items|invalid|contracts/i); + } finally { + cleanup(); + } + }); + }); +}); diff --git a/tests/e2e/19-init-enhanced.test.ts b/tests/e2e/19-init-enhanced.test.ts new file mode 100644 index 0000000..074ebc4 --- /dev/null +++ b/tests/e2e/19-init-enhanced.test.ts @@ -0,0 +1,240 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + writeFile, + readJSON, + readYAML, + fileExists, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +describe('contractual init (enhanced)', () => { + describe('version options', () => { + test('--initial-version sets starting version', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Create a spec file + copyFixture('json-schema/order-base.json', path.join(dir, 'order.schema.json')); + + const result = run('init --initial-version 1.5.0 -y', dir); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/v1\.5\.0/); + + // Verify versions.json + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + const contractName = Object.keys(versions)[0]; + expect(versions[contractName].version).toBe('1.5.0'); + } finally { + cleanup(); + } + }); + + test('-y uses default version (0.0.0)', () => { + const { dir, cleanup } = createTempRepo(); + try { + copyFixture('json-schema/order-base.json', path.join(dir, 'order.schema.json')); + + const result = run('init -y', dir); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/v0\.0\.0/); + + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + const contractName = Object.keys(versions)[0]; + expect(versions[contractName].version).toBe('0.0.0'); + } finally { + cleanup(); + } + }); + + test('creates snapshots at initial version', () => { + const { dir, cleanup } = createTempRepo(); + try { + copyFixture('json-schema/order-base.json', path.join(dir, 'order.schema.json')); + + run('init --initial-version 2.0.0 -y', dir); + + // Verify snapshot was created + expect(fileExists(dir, '.contractual/snapshots')).toBe(true); + + // Find snapshot file + const config = readYAML(dir, 'contractual.yaml') as { + contracts: Array<{ name: string }>; + }; + const contractName = config.contracts[0].name; + + // Snapshot should exist (extension may vary) + const snapshotExists = + fileExists(dir, `.contractual/snapshots/${contractName}.json`) || + fileExists(dir, `.contractual/snapshots/${contractName}.yaml`); + expect(snapshotExists).toBe(true); + } finally { + cleanup(); + } + }); + }); + + describe('versioning modes', () => { + test('--versioning independent (default)', () => { + const { dir, cleanup } = createTempRepo(); + try { + copyFixture('json-schema/order-base.json', path.join(dir, 'order.schema.json')); + + const result = run('init --versioning independent -y', dir); + + expect(result.exitCode).toBe(0); + + // Independent mode should not add versioning section (it's the default) + const config = readYAML(dir, 'contractual.yaml') as { + versioning?: { mode: string }; + }; + // Default mode doesn't need explicit config + expect(config.versioning?.mode).toBeUndefined(); + } finally { + cleanup(); + } + }); + + test('--versioning fixed', () => { + const { dir, cleanup } = createTempRepo(); + try { + copyFixture('json-schema/order-base.json', path.join(dir, 'order.schema.json')); + + const result = run('init --versioning fixed -y', dir); + + expect(result.exitCode).toBe(0); + + const config = readYAML(dir, 'contractual.yaml') as { + versioning?: { mode: string }; + }; + expect(config.versioning?.mode).toBe('fixed'); + } finally { + cleanup(); + } + }); + }); + + describe('--force flag', () => { + test('reinitializes existing project', () => { + const { dir, cleanup } = createTempRepo(); + try { + copyFixture('json-schema/order-base.json', path.join(dir, 'order.schema.json')); + + // First init + run('init --initial-version 1.0.0 -y', dir); + + // Verify first init + let versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + const contractName = Object.keys(versions)[0]; + expect(versions[contractName].version).toBe('1.0.0'); + + // Force reinit with different version + const result = run('init --initial-version 2.0.0 --force -y', dir); + + expect(result.exitCode).toBe(0); + + // Verify reinit + versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions[contractName].version).toBe('2.0.0'); + } finally { + cleanup(); + } + }); + }); + + describe('existing project handling', () => { + test('initializes unversioned contracts in existing project', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Create config manually with two contracts + writeFile( + dir, + 'contractual.yaml', + `contracts: + - name: order-schema + type: json-schema + path: schemas/order.json + - name: user-schema + type: json-schema + path: schemas/user.json +` + ); + + // Create spec files + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/user.json')); + + // Create .contractual dir with only one contract versioned + writeFile(dir, '.contractual/versions.json', '{}'); + + // Run init - should detect unversioned contracts + const result = run('init -y', dir); + + expect(result.exitCode).toBe(0); + // Should mention finding unversioned contracts + expect(result.stdout).toMatch(/without version|uninitialized|initialized/i); + } finally { + cleanup(); + } + }); + + test('skips already versioned contracts', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Create config + writeFile( + dir, + 'contractual.yaml', + `contracts: + - name: order-schema + type: json-schema + path: schemas/order.json +` + ); + + // Create spec and snapshot + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Run init - should recognize all contracts have snapshots + const result = run('init -y', dir); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/already initialized|all contracts have snapshots/i); + } finally { + cleanup(); + } + }); + }); +}); diff --git a/tests/e2e/20-version-enhanced.test.ts b/tests/e2e/20-version-enhanced.test.ts new file mode 100644 index 0000000..7cd984b --- /dev/null +++ b/tests/e2e/20-version-enhanced.test.ts @@ -0,0 +1,453 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + setupRepoWithConfig, + writeFile, + readJSON, + fileExists, + listFiles, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +describe('contractual version (enhanced)', () => { + describe('--dry-run', () => { + test('shows preview without applying changes', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // Create changeset + const changeset = `--- +"order-schema": minor +--- + +Added new feature +`; + writeFile(dir, '.contractual/changesets/feature.md', changeset); + + const result = run('version --dry-run', dir); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/dry run|preview/i); + expect(result.stdout).toMatch(/order-schema/); + expect(result.stdout).toMatch(/1\.0\.0/); + expect(result.stdout).toMatch(/1\.1\.0|minor/); + } finally { + cleanup(); + } + }); + + test('does not modify versions.json', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const changeset = `--- +"order-schema": major +--- + +Breaking change +`; + writeFile(dir, '.contractual/changesets/breaking.md', changeset); + + run('version --dry-run', dir); + + // Verify version unchanged + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['order-schema'].version).toBe('1.0.0'); + } finally { + cleanup(); + } + }); + + test('does not delete changesets', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const changeset = `--- +"order-schema": patch +--- + +Bug fix +`; + writeFile(dir, '.contractual/changesets/fix.md', changeset); + + run('version --dry-run', dir); + + // Verify changeset still exists + const changesets = listFiles(dir, '.contractual/changesets'); + expect(changesets).toContain('fix.md'); + } finally { + cleanup(); + } + }); + }); + + describe('--json', () => { + test('outputs structured JSON', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const changeset = `--- +"order-schema": minor +--- + +New feature +`; + writeFile(dir, '.contractual/changesets/feature.md', changeset); + + const result = run('version --json', dir); + + expect(result.exitCode).toBe(0); + + const output = JSON.parse(result.stdout); + expect(output.bumps).toBeDefined(); + expect(Array.isArray(output.bumps)).toBe(true); + + if (output.bumps.length > 0) { + expect(output.bumps[0].contract).toBe('order-schema'); + expect(output.bumps[0].old).toBe('1.0.0'); + expect(output.bumps[0].new).toBe('1.1.0'); + expect(output.bumps[0].type).toBe('minor'); + } + } finally { + cleanup(); + } + }); + + test('implies --yes (no prompts)', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const changeset = `--- +"order-schema": minor +--- + +Feature +`; + writeFile(dir, '.contractual/changesets/feat.md', changeset); + + // --json should apply without prompting (implies --yes) + const result = run('version --json', dir); + + expect(result.exitCode).toBe(0); + + // Verify version was bumped + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['order-schema'].version).toBe('1.1.0'); + } finally { + cleanup(); + } + }); + + test('works with --dry-run', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const changeset = `--- +"order-schema": major +--- + +Breaking +`; + writeFile(dir, '.contractual/changesets/breaking.md', changeset); + + const result = run('version --json --dry-run', dir); + + expect(result.exitCode).toBe(0); + + const output = JSON.parse(result.stdout); + expect(output.dryRun).toBe(true); + expect(output.bumps).toBeDefined(); + + // Verify version NOT changed + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['order-schema'].version).toBe('1.0.0'); + } finally { + cleanup(); + } + }); + }); + + describe('-y/--yes', () => { + test('skips confirmation prompt', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order-schema.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const changeset = `--- +"order-schema": patch +--- + +Fix +`; + writeFile(dir, '.contractual/changesets/fix.md', changeset); + + const result = run('version --yes', dir); + + expect(result.exitCode).toBe(0); + + // Verify version was bumped + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['order-schema'].version).toBe('1.0.1'); + } finally { + cleanup(); + } + }); + + test('applies changes immediately', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'api-1', + type: 'json-schema', + path: 'schemas/api1.json', + }, + { + name: 'api-2', + type: 'json-schema', + path: 'schemas/api2.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/api1.json')); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/api2.json')); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/api-1.json') + ); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/api-2.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'api-1': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + 'api-2': { version: '2.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const changeset = `--- +"api-1": minor +"api-2": major +--- + +Multiple changes +`; + writeFile(dir, '.contractual/changesets/multi.md', changeset); + + const result = run('version -y', dir); + + expect(result.exitCode).toBe(0); + + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['api-1'].version).toBe('1.1.0'); + expect(versions['api-2'].version).toBe('3.0.0'); + + // Verify changeset was consumed + const changesets = listFiles(dir, '.contractual/changesets'); + expect(changesets.filter((f) => f.endsWith('.md'))).toHaveLength(0); + } finally { + cleanup(); + } + }); + }); + + describe('no changesets', () => { + test('shows message when no changesets with --json', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'order-schema', + type: 'json-schema', + path: 'schemas/order.json', + }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'order-schema': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + // No changesets + + const result = run('version --json', dir); + + expect(result.exitCode).toBe(0); + + const output = JSON.parse(result.stdout); + expect(output.bumps).toHaveLength(0); + expect(output.changesets).toBe(0); + } finally { + cleanup(); + } + }); + }); +}); diff --git a/tests/e2e/21-spec-version-sync.test.ts b/tests/e2e/21-spec-version-sync.test.ts new file mode 100644 index 0000000..cada4b9 --- /dev/null +++ b/tests/e2e/21-spec-version-sync.test.ts @@ -0,0 +1,992 @@ +import { describe, test, expect, beforeAll } from 'vitest'; +import path from 'node:path'; +import { + createTempRepo, + copyFixture, + run, + setupRepoWithConfig, + writeFile, + readFile, + readJSON, + readYAML, + fileExists, + listFiles, + ensureCliBuilt, +} from './helpers.js'; + +beforeAll(() => { + ensureCliBuilt(); +}); + +describe('spec version sync', () => { + describe('OpenAPI (YAML)', () => { + test('updates info.version in spec on version bump', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'petstore', type: 'openapi', path: 'specs/petstore.yaml' }, + ]); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/petstore.yaml')); + + // Copy to snapshot (simulating init) + copyFixture( + 'openapi/petstore-base.yaml', + path.join(dir, '.contractual/snapshots/petstore.yaml') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + petstore: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + writeFile( + dir, + '.contractual/changesets/bump-petstore.md', + `---\n"petstore": minor\n---\n\n## petstore\n- Added new endpoint\n` + ); + + const result = run('version', dir); + expect(result.exitCode).toBe(0); + + // Spec file should have updated version + const spec = readYAML(dir, 'specs/petstore.yaml') as { info: { version: string } }; + expect(spec.info.version).toBe('1.1.0'); + + // Snapshot should also have updated version + const snapshot = readYAML(dir, '.contractual/snapshots/petstore.yaml') as { + info: { version: string }; + }; + expect(snapshot.info.version).toBe('1.1.0'); + } finally { + cleanup(); + } + }); + + test('preserves YAML comments and formatting when updating version', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'api', type: 'openapi', path: 'specs/api.yaml' }, + ]); + + // Write a spec with comments + writeFile( + dir, + 'specs/api.yaml', + `# My API spec +openapi: 3.0.3 +info: + title: My API + # Current version + version: 1.0.0 +paths: {} +` + ); + + copyFixture( + 'openapi/petstore-base.yaml', + path.join(dir, '.contractual/snapshots/api.yaml') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + api: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + writeFile( + dir, + '.contractual/changesets/bump.md', + `---\n"api": patch\n---\n\n## api\n- Fixed bug\n` + ); + + run('version', dir); + + const content = readFile(dir, 'specs/api.yaml'); + // Version updated + expect(content).toContain('version: 1.0.1'); + // Comments preserved + expect(content).toContain('# My API spec'); + expect(content).toContain('# Current version'); + } finally { + cleanup(); + } + }); + }); + + describe('OpenAPI (JSON)', () => { + test('updates info.version in JSON spec on version bump', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'api', type: 'openapi', path: 'specs/api.json' }, + ]); + + writeFile( + dir, + 'specs/api.json', + JSON.stringify( + { + openapi: '3.0.3', + info: { title: 'My API', version: '2.0.0' }, + paths: {}, + }, + null, + 2 + ) + '\n' + ); + + // Snapshot + writeFile( + dir, + '.contractual/snapshots/api.json', + JSON.stringify( + { + openapi: '3.0.3', + info: { title: 'My API', version: '2.0.0' }, + paths: {}, + }, + null, + 2 + ) + '\n' + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + api: { version: '2.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + writeFile( + dir, + '.contractual/changesets/bump.md', + `---\n"api": major\n---\n\n## api\n- Breaking change\n` + ); + + run('version', dir); + + const spec = readJSON(dir, 'specs/api.json') as { info: { version: string } }; + expect(spec.info.version).toBe('3.0.0'); + + // Snapshot too + const snapshot = readJSON(dir, '.contractual/snapshots/api.json') as { + info: { version: string }; + }; + expect(snapshot.info.version).toBe('3.0.0'); + } finally { + cleanup(); + } + }); + }); + + describe('JSON Schema (no version field)', () => { + test('does not modify json-schema spec (no standard version field)', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'order', type: 'json-schema', path: 'schemas/order.json' }, + ]); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + order: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const specBefore = readFile(dir, 'schemas/order.json'); + + writeFile( + dir, + '.contractual/changesets/bump.md', + `---\n"order": minor\n---\n\n## order\n- Added field\n` + ); + + run('version', dir); + + // Spec content should be unchanged (no version field to update) + const specAfter = readFile(dir, 'schemas/order.json'); + expect(specAfter).toBe(specBefore); + + // But versions.json should still be bumped + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['order'].version).toBe('1.1.0'); + } finally { + cleanup(); + } + }); + }); + + describe('syncVersion: false', () => { + test('does not update spec version when syncVersion is false', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { + name: 'petstore', + type: 'openapi', + path: 'specs/petstore.yaml', + syncVersion: false, + }, + ]); + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/petstore.yaml')); + copyFixture( + 'openapi/petstore-base.yaml', + path.join(dir, '.contractual/snapshots/petstore.yaml') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + petstore: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + writeFile( + dir, + '.contractual/changesets/bump.md', + `---\n"petstore": minor\n---\n\n## petstore\n- Added endpoint\n` + ); + + run('version', dir); + + // Spec should keep original version (1.0.27 from the fixture) + const spec = readYAML(dir, 'specs/petstore.yaml') as { info: { version: string } }; + expect(spec.info.version).toBe('1.0.27'); + + // But versions.json should still be bumped + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['petstore'].version).toBe('1.1.0'); + } finally { + cleanup(); + } + }); + + test('mixed: syncs one contract, skips another with syncVersion: false', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'synced-api', type: 'openapi', path: 'specs/synced.yaml' }, + { + name: 'unsynced-api', + type: 'openapi', + path: 'specs/unsynced.yaml', + syncVersion: false, + }, + ]); + + const specContent = `openapi: 3.0.3\ninfo:\n title: Test\n version: 1.0.0\npaths: {}\n`; + writeFile(dir, 'specs/synced.yaml', specContent); + writeFile(dir, 'specs/unsynced.yaml', specContent); + writeFile(dir, '.contractual/snapshots/synced-api.yaml', specContent); + writeFile(dir, '.contractual/snapshots/unsynced-api.yaml', specContent); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'synced-api': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + 'unsynced-api': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + writeFile( + dir, + '.contractual/changesets/bump-both.md', + `---\n"synced-api": minor\n"unsynced-api": minor\n---\n\n## synced-api\n- Change\n\n## unsynced-api\n- Change\n` + ); + + run('version', dir); + + // Synced: spec version updated + const syncedSpec = readYAML(dir, 'specs/synced.yaml') as { info: { version: string } }; + expect(syncedSpec.info.version).toBe('1.1.0'); + + // Unsynced: spec version unchanged + const unsyncedSpec = readYAML(dir, 'specs/unsynced.yaml') as { + info: { version: string }; + }; + expect(unsyncedSpec.info.version).toBe('1.0.0'); + + // Both bumped in versions.json + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['synced-api'].version).toBe('1.1.0'); + expect(versions['unsynced-api'].version).toBe('1.1.0'); + } finally { + cleanup(); + } + }); + }); + + describe('--no-sync-version CLI flag', () => { + test('skips spec version update when --no-sync-version is passed', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'api', type: 'openapi', path: 'specs/api.yaml' }, + ]); + + const specContent = `openapi: 3.0.3\ninfo:\n title: API\n version: 1.0.0\npaths: {}\n`; + writeFile(dir, 'specs/api.yaml', specContent); + writeFile(dir, '.contractual/snapshots/api.yaml', specContent); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ api: { version: '1.0.0', released: '2026-01-01T00:00:00Z' } }) + ); + + writeFile( + dir, + '.contractual/changesets/bump.md', + `---\n"api": minor\n---\n\n## api\n- Feature\n` + ); + + run('version --no-sync-version', dir); + + // Spec should keep original version + const spec = readYAML(dir, 'specs/api.yaml') as { info: { version: string } }; + expect(spec.info.version).toBe('1.0.0'); + + // But versions.json should still be bumped + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['api'].version).toBe('1.1.0'); + } finally { + cleanup(); + } + }); + + test('--no-sync-version overrides syncVersion: true in config', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'api', type: 'openapi', path: 'specs/api.yaml', syncVersion: true }, + ]); + + const specContent = `openapi: 3.0.3\ninfo:\n title: API\n version: 1.0.0\npaths: {}\n`; + writeFile(dir, 'specs/api.yaml', specContent); + writeFile(dir, '.contractual/snapshots/api.yaml', specContent); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ api: { version: '1.0.0', released: '2026-01-01T00:00:00Z' } }) + ); + + writeFile( + dir, + '.contractual/changesets/bump.md', + `---\n"api": minor\n---\n\n## api\n- Feature\n` + ); + + run('version --no-sync-version', dir); + + // CLI flag wins over config + const spec = readYAML(dir, 'specs/api.yaml') as { info: { version: string } }; + expect(spec.info.version).toBe('1.0.0'); + } finally { + cleanup(); + } + }); + }); + + describe('missing version field in spec', () => { + test('creates info.version when missing from OpenAPI spec', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'api', type: 'openapi', path: 'specs/api.yaml' }, + ]); + + // Write a spec WITHOUT info.version + writeFile( + dir, + 'specs/api.yaml', + `openapi: 3.0.3\ninfo:\n title: No Version API\npaths: {}\n` + ); + writeFile( + dir, + '.contractual/snapshots/api.yaml', + `openapi: 3.0.3\ninfo:\n title: No Version API\npaths: {}\n` + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + api: { version: '0.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + writeFile( + dir, + '.contractual/changesets/initial.md', + `---\n"api": minor\n---\n\n## api\n- Initial feature\n` + ); + + run('version', dir); + + // Version field should be created + const spec = readYAML(dir, 'specs/api.yaml') as { info: { version: string } }; + expect(spec.info.version).toBe('0.1.0'); + } finally { + cleanup(); + } + }); + + test('creates info object and version when info is missing from OpenAPI spec', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'api', type: 'openapi', path: 'specs/api.yaml' }, + ]); + + // Write a spec WITHOUT info at all + writeFile(dir, 'specs/api.yaml', `openapi: 3.0.3\npaths: {}\n`); + writeFile(dir, '.contractual/snapshots/api.yaml', `openapi: 3.0.3\npaths: {}\n`); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + api: { version: '0.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + writeFile( + dir, + '.contractual/changesets/initial.md', + `---\n"api": major\n---\n\n## api\n- Initial release\n` + ); + + run('version', dir); + + const spec = readYAML(dir, 'specs/api.yaml') as { info?: { version?: string } }; + expect(spec.info).toBeDefined(); + expect(spec.info!.version).toBe('1.0.0'); + } finally { + cleanup(); + } + }); + + test('creates info.version in JSON spec when missing', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'api', type: 'openapi', path: 'specs/api.json' }, + ]); + + const specNoVersion = { openapi: '3.0.3', info: { title: 'Test' }, paths: {} }; + writeFile(dir, 'specs/api.json', JSON.stringify(specNoVersion, null, 2) + '\n'); + writeFile( + dir, + '.contractual/snapshots/api.json', + JSON.stringify(specNoVersion, null, 2) + '\n' + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + api: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + writeFile( + dir, + '.contractual/changesets/bump.md', + `---\n"api": patch\n---\n\n## api\n- Fix\n` + ); + + run('version', dir); + + const spec = readJSON(dir, 'specs/api.json') as { info: { version: string } }; + expect(spec.info.version).toBe('1.0.1'); + } finally { + cleanup(); + } + }); + }); + + describe('all bump types', () => { + test('patch bump syncs spec version', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'api', type: 'openapi', path: 'specs/api.yaml' }, + ]); + + const specContent = `openapi: 3.0.3\ninfo:\n title: API\n version: 1.2.3\npaths: {}\n`; + writeFile(dir, 'specs/api.yaml', specContent); + writeFile(dir, '.contractual/snapshots/api.yaml', specContent); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ api: { version: '1.2.3', released: '2026-01-01T00:00:00Z' } }) + ); + + writeFile( + dir, + '.contractual/changesets/fix.md', + `---\n"api": patch\n---\n\n## api\n- Bug fix\n` + ); + + run('version', dir); + + const spec = readYAML(dir, 'specs/api.yaml') as { info: { version: string } }; + expect(spec.info.version).toBe('1.2.4'); + } finally { + cleanup(); + } + }); + + test('minor bump syncs spec version', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'api', type: 'openapi', path: 'specs/api.yaml' }, + ]); + + const specContent = `openapi: 3.0.3\ninfo:\n title: API\n version: 1.2.3\npaths: {}\n`; + writeFile(dir, 'specs/api.yaml', specContent); + writeFile(dir, '.contractual/snapshots/api.yaml', specContent); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ api: { version: '1.2.3', released: '2026-01-01T00:00:00Z' } }) + ); + + writeFile( + dir, + '.contractual/changesets/feature.md', + `---\n"api": minor\n---\n\n## api\n- New feature\n` + ); + + run('version', dir); + + const spec = readYAML(dir, 'specs/api.yaml') as { info: { version: string } }; + expect(spec.info.version).toBe('1.3.0'); + } finally { + cleanup(); + } + }); + + test('major bump syncs spec version', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'api', type: 'openapi', path: 'specs/api.yaml' }, + ]); + + const specContent = `openapi: 3.0.3\ninfo:\n title: API\n version: 2.5.3\npaths: {}\n`; + writeFile(dir, 'specs/api.yaml', specContent); + writeFile(dir, '.contractual/snapshots/api.yaml', specContent); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ api: { version: '2.5.3', released: '2026-01-01T00:00:00Z' } }) + ); + + writeFile( + dir, + '.contractual/changesets/breaking.md', + `---\n"api": major\n---\n\n## api\n- Breaking change\n` + ); + + run('version', dir); + + const spec = readYAML(dir, 'specs/api.yaml') as { info: { version: string } }; + expect(spec.info.version).toBe('3.0.0'); + } finally { + cleanup(); + } + }); + }); + + describe('first version (from 0.0.0)', () => { + test('syncs spec version on first bump from implicit 0.0.0', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'api', type: 'openapi', path: 'specs/api.yaml' }, + ]); + + writeFile( + dir, + 'specs/api.yaml', + `openapi: 3.0.3\ninfo:\n title: New API\n version: 0.0.0\npaths: {}\n` + ); + writeFile( + dir, + '.contractual/snapshots/api.yaml', + `openapi: 3.0.3\ninfo:\n title: New API\n version: 0.0.0\npaths: {}\n` + ); + + // No initial version in versions.json (first release) + + writeFile( + dir, + '.contractual/changesets/initial.md', + `---\n"api": major\n---\n\n## api\n- Initial release\n` + ); + + run('version', dir); + + const spec = readYAML(dir, 'specs/api.yaml') as { info: { version: string } }; + expect(spec.info.version).toBe('1.0.0'); + + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['api'].version).toBe('1.0.0'); + } finally { + cleanup(); + } + }); + }); + + describe('multiple changesets aggregation', () => { + test('spec gets the aggregated version (highest bump wins)', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'api', type: 'openapi', path: 'specs/api.yaml' }, + ]); + + const specContent = `openapi: 3.0.3\ninfo:\n title: API\n version: 1.0.0\npaths: {}\n`; + writeFile(dir, 'specs/api.yaml', specContent); + writeFile(dir, '.contractual/snapshots/api.yaml', specContent); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ api: { version: '1.0.0', released: '2026-01-01T00:00:00Z' } }) + ); + + // Three changesets: patch, minor, major β†’ major wins β†’ 2.0.0 + writeFile( + dir, + '.contractual/changesets/fix.md', + `---\n"api": patch\n---\n\n## api\n- Fix\n` + ); + writeFile( + dir, + '.contractual/changesets/feature.md', + `---\n"api": minor\n---\n\n## api\n- Feature\n` + ); + writeFile( + dir, + '.contractual/changesets/breaking.md', + `---\n"api": major\n---\n\n## api\n- Breaking\n` + ); + + run('version', dir); + + const spec = readYAML(dir, 'specs/api.yaml') as { info: { version: string } }; + expect(spec.info.version).toBe('2.0.0'); + } finally { + cleanup(); + } + }); + }); + + describe('multi-contract', () => { + test('syncs version in multiple OpenAPI specs from one changeset', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'orders-api', type: 'openapi', path: 'specs/orders.yaml' }, + { name: 'users-api', type: 'openapi', path: 'specs/users.yaml' }, + ]); + + const ordersSpec = `openapi: 3.0.3\ninfo:\n title: Orders\n version: 1.0.0\npaths: {}\n`; + const usersSpec = `openapi: 3.0.3\ninfo:\n title: Users\n version: 2.0.0\npaths: {}\n`; + writeFile(dir, 'specs/orders.yaml', ordersSpec); + writeFile(dir, 'specs/users.yaml', usersSpec); + writeFile(dir, '.contractual/snapshots/orders-api.yaml', ordersSpec); + writeFile(dir, '.contractual/snapshots/users-api.yaml', usersSpec); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'orders-api': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + 'users-api': { version: '2.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + writeFile( + dir, + '.contractual/changesets/update.md', + `---\n"orders-api": minor\n"users-api": patch\n---\n\n## orders-api\n- New endpoint\n\n## users-api\n- Fix typo\n` + ); + + run('version', dir); + + const ordersResult = readYAML(dir, 'specs/orders.yaml') as { info: { version: string } }; + expect(ordersResult.info.version).toBe('1.1.0'); + + const usersResult = readYAML(dir, 'specs/users.yaml') as { info: { version: string } }; + expect(usersResult.info.version).toBe('2.0.1'); + + // Snapshots too + const ordersSnapshot = readYAML(dir, '.contractual/snapshots/orders-api.yaml') as { + info: { version: string }; + }; + expect(ordersSnapshot.info.version).toBe('1.1.0'); + + const usersSnapshot = readYAML(dir, '.contractual/snapshots/users-api.yaml') as { + info: { version: string }; + }; + expect(usersSnapshot.info.version).toBe('2.0.1'); + } finally { + cleanup(); + } + }); + + test('mixed types: syncs OpenAPI, skips JSON Schema', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'api', type: 'openapi', path: 'specs/api.yaml' }, + { name: 'order', type: 'json-schema', path: 'schemas/order.json' }, + ]); + + const apiSpec = `openapi: 3.0.3\ninfo:\n title: API\n version: 1.0.0\npaths: {}\n`; + writeFile(dir, 'specs/api.yaml', apiSpec); + writeFile(dir, '.contractual/snapshots/api.yaml', apiSpec); + copyFixture('json-schema/order-base.json', path.join(dir, 'schemas/order.json')); + copyFixture( + 'json-schema/order-base.json', + path.join(dir, '.contractual/snapshots/order.json') + ); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + api: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + order: { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + const schemaBefore = readFile(dir, 'schemas/order.json'); + + writeFile( + dir, + '.contractual/changesets/bump.md', + `---\n"api": minor\n"order": minor\n---\n\n## api\n- Feature\n\n## order\n- Field added\n` + ); + + run('version', dir); + + // OpenAPI spec updated + const apiResult = readYAML(dir, 'specs/api.yaml') as { info: { version: string } }; + expect(apiResult.info.version).toBe('1.1.0'); + + // JSON Schema unchanged + const schemaAfter = readFile(dir, 'schemas/order.json'); + expect(schemaAfter).toBe(schemaBefore); + + // Both bumped in versions.json + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['api'].version).toBe('1.1.0'); + expect(versions['order'].version).toBe('1.1.0'); + } finally { + cleanup(); + } + }); + }); + + describe('pre-release version sync', () => { + test('syncs pre-release version into spec', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'api', type: 'openapi', path: 'specs/api.yaml' }, + ]); + + const specContent = `openapi: 3.0.3\ninfo:\n title: API\n version: 1.0.0\npaths: {}\n`; + writeFile(dir, 'specs/api.yaml', specContent); + writeFile(dir, '.contractual/snapshots/api.yaml', specContent); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ api: { version: '1.0.0', released: '2026-01-01T00:00:00Z' } }) + ); + + // Enter pre-release mode + run('pre enter beta', dir); + + writeFile( + dir, + '.contractual/changesets/feature.md', + `---\n"api": minor\n---\n\n## api\n- Beta feature\n` + ); + + run('version --yes', dir); + + // Spec should have pre-release version + const spec = readYAML(dir, 'specs/api.yaml') as { info: { version: string } }; + expect(spec.info.version).toMatch(/^1\.1\.0-beta/); + + // versions.json should match + const versions = readJSON(dir, '.contractual/versions.json') as Record< + string, + { version: string } + >; + expect(versions['api'].version).toBe(spec.info.version); + } finally { + cleanup(); + } + }); + }); + + describe('AsyncAPI', () => { + test('updates info.version in AsyncAPI spec', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'events', type: 'asyncapi', path: 'specs/events.yaml' }, + ]); + + const asyncSpec = `asyncapi: 2.6.0\ninfo:\n title: Events API\n version: 1.0.0\nchannels: {}\n`; + writeFile(dir, 'specs/events.yaml', asyncSpec); + writeFile(dir, '.contractual/snapshots/events.yaml', asyncSpec); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ events: { version: '1.0.0', released: '2026-01-01T00:00:00Z' } }) + ); + + writeFile( + dir, + '.contractual/changesets/event-change.md', + `---\n"events": minor\n---\n\n## events\n- Added new channel\n` + ); + + run('version', dir); + + const spec = readYAML(dir, 'specs/events.yaml') as { info: { version: string } }; + expect(spec.info.version).toBe('1.1.0'); + + // Snapshot too + const snapshot = readYAML(dir, '.contractual/snapshots/events.yaml') as { + info: { version: string }; + }; + expect(snapshot.info.version).toBe('1.1.0'); + } finally { + cleanup(); + } + }); + }); + + describe('ODCS', () => { + test('updates top-level version in ODCS spec', () => { + const { dir, cleanup } = createTempRepo(); + try { + setupRepoWithConfig(dir, [ + { name: 'data-contract', type: 'odcs', path: 'specs/contract.yaml' }, + ]); + + const odcsSpec = `dataContractSpecification: 3.0.0\nkind: DataContract\nversion: 1.0.0\ninfo:\n title: Sales Data\n`; + writeFile(dir, 'specs/contract.yaml', odcsSpec); + writeFile(dir, '.contractual/snapshots/data-contract.yaml', odcsSpec); + + writeFile( + dir, + '.contractual/versions.json', + JSON.stringify({ + 'data-contract': { version: '1.0.0', released: '2026-01-01T00:00:00Z' }, + }) + ); + + writeFile( + dir, + '.contractual/changesets/schema-update.md', + `---\n"data-contract": minor\n---\n\n## data-contract\n- Added new column\n` + ); + + run('version', dir); + + const spec = readYAML(dir, 'specs/contract.yaml') as { version: string }; + expect(spec.version).toBe('1.1.0'); + + // Snapshot too + const snapshot = readYAML(dir, '.contractual/snapshots/data-contract.yaml') as { + version: string; + }; + expect(snapshot.version).toBe('1.1.0'); + } finally { + cleanup(); + } + }); + }); + + describe('contract add', () => { + test('syncs initial version into spec on contract add', () => { + const { dir, cleanup } = createTempRepo(); + try { + // Create contractual.yaml with empty contracts + writeFile(dir, 'contractual.yaml', 'contracts: []\n'); + writeFile(dir, '.contractual/versions.json', '{}'); + + const specContent = `openapi: 3.0.3\ninfo:\n title: API\n version: 0.0.0\npaths: {}\n`; + writeFile(dir, 'specs/api.yaml', specContent); + + run( + 'contract add --name my-api --type openapi --path specs/api.yaml --initial-version 1.0.0 --yes --skip-validation', + dir + ); + + // Spec version should be set to 1.0.0 + const spec = readYAML(dir, 'specs/api.yaml') as { info: { version: string } }; + expect(spec.info.version).toBe('1.0.0'); + + // Snapshot should also have it + expect(fileExists(dir, '.contractual/snapshots/my-api.yaml')).toBe(true); + const snapshot = readYAML(dir, '.contractual/snapshots/my-api.yaml') as { + info: { version: string }; + }; + expect(snapshot.info.version).toBe('1.0.0'); + } finally { + cleanup(); + } + }); + }); +}); diff --git a/tests/e2e/helpers.ts b/tests/e2e/helpers.ts new file mode 100644 index 0000000..9ff5074 --- /dev/null +++ b/tests/e2e/helpers.ts @@ -0,0 +1,308 @@ +import { execSync } from 'node:child_process'; +import { + mkdtempSync, + cpSync, + readFileSync, + existsSync, + writeFileSync, + readdirSync, + rmSync, + mkdirSync, +} from 'node:fs'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { parse as parseYaml, stringify as stringifyYaml } from 'yaml'; + +/** Path to the built CLI binary */ +const CLI_BIN = path.resolve(__dirname, '../../packages/cli/bin/cli.js'); + +/** Path to the fixtures directory */ +const FIXTURES_DIR = path.resolve(__dirname, '../fixtures'); + +/** + * Create a temp directory that auto-cleans after test + */ +export function createTempRepo(): { dir: string; cleanup: () => void } { + const dir = mkdtempSync(path.join(tmpdir(), 'contractual-e2e-')); + + return { + dir, + cleanup: () => { + try { + rmSync(dir, { recursive: true, force: true }); + } catch { + // Ignore cleanup errors + } + }, + }; +} + +/** + * Create a temp directory WITH git initialized (for tests that need git commits) + */ +export function createTempGitRepo(): { dir: string; cleanup: () => void } { + const dir = mkdtempSync(path.join(tmpdir(), 'contractual-e2e-')); + + // Initialize git repo with explicit error handling + try { + execSync('git init --initial-branch=main', { cwd: dir, encoding: 'utf-8' }); + execSync('git config user.email "test@test.com"', { cwd: dir, encoding: 'utf-8' }); + execSync('git config user.name "Test User"', { cwd: dir, encoding: 'utf-8' }); + } catch (err) { + // Clean up on failure + try { + rmSync(dir, { recursive: true, force: true }); + } catch { + // Ignore cleanup errors + } + const error = err as Error & { stderr?: string }; + throw new Error(`Failed to initialize git repo: ${error.message}\n${error.stderr ?? ''}`); + } + + // Verify .git directory exists + if (!existsSync(path.join(dir, '.git'))) { + try { + rmSync(dir, { recursive: true, force: true }); + } catch { + // Ignore cleanup errors + } + throw new Error(`Git init succeeded but .git directory not found in ${dir}`); + } + + return { + dir, + cleanup: () => { + try { + rmSync(dir, { recursive: true, force: true }); + } catch { + // Ignore cleanup errors + } + }, + }; +} + +/** + * Run a contractual CLI command in the given directory + */ +export function run( + command: string, + cwd: string, + options?: { expectFail?: boolean; env?: Record; timeout?: number } +): { stdout: string; stderr: string; exitCode: number } { + const fullCmd = `node "${CLI_BIN}" ${command}`; + + try { + const stdout = execSync(fullCmd, { + cwd, + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + timeout: options?.timeout, + env: { ...process.env, ...options?.env, NO_COLOR: '1', FORCE_COLOR: '0' }, + }); + return { stdout, stderr: '', exitCode: 0 }; + } catch (err: unknown) { + const error = err as { stdout?: string; stderr?: string; status?: number }; + if (!options?.expectFail) { + // Unexpected failure β€” include output for debugging + throw new Error( + `Command failed: ${fullCmd}\n` + + `Exit code: ${error.status}\n` + + `stdout: ${error.stdout}\n` + + `stderr: ${error.stderr}` + ); + } + return { + stdout: error.stdout ?? '', + stderr: error.stderr ?? '', + exitCode: error.status ?? 1, + }; + } +} + +/** + * Copy fixture files into the temp repo + */ +export function copyFixtures(fixtureDir: string, destDir: string): void { + const srcDir = path.join(FIXTURES_DIR, fixtureDir); + cpSync(srcDir, destDir, { recursive: true }); +} + +/** + * Copy a single fixture file + */ +export function copyFixture(fixturePath: string, destPath: string): void { + const src = path.join(FIXTURES_DIR, fixturePath); + const destDir = path.dirname(destPath); + + // Ensure destination directory exists + if (!existsSync(destDir)) { + mkdirSync(destDir, { recursive: true }); + } + + cpSync(src, destPath); +} + +/** + * Read a file from the temp repo + */ +export function readFile(repoDir: string, relativePath: string): string { + return readFileSync(path.join(repoDir, relativePath), 'utf-8'); +} + +/** + * Check if a file exists in the temp repo + */ +export function fileExists(repoDir: string, relativePath: string): boolean { + return existsSync(path.join(repoDir, relativePath)); +} + +/** + * Write a file in the temp repo + */ +export function writeFile(repoDir: string, relativePath: string, content: string): void { + const fullPath = path.join(repoDir, relativePath); + const dir = path.dirname(fullPath); + + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); + } + + writeFileSync(fullPath, content); +} + +/** + * List files in a directory within the temp repo + */ +export function listFiles(repoDir: string, relativePath: string): string[] { + const fullPath = path.join(repoDir, relativePath); + if (!existsSync(fullPath)) return []; + return readdirSync(fullPath); +} + +/** + * Read and parse JSON from the temp repo + */ +export function readJSON(repoDir: string, relativePath: string): unknown { + return JSON.parse(readFile(repoDir, relativePath)); +} + +/** + * Read and parse YAML from the temp repo + */ +export function readYAML(repoDir: string, relativePath: string): unknown { + const content = readFile(repoDir, relativePath); + return parseYaml(content); +} + +/** + * Git commit all changes in the temp repo + */ +export function gitCommitAll(repoDir: string, message: string): void { + // Verify .git directory exists + if (!existsSync(path.join(repoDir, '.git'))) { + throw new Error(`gitCommitAll: Not a git repository: ${repoDir}`); + } + + try { + execSync('git add -A', { cwd: repoDir, encoding: 'utf-8' }); + execSync(`git commit -m "${message}" --allow-empty`, { cwd: repoDir, encoding: 'utf-8' }); + } catch (err) { + const error = err as Error & { stderr?: string; stdout?: string }; + throw new Error( + `gitCommitAll failed in ${repoDir}: ${error.message}\nstderr: ${error.stderr ?? ''}\nstdout: ${error.stdout ?? ''}` + ); + } +} + +/** + * Modify a YAML file (read, transform, write back) + */ +export function modifyYAML( + repoDir: string, + relativePath: string, + transform: (content: unknown) => unknown +): void { + const content = parseYaml(readFile(repoDir, relativePath)); + const modified = transform(content); + writeFile(repoDir, relativePath, stringifyYaml(modified as Record)); +} + +/** + * Modify a JSON file (read, transform, write back) + */ +export function modifyJSON( + repoDir: string, + relativePath: string, + transform: (content: unknown) => unknown +): void { + const content = readJSON(repoDir, relativePath); + const modified = transform(content); + writeFile(repoDir, relativePath, JSON.stringify(modified, null, 2) + '\n'); +} + +/** + * Setup a repo with a contractual.yaml config and .contractual directory + */ +export function setupRepoWithConfig( + repoDir: string, + contracts: Array<{ + name: string; + type: string; + path: string; + lint?: string | false; + breaking?: string | false; + syncVersion?: boolean; + }> +): void { + const config = { + contracts: contracts.map((c) => ({ + name: c.name, + type: c.type, + path: c.path, + ...(c.lint !== undefined ? { lint: c.lint } : {}), + ...(c.breaking !== undefined ? { breaking: c.breaking } : {}), + ...(c.syncVersion !== undefined ? { syncVersion: c.syncVersion } : {}), + })), + }; + + writeFile(repoDir, 'contractual.yaml', stringifyYaml(config)); + + // Create .contractual directory structure + mkdirSync(path.join(repoDir, '.contractual/changesets'), { recursive: true }); + mkdirSync(path.join(repoDir, '.contractual/snapshots'), { recursive: true }); + + if (!fileExists(repoDir, '.contractual/versions.json')) { + writeFile(repoDir, '.contractual/versions.json', '{}'); + } +} + +/** + * Check if oasdiff is available on the system + */ +export function checkOasdiff(): boolean { + try { + execSync('oasdiff --version', { stdio: 'pipe' }); + return true; + } catch { + return false; + } +} + +/** + * Ensure CLI is built before running tests + */ +export function ensureCliBuilt(): void { + if (!existsSync(CLI_BIN)) { + throw new Error( + `CLI binary not found at ${CLI_BIN}. Run 'pnpm build' in the root directory first.` + ); + } + + // Also check that the dist directory has the commands.js file + const commandsPath = path.resolve(__dirname, '../../packages/cli/dist/commands.js'); + if (!existsSync(commandsPath)) { + throw new Error( + `CLI not built properly. dist/commands.js not found. Run 'pnpm build' in the root directory.` + ); + } +} diff --git a/tests/fixtures/json-schema/order-additional-props-denied.json b/tests/fixtures/json-schema/order-additional-props-denied.json new file mode 100644 index 0000000..a59cf5f --- /dev/null +++ b/tests/fixtures/json-schema/order-additional-props-denied.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/order.schema.json", + "title": "Order", + "description": "An order in the system", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique order identifier" + }, + "status": { + "type": "string", + "enum": ["pending", "processing", "shipped", "delivered"], + "description": "Current order status" + }, + "amount": { + "type": "string", + "description": "Order total amount as string" + }, + "currency": { + "type": "string", + "format": "iso-4217", + "description": "Currency code (ISO 4217)" + }, + "items": { + "type": "array", + "description": "Line items in the order", + "items": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "quantity": { + "type": "integer", + "minimum": 1 + }, + "price": { + "type": "string" + } + }, + "required": ["sku", "quantity", "price"] + } + }, + "shipping_address": { + "type": "object", + "description": "Shipping address", + "properties": { + "street": { + "type": "string" + }, + "city": { + "type": "string" + }, + "zip": { + "type": "string", + "maxLength": 10 + }, + "country": { + "type": "string" + } + }, + "required": ["street", "city", "zip", "country"] + }, + "customer_email": { + "type": "string", + "format": "email", + "description": "Customer email address" + }, + "notes": { + "type": "string", + "description": "Optional order notes", + "maxLength": 500 + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Order creation timestamp" + }, + "metadata": { + "type": "object", + "description": "Additional metadata", + "additionalProperties": false + } + }, + "required": ["id", "status", "amount", "currency", "items", "created_at"], + "additionalProperties": false +} diff --git a/tests/fixtures/json-schema/order-base.json b/tests/fixtures/json-schema/order-base.json new file mode 100644 index 0000000..cec8f1b --- /dev/null +++ b/tests/fixtures/json-schema/order-base.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/order.schema.json", + "title": "Order", + "description": "An order in the system", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique order identifier" + }, + "status": { + "type": "string", + "enum": ["pending", "processing", "shipped", "delivered"], + "description": "Current order status" + }, + "amount": { + "type": "string", + "description": "Order total amount as string" + }, + "currency": { + "type": "string", + "format": "iso-4217", + "description": "Currency code (ISO 4217)" + }, + "items": { + "type": "array", + "description": "Line items in the order", + "items": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "quantity": { + "type": "integer", + "minimum": 1 + }, + "price": { + "type": "string" + } + }, + "required": ["sku", "quantity", "price"] + } + }, + "shipping_address": { + "type": "object", + "description": "Shipping address", + "properties": { + "street": { + "type": "string" + }, + "city": { + "type": "string" + }, + "zip": { + "type": "string", + "maxLength": 10 + }, + "country": { + "type": "string" + } + }, + "required": ["street", "city", "zip", "country"] + }, + "customer_email": { + "type": "string", + "format": "email", + "description": "Customer email address" + }, + "notes": { + "type": "string", + "description": "Optional order notes", + "maxLength": 500 + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Order creation timestamp" + }, + "metadata": { + "type": "object", + "description": "Additional metadata", + "additionalProperties": true + } + }, + "required": ["id", "status", "amount", "currency", "items", "created_at"], + "additionalProperties": false +} diff --git a/tests/fixtures/json-schema/order-constraint-tightened.json b/tests/fixtures/json-schema/order-constraint-tightened.json new file mode 100644 index 0000000..4b94b01 --- /dev/null +++ b/tests/fixtures/json-schema/order-constraint-tightened.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/order.schema.json", + "title": "Order", + "description": "An order in the system", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique order identifier" + }, + "status": { + "type": "string", + "enum": ["pending", "processing", "shipped", "delivered"], + "description": "Current order status" + }, + "amount": { + "type": "string", + "description": "Order total amount as string" + }, + "currency": { + "type": "string", + "format": "iso-4217", + "description": "Currency code (ISO 4217)" + }, + "items": { + "type": "array", + "description": "Line items in the order", + "items": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "quantity": { + "type": "integer", + "minimum": 1 + }, + "price": { + "type": "string" + } + }, + "required": ["sku", "quantity", "price"] + } + }, + "shipping_address": { + "type": "object", + "description": "Shipping address", + "properties": { + "street": { + "type": "string" + }, + "city": { + "type": "string" + }, + "zip": { + "type": "string", + "maxLength": 5 + }, + "country": { + "type": "string" + } + }, + "required": ["street", "city", "zip", "country"] + }, + "customer_email": { + "type": "string", + "format": "email", + "description": "Customer email address" + }, + "notes": { + "type": "string", + "description": "Optional order notes", + "maxLength": 200 + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Order creation timestamp" + }, + "metadata": { + "type": "object", + "description": "Additional metadata", + "additionalProperties": true + } + }, + "required": ["id", "status", "amount", "currency", "items", "created_at"], + "additionalProperties": false +} diff --git a/tests/fixtures/json-schema/order-description-changed.json b/tests/fixtures/json-schema/order-description-changed.json new file mode 100644 index 0000000..86de9a2 --- /dev/null +++ b/tests/fixtures/json-schema/order-description-changed.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/order.schema.json", + "title": "Order", + "description": "An order in the e-commerce system", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique order identifier (UUID format)" + }, + "status": { + "type": "string", + "enum": ["pending", "processing", "shipped", "delivered"], + "description": "Current order status in the fulfillment pipeline" + }, + "amount": { + "type": "string", + "description": "Order total amount as string" + }, + "currency": { + "type": "string", + "format": "iso-4217", + "description": "Currency code (ISO 4217)" + }, + "items": { + "type": "array", + "description": "Line items in the order", + "items": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "quantity": { + "type": "integer", + "minimum": 1 + }, + "price": { + "type": "string" + } + }, + "required": ["sku", "quantity", "price"] + } + }, + "shipping_address": { + "type": "object", + "description": "Shipping address", + "properties": { + "street": { + "type": "string" + }, + "city": { + "type": "string" + }, + "zip": { + "type": "string", + "maxLength": 10 + }, + "country": { + "type": "string" + } + }, + "required": ["street", "city", "zip", "country"] + }, + "customer_email": { + "type": "string", + "format": "email", + "description": "Customer email address" + }, + "notes": { + "type": "string", + "description": "Optional order notes", + "maxLength": 500 + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Order creation timestamp" + }, + "metadata": { + "type": "object", + "description": "Additional metadata", + "additionalProperties": true + } + }, + "required": ["id", "status", "amount", "currency", "items", "created_at"], + "additionalProperties": false +} diff --git a/tests/fixtures/json-schema/order-enum-value-added.json b/tests/fixtures/json-schema/order-enum-value-added.json new file mode 100644 index 0000000..99e03b4 --- /dev/null +++ b/tests/fixtures/json-schema/order-enum-value-added.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/order.schema.json", + "title": "Order", + "description": "An order in the system", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique order identifier" + }, + "status": { + "type": "string", + "enum": ["pending", "processing", "shipped", "delivered", "cancelled"], + "description": "Current order status" + }, + "amount": { + "type": "string", + "description": "Order total amount as string" + }, + "currency": { + "type": "string", + "format": "iso-4217", + "description": "Currency code (ISO 4217)" + }, + "items": { + "type": "array", + "description": "Line items in the order", + "items": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "quantity": { + "type": "integer", + "minimum": 1 + }, + "price": { + "type": "string" + } + }, + "required": ["sku", "quantity", "price"] + } + }, + "shipping_address": { + "type": "object", + "description": "Shipping address", + "properties": { + "street": { + "type": "string" + }, + "city": { + "type": "string" + }, + "zip": { + "type": "string", + "maxLength": 10 + }, + "country": { + "type": "string" + } + }, + "required": ["street", "city", "zip", "country"] + }, + "customer_email": { + "type": "string", + "format": "email", + "description": "Customer email address" + }, + "notes": { + "type": "string", + "description": "Optional order notes", + "maxLength": 500 + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Order creation timestamp" + }, + "metadata": { + "type": "object", + "description": "Additional metadata", + "additionalProperties": true + } + }, + "required": ["id", "status", "amount", "currency", "items", "created_at"], + "additionalProperties": false +} diff --git a/tests/fixtures/json-schema/order-enum-value-removed.json b/tests/fixtures/json-schema/order-enum-value-removed.json new file mode 100644 index 0000000..7c836a9 --- /dev/null +++ b/tests/fixtures/json-schema/order-enum-value-removed.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/order.schema.json", + "title": "Order", + "description": "An order in the system", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique order identifier" + }, + "status": { + "type": "string", + "enum": ["pending", "processing", "shipped"], + "description": "Current order status" + }, + "amount": { + "type": "string", + "description": "Order total amount as string" + }, + "currency": { + "type": "string", + "format": "iso-4217", + "description": "Currency code (ISO 4217)" + }, + "items": { + "type": "array", + "description": "Line items in the order", + "items": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "quantity": { + "type": "integer", + "minimum": 1 + }, + "price": { + "type": "string" + } + }, + "required": ["sku", "quantity", "price"] + } + }, + "shipping_address": { + "type": "object", + "description": "Shipping address", + "properties": { + "street": { + "type": "string" + }, + "city": { + "type": "string" + }, + "zip": { + "type": "string", + "maxLength": 10 + }, + "country": { + "type": "string" + } + }, + "required": ["street", "city", "zip", "country"] + }, + "customer_email": { + "type": "string", + "format": "email", + "description": "Customer email address" + }, + "notes": { + "type": "string", + "description": "Optional order notes", + "maxLength": 500 + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Order creation timestamp" + }, + "metadata": { + "type": "object", + "description": "Additional metadata", + "additionalProperties": true + } + }, + "required": ["id", "status", "amount", "currency", "items", "created_at"], + "additionalProperties": false +} diff --git a/tests/fixtures/json-schema/order-field-removed.json b/tests/fixtures/json-schema/order-field-removed.json new file mode 100644 index 0000000..4b8cb38 --- /dev/null +++ b/tests/fixtures/json-schema/order-field-removed.json @@ -0,0 +1,84 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/order.schema.json", + "title": "Order", + "description": "An order in the system", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique order identifier" + }, + "status": { + "type": "string", + "enum": ["pending", "processing", "shipped", "delivered"], + "description": "Current order status" + }, + "amount": { + "type": "string", + "description": "Order total amount as string" + }, + "currency": { + "type": "string", + "format": "iso-4217", + "description": "Currency code (ISO 4217)" + }, + "items": { + "type": "array", + "description": "Line items in the order", + "items": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "quantity": { + "type": "integer", + "minimum": 1 + }, + "price": { + "type": "string" + } + }, + "required": ["sku", "quantity", "price"] + } + }, + "shipping_address": { + "type": "object", + "description": "Shipping address", + "properties": { + "street": { + "type": "string" + }, + "city": { + "type": "string" + }, + "zip": { + "type": "string", + "maxLength": 10 + }, + "country": { + "type": "string" + } + }, + "required": ["street", "city", "zip", "country"] + }, + "notes": { + "type": "string", + "description": "Optional order notes", + "maxLength": 500 + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Order creation timestamp" + }, + "metadata": { + "type": "object", + "description": "Additional metadata", + "additionalProperties": true + } + }, + "required": ["id", "status", "amount", "currency", "items", "created_at"], + "additionalProperties": false +} diff --git a/tests/fixtures/json-schema/order-identical.json b/tests/fixtures/json-schema/order-identical.json new file mode 100644 index 0000000..cec8f1b --- /dev/null +++ b/tests/fixtures/json-schema/order-identical.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/order.schema.json", + "title": "Order", + "description": "An order in the system", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique order identifier" + }, + "status": { + "type": "string", + "enum": ["pending", "processing", "shipped", "delivered"], + "description": "Current order status" + }, + "amount": { + "type": "string", + "description": "Order total amount as string" + }, + "currency": { + "type": "string", + "format": "iso-4217", + "description": "Currency code (ISO 4217)" + }, + "items": { + "type": "array", + "description": "Line items in the order", + "items": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "quantity": { + "type": "integer", + "minimum": 1 + }, + "price": { + "type": "string" + } + }, + "required": ["sku", "quantity", "price"] + } + }, + "shipping_address": { + "type": "object", + "description": "Shipping address", + "properties": { + "street": { + "type": "string" + }, + "city": { + "type": "string" + }, + "zip": { + "type": "string", + "maxLength": 10 + }, + "country": { + "type": "string" + } + }, + "required": ["street", "city", "zip", "country"] + }, + "customer_email": { + "type": "string", + "format": "email", + "description": "Customer email address" + }, + "notes": { + "type": "string", + "description": "Optional order notes", + "maxLength": 500 + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Order creation timestamp" + }, + "metadata": { + "type": "object", + "description": "Additional metadata", + "additionalProperties": true + } + }, + "required": ["id", "status", "amount", "currency", "items", "created_at"], + "additionalProperties": false +} diff --git a/tests/fixtures/json-schema/order-optional-field-added.json b/tests/fixtures/json-schema/order-optional-field-added.json new file mode 100644 index 0000000..bb1166e --- /dev/null +++ b/tests/fixtures/json-schema/order-optional-field-added.json @@ -0,0 +1,93 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/order.schema.json", + "title": "Order", + "description": "An order in the system", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique order identifier" + }, + "status": { + "type": "string", + "enum": ["pending", "processing", "shipped", "delivered"], + "description": "Current order status" + }, + "amount": { + "type": "string", + "description": "Order total amount as string" + }, + "currency": { + "type": "string", + "format": "iso-4217", + "description": "Currency code (ISO 4217)" + }, + "items": { + "type": "array", + "description": "Line items in the order", + "items": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "quantity": { + "type": "integer", + "minimum": 1 + }, + "price": { + "type": "string" + } + }, + "required": ["sku", "quantity", "price"] + } + }, + "shipping_address": { + "type": "object", + "description": "Shipping address", + "properties": { + "street": { + "type": "string" + }, + "city": { + "type": "string" + }, + "zip": { + "type": "string", + "maxLength": 10 + }, + "country": { + "type": "string" + } + }, + "required": ["street", "city", "zip", "country"] + }, + "customer_email": { + "type": "string", + "format": "email", + "description": "Customer email address" + }, + "notes": { + "type": "string", + "description": "Optional order notes", + "maxLength": 500 + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Order creation timestamp" + }, + "metadata": { + "type": "object", + "description": "Additional metadata", + "additionalProperties": true + }, + "tracking_number": { + "type": "string", + "description": "Shipment tracking number" + } + }, + "required": ["id", "status", "amount", "currency", "items", "created_at"], + "additionalProperties": false +} diff --git a/tests/fixtures/json-schema/order-required-added.json b/tests/fixtures/json-schema/order-required-added.json new file mode 100644 index 0000000..249e0b3 --- /dev/null +++ b/tests/fixtures/json-schema/order-required-added.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/order.schema.json", + "title": "Order", + "description": "An order in the system", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique order identifier" + }, + "status": { + "type": "string", + "enum": ["pending", "processing", "shipped", "delivered"], + "description": "Current order status" + }, + "amount": { + "type": "string", + "description": "Order total amount as string" + }, + "currency": { + "type": "string", + "format": "iso-4217", + "description": "Currency code (ISO 4217)" + }, + "items": { + "type": "array", + "description": "Line items in the order", + "items": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "quantity": { + "type": "integer", + "minimum": 1 + }, + "price": { + "type": "string" + } + }, + "required": ["sku", "quantity", "price"] + } + }, + "shipping_address": { + "type": "object", + "description": "Shipping address", + "properties": { + "street": { + "type": "string" + }, + "city": { + "type": "string" + }, + "zip": { + "type": "string", + "maxLength": 10 + }, + "country": { + "type": "string" + } + }, + "required": ["street", "city", "zip", "country"] + }, + "customer_email": { + "type": "string", + "format": "email", + "description": "Customer email address" + }, + "notes": { + "type": "string", + "description": "Optional order notes", + "maxLength": 500 + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Order creation timestamp" + }, + "metadata": { + "type": "object", + "description": "Additional metadata", + "additionalProperties": true + } + }, + "required": ["id", "status", "amount", "currency", "items", "created_at", "customer_email"], + "additionalProperties": false +} diff --git a/tests/fixtures/json-schema/order-type-changed.json b/tests/fixtures/json-schema/order-type-changed.json new file mode 100644 index 0000000..c9c585a --- /dev/null +++ b/tests/fixtures/json-schema/order-type-changed.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/order.schema.json", + "title": "Order", + "description": "An order in the system", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique order identifier" + }, + "status": { + "type": "string", + "enum": ["pending", "processing", "shipped", "delivered"], + "description": "Current order status" + }, + "amount": { + "type": "number", + "description": "Order total amount as number" + }, + "currency": { + "type": "string", + "format": "iso-4217", + "description": "Currency code (ISO 4217)" + }, + "items": { + "type": "array", + "description": "Line items in the order", + "items": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "quantity": { + "type": "integer", + "minimum": 1 + }, + "price": { + "type": "string" + } + }, + "required": ["sku", "quantity", "price"] + } + }, + "shipping_address": { + "type": "object", + "description": "Shipping address", + "properties": { + "street": { + "type": "string" + }, + "city": { + "type": "string" + }, + "zip": { + "type": "string", + "maxLength": 10 + }, + "country": { + "type": "string" + } + }, + "required": ["street", "city", "zip", "country"] + }, + "customer_email": { + "type": "string", + "format": "email", + "description": "Customer email address" + }, + "notes": { + "type": "string", + "description": "Optional order notes", + "maxLength": 500 + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Order creation timestamp" + }, + "metadata": { + "type": "object", + "description": "Additional metadata", + "additionalProperties": true + } + }, + "required": ["id", "status", "amount", "currency", "items", "created_at"], + "additionalProperties": false +} diff --git a/tests/fixtures/openapi/circular-refs-base.yaml b/tests/fixtures/openapi/circular-refs-base.yaml new file mode 100644 index 0000000..ebfbcd7 --- /dev/null +++ b/tests/fixtures/openapi/circular-refs-base.yaml @@ -0,0 +1,60 @@ +openapi: 3.1.0 +info: + title: Circular Refs API + version: 1.0.0 +paths: + /categories: + get: + operationId: listCategories + summary: List categories (tree structure with circular refs) + responses: + "200": + description: A list of categories + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Category' + /nodes: + get: + operationId: listNodes + summary: List nodes (linked list with circular refs) + responses: + "200": + description: A list of nodes + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Node' +components: + schemas: + Category: + type: object + required: + - id + - name + properties: + id: + type: integer + name: + type: string + parent: + $ref: '#/components/schemas/Category' + children: + type: array + items: + $ref: '#/components/schemas/Category' + Node: + type: object + required: + - id + properties: + id: + type: integer + value: + type: string + next: + $ref: '#/components/schemas/Node' diff --git a/tests/fixtures/openapi/circular-refs-breaking.yaml b/tests/fixtures/openapi/circular-refs-breaking.yaml new file mode 100644 index 0000000..a0a0463 --- /dev/null +++ b/tests/fixtures/openapi/circular-refs-breaking.yaml @@ -0,0 +1,61 @@ +openapi: 3.1.0 +info: + title: Circular Refs API + version: 1.0.0 +paths: + /categories: + get: + operationId: listCategories + summary: List categories (tree structure with circular refs) + responses: + "200": + description: A list of categories + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Category' + /nodes: + get: + operationId: listNodes + summary: List nodes (linked list with circular refs) + responses: + "200": + description: A list of nodes + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Node' +components: + schemas: + Category: + type: object + required: + - id + - name + properties: + id: + # BREAKING: changed from integer to string + type: string + name: + type: string + parent: + $ref: '#/components/schemas/Category' + children: + type: array + items: + $ref: '#/components/schemas/Category' + Node: + type: object + required: + - id + properties: + id: + type: integer + value: + type: string + next: + $ref: '#/components/schemas/Node' diff --git a/tests/fixtures/openapi/petstore-31-base.yaml b/tests/fixtures/openapi/petstore-31-base.yaml new file mode 100644 index 0000000..0cace6b --- /dev/null +++ b/tests/fixtures/openapi/petstore-31-base.yaml @@ -0,0 +1,103 @@ +openapi: 3.1.0 +info: + title: Petstore - OpenAPI 3.1 + summary: A sample petstore using OpenAPI 3.1 features + license: + name: MIT + identifier: MIT + version: 1.0.0 +jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema +servers: +- url: /api/v3 +paths: + /pets: + get: + operationId: listPets + summary: List all pets + parameters: + - name: limit + in: query + required: false + schema: + type: integer + exclusiveMaximum: 100 + responses: + "200": + description: A list of pets + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + post: + operationId: createPet + summary: Create a pet + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NewPet' + responses: + "201": + description: Pet created + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + /pets/{petId}: + get: + operationId: getPetById + summary: Get a pet by ID + parameters: + - name: petId + in: path + required: true + schema: + type: string + responses: + "200": + description: A pet + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + "404": + description: Pet not found +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: + - string + - "null" + status: + type: string + enum: + - available + - pending + - sold + NewPet: + type: object + required: + - name + properties: + name: + type: string + tag: + type: + - string + - "null" diff --git a/tests/fixtures/openapi/petstore-31-breaking-endpoint-removed.yaml b/tests/fixtures/openapi/petstore-31-breaking-endpoint-removed.yaml new file mode 100644 index 0000000..14b3ea0 --- /dev/null +++ b/tests/fixtures/openapi/petstore-31-breaking-endpoint-removed.yaml @@ -0,0 +1,85 @@ +openapi: 3.1.0 +info: + title: Petstore - OpenAPI 3.1 + summary: A sample petstore using OpenAPI 3.1 features + license: + name: MIT + identifier: MIT + version: 1.0.0 +jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema +servers: +- url: /api/v3 +paths: + /pets: + get: + operationId: listPets + summary: List all pets + parameters: + - name: limit + in: query + required: false + schema: + type: integer + exclusiveMaximum: 100 + responses: + "200": + description: A list of pets + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + post: + operationId: createPet + summary: Create a pet + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NewPet' + responses: + "201": + description: Pet created + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + # BREAKING: /pets/{petId} path removed entirely +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: + - string + - "null" + status: + type: string + enum: + - available + - pending + - sold + NewPet: + type: object + required: + - name + properties: + name: + type: string + tag: + type: + - string + - "null" diff --git a/tests/fixtures/openapi/petstore-31-breaking-required-param.yaml b/tests/fixtures/openapi/petstore-31-breaking-required-param.yaml new file mode 100644 index 0000000..f0f6191 --- /dev/null +++ b/tests/fixtures/openapi/petstore-31-breaking-required-param.yaml @@ -0,0 +1,109 @@ +openapi: 3.1.0 +info: + title: Petstore - OpenAPI 3.1 + summary: A sample petstore using OpenAPI 3.1 features + license: + name: MIT + identifier: MIT + version: 1.0.0 +jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema +servers: +- url: /api/v3 +paths: + /pets: + get: + operationId: listPets + summary: List all pets + parameters: + - name: limit + in: query + required: false + schema: + type: integer + exclusiveMaximum: 100 + # BREAKING: new required query parameter + - name: apiKey + in: query + required: true + schema: + type: string + responses: + "200": + description: A list of pets + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + post: + operationId: createPet + summary: Create a pet + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NewPet' + responses: + "201": + description: Pet created + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + /pets/{petId}: + get: + operationId: getPetById + summary: Get a pet by ID + parameters: + - name: petId + in: path + required: true + schema: + type: string + responses: + "200": + description: A pet + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + "404": + description: Pet not found +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: + - string + - "null" + status: + type: string + enum: + - available + - pending + - sold + NewPet: + type: object + required: + - name + properties: + name: + type: string + tag: + type: + - string + - "null" diff --git a/tests/fixtures/openapi/petstore-31-breaking-type-narrowed.yaml b/tests/fixtures/openapi/petstore-31-breaking-type-narrowed.yaml new file mode 100644 index 0000000..9b57ab5 --- /dev/null +++ b/tests/fixtures/openapi/petstore-31-breaking-type-narrowed.yaml @@ -0,0 +1,102 @@ +openapi: 3.1.0 +info: + title: Petstore - OpenAPI 3.1 + summary: A sample petstore using OpenAPI 3.1 features + license: + name: MIT + identifier: MIT + version: 1.0.0 +jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema +servers: +- url: /api/v3 +paths: + /pets: + get: + operationId: listPets + summary: List all pets + parameters: + - name: limit + in: query + required: false + schema: + type: integer + exclusiveMaximum: 100 + responses: + "200": + description: A list of pets + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + post: + operationId: createPet + summary: Create a pet + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NewPet' + responses: + "201": + description: Pet created + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + /pets/{petId}: + get: + operationId: getPetById + summary: Get a pet by ID + parameters: + - name: petId + in: path + required: true + schema: + type: string + responses: + "200": + description: A pet + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + "404": + description: Pet not found +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + # BREAKING: narrowed from ["string", "null"] to just "string" β€” null no longer allowed + type: string + status: + type: string + enum: + - available + - pending + - sold + NewPet: + type: object + required: + - name + properties: + name: + type: string + tag: + type: + - string + - "null" diff --git a/tests/fixtures/openapi/petstore-31-description-changed.yaml b/tests/fixtures/openapi/petstore-31-description-changed.yaml new file mode 100644 index 0000000..552b3b9 --- /dev/null +++ b/tests/fixtures/openapi/petstore-31-description-changed.yaml @@ -0,0 +1,107 @@ +openapi: 3.1.0 +info: + title: Petstore - OpenAPI 3.1 + # CHANGED: description updated + summary: An updated sample petstore using OpenAPI 3.1 features + license: + name: MIT + identifier: MIT + version: 1.0.0 +jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema +servers: +- url: /api/v3 +paths: + /pets: + get: + operationId: listPets + # CHANGED: operation summary updated + summary: List all available pets + parameters: + - name: limit + in: query + required: false + schema: + type: integer + exclusiveMaximum: 100 + responses: + "200": + description: A list of pets + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + post: + operationId: createPet + summary: Create a pet + # CHANGED: operation description added + description: Creates a new pet in the store. Requires a valid pet name. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NewPet' + responses: + "201": + description: Pet created + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + /pets/{petId}: + get: + operationId: getPetById + summary: Get a pet by ID + parameters: + - name: petId + in: path + required: true + schema: + type: string + responses: + "200": + description: A pet + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + "404": + description: Pet not found +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: + - string + - "null" + status: + type: string + enum: + - available + - pending + - sold + NewPet: + type: object + required: + - name + properties: + name: + type: string + tag: + type: + - string + - "null" diff --git a/tests/fixtures/openapi/petstore-31-nonbreaking-optional-param.yaml b/tests/fixtures/openapi/petstore-31-nonbreaking-optional-param.yaml new file mode 100644 index 0000000..14954d6 --- /dev/null +++ b/tests/fixtures/openapi/petstore-31-nonbreaking-optional-param.yaml @@ -0,0 +1,109 @@ +openapi: 3.1.0 +info: + title: Petstore - OpenAPI 3.1 + summary: A sample petstore using OpenAPI 3.1 features + license: + name: MIT + identifier: MIT + version: 1.0.0 +jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema +servers: +- url: /api/v3 +paths: + /pets: + get: + operationId: listPets + summary: List all pets + parameters: + - name: limit + in: query + required: false + schema: + type: integer + exclusiveMaximum: 100 + # NON-BREAKING: new optional query parameter + - name: offset + in: query + required: false + schema: + type: integer + responses: + "200": + description: A list of pets + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + post: + operationId: createPet + summary: Create a pet + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NewPet' + responses: + "201": + description: Pet created + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + /pets/{petId}: + get: + operationId: getPetById + summary: Get a pet by ID + parameters: + - name: petId + in: path + required: true + schema: + type: string + responses: + "200": + description: A pet + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + "404": + description: Pet not found +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: + - string + - "null" + status: + type: string + enum: + - available + - pending + - sold + NewPet: + type: object + required: + - name + properties: + name: + type: string + tag: + type: + - string + - "null" diff --git a/tests/fixtures/openapi/petstore-31-nonbreaking-type-widened.yaml b/tests/fixtures/openapi/petstore-31-nonbreaking-type-widened.yaml new file mode 100644 index 0000000..b26efd3 --- /dev/null +++ b/tests/fixtures/openapi/petstore-31-nonbreaking-type-widened.yaml @@ -0,0 +1,106 @@ +openapi: 3.1.0 +info: + title: Petstore - OpenAPI 3.1 + summary: A sample petstore using OpenAPI 3.1 features + license: + name: MIT + identifier: MIT + version: 1.0.0 +jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema +servers: +- url: /api/v3 +paths: + /pets: + get: + operationId: listPets + summary: List all pets + parameters: + - name: limit + in: query + required: false + schema: + type: integer + exclusiveMaximum: 100 + responses: + "200": + description: A list of pets + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + post: + operationId: createPet + summary: Create a pet + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NewPet' + responses: + "201": + description: Pet created + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + /pets/{petId}: + get: + operationId: getPetById + summary: Get a pet by ID + parameters: + - name: petId + in: path + required: true + schema: + type: string + responses: + "200": + description: A pet + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + "404": + description: Pet not found +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + # NON-BREAKING: widened from integer to [integer, string] + type: + - integer + - string + format: int64 + name: + type: string + tag: + type: + - string + - "null" + status: + type: string + enum: + - available + - pending + - sold + NewPet: + type: object + required: + - name + properties: + name: + type: string + tag: + type: + - string + - "null" diff --git a/tests/fixtures/openapi/petstore-base.yaml b/tests/fixtures/openapi/petstore-base.yaml new file mode 100644 index 0000000..de3d03c --- /dev/null +++ b/tests/fixtures/openapi/petstore-base.yaml @@ -0,0 +1,830 @@ +openapi: 3.0.3 +info: + title: Swagger Petstore - OpenAPI 3.0 + description: |- + This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about + Swagger at [https://swagger.io](https://swagger.io). In the third iteration of the pet store, we've switched to the design first approach! + You can now help us improve the API whether it's by making changes to the definition itself or to the code. + That way, with time, we can improve the API in general, and expose some of the new features in OAS3. + + Some useful links: + - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore) + - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml) + termsOfService: https://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.27 +externalDocs: + description: Find out more about Swagger + url: https://swagger.io +servers: +- url: /api/v3 +tags: +- name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: https://swagger.io +- name: store + description: Access to Petstore orders + externalDocs: + description: Find out more about our store + url: https://swagger.io +- name: user + description: Operations about user +paths: + /pet: + put: + tags: + - pet + summary: Update an existing pet. + description: Update an existing pet by Id. + operationId: updatePet + requestBody: + description: Update an existent pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid ID supplied + "404": + description: Pet not found + "422": + description: Validation exception + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Add a new pet to the store. + description: Add a new pet to the store. + operationId: addPet + requestBody: + description: Create a new pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid input + "422": + description: Validation exception + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status. + description: Multiple status values can be provided with comma separated strings. + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + explode: true + schema: + type: string + default: available + enum: + - available + - pending + - sold + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid status value + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags. + description: "Multiple tags can be provided with comma separated strings. Use\ + \ tag1, tag2, tag3 for testing." + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + explode: true + schema: + type: array + items: + type: string + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid tag value + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}: + get: + tags: + - pet + summary: Find pet by ID. + description: Returns a single pet. + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid ID supplied + "404": + description: Pet not found + default: + description: Unexpected error + security: + - api_key: [] + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Updates a pet in the store with form data. + description: Updates a pet resource based on the form data. + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + - name: name + in: query + description: Name of pet that needs to be updated + schema: + type: string + - name: status + in: query + description: Status of pet that needs to be updated + schema: + type: string + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid input + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + delete: + tags: + - pet + summary: Deletes a pet. + description: Delete a pet. + operationId: deletePet + parameters: + - name: api_key + in: header + description: "" + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: Pet deleted + "400": + description: Invalid pet value + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}/uploadImage: + post: + tags: + - pet + summary: Uploads an image. + description: Upload image of the pet. + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + - name: additionalMetadata + in: query + description: Additional Metadata + required: false + schema: + type: string + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + "400": + description: No file uploaded + "404": + description: Pet not found + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status. + description: Returns a map of status codes to quantities. + operationId: getInventory + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + default: + description: Unexpected error + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet. + description: Place a new order in the store. + operationId: placeOrder + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Order' + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + "400": + description: Invalid input + "422": + description: Validation exception + default: + description: Unexpected error + /store/order/{orderId}: + get: + tags: + - store + summary: Find purchase order by ID. + description: For valid response try integer IDs with value <= 5 or > 10. Other + values will generate exceptions. + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of order that needs to be fetched + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + "400": + description: Invalid ID supplied + "404": + description: Order not found + default: + description: Unexpected error + delete: + tags: + - store + summary: Delete purchase order by identifier. + description: For valid response try integer IDs with value < 1000. Anything + above 1000 or non-integers will generate API errors. + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: order deleted + "400": + description: Invalid ID supplied + "404": + description: Order not found + default: + description: Unexpected error + /user: + post: + tags: + - user + summary: Create user. + description: This can only be done by the logged in user. + operationId: createUser + requestBody: + description: Created user object + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + default: + description: Unexpected error + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array. + description: Creates list of users with given input array. + operationId: createUsersWithListInput + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + default: + description: Unexpected error + /user/login: + get: + tags: + - user + summary: Logs user into the system. + description: Log into the system. + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: false + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: false + schema: + type: string + responses: + "200": + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + "400": + description: Invalid username/password supplied + default: + description: Unexpected error + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session. + description: Log user out of the system. + operationId: logoutUser + parameters: [] + responses: + "200": + description: successful operation + default: + description: Unexpected error + /user/{username}: + get: + tags: + - user + summary: Get user by user name. + description: Get user detail based on username. + operationId: getUserByName + parameters: + - name: username + in: path + description: The name that needs to be fetched. Use user1 for testing + required: true + schema: + type: string + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + "400": + description: Invalid username supplied + "404": + description: User not found + default: + description: Unexpected error + put: + tags: + - user + summary: Update user resource. + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + schema: + type: string + requestBody: + description: Update an existent user in the store + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + "200": + description: successful operation + "400": + description: bad request + "404": + description: user not found + default: + description: Unexpected error + delete: + tags: + - user + summary: Delete user resource. + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + "200": + description: User deleted + "400": + description: Invalid username supplied + "404": + description: User not found + default: + description: Unexpected error +components: + schemas: + Order: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + petId: + type: integer + format: int64 + example: 198772 + quantity: + type: integer + format: int32 + example: 7 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + example: approved + enum: + - placed + - approved + - delivered + complete: + type: boolean + xml: + name: order + Category: + type: object + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + xml: + name: category + User: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: "12345" + phone: + type: string + example: "12345" + userStatus: + type: integer + description: User Status + format: int32 + example: 1 + xml: + name: user + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: tag + Pet: + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + $ref: '#/components/schemas/Category' + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + xml: + name: '##default' + requestBodies: + Pet: + description: Pet object that needs to be added to the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + UserArray: + description: List of user object + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: https://petstore3.swagger.io/oauth/authorize + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header diff --git a/tests/fixtures/openapi/petstore-breaking-endpoint-removed.yaml b/tests/fixtures/openapi/petstore-breaking-endpoint-removed.yaml new file mode 100644 index 0000000..ae1b38e --- /dev/null +++ b/tests/fixtures/openapi/petstore-breaking-endpoint-removed.yaml @@ -0,0 +1,790 @@ +openapi: 3.0.3 +info: + title: Swagger Petstore - OpenAPI 3.0 + description: |- + This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about + Swagger at [https://swagger.io](https://swagger.io). In the third iteration of the pet store, we've switched to the design first approach! + You can now help us improve the API whether it's by making changes to the definition itself or to the code. + That way, with time, we can improve the API in general, and expose some of the new features in OAS3. + + Some useful links: + - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore) + - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml) + termsOfService: https://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.27 +externalDocs: + description: Find out more about Swagger + url: https://swagger.io +servers: +- url: /api/v3 +tags: +- name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: https://swagger.io +- name: store + description: Access to Petstore orders + externalDocs: + description: Find out more about our store + url: https://swagger.io +- name: user + description: Operations about user +paths: + /pet: + put: + tags: + - pet + summary: Update an existing pet. + description: Update an existing pet by Id. + operationId: updatePet + requestBody: + description: Update an existent pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid ID supplied + "404": + description: Pet not found + "422": + description: Validation exception + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Add a new pet to the store. + description: Add a new pet to the store. + operationId: addPet + requestBody: + description: Create a new pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid input + "422": + description: Validation exception + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status. + description: Multiple status values can be provided with comma separated strings. + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + explode: true + schema: + type: string + default: available + enum: + - available + - pending + - sold + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid status value + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}: + get: + tags: + - pet + summary: Find pet by ID. + description: Returns a single pet. + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid ID supplied + "404": + description: Pet not found + default: + description: Unexpected error + security: + - api_key: [] + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Updates a pet in the store with form data. + description: Updates a pet resource based on the form data. + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + - name: name + in: query + description: Name of pet that needs to be updated + schema: + type: string + - name: status + in: query + description: Status of pet that needs to be updated + schema: + type: string + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid input + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + delete: + tags: + - pet + summary: Deletes a pet. + description: Delete a pet. + operationId: deletePet + parameters: + - name: api_key + in: header + description: "" + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: Pet deleted + "400": + description: Invalid pet value + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}/uploadImage: + post: + tags: + - pet + summary: Uploads an image. + description: Upload image of the pet. + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + - name: additionalMetadata + in: query + description: Additional Metadata + required: false + schema: + type: string + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + "400": + description: No file uploaded + "404": + description: Pet not found + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status. + description: Returns a map of status codes to quantities. + operationId: getInventory + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + default: + description: Unexpected error + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet. + description: Place a new order in the store. + operationId: placeOrder + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Order' + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + "400": + description: Invalid input + "422": + description: Validation exception + default: + description: Unexpected error + /store/order/{orderId}: + get: + tags: + - store + summary: Find purchase order by ID. + description: For valid response try integer IDs with value <= 5 or > 10. Other + values will generate exceptions. + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of order that needs to be fetched + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + "400": + description: Invalid ID supplied + "404": + description: Order not found + default: + description: Unexpected error + delete: + tags: + - store + summary: Delete purchase order by identifier. + description: For valid response try integer IDs with value < 1000. Anything + above 1000 or non-integers will generate API errors. + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: order deleted + "400": + description: Invalid ID supplied + "404": + description: Order not found + default: + description: Unexpected error + /user: + post: + tags: + - user + summary: Create user. + description: This can only be done by the logged in user. + operationId: createUser + requestBody: + description: Created user object + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + default: + description: Unexpected error + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array. + description: Creates list of users with given input array. + operationId: createUsersWithListInput + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + default: + description: Unexpected error + /user/login: + get: + tags: + - user + summary: Logs user into the system. + description: Log into the system. + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: false + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: false + schema: + type: string + responses: + "200": + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + "400": + description: Invalid username/password supplied + default: + description: Unexpected error + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session. + description: Log user out of the system. + operationId: logoutUser + parameters: [] + responses: + "200": + description: successful operation + default: + description: Unexpected error + /user/{username}: + get: + tags: + - user + summary: Get user by user name. + description: Get user detail based on username. + operationId: getUserByName + parameters: + - name: username + in: path + description: The name that needs to be fetched. Use user1 for testing + required: true + schema: + type: string + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + "400": + description: Invalid username supplied + "404": + description: User not found + default: + description: Unexpected error + put: + tags: + - user + summary: Update user resource. + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + schema: + type: string + requestBody: + description: Update an existent user in the store + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + "200": + description: successful operation + "400": + description: bad request + "404": + description: user not found + default: + description: Unexpected error + delete: + tags: + - user + summary: Delete user resource. + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + "200": + description: User deleted + "400": + description: Invalid username supplied + "404": + description: User not found + default: + description: Unexpected error +components: + schemas: + Order: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + petId: + type: integer + format: int64 + example: 198772 + quantity: + type: integer + format: int32 + example: 7 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + example: approved + enum: + - placed + - approved + - delivered + complete: + type: boolean + xml: + name: order + Category: + type: object + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + xml: + name: category + User: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: "12345" + phone: + type: string + example: "12345" + userStatus: + type: integer + description: User Status + format: int32 + example: 1 + xml: + name: user + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: tag + Pet: + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + $ref: '#/components/schemas/Category' + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + xml: + name: '##default' + requestBodies: + Pet: + description: Pet object that needs to be added to the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + UserArray: + description: List of user object + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: https://petstore3.swagger.io/oauth/authorize + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header diff --git a/tests/fixtures/openapi/petstore-breaking-type-changed.yaml b/tests/fixtures/openapi/petstore-breaking-type-changed.yaml new file mode 100644 index 0000000..615ff40 --- /dev/null +++ b/tests/fixtures/openapi/petstore-breaking-type-changed.yaml @@ -0,0 +1,829 @@ +openapi: 3.0.3 +info: + title: Swagger Petstore - OpenAPI 3.0 + description: |- + This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about + Swagger at [https://swagger.io](https://swagger.io). In the third iteration of the pet store, we've switched to the design first approach! + You can now help us improve the API whether it's by making changes to the definition itself or to the code. + That way, with time, we can improve the API in general, and expose some of the new features in OAS3. + + Some useful links: + - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore) + - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml) + termsOfService: https://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.27 +externalDocs: + description: Find out more about Swagger + url: https://swagger.io +servers: +- url: /api/v3 +tags: +- name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: https://swagger.io +- name: store + description: Access to Petstore orders + externalDocs: + description: Find out more about our store + url: https://swagger.io +- name: user + description: Operations about user +paths: + /pet: + put: + tags: + - pet + summary: Update an existing pet. + description: Update an existing pet by Id. + operationId: updatePet + requestBody: + description: Update an existent pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid ID supplied + "404": + description: Pet not found + "422": + description: Validation exception + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Add a new pet to the store. + description: Add a new pet to the store. + operationId: addPet + requestBody: + description: Create a new pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid input + "422": + description: Validation exception + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status. + description: Multiple status values can be provided with comma separated strings. + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + explode: true + schema: + type: string + default: available + enum: + - available + - pending + - sold + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid status value + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags. + description: "Multiple tags can be provided with comma separated strings. Use\ + \ tag1, tag2, tag3 for testing." + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + explode: true + schema: + type: array + items: + type: string + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid tag value + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}: + get: + tags: + - pet + summary: Find pet by ID. + description: Returns a single pet. + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid ID supplied + "404": + description: Pet not found + default: + description: Unexpected error + security: + - api_key: [] + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Updates a pet in the store with form data. + description: Updates a pet resource based on the form data. + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + - name: name + in: query + description: Name of pet that needs to be updated + schema: + type: string + - name: status + in: query + description: Status of pet that needs to be updated + schema: + type: string + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid input + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + delete: + tags: + - pet + summary: Deletes a pet. + description: Delete a pet. + operationId: deletePet + parameters: + - name: api_key + in: header + description: "" + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: Pet deleted + "400": + description: Invalid pet value + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}/uploadImage: + post: + tags: + - pet + summary: Uploads an image. + description: Upload image of the pet. + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + - name: additionalMetadata + in: query + description: Additional Metadata + required: false + schema: + type: string + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + "400": + description: No file uploaded + "404": + description: Pet not found + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status. + description: Returns a map of status codes to quantities. + operationId: getInventory + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + default: + description: Unexpected error + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet. + description: Place a new order in the store. + operationId: placeOrder + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Order' + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + "400": + description: Invalid input + "422": + description: Validation exception + default: + description: Unexpected error + /store/order/{orderId}: + get: + tags: + - store + summary: Find purchase order by ID. + description: For valid response try integer IDs with value <= 5 or > 10. Other + values will generate exceptions. + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of order that needs to be fetched + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + "400": + description: Invalid ID supplied + "404": + description: Order not found + default: + description: Unexpected error + delete: + tags: + - store + summary: Delete purchase order by identifier. + description: For valid response try integer IDs with value < 1000. Anything + above 1000 or non-integers will generate API errors. + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: order deleted + "400": + description: Invalid ID supplied + "404": + description: Order not found + default: + description: Unexpected error + /user: + post: + tags: + - user + summary: Create user. + description: This can only be done by the logged in user. + operationId: createUser + requestBody: + description: Created user object + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + default: + description: Unexpected error + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array. + description: Creates list of users with given input array. + operationId: createUsersWithListInput + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + default: + description: Unexpected error + /user/login: + get: + tags: + - user + summary: Logs user into the system. + description: Log into the system. + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: false + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: false + schema: + type: string + responses: + "200": + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + "400": + description: Invalid username/password supplied + default: + description: Unexpected error + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session. + description: Log user out of the system. + operationId: logoutUser + parameters: [] + responses: + "200": + description: successful operation + default: + description: Unexpected error + /user/{username}: + get: + tags: + - user + summary: Get user by user name. + description: Get user detail based on username. + operationId: getUserByName + parameters: + - name: username + in: path + description: The name that needs to be fetched. Use user1 for testing + required: true + schema: + type: string + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + "400": + description: Invalid username supplied + "404": + description: User not found + default: + description: Unexpected error + put: + tags: + - user + summary: Update user resource. + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + schema: + type: string + requestBody: + description: Update an existent user in the store + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + "200": + description: successful operation + "400": + description: bad request + "404": + description: user not found + default: + description: Unexpected error + delete: + tags: + - user + summary: Delete user resource. + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + "200": + description: User deleted + "400": + description: Invalid username supplied + "404": + description: User not found + default: + description: Unexpected error +components: + schemas: + Order: + type: object + properties: + id: + type: string + example: "10" + petId: + type: integer + format: int64 + example: 198772 + quantity: + type: integer + format: int32 + example: 7 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + example: approved + enum: + - placed + - approved + - delivered + complete: + type: boolean + xml: + name: order + Category: + type: object + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + xml: + name: category + User: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: "12345" + phone: + type: string + example: "12345" + userStatus: + type: integer + description: User Status + format: int32 + example: 1 + xml: + name: user + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: tag + Pet: + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + $ref: '#/components/schemas/Category' + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + xml: + name: '##default' + requestBodies: + Pet: + description: Pet object that needs to be added to the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + UserArray: + description: List of user object + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: https://petstore3.swagger.io/oauth/authorize + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header diff --git a/tests/fixtures/openapi/petstore-identical.yaml b/tests/fixtures/openapi/petstore-identical.yaml new file mode 100644 index 0000000..de3d03c --- /dev/null +++ b/tests/fixtures/openapi/petstore-identical.yaml @@ -0,0 +1,830 @@ +openapi: 3.0.3 +info: + title: Swagger Petstore - OpenAPI 3.0 + description: |- + This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about + Swagger at [https://swagger.io](https://swagger.io). In the third iteration of the pet store, we've switched to the design first approach! + You can now help us improve the API whether it's by making changes to the definition itself or to the code. + That way, with time, we can improve the API in general, and expose some of the new features in OAS3. + + Some useful links: + - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore) + - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml) + termsOfService: https://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.27 +externalDocs: + description: Find out more about Swagger + url: https://swagger.io +servers: +- url: /api/v3 +tags: +- name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: https://swagger.io +- name: store + description: Access to Petstore orders + externalDocs: + description: Find out more about our store + url: https://swagger.io +- name: user + description: Operations about user +paths: + /pet: + put: + tags: + - pet + summary: Update an existing pet. + description: Update an existing pet by Id. + operationId: updatePet + requestBody: + description: Update an existent pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid ID supplied + "404": + description: Pet not found + "422": + description: Validation exception + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Add a new pet to the store. + description: Add a new pet to the store. + operationId: addPet + requestBody: + description: Create a new pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid input + "422": + description: Validation exception + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status. + description: Multiple status values can be provided with comma separated strings. + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + explode: true + schema: + type: string + default: available + enum: + - available + - pending + - sold + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid status value + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags. + description: "Multiple tags can be provided with comma separated strings. Use\ + \ tag1, tag2, tag3 for testing." + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + explode: true + schema: + type: array + items: + type: string + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid tag value + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}: + get: + tags: + - pet + summary: Find pet by ID. + description: Returns a single pet. + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid ID supplied + "404": + description: Pet not found + default: + description: Unexpected error + security: + - api_key: [] + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Updates a pet in the store with form data. + description: Updates a pet resource based on the form data. + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + - name: name + in: query + description: Name of pet that needs to be updated + schema: + type: string + - name: status + in: query + description: Status of pet that needs to be updated + schema: + type: string + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid input + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + delete: + tags: + - pet + summary: Deletes a pet. + description: Delete a pet. + operationId: deletePet + parameters: + - name: api_key + in: header + description: "" + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: Pet deleted + "400": + description: Invalid pet value + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}/uploadImage: + post: + tags: + - pet + summary: Uploads an image. + description: Upload image of the pet. + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + - name: additionalMetadata + in: query + description: Additional Metadata + required: false + schema: + type: string + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + "400": + description: No file uploaded + "404": + description: Pet not found + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status. + description: Returns a map of status codes to quantities. + operationId: getInventory + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + default: + description: Unexpected error + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet. + description: Place a new order in the store. + operationId: placeOrder + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Order' + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + "400": + description: Invalid input + "422": + description: Validation exception + default: + description: Unexpected error + /store/order/{orderId}: + get: + tags: + - store + summary: Find purchase order by ID. + description: For valid response try integer IDs with value <= 5 or > 10. Other + values will generate exceptions. + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of order that needs to be fetched + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + "400": + description: Invalid ID supplied + "404": + description: Order not found + default: + description: Unexpected error + delete: + tags: + - store + summary: Delete purchase order by identifier. + description: For valid response try integer IDs with value < 1000. Anything + above 1000 or non-integers will generate API errors. + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: order deleted + "400": + description: Invalid ID supplied + "404": + description: Order not found + default: + description: Unexpected error + /user: + post: + tags: + - user + summary: Create user. + description: This can only be done by the logged in user. + operationId: createUser + requestBody: + description: Created user object + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + default: + description: Unexpected error + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array. + description: Creates list of users with given input array. + operationId: createUsersWithListInput + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + default: + description: Unexpected error + /user/login: + get: + tags: + - user + summary: Logs user into the system. + description: Log into the system. + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: false + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: false + schema: + type: string + responses: + "200": + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + "400": + description: Invalid username/password supplied + default: + description: Unexpected error + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session. + description: Log user out of the system. + operationId: logoutUser + parameters: [] + responses: + "200": + description: successful operation + default: + description: Unexpected error + /user/{username}: + get: + tags: + - user + summary: Get user by user name. + description: Get user detail based on username. + operationId: getUserByName + parameters: + - name: username + in: path + description: The name that needs to be fetched. Use user1 for testing + required: true + schema: + type: string + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + "400": + description: Invalid username supplied + "404": + description: User not found + default: + description: Unexpected error + put: + tags: + - user + summary: Update user resource. + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + schema: + type: string + requestBody: + description: Update an existent user in the store + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + "200": + description: successful operation + "400": + description: bad request + "404": + description: user not found + default: + description: Unexpected error + delete: + tags: + - user + summary: Delete user resource. + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + "200": + description: User deleted + "400": + description: Invalid username supplied + "404": + description: User not found + default: + description: Unexpected error +components: + schemas: + Order: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + petId: + type: integer + format: int64 + example: 198772 + quantity: + type: integer + format: int32 + example: 7 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + example: approved + enum: + - placed + - approved + - delivered + complete: + type: boolean + xml: + name: order + Category: + type: object + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + xml: + name: category + User: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: "12345" + phone: + type: string + example: "12345" + userStatus: + type: integer + description: User Status + format: int32 + example: 1 + xml: + name: user + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: tag + Pet: + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + $ref: '#/components/schemas/Category' + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + xml: + name: '##default' + requestBodies: + Pet: + description: Pet object that needs to be added to the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + UserArray: + description: List of user object + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: https://petstore3.swagger.io/oauth/authorize + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header diff --git a/tests/fixtures/openapi/petstore-nonbreaking-description.yaml b/tests/fixtures/openapi/petstore-nonbreaking-description.yaml new file mode 100644 index 0000000..8901a5c --- /dev/null +++ b/tests/fixtures/openapi/petstore-nonbreaking-description.yaml @@ -0,0 +1,830 @@ +openapi: 3.0.3 +info: + title: Swagger Petstore - OpenAPI 3.0 + description: |- + This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about + Swagger at [https://swagger.io](https://swagger.io). We've switched to the design first approach! + Help us improve the API by making changes to the definition itself or to the code. + Over time, we can improve the API and expose new features in OAS3. + + Useful links: + - [Pet Store repository](https://github.com/swagger-api/swagger-petstore) + - [Source API definition](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml) + termsOfService: https://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.27 +externalDocs: + description: Find out more about Swagger + url: https://swagger.io +servers: +- url: /api/v3 +tags: +- name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: https://swagger.io +- name: store + description: Access to Petstore orders + externalDocs: + description: Find out more about our store + url: https://swagger.io +- name: user + description: Operations about user +paths: + /pet: + put: + tags: + - pet + summary: Update an existing pet. + description: Update an existing pet by Id. + operationId: updatePet + requestBody: + description: Update an existent pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid ID supplied + "404": + description: Pet not found + "422": + description: Validation exception + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Add a new pet to the store. + description: Add a new pet to the store. + operationId: addPet + requestBody: + description: Create a new pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid input + "422": + description: Validation exception + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status. + description: Multiple status values can be provided with comma separated strings. + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + explode: true + schema: + type: string + default: available + enum: + - available + - pending + - sold + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid status value + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags. + description: "Multiple tags can be provided with comma separated strings. Use\ + \ tag1, tag2, tag3 for testing." + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + explode: true + schema: + type: array + items: + type: string + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid tag value + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}: + get: + tags: + - pet + summary: Find pet by ID. + description: Returns a single pet. + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid ID supplied + "404": + description: Pet not found + default: + description: Unexpected error + security: + - api_key: [] + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Updates a pet in the store with form data. + description: Updates a pet resource based on the form data. + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + - name: name + in: query + description: Name of pet that needs to be updated + schema: + type: string + - name: status + in: query + description: Status of pet that needs to be updated + schema: + type: string + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid input + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + delete: + tags: + - pet + summary: Deletes a pet. + description: Delete a pet. + operationId: deletePet + parameters: + - name: api_key + in: header + description: "" + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: Pet deleted + "400": + description: Invalid pet value + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}/uploadImage: + post: + tags: + - pet + summary: Uploads an image. + description: Upload image of the pet. + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + - name: additionalMetadata + in: query + description: Additional Metadata + required: false + schema: + type: string + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + "400": + description: No file uploaded + "404": + description: Pet not found + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status. + description: Returns a map of status codes to quantities. + operationId: getInventory + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + default: + description: Unexpected error + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet. + description: Place a new order in the store. + operationId: placeOrder + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Order' + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + "400": + description: Invalid input + "422": + description: Validation exception + default: + description: Unexpected error + /store/order/{orderId}: + get: + tags: + - store + summary: Find purchase order by ID. + description: For valid response try integer IDs with value <= 5 or > 10. Other + values will generate exceptions. + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of order that needs to be fetched + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + "400": + description: Invalid ID supplied + "404": + description: Order not found + default: + description: Unexpected error + delete: + tags: + - store + summary: Delete purchase order by identifier. + description: For valid response try integer IDs with value < 1000. Anything + above 1000 or non-integers will generate API errors. + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: order deleted + "400": + description: Invalid ID supplied + "404": + description: Order not found + default: + description: Unexpected error + /user: + post: + tags: + - user + summary: Create user. + description: This can only be done by the logged in user. + operationId: createUser + requestBody: + description: Created user object + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + default: + description: Unexpected error + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array. + description: Creates list of users with given input array. + operationId: createUsersWithListInput + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + default: + description: Unexpected error + /user/login: + get: + tags: + - user + summary: Logs user into the system. + description: Log into the system. + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: false + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: false + schema: + type: string + responses: + "200": + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + "400": + description: Invalid username/password supplied + default: + description: Unexpected error + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session. + description: Log user out of the system. + operationId: logoutUser + parameters: [] + responses: + "200": + description: successful operation + default: + description: Unexpected error + /user/{username}: + get: + tags: + - user + summary: Get user by user name. + description: Get user detail based on username. + operationId: getUserByName + parameters: + - name: username + in: path + description: The name that needs to be fetched. Use user1 for testing + required: true + schema: + type: string + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + "400": + description: Invalid username supplied + "404": + description: User not found + default: + description: Unexpected error + put: + tags: + - user + summary: Update user resource. + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + schema: + type: string + requestBody: + description: Update an existent user in the store + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + "200": + description: successful operation + "400": + description: bad request + "404": + description: user not found + default: + description: Unexpected error + delete: + tags: + - user + summary: Delete user resource. + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + "200": + description: User deleted + "400": + description: Invalid username supplied + "404": + description: User not found + default: + description: Unexpected error +components: + schemas: + Order: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + petId: + type: integer + format: int64 + example: 198772 + quantity: + type: integer + format: int32 + example: 7 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + example: approved + enum: + - placed + - approved + - delivered + complete: + type: boolean + xml: + name: order + Category: + type: object + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + xml: + name: category + User: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: "12345" + phone: + type: string + example: "12345" + userStatus: + type: integer + description: User Status + format: int32 + example: 1 + xml: + name: user + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: tag + Pet: + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + $ref: '#/components/schemas/Category' + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + xml: + name: '##default' + requestBodies: + Pet: + description: Pet object that needs to be added to the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + UserArray: + description: List of user object + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: https://petstore3.swagger.io/oauth/authorize + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header diff --git a/tests/fixtures/openapi/petstore-nonbreaking-endpoint-added.yaml b/tests/fixtures/openapi/petstore-nonbreaking-endpoint-added.yaml new file mode 100644 index 0000000..46599cf --- /dev/null +++ b/tests/fixtures/openapi/petstore-nonbreaking-endpoint-added.yaml @@ -0,0 +1,856 @@ +openapi: 3.0.3 +info: + title: Swagger Petstore - OpenAPI 3.0 + description: |- + This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about + Swagger at [https://swagger.io](https://swagger.io). In the third iteration of the pet store, we've switched to the design first approach! + You can now help us improve the API whether it's by making changes to the definition itself or to the code. + That way, with time, we can improve the API in general, and expose some of the new features in OAS3. + + Some useful links: + - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore) + - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml) + termsOfService: https://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.27 +externalDocs: + description: Find out more about Swagger + url: https://swagger.io +servers: +- url: /api/v3 +tags: +- name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: https://swagger.io +- name: store + description: Access to Petstore orders + externalDocs: + description: Find out more about our store + url: https://swagger.io +- name: user + description: Operations about user +paths: + /pet: + put: + tags: + - pet + summary: Update an existing pet. + description: Update an existing pet by Id. + operationId: updatePet + requestBody: + description: Update an existent pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid ID supplied + "404": + description: Pet not found + "422": + description: Validation exception + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Add a new pet to the store. + description: Add a new pet to the store. + operationId: addPet + requestBody: + description: Create a new pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid input + "422": + description: Validation exception + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status. + description: Multiple status values can be provided with comma separated strings. + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + explode: true + schema: + type: string + default: available + enum: + - available + - pending + - sold + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid status value + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags. + description: "Multiple tags can be provided with comma separated strings. Use\ + \ tag1, tag2, tag3 for testing." + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + explode: true + schema: + type: array + items: + type: string + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid tag value + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}: + get: + tags: + - pet + summary: Find pet by ID. + description: Returns a single pet. + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid ID supplied + "404": + description: Pet not found + default: + description: Unexpected error + security: + - api_key: [] + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Updates a pet in the store with form data. + description: Updates a pet resource based on the form data. + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + - name: name + in: query + description: Name of pet that needs to be updated + schema: + type: string + - name: status + in: query + description: Status of pet that needs to be updated + schema: + type: string + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid input + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + delete: + tags: + - pet + summary: Deletes a pet. + description: Delete a pet. + operationId: deletePet + parameters: + - name: api_key + in: header + description: "" + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: Pet deleted + "400": + description: Invalid pet value + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}/uploadImage: + post: + tags: + - pet + summary: Uploads an image. + description: Upload image of the pet. + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + - name: additionalMetadata + in: query + description: Additional Metadata + required: false + schema: + type: string + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + "400": + description: No file uploaded + "404": + description: Pet not found + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/health: + get: + tags: + - pet + summary: Check pet service health. + description: Returns the health status of the pet service. + operationId: getPetHealth + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: object + properties: + status: + type: string + enum: + - healthy + - degraded + - unhealthy + timestamp: + type: string + format: date-time + default: + description: Unexpected error + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status. + description: Returns a map of status codes to quantities. + operationId: getInventory + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + default: + description: Unexpected error + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet. + description: Place a new order in the store. + operationId: placeOrder + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Order' + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + "400": + description: Invalid input + "422": + description: Validation exception + default: + description: Unexpected error + /store/order/{orderId}: + get: + tags: + - store + summary: Find purchase order by ID. + description: For valid response try integer IDs with value <= 5 or > 10. Other + values will generate exceptions. + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of order that needs to be fetched + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + "400": + description: Invalid ID supplied + "404": + description: Order not found + default: + description: Unexpected error + delete: + tags: + - store + summary: Delete purchase order by identifier. + description: For valid response try integer IDs with value < 1000. Anything + above 1000 or non-integers will generate API errors. + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: order deleted + "400": + description: Invalid ID supplied + "404": + description: Order not found + default: + description: Unexpected error + /user: + post: + tags: + - user + summary: Create user. + description: This can only be done by the logged in user. + operationId: createUser + requestBody: + description: Created user object + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + default: + description: Unexpected error + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array. + description: Creates list of users with given input array. + operationId: createUsersWithListInput + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + default: + description: Unexpected error + /user/login: + get: + tags: + - user + summary: Logs user into the system. + description: Log into the system. + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: false + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: false + schema: + type: string + responses: + "200": + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + "400": + description: Invalid username/password supplied + default: + description: Unexpected error + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session. + description: Log user out of the system. + operationId: logoutUser + parameters: [] + responses: + "200": + description: successful operation + default: + description: Unexpected error + /user/{username}: + get: + tags: + - user + summary: Get user by user name. + description: Get user detail based on username. + operationId: getUserByName + parameters: + - name: username + in: path + description: The name that needs to be fetched. Use user1 for testing + required: true + schema: + type: string + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + "400": + description: Invalid username supplied + "404": + description: User not found + default: + description: Unexpected error + put: + tags: + - user + summary: Update user resource. + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + schema: + type: string + requestBody: + description: Update an existent user in the store + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + "200": + description: successful operation + "400": + description: bad request + "404": + description: user not found + default: + description: Unexpected error + delete: + tags: + - user + summary: Delete user resource. + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + "200": + description: User deleted + "400": + description: Invalid username supplied + "404": + description: User not found + default: + description: Unexpected error +components: + schemas: + Order: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + petId: + type: integer + format: int64 + example: 198772 + quantity: + type: integer + format: int32 + example: 7 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + example: approved + enum: + - placed + - approved + - delivered + complete: + type: boolean + xml: + name: order + Category: + type: object + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + xml: + name: category + User: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: "12345" + phone: + type: string + example: "12345" + userStatus: + type: integer + description: User Status + format: int32 + example: 1 + xml: + name: user + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: tag + Pet: + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + $ref: '#/components/schemas/Category' + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + xml: + name: '##default' + requestBodies: + Pet: + description: Pet object that needs to be added to the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + UserArray: + description: List of user object + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: https://petstore3.swagger.io/oauth/authorize + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header diff --git a/tests/fixtures/tool-outputs/oasdiff/breaking-endpoint-removed.json b/tests/fixtures/tool-outputs/oasdiff/breaking-endpoint-removed.json new file mode 100644 index 0000000..42f0e6c --- /dev/null +++ b/tests/fixtures/tool-outputs/oasdiff/breaking-endpoint-removed.json @@ -0,0 +1 @@ +[{"id":"api-path-removed-without-deprecation","text":"api path removed without deprecation","level":3,"operation":"GET","operationId":"findPetsByTags","path":"/pet/findByTags","source":"openapi/petstore-base.yaml","section":"paths"}] diff --git a/tests/fixtures/tool-outputs/oasdiff/breaking-type-changed.json b/tests/fixtures/tool-outputs/oasdiff/breaking-type-changed.json new file mode 100644 index 0000000..94f6cf3 --- /dev/null +++ b/tests/fixtures/tool-outputs/oasdiff/breaking-type-changed.json @@ -0,0 +1 @@ +[{"id":"request-property-type-changed","text":"the 'id' request property type/format changed from 'integer'/'int64' to 'string'/'' (media type: application/json)","level":3,"operation":"POST","operationId":"placeOrder","path":"/store/order","source":"openapi/petstore-breaking-type-changed.yaml","section":"paths"},{"id":"response-property-type-changed","text":"the 'id' response's property type/format changed from 'integer'/'int64' to 'string'/'' for status '200'","level":3,"operation":"POST","operationId":"placeOrder","path":"/store/order","source":"openapi/petstore-breaking-type-changed.yaml","section":"paths"},{"id":"response-property-type-changed","text":"the 'id' response's property type/format changed from 'integer'/'int64' to 'string'/'' for status '200' (media type: application/xml)","level":3,"operation":"GET","operationId":"getOrderById","path":"/store/order/{orderId}","source":"openapi/petstore-breaking-type-changed.yaml","section":"paths"},{"id":"response-property-type-changed","text":"the 'id' response's property type/format changed from 'integer'/'int64' to 'string'/'' for status '200' (media type: application/json)","level":3,"operation":"GET","operationId":"getOrderById","path":"/store/order/{orderId}","source":"openapi/petstore-breaking-type-changed.yaml","section":"paths"}] diff --git a/tests/fixtures/tool-outputs/oasdiff/changelog-endpoint-removed.json b/tests/fixtures/tool-outputs/oasdiff/changelog-endpoint-removed.json new file mode 100644 index 0000000..42f0e6c --- /dev/null +++ b/tests/fixtures/tool-outputs/oasdiff/changelog-endpoint-removed.json @@ -0,0 +1 @@ +[{"id":"api-path-removed-without-deprecation","text":"api path removed without deprecation","level":3,"operation":"GET","operationId":"findPetsByTags","path":"/pet/findByTags","source":"openapi/petstore-base.yaml","section":"paths"}] diff --git a/tests/fixtures/tool-outputs/oasdiff/diff-endpoint-removed.json b/tests/fixtures/tool-outputs/oasdiff/diff-endpoint-removed.json new file mode 100644 index 0000000..59bdd3a --- /dev/null +++ b/tests/fixtures/tool-outputs/oasdiff/diff-endpoint-removed.json @@ -0,0 +1 @@ +{"paths":{"deleted":["/pet/findByTags"]}} diff --git a/tests/fixtures/tool-outputs/oasdiff/no-changes.json b/tests/fixtures/tool-outputs/oasdiff/no-changes.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/tests/fixtures/tool-outputs/oasdiff/no-changes.json @@ -0,0 +1 @@ +[] diff --git a/tests/fixtures/tool-outputs/oasdiff/nonbreaking-endpoint-added.json b/tests/fixtures/tool-outputs/oasdiff/nonbreaking-endpoint-added.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/tests/fixtures/tool-outputs/oasdiff/nonbreaking-endpoint-added.json @@ -0,0 +1 @@ +[] diff --git a/tsconfig.build.json b/tsconfig.build.json index 32971be..5ea453e 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,10 +1,6 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "target": "esnext", - "module": "esnext", - "moduleResolution": "node", - "incremental": false, "sourceMap": false }, "exclude": [ diff --git a/tsconfig.json b/tsconfig.json index 26aa473..33aeced 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,19 +1,19 @@ { "compilerOptions": { - "module": "esnext", - "target": "esnext", - "skipLibCheck": true, + "module": "NodeNext", + "target": "ES2022", + "moduleResolution": "NodeNext", "declaration": true, + "declarationMap": true, + "skipLibCheck": true, "removeComments": false, "emitDecoratorMetadata": true, "experimentalDecorators": true, "sourceMap": true, "baseUrl": "./", "outDir": "./dist", - "incremental": false, "noImplicitAny": true, "strictNullChecks": true, - "moduleResolution": "node", "esModuleInterop": true, "resolveJsonModule": true, "types": [ diff --git a/vitest.config.e2e.ts b/vitest.config.e2e.ts new file mode 100644 index 0000000..166305d --- /dev/null +++ b/vitest.config.e2e.ts @@ -0,0 +1,18 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['tests/e2e/**/*.test.ts'], + testTimeout: 60_000, + hookTimeout: 30_000, + teardownTimeout: 30_000, + // Run serially β€” each test creates temp dirs, parallel risks port/path conflicts + fileParallelism: false, + globals: true, + server: { + deps: { + inline: ['yaml'], + }, + }, + }, +});