diff --git a/.eslintrc b/.eslintrc index fc09a8f..413ced2 100644 --- a/.eslintrc +++ b/.eslintrc @@ -72,6 +72,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..4c357d1 --- /dev/null +++ b/.github/workflows/e2e-release.yml @@ -0,0 +1,162 @@ +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: 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: 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 jq + + - 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: Config Git + run: | + git config --global user.email "e2e@contractual.dev" + git config --global user.name "Contractual e2e" + + - name: Commit Provenance Change + run: | + git add . + git commit -am "remove provenance" + + - 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..810df57 100644 --- a/.github/workflows/publish-packages.yml +++ b/.github/workflows/publish-packages.yml @@ -20,145 +20,97 @@ 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 - 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 }} - - - 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 }} - 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: 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: Build - run: pnpm 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 - run: | - git config --global user.email "e2e@suites.dev" - git config --global user.name "Suites e2e" - git add . - git commit -am "remove provenance" - - - 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 - - - name: Install Dependencies - 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 - run: | - cd "$PWD/e2e/$LIBRARY/$FRAMEWORK" - npm test + name: E2E Release Simulation + uses: ./.github/workflows/e2e-release.yml + with: + target_branch: ${{ github.event.inputs.target_branch }} + permissions: + contents: write + id-token: write publish: - name: Publish Packages + name: Publish to NPM needs: [e2e] runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.target_branch }} + fetch-depth: 0 - - name: Setup Registry + - name: Setup Node uses: actions/setup-node@v4 with: + node-version: '22.x' registry-url: https://registry.npmjs.org/ - scope: '@suites' + scope: '@contractual' always-auth: true + - name: Install pnpm uses: pnpm/action-setup@v4 with: version: 9.15.4 - - name: pnpm + - name: Install dependencies run: pnpm install --frozen-lockfile - name: Build - run: pnpm build + run: pnpm lerna run build + + - name: Capture Versions for Report + run: | + 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 ${{ github.event.inputs.strategy }} --access public --tag ${{ github.event.inputs.dist_tag }} + run: | + npx lerna publish from-package --yes \ + --dist-tag ${{ github.event.inputs.dist_tag }} \ + --no-git-reset env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Generate Success Summary + if: success() + run: | + 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: | + 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..0fd8d19 100644 --- a/.github/workflows/release-packages.yml +++ b/.github/workflows/release-packages.yml @@ -40,8 +40,18 @@ permissions: id-token: write jobs: + e2e: + name: E2E Release Simulation + uses: ./.github/workflows/e2e-release.yml + with: + target_branch: ${{ github.event.inputs.target_branch }} + permissions: + contents: write + id-token: write + tag-version: name: Tag Version + needs: [e2e] runs-on: ubuntu-latest steps: - name: Checkout @@ -50,6 +60,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 +84,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 +111,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..fdf186c 100644 --- a/README.md +++ b/README.md @@ -1,173 +1,107 @@ -

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 + +The `contractual` CLI and GitHub Action manage schema contract lifecycle for OpenAPI, JSON Schema, and AsyncAPI. + +It provides: +- Linting of specs +- Structural breaking change detection against snapshots +- Changeset generation and versioning +- Changelog generation +- GitHub Action integration for PR checks and release automation + +## Installation + +### npm + +```sh +npm install -g contractual +``` + +### Other package managers + +```sh +pnpm add -g contractual +yarn global add contractual +bun add -g contractual +``` + +## Usage + +The CLI provides command summaries and flags: + +```sh +contractual --help +``` + +For usage details, see the documentation, especially: + +- [`contractual breaking`][breaking-docs] +- [`contractual lint`][lint-docs] +- [`contractual changeset`][changeset-docs] +- [`contractual version`][version-docs] +- [GitHub Action setup][action-docs] + +## CLI breaking change policy + +Breaking changes are documented in release notes for the npm package. + +## Goals for schema contracts + +Schema contracts are a compatibility boundary between producers and consumers. Contractual standardizes linting, breaking change detection, versioning, and changelog generation across OpenAPI, JSON Schema, and AsyncAPI. + +Contractual wraps existing tooling where possible and adds missing lifecycle steps, including built-in JSON Schema diffing where production-grade tooling is limited. + +## The Contractual workflow + +Contractual uses a repository state directory at `.contractual/` to store versions, snapshots, and pending changesets. The GitHub Action can post diff tables on pull requests and open a Version Contracts PR for release automation. + +The GitHub Action is optional. The CLI can run locally or in CI. + +## More advanced CLI features + +- Custom linters and differs via `contractual.yaml` +- Custom outputs for code generation +- JSON output formats for CI systems +- Base snapshot selection with `--base` +- Monorepo support with multiple configs +- Optional AI explanations with `ANTHROPIC_API_KEY` + +## Next steps + +After installation, follow the CLI quickstart: + +- [Quickstart][quickstart-docs] +- [Configuration reference][config-docs] +- [Breaking change detection][breaking-overview] + +## Builds + +The CLI is distributed via npm and requires Node.js 18 or later. + +| Platform | Support | +|----------|---------| +| macOS | Node.js 18+ | +| Linux | Node.js 18+ | +| Windows | Node.js 18+ | + +## Community + +Issues and feature requests: +- [GitHub issues][issues] + +Documentation: +- [contractual.dev][docs] + +License: +- [MIT](LICENSE) + +[docs]: https://contractual.dev +[issues]: https://github.com/contractual-dev/contractual/issues +[quickstart-docs]: https://contractual.dev/getting-started/quickstart +[config-docs]: https://contractual.dev/reference/configuration +[breaking-docs]: https://contractual.dev/breaking/usage +[breaking-overview]: https://contractual.dev/breaking/overview +[lint-docs]: https://contractual.dev/linting/usage +[changeset-docs]: https://contractual.dev/versioning/usage +[version-docs]: https://contractual.dev/versioning/usage +[action-docs]: https://contractual.dev/github-action/setup 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..f4e976f --- /dev/null +++ b/e2e/cli-basic/cli-install.test.ts @@ -0,0 +1,42 @@ +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 --version'); + expect(result).toMatch(/\d+\.\d+\.\d+/); + }); + + test('contractual --help shows available commands', () => { + const result = run('npx contractual --help'); + expect(result).toContain('init'); + expect(result).toContain('lint'); + expect(result).toContain('breaking'); + expect(result).toContain('changeset'); + expect(result).toContain('version'); + expect(result).toContain('status'); + }); + + test('contractual init --help shows init options', () => { + const result = run('npx contractual init --help'); + expect(result.toLowerCase()).toContain('initialize'); + }); + + test('contractual lint --help shows lint options', () => { + const result = run('npx contractual lint --help'); + expect(result).toContain('--format'); + }); + + test('contractual breaking --help shows breaking options', () => { + const result = run('npx contractual breaking --help'); + expect(result).toContain('--format'); + }); +}); 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..8f0ddbd --- /dev/null +++ b/e2e/cli-lifecycle/full-lifecycle.test.ts @@ -0,0 +1,135 @@ +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'); + }); +}); diff --git a/e2e/cli-lifecycle/helpers.ts b/e2e/cli-lifecycle/helpers.ts new file mode 100644 index 0000000..9a1d158 --- /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 ${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..8f05a64 --- /dev/null +++ b/local-e2e.docker-compose.yaml @@ -0,0 +1,19 @@ +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 + +networks: + e2e-network: + driver: bridge + +volumes: + verdaccio-storage: 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..9c99ed9 --- /dev/null +++ b/packages/changesets/index.ts @@ -0,0 +1,27 @@ +// 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, + VERSIONS_FILE, + SNAPSHOTS_DIR, + CHANGESETS_DIR, + DEFAULT_VERSION, + SPEC_EXTENSIONS, + incrementVersion, + VersionError, + type BumpOperationResult, +} from './versioning/manager.js'; +export { formatDate, appendChangelog } from './versioning/changelog.js'; diff --git a/packages/changesets/package.json b/packages/changesets/package.json new file mode 100644 index 0000000..e4a9329 --- /dev/null +++ b/packages/changesets/package.json @@ -0,0 +1,51 @@ +{ + "name": "@contractual/changesets", + "private": false, + "version": "0.0.0", + "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..ad5b39e --- /dev/null +++ b/packages/changesets/versioning/manager.ts @@ -0,0 +1,207 @@ +import { existsSync, readFileSync, writeFileSync, copyFileSync } from 'node:fs'; +import { join, extname } from 'node:path'; +import * as semver from 'semver'; +import type { VersionsFile, SimpleVersionEntry, BumpType } 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; + +/** + * 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 }; + } + + /** + * Save versions.json to disk + */ + private save(): void { + const content = JSON.stringify(this.versions, null, 2); + writeFileSync(this.versionsPath, content, 'utf-8'); + } +} 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..6fd3977 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -4,9 +4,17 @@ "version": "0.0.0", "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,27 @@ "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:*", + "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..dafce66 100644 --- a/packages/cli/src/commands.ts +++ b/packages/cli/src/commands.ts @@ -1,22 +1,62 @@ import { Command } from 'commander'; -import { graduateSpec, generateContract } from './commands/generate.command.js'; +import { initCommand } from './commands/init.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 { 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') + .action(initCommand); + +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') + .action(versionCommand); + +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/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..29ca827 --- /dev/null +++ b/packages/cli/src/commands/init.command.ts @@ -0,0 +1,207 @@ +import { existsSync, writeFileSync } from 'node:fs'; +import { join, basename } from 'node:path'; +import fg from 'fast-glob'; +import chalk from 'chalk'; +import ora from 'ora'; +import { stringify as stringifyYaml } from 'yaml'; +import { ensureContractualDir, detectSpecType } from '../utils/files.js'; +import type { ContractDefinition, ContractType } from '@contractual/types'; + +/** + * 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; +} + +/** + * Initialize Contractual in a repository + * + * Scans for spec files and generates contractual.yaml configuration + */ +export async function initCommand(): Promise { + const cwd = process.cwd(); + const configPath = join(cwd, 'contractual.yaml'); + + // Check if already initialized + if (existsSync(configPath)) { + console.log(chalk.red('Already initialized:') + ' contractual.yaml exists'); + console.log(chalk.dim('Use `contractual status` to see current state')); + process.exitCode = 1; + 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.text = `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) { + spinner.warn('No 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; + } + + // Generate config + const config = { + contracts, + changeset: { + autoDetect: true, + requireOnPR: true, + }, + }; + + // Write contractual.yaml + const yamlContent = stringifyYaml(config, { + lineWidth: 100, + singleQuote: true, + }); + writeFileSync(configPath, yamlContent, 'utf-8'); + + // Create .contractual directory structure + ensureContractualDir(cwd); + + spinner.succeed('Initialized Contractual'); + + // Print summary + 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):`)); + + 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)}`); + } + + console.log(); + console.log(chalk.dim('Next steps:')); + console.log(chalk.dim(' 1. Review contractual.yaml and adjust as needed')); + console.log(chalk.dim(' 2. Run `contractual status` to check current state')); + console.log(chalk.dim(' 3. Run `contractual lint` to validate specs')); + } catch (error) { + spinner.fail('Initialization failed'); + const message = error instanceof Error ? error.message : 'Unknown error'; + console.error(chalk.red(message)); + process.exitCode = 1; + } +} + +/** + * 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/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..3cfe9d5 --- /dev/null +++ b/packages/cli/src/commands/version.command.ts @@ -0,0 +1,141 @@ +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 { + VersionManager, + readChangesets, + aggregateBumps, + extractContractChanges, + appendChangelog, +} from '@contractual/changesets'; +import type { BumpResult } from '@contractual/types'; + +/** + * Consume changesets and bump versions + */ +export async function versionCommand(): 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); + } + + 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'); + 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) { + console.log(chalk.gray('No version bumps required.')); + process.exit(0); + } + + // Initialize version manager + const versionManager = new VersionManager(contractualDir); + + // Process each contract bump + const bumpSpinner = ora('Applying version bumps...').start(); + const bumpResults: BumpResult[] = []; + const consumedChangesetPaths: string[] = []; + + for (const [contractName, bumpType] of Object.entries(aggregatedBumps)) { + // Find the contract in config + const contract = config.contracts.find((c) => c.name === contractName); + if (!contract) { + console.warn( + chalk.yellow(`Warning: Contract "${contractName}" not found in config, skipping.`) + ); + continue; + } + + // Apply semver bump and update snapshot + const { oldVersion, newVersion } = 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 = 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 = 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 (error) { + // Ignore cleanup errors + } + } + cleanupSpinner.succeed(`Removed ${consumedChangesetPaths.length} changeset(s)`); + + // Print summary + 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!'), 'Run `contractual status` to verify changes.'); +} 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..1f5b8dd --- /dev/null +++ b/packages/cli/src/config/schema.json @@ -0,0 +1,108 @@ +{ + "$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" } + ] + }, + "generate": { + "type": "array", + "description": "Output generation commands", + "items": { "type": "string" } + } + }, + "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..c46e9ce --- /dev/null +++ b/packages/cli/src/formatters/diff.ts @@ -0,0 +1,149 @@ +/** + * 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 + */ +export function formatDiffJson(results: DiffResult[]): string { + return JSON.stringify({ results }, 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..740bb83 --- /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..ba5dc92 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1 +1,33 @@ -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, + createChangeset, + readChangesets, + aggregateBumps, + generateChangesetName, + extractContractChanges, + appendChangelog, + VERSIONS_FILE, + SNAPSHOTS_DIR, + CHANGESETS_DIR, +} 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/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/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..4ab1f9d --- /dev/null +++ b/packages/differs.json-schema/package.json @@ -0,0 +1,58 @@ +{ + "name": "@contractual/differs.json-schema", + "version": "0.1.0", + "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" + ], + "devDependencies": { + "rimraf": "^5.0.5", + "vitest": "^3.0.3" + }, + "publishConfig": { + "access": "public", + "provenance": true + } +} diff --git a/packages/differs.json-schema/src/classifiers.ts b/packages/differs.json-schema/src/classifiers.ts new file mode 100644 index 0000000..117abc1 --- /dev/null +++ b/packages/differs.json-schema/src/classifiers.ts @@ -0,0 +1,398 @@ +/** + * 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 './types.js'; + +/** + * 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', +]); + +/** + * 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', +]); + +/** + * 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', + // 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.json-schema/src/compare.ts b/packages/differs.json-schema/src/compare.ts new file mode 100644 index 0000000..623a7e6 --- /dev/null +++ b/packages/differs.json-schema/src/compare.ts @@ -0,0 +1,295 @@ +/** + * 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 './types.js'; +import { + type CompareResult, + type CompareOptions, + type StrandsTrace, + type StrandsCompatibility, + type StrandsVersion, + type SemanticVersion, +} from './types.js'; +import { resolveRefs } from './ref-resolver.js'; +import { walk } from './walker.js'; +import { classify, classifyPropertyAdded } from './classifiers.js'; + +/** + * 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..74ec7ac --- /dev/null +++ b/packages/differs.json-schema/src/differ.ts @@ -0,0 +1,374 @@ +/** + * 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, Change, ChangeSeverity, RawChange } from './types.js'; +import { resolveRefs } from './ref-resolver.js'; +import { walk } from './walker.js'; +import { classify, classifyPropertyAdded } from './classifiers.js'; + +/** + * 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 = 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 'unknown-change': + default: + return `Unknown change at ${pathDisplay}`; + } +} + +/** + * 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; +} + +/** + * Classify a change, with context-aware classification for property-added + * + * @param change - The raw change to classify + * @param newSchema - The new schema for context (used for property-added) + * @returns The severity classification + */ +function classifyChange(change: RawChange, newSchema: unknown): ChangeSeverity { + if (change.type === 'property-added') { + return classifyPropertyAdded(change, newSchema); + } + return classify(change); +} + +/** + * Diff two JSON Schema files and detect structural changes + * + * Reads both schema files, resolves $ref references, walks the schemas + * to detect differences, and classifies each change by severity. + * + * @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 + * + * @example + * ```typescript + * const result = await diffJsonSchema('v1/schema.json', 'v2/schema.json'); + * + * console.log(`Suggested bump: ${result.suggestedBump}`); + * console.log(`Breaking changes: ${result.summary.breaking}`); + * + * for (const change of result.changes) { + * console.log(`[${change.severity}] ${change.message}`); + * } + * ``` + */ +export async function diffJsonSchema(oldPath: string, newPath: string): Promise { + // Read both schema files + 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)}` + ); + } + + // Parse JSON + 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)}` + ); + } + + // Resolve $refs in both schemas + const resolvedOldResult = resolveRefs(oldSchema); + const resolvedNewResult = resolveRefs(newSchema); + + const resolvedOld = resolvedOldResult.schema; + const resolvedNew = resolvedNewResult.schema; + + // Log warnings if any $refs couldn't be resolved + const allWarnings = [ + ...resolvedOldResult.warnings.map((w) => `[old] ${w}`), + ...resolvedNewResult.warnings.map((w) => `[new] ${w}`), + ]; + + if (allWarnings.length > 0) { + // Warnings are informational; diff continues with best effort + // In production, you might want to expose these in the result + } + + // Walk both schemas and detect raw changes + const rawChanges = walk(resolvedOld, resolvedNew, ''); + + // Classify changes and build final Change objects + const changes: Change[] = rawChanges.map((raw) => ({ + path: raw.path, + severity: classifyChange(raw, resolvedNew), + category: raw.type, + message: formatChangeMessage(raw), + oldValue: raw.oldValue, + newValue: raw.newValue, + })); + + // Calculate summary counts + 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, + }; + + // Determine suggested semver bump based on highest severity + const suggestedBump = + summary.breaking > 0 + ? 'major' + : summary.nonBreaking > 0 + ? 'minor' + : summary.patch > 0 + ? 'patch' + : 'none'; + + return { + contract: '', + changes, + summary, + suggestedBump, + }; +} + +/** + * Diff two JSON Schema objects and detect structural changes + * + * Like diffJsonSchema but accepts schema objects directly instead of file paths. + * + * @param oldSchema - The old/base schema object + * @param newSchema - The new/changed schema object + * @returns DiffResult with classified changes and suggested bump + * + * @example + * ```typescript + * const oldSchema = { type: 'object', properties: { name: { type: 'string' } } }; + * const newSchema = { type: 'object', properties: { name: { type: 'number' } } }; + * + * const result = diffJsonSchemaObjects(oldSchema, newSchema); + * console.log(`Suggested bump: ${result.suggestedBump}`); + * ``` + */ +export function diffJsonSchemaObjects(oldSchema: unknown, newSchema: unknown): DiffResult { + // Resolve $refs in both schemas + const resolvedOldResult = resolveRefs(oldSchema); + const resolvedNewResult = resolveRefs(newSchema); + + const resolvedOld = resolvedOldResult.schema; + const resolvedNew = resolvedNewResult.schema; + + // Walk both schemas and detect raw changes + const rawChanges = walk(resolvedOld, resolvedNew, ''); + + // Classify changes and build final Change objects + const changes: Change[] = rawChanges.map((raw) => ({ + path: raw.path, + severity: classifyChange(raw, resolvedNew), + category: raw.type, + message: formatChangeMessage(raw), + oldValue: raw.oldValue, + newValue: raw.newValue, + })); + + // Calculate summary counts + 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, + }; + + // Determine suggested semver bump based on highest severity + const suggestedBump = + summary.breaking > 0 + ? 'major' + : summary.nonBreaking > 0 + ? 'minor' + : summary.patch > 0 + ? 'patch' + : 'none'; + + return { + contract: '', + changes, + summary, + suggestedBump, + }; +} diff --git a/packages/differs.json-schema/src/index.ts b/packages/differs.json-schema/src/index.ts new file mode 100644 index 0000000..2cdb4eb --- /dev/null +++ b/packages/differs.json-schema/src/index.ts @@ -0,0 +1,128 @@ +/** + * @contractual/differs.json-schema + * + * Detect and classify breaking changes between JSON Schema versions. + * + * This package provides tools to compare JSON Schema documents and determine + * the semantic versioning impact of changes. It identifies breaking changes + * (major), non-breaking additions (minor), and documentation changes (patch). + * + * @example + * ```typescript + * import { compareSchemas } from '@contractual/differs.json-schema'; + * + * const result = compareSchemas(oldSchema, newSchema, { currentVersion: '1.0.0' }); + * console.log(result.version); // 'major' | 'minor' | 'patch' | 'equal' | null + * console.log(result.newVersion); // { major: 2, minor: 0, patch: 0, version: '2.0.0' } + * ``` + * + * @example + * ```typescript + * import { diffJsonSchema, diffJsonSchemaObjects } from '@contractual/differs.json-schema'; + * + * // Compare files + * const result = await diffJsonSchema('v1/schema.json', 'v2/schema.json'); + * console.log(result.suggestedBump); // 'major' | 'minor' | 'patch' | 'none' + * + * // Compare schema objects directly + * const result2 = diffJsonSchemaObjects(oldSchema, newSchema); + * for (const change of result2.changes) { + * console.log(`[${change.severity}] ${change.message}`); + * } + * ``` + * + * @packageDocumentation + */ + +// ============================================================================= +// Strands-compatible API (primary) +// ============================================================================= + +export { compareSchemas, checkCompatibility } from './compare.js'; + +// ============================================================================= +// Legacy file-based API (backward compatible) +// ============================================================================= + +export { diffJsonSchema, diffJsonSchemaObjects, formatChangeMessage } from './differ.js'; + +// ============================================================================= +// Classification utilities +// ============================================================================= + +export { + classify, + classifyPropertyAdded, + classifyAll, + CLASSIFICATION_SETS, +} from './classifiers.js'; + +// ============================================================================= +// Ref resolution utilities +// ============================================================================= + +export { + resolveRefs, + hasUnresolvedRefs, + extractRefs, + validateRefs, + type ResolveResult, +} from './ref-resolver.js'; + +// ============================================================================= +// Low-level walker +// ============================================================================= + +export { walk } from './walker.js'; + +// ============================================================================= +// Strands API Types +// ============================================================================= + +export type { + CompareResult, + CompareOptions, + StrandsTrace, + StrandsCompatibility, + StrandsVersion, + SemanticVersion, + JsonSchemaDraft, +} from './types.js'; + +// ============================================================================= +// Core Types +// ============================================================================= + +export type { + ResolvedSchema, + JSONSchemaType, + NormalizedType, + ConstraintKey, + ConstraintDirection, + CompositionKeyword, + MetadataKey, + AnnotationKey, + ContentKey, + WalkerContext, +} from './types.js'; + +// ============================================================================= +// Type guards and utilities +// ============================================================================= + +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'; diff --git a/packages/differs.json-schema/src/ref-resolver.ts b/packages/differs.json-schema/src/ref-resolver.ts new file mode 100644 index 0000000..c125601 --- /dev/null +++ b/packages/differs.json-schema/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.json-schema/src/types.ts b/packages/differs.json-schema/src/types.ts new file mode 100644 index 0000000..27834e3 --- /dev/null +++ b/packages/differs.json-schema/src/types.ts @@ -0,0 +1,588 @@ +/** + * Internal types for JSON Schema structural differ + */ + +// ============================================================================ +// Strands API Types +// ============================================================================ + +/** + * 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'; + +// ============================================================================ +// 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 Result Types +// ============================================================================ + +/** + * Severity classification for detected changes + */ +export type ChangeSeverity = 'breaking' | 'non-breaking' | 'patch' | 'unknown'; + +/** + * Suggested semver bump based on detected changes + */ +export type SuggestedBump = 'major' | 'minor' | 'patch' | 'none'; + +/** + * A single change detected between two spec versions + */ +export interface Change { + path: string; + severity: ChangeSeverity; + category: string; + message: string; + oldValue?: unknown; + newValue?: unknown; +} + +/** + * Summary counts of changes by severity + */ +export interface DiffSummary { + breaking: number; + nonBreaking: number; + patch: number; + unknown: number; +} + +/** + * Result of comparing two spec versions + */ +export interface DiffResult { + contract: string; + changes: Change[]; + summary: DiffSummary; + suggestedBump: SuggestedBump; +} + +/** + * All possible structural change types for classification + */ +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 + | '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 + | 'composition-changed' + // Catch-all + | 'unknown-change'; + +/** + * Raw change detected by a differ before severity classification + */ +export interface RawChange { + path: string; + type: ChangeType; + oldValue?: unknown; + newValue?: unknown; +} + +/** + * Mapping from change types to their severity classification + */ +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', + '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', + 'anyof-option-removed': 'non-breaking', + 'oneof-option-removed': 'non-breaking', + 'allof-member-removed': 'non-breaking', + + // Patch-level changes + 'format-added': 'patch', + 'format-removed': 'patch', + 'format-changed': 'patch', + 'description-changed': 'patch', + 'title-changed': 'patch', + 'default-changed': 'patch', + 'examples-changed': 'patch', + 'deprecated-changed': 'patch', + 'read-only-changed': 'patch', + 'write-only-changed': 'patch', + 'content-encoding-changed': 'patch', + 'content-media-type-changed': 'patch', + 'content-schema-changed': 'patch', + + // Unknown (manual review) + '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', + 'unknown-change': 'unknown', +}; diff --git a/packages/differs.json-schema/src/walker.ts b/packages/differs.json-schema/src/walker.ts new file mode 100644 index 0000000..5ae4e4d --- /dev/null +++ b/packages/differs.json-schema/src/walker.ts @@ -0,0 +1,1579 @@ +/** + * 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 } from './types.js'; +import type { 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.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/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..de17c06 --- /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, hasOpenAPIBreakingChanges } from './openapi-diff.js'; + +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/differs/openapi-diff.ts b/packages/governance/differs/openapi-diff.ts new file mode 100644 index 0000000..62c3071 --- /dev/null +++ b/packages/governance/differs/openapi-diff.ts @@ -0,0 +1,170 @@ +/** + * OpenAPI Differ using openapi-diff (native Node.js) + * + * Pure Node.js library for detecting breaking changes in OpenAPI specs. + * No binary dependencies required. + * + * @see https://www.npmjs.com/package/openapi-diff + */ + +import { readFile } from 'node:fs/promises'; +import type { DiffResult, Change, ChangeSeverity } from '@contractual/types'; +import openapiDiff from 'openapi-diff'; + +// Types from openapi-diff +type DiffResultType = 'breaking' | 'non-breaking' | 'unclassified'; + +interface DiffResultSpecEntityDetails { + location: string; + value?: unknown; +} + +interface OpenApiDiffResult { + action: 'add' | 'remove'; + code: string; + entity: string; + sourceSpecEntityDetails: DiffResultSpecEntityDetails[]; + destinationSpecEntityDetails: DiffResultSpecEntityDetails[]; + source: string; + type: T; +} + +interface DiffOutcomeSuccess { + breakingDifferencesFound: false; + nonBreakingDifferences: OpenApiDiffResult<'non-breaking'>[]; + unclassifiedDifferences: OpenApiDiffResult<'unclassified'>[]; +} + +interface DiffOutcomeFailure { + breakingDifferencesFound: true; + breakingDifferences: OpenApiDiffResult<'breaking'>[]; + nonBreakingDifferences: OpenApiDiffResult<'non-breaking'>[]; + unclassifiedDifferences: OpenApiDiffResult<'unclassified'>[]; +} + +type DiffOutcome = DiffOutcomeSuccess | DiffOutcomeFailure; + +/** + * Diff two OpenAPI specifications using openapi-diff + */ +export async function diffOpenAPI(oldSpecPath: string, newSpecPath: string): Promise { + const oldContent = await readSpecFile(oldSpecPath); + const newContent = await readSpecFile(newSpecPath); + + const result = await openapiDiff.diffSpecs({ + sourceSpec: { + content: oldContent, + location: oldSpecPath, + format: detectFormat(oldContent), + }, + destinationSpec: { + content: newContent, + location: newSpecPath, + format: detectFormat(newContent), + }, + }); + + return parseResult(result); +} + +/** + * Diff two OpenAPI spec objects directly (no file I/O) + */ +export async function diffOpenAPIObjects(oldSpec: object, newSpec: object): Promise { + const oldContent = JSON.stringify(oldSpec); + const newContent = JSON.stringify(newSpec); + + const result = await openapiDiff.diffSpecs({ + sourceSpec: { + content: oldContent, + location: 'old-spec.json', + format: detectFormatFromObject(oldSpec), + }, + destinationSpec: { + content: newContent, + location: 'new-spec.json', + format: detectFormatFromObject(newSpec), + }, + }); + + return parseResult(result); +} + +/** + * Quick check if specs have breaking changes + */ +export async function hasOpenAPIBreakingChanges( + oldSpec: object, + newSpec: object +): Promise { + const result = await diffOpenAPIObjects(oldSpec, newSpec); + return result.summary.breaking > 0; +} + +// --- Internal helpers --- + +async function readSpecFile(path: string): Promise { + try { + return await readFile(path, 'utf-8'); + } catch (error) { + throw new Error( + `Failed to read spec "${path}": ${error instanceof Error ? error.message : String(error)}` + ); + } +} + +function detectFormat(content: string): 'openapi3' | 'swagger2' { + if (content.includes('openapi:') || content.includes('"openapi"')) { + return 'openapi3'; + } + return 'swagger2'; +} + +function detectFormatFromObject(spec: object): 'openapi3' | 'swagger2' { + const s = spec as Record; + if (s.openapi && typeof s.openapi === 'string' && s.openapi.startsWith('3')) { + return 'openapi3'; + } + return 'swagger2'; +} + +function parseResult(result: DiffOutcome): DiffResult { + const breaking = result.breakingDifferencesFound ? result.breakingDifferences : []; + const nonBreaking = result.nonBreakingDifferences ?? []; + const unclassified = result.unclassifiedDifferences ?? []; + + const changes: Change[] = [ + ...breaking.map((d) => mapChange(d, 'breaking')), + ...nonBreaking.map((d) => mapChange(d, 'non-breaking')), + ...unclassified.map((d) => mapChange(d, 'unknown')), + ]; + + const summary = { + breaking: breaking.length, + nonBreaking: nonBreaking.length, + patch: 0, + unknown: unclassified.length, + }; + + const suggestedBump = summary.breaking > 0 ? 'major' : summary.nonBreaking > 0 ? 'minor' : 'none'; + + return { contract: '', changes, summary, suggestedBump }; +} + +function mapChange(diff: OpenApiDiffResult, severity: ChangeSeverity): Change { + const srcDetails = diff.sourceSpecEntityDetails?.[0]; + const destDetails = diff.destinationSpecEntityDetails?.[0]; + const path = destDetails?.location || srcDetails?.location || '/'; + + // Generate human-readable message from code + const message = `${diff.action} ${diff.entity.replace(/\./g, ' ')} at ${path}`; + + return { + path, + severity, + category: diff.code, + message, + oldValue: srcDetails?.value, + newValue: destDetails?.value, + }; +} diff --git a/packages/governance/index.ts b/packages/governance/index.ts new file mode 100644 index 0000000..3bd449d --- /dev/null +++ b/packages/governance/index.ts @@ -0,0 +1,110 @@ +/** + * 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 './differs/openapi-diff.js'; +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 differs +export { + diffOpenAPI, + diffOpenAPIObjects, + hasOpenAPIBreakingChanges, +} from './differs/openapi-diff.js'; + +// 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..0adf136 --- /dev/null +++ b/packages/governance/package.json @@ -0,0 +1,67 @@ +{ + "name": "@contractual/governance", + "private": false, + "version": "0.0.0", + "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/types": "workspace:*", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "openapi-diff": "^0.24.1" + }, + "peerDependencies": { + "@redocly/openapi-core": "^1.0.0" + }, + "peerDependenciesMeta": { + "@redocly/openapi-core": { + "optional": true + } + }, + "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..897432d --- /dev/null +++ b/packages/types/config.ts @@ -0,0 +1,147 @@ +/** + * 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; + /** 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; +} + +/** + * 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[]; + /** 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..06e7342 --- /dev/null +++ b/packages/types/governance.ts @@ -0,0 +1,344 @@ +/** + * 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' + // 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', + '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..f8cc768 --- /dev/null +++ b/packages/types/package.json @@ -0,0 +1,51 @@ +{ + "name": "@contractual/types", + "private": false, + "version": "0.0.0", + "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..c581c9c --- /dev/null +++ b/packages/types/versioning.ts @@ -0,0 +1,183 @@ +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) + ); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc4a422..9def316 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,240 +7,150 @@ settings: 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 + 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) - - packages/contract: - 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 - devDependencies: - ora: - specifier: ^8.1.1 - version: 8.1.1 - typescript: - specifier: ~5.7.2 - version: 5.7.3 + version: 3.2.4(@types/node@22.19.11)(yaml@2.8.2) - packages/generators/diff: - 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 + packages/differs.json-schema: 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/governance: dependencies: - '@contractual/generators.diff': + '@contractual/differs.json-schema': 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.json-schema + '@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) + openapi-diff: + specifier: ^0.24.1 + version: 0.24.1(openapi-types@12.1.3) 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 + '@redocly/openapi-core': + specifier: ^1.0.0 + version: 1.34.6 + + packages/types: {} packages: @@ -248,10 +158,6 @@ packages: 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==} @@ -270,354 +176,204 @@ packages: 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==} + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.25.9': - resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.25.9': - resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.26.0': - resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} - engines: {node: '>=6.9.0'} - - '@babel/highlight@7.25.9': - resolution: {integrity: sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.26.5': - resolution: {integrity: sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==} + '@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==} + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} 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==} '@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'} + '@emnapi/core@1.8.1': + resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} - '@dependents/detective-less@4.1.0': - resolution: {integrity: sha512-KrkT6qO5NxqNfy68sBl6CTSoJ4SNDIS5iQArkibhlbGU4LaDukZ3q2HIkh8aUKDio6o4itU4xDR7t82Y2eP1Bg==} - engines: {node: '>=14'} + '@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.24.2': - resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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': @@ -628,9 +384,6 @@ packages: 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==} - '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -640,10 +393,6 @@ packages: 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'} - '@humanwhocodes/object-schema@2.0.3': resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead @@ -652,215 +401,55 @@ packages: resolution: {integrity: sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==} engines: {node: '>=6.9.0'} - '@inquirer/checkbox@4.0.6': - resolution: {integrity: sha512-PgP35JfmGjHU0LSXOyRew0zHuA9N6OJwOlos1fZ20b7j8ISeAdib3L+n0jIxBtX958UeEpte6xhG/gxJ5iUqMw==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - - '@inquirer/confirm@5.1.3': - resolution: {integrity: sha512-fuF9laMmHoOgWapF9h9hv6opA5WvmGFHsTYGCmuFxcghIhEhb3dN0CdQR4BUMqa2H506NCj8cGX4jwMsE4t6dA==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - - '@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'} - peerDependencies: - '@types/node': '>=18' - - '@inquirer/expand@4.0.6': - resolution: {integrity: sha512-TRTfi1mv1GeIZGyi9PQmvAaH65ZlG4/FACq6wSzs7Vvf1z5dnNWsAAXBjWMHt76l+1hUY8teIqJFrWBk5N6gsg==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - - '@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'} - peerDependencies: - '@types/node': '>=18' - - '@inquirer/number@3.0.6': - resolution: {integrity: sha512-xO07lftUHk1rs1gR0KbqB+LJPhkUNkyzV/KhH+937hdkMazmAYHLm1OIrNKpPelppeV1FgWrgFDjdUD8mM+XUg==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - - '@inquirer/password@4.0.6': - resolution: {integrity: sha512-QLF0HmMpHZPPMp10WGXh6F+ZPvzWE7LX6rNoccdktv/Rov0B+0f+eyXkAcgqy5cH9V+WSpbLxu2lo3ysEVK91w==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - - '@inquirer/prompts@7.2.3': - resolution: {integrity: sha512-hzfnm3uOoDySDXfDNOm9usOuYIaQvTgKp/13l1uJoe6UNY+Zpcn2RYt0jXz3yA+yemGHvDOxVzqWl3S5sQq53Q==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - - '@inquirer/rawlist@4.0.6': - resolution: {integrity: sha512-QoE4s1SsIPx27FO4L1b1mUjVcoHm1pWE/oCmm4z/Hl+V1Aw5IXl8FYYzGmfXaBT0l/sWr49XmNSiq7kg3Kd/Lg==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - - '@inquirer/search@3.0.6': - resolution: {integrity: sha512-eFZ2hiAq0bZcFPuFFBmZEtXU1EarHLigE+ENCtpO+37NHCl4+Yokq1P/d09kUblObaikwfo97w+0FtG/EXl5Ng==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - - '@inquirer/select@4.0.6': - resolution: {integrity: sha512-yANzIiNZ8fhMm4NORm+a74+KFYHmf7BZphSOBovIzYPVLquseTGEkU5l2UTnBOf5k0VLmTgPighNDLE9QtbViQ==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - - '@inquirer/type@3.0.2': - resolution: {integrity: sha512-ZhQ4TvhwHZF+lGhQ2O/rsjo80XoZR5/5qhOY3t6FJuX5XBg5Be8YzYTvaUGJnc12AUGI2nr4QSUE4PhKSigx7g==} + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} engines: {node: '>=18'} 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'} + '@isaacs/string-locale-compare@1.1.0': + resolution: {integrity: sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==} '@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} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - 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} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - 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} - - '@jest/transform@29.7.0': - resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/types@29.6.3': - resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jridgewell/gen-mapping@0.3.8': - resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} - engines: {node: '>=6.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/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} '@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 + '@lerna/create@8.2.4': + resolution: {integrity: sha512-A8AlzetnS2WIuhijdAzKUyFpR5YbLLfV3luQ4lzBgIBgRfuoBDZeF+RSZPhra+7A6/zTUlrbhKZIOi/MNhqgvQ==} + engines: {node: '>=18.0.0'} - '@manypkg/find-root@2.2.3': - resolution: {integrity: sha512-jtEZKczWTueJYHjGpxU3KJQ08Gsrf4r6Q2GjmPp/RGk5leeYAA1eyDADSAF+KVCsQ6EwZd/FMcOFCoMhtqdCtQ==} - engines: {node: '>=14.18.0'} + '@napi-rs/wasm-runtime@0.2.12': + resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@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'} + '@napi-rs/wasm-runtime@0.2.4': + resolution: {integrity: sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -878,417 +467,379 @@ packages: 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} + '@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} + '@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} 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/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/run-script@6.0.2': - resolution: {integrity: sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA==} + '@npmcli/query@3.1.0': + resolution: {integrity: sha512-C/iR0tk7KSKGldibYIB9x8GtO/0Bd0I2mhOaDb8ucQL/bQVTmGoeREaFj64Z5+iCBRf3dQfed0CjJL7I8iTkiQ==} 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==} + '@npmcli/redact@2.0.1': + resolution: {integrity: sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==} + engines: {node: ^16.14.0 || >=18.0.0} - '@nrwl/tao@16.10.0': - resolution: {integrity: sha512-QNAanpINbr+Pod6e1xNgFbzK1x5wmZl+jMocgiEFXZ67KHvmbD6MAQQr0MMz+GPhIu7EE4QCTLTyCEMlAG+K5Q==} - hasBin: true + '@npmcli/run-script@8.1.0': + resolution: {integrity: sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==} + engines: {node: ^16.14.0 || >=18.0.0} - '@nx/devkit@16.10.0': - resolution: {integrity: sha512-IvKQqRJFDDiaj33SPfGd3ckNHhHi6ceEoqCbAP4UuMXOPPVOX6H0KVk+9tknkPb48B7jWIw6/AgOeWkBxPRO5w==} + '@nx/devkit@20.8.2': + resolution: {integrity: sha512-rr9p2/tZDQivIpuBUpZaFBK6bZ+b5SAjZk75V4tbCUqGW3+5OPuVvBPm+X+7PYwUF6rwSpewxkjWNeGskfCe+Q==} peerDependencies: - nx: '>= 15 <= 17' + nx: '>= 19 <= 21' - '@nx/nx-darwin-arm64@16.10.0': - resolution: {integrity: sha512-YF+MIpeuwFkyvM5OwgY/rTNRpgVAI/YiR0yTYCZR+X3AAvP775IVlusNgQ3oedTBRUzyRnI4Tknj1WniENFsvQ==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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==} + '@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/auth-token@4.0.0': + resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==} + engines: {node: '>= 18'} - '@octokit/core@4.2.4': - resolution: {integrity: sha512-rYKilwgzQ7/imScn3M9/pFfUf4I1AZEH3KhyJmtPdE2zfaXAn2mFfUy4FbKewzc2We5y/LlKLj36fWJLKC2SIQ==} - engines: {node: '>= 14'} + '@octokit/core@5.2.2': + resolution: {integrity: sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==} + engines: {node: '>= 18'} - '@octokit/endpoint@7.0.6': - resolution: {integrity: sha512-5L4fseVRUsDFGR00tMWD/Trdeeihn999rTMGRMC1G/Ldi1uWlWJzI98H4Iak5DB/RVvQuyMYKqSK/R6mbSOQyg==} - engines: {node: '>= 14'} + '@octokit/endpoint@9.0.6': + resolution: {integrity: sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==} + engines: {node: '>= 18'} - '@octokit/graphql@5.0.6': - resolution: {integrity: sha512-Fxyxdy/JH0MnIB5h+UQ3yCoh1FG4kWXfFKkpWqjZHw/p+Kc8Y44Hu/kCgNBT6nU1shNumEchmW/sUO1JuQnPcw==} - engines: {node: '>= 14'} + '@octokit/graphql@7.1.1': + resolution: {integrity: sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==} + engines: {node: '>= 18'} - '@octokit/openapi-types@18.1.1': - resolution: {integrity: sha512-VRaeH8nCDtF5aXWnjPuEMIYf1itK/s3JYyJcWFJT8X9pSNnBtriDf7wlEWsGuhPLl4QIH4xM8fqTXDwJ3Mu6sw==} + '@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'} + '@octokit/plugin-paginate-rest@11.4.4-cjs.2': + resolution: {integrity: sha512-2dK6z8fhs8lla5PaOTgqfCGBxgAv/le+EhPs27KklPhm1bKObpu6lXzwfUEQ16ajXzqNrKMujsFyo9K2eaoISw==} + engines: {node: '>= 18'} peerDependencies: - '@octokit/core': '>=4' + '@octokit/core': '5' - '@octokit/plugin-request-log@1.0.4': - resolution: {integrity: sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==} + '@octokit/plugin-request-log@4.0.1': + resolution: {integrity: sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==} + engines: {node: '>= 18'} peerDependencies: - '@octokit/core': '>=3' + '@octokit/core': '5' - '@octokit/plugin-rest-endpoint-methods@7.2.3': - resolution: {integrity: sha512-I5Gml6kTAkzVlN7KCtjOM+Ruwe/rQppp0QU372K1GP7kNOYEKe8Xn5BW4sE62JAHdwpq95OQK/qGNyKQMUzVgA==} - engines: {node: '>= 14'} + '@octokit/plugin-rest-endpoint-methods@13.3.2-cjs.1': + resolution: {integrity: sha512-VUjIjOOvF2oELQmiFpWA1aOPdawpyaCUqcEBc/UOUnj3Xp6DJGrJ1+bjUIIDzdHjnFNO6q57ODMfdEZnoBkCwQ==} + engines: {node: '>= 18'} peerDependencies: - '@octokit/core': '>=3' + '@octokit/core': ^5 - '@octokit/request-error@3.0.3': - resolution: {integrity: sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ==} - engines: {node: '>= 14'} + '@octokit/request-error@5.1.1': + resolution: {integrity: sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==} + engines: {node: '>= 18'} - '@octokit/request@6.2.8': - resolution: {integrity: sha512-ow4+pkVQ+6XVVsekSYBzJC0VTVvh/FCTUUgTsboGq+DTeWdyIFV8WSCdo0RIxk6wSkBTHqIK1mYuY7nOBXOchw==} - engines: {node: '>= 14'} + '@octokit/request@8.4.1': + resolution: {integrity: sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==} + engines: {node: '>= 18'} - '@octokit/rest@19.0.11': - resolution: {integrity: sha512-m2a9VhaP5/tUw8FwfnW2ICXlXpLPIqxtg3XcAiGMLj/Xhw3RSBfZ8le/466ktO1Gcjr8oXudGnHhxV1TXJgFxw==} - engines: {node: '>= 14'} + '@octokit/rest@20.1.2': + resolution: {integrity: sha512-GmYiltypkHHtihFwPRxlaorG5R9VAHuk/vbszVoRTGXnAsY60wYLkh/E2XiFmdZmqrisw+9FaazS1i5SbdWYgA==} + engines: {node: '>= 18'} - '@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/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==} + '@pkgr/core@0.2.9': + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} 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 + '@redocly/ajv@8.17.4': + resolution: {integrity: sha512-BieiCML/IgP6x99HZByJSt7fJE4ipgzO7KAFss92Bs+PEI35BhY7vGIysFXLT+YmS7nHtQjZjhOQyPPEf7xGHA==} - '@readme/json-schema-ref-parser@1.2.0': - resolution: {integrity: sha512-Bt3QVovFSua4QmHa65EHUmh2xS0XJ3rgTEUPH998f4OW4VVJke3BuS16f+kM0ZLOGdvIrzrPRqwihuv5BAjtrA==} + '@redocly/config@0.22.2': + resolution: {integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==} - '@readme/openapi-parser@2.6.0': - resolution: {integrity: sha512-pyFJXezWj9WI1O+gdp95CoxfY+i+Uq3kKk4zXIFuRAZi9YnHpHOpjumWWr67wkmRTw19Hskh9spyY0Iyikf3fA==} - engines: {node: '>=18'} - peerDependencies: - openapi-types: '>=7' + '@redocly/openapi-core@1.34.6': + resolution: {integrity: sha512-2+O+riuIUgVSuLl3Lyh5AplWZyVMNuG2F98/o6NrutKJfW4/GTZdPpZlIphS0HGgcOHgmWcCSHj+dWFlZaGSHw==} + engines: {node: '>=18.17.0', npm: '>=9.5.0'} - '@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==} + '@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-loong64-musl@4.57.1': + resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.31.0': - resolution: {integrity: sha512-D7TXT7I/uKEuWiRkEFbed1UUYZwcJDU4vZQdPTcepK7ecPhzKOYk4Er2YR4uHKme4qDeIh6N3XrLfpuM7vzRWQ==} + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.31.0': - resolution: {integrity: sha512-wal2Tc8O5lMBtoePLBYRKj2CImUCJ4UNGJlLwspx7QApYny7K1cUYlzQ/4IGQBLmm+y0RS7dwc3TDO/pmcneTw==} + '@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.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] + + '@rollup/rollup-win32-x64-msvc@4.57.1': + resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} 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} - - '@sigstore/tuf@1.0.3': - resolution: {integrity: sha512-2bRovzs0nJZFlCN3rXirE4gwxCn97JNjMmwpecqlbgV9WcxX7WRuIrgzx/X7Ib7MYRbyUTpBYE0s2x6AmZXnlg==} - engines: {node: ^14.17.0 || ^16.13.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==} + '@sigstore/bundle@2.3.2': + resolution: {integrity: sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==} + engines: {node: ^16.14.0 || >=18.0.0} - '@tufjs/canonical-json@1.0.0': - resolution: {integrity: sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + '@sigstore/core@1.1.0': + resolution: {integrity: sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==} + engines: {node: ^16.14.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} + '@sigstore/protobuf-specs@0.3.3': + resolution: {integrity: sha512-RpacQhBlwpBWd7KEJsRKcBQalbV28fvkxwTOJIqhIuDysMMaJW47V4OqW30iJB9uRpqOSxxEAQFdr8tTattReQ==} + engines: {node: ^18.17.0 || >=20.5.0} - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + '@sigstore/sign@2.3.2': + resolution: {integrity: sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==} + engines: {node: ^16.14.0 || >=18.0.0} - '@types/babel__generator@7.6.8': - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + '@sigstore/tuf@2.3.4': + resolution: {integrity: sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==} + engines: {node: ^16.14.0 || >=18.0.0} - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + '@sigstore/verify@1.2.1': + resolution: {integrity: sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==} + engines: {node: ^16.14.0 || >=18.0.0} - '@types/babel__traverse@7.20.6': - resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - '@types/estree@1.0.6': - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@tufjs/canonical-json@2.0.0': + resolution: {integrity: sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==} + engines: {node: ^16.14.0 || >=18.0.0} - '@types/fs-extra@9.0.13': - resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} + '@tufjs/models@2.0.1': + resolution: {integrity: sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==} + engines: {node: ^16.14.0 || >=18.0.0} - '@types/graceful-fs@4.1.9': - resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} - '@types/http-cache-semantics@4.0.4': - resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} + '@tybys/wasm-util@0.9.0': + resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} - '@types/istanbul-lib-coverage@2.0.6': - resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - '@types/istanbul-lib-report@3.0.3': - resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - '@types/istanbul-reports@3.0.4': - resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -1302,26 +853,14 @@ packages: '@types/minimist@1.2.5': 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==} - - '@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==} @@ -1358,23 +897,10 @@ 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 - '@typescript-eslint/typescript-estree@6.21.0': resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} engines: {node: ^16.0.0 || >=18.0.0} @@ -1390,167 +916,190 @@ packages: 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} - '@typespec/compiler@0.63.0': - resolution: {integrity: sha512-cC3YniwbFghn1fASX3r1IgNjMrwaY4gmzznkHT4f/NxE+HK4XoXWn4EG7287QgVMCaHUykzJCIfW9k7kIleW5A==} - engines: {node: '>=18.0.0'} - hasBin: true + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - '@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-android-arm-eabi@1.11.1': + resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} + cpu: [arm] + os: [android] - '@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-android-arm64@1.11.1': + resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==} + cpu: [arm64] + os: [android] - '@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-darwin-arm64@1.11.1': + resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==} + cpu: [arm64] + os: [darwin] - '@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-darwin-x64@1.11.1': + resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==} + cpu: [x64] + os: [darwin] - '@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-freebsd-x64@1.11.1': + resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==} + cpu: [x64] + os: [freebsd] - '@ungap/structured-clone@1.2.1': - resolution: {integrity: sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==} + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==} + cpu: [arm] + 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-arm-musleabihf@1.11.1': + resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} + cpu: [arm64] + os: [linux] + + '@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] - '@vitest/coverage-v8@3.0.3': - resolution: {integrity: sha512-uVbJ/xhImdNtzPnLyxCZJMTeTIYdgcC2nWtBBBpR1H6z0w8m7D+9/zrDIx2nNxgMg9r+X8+RY2qVpUDeW2b3nw==} + '@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] + + '@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/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/mocker@3.0.3': - resolution: {integrity: sha512-XT2XBc4AN9UdaxJAeIlcSZ0ILi/GzmG5G8XSly4gaiqIvPV3HMTSIDZWJVX6QRJ0PX1m+W8Cy0K9ByXNb/bPIA==} + '@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/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - '@vitest/runner@3.0.3': - resolution: {integrity: sha512-Rgi2kOAk5ZxWZlwPguRJFOBmWs6uvvyAAR9k3MvjRvYrG7xYvKChZcmnnpJCS98311CBDMqsW9MzzRFsj2gX3g==} + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} - '@vitest/snapshot@3.0.3': - resolution: {integrity: sha512-kNRcHlI4txBGztuJfPEJ68VezlPAXLRT1u5UCx219TU3kOG2DplNxhWLwDf2h6emwmTPogzLnGVwP6epDaJN6Q==} + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} - '@vitest/spy@3.0.3': - resolution: {integrity: sha512-7/dgux8ZBbF7lEIKNnEqQlyRaER9nkAL9eTmdKJkDO3hS8p59ATGwKOCUDHcBLKr7h/oi/6hP+7djQk8049T2A==} + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - '@vitest/utils@3.0.3': - resolution: {integrity: sha512-f+s8CvyzPtMFY1eZKkIHGhPsQgYo5qCm6O8KZoim9qm1/jT64qBgGpO5tHscNH6BzRHM+edLNOP+3vO8+8pE/A==} + '@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'} + '@yarnpkg/parsers@3.0.2': + resolution: {integrity: sha512-/HcYgtUSiJiot/XWGLOlGxPYUG65+/31V8oqk17vZLW1xlCoR4PampyePljOxY2n8/3jz9+tIFzICsyGujJZoA==} + engines: {node: '>=18.12.0'} - '@zkochan/js-yaml@0.0.6': - resolution: {integrity: sha512-nzvgl3VfhcELQ8LyVrYOru+UtAy1nrygk2+AGbTm8a5YcO6o8lSjAT+pfg3vJWxIoZKOUhrK6UU7xW/+00kQrg==} + '@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==} 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==} 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==} + 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'} + 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==} + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} peerDependencies: - ajv: ^8.5.0 + ajv: ^8.0.0 peerDependenciesMeta: ajv: optional: true @@ -1558,8 +1107,8 @@ packages: ajv@6.12.6: 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==} @@ -1577,14 +1126,10 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.1.0: - resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -1593,31 +1138,13 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} 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==} - 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==} - argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -1635,16 +1162,16 @@ packages: array-ify@1.0.0: resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} - array-includes@3.1.8: - resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} + 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==} + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} engines: {node: '>= 0.4'} array.prototype.flat@1.3.3: @@ -1675,13 +1202,12 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - ast-module-types@5.0.0: - resolution: {integrity: sha512-JvqziE0Wc0rXQfma0HZC/aY7URXHFuZV84fJRtP8u+lhp0JYCNd5wJzVXP45t0PH0Mej3ynlzvdyITYIu0G4LQ==} - engines: {node: '>=14'} + ast-v8-to-istanbul@0.3.11: + resolution: {integrity: sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==} - astral-regex@2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} @@ -1689,37 +1215,12 @@ packages: asynckit@0.4.0: 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 + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} - 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 + axios@1.13.5: + resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1730,82 +1231,51 @@ packages: before-after-hook@2.2.3: 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@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + 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==} - buffer-from@1.1.2: 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==} - 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 - 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'} + cacache@18.0.4: + resolution: {integrity: sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==} + engines: {node: ^16.14.0 || >=18.0.0} - call-bind-apply-helpers@1.0.1: - resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} + 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==} + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} call-me-maybe@1.0.2: @@ -1823,20 +1293,9 @@ packages: 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'} + 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==} @@ -1850,22 +1309,15 @@ packages: 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==} + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - change-case@5.4.4: - resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} - 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==} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} chownr@2.0.0: @@ -1876,8 +1328,9 @@ packages: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} - cjs-module-lexer@1.4.1: - resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==} + 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==} @@ -1911,10 +1364,6 @@ packages: 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'} - cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} @@ -1930,31 +1379,14 @@ packages: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} - cmd-shim@6.0.1: - resolution: {integrity: sha512-S9iI9y0nKR4hwEQsVWpyxld/6kRfGepGfzff83FcaiEBpmvlbA2nnGe7Cylgrx2f/p1P5S5wpRm9oL8z1PbS3Q==} + cmd-shim@6.0.3: + resolution: {integrity: sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA==} 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==} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -1962,6 +1394,9 @@ packages: 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==} @@ -1985,10 +1420,6 @@ packages: 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'} @@ -1997,8 +1428,8 @@ packages: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} engines: {node: ^12.20.0 || >=14} - commondir@1.0.1: - resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + common-ancestor-path@1.0.1: + resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} compare-func@2.0.0: resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} @@ -2010,9 +1441,6 @@ packages: 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==} - console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} @@ -2047,9 +1475,6 @@ packages: 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'} @@ -2057,11 +1482,8 @@ packages: 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==} + cosmiconfig@9.0.0: + resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} engines: {node: '>=14'} peerDependencies: typescript: '>=4.9.5' @@ -2069,21 +1491,15 @@ packages: 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'} + 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'} @@ -2120,8 +1536,8 @@ packages: supports-color: optional: true - debug@4.4.0: - resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -2137,13 +1553,6 @@ packages: 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==} - dedent@1.5.3: resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} peerDependencies: @@ -2156,24 +1565,12 @@ packages: 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'} - 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'} - 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'} - define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -2190,14 +1587,6 @@ packages: 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 - deprecation@2.3.1: resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} @@ -2205,55 +1594,10 @@ packages: 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} - 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'} - dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -2270,21 +1614,18 @@ packages: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} - dotenv-expand@10.0.0: - resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} + dotenv-expand@11.0.7: + resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} engines: {node: '>=12'} - dotenv@16.3.2: - resolution: {integrity: sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ==} + 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==} - eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -2293,15 +1634,8 @@ packages: 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==} @@ -2312,12 +1646,8 @@ packages: encoding@0.1.13: 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==} @@ -2327,19 +1657,19 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} - envinfo@7.8.1: - resolution: {integrity: sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==} + 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==} + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - es-abstract@1.23.9: - resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} + es-abstract@1.24.1: + resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} engines: {node: '>= 0.4'} es-define-property@1.0.1: @@ -2350,8 +1680,8 @@ packages: 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==} + 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==} @@ -2361,15 +1691,16 @@ packages: 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==} + 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==} + esbuild@0.27.3: + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} engines: {node: '>=18'} hasBin: true @@ -2381,21 +1712,12 @@ packages: 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'} - 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==} + eslint-config-prettier@9.1.2: + resolution: {integrity: sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==} hasBin: true peerDependencies: eslint: '>=7.0.0' @@ -2403,8 +1725,8 @@ packages: 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==} + eslint-import-resolver-typescript@3.10.1: + resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: eslint: '*' @@ -2416,8 +1738,8 @@ packages: eslint-plugin-import-x: optional: true - eslint-module-utils@2.12.0: - resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} + eslint-module-utils@2.12.1: + resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' @@ -2437,8 +1759,8 @@ packages: eslint-import-resolver-webpack: optional: true - eslint-plugin-import@2.31.0: - resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} + eslint-plugin-import@2.32.0: + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' @@ -2447,13 +1769,13 @@ packages: '@typescript-eslint/parser': optional: true - eslint-plugin-prettier@5.2.3: - resolution: {integrity: sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==} + 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': @@ -2484,8 +1806,8 @@ packages: engines: {node: '>=4'} hasBin: true - esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} esrecurse@4.3.0: @@ -2503,45 +1825,26 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - eval-estree-expression@2.0.3: - resolution: {integrity: sha512-6zXgUV+NHvx6PwHxPsIQ8T4cCUgsnhaH6ZyYF1OSKZIrkcAzvSvZgHAbdj72GlNm8eH6c8FI8ywcwqm42Xq1aQ==} - eventemitter3@4.0.7: 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'} - 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==} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} 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'} + exponential-backoff@3.1.3: + resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} extsprintf@1.4.1: resolution: {integrity: sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==} @@ -2563,14 +1866,20 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-uri@3.0.6: - resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - fastq@1.18.0: - resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==} + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} - fb-watchman@2.0.2: - resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + 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==} @@ -2583,11 +1892,6 @@ packages: 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 - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -2612,11 +1916,11 @@ packages: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true - flatted@3.3.2: - resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - follow-redirects@1.15.6: - resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -2624,40 +1928,28 @@ packages: 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'} + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} - foreground-child@3.3.0: - resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} 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==} + 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==} + fs-extra@11.3.3: + resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==} engines: {node: '>=14.14'} - fs-extra@8.1.0: - resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} - engines: {node: '>=6 <7 || >=8'} - fs-minipass@2.1.0: resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} engines: {node: '>= 8'} @@ -2684,38 +1976,22 @@ packages: 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'} + 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==} + get-east-asian-width@1.4.0: + resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} engines: {node: '>=18'} - get-intrinsic@1.2.7: - resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} 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'} - get-pkg-repo@4.2.1: resolution: {integrity: sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==} engines: {node: '>=6.9.0'} @@ -2741,8 +2017,8 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} - get-tsconfig@4.9.0: - resolution: {integrity: sha512-52n24W52sIueosRe0XZ8Ex5Yle+WbhfCKnV/gWXpbVR8FXNTfqdKEKUSypKso66VRHTvvcQxL44UTZbJRlCTnw==} + 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==} @@ -2761,8 +2037,8 @@ packages: git-up@7.0.0: 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==} @@ -2775,30 +2051,19 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me 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 + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me 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'} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me globals@13.24.0: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} @@ -2812,32 +2077,13 @@ packages: 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 - 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==} - 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==} - graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} @@ -2854,10 +2100,6 @@ packages: 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'} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -2887,35 +2129,27 @@ packages: 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'} - 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} + 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'} + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} - http2-wrapper@2.2.1: - resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} - engines: {node: '>=10.19.0'} + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} - https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} + 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==} @@ -2925,29 +2159,22 @@ packages: resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} engines: {node: '>=14.18.0'} - humanize-ms@1.2.1: - resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} - husky@8.0.3: 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'} + 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} - 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} @@ -2956,8 +2183,8 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} import-local@3.1.0: @@ -2965,11 +2192,6 @@ packages: engines: {node: '>=8'} hasBin: true - import-local@3.2.0: - resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} - engines: {node: '>=8'} - hasBin: true - imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -2978,9 +2200,6 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} - infer-owner@1.0.4: - resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} - 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. @@ -2991,31 +2210,26 @@ packages: ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - init-package-json@5.0.0: - resolution: {integrity: sha512-kBhlSheBfYmq3e0L1ii+VKe3zBTLL5lDCDWR+f9dLmEGSB3MqLlMlsolubSsyI88Bg6EA+BIMlomAnQ1SwgQBw==} + ini@4.1.3: + resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} 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' + 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.6: - resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} + 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==} + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} engines: {node: '>= 12'} - ip@2.0.1: - resolution: {integrity: sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==} - is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -3023,20 +2237,20 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-async-function@2.1.0: - resolution: {integrity: sha512-GExz9MtyhlZyXYLxzlJRj5WUCE661zhDa1Yna52CN57AJsymh+DvXXjyveSioqSRdxvUrdKdvqB1b5cVKsNpWQ==} + 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==} + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} engines: {node: '>= 0.4'} - is-bun-module@1.3.0: - resolution: {integrity: sha512-DgXeu5UWI0IsMQundYb5UAOzm6G2eVnarJ0byP6Tm55iZNKceD59LNPA2L4VvsScTtHcw0yEkVwSf7PC+QoLSA==} + is-bun-module@2.0.0: + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} @@ -3079,12 +2293,8 @@ packages: 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==} + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} engines: {node: '>= 0.4'} is-glob@4.0.3: @@ -3106,6 +2316,10 @@ packages: 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'} @@ -3114,10 +2328,6 @@ packages: 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'} - is-obj@2.0.0: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} @@ -3134,21 +2344,10 @@ packages: 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'} - 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==} - is-set@2.0.3: resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} engines: {node: '>= 0.4'} @@ -3157,17 +2356,13 @@ packages: resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} - is-ssh@1.4.0: - resolution: {integrity: sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==} + 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'} - is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -3200,19 +2395,12 @@ packages: 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==} - 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==} + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} engines: {node: '>= 0.4'} is-weakset@2.0.4: @@ -3232,6 +2420,10 @@ packages: isexe@2.0.0: 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'} @@ -3240,187 +2432,57 @@ packages: 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'} - 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'} - 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==} + 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==} + jake@10.9.4: + resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} 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} - 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} - - 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==} + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - jest-worker@29.7.0: - resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} 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 + js-levenshtein@1.1.6: + resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} + engines: {node: '>=0.10.0'} - jju@1.4.0: - resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + 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==} + 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==} 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: @@ -3455,16 +2517,18 @@ packages: json-schema-traverse@1.0.0: 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==} + json-stringify-nice@1.1.4: + resolution: {integrity: sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==} + json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - json-to-ast@2.1.0: - resolution: {integrity: sha512-W9Lq347r8tA1DfMvAGn9QNcgYm4Wm7Yc+k8e6vezpMnRT+NHbtlxgNBXRVjXe9YM6eTn6+p/MKOlV/aABJcSnQ==} - engines: {node: '>= 4'} - json5@1.0.2: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true @@ -3477,11 +2541,8 @@ packages: jsonc-parser@3.2.0: 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==} @@ -3491,6 +2552,12 @@ packages: resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} engines: {node: '>=0.10.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==} @@ -3498,30 +2565,22 @@ packages: 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'} + 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} + libnpmaccess@8.0.6: + resolution: {integrity: sha512-uM8DHDEfYG6G5gVivVl+yQd4pH3uRclHC59lzIbSvy7b5FEwR+mU49Zq1jEyRtRFv7+M99mUW9S0wL/4laT4lw==} + engines: {node: ^16.14.0 || >=18.0.0} - libnpmpublish@7.3.0: - resolution: {integrity: sha512-fHUxw5VJhZCNSls0KLNEG0mCD2PN1i14gH5elGOgiVnU3VgTcRahagYP2LKI1m0tFCJ+XrAm0zVYyF5RCbXzcg==} - engines: {node: ^14.17.0 || ^16.13.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==} @@ -3530,8 +2589,8 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - lines-and-columns@2.0.4: - resolution: {integrity: sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==} + 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: @@ -3573,24 +2632,20 @@ packages: lodash.get@4.4.2: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + deprecated: This package is deprecated. Use the optional chaining (?.) operator instead. lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. lodash.ismatch@4.4.0: resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==} - lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - lodash.merge@4.6.2: 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==} @@ -3604,42 +2659,18 @@ packages: 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} + 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==} - 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==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} magicast@0.3.5: resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} @@ -3652,19 +2683,9 @@ packages: 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==} + 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==} @@ -3717,14 +2738,6 @@ packages: 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} - min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -3758,13 +2771,9 @@ packages: 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} + 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==} @@ -3774,9 +2783,6 @@ packages: resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} engines: {node: '>= 8'} - minipass-json-stream@1.0.2: - resolution: {integrity: sha512-myxeeTm57lYs8pH2nxPzmEEg8DGIgW+9mv6D4JZD2pa81I/OBjeU7PtICXV6c9eRGTA5JMDsuIPUZRCyBMYNhg==} - minipass-pipeline@1.2.4: resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} engines: {node: '>=8'} @@ -3814,16 +2820,6 @@ packages: 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 - ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -3834,10 +2830,6 @@ packages: resolution: {integrity: sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==} engines: {node: '>=10'} - mustache@4.2.0: - resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} - hasBin: true - mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} @@ -3845,15 +2837,16 @@ packages: 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} - - nanoid@3.3.8: - resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + 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==} @@ -3864,9 +2857,6 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - node-addon-api@3.2.1: - resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} - node-fetch@2.6.7: resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} engines: {node: 4.x || >=6.0.0} @@ -3876,31 +2866,17 @@ packages: 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} + 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: @@ -3910,20 +2886,9 @@ packages: 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==} + 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==} @@ -3933,37 +2898,25 @@ packages: 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==} - 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-package-arg@11.0.2: + resolution: {integrity: sha512-IGN0IAwmhDJwy13Wc8k+4PEbTPhpJnMtfR53ZbOyjkvmEcLS4nCwp6mvMWjS5sUjeiW3mpx6cHmuhKEu9XmcQw==} + engines: {node: ^16.14.0 || >=18.0.0} - npm-packlist@7.0.4: - resolution: {integrity: sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==} + 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@8.0.2: - resolution: {integrity: sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==} - 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@14.0.5: - resolution: {integrity: sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA==} - engines: {node: ^14.17.0 || ^16.13.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==} @@ -3973,16 +2926,11 @@ packages: 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==} + 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,8 +2938,8 @@ packages: '@swc/core': optional: true - object-inspect@1.13.3: - resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==} + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} object-keys@1.1.1: @@ -4033,51 +2981,37 @@ packages: 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'} + openapi-diff@0.24.1: + resolution: {integrity: sha512-6DE6XIDm9PunxtZ9BS1hU9WPnupv2SMqjzBpcNyhKwOYUsPJFErjNPZXA66W0nVDYzxGNtvaPF+QbJOhx8eBeQ==} + engines: {node: '>=18'} 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==} - optionator@0.9.4: 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==} + ora@8.2.0: + resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} engines: {node: '>=18'} - os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} - 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'} - p-finally@1.0.0: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} @@ -4145,23 +3079,18 @@ packages: 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} + 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 + 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==} @@ -4171,28 +3100,12 @@ packages: 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==} + 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 - path-exists@3.0.0: resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} engines: {node: '>=4'} @@ -4228,15 +3141,11 @@ packages: 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==} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pathval@2.0.0: - resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} engines: {node: '>= 14.16'} picocolors@1.1.1: @@ -4246,6 +3155,10 @@ packages: 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'} @@ -4267,10 +3180,6 @@ packages: resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} engines: {node: '>=10'} - pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} - engines: {node: '>= 6'} - pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} @@ -4279,45 +3188,28 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} - possible-typed-array-names@1.0.0: - resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} - postcss-values-parser@6.0.2: - resolution: {integrity: sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw==} - engines: {node: '>=10'} - peerDependencies: - postcss: ^8.2.9 + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} - postcss@8.5.1: - resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} 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 - 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==} + prettier-linter-helpers@1.0.1: + resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} 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==} + prettier@3.8.1: + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} engines: {node: '>=14'} hasBin: true @@ -4325,17 +3217,23 @@ packages: 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==} + 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==} + 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==} peerDependencies: @@ -4348,33 +3246,20 @@ packages: 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'} - 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==} + 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==} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - pure-rand@6.1.0: - resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -4382,17 +3267,6 @@ packages: 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 - react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} @@ -4404,11 +3278,6 @@ packages: 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. - read-pkg-up@3.0.0: resolution: {integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==} engines: {node: '>=4'} @@ -4425,10 +3294,6 @@ packages: 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} - read@3.0.1: resolution: {integrity: sha512-SLBrDU/Srs/9EoWhU5GdbAoxG1GzpQHo/6qiGItaoLJ1thmYpcNIM1qISEUvyHBzfGlWIyd6p2DNi1oV1VmAuw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -4448,21 +3313,10 @@ packages: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} - regenerator-runtime@0.14.1: - resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - 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'} - require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -4471,26 +3325,10 @@ packages: 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==} - 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'} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -4506,15 +3344,11 @@ packages: resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} engines: {node: '>=10'} - resolve@1.22.10: - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + 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'} @@ -4531,8 +3365,8 @@ packages: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} rfdc@1.4.1: @@ -4552,8 +3386,8 @@ packages: resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} hasBin: true - rollup@4.31.0: - resolution: {integrity: sha512-9cCE8P4rZLx9+PjoyqHLs31V9a9Vpvfo4qNcs6JCiGWYhw2gijSetFbH6SSy1whnkgcefnUwr8sad7tgqsGvnw==} + rollup@4.57.1: + resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -4561,15 +3395,11 @@ packages: 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'} - run-parallel@1.2.0: 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==} @@ -4592,14 +3422,6 @@ packages: 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==} - semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -4608,13 +3430,8 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.5.3: - resolution: {integrity: sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==} - engines: {node: '>=10'} - hasBin: true - - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} hasBin: true @@ -4637,18 +3454,10 @@ packages: 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'} - 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'} - shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} @@ -4679,26 +3488,14 @@ packages: 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==} + 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'} - slice-ansi@5.0.0: resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} engines: {node: '>=12'} @@ -4707,12 +3504,12 @@ packages: 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-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} - socks@2.8.3: - resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==} + 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: @@ -4723,16 +3520,10 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - source-map-support@0.5.13: - resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} - 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==} - spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} @@ -4742,8 +3533,8 @@ packages: spdx-expression-parse@3.0.1: 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==} @@ -4754,45 +3545,31 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - sprintf-js@1.1.3: - resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - 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'} + stable-hash@0.0.5: + resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} stackback@0.0.2: 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==} + 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'} - string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -4823,16 +3600,12 @@ packages: 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'} - strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} strip-bom@3.0.0: @@ -4855,36 +3628,17 @@ packages: 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'} - 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'} + 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'} - supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -4893,47 +3647,22 @@ packages: resolution: {integrity: sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==} engines: {node: '>=10'} - synckit@0.9.2: - resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==} + synckit@0.11.12: + resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} 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'} - 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==} + 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'} - test-exclude@7.0.1: resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} engines: {node: '>=18'} @@ -4957,29 +3686,30 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - tinypool@1.0.2: - resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + 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==} + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} 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==} + tmp@0.2.5: + resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} engines: {node: '>=14.14'} - tmpl@1.0.5: - resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -4987,6 +3717,10 @@ packages: tr46@0.0.3: 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'} @@ -4994,56 +3728,8 @@ packages: ts-api-utils@1.4.3: 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==} + peerDependencies: + typescript: '>=4.2.0' tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} @@ -5052,30 +3738,17 @@ packages: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} - tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - 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} + 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'} - type-fest@0.18.1: resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} engines: {node: '>=10'} @@ -5104,10 +3777,6 @@ packages: 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'} - typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -5127,11 +3796,6 @@ packages: 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 - typescript@5.7.3: resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} engines: {node: '>=14.17'} @@ -5146,25 +3810,13 @@ packages: 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} + 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} - unique-slug@4.0.0: resolution: {integrity: sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -5172,80 +3824,60 @@ packages: 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'} - universalify@2.0.1: 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' - uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + 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==} + validate-npm-package-name@5.0.1: + resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - validator@13.12.0: - resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==} + validator@13.15.26: + resolution: {integrity: sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==} 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==} + 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 +3905,23 @@ packages: yaml: optional: true - vitest@3.0.3: - resolution: {integrity: sha512-dWdwTFUW9rcnL0LyF2F+IfvNQWB0w9DERySCk8VMG75F8k25C7LsZoh6XfCjPvcR8Nb+Lqi9JKr6vnzH7HSrpQ==} + 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,29 +3933,8 @@ 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==} @@ -5331,10 +3945,6 @@ packages: 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'} - which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -5347,22 +3957,18 @@ packages: resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} engines: {node: '>= 0.4'} - which-typed-array@1.1.18: - resolution: {integrity: sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==} + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} engines: {node: '>= 0.4'} - which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true - which@2.0.2: 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: @@ -5398,10 +4004,6 @@ packages: 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} - write-file-atomic@5.0.1: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -5422,15 +4024,12 @@ packages: 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==} - yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yaml-ast-parser@0.0.43: + resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} + yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} @@ -5439,20 +4038,11 @@ packages: 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'} + 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'} @@ -5469,52 +4059,35 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} - yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - 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==} - 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 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 '@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 + js-yaml: 4.1.1 '@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 + js-yaml: 4.1.1 '@apidevtools/openapi-schemas@2.1.0': {} @@ -5530,322 +4103,134 @@ snapshots: 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)': + '@babel/code-frame@7.29.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 - - '@babel/highlight@7.25.9': - 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 +4238,154 @@ 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 + debug: 4.4.3 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)': - 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 - - '@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 - - '@inquirer/figures@1.0.9': {} - - '@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 - - '@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/password@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 - 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/type@3.0.2(@types/node@22.10.7)': + '@inquirer/external-editor@1.0.3(@types/node@22.19.11)': dependencies: - '@types/node': 22.10.7 + chardet: 2.1.1 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 22.19.11 '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 + 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 - '@istanbuljs/load-nyc-config@1.1.0': - 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 + '@isaacs/string-locale-compare@1.1.0': {} '@istanbuljs/schema@0.1.3': {} - '@jest/console@29.7.0': - 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 - - '@jest/core@29.7.0(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3))': - 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 - - '@jest/environment@29.7.0': - dependencies: - '@jest/fake-timers': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.10.7 - jest-mock: 29.7.0 - - '@jest/expect-utils@29.7.0': - dependencies: - jest-get-type: 29.6.3 - - '@jest/expect@29.7.0': - dependencies: - expect: 29.7.0 - jest-snapshot: 29.7.0 - transitivePeerDependencies: - - supports-color - - '@jest/fake-timers@29.7.0': - 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 - - '@jest/globals@29.7.0': - dependencies: - '@jest/environment': 29.7.0 - '@jest/expect': 29.7.0 - '@jest/types': 29.6.3 - jest-mock: 29.7.0 - transitivePeerDependencies: - - supports-color - - '@jest/reporters@29.7.0': - 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 - '@jest/schemas@29.6.3': dependencies: '@sinclair/typebox': 0.27.8 - '@jest/source-map@29.6.3': - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - callsites: 3.1.0 - graceful-fs: 4.2.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 - - '@jest/test-sequencer@29.7.0': - dependencies: - '@jest/test-result': 29.7.0 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - slash: 3.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 - - '@jest/types@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 - - '@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/trace-mapping@0.3.9': + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.5 '@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 - - '@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': + '@napi-rs/wasm-runtime@0.2.12': 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': - 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 +4397,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 +4475,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': {} + '@pkgr/core@0.2.9': {} - '@pnpm/config.env-replace@1.1.0': {} - - '@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': - dependencies: - '@sinonjs/commons': 3.0.1 - - '@szmarczak/http-timer@5.0.1': + '@sigstore/verify@1.2.1': dependencies: - defer-to-connect: 2.0.1 + '@sigstore/bundle': 2.3.2 + '@sigstore/core': 1.1.0 + '@sigstore/protobuf-specs': 0.3.3 - '@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': {} - - '@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 +4810,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 +4844,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 +4860,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 +4868,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 +4887,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 + + '@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 - '@ungap/structured-clone@1.2.1': {} + '@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 + + '@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.0.3(vitest@3.0.3(@types/node@22.10.7)(yaml@2.7.0))': + '@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: {} - - acorn-jsx@5.3.2(acorn@8.14.0): - dependencies: - acorn: 8.14.0 + abbrev@2.0.0: {} - acorn-walk@8.3.4: + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: - acorn: 8.14.0 + acorn: 8.15.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 +5070,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 +5089,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 +5097,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,55 +5109,58 @@ 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: {} @@ -7145,9 +5171,13 @@ snapshots: 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 +5185,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 +5227,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,97 +5234,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-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 call-me-maybe@1.0.2: {} @@ -7371,23 +5282,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 +5302,17 @@ snapshots: chalk@5.3.0: {} - chalk@5.4.1: {} - - change-case@5.4.4: {} + chalk@5.6.2: {} - char-regex@1.0.2: {} + chardet@2.1.1: {} - 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: {} @@ -7442,8 +5339,6 @@ snapshots: cli-width@3.0.0: {} - cli-width@4.1.0: {} - cliui@7.0.4: dependencies: string-width: 4.2.3 @@ -7464,28 +5359,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: @@ -7503,14 +5388,12 @@ snapshots: 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 +5409,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 +5438,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,8 +5463,6 @@ 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 @@ -7594,63 +5470,40 @@ snapshots: 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 +5517,7 @@ snapshots: dependencies: ms: 2.1.2 - debug@4.4.0: + debug@4.4.3: dependencies: ms: 2.1.3 @@ -7675,28 +5528,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 +5554,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 +5576,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 +5605,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 +5639,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 +5652,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 +5669,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 +5678,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 +5693,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 +5707,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 +5750,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 +5809,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 +5827,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 +5856,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 +5870,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 +5888,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,25 +5920,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 + expect-type@1.3.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 + exponential-backoff@3.1.3: {} extsprintf@1.4.1: {} @@ -8207,15 +5942,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: @@ -8229,21 +5964,6 @@ snapshots: 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 - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -8264,57 +5984,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 +6041,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 +6049,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 +6068,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 +6088,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 +6110,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 +6133,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 +6151,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 +6158,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 +6176,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 +6195,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 +6219,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 +6249,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 +6278,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 +6291,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 +6331,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 +6353,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 +6374,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 +6389,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,15 +6415,15 @@ 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: {} @@ -8834,43 +6434,35 @@ snapshots: dependencies: isobject: 3.0.1 - is-plain-object@5.0.0: {} - is-regex@1.2.1: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 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 + call-bound: 1.0.4 - is-ssh@1.4.0: + is-ssh@1.4.1: dependencies: - protocols: 2.0.1 + protocols: 2.0.2 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 + call-bound: 1.0.4 has-tostringtag: 1.0.2 is-symbol@1.1.1: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 has-symbols: 1.1.0 safe-regex-test: 1.1.0 @@ -8880,28 +6472,24 @@ snapshots: is-typed-array@1.1.15: dependencies: - which-typed-array: 1.1.18 + which-typed-array: 1.1.20 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-unicode-supported@2.1.0: {} is-weakmap@2.0.2: {} - is-weakref@1.1.0: + is-weakref@1.1.1: dependencies: - call-bound: 1.0.3 + call-bound: 1.0.4 is-weakset@2.0.4: dependencies: - call-bound: 1.0.3 - get-intrinsic: 1.2.7 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 is-wsl@2.2.0: dependencies: @@ -8913,53 +6501,27 @@ snapshots: isexe@2.0.0: {} + isexe@3.1.5: {} + 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 + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3 istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color - istanbul-reports@3.1.7: + istanbul-reports@3.2.0: dependencies: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 @@ -8970,94 +6532,11 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jake@10.9.2: + jake@10.9.4: 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 + picocolors: 1.1.1 jest-diff@29.7.0: dependencies: @@ -9066,231 +6545,17 @@ snapshots: jest-get-type: 29.6.3 pretty-format: 29.7.0 - jest-docblock@29.7.0: - dependencies: - detect-newline: 3.1.0 - - jest-each@29.7.0: - 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 - - jest-environment-node@29.7.0: - 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 - jest-get-type@29.6.3: {} - 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 - - jest-leak-detector@29.7.0: - dependencies: - jest-get-type: 29.6.3 - pretty-format: 29.7.0 - - jest-matcher-utils@29.7.0: - dependencies: - chalk: 4.1.2 - jest-diff: 29.7.0 - jest-get-type: 29.6.3 - pretty-format: 29.7.0 - - jest-message-util@29.7.0: - 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 - - jest-mock@29.7.0: - dependencies: - '@jest/types': 29.6.3 - '@types/node': 22.10.7 - jest-util: 29.7.0 - - jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): - optionalDependencies: - jest-resolve: 29.7.0 - - jest-regex-util@29.6.3: {} - - jest-resolve-dependencies@29.7.0: - dependencies: - jest-regex-util: 29.6.3 - jest-snapshot: 29.7.0 - transitivePeerDependencies: - - supports-color - - jest-resolve@29.7.0: - 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 - - jest-runner@29.7.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 - - jest-runtime@29.7.0: - 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 - - 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 - transitivePeerDependencies: - - supports-color - - jest-util@29.7.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 - - jest-validate@29.7.0: - 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 - - jest-watcher@29.7.0: - 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 - - jest-worker@29.7.0: - dependencies: - '@types/node': 22.10.7 - jest-util: 29.7.0 - merge-stream: 2.0.0 - supports-color: 8.1.1 - - 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 + js-levenshtein@1.1.6: {} - jju@1.4.0: {} + 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 +6564,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: {} @@ -9313,12 +6578,12 @@ snapshots: json-schema-diff@0.18.1: dependencies: - ajv: 8.17.1 + ajv: 8.18.0 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 + lodash: 4.17.23 verror: 1.10.1 json-schema-ref-parser@9.0.9: @@ -9331,14 +6596,13 @@ snapshots: 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 +6612,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: @@ -9362,122 +6622,128 @@ snapshots: jsonpointer@5.0.1: {} + just-diff-apply@5.5.0: {} + + just-diff@6.0.2: {} + keyv@4.5.4: dependencies: json-buffer: 3.0.1 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 +6752,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 +6774,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 @@ -9550,13 +6816,9 @@ snapshots: 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 +6827,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 +6835,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 +6863,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 +6928,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 +6962,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 +6978,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 +7005,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 +7015,15 @@ 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: {} + nanoid@3.3.11: {} - nanoid@3.3.8: {} + napi-postinstall@0.3.4: {} natural-compare@1.4.0: {} @@ -9864,51 +7031,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 +7069,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 @@ -9997,73 +7127,64 @@ snapshots: 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 - - 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 +7194,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,14 +7232,14 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 - openapi-diff@0.23.7(openapi-types@12.1.3): + openapi-diff@0.24.1(openapi-types@12.1.3): dependencies: - axios: 1.7.9 + axios: 1.13.5 commander: 8.3.0 - js-yaml: 4.1.0 + js-yaml: 4.1.1 json-schema-diff: 0.18.1 jsonpointer: 5.0.1 - lodash: 4.17.21 + lodash: 4.17.23 openapi3-ts: 2.0.2 swagger-parser: 10.0.3(openapi-types@12.1.3) verror: 1.10.1 @@ -10128,40 +7249,10 @@ snapshots: 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 +7262,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 +7285,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 +7295,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 +7358,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 +7385,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 +7434,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 +7454,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 +7487,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 +7504,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 +7527,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 +7551,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 +7580,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 +7596,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 +7612,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 +7635,7 @@ snapshots: retry@0.12.0: {} - reusify@1.0.4: {} + reusify@1.1.0: {} rfdc@1.4.1: {} @@ -10646,50 +7649,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 +7711,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 +7730,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 +7751,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 +7791,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 +7830,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 +7856,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 +7885,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 +7924,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,32 +7944,16 @@ 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: - 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: + strip-literal@3.1.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): @@ -11029,44 +7962,19 @@ snapshots: transitivePeerDependencies: - openapi-types - synckit@0.9.2: - dependencies: - '@pkgr/core': 0.1.1 - tslib: 2.8.1 - - table@6.9.0: + synckit@0.11.12: 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: - 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 +7985,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 +8006,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 +8030,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 +8051,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): - dependencies: - tslib: 1.14.1 - typescript: 5.7.3 - - tuf-js@1.1.7: + tuf-js@2.2.1: 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 +8065,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 +8079,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 +8097,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 +8106,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 +8121,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,32 +8172,16 @@ 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 + validate-npm-package-name@5.0.1: {} - validator@13.12.0: {} + validator@13.15.26: {} verror@1.10.1: dependencies: @@ -11353,13 +8189,13 @@ snapshots: 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 +8210,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 +8264,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 +8277,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 +8308,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 +8353,9 @@ 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 wrappy@1.0.2: {} @@ -11550,11 +8365,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 +8389,15 @@ snapshots: y18n@5.0.8: {} - yallist@2.1.2: {} - - yallist@3.1.1: {} - yallist@4.0.0: {} + yaml-ast-parser@0.0.43: {} + yaml@1.10.2: {} 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 +8423,12 @@ 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 + validator: 13.15.26 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..f717fb0 --- /dev/null +++ b/tests/e2e/01-init.test.ts @@ -0,0 +1,130 @@ +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 empty object + expect(readJSON(dir, '.contractual/versions.json')).toEqual({}); + + // 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('aborts if already initialized', () => { + const { dir, cleanup } = createTempRepo(); + try { + copyFixture('openapi/petstore-base.yaml', path.join(dir, 'specs/api.openapi.yaml')); + + run('init', dir); + + // Second init should fail + const result = run('init', dir, { expectFail: true }); + expect(result.exitCode).not.toBe(0); + expect(result.stdout + result.stderr).toMatch(/already initialized|exists/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/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/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/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..5e77222 --- /dev/null +++ b/tests/e2e/16-diff-command.test.ts @@ -0,0 +1,631 @@ +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('results'); + expect(Array.isArray(output.results)).toBe(true); + + if (output.results.length > 0) { + const firstResult = output.results[0]; + expect(firstResult).toHaveProperty('contract'); + expect(firstResult).toHaveProperty('changes'); + expect(firstResult).toHaveProperty('summary'); + expect(firstResult).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 results should be breaking + for (const result of output.results) { + for (const change of result.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 result of output.results) { + for (const change of result.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/helpers.ts b/tests/e2e/helpers.ts new file mode 100644 index 0000000..1885a0b --- /dev/null +++ b/tests/e2e/helpers.ts @@ -0,0 +1,305 @@ +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 } +): { 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'], + 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; + }> +): 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 } : {}), + })), + }; + + 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/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'], + }, + }, + }, +});