diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..e2d1277b9f --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,45 @@ +{ + "name": "Release Drafter", + "image": "mcr.microsoft.com/devcontainers/typescript-node:24", + "postCreateCommand": "npm install", + "customizations": { + "codespaces": { + "openFiles": ["README.md"] + }, + "vscode": { + "extensions": [ + "bierner.markdown-preview-github-styles", + "davidanson.vscode-markdownlint", + "biomejs.biome", + "github.copilot", + "github.copilot-chat", + "github.vscode-github-actions", + "github.vscode-pull-request-github", + "me-dutour-mathieu.vscode-github-actions", + "redhat.vscode-yaml", + "yzhang.markdown-all-in-one", + "vivaxy.vscode-conventional-commits" + ], + "settings": { + "editor.defaultFormatter": "biomejs.biome", + "editor.tabSize": 2, + "editor.formatOnSave": true, + "markdown.extension.list.indentationSize": "adaptive", + "markdown.extension.italic.indicator": "_", + "markdown.extension.orderedList.marker": "one" + } + } + }, + "remoteEnv": { + "GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}" + }, + "features": { + "ghcr.io/devcontainers/features/github-cli:1": {} + }, + "mounts": [ + /** + * Mount local ssh keys such as container's git may use ssh commit signing + */ + "source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh,target=/home/node/.ssh,type=bind,consistency=cached" + ] +} diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index dfa9018e01..0000000000 --- a/.dockerignore +++ /dev/null @@ -1,8 +0,0 @@ -design -node_modules -.env -.env.example -coverage -.buildkite -*.pem -.git \ No newline at end of file diff --git a/.env.example b/.env.example deleted file mode 100644 index 9a480def36..0000000000 --- a/.env.example +++ /dev/null @@ -1,9 +0,0 @@ -# The ID of your GitHub App -APP_ID=13956 -WEBHOOK_SECRET=development - -# Use `trace` to get verbose logging or `info` to show less -LOG_LEVEL=debug - -# Go to https://smee.io/new set this to the URL that you are redirected to. -WEBHOOK_PROXY_URL= diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index ed9f9cc128..0000000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -coverage \ No newline at end of file diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 6b74ceb951..0000000000 --- a/.eslintrc +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": ["eslint:recommended", "prettier"], - "plugins": ["prettier"], - "parserOptions": { - "ecmaVersion": 9 - }, - "env": { "node": true, "es6": true }, - "rules": { - "prettier/prettier": "warn", - "no-console": "off", - "no-unused-vars": "warn" - } -} diff --git a/.gitattributes b/.gitattributes index 0cf76493c3..6ba54568bf 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,3 @@ * text=auto eol=lf -*.png binary -*.sketch binary + +dist/** -diff linguist-generated=true diff --git a/.github/.codeql-config.yml b/.github/.codeql-config.yml new file mode 100644 index 0000000000..ffb47fabe6 --- /dev/null +++ b/.github/.codeql-config.yml @@ -0,0 +1,5 @@ +name: JavaScript CodeQL Configuration + +paths-ignore: + - node_modules + - dist diff --git a/.github/.licensed.yml b/.github/.licensed.yml new file mode 100644 index 0000000000..c7f3f066a3 --- /dev/null +++ b/.github/.licensed.yml @@ -0,0 +1,19 @@ +# See: https://github.com/licensee/licensed/blob/main/docs/configuration.md + +sources: + npm: true + +allowed: + - apache-2.0 + - bsd-2-clause + - bsd-3-clause + - isc + - mit + - cc0-1.0 + - 0bsd + - other + +ignored: + npm: + # Used by Rollup.js when building in GitHub Actions + - '@rollup/rollup-linux-x64-gnu' diff --git a/.github/ISSUE_TEMPLATE/01-blank.yml b/.github/ISSUE_TEMPLATE/01-blank.yml new file mode 100644 index 0000000000..769fea7420 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/01-blank.yml @@ -0,0 +1,27 @@ +name: Generic issue template +description: Report a generic issue, bug or feature request that does not fit into any of the other issue templates. +body: + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the bug you encountered. (see [Issues](https://github.com/release-drafter/release-drafter/issues)) + options: + - label: I have searched the existing issues + required: true + + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/release-drafter/release-drafter/blob/master/docs/CODE_OF_CONDUCT.md). + options: + - label: I agree to follow this project's Code of Conduct + required: true + + - type: textarea + id: details + attributes: + label: Add a description + description: Provide a detailed description of the issue, bug, or feature request. + validations: + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/02-previous-published-release-not-found.yml b/.github/ISSUE_TEMPLATE/02-previous-published-release-not-found.yml new file mode 100644 index 0000000000..738f5e71a3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/02-previous-published-release-not-found.yml @@ -0,0 +1,113 @@ +name: Previous published release not found +description: Report cases where Release Drafter could not detect the expected previous published release +body: + - type: markdown + attributes: + value: | + Release Drafter could not find a previous published release and created a draft without a comparison baseline. + + Please fill out the details below so the behavior can be investigated. + + - type: checkboxes + attributes: + label: I have checked my repo's config + description: Please pay attention to the following before submitting this issue + options: + - label: My repo does contain a previously published release release-drafter was supposed to find (or similar) + required: true + - label: My release-drafter config seem to point to that release (e.g. no unexpected tag-prefix, correct commitish, etc.) + required: true + + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the bug you encountered. (see [Issues](https://github.com/release-drafter/release-drafter/issues)) + options: + - label: I have searched the existing issues + required: true + + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/release-drafter/release-drafter/blob/master/docs/CODE_OF_CONDUCT.md). + options: + - label: I agree to follow this project's Code of Conduct + required: true + + - type: input + id: repository + attributes: + label: Repository + description: Which repository is affected? Leave empty if private. + placeholder: owner/repository + validations: + required: false + + - type: input + id: branch_or_channel + attributes: + label: Branch or release channel + description: Which branch, commitish, or release channel were you drafting for? + placeholder: "'commitish: main' without tag-prefix" + validations: + required: true + + - type: input + id: expected_release + attributes: + label: Expected previous published release + description: Which published release did you expect Release Drafter to use as the baseline? Enter the tag_name of that release + placeholder: v1.2.3 + validations: + required: false + + - type: dropdown + id: release_state + attributes: + label: What was in your repo before the (attempted) release-drafter run? + description: Select all that apply. + multiple: true + options: + - One or multiple published GitHub releases + - One or multiple draft releases + - One or multiple prerelease releases + - One or multiple git tags (that are not associated with a GitHub release) + - I am not sure + - I don't want to share this information + validations: + required: true + + - type: textarea + id: config + attributes: + label: Relevant Release Drafter config(s) and workflows. + description: Paste the relevant config(s), including Github Actions workflow(s). (Formatted as YAML) + render: yaml + validations: + required: false + + - type: textarea + id: logs + attributes: + label: Logs + description: Paste action logs. If possible, rerun the workflow with debug logging enabled and paste the relevant logs here. + render: shell + validations: + required: false + + - type: textarea + id: screenshots + attributes: + label: Screenshots + description: Paste relevant screenshots of the releases page. + validations: + required: false + + - type: textarea + id: details + attributes: + label: What did you expect to happen? + description: Describe why you expected a previous published release to be found. + validations: + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/03-rfc.yml b/.github/ISSUE_TEMPLATE/03-rfc.yml new file mode 100644 index 0000000000..419a14e61c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/03-rfc.yml @@ -0,0 +1,61 @@ +name: RFC +description: Propose and discuss a larger change before implementation. +title: "RFC: " +labels: [rfc] +body: + - type: checkboxes + attributes: + label: Is there an existing RFC or issue for this? + description: Please search to see if this proposal has already been discussed. (see [Issues](https://github.com/release-drafter/release-drafter/issues)) + options: + - label: I have searched the existing issues + required: true + + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this RFC, you agree to follow our [Code of Conduct](https://github.com/release-drafter/release-drafter/blob/master/docs/CODE_OF_CONDUCT.md). + options: + - label: I agree to follow this project's Code of Conduct + required: true + + - type: textarea + id: summary + attributes: + label: Summary + description: Describe the proposed change in one or two paragraphs. + validations: + required: true + + - type: textarea + id: motivation + attributes: + label: Motivation + description: What problem does this solve, and who does it help? + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposal + description: Explain the expected user-facing behavior and any important implementation details. + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives and drawbacks + description: What other approaches were considered, and why should this change not be made? + validations: + required: false + + - type: textarea + id: unresolved + attributes: + label: Unresolved questions + description: What still needs a decision before this can be implemented? + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..fb916f1b2d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false # Github's default blank template will be visible by maintainers diff --git a/.github/boomper.yml b/.github/boomper.yml deleted file mode 100644 index 2ea71de604..0000000000 --- a/.github/boomper.yml +++ /dev/null @@ -1,3 +0,0 @@ -updates: - - path: README.md - pattern: 'uses = "release-drafter/release-drafter@(.*)"' diff --git a/.github/release-drafter.autolabeler.tests.yml b/.github/release-drafter.autolabeler.tests.yml new file mode 100644 index 0000000000..aea80e3c43 --- /dev/null +++ b/.github/release-drafter.autolabeler.tests.yml @@ -0,0 +1,26 @@ +######################################### +# Only used for (smoke) tests, # +# not the actual release drafter action # +######################################### + +autolabeler: + - label: 'chore' + files: + - '*.md' + branch: + - '/docs{0,1}\/.+/' + - label: 'bug' + branch: + - '/fix\/.+/' + title: + - '/fix/i' + - label: 'feature' + branch: + - '/src\/.+/' + title: + - '/src/i' + - label: 'enhancement' + branch: + - '/feature\/.+/' + body: + - '/JIRA-[0-9]{1,4}/' diff --git a/.github/release-drafter.tests.yml b/.github/release-drafter.tests.yml new file mode 100644 index 0000000000..7bdd917659 --- /dev/null +++ b/.github/release-drafter.tests.yml @@ -0,0 +1,50 @@ +######################################### +# Only used for (smoke) tests, # +# not the actual release drafter action # +######################################### + +name-template: 'v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' +template: | + # What's Changed + + $CHANGES + + **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION + +categories: + - title: 'Breaking' + semver-increment: 'major' + when: + label: 'type: breaking' + - title: 'New' + semver-increment: 'minor' + when: + label: 'type: feature' + - title: 'Bug Fixes' + semver-increment: 'patch' + when: + label: 'type: bug' + - title: 'Maintenance' + semver-increment: 'patch' + when: + label: 'type: maintenance' + - title: 'Documentation' + semver-increment: 'patch' + when: + label: 'type: docs' + - title: 'Other changes' + - title: 'Dependency Updates' + semver-increment: 'patch' + when: + labels: + - 'type: dependencies' + - 'dependencies' + collapse-after: 5 + - type: 'version-resolver' + semver-increment: 'patch' + when: + label: 'type: security' + - type: 'pre-exclude' + when: + label: 'skip-changelog' diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 25ac6ba493..6abfd81459 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -4,31 +4,46 @@ template: | # What's Changed $CHANGES + + ## New Contributors + + $NEW_CONTRIBUTORS + + **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION + categories: - title: 'Breaking' - label: 'type: breaking' + semver-increment: 'major' + when: + label: 'type: breaking' - title: 'New' - label: 'type: feature' + semver-increment: 'minor' + when: + label: 'type: feature' - title: 'Bug Fixes' - label: 'type: bug' + semver-increment: 'patch' + when: + label: 'type: bug' - title: 'Maintenance' - label: 'type: maintenance' + semver-increment: 'patch' + when: + label: 'type: maintenance' - title: 'Documentation' - label: 'type: docs' + semver-increment: 'patch' + when: + label: 'type: docs' + - title: 'Other changes' - title: 'Dependency Updates' - label: 'type: dependencies' - -version-resolver: - major: - labels: - - 'type: breaking' - minor: - labels: - - 'type: feature' - patch: - labels: - - 'type: bug' - - 'type: maintenance' - - 'type: docs' - - 'type: dependencies' - - 'type: security' + semver-increment: 'patch' + when: + labels: + - 'type: dependencies' + - 'dependencies' + collapse-after: 5 + - type: 'version-resolver' + semver-increment: 'patch' + when: + label: 'type: security' + - type: 'pre-exclude' + when: + label: 'skip-changelog' diff --git a/.github/renovate.json5 b/.github/renovate.json5 new file mode 100644 index 0000000000..ff1f0effd2 --- /dev/null +++ b/.github/renovate.json5 @@ -0,0 +1,72 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended", "helpers:pinGitHubActionDigests"], + "customManagers": [ + { + "customType": "regex", + "fileMatch": ["^biome\\.jsonc$"], + "matchStrings": ["biomejs\\.dev/schemas/(?[^/]+)"], + "depNameTemplate": "@biomejs/biome", + "datasourceTemplate": "npm", + }, + ], + "dependencyDashboard": false, + "semanticCommits": "enabled", + "labels": ["type: dependencies"], + "rangeStrategy": "bump", + "constraints": { + "npm": "^12.0.1", + }, + "commitMessageExtra": "to {{newVersion}}", + "schedule": ["every weekend"], + // Mirror .npmrc min-release-age. + "minimumReleaseAge": "7 days", + "packageRules": [ + { + "matchManagers": ["github-actions"], + "semanticCommitType": "ci", + "labels": ["type: maintenance"], + }, + { + "matchCategories": ["security"], + "labels": ["type: security"], + }, + { + "matchDepTypes": ["devDependencies"], + "labels": ["type: maintenance"], + }, + { + "matchPackageNames": ["@biomejs/biome"], + "groupName": "biome", + }, + { + "matchPackageNames": ["vitest", "@vitest/*"], + "groupName": "vitest", + }, + { + "matchPackageNames": ["@graphql-codegen/*"], + "groupName": "graphql-codegen", + }, + { + "matchPackageNames": ["vite", "vite-*"], + "groupName": "vite", + }, + { + "matchPackageNames": ["@actions/*"], + "groupName": "actions", + }, + { + "matchPackageNames": ["@types/*"], + "groupName": "types", + }, + { + "matchPackageNames": ["@types/node"], + "allowedVersions": "<25", + "groupName": "types", + }, + { + "matchDepTypes": ["engines"], + "enabled": false, + }, + ], +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..8586859bf9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,139 @@ +name: Continuous Integration + +on: + pull_request: + push: + branches: + - master + +permissions: + contents: read + +jobs: + check-pr-labels: + name: 'Check PR Labels' + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + + steps: + - name: "Check for type: or dependencies label" + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + # Fetch at runtime so that re-running a failed workflow + # after adding a label will pick up the newly added label. + LABELS=$(gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/labels" --jq '.[].name') + if echo "$LABELS" | grep -qE '^type: |^dependencies$'; then + echo "PR has a valid label." + else + echo "Error: PR must have a 'dependencies' label or at least one label starting with 'type: '." + exit 1 + fi + + smoke-test-drafter: + name: 'Smoke Test for Drafter' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + + - uses: ./ + with: + config-name: file:release-drafter.tests.yml + dry-run: true + + smoke-test-autolabeler: + name: 'Smoke Test for Autolabeler' + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + + - uses: ./autolabeler + with: + config-name: file:release-drafter.autolabeler.tests.yml + dry-run: true + + tests: + name: Tests & Build-Checks + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 + with: + node-version-file: .node-version + cache: npm + + - run: npm ci + + - name: Run all checks and rebuild artifacts + run: npm run all + + - name: Upload coverage summary + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: coverage-summary + path: coverage/coverage-summary.json + if-no-files-found: error + + - name: Check for clean working directory + run: npm run check:clean + + coverage: + name: Coverage + runs-on: ubuntu-latest + needs: tests + env: + COVERAGE_THRESHOLD: '90' + + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 + with: + node-version-file: .node-version + cache: npm + + - run: npm ci + + # Downloads the artifact named below from this workflow run; `needs: tests` ensures that upload finished first. + - name: Download coverage summary + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: coverage-summary + path: coverage + + - name: Generate coverage summary + id: coverage + run: | + COVERAGE=$(npm run --silent coverage) + echo "pct=$COVERAGE" >> "$GITHUB_OUTPUT" + + - name: Enforce coverage threshold + env: + COVERAGE_PCT: ${{ steps.coverage.outputs.pct }} + COVERAGE_MIN: ${{ env.COVERAGE_THRESHOLD }} + run: | + node --input-type=module <<'EOF' + const pct = Number(process.env.COVERAGE_PCT) + const min = Number(process.env.COVERAGE_MIN) + + if (Number.isNaN(pct)) { + throw new Error(`Invalid coverage percentage: ${process.env.COVERAGE_PCT}`) + } + + if (pct < min) { + console.error( + `Coverage ${pct.toFixed(2)}% is below required ${min.toFixed(0)}%`, + ) + process.exit(1) + } + + console.log( + `Coverage ${pct.toFixed(2)}% meets required ${min.toFixed(0)}%`, + ) + EOF diff --git a/.github/workflows/codegen.yml b/.github/workflows/codegen.yml new file mode 100644 index 0000000000..4cdd9a8498 --- /dev/null +++ b/.github/workflows/codegen.yml @@ -0,0 +1,48 @@ +name: Update GraphQL Codegen + +on: + schedule: + # First day of the month at 00:00 UTC + - cron: '0 0 1 * *' + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + codegen: + name: Regenerate GraphQL types + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 + with: + node-version-file: .node-version + cache: npm + + - run: npm ci + + - name: Run codegen + run: npm run codegen + + - name: Rebuild dist and run validation + run: npm run all + + - name: Create PR if changed + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8 + with: + commit-message: 'chore: update generated GraphQL types' + title: 'chore: update generated GraphQL types' + body: | + Automated monthly update of GitHub GraphQL schema types. + + Generated by `npm run codegen`. + + > [!WARNING] + > Automatically generated PRs won't trigger CI checks. Please ensure the changes are correct before merging. + branch: chore/codegen-update + delete-branch: true + labels: "type: maintenance" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000000..c4500a9fb3 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,41 @@ +name: CodeQL + +on: + pull_request: + push: + branches: + - master + +permissions: + actions: read + checks: write + contents: read + security-events: write + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + steps: + - name: Checkout + id: checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + + - name: Initialize CodeQL + id: initialize + uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # v4 + with: + config-file: .github/.codeql-config.yml + languages: typescript + source-root: src + + - name: Autobuild + id: autobuild + uses: github/codeql-action/autobuild@7188fc363630916deb702c7fdcf4e481b751f97a # v4 + + - name: Perform CodeQL Analysis + id: analyze + uses: github/codeql-action/analyze@7188fc363630916deb702c7fdcf4e481b751f97a # v4 + with: + category: '/language:typescript' diff --git a/.github/workflows/draft.yml b/.github/workflows/draft.yml index cf6d83074a..473cf9d53a 100644 --- a/.github/workflows/draft.yml +++ b/.github/workflows/draft.yml @@ -9,6 +9,5 @@ jobs: update_release_draft: runs-on: ubuntu-latest steps: - - uses: release-drafter/release-drafter@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + - uses: ./ diff --git a/.github/workflows/licensed.yml b/.github/workflows/licensed.yml new file mode 100644 index 0000000000..fd340e0e74 --- /dev/null +++ b/.github/workflows/licensed.yml @@ -0,0 +1,74 @@ +# This workflow checks the statuses of cached dependencies used in this action +# with the help of the Licensed tool. If any licenses are invalid or missing, +# this workflow will fail. See: https://github.com/licensee/licensed + +name: Licensed + +on: + # Uncomment the below lines to run this workflow on pull requests and pushes + # to the default branch. This is useful for checking licenses before merging + # changes into the default branch. + # pull_request: + # branches: + # - main + # push: + # branches: + # - main + workflow_dispatch: + +permissions: + contents: write + +jobs: + licensed: + name: Check Licenses + runs-on: ubuntu-latest + + steps: + - name: Checkout + id: checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 + with: + node-version-file: .node-version + cache: npm + + - run: npm ci + + - name: Setup Ruby + id: setup-ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ruby + + - uses: licensee/setup-licensed@0d52e575b3258417672be0dff2f115d7db8771d8 # v1.3.2 + with: + version: 4.x + github_token: ${{ secrets.GITHUB_TOKEN }} + + # If this is a workflow_dispatch event, update the cached licenses. + - if: ${{ github.event_name == 'workflow_dispatch' }} + name: Update Licenses + id: update-licenses + run: licensed cache -c ./.github/.licensed.yml + + # Then, commit the updated licenses to the repository. + - if: ${{ github.event_name == 'workflow_dispatch' }} + name: Commit Licenses + id: commit-licenses + run: | + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add . + if git diff --staged --quiet; then + echo "No license updates to commit" + else + git commit -m "chore: update cached license files" + git push + fi + + # Last, check the status of the cached licenses. + - name: Check Licenses + id: check-licenses + run: licensed status -c ./.github/.licensed.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 05e1c0815d..0000000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Lint - -on: pull_request - -jobs: - eslint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 1 - - uses: actions/setup-node@v1 - with: - version: 13.x - - run: yarn --frozen-lockfile --ignore-engines --ignore-optional --no-bin-links --non-interactive --silent --ignore-scripts --production=false - env: - HUSKY_SKIP_INSTALL: true - - uses: tinovyatkin/action-eslint@v1 - with: - repo-token: ${{secrets.GITHUB_TOKEN}} - check-name: eslint diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml deleted file mode 100644 index ae80c3214e..0000000000 --- a/.github/workflows/rebase.yml +++ /dev/null @@ -1,24 +0,0 @@ -on: - issue_comment: - types: [created] -name: Automatic Rebase -jobs: - rebase: - name: Rebase - if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase') - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - with: - fetch-depth: 0 - - name: Automatic Rebase - uses: cirrus-actions/rebase@1.2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # https://github.community/t5/GitHub-Actions/Workflow-is-failing-if-no-job-can-be-ran-due-to-condition/m-p/38186#M3250 - always_job: - name: Always run job - runs-on: ubuntu-latest - steps: - - name: Always run - run: echo "This job is used to prevent the workflow to fail when all other jobs are skipped." diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..60cab51091 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,57 @@ +name: Release + +on: + push: + tags: + - v*.*.* + +permissions: + id-token: write + contents: write + +jobs: + build: + if: github.repository == 'release-drafter/release-drafter' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + fetch-depth: 0 + + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 + with: + node-version-file: .node-version + cache: npm + + - run: npm ci + + - name: Run all checks and rebuild artifacts + run: npm run all + + - name: Check for clean working directory + run: npm run check:clean + + - name: Publish + if: false # Publishing is currently disabled, see https://github.com/release-drafter/release-drafter/pull/1475#discussion_r2901869066 + run: npm publish # Publishes to npmjs, using package.json's version + + - name: Parse pushed tag version + id: version + run: | + tag=${GITHUB_REF/refs\/tags\//} + version=${tag#v} + major=${version%%.*} + echo "tag=${tag}" >> $GITHUB_OUTPUT + echo "version=${version}" >> $GITHUB_OUTPUT + echo "major=${major}" >> $GITHUB_OUTPUT + + - name: Publish Release + uses: ./ + with: + version: ${{ steps.version.outputs.version }} + publish: true + + - name: Update major tag v${{ steps.version.outputs.major }} + run: | + git tag v${{ steps.version.outputs.major }} ${{ steps.version.outputs.tag }} -f + git push origin refs/tags/v${{ steps.version.outputs.major }} -f diff --git a/.github/workflows/renovate-build.yml b/.github/workflows/renovate-build.yml new file mode 100644 index 0000000000..3901725598 --- /dev/null +++ b/.github/workflows/renovate-build.yml @@ -0,0 +1,45 @@ +name: Renovate Build + +on: + push: + branches: + - renovate/** + +permissions: + contents: write + +jobs: + build: + name: Rebuild artifacts + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 + with: + node-version-file: .node-version + cache: npm + + - run: npm ci + + - name: Run all checks and rebuild artifacts + run: npm run all + + - name: Commit updated artifacts + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + if [ -n "$(git status --porcelain)" ]; then + git add dist/ \ + schema.json \ + autolabeler/schema.json \ + drafter/schema.json + git commit -m "chore(build): rebuild artifacts" + git push + else + echo "No changes to commit" + fi + + - name: Check for clean working directory + run: npm run check:clean diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000000..b8a1980b8c --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,35 @@ +name: Close Stale Issues + +on: + schedule: + # Run every day at 2:00 AM UTC + - cron: '0 2 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + # Only process issues with the info-needed label (can have other labels too) + any-of-issue-labels: 'info-needed' + # Days before an issue with the info-needed label is considered stale + days-before-issue-stale: 30 + # Days before closing a stale issue (7 day delay after marking as stale) + days-before-issue-close: 7 + # Don't process pull requests + days-before-pr-stale: -1 + days-before-pr-close: -1 + stale-issue-label: 'stale' + close-issue-label: 'closed-by-stale' + # Remove stale label when issue receives comments/updates + remove-issue-stale-when-updated: true + # Customized messages + stale-issue-message: | + This issue has been inactive for 30 days with the `info-needed` label. + Please provide the requested information to keep this issue open. + This issue will be closed in 7 days if no response is provided. + close-issue-message: | + This issue has been closed because it has been inactive for 30 days with the `info-needed` label and no information was provided. + If you have additional context or want to reopen this issue, please comment and we'll be happy to help! diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 151b6b086d..0000000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Tests - -on: [push, pull_request] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - uses: actions/setup-node@v1 - with: - version: 13.x - - name: Test - run: | - yarn install - yarn test diff --git a/.gitignore b/.gitignore index 9faf7d1198..80a8991598 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,102 @@ +# Dependency directory node_modules -npm-debug.log -*.pem -.env + +# Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul coverage -now.json -docker-compose-logs -package-lock.json +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# OS metadata +.DS_Store +Thumbs.db + +# Ignore built ts files +__tests__/runner/* + +# IDE files +.idea +*.code-workspace diff --git a/.licenses/npm/@actions/core.dep.yml b/.licenses/npm/@actions/core.dep.yml new file mode 100644 index 0000000000..33f5fd8dc4 --- /dev/null +++ b/.licenses/npm/@actions/core.dep.yml @@ -0,0 +1,20 @@ +--- +name: "@actions/core" +version: 3.0.0 +type: npm +summary: Actions core lib +homepage: https://github.com/actions/toolkit/tree/main/packages/core +license: mit +licenses: +- sources: LICENSE.md + text: |- + The MIT License (MIT) + + Copyright 2019 GitHub + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +notices: [] diff --git a/.licenses/npm/@actions/exec.dep.yml b/.licenses/npm/@actions/exec.dep.yml new file mode 100644 index 0000000000..003562f8bc --- /dev/null +++ b/.licenses/npm/@actions/exec.dep.yml @@ -0,0 +1,20 @@ +--- +name: "@actions/exec" +version: 3.0.0 +type: npm +summary: Actions exec lib +homepage: https://github.com/actions/toolkit/tree/main/packages/exec +license: mit +licenses: +- sources: LICENSE.md + text: |- + The MIT License (MIT) + + Copyright 2019 GitHub + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +notices: [] diff --git a/.licenses/npm/@actions/github.dep.yml b/.licenses/npm/@actions/github.dep.yml new file mode 100644 index 0000000000..09817e46c8 --- /dev/null +++ b/.licenses/npm/@actions/github.dep.yml @@ -0,0 +1,20 @@ +--- +name: "@actions/github" +version: 9.0.0 +type: npm +summary: Actions github lib +homepage: https://github.com/actions/toolkit/tree/main/packages/github +license: mit +licenses: +- sources: LICENSE.md + text: |- + The MIT License (MIT) + + Copyright 2019 GitHub + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +notices: [] diff --git a/.licenses/npm/@actions/http-client-3.0.2.dep.yml b/.licenses/npm/@actions/http-client-3.0.2.dep.yml new file mode 100644 index 0000000000..dd1f80dd78 --- /dev/null +++ b/.licenses/npm/@actions/http-client-3.0.2.dep.yml @@ -0,0 +1,32 @@ +--- +name: "@actions/http-client" +version: 3.0.2 +type: npm +summary: Actions Http Client +homepage: https://github.com/actions/toolkit/tree/main/packages/http-client +license: other +licenses: +- sources: LICENSE + text: | + Actions Http Client for Node.js + + Copyright (c) GitHub, Inc. + + All rights reserved. + + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +notices: [] diff --git a/.licenses/npm/@actions/http-client-4.0.0.dep.yml b/.licenses/npm/@actions/http-client-4.0.0.dep.yml new file mode 100644 index 0000000000..e1a55cacb3 --- /dev/null +++ b/.licenses/npm/@actions/http-client-4.0.0.dep.yml @@ -0,0 +1,32 @@ +--- +name: "@actions/http-client" +version: 4.0.0 +type: npm +summary: Actions Http Client +homepage: https://github.com/actions/toolkit/tree/main/packages/http-client +license: other +licenses: +- sources: LICENSE + text: | + Actions Http Client for Node.js + + Copyright (c) GitHub, Inc. + + All rights reserved. + + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +notices: [] diff --git a/.licenses/npm/@actions/io.dep.yml b/.licenses/npm/@actions/io.dep.yml new file mode 100644 index 0000000000..dadddb4ed5 --- /dev/null +++ b/.licenses/npm/@actions/io.dep.yml @@ -0,0 +1,20 @@ +--- +name: "@actions/io" +version: 3.0.2 +type: npm +summary: Actions io lib +homepage: https://github.com/actions/toolkit/tree/main/packages/io +license: mit +licenses: +- sources: LICENSE.md + text: |- + The MIT License (MIT) + + Copyright 2019 GitHub + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +notices: [] diff --git a/.licenses/npm/@emnapi/core.dep.yml b/.licenses/npm/@emnapi/core.dep.yml new file mode 100644 index 0000000000..c073dba333 --- /dev/null +++ b/.licenses/npm/@emnapi/core.dep.yml @@ -0,0 +1,32 @@ +--- +name: "@emnapi/core" +version: 1.8.1 +type: npm +summary: emnapi core +homepage: https://github.com/toyobayashi/emnapi#readme +license: mit +licenses: +- sources: LICENSE + text: | + MIT License + + Copyright (c) 2021-present Toyobayashi + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +notices: [] diff --git a/.licenses/npm/@emnapi/runtime.dep.yml b/.licenses/npm/@emnapi/runtime.dep.yml new file mode 100644 index 0000000000..858d224b1f --- /dev/null +++ b/.licenses/npm/@emnapi/runtime.dep.yml @@ -0,0 +1,32 @@ +--- +name: "@emnapi/runtime" +version: 1.8.1 +type: npm +summary: emnapi runtime +homepage: https://github.com/toyobayashi/emnapi#readme +license: mit +licenses: +- sources: LICENSE + text: | + MIT License + + Copyright (c) 2021-present Toyobayashi + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +notices: [] diff --git a/.licenses/npm/@emnapi/wasi-threads.dep.yml b/.licenses/npm/@emnapi/wasi-threads.dep.yml new file mode 100644 index 0000000000..1081f7b0b7 --- /dev/null +++ b/.licenses/npm/@emnapi/wasi-threads.dep.yml @@ -0,0 +1,32 @@ +--- +name: "@emnapi/wasi-threads" +version: 1.1.0 +type: npm +summary: WASI threads proposal implementation in JavaScript +homepage: https://github.com/toyobayashi/emnapi#readme +license: mit +licenses: +- sources: LICENSE + text: | + MIT License + + Copyright (c) 2021-present Toyobayashi + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +notices: [] diff --git a/.licenses/npm/@octokit/auth-token.dep.yml b/.licenses/npm/@octokit/auth-token.dep.yml new file mode 100644 index 0000000000..b3ee373c63 --- /dev/null +++ b/.licenses/npm/@octokit/auth-token.dep.yml @@ -0,0 +1,34 @@ +--- +name: "@octokit/auth-token" +version: 6.0.0 +type: npm +summary: GitHub API token authentication for browsers and Node.js +homepage: +license: mit +licenses: +- sources: LICENSE + text: | + The MIT License + + Copyright (c) 2019 Octokit contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +- sources: README.md + text: "[MIT](LICENSE)" +notices: [] diff --git a/.licenses/npm/@octokit/core.dep.yml b/.licenses/npm/@octokit/core.dep.yml new file mode 100644 index 0000000000..828ea46c9a --- /dev/null +++ b/.licenses/npm/@octokit/core.dep.yml @@ -0,0 +1,34 @@ +--- +name: "@octokit/core" +version: 7.0.6 +type: npm +summary: Extendable client for GitHub's REST & GraphQL APIs +homepage: +license: mit +licenses: +- sources: LICENSE + text: | + The MIT License + + Copyright (c) 2019 Octokit contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +- sources: README.md + text: "[MIT](LICENSE)" +notices: [] diff --git a/.licenses/npm/@octokit/endpoint.dep.yml b/.licenses/npm/@octokit/endpoint.dep.yml new file mode 100644 index 0000000000..c8d9ae517b --- /dev/null +++ b/.licenses/npm/@octokit/endpoint.dep.yml @@ -0,0 +1,34 @@ +--- +name: "@octokit/endpoint" +version: 11.0.2 +type: npm +summary: Turns REST API endpoints into generic request options +homepage: +license: mit +licenses: +- sources: LICENSE + text: | + The MIT License + + Copyright (c) 2018 Octokit contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +- sources: README.md + text: "[MIT](LICENSE)" +notices: [] diff --git a/.licenses/npm/@octokit/graphql.dep.yml b/.licenses/npm/@octokit/graphql.dep.yml new file mode 100644 index 0000000000..56f8003cd7 --- /dev/null +++ b/.licenses/npm/@octokit/graphql.dep.yml @@ -0,0 +1,34 @@ +--- +name: "@octokit/graphql" +version: 9.0.3 +type: npm +summary: GitHub GraphQL API client for browsers and Node +homepage: +license: mit +licenses: +- sources: LICENSE + text: | + The MIT License + + Copyright (c) 2018 Octokit contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +- sources: README.md + text: "[MIT](LICENSE)" +notices: [] diff --git a/.licenses/npm/@octokit/openapi-types.dep.yml b/.licenses/npm/@octokit/openapi-types.dep.yml new file mode 100644 index 0000000000..c119d0d6a6 --- /dev/null +++ b/.licenses/npm/@octokit/openapi-types.dep.yml @@ -0,0 +1,20 @@ +--- +name: "@octokit/openapi-types" +version: 27.0.0 +type: npm +summary: Generated TypeScript definitions based on GitHub's OpenAPI spec for api.github.com +homepage: +license: mit +licenses: +- sources: LICENSE + text: | + Copyright (c) GitHub 2025 - Licensed as MIT. + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +- sources: README.md + text: "[MIT](LICENSE)" +notices: [] diff --git a/.licenses/npm/@octokit/plugin-paginate-rest.dep.yml b/.licenses/npm/@octokit/plugin-paginate-rest.dep.yml new file mode 100644 index 0000000000..c320c2bf86 --- /dev/null +++ b/.licenses/npm/@octokit/plugin-paginate-rest.dep.yml @@ -0,0 +1,20 @@ +--- +name: "@octokit/plugin-paginate-rest" +version: 14.0.0 +type: npm +summary: Octokit plugin to paginate REST API endpoint responses +homepage: +license: mit +licenses: +- sources: LICENSE + text: | + MIT License Copyright (c) 2019 Octokit contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +- sources: README.md + text: "[MIT](LICENSE)" +notices: [] diff --git a/.licenses/npm/@octokit/plugin-rest-endpoint-methods.dep.yml b/.licenses/npm/@octokit/plugin-rest-endpoint-methods.dep.yml new file mode 100644 index 0000000000..72dc79f5fc --- /dev/null +++ b/.licenses/npm/@octokit/plugin-rest-endpoint-methods.dep.yml @@ -0,0 +1,20 @@ +--- +name: "@octokit/plugin-rest-endpoint-methods" +version: 17.0.0 +type: npm +summary: Octokit plugin adding one method for all of api.github.com REST API endpoints +homepage: +license: mit +licenses: +- sources: LICENSE + text: | + MIT License Copyright (c) 2019 Octokit contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +- sources: README.md + text: "[MIT](LICENSE)" +notices: [] diff --git a/.licenses/npm/@octokit/request-error.dep.yml b/.licenses/npm/@octokit/request-error.dep.yml new file mode 100644 index 0000000000..e41fe3b963 --- /dev/null +++ b/.licenses/npm/@octokit/request-error.dep.yml @@ -0,0 +1,34 @@ +--- +name: "@octokit/request-error" +version: 7.1.0 +type: npm +summary: Error class for Octokit request errors +homepage: +license: mit +licenses: +- sources: LICENSE + text: | + The MIT License + + Copyright (c) 2019 Octokit contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +- sources: README.md + text: "[MIT](LICENSE)" +notices: [] diff --git a/.licenses/npm/@octokit/request.dep.yml b/.licenses/npm/@octokit/request.dep.yml new file mode 100644 index 0000000000..3e2e17e896 --- /dev/null +++ b/.licenses/npm/@octokit/request.dep.yml @@ -0,0 +1,35 @@ +--- +name: "@octokit/request" +version: 10.0.7 +type: npm +summary: Send parameterized requests to GitHub's APIs with sensible defaults in browsers + and Node +homepage: +license: mit +licenses: +- sources: LICENSE + text: | + The MIT License + + Copyright (c) 2018 Octokit contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +- sources: README.md + text: "[MIT](LICENSE)" +notices: [] diff --git a/.licenses/npm/@octokit/types.dep.yml b/.licenses/npm/@octokit/types.dep.yml new file mode 100644 index 0000000000..2c471721be --- /dev/null +++ b/.licenses/npm/@octokit/types.dep.yml @@ -0,0 +1,20 @@ +--- +name: "@octokit/types" +version: 16.0.0 +type: npm +summary: Shared TypeScript definitions for Octokit projects +homepage: +license: mit +licenses: +- sources: LICENSE + text: | + MIT License Copyright (c) 2019 Octokit contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +- sources: README.md + text: "[MIT](LICENSE)" +notices: [] diff --git a/.licenses/npm/@tybys/wasm-util.dep.yml b/.licenses/npm/@tybys/wasm-util.dep.yml new file mode 100644 index 0000000000..ff970ef199 --- /dev/null +++ b/.licenses/npm/@tybys/wasm-util.dep.yml @@ -0,0 +1,30 @@ +--- +name: "@tybys/wasm-util" +version: 0.10.1 +type: npm +summary: WASI polyfill for browser and some wasm util +homepage: +license: mit +licenses: +- sources: Auto-generated MIT license text + text: | + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +notices: [] diff --git a/.licenses/npm/before-after-hook.dep.yml b/.licenses/npm/before-after-hook.dep.yml new file mode 100644 index 0000000000..9c3bd2fc5d --- /dev/null +++ b/.licenses/npm/before-after-hook.dep.yml @@ -0,0 +1,214 @@ +--- +name: before-after-hook +version: 4.0.0 +type: npm +summary: asynchronous before/error/after hooks for internal functionality +homepage: +license: apache-2.0 +licenses: +- sources: LICENSE + text: |2 + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 Gregor Martynus and other contributors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +- sources: README.md + text: "[Apache 2.0](LICENSE)" +notices: [] diff --git a/.licenses/npm/compare-versions.dep.yml b/.licenses/npm/compare-versions.dep.yml new file mode 100644 index 0000000000..8ae0d74d9c --- /dev/null +++ b/.licenses/npm/compare-versions.dep.yml @@ -0,0 +1,32 @@ +--- +name: compare-versions +version: 4.1.3 +type: npm +summary: Compare semver version strings to find greater, equal or lesser. +homepage: https://github.com/omichelsen/compare-versions#readme +license: mit +licenses: +- sources: LICENSE + text: | + The MIT License (MIT) + + Copyright (c) 2015-2021 Ole Michelsen + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +notices: [] diff --git a/.licenses/npm/escape-string-regexp.dep.yml b/.licenses/npm/escape-string-regexp.dep.yml new file mode 100644 index 0000000000..cec18fce2a --- /dev/null +++ b/.licenses/npm/escape-string-regexp.dep.yml @@ -0,0 +1,20 @@ +--- +name: escape-string-regexp +version: 5.0.0 +type: npm +summary: Escape RegExp special characters +homepage: +license: mit +licenses: +- sources: license + text: | + MIT License + + Copyright (c) Sindre Sorhus (https://sindresorhus.com) + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +notices: [] diff --git a/.licenses/npm/fast-content-type-parse.dep.yml b/.licenses/npm/fast-content-type-parse.dep.yml new file mode 100644 index 0000000000..e411b38df5 --- /dev/null +++ b/.licenses/npm/fast-content-type-parse.dep.yml @@ -0,0 +1,37 @@ +--- +name: fast-content-type-parse +version: 3.0.0 +type: npm +summary: Parse HTTP Content-Type header according to RFC 7231 +homepage: https://github.com/fastify/fast-content-type-parse#readme +license: other +licenses: +- sources: LICENSE + text: |- + MIT License + + Copyright (c) 2023 The Fastify Team + + The Fastify team members are listed at https://github.com/fastify/fastify#team + and in the README file. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +- sources: README.md + text: Licensed under [MIT](./LICENSE). +notices: [] diff --git a/.licenses/npm/ignore.dep.yml b/.licenses/npm/ignore.dep.yml new file mode 100644 index 0000000000..93abd8b19d --- /dev/null +++ b/.licenses/npm/ignore.dep.yml @@ -0,0 +1,33 @@ +--- +name: ignore +version: 7.0.5 +type: npm +summary: Ignore is a manager and filter for .gitignore rules, the one used by eslint, + gitbook and many others. +homepage: +license: other +licenses: +- sources: LICENSE-MIT + text: |- + Copyright (c) 2013 Kael Zhang , contributors + http://kael.me/ + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +notices: [] diff --git a/.licenses/npm/regex-parser.dep.yml b/.licenses/npm/regex-parser.dep.yml new file mode 100644 index 0000000000..3de5ff6f34 --- /dev/null +++ b/.licenses/npm/regex-parser.dep.yml @@ -0,0 +1,33 @@ +--- +name: regex-parser +version: 2.3.1 +type: npm +summary: A module that parses a string as regular expression and returns the parsed + value. +homepage: https://github.com/IonicaBizau/regex-parser.js +license: mit +licenses: +- sources: LICENSE + text: | + The MIT License (MIT) + + Copyright (c) 2014-25 Ionică Bizău (https://ionicabizau.net) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +notices: [] diff --git a/.licenses/npm/semver.dep.yml b/.licenses/npm/semver.dep.yml new file mode 100644 index 0000000000..77ae9813fe --- /dev/null +++ b/.licenses/npm/semver.dep.yml @@ -0,0 +1,26 @@ +--- +name: semver +version: 7.7.3 +type: npm +summary: The semantic version parser used by npm. +homepage: +license: isc +licenses: +- sources: LICENSE + text: | + The ISC License + + Copyright (c) Isaac Z. Schlueter and Contributors + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +notices: [] diff --git a/.licenses/npm/tslib.dep.yml b/.licenses/npm/tslib.dep.yml new file mode 100644 index 0000000000..4611137caf --- /dev/null +++ b/.licenses/npm/tslib.dep.yml @@ -0,0 +1,23 @@ +--- +name: tslib +version: 2.8.1 +type: npm +summary: Runtime library for TypeScript helper functions +homepage: https://www.typescriptlang.org/ +license: 0bsd +licenses: +- sources: LICENSE.txt + text: |- + Copyright (c) Microsoft Corporation. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. +notices: [] diff --git a/.licenses/npm/tunnel.dep.yml b/.licenses/npm/tunnel.dep.yml new file mode 100644 index 0000000000..9a7111da96 --- /dev/null +++ b/.licenses/npm/tunnel.dep.yml @@ -0,0 +1,35 @@ +--- +name: tunnel +version: 0.0.6 +type: npm +summary: Node HTTP/HTTPS Agents for tunneling proxies +homepage: https://github.com/koichik/node-tunnel/ +license: mit +licenses: +- sources: LICENSE + text: | + The MIT License (MIT) + + Copyright (c) 2012 Koichi Kobayashi + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +- sources: README.md + text: Licensed under the [MIT](https://github.com/koichik/node-tunnel/blob/master/LICENSE) + license. +notices: [] diff --git a/.licenses/npm/undici.dep.yml b/.licenses/npm/undici.dep.yml new file mode 100644 index 0000000000..8876000456 --- /dev/null +++ b/.licenses/npm/undici.dep.yml @@ -0,0 +1,34 @@ +--- +name: undici +version: 6.23.0 +type: npm +summary: An HTTP/1.1 client, written from scratch for Node.js +homepage: https://undici.nodejs.org +license: mit +licenses: +- sources: LICENSE + text: | + MIT License + + Copyright (c) Matteo Collina and Undici contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +- sources: README.md + text: MIT +notices: [] diff --git a/.licenses/npm/universal-user-agent.dep.yml b/.licenses/npm/universal-user-agent.dep.yml new file mode 100644 index 0000000000..b6ff303de8 --- /dev/null +++ b/.licenses/npm/universal-user-agent.dep.yml @@ -0,0 +1,20 @@ +--- +name: universal-user-agent +version: 7.0.3 +type: npm +summary: Get a user agent string across all JavaScript Runtime Environments +homepage: +license: isc +licenses: +- sources: LICENSE.md + text: | + # [ISC License](https://spdx.org/licenses/ISC) + + Copyright (c) 2018-2021, Gregor Martynus (https://github.com/gr2m) + + Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +- sources: README.md + text: "[ISC](LICENSE.md)" +notices: [] diff --git a/.licenses/npm/yaml.dep.yml b/.licenses/npm/yaml.dep.yml new file mode 100644 index 0000000000..21ece6b8d3 --- /dev/null +++ b/.licenses/npm/yaml.dep.yml @@ -0,0 +1,24 @@ +--- +name: yaml +version: 2.8.2 +type: npm +summary: JavaScript parser and stringifier for YAML +homepage: https://eemeli.org/yaml/ +license: isc +licenses: +- sources: LICENSE + text: | + Copyright Eemeli Aro + + Permission to use, copy, modify, and/or distribute this software for any purpose + with or without fee is hereby granted, provided that the above copyright notice + and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. +notices: [] diff --git a/.licenses/npm/zod.dep.yml b/.licenses/npm/zod.dep.yml new file mode 100644 index 0000000000..192aa49918 --- /dev/null +++ b/.licenses/npm/zod.dep.yml @@ -0,0 +1,33 @@ +--- +name: zod +version: 4.3.5 +type: npm +summary: TypeScript-first schema declaration and validation library with static type + inference +homepage: https://zod.dev +license: mit +licenses: +- sources: LICENSE + text: | + MIT License + + Copyright (c) 2025 Colin McDonnell + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +notices: [] diff --git a/.node-version b/.node-version new file mode 100644 index 0000000000..7329493ecf --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +24.18.0 \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000..90b06daee0 --- /dev/null +++ b/.npmrc @@ -0,0 +1,8 @@ +message="chore: release v%s" + +# Reject versions published <7d ago. +min-release-age=7 + +# Block pre/post-install scripts. macOS devs who want native vitest file +# watching: `npm rebuild fsevents --foreground-scripts`. +ignore-scripts=true diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 95988100b5..0000000000 --- a/.prettierignore +++ /dev/null @@ -1 +0,0 @@ -config-with-yaml-exception.yml diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 0beac85569..0000000000 --- a/.prettierrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "semi": false, - "singleQuote": true, - "bracketSpacing": true -} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f69e486aac..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -language: minimal -sudo: required -services: - - docker -script: docker-compose run app npm test diff --git a/.vscode/extensions.json b/.vscode/extensions.json index f239e96c90..699ed73319 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,9 +1,3 @@ { - // See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. - // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp - - // List of extensions which should be recommended for users of this workspace. - "recommendations": ["dbaeumer.vscode-eslint"], - // List of extensions recommended by VS Code that should not be recommended for users of this workspace. - "unwantedRecommendations": [] + "recommendations": ["biomejs.biome"] } diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 70d6af97b6..0000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Jest All", - "program": "${workspaceFolder}/node_modules/.bin/jest", - "args": ["--runInBand"], - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen", - "disableOptimisticBPs": true, - "windows": { - "program": "${workspaceFolder}/node_modules/jest/bin/jest" - } - }, - { - "type": "node", - "request": "launch", - "name": "Jest Current File", - "program": "${workspaceFolder}/node_modules/.bin/jest", - "args": ["${fileBasenameNoExtension}"], - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen", - "disableOptimisticBPs": true, - "windows": { - "program": "${workspaceFolder}/node_modules/jest/bin/jest" - } - }, - { - "type": "node", - "request": "launch", - "name": "Jest Run Selected Test", - "program": "${workspaceFolder}/node_modules/.bin/jest", - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen", - "disableOptimisticBPs": true, - "windows": { - "program": "${workspaceFolder}/node_modules/jest/bin/jest" - }, - "args": ["${fileBasenameNoExtension}", "-t", "${selectedText}"] - } - ] -} diff --git a/.vscode/mcp.json b/.vscode/mcp.json new file mode 100644 index 0000000000..7d7a7c0900 --- /dev/null +++ b/.vscode/mcp.json @@ -0,0 +1,9 @@ +{ + "servers": { + "github": { + "url": "https://api.githubcopilot.com/mcp/", + "type": "http" + } + }, + "inputs": [] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 3e57256ce9..d50419fb47 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,53 @@ { - "eslint.autoFixOnSave": true, + "github.copilot.chat.reviewSelection.instructions": [ + { + "text": "Review the code changes carefully before accepting them." + } + ], + "github.copilot.chat.commitMessageGeneration.instructions": [ + { + "text": "Use conventional commit message format." + } + ], + "github.copilot.chat.pullRequestDescriptionGeneration.instructions": [ + { + "text": "Always include a list of key changes." + } + ], "editor.codeActionsOnSave": { - "source.fixAll.eslint": true - } + "source.fixAll.biome": "explicit" + }, + "biome.enabled": true, + "eslint.enable": false, + "editor.defaultFormatter": "biomejs.biome", + "[jsonc]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "json.validate.enable": false, + "[yaml]": { + "editor.defaultFormatter": "biomejs.biome", + "yaml.format.enable": false // waiting for https://github.com/biomejs/biome/issues/2365 + }, + "[github-actions-workflow]": { + "editor.defaultFormatter": "biomejs.biome", + "yaml.format.enable": false // waiting for https://github.com/biomejs/biome/issues/2365 + }, + "markdown.validate.enabled": true, + "search.exclude": { + "**/node_modules": true, + "**/bower_components": true, + "**/*.code-search": true, + "**/dist": true + }, + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/.DS_Store": true, + "**/Thumbs.db": true + }, + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "js/ts.tsdk.path": "node_modules/typescript/lib" } diff --git a/.vscode/typescript.code-snippets b/.vscode/typescript.code-snippets new file mode 100644 index 0000000000..bb18dad2ed --- /dev/null +++ b/.vscode/typescript.code-snippets @@ -0,0 +1,17 @@ +{ + "release-drafter-e2e-test-suite": { + "prefix": ["release-drafter-e2e-test-suite", "rde2ets"], + "body": [ + "await mockContext('${1:push}')", + "await mockInput('${2:version}', '${3:2.1.1}')", + "mocks.config.mockReturnValue('${4:config-with-input-version-template}')", + "const scope = nockGetAndPostReleases({\n\tfetchedReleases: ['${5:release}']\n})", + "const gqlScope = mockGraphqlQuery({\n\tpayload: '${6:graphql-commits-merge-commit}'\n})", + "await runDrafter()", + "expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(`\n\t\t[\n$7\n\t\t]\n`)", + "expect(scope.isDone()).toBe(true) // should call the mocked endpoints", + "expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints", + "expect(mocks.core.setFailed).not.toHaveBeenCalled()" + ] + } +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..49507ea632 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,127 @@ +# AGENTS.md + +## Repository Overview + +This GitHub Action is written in TypeScript and transpiled to JavaScript. Both +the TypeScript sources and the generated JavaScript code are contained in this +repository. + +- `src/`: TypeScript source code +- `dist/`: generated JavaScript code + +Do not review changes to `dist/` in isolation. It is expected to closely mirror +the code generated from `src/`. CI checks that `dist/` is up to date. + +## Repository Structure + +| Path | Description | +| ---------------- | ------------------------------------------------ | +| `.devcontainer/` | Development container configuration | +| `.github/` | GitHub configuration | +| `.licenses/` | License information | +| `.vscode/` | VS Code configuration | +| `autolabeler/` | Entrypoint for the Autolabeler action | +| `badges/` | Badges for README | +| `coverage/` | Non-versioned coverage output | +| `dist/` | Generated JavaScript code | +| `docs/` | Documentation complementary to `README.md` | +| `drafter/` | Entrypoint for the Drafter action | +| `src/` | TypeScript source code | +| `.node-version` | Node.js version configuration | +| `biome.jsonc` | Biome linter and formatter configuration | +| `action.yml` | Entrypoint to the Drafter action | +| `vite.config.ts` | Vite configuration for bundling and testing | +| `LICENSE` | License file | +| `package.json` | NPM package configuration | +| `README.md` | Project documentation | +| `tsconfig.json` | TypeScript configuration | + +## Setup + +Install dependencies: + +```bash +npm install +``` + +## Required Checks + +Before pushing, run the full pipeline so formatting, linting, type checks, +tests, and generated files are all up to date: + +```bash +npm run all +``` + +CI will fail if generated files are stale. + +## Testing + +Type-check with: + +```bash +npm run tsc:check +``` + +Run unit tests with: + +```bash +npm run test:run +``` + +Do not use `npm run test` for normal verification. It starts the Vite +development server and is intended for interactive sessions. + +Tests live in `src/tests` and use `vitest`. + +## Bundling + +Before pushing changes, ensure `dist/` is regenerated from `src/`: + +```bash +npm run build +``` + +## Coding Guidelines + +- Follow existing TypeScript and JavaScript conventions in the repo. +- Keep changes minimal and consistent with surrounding patterns. +- Update documentation and comments when behavior changes. +- Avoid comments that restate obvious code; explain why when needed. +- Use consistent error-handling patterns. +- Lean on TypeScript for type safety and clarity. +- Keep functions focused and manageable. +- Use descriptive names. +- Use JSDoc for public functions, classes, and non-obvious logic. +- Prefer maintainable, simple solutions over unnecessary complexity. +- Cover both happy paths and edge cases in unit tests when adding or changing + behavior. +- Use `@actions/core` for logging instead of `console`. +- Do not use Zod `refine` or `superRefine` on schemas that are converted to JSON + schema. Keep those schemas JSON-schema-compatible and perform semantic + validation at runtime parsing or config validation time instead. + +## Versioning + +GitHub Actions are versioned using branch and tag names. Keep the version in +`package.json` aligned with codebase changes and follow +[Semantic Versioning](https://semver.org/). + +## Pull Requests + +- Keep changes focused and minimal. +- Ensure formatting, linting, and unit tests pass. +- Ensure `dist/` is up to date with the latest source changes. +- Update `README.md` when functionality or usage changes. + +PR bodies should include: + +- A summary of the changes. +- Any dependency changes. +- Links to relevant issues or discussions. +- Extra reviewer context when helpful. + +## Code Review + +- If a change modifies functionality or usage, confirm that `README.md` was + updated accordingly. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index e0c60da780..0000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,46 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment include: - -- Using welcoming and inclusive language -- Being respectful of differing viewpoints and experiences -- Gracefully accepting constructive criticism -- Focusing on what is best for the community -- Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -- The use of sexualized language or imagery and unwelcome sexual attention or advances -- Trolling, insulting/derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or electronic address, without explicit permission -- Other conduct which could reasonably be considered inappropriate in a professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at t@toolmantim.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] - -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index a6c41b4a77..0000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,35 +0,0 @@ -## Contributing - -[fork]: /fork -[pr]: /compare -[style]: https://standardjs.com/ -[code-of-conduct]: CODE_OF_CONDUCT.md - -Hi there! I'm thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. - -Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms. - -## Submitting a pull request - -1. [Fork][fork] and clone the repository -1. Configure and install the dependencies: `npm install` -1. Make sure the tests pass on your machine: `npm test`, note: these tests also apply the linter, so no need to lint seperately -1. Create a new branch: `git checkout -b my-branch-name` -1. Make your change, add tests, and make sure the tests still pass -1. Push to your fork and [submit a pull request][pr] -1. Give yourself a high five, and wait for your pull request to be reviewed and merged. - -Here are a few things you can do that will increase the likelihood of your pull request being accepted: - -- Follow the [style guide][style] which is using standard. Any linting errors should be shown when running `npm test` -- Write and update tests. -- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. -- Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). - -Work in Progress pull request are also welcome to get feedback early on, or if there is something blocked you. - -## Resources - -- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) -- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) -- [GitHub Help](https://help.github.com) diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 2f17a117c0..0000000000 --- a/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM node:14.4.0-alpine@sha256:796fbe3509bdd36aef0e62508379f84e32a172062b7a37cb3609dee1567893b9 -ARG NODE_ENV=production -ENV NODE_ENV=${NODE_ENV} -LABEL "repository"="https://github.com/toolmantim/release-drafter" -LABEL "homepage"="https://github.com/toolmantim/release-drafter" -LABEL "maintainer"="Tim Lucas" -LABEL "com.github.actions.name"="Release Drafter" -LABEL "com.github.actions.description"="Drafts your next release notes as pull requests are merged into master." -LABEL "com.github.actions.icon"="edit-2" -LABEL "com.github.actions.color"="orange" -WORKDIR /app -COPY . . -RUN yarn --frozen-lockfile -ENTRYPOINT [ "/app/node_modules/.bin/probot", "receive", "/app/index.js" ] \ No newline at end of file diff --git a/README.md b/README.md index a81bb5ad5b..7c4267f2ad 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,59 @@

- Release Drafter Logo + Release Drafter Logo

-

Drafts your next release notes as pull requests are merged into master. Built with Probot.

+

Drafts your next release notes as pull requests are merged into master.

---- +![CI](https://github.com/release-drafter/release-drafter/actions/workflows/ci.yml/badge.svg) +![CodeQL](https://github.com/release-drafter/release-drafter/actions/workflows/codeql-analysis.yml/badge.svg) ## Usage -You can use the [Release Drafter GitHub Action](https://github.com/marketplace/actions/release-drafter) in a [GitHub Actions Workflow](https://help.github.com/en/articles/about-github-actions) by configuring a YAML-based workflow file, e.g. `.github/workflows/release-drafter.yml`, with the following: +You can use the +[Release Drafter GitHub Action](https://github.com/marketplace/actions/release-drafter) +in a +[GitHub Actions Workflow](https://help.github.com/en/actions/about-github-actions) +by configuring a YAML-based workflow file, e.g. +`.github/workflows/release-drafter.yml`, with the following: ```yaml name: Release Drafter on: push: - # branches to consider in the event; optional, defaults to all branches: + - main - master +# Permissions for default token (secrets.GITHUB_TOKEN) +permissions: + contents: write + pull-requests: read + jobs: update_release_draft: runs-on: ubuntu-latest steps: - # Drafts your next Release notes as Pull Requests are merged into "master" - - uses: release-drafter/release-drafter@v5 + - uses: release-drafter/release-drafter@v7 with: - # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml - # config-name: my-config.yml - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + config-name: release-drafter.yml # the default, loads '.github/release-drafter.yml' ``` -If you're unable to use GitHub Actions, you can use the Release Drafter GitHub App. Please refer to the [Release Drafter GitHub App documentation](docs/github-app.md) for more information. - ## Configuration -Once you’ve added Release Drafter to your repository, it must be enabled by adding a `.github/release-drafter.yml` configuration file to each repository. +The action requires a configuration file. Default location is +`.github/release-drafter.yml`, and will be fetched using octokit behind the +scenes. You do not need to checkout your repository beforehand. + +> [!note] +> For advanced scenarios, please read dedicated +> [Configuration Loading](./docs/configuration-loading.md) article. (ex: dynamic +> config, extending other files, fetch from another repo, etc...) ### Example -For example, take the following `.github/release-drafter.yml` file in a repository: +For example, take the following `.github/release-drafter.yml` file in a +repository: ```yml template: | @@ -49,40 +62,44 @@ template: | $CHANGES ``` -As pull requests are merged, a draft release is kept up-to-date listing the changes, ready to publish when you’re ready: +As pull requests are merged, a draft release is kept up-to-date listing the +changes, ready to publish when you’re ready: -Screenshot of generated draft release +Screenshot of generated draft release -The following is a more complicated configuration, which categorises the changes into headings, and automatically suggests the next version number: +The following is a more complicated configuration, which categorises the changes +into headings, and automatically suggests the next version number: ```yml -name-template: 'v$RESOLVED_VERSION 🌈' -tag-template: 'v$RESOLVED_VERSION' +name-template: "v$RESOLVED_VERSION 🌈" +tag-template: "v$RESOLVED_VERSION" categories: - - title: '🚀 Features' - labels: - - 'feature' - - 'enhancement' - - title: '🐛 Bug Fixes' - labels: - - 'fix' - - 'bugfix' - - 'bug' - - title: '🧰 Maintenance' - label: 'chore' -change-template: '- $TITLE @$AUTHOR (#$NUMBER)' + - title: "🚀 Features" + semver-increment: minor + when: + labels: + - "feature" + - "enhancement" + - title: "🐛 Bug Fixes" + when: + labels: + - "fix" + - "bugfix" + - "bug" + - title: "🧰 Maintenance" + when: + label: "chore" + - type: "pre-exclude" + when: + label: "skip-changelog" + - type: "version-resolver" + semver-increment: "major" + when: + label: "major" + - type: "version-resolver" + semver-increment: "patch" +change-template: "- $TITLE (#$NUMBER) $AUTHORS" change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. -version-resolver: - major: - labels: - - 'major' - minor: - labels: - - 'minor' - patch: - labels: - - 'patch' - default: patch template: | ## Changes @@ -91,244 +108,639 @@ template: | ## Configuration Options -You can configure Release Drafter using the following key in your `.github/release-drafter.yml` file: - -| Key | Required | Description | -| ---------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `template` | Required | The template for the body of the draft release. Use [template variables](#template-variables) to insert values. | -| `name-template` | Optional | The template for the name of the draft release. For example: `"v$NEXT_PATCH_VERSION"`. | -| `tag-template` | Optional | The template for the tag of the draft release. For example: `"v$NEXT_PATCH_VERSION"`. | -| `version-template` | Optional | The template to use when calculating the next version number for the release. Useful for projects that don't use semantic versioning. Default: `"$MAJOR.$MINOR.$PATCH"` | -| `change-template` | Optional | The template to use for each merged pull request. Use [change template variables](#change-template-variables) to insert values. Default: `"* $TITLE (#$NUMBER) @$AUTHOR"`. | -| `change-title-escapes` | Optional | Characters to escape in `$TITLE` when inserting into `change-template` so that they are not interpreted as Markdown format characters. Default: `""` | -| `no-changes-template` | Optional | The template to use for when there’s no changes. Default: `"* No changes"`. | -| `references` | Optional | The references to listen for configuration updates to `.github/release-drafter.yml`. Refer to [References](#references) to learn more about this | -| `categories` | Optional | Categorize pull requests using labels. Refer to [Categorize Pull Requests](#categorize-pull-requests) to learn more about this option. | -| `exclude-labels` | Optional | Exclude pull requests using labels. Refer to [Exclude Pull Requests](#exclude-pull-requests) to learn more about this option. | -| `include-labels` | Optional | Include only the specified pull requests using labels. Refer to [Include Pull Requests](#include-pull-requests) to learn more about this option. | -| `replacers` | Optional | Search and replace content in the generated changelog body. Refer to [Replacers](#replacers) to learn more about this option. | -| `sort-by` | Optional | Sort changelog by merged_at or title. Can be one of: `merged_at`, `title`. Default: `merged_at`. | -| `sort-direction` | Optional | Sort changelog in ascending or descending order. Can be one of: `ascending`, `descending`. Default: `descending`. | -| `prerelease` | Optional | Mark the draft release as pre-release. Default `false`. | -| `version-resolver` | Optional | Adjust the `$RESOLVED_VERSION` variable using labels. Refer to [Version Resolver](#version-resolver) to learn more about this | - -Release Drafter also supports [Probot Config](https://github.com/probot/probot-config), if you want to store your configuration files in a central repository. This allows you to share configurations between projects, and create a organization-wide configuration file by creating a repository named `.github` with the file `.github/release-drafter.yml`. +You can configure Release Drafter using the following key in your +`.github/release-drafter.yml` file: + +| Key | Required | Description | +| -------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `template` | Required | The template for the body of the draft release. Use [template variables](#template-variables) to insert values. | +| `header` | Optional | Will be prepended to `template`. Use [template variables](#template-variables) to insert values. | +| `footer` | Optional | Will be appended to `template`. Use [template variables](#template-variables) to insert values. | +| `category-template` | Optional | The template to use for each category. Use [category template variables](#category-template-variables) to insert values. Default: `"## $TITLE"`. | +| `name-template` | Optional | The template for the name of the draft release. For example: `"v$NEXT_PATCH_VERSION"`. | +| `tag-template` | Optional | The template for the tag of the draft release. For example: `"v$NEXT_PATCH_VERSION"`. | +| `tag-prefix` | Optional | A known prefix used to filter release tags. For matching tags, this prefix is stripped before attempting to parse the version. Default: `""` | +| `version-template` | Optional | The template to use when calculating the next version number for the release. Useful for projects that don't use semantic versioning. Default: `"$MAJOR.$MINOR.$PATCH$PRERELEASE"` | +| `change-template` | Optional | The template to use for each merged pull request. Use [change template variables](#change-template-variables) to insert values. Default: `"* $TITLE (#$NUMBER) $AUTHORS"`. | +| `change-author-template` | Optional | The template to use for each author in `$AUTHORS`. Supports `$AUTHOR` for the raw login/name and `$AUTHOR_MENTION` for a GitHub-formatted mention. Default: `"$AUTHOR_MENTION"`. | +| `change-authors-separator` | Optional | The separator between authors in `$AUTHORS`. Default: `", "`. Use `"\n"` with a list-style `change-author-template` for multiline output. | +| `change-authors-final-separator` | Optional | A different separator before the final author in `$AUTHORS`, e.g. `" and "` produces `@octocat, @cchanche and @jetersen`. Defaults to `change-authors-separator`. | +| `change-title-escapes` | Optional | Characters to escape in `$TITLE` when inserting into `change-template` so that they are not interpreted as Markdown format characters. Default: `""` | +| `no-changes-template` | Optional | The template to use for when there’s no changes. Default: `"* No changes"`. | +| `categories` | Optional | Define how changes are filtered, grouped, and versioned. Categories support `type`, `when`, `exclusive`, `collapse-after`, and `semver-increment`. Refer to [Categorize Changes](#categorize-changes). | +| `exclude-contributors` | Optional | Exclude specific usernames from the generated `$CONTRIBUTORS` variable. Refer to [Exclude Contributors](#exclude-contributors) to learn more about this option. | +| `new-contributor-template` | Optional | The template to use for each new contributor in `$NEW_CONTRIBUTORS`. Use [new contributor template variables](#new-contributor-template-variables) to insert values. Default: `"* $AUTHOR_MENTION made their first contribution in #$NUMBER"`. | +| `no-contributors-template` | Optional | The template to use for `$CONTRIBUTORS` when there's no contributors to list. Default: `"No contributors"`. | +| `replacers` | Optional | Search and replace content in the generated changelog body. Refer to [Replacers](#replacers) to learn more about this option. | +| `sort-by` | Optional | Sort changelog by merged_at or title. Can be one of: `merged_at`, `title`. Default: `merged_at`. | +| `sort-direction` | Optional | Sort changelog in ascending or descending order. Can be one of: `ascending`, `descending`. Default: `descending`. | +| `prerelease` | Optional | Whether to draft a prerelease, with changes since another prerelease (if applicable). Default `false`. | +| `prerelease-identifier` | Optional | A string indicating an identifier (alpha, beta, rc, etc), to increment the prerelease version. This automatically enables `prerelease` when both options come from the same config location; explicit action inputs still take precedence. Default `''`. | +| `include-pre-releases` | Optional | When looking for the last published release to scan changes up-to, include pre-releases. Has no effect if using `prerelease: true` (already enabled). Default `false`. | +| `latest` | Optional | Mark the release as latest. Only works for published releases. Can be one of: `true`, `false`, `legacy`. Default `true`. | +| `commitish` | Optional | The release target, i.e. branch, commit SHA, or fully qualified tag or pull request ref it should point to. Tag and pull request refs are resolved to commit SHAs. Pull request merge refs always run in dry-run mode because they point to ephemeral merge commits; set `dry-run: true` explicitly to acknowledge output-only behavior and suppress the warning. Defaults to the branch that release-drafter runs for, e.g. `master` when configured to run on pushes to `master`. | +| `filter-by-range` | Optional | Filter releases that satisfies a semver range. Evaluates the tag name againts node's `semver.satisfies()`. Default : `"*"`. | +| `filter-by-commitish` | Optional | Filter previous releases to consider only those with the target matching `commitish`. Default: `false`. | +| `pull-request-limit` | Optional | Limit for associatedPullRequests API call. Use this when working with long-lived non-default branches. See #1354. Default: `5` | +| `history-limit` | Optional | Size of the pagination window when walking the repo. Can avoid erratic 502s from Github. Default: `15` | ## Template Variables -You can use any of the following variables in your `template`: +You can use any of the following variables in your `template`, `header` and +`footer`: -| Variable | Description | -| --------------- | --------------------------------------------------------------------------------------------------------------------- | -| `$CHANGES` | The markdown list of pull requests that have been merged. | -| `$CONTRIBUTORS` | A comma separated list of contributors to this release (pull request authors, commit authors, and commit committers). | -| `$PREVIOUS_TAG` | The previous releases’s tag. | +| Variable | Description | +| ------------------- | --------------------------------------------------------------------------------------------------------------------- | +| `$CHANGES` | The markdown list of pull requests that have been merged. | +| `$CONTRIBUTORS` | A comma separated list of contributors to this release (pull request authors, commit authors, and commit committers). | +| `$NEW_CONTRIBUTORS` | A Markdown list of pull request authors making their first contribution and the corresponding pull request. | +| `$PREVIOUS_TAG` | The previous releases’s tag. | +| `$REPOSITORY` | Current Repository | +| `$OWNER` | Current Repository Owner | -## Next Version Variables +## Category Template Variables + +You can use any of the following variables in `category-template`: -You can use any of the following variables in your `template`, `name-template` and `tag-template`: +| Variable | Description | +| -------- | ------------------------------------ | +| `$TITLE` | The category title, e.g. `Features`. | -| Variable | Description | -| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `$NEXT_PATCH_VERSION` | The next patch version number. For example, if the last tag or release was `v1.2.3`, the value would be `v1.2.4`. This is the most commonly used value. | -| `$NEXT_MINOR_VERSION` | The next minor version number. For example, if the last tag or release was `v1.2.3`, the value would be `v1.3.0`. | -| `$NEXT_MAJOR_VERSION` | The next major version number. For example, if the last tag or release was `v1.2.3`, the value would be `v2.0.0`. | -| `$RESOLVED_VERSION` | The next resolved version number, based on GitHub labels. Refer to [Version Resolution](#version-resolution) to learn more about this. | +## Next Version Variables + +You can use any of the following variables in your `template`, `header`, +`footer`, `name-template` and `tag-template`: + +| Variable | Description | +| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `$NEXT_PATCH_VERSION` | The next patch version number. For example, if the last tag or release was `v1.2.3`, the value would be `v1.2.4`. This is the most commonly used value. | +| `$NEXT_MINOR_VERSION` | The next minor version number. For example, if the last tag or release was `v1.2.3`, the value would be `v1.3.0`. | +| `$NEXT_MAJOR_VERSION` | The next major version number. For example, if the last tag or release was `v1.2.3`, the value would be `v2.0.0`. | +| `$NEXT_PRERELEASE_VERSION` | The next prerelease suffix. Depends on `prerelease-identifier`. Ex: `v1.2.3-beta.3`. Default : `''` | +| `$RESOLVED_VERSION` | The next resolved version number, based on which categories the matching changes end up in and the `semver-increment` configured on those categories. Refer to [Version Resolver](#version-resolver) to learn more about this. | + +### Next Version Component Helpers + +For each of the `$NEXT_{MAJOR,MINOR,PATCH}_VERSION` variables, additional +component helper variables are available that extract individual version +components: + +| Variable | Description | +| ------------------------------------- | ------------------------------------------------------------------ | +| `$NEXT_MAJOR_VERSION_MAJOR` | Major component of `$NEXT_MAJOR_VERSION`. | +| `$NEXT_MAJOR_VERSION_MINOR` | Minor component of `$NEXT_MAJOR_VERSION`. | +| `$NEXT_MAJOR_VERSION_PATCH` | Patch component of `$NEXT_MAJOR_VERSION`. | +| `$NEXT_MINOR_VERSION_MAJOR` | Major component of `$NEXT_MINOR_VERSION`. | +| `$NEXT_MINOR_VERSION_MINOR` | Minor component of `$NEXT_MINOR_VERSION`. | +| `$NEXT_MINOR_VERSION_PATCH` | Patch component of `$NEXT_MINOR_VERSION`. | +| `$NEXT_PATCH_VERSION_MAJOR` | Major component of `$NEXT_PATCH_VERSION`. | +| `$NEXT_PATCH_VERSION_MINOR` | Minor component of `$NEXT_PATCH_VERSION`. | +| `$NEXT_PATCH_VERSION_PATCH` | Patch component of `$NEXT_PATCH_VERSION`. | +| `$NEXT_PRERELEASE_VERSION_PRERELEASE` | Prerelease segment of `$NEXT_PRERELEASE_VERSION`. Ex : `'-beta.3'` | ## Version Template Variables -You can use any of the following variables in `version-template` to format the `$NEXT_{PATCH,MINOR,MAJOR}_VERSION` variables: +You can use any of the following variables in `version-template` to format the +[Next Version Variables](#next-version-variables): + +| Variable | Description | +| ------------- | --------------------------------------------------------------- | +| `$PATCH` | The patch version number. | +| `$MINOR` | The minor version number. | +| `$MAJOR` | The major version number. | +| `$PRERELEASE` | The prerelease suffix (for example `-rc.0`) or an empty string. | + +You may want to use this when producing non semver output. -| Variable | Description | -| -------- | ------------------------- | -| `$PATCH` | The patch version number. | -| `$MINOR` | The minor version number. | -| `$MAJOR` | The major version number. | +```yaml +version-template: "ver $MAJOR" +``` + +> [!IMPORTANT] +> +> If you want the next release-drafter run to parse your version, stick to +> versions parseable by semver.coerce() (we enbale `loose` mode) +> +> ```ts +> semver.coerce("ver 1", true); // { version: '1.0.0' } +> ``` +> +> If you simply want a verbose title for your releases, use the `name-template` +> config, and leave versions strictly semver-compliant. ## Version Resolver -With the `version-resolver` option version number incrementing can be resolved automatically based on labels of individual pull requests. Append the following to your `.github/release-drafter.yml` file: +Any category with `semver-increment` contributes to `$RESOLVED_VERSION`. +Use `type: version-resolver` categories when you want version resolution rules +that do not also render a changelog section. + +Before version resolution runs, any `pre-include` and `pre-exclude` categories +filter the candidate pull requests. After that: + +- `type: changelog` categories contribute only for pull requests that end up + assigned to that changelog category +- `type: version-resolver` categories contribute from their own matches without + rendering a changelog section +- the highest matching increment wins across both category types + +Category order matters when `exclusive: true` is used. Exclusivity is evaluated +independently for changelog categories and version-resolver categories. ```yml -version-resolver: - major: - labels: - - 'major' - minor: - labels: - - 'minor' - patch: - labels: - - 'patch' - default: patch +categories: + - type: "version-resolver" + semver-increment: "major" + when: + label: "major" + - type: "version-resolver" + semver-increment: "minor" + when: + label: "minor" + - type: "version-resolver" + semver-increment: "patch" + when: + label: "patch" + - type: "version-resolver" + semver-increment: "patch" ``` -The above config controls the output of the `$RESOLVED_VERSION` variable. +The example above: -If a pull requests is found with the label `major`/`minor`/`patch`, the corresponding version key will be incremented from a semantic version. The maximum out of major, minor and patch found in any of the pull requests will be used to increment the version number. If no pull requests are found with the assigned labels, the `default` will be assigned. +- uses matching categories to resolve `major`, `minor`, or `patch` +- uses the category with no `when` as the fallback when nothing else matches +- picks the highest semver increment across matching categories -## Change Template Variables +## New Contributor Template Variables -You can use any of the following variables in `change-template`: +You can use any of the following variables in `new-contributor-template`: -| Variable | Description | -| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `$NUMBER` | The number of the pull request, e.g. `42`. | -| `$TITLE` | The title of the pull request, e.g. `Add alien technology`. Any characters excluding @ and # matching `change-title-escapes` will be prepended with a backslash so that they will appear verbatim instead of being interpreted as markdown format characters. @s and #s if present in `change-title-escapes` will be appended with an HTML comment so that they don't become mentions. | -| `$AUTHOR` | The pull request author’s username, e.g. `gracehopper`. | -| `$BODY` | The body of the pull request e.g. `Fixed spelling mistake`. | -| `$URL` | The URL of the pull request e.g. `https://github.com/octocat/repo/pull/42`. | +| Variable | Description | +| ----------------- | --------------------------------------------------------------------------- | +| `$AUTHOR` | The new contributor’s username, e.g. `gracehopper`. | +| `$AUTHOR_MENTION` | The new contributor’s GitHub mention, e.g. `@gracehopper`. | +| `$AUTHOR_URL` | The URL of the new contributor’s GitHub profile, e.g. `https://github.com/gracehopper`. | +| `$NUMBER` | The number of the contributor’s first pull request, e.g. `42`. | +| `$URL` | The URL of the contributor’s first pull request, e.g. `https://github.com/octocat/repo/pull/42`. | -## References +## Change Template Variables -**Note**: This is only revelant for GitHub app users as `references` is ignored when running as GitHub action due to GitHub workflows more powerful [`on` conditions](https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#on) +You can use any of the following variables in `change-template`: -References takes an list and accepts strings and regex. -If none are specified, we default to the repository’s default branch usually master. +| Variable | Description | +| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `$NUMBER` | The number of the pull request, e.g. `42`. | +| `$CATEGORY` | The title of the category that matched the pull request, preserving its configured case. Empty for uncategorized pull requests. | +| `$TITLE` | The title of the pull request, e.g. `Add alien technology`. Any characters excluding @ and # matching `change-title-escapes` will be prepended with a backslash so that they will appear verbatim instead of being interpreted as markdown format characters. @s and #s if present in `change-title-escapes` will be appended with an HTML comment so that they don't become mentions. | +| `$AUTHOR` | The pull request author’s username, e.g. `gracehopper`. | +| `$AUTHOR_URL` | The pull request author’s GitHub profile URL, e.g. `https://github.com/gracehopper`. | +| `$AUTHORS` | The pull request author and its associated commit authors rendered with `change-author-template` and joined with `change-authors-separator`, with the pull request author first. | +| `$BODY` | The body of the pull request e.g. `Fixed spelling mistake`. | +| `$URL` | The URL of the pull request e.g. `https://github.com/octocat/repo/pull/42`. | +| `$BASE_REF_NAME` | The base name of of the base Ref associated with the pull request e.g. `master`. | +| `$HEAD_REF_NAME` | The head name of the head Ref associated with the pull request e.g. `my-bug-fix`. | + +For a multiline author list, render each author with `$AUTHOR` and join them +with a newline: ```yaml -references: - - master - - v.+ +categories: + - title: bug + when: + label: bug + - title: todo +category-template: "" +change-template: |- + - type: $CATEGORY + message: |- + $TITLE + pull: $NUMBER + authors: + $AUTHORS +change-author-template: "- $AUTHOR" +change-authors-separator: "\n " ``` -Currently matching against any `ref/heads/` and `ref/tags/` references behind the scene +Use `$AUTHOR_MENTION` instead of `$AUTHOR` in `change-author-template` when +GitHub mentions are desired. GitHub App bots are rendered as linked mentions, +for example `[@dependabot[bot]](https://github.com/apps/dependabot)`. +`$CATEGORY` preserves `categories[].title`; configure the title with the casing +required by the output. -## Categorize Pull Requests +## Categorize Changes -With the `categories` option you can categorize pull requests in release notes using labels. For example, append the following to your `.github/release-drafter.yml` file: +With the `categories` option you can describe the full change classification +pipeline: + +- `type: changelog` groups matching changes in the rendered release notes +- `type: pre-include` keeps only matching changes for later processing +- `type: pre-exclude` removes matching changes before changelog generation +- `type: version-resolver` affects `$RESOLVED_VERSION` without rendering a + changelog section + +`pre-include` always runs before `pre-exclude`, and both category types affect +both changelog generation and version resolution. + +Categories are evaluated in the order they are defined. By default, a pull +request can match multiple categories of the same type. Setting `exclusive: +true` on a `changelog` or `version-resolver` category stops later categories of +that same type from also matching the same pull request. + +Each category supports the following keys: + +| Key | Applies to | Description | +| ------------------ | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `type` | All categories | Category behavior. Defaults to `changelog`. | +| `title` | `changelog` | Required for changelog categories because `category-template` renders it. Ignored for `pre-include`, `pre-exclude`, and `version-resolver`. | +| `when` | All categories | Match conditions. Omit it or use an empty array to match all changes. | +| `exclusive` | `changelog`, `version-resolver` | Prevents later categories of the same type from also matching the same pull request. Defaults to `false`. | +| `collapse-after` | `changelog` | Collapses long changelog sections into `
`. `0` always collapses, `-1` disables collapsing. Defaults to `-1`. | +| `semver-increment` | `changelog`, `version-resolver` | Version increment contributed by matching changes. Can be `major`, `minor`, or `patch`. Defaults to `patch`. Ignored for `pre-include` and `pre-exclude`. | + +Each category can define a `when` condition as either: + +- a single condition object +- an array of condition objects, where matching any one condition is enough + +Within one condition, conventional commit, label, and path predicates are +combined with AND logic. + +The condition keys are: + +| Key | Description | +| ------------- | ----------------------------------------------------------------------- | +| `conventional` | Conventional commit predicates to compare against the change title or message. | +| `label` | Shorthand for one `labels` entry. | +| `labels` | Label predicates to compare against the pull request labels. | +| `labels-mode` | How the configured labels are matched. Defaults to `any`. | +| `path` | Shorthand for one `paths` entry. | +| `paths` | Glob patterns to compare against the files changed by the change. | +| `paths-mode` | How the configured paths are matched. Defaults to `any`. | ```yml categories: - - title: '🚀 Features' - label: 'feature' - - title: '🐛 Bug Fixes' - labels: - - 'fix' - - 'bugfix' - - 'bug' + - title: "🚀 Features" + semver-increment: "minor" + when: + - conventional: + type: "feat" + - labels: + - "feature" + - "enhancement" + - title: "🐛 Bug Fixes" + when: + - labels: + - "bug" + - "fix" + - labels: + - "regression" + paths: + - "src/**" + - title: "⬆️ Dependencies" + collapse-after: 0 + exclusive: true + when: + label: "dependencies" + - type: "pre-exclude" + when: + label: "skip-changelog" ``` -Pull requests with the label "feature" or "fix" will now be grouped together: +The `labels-mode` and `paths-mode` options control how the configured labels or +path patterns are compared. `any` is the default. Path matching operates on the +pull request's changed files. + +The `conventional` option parses the pull request title as a conventional +commit header. Set it to `true` to match any conventional title, or configure +`type`/`types`, `scope`/`scopes`, and `breaking`: + +```yml +categories: + - title: "Conventional Changes" + when: + conventional: true + - title: "🚀 Features" + semver-increment: "minor" + when: + conventional: + type: "feat" + - title: "💥 Breaking API Changes" + semver-increment: "major" + when: + conventional: + type: "feat" + scope: "api" + breaking: true +``` + +Within a condition, `label` is shorthand for a single `labels` entry. If both +`label` and `labels` are present, they are combined before `labels-mode` is +applied. With the default `labels-mode: any`, `labels: ["feature", +"enhancement"]` matches pull requests carrying either label. + +Likewise, `path` is shorthand for a single `paths` entry. If both `path` and +`paths` are present, they are combined before `paths-mode` is applied. + +The available matching modes are: + +- `any`: at least one configured value matches +- `all`: every configured value matches +- `only`: every change value is included in the configured set +- `exactly`: the change values and configured values are the same set + +If a condition does not configure any `label`/`labels` or `path`/`paths`, the +corresponding `*-mode` setting has no effect. + +An omitted or empty `when` matches all changes, but the meaning depends on the +category type: -Screenshot of generated draft release with categories +- at most one `type: changelog` category may omit `when`; it becomes the bucket + for otherwise uncategorized changes +- a `type: version-resolver` category with no `when` acts as the fallback when + no other version-resolver category matches +- `pre-include` and `pre-exclude` categories with no `when` match every change -Adding such labels to your PRs can be automated by using [PR Labeler](https://github.com/TimonVS/pr-labeler-action) or [Probot Auto Labeler](https://github.com/probot/autolabeler). +Changes with matching labels or paths will now be grouped together: -## Exclude Pull Requests +Screenshot of generated draft release with categories -With the `exclude-labels` option you can exclude pull requests from the release notes using labels. For example, append the following to your `.github/release-drafter.yml` file: +Adding such labels to your PRs can be automated by using the embedded +[Autolabeler action](#autolabeler). + +Optionally you can add a `collapse-after` entry to your category item, if the +category has more than the defined `collapse-after` pull requests then it will +show all pull requests collapsed for that category. Setting `collapse-after` to +`0` will always collapse the category regardless of the number of pull requests, +and setting it to `-1` disables collapsing. +Append the `collapse-after` integer to your category as following: ```yml -exclude-labels: - - 'skip-changelog' +categories: + - title: "⬆️ Dependencies" + collapse-after: 3 + when: + label: "dependencies" ``` -Pull requests with the label "skip-changelog" will now be excluded from the release draft. +## Exclude Changes + +The recommended way to exclude changes is a `type: pre-exclude` category. +For example, append the following to your +`.github/release-drafter.yml` file: -## Include Pull Requests +```yml +categories: + - type: "pre-exclude" + when: + label: "skip-changelog" +``` -With the `include-labels` option you can specify pull requests from the release notes using labels. Only pull requests that have the configured labels will be included in the pull request. For example, append the following to your `.github/release-drafter.yml` file: +Changes with the label "skip-changelog" will now be excluded from the +release draft. + +## Include Changes + +The recommended way to include only a subset of changes is a +`type: pre-include` category. Only changes that match at least one +`pre-include` category are kept for the rest of the pipeline. For example, +append the following to your +`.github/release-drafter.yml` file: ```yml -include-labels: - - 'app-foo' +categories: + - type: "pre-include" + when: + labels: + - "app-foo" ``` -Pull requests with the label "app-foo" will be the only pull requests included in the release draft. +Changes with the label "app-foo" will be the only changes included +in the release draft. + +## Exclude Contributors + +By default, the `$CONTRIBUTORS` variable will contain the names or usernames of +all the contributors of a release. The `exclude-contributors` option allows you +to remove certain usernames from that list. This can be useful if don't wish to +include yourself, to better highlight only the third-party contributions. + +```yml +exclude-contributors: + - "myusername" +``` ## Replacers -You can search and replace content in the generated changelog body, using regular expressions, with the `replacers` option. Each replacer is applied in order. +You can search and replace content in the generated changelog body, using +regular expressions, with the `replacers` option. Each replacer is applied in +order. ```yml replacers: - search: '/CVE-(\d{4})-(\d+)/g' - replace: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-$1-$2' - - search: 'myname' - replace: 'My Name' + replace: "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-$1-$2" + - search: "myname" + replace: "My Name" + - search: "/- ([a-z])/g" + replace: '- \u$1' # Uppercase the first letter of each changelog entry ``` -## Projects that don't use Semantic Versioning +`search` will be parsed to a RegExp, and `replace` supports substitution in the +[same flavour VSCode does](https://code.visualstudio.com/docs/editing/codebasics#_case-changing-in-regex-replace). -If your project doesn't follow [Semantic Versioning](https://semver.org) you can still use Release Drafter, but you may want to set the `version-template` option to customize how the `$NEXT_{PATCH,MINOR,MAJOR}_VERSION` environment variables are generated. +## Autolabeler -For example, if your project doesn't use patch version numbers, you can set `version-template` to `$MAJOR.$MINOR`. If the current release is version 1.0, then `$NEXT_MINOR_VERSION` will be `1.1`. +You can add automatically a label into a pull request, with the `autolabeler` +action. -## Action Inputs +```yaml +name: Auto Label -The Release Drafter GitHub Action accepts a number of optional inputs directly in your workflow configuration. These will typically override default behavior specified in your `release-drafter.yml` config. +on: + pull_request: + # Only following types are handled by the action, but one can default to all as well + types: [opened, reopened, synchronize] + # pull_request_target event is required for autolabeler to support PRs from forks + # pull_request_target: + # types: [opened, reopened, synchronize] -| Input | Description | -| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `config-name` | If your workflow requires multiple release-drafter configs it be helpful to override the config-name. The config should still be located inside `.github` as that's where we are looking for config files. | -| `name` | The name that will be used in the GitHub release that's created or updated. This will override any `name-template` specified in your `release-drafter.yml` if defined. | -| `tag` | The tag name to be associated with the GitHub release that's created or updated. This will override any `tag-template` specified in your `release-drafter.yml` if defined. | -| `version` | The version to be associated with the GitHub release that's created or updated. This will override any version calculated by the release-drafter. | -| `publish` | A boolean indicating whether the release being created or updated should be immediately published. This may be useful if the output of a previous workflow step determines that a new version of your project has been (or will be) released, as with [`salsify/action-detect-and-tag-new-version`](https://github.com/salsify/action-detect-and-tag-new-version). | -| `prerelease` | A boolean indicating whether the relase being created or updated is a prerelease. | +permissions: + contents: read -## Action Outputs +jobs: + auto_label: + permissions: + pull-requests: write + runs-on: ubuntu-latest + steps: + # runs autolabeler + - uses: release-drafter/release-drafter/autolabeler@v7 +``` -The Release Drafter GitHub Action sets a couple of outputs which can be used as inputs to other Actions in the workflow ([example](https://github.com/actions/upload-release-asset#example-workflow---upload-a-release-asset)). +Available matchers are `files` (glob), `branch` (regex), `title` (regex) and +`body` (regex). Matchers are evaluated independently; the label will be set if +at least one of the matchers meets the criteria. -| Output | Description | -| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `id` | The ID of the release that was created or updated. | -| `name` | The name of this release. | -| `tag_name` | The name of the tag associated with this release. | -| `body` | The body of the drafted release, useful if it needs to be included in files. | -| `html_url` | The URL users can navigate to in order to view the release. i.e. `https://github.com/octocat/Hello-World/releases/v1.0.0`. | -| `upload_url` | The URL for uploading assets to the release, which could be used by GitHub Actions for additional uses, for example the [`@actions/upload-release-asset GitHub Action`](https://www.github.com/actions/upload-release-asset). | +```yml +# .github/release-drafter.yml +autolabeler: + - label: "chore" + files: + - "*.md" + branch: + - '/docs{0,1}\/.+/' + - label: "bug" + branch: + - '/fix\/.+/' + title: + - "/fix/i" + - label: "enhancement" + branch: + - '/feature\/.+/' + body: + - "/JIRA-[0-9]{1,4}/" + +# ... rest of release-drafter config +``` -## Developing +## Prerelease workflow -If you have Node v10+ installed locally, you can run the tests, and a local app, using the following commands: +Release draft supports working with prereleases. It expects your workflow to be +: -```sh -# Install dependencies -yarn +- A stable release is published, ex: `v3.5.0` +- You merge or add meaningful changes your users may want to see, but you are + not quite ready for production +- You publish a prerelease, ex: `v3.5.0-rc.1` +- You merge more changes +- You publish another prerelease, ex: `v3.5.0-rc.2` +- You decide code is ready for production, you publish `v3.5.1` (or another + increment based on your changes) -# Run the tests -npm test +With release-drafter, you can draft each of these releases and prereleases with +the appropriate content using parameter '`prerelease`' and +'`prerelease-identifier`' - available as either an input of from the +config-file. -# Run the app locally -npm run dev +```yaml +jobs: + update_full_release_draft: + runs-on: ubuntu-latest + steps: + - uses: release-drafter/release-drafter@v6 + with: + prerelease: false # the default + # ... rest of your config + update_prerelease_draft: + runs-on: ubuntu-latest + steps: + - uses: release-drafter/release-drafter@v6 + with: + prerelease: true + prerelease-identifier: "rc" # Use semver identifiers : alpha, beta, rc, etc ``` -Once you've started the app, visit `localhost:3000` and you'll get [step-by-step instructions](https://probot.github.io/docs/development/#configuring-a-github-app) for installing it in your GitHub account so you can start pushing commits and testing it locally. - -If you don’t have Node installed, you can use [Docker Compose](https://docs.docker.com/compose/): +Here, both jobs run in parallel every time you add changes to the configured +branch. + +- `update_full_release_draft` will pile-up changes since `v3.5.0` inside a draft + for `v3.5.1` (or `v3.6.0` or `v4.0.0`, depending on your config) +- `update_prerelease_draft` will pile-up changes since the last prerelease in a + prerelease-draft. Changes are : + - if no previous (published) prereleases are found - changes since `v3.5.0` in + a draft for `v3.5.0-rc.1` (prerelease-draft) + - or if `v3.5.0-rc.1` exists (published) already - changes since `v3.5.0-rc.1` + in a draft for `v3.5.0-rc.2` (prerelease-draft) + +Some users like to run `update_prerelease_draft` with `publish: true`, such as +prereleases are published immediately without the need for human intervention +(or an external automation). Since prereleases are not meant to be stable in the +first place, automation may be an acceptable risk for you too. + +> [!IMPORTANT] +> +> - `prerelease-identifier` is not required when `prerelease` is enabled, but +> your prerelease may not be named after / be associated with a tag that is +> semver-compliant to an actual prerelease. +> - when specified, `prerelease-identifier` enables `prerelease: true` if both values come from the same config location; explicit action inputs still take precedence over config file values + +If you want your stable releases to include changes since the last prerelease +instead of the last stable release, use `include-pre-releases: true`. This can +reduce the number of changes included in the stable release body, but diverges +from the standard workflow depicted above. -```sh -# Run the tests -docker-compose run --rm app npm test -``` +## Projects that don't use Semantic Versioning -## Contributing +If your project doesn't follow [Semantic Versioning](https://semver.org) you can +still use Release Drafter, but you may want to set the `version-template` option +to customize how the `$NEXT_{PATCH,MINOR,MAJOR}_VERSION` environment variables +are generated. -Third-party contributions are welcome! 🙏🏼 See [CONTRIBUTING.md](CONTRIBUTING.md) for step-by-step instructions. +For example, if your project doesn't use patch version numbers, you can set +`version-template` to `$MAJOR.$MINOR`. If the current release is version 1.0, +then `$NEXT_MINOR_VERSION` will be `1.1`. -If you need help or have a question, let me know via a GitHub issue. +## Action Inputs -## Deployment +The Release Drafter GitHub Action accepts a number of optional inputs directly +in your workflow configuration. These will typically override default behavior +specified in your `release-drafter.yml` config. + +| Input | Description | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `config-name` | If your workflow requires multiple release-drafter configs it be helpful to override the config-name. The config should still be located inside `.github` as that's where we are looking for config files. | +| `token` | Access token used to make requests against the GitHub API. Defaults to `${{ github.token }}` | +| `dry-run` | When enabled, no write operations (creating/updating releases or adding labels) are performed. Instead, the action logs what it would have done. Default : `false` | +| `name` | The name that will be used in the GitHub release that's created or updated. This will override any `name-template` specified in your `release-drafter.yml` if defined. | +| `tag` | The tag name to be associated with the GitHub release that's created or updated. This will override any `tag-template` specified in your `release-drafter.yml` if defined. | +| `filter-by-range` | Filter releases that satisfies a semver range. Evaluates the tag name againts node's `semver.satisfies()`. | +| `version` | The version to be associated with the GitHub release that's created or updated. This will override any version calculated by the release-drafter. | +| `publish` | A boolean indicating whether the release being created or updated should be immediately published. This may be useful if the output of a previous workflow step determines that a new version of your project has been (or will be) released, as with [`salsify/action-detect-and-tag-new-version`](https://github.com/salsify/action-detect-and-tag-new-version). | +| `prerelease` | Whether to draft a prerelease, with changes since another prerelease (if applicable). Default `false`. | +| `prerelease-identifier` | A string indicating an identifier (alpha, beta, rc, etc), to increment the prerelease version. This automatically enables `prerelease` when both options come from the same config location; explicit action inputs still take precedence. Default `''`. | +| `include-pre-releases` | When looking for the last published release to scan changes up-to, include pre-releases. Has no effect if using `prerelease: true` (already enabled). Default `false`. | +| `latest` | A string indicating whether the release being created or updated should be marked as latest. | +| `commitish` | The release target: a branch, commit SHA, or fully qualified tag or pull request ref. Tag and pull request refs are resolved to commit SHAs. Pull request merge refs force output-only dry-run mode and disable publishing. | +| `header` | A string that would be added before the template body. | +| `footer` | A string that would be added after the template body. | -If you want to deploy your own copy of Release Drafter, follow the [Probot Deployment Guide](https://probot.github.io/docs/deployment/). +## Action Outputs -## Releasing +The Release Drafter GitHub Action sets a couple of outputs which can be used as +inputs to other Actions in the workflow +([example](https://github.com/actions/upload-release-asset#example-workflow---upload-a-release-asset)). + +| Output | Description | +| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | The ID of the release that was created or updated. | +| `name` | The name of this release. | +| `tag_name` | The name of the tag associated with this release. | +| `body` | The body of the drafted release, useful if it needs to be included in files. | +| `html_url` | The URL users can navigate to in order to view the release. i.e. `https://github.com/octocat/Hello-World/releases/v1.0.0`. | +| `upload_url` | The URL for uploading assets to the release, which could be used by GitHub Actions for additional uses, for example the [`@actions/upload-release-asset GitHub Action`](https://www.github.com/actions/upload-release-asset). | +| `resolved_version` | Version resolved by [Version Resolver](#version-resolver). i.e. `6.3.1` | +| `major_version` | Major part of resolved version by [Version Resolver](#version-resolver). i.e. `6` for version `6.3.1` | +| `minor_version` | Minor part of resolved version by [Version Resolver](#version-resolver). i.e. `3` for version `6.3.1` | +| `patch_version` | Patch part of resolved version by [Version Resolver](#version-resolver). i.e. `1` for version `6.3.1` | + +## GitHub Enterprise Server (GHES) + +Release Drafter creates its GitHub client with +[`@actions/github.getOctokit()`](https://github.com/actions/toolkit/tree/main/packages/github#readme). +In GitHub Actions, that client uses the runtime API base URL from +`GITHUB_API_URL`, so the same workflow can target GHES without extra +`github.com`-specific configuration, assuming the required REST and GraphQL +APIs are available on the instance. -Run the following command: +## Contributing -```bash -git checkout master && git pull && npm version [major | minor | patch] -``` +Third-party contributions are welcome! 🙏🏼 See +[CONTRIBUTING.md](docs/CONTRIBUTING.md) for step-by-step instructions. -The command does the following: +> [!IMPORTANT] +> +> Before pushing, run `npm run all` to format, lint, type-check, test, and +> regenerate all build artifacts. The CI pipeline enforces that no uncommitted +> changes remain after these steps. -- Ensures you’re on master and don’t have local, un-commited changes -- Bumps the version number in [package.json](package.json) based on major, minor or patch -- Runs the `postversion` npm script in [package.json](package.json), which: - - Pushes the tag to GitHub - - Publishes the npm release - - Deploys to [Now](https://now.sh) - - Opens the GitHub releases page so you can publish the release notes +If you need help or have a question, let us know via +[a GitHub issue](https://github.com/release-drafter/release-drafter/issues/new). diff --git a/action.yml b/action.yml index 737da6e88d..624e546b05 100644 --- a/action.yml +++ b/action.yml @@ -1,11 +1,15 @@ name: 'Release Drafter' -description: 'Drafts your next release notes as pull requests are merged into master.' -runs: - using: 'docker' - image: 'Dockerfile' +description: + 'Drafts your next release notes as pull requests are merged into your + branch(es).' +author: release-drafter branding: icon: edit-2 color: orange +runs: + using: node24 + main: dist/actions/drafter/run.js + inputs: config-name: description: | @@ -13,6 +17,11 @@ inputs: The config should still be located inside `.github` as that's where we are looking for config files. required: false default: 'release-drafter.yml' + token: + description: > + Access token used to make requests against the GitHub API. Defaults to + github.token. + default: ${{ github.token }} name: description: | The name that will be used in the GitHub release that's created or updated. @@ -33,14 +42,59 @@ inputs: A boolean indicating whether the release being created or updated should be immediately published. required: false default: '' + latest: + description: | + A boolean indicating whether the release being created or updated should be marked as latest. + required: false + default: '' prerelease: description: | - A boolean indicating whether the relase being created or updated is a prerelease. + Whether to draft a prerelease, with changes since another prerelease (if applicable). Default `false`. + required: false + default: '' + prerelease-identifier: + description: | + A string indicating an identifier (alpha, beta, rc, etc), to increment the prerelease version. This automatically enables `prerelease` when both values come from the same config location; explicit action inputs still take precedence. + required: false + default: '' + 'include-pre-releases': + description: | + When looking for the last published release to scan changes up-to, include pre-releases. Has no effect if using `prerelease: true` (already enabled). Default `false`. + required: false + default: '' + commitish: + description: | + The release target, i.e. branch, commit SHA, or fully qualified tag or pull request ref it should point to. + Tag and pull request refs are resolved to commit SHAs. + Pull request merge refs force dry-run mode and disable publishing because they point to ephemeral merge commits. + Defaults to the branch that release-drafter runs for, e.g. `master` when configured to run on pushes to `master`. + required: false + default: '' + header: + description: | + A string that would be added before the template body. + required: false + default: '' + footer: + description: | + A string that would be added after the template body. + required: false + default: '' + dry-run: + description: | + A boolean indicating whether to run without performing any write operations. + When enabled, the action logs what it would have done instead of creating or updating releases. + required: false + default: '' + filter-by-range: + description: | + Filter releases that satisfies this semver range. Evaluates the tag name againts node's semver.satisfies(). required: false default: '' + outputs: id: - description: The ID of therelease that was created or updated. + description: The ID of the release that was created or updated. name: description: The name of the release tag_name: @@ -50,4 +104,21 @@ outputs: html_url: description: The URL users can navigate to in order to view the release upload_url: - description: The URL for uploading assets to the release, which could be used by GitHub Actions for additional uses, for example the @actions/upload-release-asset GitHub Action. + description: + The URL for uploading assets to the release, which could be used by GitHub + Actions for additional uses, for example the @actions/upload-release-asset + GitHub Action. + major_version: + description: + The next major version number. For example, if the last tag or release was + v1.2.3, the value would be v2.0.0. + minor_version: + description: + The next minor version number. For example, if the last tag or release was + v1.2.3, the value would be v1.3.0. + patch_version: + description: + The next patch version number. For example, if the last tag or release was + v1.2.3, the value would be v1.2.4. + resolved_version: + description: The next resolved version number, based on GitHub labels. diff --git a/app.yml b/app.yml deleted file mode 100644 index 9ff07d38c6..0000000000 --- a/app.yml +++ /dev/null @@ -1,8 +0,0 @@ -default_events: - - push -default_permissions: - contents: write - metadata: read - pull_requests: read -url: https://github.com/wolfeidau/release-drafter-serverless -public: true diff --git a/autolabeler/README.md b/autolabeler/README.md new file mode 100644 index 0000000000..7cef48b663 --- /dev/null +++ b/autolabeler/README.md @@ -0,0 +1,12 @@ +# Autolabeler action + +This folder only serves as an alias when users reference the action, which +requires the presence of the [`action.yml`](./action.yml) file + +```yaml +steps: + # runs autolabeler + - uses: release-drafter/release-drafter/autolabeler@latest + # ⚠️ targets root `action.yaml` - runs drafter + - uses: release-drafter/release-drafter@latest +``` diff --git a/autolabeler/action.yml b/autolabeler/action.yml new file mode 100644 index 0000000000..1edd37b420 --- /dev/null +++ b/autolabeler/action.yml @@ -0,0 +1,29 @@ +name: 'Release Drafter - Auto Labeler' +description: 'Automatically labels pull requests based on your configuration.' +author: release-drafter +branding: + icon: edit-2 + color: orange +runs: + using: node24 + main: ../dist/actions/autolabeler/run.js +inputs: + token: + description: > + Access token used to make requests against the GitHub API. Defaults to + github.token. + default: ${{ github.token }} + config-name: + description: | + If your workflow requires multiple autolabeler configs it can be helpful to override the config-name. + The config should still be located inside `.github` as that's where we are looking for config files. + required: false + default: 'release-drafter.yml' + dry-run: + description: | + A boolean indicating whether to run without performing any write operations. + When enabled, the action logs what it would have done instead of adding labels. + required: false + default: '' + +outputs: {} diff --git a/autolabeler/schema.json b/autolabeler/schema.json new file mode 100644 index 0000000000..acddfb9ee7 --- /dev/null +++ b/autolabeler/schema.json @@ -0,0 +1,92 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "_extends": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + }, + { + "type": "object", + "properties": { + "from": { + "type": "string", + "pattern": "\\S" + }, + "strategy": { + "anyOf": [ + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string", + "enum": ["override", "append", "prepend"] + } + }, + { + "type": "null" + } + ] + } + }, + "required": ["from"], + "additionalProperties": false + } + ] + }, + "autolabeler": { + "minItems": 1, + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "minLength": 1 + }, + "files": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "branch": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "title": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "body": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "required": ["label"] + } + } + }, + "required": ["autolabeler"], + "title": "JSON schema for Release Drafter's autolabeler action config." +} diff --git a/bin/generate-fixtures.js b/bin/generate-fixtures.js deleted file mode 100755 index 4a6ac6a326..0000000000 --- a/bin/generate-fixtures.js +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs') -const path = require('path') -const fetch = require('node-fetch') -const { findCommitsWithAssociatedPullRequestsQuery } = require('../lib/commits') - -const REPO_NAME = 'release-drafter-test-repo' -const GITHUB_GRAPHQL_API_ENDPOINT = 'https://api.github.com/graphql' -const GITHUB_TOKEN = process.env.GITHUB_TOKEN - -if (!GITHUB_TOKEN) { - throw new Error( - "GitHub's GraphQL API requires a token. Please pass a valid token (GITHUB_TOKEN) as an env variable, no scopes are required." - ) -} - -const repos = [ - { - owner: 'TimonVS', - branch: 'merge-commit', - }, - { - owner: 'TimonVS', - branch: 'rebase-merging', - }, - { - owner: 'TimonVS', - branch: 'squash-merging', - }, - { - owner: 'TimonVS', - branch: 'overlapping-label', - }, - { - owner: 'jetersen', - branch: 'forking', - }, -] - -repos.forEach((repo) => { - const options = { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `bearer ${GITHUB_TOKEN}`, - }, - body: JSON.stringify({ - query: findCommitsWithAssociatedPullRequestsQuery, - variables: { - owner: repo.owner, - name: REPO_NAME, - ref: repo.branch, - withPullRequestBody: true, - withPullRequestURL: true, - }, - }), - } - - fetch(GITHUB_GRAPHQL_API_ENDPOINT, options) - .then((response) => response.json()) - .then((data) => { - // hack the generated to reduce massive rewrite inside the tests - // basically duplicating the possible configs 🤯 - let string = JSON.stringify(data, null, 2).replace( - /TimonVS\/release-drafter-test-repo/g, - 'toolmantim/release-drafter-test-project' - ) - fs.writeFileSync( - path.resolve( - __dirname, - '../test/fixtures/__generated__', - `graphql-commits-${repo.branch}.json` - ), - string + '\n' - ) - }) - .catch(console.error) -}) diff --git a/bin/generate-schema.js b/bin/generate-schema.js deleted file mode 100644 index ec83210a8f..0000000000 --- a/bin/generate-schema.js +++ /dev/null @@ -1,22 +0,0 @@ -// joi-to-json-schema currently does not support v16 of Joi (https://github.com/lightsofapollo/joi-to-json-schema/issues/57) -const convert = require('joi-to-json-schema') -const fs = require('fs') -const { schema } = require('../lib/schema') -const args = process.argv.slice(2) || [] - -const jsonSchema = { - title: 'JSON schema for Release Drafter yaml files', - id: - 'https://github.com/release-drafter/release-drafter/blob/master/schema.json', - $schema: 'http://json-schema.org/draft-04/schema#', - ...convert(schema()), -} - -// template is only required after deep merged, should not be required in the JSON schema -jsonSchema.required = jsonSchema.required.filter((item) => item !== 'template') - -if (args[0] === 'print') { - fs.writeFileSync('./schema.json', `${JSON.stringify(jsonSchema, null, 2)}\n`) -} - -module.exports.jsonSchema = jsonSchema diff --git a/biome.jsonc b/biome.jsonc new file mode 100644 index 0000000000..405b364de8 --- /dev/null +++ b/biome.jsonc @@ -0,0 +1,93 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.5.3/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true, + }, + "files": { + "ignoreUnknown": true, + "includes": [ + "**", + "!dist", + "!node_modules", + "!coverage", + "!**/*.generated.ts", + "!linter", + "!logs", + "!.licenses", + "!src/tests/fixtures/config/config-with-yaml-exception.yml", + ], + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 80, + "lineEnding": "lf", + }, + "linter": { + "enabled": true, + "rules": { + "preset": "recommended", + "correctness": { + "noUnusedVariables": "error", + "noUnusedImports": "error", + "noUnusedFunctionParameters": "error", + }, + "suspicious": { + "noEvolvingTypes": "error", + }, + "style": { + "noRestrictedImports": { + "level": "error", + "options": { + "patterns": [ + { + "group": ["src/**"], + "message": "Use the '#src/*' subpath import alias instead.", + }, + ], + }, + }, + }, + }, + "domains": { + "test": "recommended", + "project": "all", + }, + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "jsxQuoteStyle": "double", + "quoteProperties": "asNeeded", + "trailingCommas": "all", + "semicolons": "asNeeded", + "arrowParentheses": "always", + "bracketSpacing": true, + "bracketSameLine": false, + }, + }, + "overrides": [ + { + "includes": ["*.jsonc", "*.json5"], + "json": { + "formatter": { + "trailingCommas": "all", + }, + }, + }, + { + // Vitest catches broken imports; nock's package.json trips the resolver. + "includes": ["src/tests/**"], + "linter": { + "rules": { + "correctness": { + "noUnresolvedImports": "off", + }, + }, + }, + }, + ], +} diff --git a/dist/actions/autolabeler/run.js b/dist/actions/autolabeler/run.js new file mode 100644 index 0000000000..0ad65db91e --- /dev/null +++ b/dist/actions/autolabeler/run.js @@ -0,0 +1,177 @@ +import { A as setOutput, D as getInput, N as __toESM, O as info, S as string, _ as array, f as composeConfigGet, i as sharedInputSchema, j as warning, k as setFailed, m as context, n as stringToRegex, p as getOctokit, t as require_ignore, u as getPullRequestChangedFiles, x as object } from "../../chunks/ignore.js"; +//#region src/actions/autolabeler/config/action-input.schema.ts +var actionInputSchema = object({ +/** +* If your workflow requires multiple release-drafter configs it be helpful to override the config-name. +* The config should still be located inside `.github` as that's where we are looking for config files. +* @default 'release-drafter.yml' +*/ +"config-name": string().optional().default("release-drafter.yml") }).and(sharedInputSchema); +//#endregion +//#region src/actions/autolabeler/config/config.schema.ts +var configSchema = object({ +/** +* You can add automatically a label into a pull request. +* Available matchers are `files` (glob), `branch` (regex), `title` (regex) and `body` (regex). +* Matchers are evaluated independently; the label will be set if at least one of the matchers meets the criteria. +*/ +autolabeler: array(object({ + label: string().min(1), + files: array(string().min(1)).optional().default([]), + branch: array(string().min(1)).optional().default([]), + title: array(string().min(1)).optional().default([]), + body: array(string().min(1)).optional().default([]) +})).min(1) }).meta({ + title: "JSON schema for Release Drafter's autolabeler action config.", + id: "https://github.com/release-drafter/release-drafter/blob/master/autolabeler/schema.json" +}); +//#endregion +//#region src/actions/autolabeler/config/get-action-inputs.ts +var getActionInput = () => { + const getInput$1 = (name) => getInput(name) || void 0; + return actionInputSchema.parse({ + "config-name": getInput$1("config-name"), + token: getInput$1("token"), + "dry-run": getInput$1("dry-run") + }); +}; +//#endregion +//#region src/actions/autolabeler/config/get-config.ts +var getConfig = async (configName) => { + const { config, contexts } = await composeConfigGet(configName, context); + if (contexts.length > 1) info(`Config was fetched from ${contexts.length} different contexts.`); + else if (contexts.length === 1) info(`Config fetched ${contexts[0].scheme === "file" ? "locally" : `on remote "${contexts[0].repo.owner}/${contexts[0].repo.repo}${contexts[0].ref ? `@${contexts[0].ref}` : ""}"${!contexts[0].ref ? " on the default branch" : ""}`}.`); + return configSchema.parse(config); +}; +//#endregion +//#region src/actions/autolabeler/config/parse-config.ts +/** +* Returns a copy of `config`, updated with values from `input`. +* +* Also performs some validation. +* +* Input takes precedence, because it's more easy to change at runtime +*/ +var parseConfig = ({ config: originalConfig }) => { + const config = structuredClone(originalConfig); + const autolabeler = config.autolabeler.map((autolabel) => { + try { + return { + ...autolabel, + branch: autolabel.branch.map((reg) => { + return stringToRegex(reg); + }), + title: autolabel.title.map((reg) => { + return stringToRegex(reg); + }), + body: autolabel.body.map((reg) => { + return stringToRegex(reg); + }) + }; + } catch { + warning(`Bad autolabeler regex: '${autolabel.branch}', '${autolabel.title}' or '${autolabel.body}'`); + return false; + } + }).filter((a) => !!a); + return { + ...config, + autolabeler + }; +}; +//#endregion +//#region src/actions/autolabeler/main.ts +var import_ignore = /* @__PURE__ */ __toESM(require_ignore(), 1); +var main = async (params) => { + info(`Running for event "${context.eventName || "[undefined]"}.${context.payload.action || "[undefined]"}"`); + if (context.eventName !== "pull_request" && context.eventName !== "pull_request_target") throw new Error(`Event type is wrong. Expected 'pull_request' or 'pull_request_target', received '${context.eventName}'`); + const octokit = getOctokit(); + /** + * @see https://docs.github.com/en/webhooks/webhook-events-and-payloads#pull_request + */ + const payload = context.payload; + const changedFiles = await getPullRequestChangedFiles(octokit, { + ...context.repo, + pull_number: payload.number + }); + const labels = /* @__PURE__ */ new Set(); + for (const autolabel of params.config.autolabeler) { + let found = false; + if (!found && autolabel.files.length > 0) { + const matcher = (0, import_ignore.default)().add(autolabel.files); + if (changedFiles.some((file) => matcher.ignores(file))) { + labels.add(autolabel.label); + found = true; + info(`Found label for files: '${autolabel.label}'`); + } + } + if (!found && autolabel.branch.length > 0) { + for (const matcher of autolabel.branch) if (matcher.test(payload.pull_request.head.ref)) { + labels.add(autolabel.label); + found = true; + info(`Found label for branch: '${autolabel.label}'`); + break; + } + } + if (!found && autolabel.title.length > 0) { + for (const matcher of autolabel.title) if (matcher.test(payload.pull_request.title)) { + labels.add(autolabel.label); + found = true; + info(`Found label for title: '${autolabel.label}'`); + break; + } + } + if (!found && payload.pull_request.body != null && autolabel.body.length > 0) { + for (const matcher of autolabel.body) if (matcher.test(payload.pull_request.body)) { + labels.add(autolabel.label); + found = true; + info(`Found label for body: '${autolabel.label}'`); + break; + } + } + } + if (labels.size > 0) if (params.dryRun) info(`[dry-run] Would add labels [${Array.from(labels).join(", ")}] to PR #${payload.number}`); + else await octokit.rest.issues.addLabels({ + ...context.repo, + issue_number: payload.number, + labels: Array.from(labels) + }); + return { + pr_number: payload.number.toString(), + labels: labels.size ? Array.from(labels).join(",") : void 0 + }; +}; +//#endregion +//#region src/actions/autolabeler/runner.ts +/** +* The main function for the action. +* +* @returns Resolves when the action is complete. +*/ +async function run() { + try { + const input = getActionInput(); + const { labels, pr_number } = await main({ + config: parseConfig({ config: await getConfig(input["config-name"]) }), + dryRun: input["dry-run"] + }); + if (pr_number) setOutput("number", pr_number); + if (labels) setOutput("labels", labels); + } catch (error) { + if (error instanceof Error) setFailed(error.message); + } +} +//#endregion +//#region src/actions/autolabeler/run.ts +/* node:coverage ignore file -- @preserve */ +/** +* The entrypoint for the action. This file simply imports and runs the action's +* main logic. +* +* Do not add any logic to this file; instead, add it to `runner.ts`. +* +* `runner.ts` is the entrypoint for tests and should contain all the action's +* main logic. +*/ +await run(); +//#endregion +export {}; diff --git a/dist/actions/drafter/run.js b/dist/actions/drafter/run.js new file mode 100644 index 0000000000..9046505760 --- /dev/null +++ b/dist/actions/drafter/run.js @@ -0,0 +1,3425 @@ +import { A as setOutput, C as stringbool, D as getInput, E as error, M as __commonJSMin, N as __toESM, O as info, S as string, T as debug, _ as array, a as parseCommitishForRelease, b as number, c as executeGraphql, d as getPullRequestsChangedFiles, f as composeConfigGet, g as _enum, h as ZodDefault, i as sharedInputSchema, j as warning, k as setFailed, l as paginateGraphql, m as context, n as stringToRegex, o as FindCommitsInComparisonDocument, p as getOctokit, r as escapeStringRegexp, s as FindRecentMergedPullRequestsDocument, t as require_ignore, v as boolean, w as union, x as object, y as literal } from "../../chunks/ignore.js"; +//#region src/actions/drafter/config/schemas/common-config.schema.ts +/** +* Configuration parameters that can be specified in both +* the config file or the action input. +* +* Default values cannot be defined here, +* as action inputs may override config file values. +* +* @see merge-input-and-config.ts for how the merging of config and input is handled, including default values. +*/ +var commonConfigSchema = object({ + /** + * A boolean indicating whether the release being created or updated should be marked as latest. + */ + latest: stringbool().or(boolean()).optional(), + /** + * Whether to draft a prerelease, with changes since another prerelease (if applicable). Default `false`. + */ + prerelease: stringbool().or(boolean()).optional(), + /** + * A string indicating an identifier (alpha, beta, rc, etc), to increment the prerelease version. This automatically enables `prerelease` when both values come from the same config location; explicit action inputs still take precedence. Default `''`. + */ + "prerelease-identifier": string().optional(), + /** + * When looking for the last published release to scan changes up-to, include pre-releases. Has no effect if using `prerelease: true` (already enabled). Default `false`. + */ + "include-pre-releases": stringbool().or(boolean()).optional(), + /** + * The release target, i.e. branch, commit SHA, or fully qualified tag or pull request ref it should point to. Tag and pull request refs are resolved to commit SHAs. Defaults to the branch that release-drafter runs for, e.g. `master` when configured to run on pushes to `master`. + */ + commitish: string().optional(), + /** + * A string that would be added before the template body. + */ + header: string().optional(), + /** + * A string that would be added after the template body. + */ + footer: string().optional(), + /** + * Filter releases that satisfies this semver range. Evaluates the tag name againts node's semver.satisfies(). + */ + "filter-by-range": string().optional() +}); +var actionInputSchema = object({ + /** + * If your workflow requires multiple release-drafter configs it be helpful to override the config-name. + * The config should still be located inside `.github` as that's where we are looking for config files. + * @default 'release-drafter.yml' + */ + "config-name": string().optional().default("release-drafter.yml"), + /** + * The name that will be used in the GitHub release that's created or updated. + * This will override any `name-template` specified in your `release-drafter.yml` if defined. + */ + name: string().optional(), + /** + * The tag name to be associated with the GitHub release that's created or updated. + * This will override any `tag-template` specified in your `release-drafter.yml` if defined. + */ + tag: string().optional(), + /** + * The version to be associated with the GitHub release that's created or updated. + * This will override any version calculated by the release-drafter. + */ + version: string().optional(), + /** + * A boolean indicating whether the release being created or updated should be immediately published. + */ + publish: stringbool().optional().default(false) +}).and(sharedInputSchema).and(commonConfigSchema); +//#endregion +//#region src/actions/drafter/config/get-action-inputs.ts +var getActionInput = () => { + const getInput$1 = (name) => getInput(name) || void 0; + const actionInput = { + "config-name": getInput$1("config-name"), + name: getInput$1("name"), + tag: getInput$1("tag"), + version: getInput$1("version"), + publish: getInput$1("publish"), + token: getInput$1("token"), + latest: getInput$1("latest"), + prerelease: getInput$1("prerelease"), + "prerelease-identifier": getInput$1("prerelease-identifier"), + "include-pre-releases": getInput$1("include-pre-releases"), + commitish: getInput$1("commitish"), + header: getInput$1("header"), + footer: getInput$1("footer"), + "dry-run": getInput$1("dry-run"), + "filter-by-range": getInput$1("filter-by-range") + }; + return actionInputSchema.parse(actionInput); +}; +//#endregion +//#region src/actions/drafter/config/schemas/config.schema.ts +/** +* A single set of predicates that are combined with AND logic. +* All specified predicates must be satisfied for a change to match. +*/ +var changeConditionSchema = object({ + /** + * Conventional commit predicate: matches a change whose title or message + * follows the conventional commit shape, e.g. `feat(api)!: add endpoint`. + */ + conventional: union([literal(true), object({ + /** Shorthand for one `types` entry. */ + type: string().min(1).optional(), + /** Conventional commit types to match, e.g. `feat` or `fix`. */ + types: array(string().min(1)).optional().default([]), + /** Shorthand for one `scopes` entry. */ + scope: string().min(1).optional(), + /** Conventional commit scopes to match, e.g. `api` or `ui`. */ + scopes: array(string().min(1)).optional().default([]), + /** Match titles with (`true`) or without (`false`) a breaking `!`. */ + breaking: boolean().optional() + })]).optional(), + /** + * Label predicate: matches a change that carries this label. + * + * Shorthand for adding a single value to `labels`. + * If `label` and `labels` are both specified, they are combined. + * + * Use `labels-mode` to configure how this label is compared to change labels. + */ + label: string().min(1).optional(), + /** + * Labels predicate: matches a change that carries these labels. + * + * `labels-mode` defaults to `any`, so the condition matches when the change + * shares at least one configured label unless another mode is set. + * + * Use `labels-mode` to configure how these labels are compared to change labels. + */ + labels: array(string().min(1)).optional().default([]), + /** + * Matching mode for the `labels` predicate. + * + * Has no effect unless `label` or `labels` is configured in the same condition. + * + * The comparison is set-based (label order is ignored). + * + * - `any`: Change and configured labels overlap (current behavior). + * - `all`: Change contains every configured label. Change can have more labels. + * - `only`: Every change label is included in configured labels. Configured labels can specify more. + * - `exactly`: Change labels and configured labels are the same set. + */ + "labels-mode": _enum([ + "any", + "all", + "only", + "exactly" + ]).optional().default("any"), + /** + * Path predicate: matches a change that touched this path pattern. Supports glob patterns. + * + * Same as specifying a single `paths` value. + * If `path` and `paths` are both specified, they are combined. + * + * Use `paths-mode` to configure how this path is matched against the pull + * request's changed files. + */ + path: string().min(1).optional(), + /** + * Paths predicate: matches a change that touched any of these path patterns. + * Values support glob patterns. + * + * If `path` and `paths` are both specified, they are combined before + * `paths-mode` is applied. + * + * Use `paths-mode` to configure how these path patterns are compared to the + * pull request's changed files. + */ + paths: array(string().min(1)).optional().default([]), + /** + * Matching mode for the `paths` predicate. + * + * Has no effect unless `path` or `paths` is configured in the same condition. + * + * The comparison is set-based (path order is ignored). + * + * - `any`: At least one changed file matched a configured path pattern. + * - `all`: Every configured path pattern matched at least one changed file. + * - `only`: Every changed file matched a configured path pattern. + * - `exactly`: Every changed file matched a configured path pattern and every + * configured path pattern matched at least one changed file. + */ + "paths-mode": _enum([ + "any", + "all", + "only", + "exactly" + ]).optional().default("any") +}); +var changeConditionSchemaDefaults = changeConditionSchema.parse({}); +var categorySchema = object({ + /** + * Expanded in $TITLE in the category-template. + * + * Required when `type` is `changelog` (default). + * This is enforced during merged-config validation rather than by this schema alone. + * + * May be omitted for non-changelog categories because + * they are not rendered in the changelog output. + */ + title: string().min(1).optional(), + /** + * The type of the category. + * + * - `changelog`: Included in the generated changelog. + * - `pre-include`: Keep only matching changes for later changelog categorization. + * - `pre-exclude`: Exclude matching changes for later changelog categorization. Is run against changes that were included in category type `pre-include` if specified. + * - `version-resolver`: Used solely to determine `$RESOLVED_VERSION` from the changes this category matches, without rendering a changelog section. Use `type: 'changelog'` (default) and `categories[*].semver-increment` instead if you mean this category to also be included in the changelog. + * + * `pre-include` always runs before `pre-exclude` in the pipeline. + * Omitted values default to `changelog`. + * + * @default "changelog" + */ + type: _enum([ + "changelog", + "pre-include", + "pre-exclude", + "version-resolver" + ]).optional().default("changelog"), + /** + * Whether changes included in this category should be excluded from other categories. + * + * Default behavior allows changes to appear in multiple categories if they match multiple category criteria. + * + * Only applicable to categories of `type: changelog` or `type: version-resolver`. + * This only controls inclusion for a single category type at a time, so a change can still match + * one exclusive changelog category and one exclusive version-resolver category. + * + * @default false + */ + exclusive: boolean().optional().default(false), + /** + * Collapses the category's change list into a `
`/`` block + * when the number of changes is greater than this value. + * + * Only applicable to categories of `type: changelog`. + * + * Set to `0` to always collapse. Set to `-1` to disable collapsing. + * + * @default -1 + */ + "collapse-after": number().int().min(-1).optional().default(-1), + /** + * Which version increment this category contributes to `$RESOLVED_VERSION`. + * + * For `type: changelog` categories, this applies to changes that end up assigned + * to the category after changelog matching and `exclusive` handling. + * For `type: version-resolver` categories, this applies to changes the category + * matches directly, with a category that omits `when` acting as the fallback + * when no other `type: version-resolver` category matches. + * + * If multiple categories contribute, the most severe increment wins. + * For example, if one contributing category has `semver-increment: 'minor'` + * and another has `semver-increment: 'patch'`, the resulting increment will + * be `minor`. + * + * Applicable to categories of `type: changelog` and `type: version-resolver`. + * Ignored for `type: pre-include` and `type: pre-exclude`. + * + * @default "patch" + */ + "semver-increment": _enum([ + "major", + "minor", + "patch" + ]).optional().default("patch"), + /** + * Compatibility shorthand for adding label matching to this category. + * + * Equivalent to adding the same `labels` predicate to every `when` condition. + * + * @deprecated Use `when.labels` instead. + */ + labels: array(string().min(1)).optional().default([]), + /** + * Compatibility shorthand for adding a single label match to this category. + * + * Equivalent to adding the same `label` predicate to every `when` condition. + * + * @deprecated Use `when.label` instead. + */ + label: string().min(1).optional(), + /** + * Conditions that determine whether a change belongs to this category. + * + * Can be specified as: + * - A **single condition** (object): the change must satisfy all predicates in that condition. + * - An **array of conditions**: the change must satisfy all predicates of **at least one** + * condition (OR logic across conditions, AND logic within each condition). + * + * An empty array (default) matches all changes. + * + * @example + * # Shorthand: single condition (must have label "bug" AND touch "src/") + * when: + * labels: [bug] + * paths: [src/**] + * + * @example + * # Array: (label "bug" AND path "src/") OR (label "enhancement") + * when: + * - labels: [bug] + * paths: [src/**] + * - labels: [enhancement] + */ + when: changeConditionSchema.or(array(changeConditionSchema)).optional().default([]) +}); +var categorySchemaDefaults = categorySchema.parse({}); +var exclusiveConfigSchema = object({ + /** + * The template to use for each merged change. + */ + "change-template": string().optional().default("* $TITLE (#$NUMBER) $AUTHORS"), + /** + * The template to use for each author in `$AUTHORS`. + */ + "change-author-template": string().optional().default("$AUTHOR_MENTION"), + /** + * The separator to use between authors in `$AUTHORS`. + */ + "change-authors-separator": string().optional().default(", "), + /** + * An optional separator to use before the final author in `$AUTHORS`. + */ + "change-authors-final-separator": string().optional(), + /** + * Characters to escape in `$TITLE` when inserting into `change-template` so that they are not interpreted as Markdown format characters. + */ + "change-title-escapes": string().optional(), + /** + * The template to use for when there’s no changes. + */ + "no-changes-template": string().optional().default("* No changes"), + /** + * The template to use when calculating the next version number for the release. Useful for projects that don't use semantic versioning. + */ + "version-template": string().optional().default("$MAJOR.$MINOR.$PATCH$PRERELEASE"), + /** + * The template for the name of the draft release. + */ + "name-template": string().optional(), + /** + * A known prefix used to filter release tags. For matching tags, this prefix is stripped before attempting to parse the version. + */ + "tag-prefix": string().optional(), + /** + * The template for the tag of the draft release. + */ + "tag-template": string().optional(), + /** + * Exclude changes using labels. + * + * @deprecated Use a `type: pre-exclude` category with `when.labels` instead. + */ + "exclude-labels": array(string()).optional().default([]), + /** + * Include only the specified changes using labels. + * + * @deprecated Use a `type: pre-include` category with `when.labels` instead. + */ + "include-labels": array(string()).optional().default([]), + /** + * Restrict changes included in the release notes to only the changes that modified any of the paths in this array. + * Supports files and directories. + * + * @deprecated Use a `type: pre-include` category with `when.paths` instead. + */ + "include-paths": array(string()).optional().default([]), + /** + * Exclude changes from the release notes if they modified any of the paths in this array. + * Supports files and directories. If used with `include-paths`, the exclusion takes precedence. + * + * @deprecated Use a `type: pre-exclude` category with `when.paths` instead. + */ + "exclude-paths": array(string()).optional().default([]), + /** + * Exclude specific usernames from the generated `$CONTRIBUTORS` variable. + */ + "exclude-contributors": array(string()).optional().default([]), + /** + * The template to use for each new contributor in `$NEW_CONTRIBUTORS`. + */ + "new-contributor-template": string().optional().default("* $AUTHOR_MENTION made their first contribution in #$NUMBER"), + /** + * The template to use for `$CONTRIBUTORS` when there's no contributors to list. + */ + "no-contributors-template": string().optional().default("No contributors"), + /** + * Sort changelog by merged_at or title. + */ + "sort-by": _enum(["merged_at", "title"]).optional().default("merged_at"), + /** + * Sort changelog in ascending or descending order. + */ + "sort-direction": _enum(["ascending", "descending"]).optional().default("descending"), + /** + * Filter previous releases to consider only those with the target matching `commitish`. + */ + "filter-by-commitish": boolean().optional().default(false), + "pull-request-limit": number().int().positive().optional().default(5), + /** + * Size of the pagination window when walking the repo. Can avoid erratic 502s from Github. Default: `15` + */ + "history-limit": number().int().positive().optional().default(15), + /** + * Search and replace content in the generated changelog body. + */ + replacers: array(object({ + search: string().min(1), + replace: string().min(0) + })).optional().default([]), + /** + * Categorize changes + */ + categories: array(categorySchema).optional().default([]), + /** + * Adjust the `$RESOLVED_VERSION` variable using labels. + * + * @deprecated Use a category with a `semver-increment` instead. Use category[ies] with `type: version-resolver` to separate version resolution from changelog inclusion concerns. + */ + "version-resolver": object({ + major: object({ labels: array(string().min(1)) }).optional().default({ labels: [] }), + minor: object({ labels: array(string().min(1)) }).optional().default({ labels: [] }), + patch: object({ labels: array(string().min(1)) }).optional().default({ labels: [] }), + default: _enum([ + "major", + "minor", + "patch" + ]).optional().default("patch") + }).optional().default({ + major: { labels: [] }, + minor: { labels: [] }, + patch: { labels: [] }, + default: "patch" + }), + /** + * The template to use for each category. + */ + "category-template": string().optional().default("## $TITLE"), + /** + * The template for the body of the draft release. + * Optional as it may be inherited via `_extends`. + */ + template: string().optional().default("") +}).meta({ + title: "JSON schema for Release Drafter yaml files", + id: "https://github.com/release-drafter/release-drafter/blob/master/drafter/schema.json" +}); +var configSchema = exclusiveConfigSchema.and(commonConfigSchema); +var configSchemaDefaults = Object.fromEntries(Object.entries({ + ...exclusiveConfigSchema.shape, + ...commonConfigSchema.shape +}).map(([key, value]) => { + if (value instanceof ZodDefault) return [key, value.def.defaultValue]; + return [key, void 0]; +})); +//#endregion +//#region src/actions/drafter/config/get-config.ts +var getConfig = async (configName) => { + const { config, contexts } = await composeConfigGet(configName, context); + contexts.forEach(({ filepath, ref, repo, scheme }) => { + const remotePath = `${repo.owner}/${repo.repo}/${filepath}${ref ? `@${ref}` : ""}`; + info(`Config fetched ${scheme === "file" ? `locally from "${filepath}"` : `from "${remotePath}"${ref ? "" : " on the default branch"}`}.`); + }); + return configSchema.parse(config); +}; +//#endregion +//#region node_modules/semver/internal/lrucache.js +var require_lrucache = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var LRUCache = class { + constructor() { + this.max = 1e3; + this.map = /* @__PURE__ */ new Map(); + } + get(key) { + const value = this.map.get(key); + if (value === void 0) return; + else { + this.map.delete(key); + this.map.set(key, value); + return value; + } + } + delete(key) { + return this.map.delete(key); + } + set(key, value) { + if (!this.delete(key) && value !== void 0) { + if (this.map.size >= this.max) { + const firstKey = this.map.keys().next().value; + this.delete(firstKey); + } + this.map.set(key, value); + } + return this; + } + }; + module.exports = LRUCache; +})); +//#endregion +//#region node_modules/semver/internal/parse-options.js +var require_parse_options = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var looseOption = Object.freeze({ loose: true }); + var emptyOpts = Object.freeze({}); + var parseOptions = (options) => { + if (!options) return emptyOpts; + if (typeof options !== "object") return looseOption; + return options; + }; + module.exports = parseOptions; +})); +//#endregion +//#region node_modules/semver/internal/constants.js +var require_constants = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var SEMVER_SPEC_VERSION = "2.0.0"; + var MAX_LENGTH = 256; + var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; + module.exports = { + MAX_LENGTH, + MAX_SAFE_COMPONENT_LENGTH: 16, + MAX_SAFE_BUILD_LENGTH: MAX_LENGTH - 6, + MAX_SAFE_INTEGER, + RELEASE_TYPES: [ + "major", + "premajor", + "minor", + "preminor", + "patch", + "prepatch", + "prerelease" + ], + SEMVER_SPEC_VERSION, + FLAG_INCLUDE_PRERELEASE: 1, + FLAG_LOOSE: 2 + }; +})); +//#endregion +//#region node_modules/semver/internal/debug.js +var require_debug = /* @__PURE__ */ __commonJSMin(((exports, module) => { + module.exports = typeof process === "object" && process.env && process.env.NODE_DEBUG && /\bsemver\b/i.test(process.env.NODE_DEBUG) ? (...args) => console.error("SEMVER", ...args) : () => {}; +})); +//#endregion +//#region node_modules/semver/internal/re.js +var require_re = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { MAX_SAFE_COMPONENT_LENGTH, MAX_SAFE_BUILD_LENGTH, MAX_LENGTH } = require_constants(); + var debug = require_debug(); + exports = module.exports = {}; + var re = exports.re = []; + var safeRe = exports.safeRe = []; + var src = exports.src = []; + var safeSrc = exports.safeSrc = []; + var t = exports.t = {}; + var R = 0; + var LETTERDASHNUMBER = "[a-zA-Z0-9-]"; + var safeRegexReplacements = [ + ["\\s", 1], + ["\\d", MAX_LENGTH], + [LETTERDASHNUMBER, MAX_SAFE_BUILD_LENGTH] + ]; + var makeSafeRegex = (value) => { + for (const [token, max] of safeRegexReplacements) value = value.split(`${token}*`).join(`${token}{0,${max}}`).split(`${token}+`).join(`${token}{1,${max}}`); + return value; + }; + var createToken = (name, value, isGlobal) => { + const safe = makeSafeRegex(value); + const index = R++; + debug(name, index, value); + t[name] = index; + src[index] = value; + safeSrc[index] = safe; + re[index] = new RegExp(value, isGlobal ? "g" : void 0); + safeRe[index] = new RegExp(safe, isGlobal ? "g" : void 0); + }; + createToken("NUMERICIDENTIFIER", "0|[1-9]\\d*"); + createToken("NUMERICIDENTIFIERLOOSE", "\\d+"); + createToken("NONNUMERICIDENTIFIER", `\\d*[a-zA-Z-]${LETTERDASHNUMBER}*`); + createToken("MAINVERSION", `(${src[t.NUMERICIDENTIFIER]})\\.(${src[t.NUMERICIDENTIFIER]})\\.(${src[t.NUMERICIDENTIFIER]})`); + createToken("MAINVERSIONLOOSE", `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.(${src[t.NUMERICIDENTIFIERLOOSE]})\\.(${src[t.NUMERICIDENTIFIERLOOSE]})`); + createToken("PRERELEASEIDENTIFIER", `(?:${src[t.NONNUMERICIDENTIFIER]}|${src[t.NUMERICIDENTIFIER]})`); + createToken("PRERELEASEIDENTIFIERLOOSE", `(?:${src[t.NONNUMERICIDENTIFIER]}|${src[t.NUMERICIDENTIFIERLOOSE]})`); + createToken("PRERELEASE", `(?:-(${src[t.PRERELEASEIDENTIFIER]}(?:\\.${src[t.PRERELEASEIDENTIFIER]})*))`); + createToken("PRERELEASELOOSE", `(?:-?(${src[t.PRERELEASEIDENTIFIERLOOSE]}(?:\\.${src[t.PRERELEASEIDENTIFIERLOOSE]})*))`); + createToken("BUILDIDENTIFIER", `${LETTERDASHNUMBER}+`); + createToken("BUILD", `(?:\\+(${src[t.BUILDIDENTIFIER]}(?:\\.${src[t.BUILDIDENTIFIER]})*))`); + createToken("FULLPLAIN", `v?${src[t.MAINVERSION]}${src[t.PRERELEASE]}?${src[t.BUILD]}?`); + createToken("FULL", `^${src[t.FULLPLAIN]}$`); + createToken("LOOSEPLAIN", `[v=\\s]*${src[t.MAINVERSIONLOOSE]}${src[t.PRERELEASELOOSE]}?${src[t.BUILD]}?`); + createToken("LOOSE", `^${src[t.LOOSEPLAIN]}$`); + createToken("GTLT", "((?:<|>)?=?)"); + createToken("XRANGEIDENTIFIERLOOSE", `${src[t.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`); + createToken("XRANGEIDENTIFIER", `${src[t.NUMERICIDENTIFIER]}|x|X|\\*`); + createToken("XRANGEPLAIN", `[v=\\s]*(${src[t.XRANGEIDENTIFIER]})(?:\\.(${src[t.XRANGEIDENTIFIER]})(?:\\.(${src[t.XRANGEIDENTIFIER]})(?:${src[t.PRERELEASE]})?${src[t.BUILD]}?)?)?`); + createToken("XRANGEPLAINLOOSE", `[v=\\s]*(${src[t.XRANGEIDENTIFIERLOOSE]})(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})(?:${src[t.PRERELEASELOOSE]})?${src[t.BUILD]}?)?)?`); + createToken("XRANGE", `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAIN]}$`); + createToken("XRANGELOOSE", `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAINLOOSE]}$`); + createToken("COERCEPLAIN", `(^|[^\\d])(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}})(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?`); + createToken("COERCE", `${src[t.COERCEPLAIN]}(?:$|[^\\d])`); + createToken("COERCEFULL", src[t.COERCEPLAIN] + `(?:${src[t.PRERELEASE]})?(?:${src[t.BUILD]})?(?:$|[^\\d])`); + createToken("COERCERTL", src[t.COERCE], true); + createToken("COERCERTLFULL", src[t.COERCEFULL], true); + createToken("LONETILDE", "(?:~>?)"); + createToken("TILDETRIM", `(\\s*)${src[t.LONETILDE]}\\s+`, true); + exports.tildeTrimReplace = "$1~"; + createToken("TILDE", `^${src[t.LONETILDE]}${src[t.XRANGEPLAIN]}$`); + createToken("TILDELOOSE", `^${src[t.LONETILDE]}${src[t.XRANGEPLAINLOOSE]}$`); + createToken("LONECARET", "(?:\\^)"); + createToken("CARETTRIM", `(\\s*)${src[t.LONECARET]}\\s+`, true); + exports.caretTrimReplace = "$1^"; + createToken("CARET", `^${src[t.LONECARET]}${src[t.XRANGEPLAIN]}$`); + createToken("CARETLOOSE", `^${src[t.LONECARET]}${src[t.XRANGEPLAINLOOSE]}$`); + createToken("COMPARATORLOOSE", `^${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]})$|^$`); + createToken("COMPARATOR", `^${src[t.GTLT]}\\s*(${src[t.FULLPLAIN]})$|^$`); + createToken("COMPARATORTRIM", `(\\s*)${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]}|${src[t.XRANGEPLAIN]})`, true); + exports.comparatorTrimReplace = "$1$2$3"; + createToken("HYPHENRANGE", `^\\s*(${src[t.XRANGEPLAIN]})\\s+-\\s+(${src[t.XRANGEPLAIN]})\\s*$`); + createToken("HYPHENRANGELOOSE", `^\\s*(${src[t.XRANGEPLAINLOOSE]})\\s+-\\s+(${src[t.XRANGEPLAINLOOSE]})\\s*$`); + createToken("STAR", "(<|>)?=?\\s*\\*"); + createToken("GTE0", "^\\s*>=\\s*0\\.0\\.0\\s*$"); + createToken("GTE0PRE", "^\\s*>=\\s*0\\.0\\.0-0\\s*$"); +})); +//#endregion +//#region node_modules/semver/internal/identifiers.js +var require_identifiers = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var numeric = /^[0-9]+$/; + var compareIdentifiers = (a, b) => { + if (typeof a === "number" && typeof b === "number") return a === b ? 0 : a < b ? -1 : 1; + const anum = numeric.test(a); + const bnum = numeric.test(b); + if (anum && bnum) { + a = +a; + b = +b; + } + return a === b ? 0 : anum && !bnum ? -1 : bnum && !anum ? 1 : a < b ? -1 : 1; + }; + var rcompareIdentifiers = (a, b) => compareIdentifiers(b, a); + module.exports = { + compareIdentifiers, + rcompareIdentifiers + }; +})); +//#endregion +//#region node_modules/semver/classes/semver.js +var require_semver = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var debug = require_debug(); + var { MAX_LENGTH, MAX_SAFE_INTEGER } = require_constants(); + var { safeRe: re, t } = require_re(); + var parseOptions = require_parse_options(); + var { compareIdentifiers } = require_identifiers(); + var isPrereleaseIdentifier = (prerelease, identifier) => { + const identifiers = identifier.split("."); + if (identifiers.length > prerelease.length) return false; + for (let i = 0; i < identifiers.length; i++) if (compareIdentifiers(prerelease[i], identifiers[i]) !== 0) return false; + return true; + }; + module.exports = class SemVer { + constructor(version, options) { + options = parseOptions(options); + if (version instanceof SemVer) if (version.loose === !!options.loose && version.includePrerelease === !!options.includePrerelease) return version; + else version = version.version; + else if (typeof version !== "string") throw new TypeError(`Invalid version. Must be a string. Got type "${typeof version}".`); + if (version.length > MAX_LENGTH) throw new TypeError(`version is longer than ${MAX_LENGTH} characters`); + debug("SemVer", version, options); + this.options = options; + this.loose = !!options.loose; + this.includePrerelease = !!options.includePrerelease; + const m = version.trim().match(options.loose ? re[t.LOOSE] : re[t.FULL]); + if (!m) throw new TypeError(`Invalid Version: ${version}`); + this.raw = version; + this.major = +m[1]; + this.minor = +m[2]; + this.patch = +m[3]; + if (this.major > MAX_SAFE_INTEGER || this.major < 0) throw new TypeError("Invalid major version"); + if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) throw new TypeError("Invalid minor version"); + if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) throw new TypeError("Invalid patch version"); + if (!m[4]) this.prerelease = []; + else this.prerelease = m[4].split(".").map((id) => { + if (/^[0-9]+$/.test(id)) { + const num = +id; + if (num >= 0 && num < MAX_SAFE_INTEGER) return num; + } + return id; + }); + this.build = m[5] ? m[5].split(".") : []; + this.format(); + } + format() { + this.version = `${this.major}.${this.minor}.${this.patch}`; + if (this.prerelease.length) this.version += `-${this.prerelease.join(".")}`; + return this.version; + } + toString() { + return this.version; + } + compare(other) { + debug("SemVer.compare", this.version, this.options, other); + if (!(other instanceof SemVer)) { + if (typeof other === "string" && other === this.version) return 0; + other = new SemVer(other, this.options); + } + if (other.version === this.version) return 0; + return this.compareMain(other) || this.comparePre(other); + } + compareMain(other) { + if (!(other instanceof SemVer)) other = new SemVer(other, this.options); + if (this.major < other.major) return -1; + if (this.major > other.major) return 1; + if (this.minor < other.minor) return -1; + if (this.minor > other.minor) return 1; + if (this.patch < other.patch) return -1; + if (this.patch > other.patch) return 1; + return 0; + } + comparePre(other) { + if (!(other instanceof SemVer)) other = new SemVer(other, this.options); + if (this.prerelease.length && !other.prerelease.length) return -1; + else if (!this.prerelease.length && other.prerelease.length) return 1; + else if (!this.prerelease.length && !other.prerelease.length) return 0; + let i = 0; + do { + const a = this.prerelease[i]; + const b = other.prerelease[i]; + debug("prerelease compare", i, a, b); + if (a === void 0 && b === void 0) return 0; + else if (b === void 0) return 1; + else if (a === void 0) return -1; + else if (a === b) continue; + else return compareIdentifiers(a, b); + } while (++i); + } + compareBuild(other) { + if (!(other instanceof SemVer)) other = new SemVer(other, this.options); + let i = 0; + do { + const a = this.build[i]; + const b = other.build[i]; + debug("build compare", i, a, b); + if (a === void 0 && b === void 0) return 0; + else if (b === void 0) return 1; + else if (a === void 0) return -1; + else if (a === b) continue; + else return compareIdentifiers(a, b); + } while (++i); + } + inc(release, identifier, identifierBase) { + if (release.startsWith("pre")) { + if (!identifier && identifierBase === false) throw new Error("invalid increment argument: identifier is empty"); + if (identifier) { + const match = `-${identifier}`.match(this.options.loose ? re[t.PRERELEASELOOSE] : re[t.PRERELEASE]); + if (!match || match[1] !== identifier) throw new Error(`invalid identifier: ${identifier}`); + } + } + switch (release) { + case "premajor": + this.prerelease.length = 0; + this.patch = 0; + this.minor = 0; + this.major++; + this.inc("pre", identifier, identifierBase); + break; + case "preminor": + this.prerelease.length = 0; + this.patch = 0; + this.minor++; + this.inc("pre", identifier, identifierBase); + break; + case "prepatch": + this.prerelease.length = 0; + this.inc("patch", identifier, identifierBase); + this.inc("pre", identifier, identifierBase); + break; + case "prerelease": + if (this.prerelease.length === 0) this.inc("patch", identifier, identifierBase); + this.inc("pre", identifier, identifierBase); + break; + case "release": + if (this.prerelease.length === 0) throw new Error(`version ${this.raw} is not a prerelease`); + this.prerelease.length = 0; + break; + case "major": + if (this.minor !== 0 || this.patch !== 0 || this.prerelease.length === 0) this.major++; + this.minor = 0; + this.patch = 0; + this.prerelease = []; + break; + case "minor": + if (this.patch !== 0 || this.prerelease.length === 0) this.minor++; + this.patch = 0; + this.prerelease = []; + break; + case "patch": + if (this.prerelease.length === 0) this.patch++; + this.prerelease = []; + break; + case "pre": { + const base = Number(identifierBase) ? 1 : 0; + if (this.prerelease.length === 0) this.prerelease = [base]; + else { + let i = this.prerelease.length; + while (--i >= 0) if (typeof this.prerelease[i] === "number") { + this.prerelease[i]++; + i = -2; + } + if (i === -1) { + if (identifier === this.prerelease.join(".") && identifierBase === false) throw new Error("invalid increment argument: identifier already exists"); + this.prerelease.push(base); + } + } + if (identifier) { + let prerelease = [identifier, base]; + if (identifierBase === false) prerelease = [identifier]; + if (isPrereleaseIdentifier(this.prerelease, identifier)) { + const prereleaseBase = this.prerelease[identifier.split(".").length]; + if (isNaN(prereleaseBase)) this.prerelease = prerelease; + } else this.prerelease = prerelease; + } + break; + } + default: throw new Error(`invalid increment argument: ${release}`); + } + this.raw = this.format(); + if (this.build.length) this.raw += `+${this.build.join(".")}`; + return this; + } + }; +})); +//#endregion +//#region node_modules/semver/functions/compare.js +var require_compare = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var SemVer = require_semver(); + var compare = (a, b, loose) => new SemVer(a, loose).compare(new SemVer(b, loose)); + module.exports = compare; +})); +//#endregion +//#region node_modules/semver/functions/eq.js +var require_eq = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var compare = require_compare(); + var eq = (a, b, loose) => compare(a, b, loose) === 0; + module.exports = eq; +})); +//#endregion +//#region node_modules/semver/functions/neq.js +var require_neq = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var compare = require_compare(); + var neq = (a, b, loose) => compare(a, b, loose) !== 0; + module.exports = neq; +})); +//#endregion +//#region node_modules/semver/functions/gt.js +var require_gt = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var compare = require_compare(); + var gt = (a, b, loose) => compare(a, b, loose) > 0; + module.exports = gt; +})); +//#endregion +//#region node_modules/semver/functions/gte.js +var require_gte = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var compare = require_compare(); + var gte = (a, b, loose) => compare(a, b, loose) >= 0; + module.exports = gte; +})); +//#endregion +//#region node_modules/semver/functions/lt.js +var require_lt = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var compare = require_compare(); + var lt = (a, b, loose) => compare(a, b, loose) < 0; + module.exports = lt; +})); +//#endregion +//#region node_modules/semver/functions/lte.js +var require_lte = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var compare = require_compare(); + var lte = (a, b, loose) => compare(a, b, loose) <= 0; + module.exports = lte; +})); +//#endregion +//#region node_modules/semver/functions/cmp.js +var require_cmp = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var eq = require_eq(); + var neq = require_neq(); + var gt = require_gt(); + var gte = require_gte(); + var lt = require_lt(); + var lte = require_lte(); + var cmp = (a, op, b, loose) => { + switch (op) { + case "===": + if (typeof a === "object") a = a.version; + if (typeof b === "object") b = b.version; + return a === b; + case "!==": + if (typeof a === "object") a = a.version; + if (typeof b === "object") b = b.version; + return a !== b; + case "": + case "=": + case "==": return eq(a, b, loose); + case "!=": return neq(a, b, loose); + case ">": return gt(a, b, loose); + case ">=": return gte(a, b, loose); + case "<": return lt(a, b, loose); + case "<=": return lte(a, b, loose); + default: throw new TypeError(`Invalid operator: ${op}`); + } + }; + module.exports = cmp; +})); +//#endregion +//#region node_modules/semver/classes/comparator.js +var require_comparator = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var ANY = Symbol("SemVer ANY"); + module.exports = class Comparator { + static get ANY() { + return ANY; + } + constructor(comp, options) { + options = parseOptions(options); + if (comp instanceof Comparator) if (comp.loose === !!options.loose) return comp; + else comp = comp.value; + comp = comp.trim().split(/\s+/).join(" "); + debug("comparator", comp, options); + this.options = options; + this.loose = !!options.loose; + this.parse(comp); + if (this.semver === ANY) this.value = ""; + else this.value = this.operator + this.semver.version; + debug("comp", this); + } + parse(comp) { + const r = this.options.loose ? re[t.COMPARATORLOOSE] : re[t.COMPARATOR]; + const m = comp.match(r); + if (!m) throw new TypeError(`Invalid comparator: ${comp}`); + this.operator = m[1] !== void 0 ? m[1] : ""; + if (this.operator === "=") this.operator = ""; + if (!m[2]) this.semver = ANY; + else this.semver = new SemVer(m[2], this.options.loose); + } + toString() { + return this.value; + } + test(version) { + debug("Comparator.test", version, this.options.loose); + if (this.semver === ANY || version === ANY) return true; + if (typeof version === "string") try { + version = new SemVer(version, this.options); + } catch (er) { + return false; + } + return cmp(version, this.operator, this.semver, this.options); + } + intersects(comp, options) { + if (!(comp instanceof Comparator)) throw new TypeError("a Comparator is required"); + if (this.operator === "") { + if (this.value === "") return true; + return new Range(comp.value, options).test(this.value); + } else if (comp.operator === "") { + if (comp.value === "") return true; + return new Range(this.value, options).test(comp.semver); + } + options = parseOptions(options); + if (options.includePrerelease && (this.value === "<0.0.0-0" || comp.value === "<0.0.0-0")) return false; + if (!options.includePrerelease && (this.value.startsWith("<0.0.0") || comp.value.startsWith("<0.0.0"))) return false; + if (this.operator.startsWith(">") && comp.operator.startsWith(">")) return true; + if (this.operator.startsWith("<") && comp.operator.startsWith("<")) return true; + if (this.semver.version === comp.semver.version && this.operator.includes("=") && comp.operator.includes("=")) return true; + if (cmp(this.semver, "<", comp.semver, options) && this.operator.startsWith(">") && comp.operator.startsWith("<")) return true; + if (cmp(this.semver, ">", comp.semver, options) && this.operator.startsWith("<") && comp.operator.startsWith(">")) return true; + return false; + } + }; + var parseOptions = require_parse_options(); + var { safeRe: re, t } = require_re(); + var cmp = require_cmp(); + var debug = require_debug(); + var SemVer = require_semver(); + var Range = require_range(); +})); +//#endregion +//#region node_modules/semver/classes/range.js +var require_range = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var SPACE_CHARACTERS = /\s+/g; + module.exports = class Range { + constructor(range, options) { + options = parseOptions(options); + if (range instanceof Range) if (range.loose === !!options.loose && range.includePrerelease === !!options.includePrerelease) return range; + else return new Range(range.raw, options); + if (range instanceof Comparator) { + this.raw = range.value; + this.set = [[range]]; + this.formatted = void 0; + return this; + } + this.options = options; + this.loose = !!options.loose; + this.includePrerelease = !!options.includePrerelease; + this.raw = range.trim().replace(SPACE_CHARACTERS, " "); + this.set = this.raw.split("||").map((r) => this.parseRange(r.trim())).filter((c) => c.length); + if (!this.set.length) throw new TypeError(`Invalid SemVer Range: ${this.raw}`); + if (this.set.length > 1) { + const first = this.set[0]; + this.set = this.set.filter((c) => !isNullSet(c[0])); + if (this.set.length === 0) this.set = [first]; + else if (this.set.length > 1) { + for (const c of this.set) if (c.length === 1 && isAny(c[0])) { + this.set = [c]; + break; + } + } + } + this.formatted = void 0; + } + get range() { + if (this.formatted === void 0) { + this.formatted = ""; + for (let i = 0; i < this.set.length; i++) { + if (i > 0) this.formatted += "||"; + const comps = this.set[i]; + for (let k = 0; k < comps.length; k++) { + if (k > 0) this.formatted += " "; + this.formatted += comps[k].toString().trim(); + } + } + } + return this.formatted; + } + format() { + return this.range; + } + toString() { + return this.range; + } + parseRange(range) { + range = range.replace(BUILDSTRIPRE, ""); + const memoKey = ((this.options.includePrerelease && FLAG_INCLUDE_PRERELEASE) | (this.options.loose && FLAG_LOOSE)) + ":" + range; + const cached = cache.get(memoKey); + if (cached) return cached; + const loose = this.options.loose; + const hr = loose ? re[t.HYPHENRANGELOOSE] : re[t.HYPHENRANGE]; + range = range.replace(hr, hyphenReplace(this.options.includePrerelease)); + debug("hyphen replace", range); + range = range.replace(re[t.COMPARATORTRIM], comparatorTrimReplace); + debug("comparator trim", range); + range = range.replace(re[t.TILDETRIM], tildeTrimReplace); + debug("tilde trim", range); + range = range.replace(re[t.CARETTRIM], caretTrimReplace); + debug("caret trim", range); + let rangeList = range.split(" ").map((comp) => parseComparator(comp, this.options)).join(" ").split(/\s+/).map((comp) => replaceGTE0(comp, this.options)); + if (loose) rangeList = rangeList.filter((comp) => { + debug("loose invalid filter", comp, this.options); + return !!comp.match(re[t.COMPARATORLOOSE]); + }); + debug("range list", rangeList); + const rangeMap = /* @__PURE__ */ new Map(); + const comparators = rangeList.map((comp) => new Comparator(comp, this.options)); + for (const comp of comparators) { + if (isNullSet(comp)) return [comp]; + rangeMap.set(comp.value, comp); + } + if (rangeMap.size > 1 && rangeMap.has("")) rangeMap.delete(""); + const result = [...rangeMap.values()]; + cache.set(memoKey, result); + return result; + } + intersects(range, options) { + if (!(range instanceof Range)) throw new TypeError("a Range is required"); + return this.set.some((thisComparators) => { + return isSatisfiable(thisComparators, options) && range.set.some((rangeComparators) => { + return isSatisfiable(rangeComparators, options) && thisComparators.every((thisComparator) => { + return rangeComparators.every((rangeComparator) => { + return thisComparator.intersects(rangeComparator, options); + }); + }); + }); + }); + } + test(version) { + if (!version) return false; + if (typeof version === "string") try { + version = new SemVer(version, this.options); + } catch (er) { + return false; + } + for (let i = 0; i < this.set.length; i++) if (testSet(this.set[i], version, this.options)) return true; + return false; + } + }; + var cache = new (require_lrucache())(); + var parseOptions = require_parse_options(); + var Comparator = require_comparator(); + var debug = require_debug(); + var SemVer = require_semver(); + var { safeRe: re, src, t, comparatorTrimReplace, tildeTrimReplace, caretTrimReplace } = require_re(); + var { FLAG_INCLUDE_PRERELEASE, FLAG_LOOSE } = require_constants(); + var BUILDSTRIPRE = new RegExp(src[t.BUILD], "g"); + var isNullSet = (c) => c.value === "<0.0.0-0"; + var isAny = (c) => c.value === ""; + var isSatisfiable = (comparators, options) => { + let result = true; + const remainingComparators = comparators.slice(); + let testComparator = remainingComparators.pop(); + while (result && remainingComparators.length) { + result = remainingComparators.every((otherComparator) => { + return testComparator.intersects(otherComparator, options); + }); + testComparator = remainingComparators.pop(); + } + return result; + }; + var parseComparator = (comp, options) => { + comp = comp.replace(re[t.BUILD], ""); + debug("comp", comp, options); + comp = replaceCarets(comp, options); + debug("caret", comp); + comp = replaceTildes(comp, options); + debug("tildes", comp); + comp = replaceXRanges(comp, options); + debug("xrange", comp); + comp = replaceStars(comp, options); + debug("stars", comp); + return comp; + }; + var isX = (id) => !id || id.toLowerCase() === "x" || id === "*"; + var invalidXRangeOrder = (M, m, p) => isX(M) && !isX(m) || isX(m) && p && !isX(p); + var replaceTildes = (comp, options) => { + return comp.trim().split(/\s+/).map((c) => replaceTilde(c, options)).join(" "); + }; + var replaceTilde = (comp, options) => { + const r = options.loose ? re[t.TILDELOOSE] : re[t.TILDE]; + const z = options.includePrerelease ? "-0" : ""; + return comp.replace(r, (_, M, m, p, pr) => { + debug("tilde", comp, _, M, m, p, pr); + let ret; + if (isX(M)) ret = ""; + else if (isX(m)) ret = `>=${M}.0.0${z} <${+M + 1}.0.0-0`; + else if (isX(p)) ret = `>=${M}.${m}.0${z} <${M}.${+m + 1}.0-0`; + else if (pr) { + debug("replaceTilde pr", pr); + ret = `>=${M}.${m}.${p}-${pr} <${M}.${+m + 1}.0-0`; + } else ret = `>=${M}.${m}.${p} <${M}.${+m + 1}.0-0`; + debug("tilde return", ret); + return ret; + }); + }; + var replaceCarets = (comp, options) => { + return comp.trim().split(/\s+/).map((c) => replaceCaret(c, options)).join(" "); + }; + var replaceCaret = (comp, options) => { + debug("caret", comp, options); + const r = options.loose ? re[t.CARETLOOSE] : re[t.CARET]; + const z = options.includePrerelease ? "-0" : ""; + return comp.replace(r, (_, M, m, p, pr) => { + debug("caret", comp, _, M, m, p, pr); + let ret; + if (isX(M)) ret = ""; + else if (isX(m)) ret = `>=${M}.0.0${z} <${+M + 1}.0.0-0`; + else if (isX(p)) if (M === "0") ret = `>=${M}.${m}.0${z} <${M}.${+m + 1}.0-0`; + else ret = `>=${M}.${m}.0${z} <${+M + 1}.0.0-0`; + else if (pr) { + debug("replaceCaret pr", pr); + if (M === "0") if (m === "0") ret = `>=${M}.${m}.${p}-${pr} <${M}.${m}.${+p + 1}-0`; + else ret = `>=${M}.${m}.${p}-${pr} <${M}.${+m + 1}.0-0`; + else ret = `>=${M}.${m}.${p}-${pr} <${+M + 1}.0.0-0`; + } else { + debug("no pr"); + if (M === "0") if (m === "0") ret = `>=${M}.${m}.${p} <${M}.${m}.${+p + 1}-0`; + else ret = `>=${M}.${m}.${p} <${M}.${+m + 1}.0-0`; + else ret = `>=${M}.${m}.${p} <${+M + 1}.0.0-0`; + } + debug("caret return", ret); + return ret; + }); + }; + var replaceXRanges = (comp, options) => { + debug("replaceXRanges", comp, options); + return comp.split(/\s+/).map((c) => replaceXRange(c, options)).join(" "); + }; + var replaceXRange = (comp, options) => { + comp = comp.trim(); + const r = options.loose ? re[t.XRANGELOOSE] : re[t.XRANGE]; + return comp.replace(r, (ret, gtlt, M, m, p, pr) => { + debug("xRange", comp, ret, gtlt, M, m, p, pr); + if (invalidXRangeOrder(M, m, p)) return comp; + const xM = isX(M); + const xm = xM || isX(m); + const xp = xm || isX(p); + const anyX = xp; + if (gtlt === "=" && anyX) gtlt = ""; + pr = options.includePrerelease ? "-0" : ""; + if (xM) if (gtlt === ">" || gtlt === "<") ret = "<0.0.0-0"; + else ret = "*"; + else if (gtlt && anyX) { + if (xm) m = 0; + p = 0; + if (gtlt === ">") { + gtlt = ">="; + if (xm) { + M = +M + 1; + m = 0; + p = 0; + } else { + m = +m + 1; + p = 0; + } + } else if (gtlt === "<=") { + gtlt = "<"; + if (xm) M = +M + 1; + else m = +m + 1; + } + if (gtlt === "<") pr = "-0"; + ret = `${gtlt + M}.${m}.${p}${pr}`; + } else if (xm) ret = `>=${M}.0.0${pr} <${+M + 1}.0.0-0`; + else if (xp) ret = `>=${M}.${m}.0${pr} <${M}.${+m + 1}.0-0`; + debug("xRange return", ret); + return ret; + }); + }; + var replaceStars = (comp, options) => { + debug("replaceStars", comp, options); + return comp.trim().replace(re[t.STAR], ""); + }; + var replaceGTE0 = (comp, options) => { + debug("replaceGTE0", comp, options); + return comp.trim().replace(re[options.includePrerelease ? t.GTE0PRE : t.GTE0], ""); + }; + var hyphenReplace = (incPr) => ($0, from, fM, fm, fp, fpr, fb, to, tM, tm, tp, tpr) => { + if (isX(fM)) from = ""; + else if (isX(fm)) from = `>=${fM}.0.0${incPr ? "-0" : ""}`; + else if (isX(fp)) from = `>=${fM}.${fm}.0${incPr ? "-0" : ""}`; + else if (fpr) from = `>=${from}`; + else from = `>=${from}${incPr ? "-0" : ""}`; + if (isX(tM)) to = ""; + else if (isX(tm)) to = `<${+tM + 1}.0.0-0`; + else if (isX(tp)) to = `<${tM}.${+tm + 1}.0-0`; + else if (tpr) to = `<=${tM}.${tm}.${tp}-${tpr}`; + else if (incPr) to = `<${tM}.${tm}.${+tp + 1}-0`; + else to = `<=${to}`; + return `${from} ${to}`.trim(); + }; + var testSet = (set, version, options) => { + for (let i = 0; i < set.length; i++) if (!set[i].test(version)) return false; + if (version.prerelease.length && !options.includePrerelease) { + for (let i = 0; i < set.length; i++) { + debug(set[i].semver); + if (set[i].semver === Comparator.ANY) continue; + if (set[i].semver.prerelease.length > 0) { + const allowed = set[i].semver; + if (allowed.major === version.major && allowed.minor === version.minor && allowed.patch === version.patch) return true; + } + } + return false; + } + return true; + }; +})); +//#endregion +//#region src/actions/drafter/config/parse-categories.ts +var import_valid = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => { + var Range = require_range(); + var validRange = (range, options) => { + try { + return new Range(range, options).range || "*"; + } catch (er) { + return null; + } + }; + module.exports = validRange; +})))(), 1); +var categoryMigrationDocumentationUrl = "https://github.com/release-drafter/release-drafter/pull/1558"; +var withMigrationDocumentationLink = (message) => `${message} Migration documentation: ${categoryMigrationDocumentationUrl}`; +var normalizeConventional = (conventional) => { + if (!conventional) return; + if (conventional === true) return { + types: [], + scopes: [], + breaking: void 0 + }; + if (Object.keys(conventional).length === 0) warning("Use 'conventional: true' instead of 'conventional: {}' to match any conventional title."); + return { + types: [...conventional.types || [], ...conventional.type ? [conventional.type] : []], + scopes: [...conventional.scopes || [], ...conventional.scope ? [conventional.scope] : []], + breaking: conventional.breaking + }; +}; +/** +* Parses all categories from the config, normalizing conditions and +* handling backward compatibility with deprecated fields. +* +* This function: +* - Normalizes a missing `type` to `changelog` to match schema defaults +* - Normalizes the `when` field to always be an array of conditions +* - Applies deprecated category-level `label`/`labels` shorthands to every +* normalized `when` condition +* - Warns when deprecated compatibility fields are used +* - Preserves all other category fields as-is +* +* Accepts both fully-typed and partial category objects for flexibility. +* +* @param categories - Categories from the raw config +* @returns Array of fully parsed categories with normalized conditions +*/ +function parseCategories(categories, deprecatedConfig) { + const parsedCategories = structuredClone(categories.categories).map((cat) => { + const { labels, label, when: _when, "collapse-after": rawCollapseAfter, "semver-increment": rawSemverIncrement, exclusive: rawExclusive, title, ..._cat } = cat; + const collapseAfter = rawCollapseAfter ?? categorySchemaDefaults["collapse-after"]; + const semverIncrement = rawSemverIncrement ?? categorySchemaDefaults["semver-increment"]; + const exclusive = rawExclusive ?? categorySchemaDefaults.exclusive; + const deprecatedLabels = [...labels || [], ...label ? [label] : []]; + if (deprecatedLabels.length > 0) warning(withMigrationDocumentationLink(`Use of deprecated 'categories[*].label' or 'categories[*].labels' field detected${title ? ` on category "${title}"` : ""}. Please migrate. This field will be removed in a future release. To migrate, move the labels into the category's 'when' condition.`)); + const parsedWhenConditions = (_when !== void 0 ? Array.isArray(_when) ? _when.length > 0 || deprecatedLabels.length === 0 ? _when : [{}] : [_when] : deprecatedLabels.length > 0 ? [{}] : []).map((condition) => { + const { path, label, conventional, ..._cond } = condition; + const normalizedConventional = normalizeConventional(conventional); + return { + ..._cond, + "labels-mode": condition["labels-mode"] ?? changeConditionSchemaDefaults["labels-mode"], + "paths-mode": condition["paths-mode"] ?? changeConditionSchemaDefaults["paths-mode"], + paths: [...condition.paths || [], ...path ? [path] : []], + labels: [ + ...deprecatedLabels, + ...condition.labels || [], + ...label ? [label] : [] + ], + ...normalizedConventional ? { conventional: normalizedConventional } : {} + }; + }).filter((condition) => condition.paths.length > 0 || condition.labels.length > 0 || !!condition.conventional); + const categoryType = _cat.type ?? categorySchemaDefaults.type; + switch (categoryType) { + case "changelog": return { + type: "changelog", + when: parsedWhenConditions, + "collapse-after": collapseAfter, + "semver-increment": semverIncrement, + exclusive, + title + }; + case "version-resolver": + if (title) warning(`Title "${title}" ignored for category of type "${categoryType}"`); + if (collapseAfter !== -1) warning(`"collapse-after" "${collapseAfter}" ignored for category of type "${categoryType}"`); + return { + type: "version-resolver", + when: parsedWhenConditions, + "semver-increment": semverIncrement, + exclusive + }; + case "pre-exclude": + case "pre-include": + if (title) warning(`Title "${title}" ignored for category of type "${categoryType}"`); + if (collapseAfter !== -1) warning(`"collapse-after" "${collapseAfter}" ignored for category of type "${categoryType}"`); + if (exclusive) throw new Error(`"exclusive" can only be set on categories of type "changelog" or "version-resolver"; it cannot be used on category of type "${categoryType}".`); + if (semverIncrement !== "patch") warning(`"semver-increment" "${semverIncrement}" ignored for category of type "${categoryType}"`); + return { + type: categoryType, + when: parsedWhenConditions + }; + default: throw new Error(`Unsupported category type: ${categoryType}`); + } + }); + if (deprecatedConfig["exclude-labels"] && deprecatedConfig["exclude-labels"].length > 0 || deprecatedConfig["exclude-paths"] && deprecatedConfig["exclude-paths"].length > 0) warning(withMigrationDocumentationLink(`Use of deprecated 'exclude-labels' or 'exclude-paths' field detected. Please migrate. This field will be removed in a future release. To migrate, add the correspoding labels or paths to a 'type: "pre-exclude"' category.`)); + if (deprecatedConfig["exclude-labels"] && deprecatedConfig["exclude-labels"].length > 0 || deprecatedConfig["exclude-paths"] && deprecatedConfig["exclude-paths"].length > 0) { + if (parsedCategories.findIndex((cat) => cat.type === "pre-exclude") !== -1) throw new Error("A 'pre-exclude' category already exists. Cannot migrate deprecated exclude-labels field. Please either remove the deprecated field or remove the existing 'pre-exclude' category to resolve this conflict."); + parsedCategories.push({ + type: "pre-exclude", + when: [{ + labels: deprecatedConfig["exclude-labels"] || [], + "labels-mode": "any", + paths: deprecatedConfig["exclude-paths"] || [], + "paths-mode": "any" + }] + }); + } + if (deprecatedConfig["include-labels"] && deprecatedConfig["include-labels"].length > 0 || deprecatedConfig["include-paths"] && deprecatedConfig["include-paths"].length > 0) { + warning(withMigrationDocumentationLink(`Use of deprecated 'include-labels' or 'include-paths' field detected. Please migrate. This field will be removed in a future release. To migrate, add the correspoding labels or paths to a 'type: "pre-include"' category.`)); + if (parsedCategories.findIndex((cat) => cat.type === "pre-include") !== -1) throw new Error("A 'pre-include' category already exists. Cannot migrate deprecated include-labels or include-paths fields. Please either remove the deprecated fields or remove the existing 'pre-include' category to resolve this conflict."); + parsedCategories.push({ + type: "pre-include", + when: [{ + labels: deprecatedConfig["include-labels"] || [], + "labels-mode": "any", + paths: deprecatedConfig["include-paths"] || [], + "paths-mode": "any" + }] + }); + } + if (deprecatedConfig["version-resolver"].default !== configSchemaDefaults["version-resolver"].default) { + warning(withMigrationDocumentationLink(`Use of deprecated 'version-resolver.default' field detected. Please migrate. This field will be removed in a future release. To migrate, either add 'semver-increment: "${deprecatedConfig["version-resolver"].default}"' to 'type: changelog' category with no 'when' condition (uncategorized changes), or move the default resolver to a new category with type 'version-resolver' and 'semver-increment' set to "${deprecatedConfig["version-resolver"].default}" - also without 'when' conditions.`)); + if (parsedCategories.findIndex((cat) => cat.type === "version-resolver" && cat.when.length === 0) !== -1) throw new Error("A 'version-resolver' category with no 'when' condition already exists. Cannot migrate deprecated 'version-resolver.default' field. Please either remove the deprecated field or remove the existing 'version-resolver' category to resolve this conflict."); + parsedCategories.push({ + type: "version-resolver", + "semver-increment": deprecatedConfig["version-resolver"].default, + when: [], + exclusive: false + }); + } + if (deprecatedConfig["version-resolver"].major.labels !== configSchemaDefaults["version-resolver"].major.labels && deprecatedConfig["version-resolver"].major.labels.length > 0) { + warning(withMigrationDocumentationLink(`Use of deprecated 'version-resolver.major.labels' field detected. Please migrate. This field will be removed in a future release. To migrate, either add 'semver-increment: "major"' to a pre-existing 'type: changelog' category, or move the labels from 'version-resolver.major.labels' to a new category with type 'version-resolver' and 'semver-increment' set to 'major'.`)); + parsedCategories.push({ + type: "version-resolver", + "semver-increment": "major", + when: [{ + labels: deprecatedConfig["version-resolver"].major.labels || [], + "labels-mode": "any", + paths: [], + "paths-mode": "any" + }], + exclusive: false + }); + } + if (deprecatedConfig["version-resolver"].minor.labels !== configSchemaDefaults["version-resolver"].minor.labels && deprecatedConfig["version-resolver"].minor.labels.length > 0) { + warning(withMigrationDocumentationLink(`Use of deprecated 'version-resolver.minor.labels' field detected. Please migrate. This field will be removed in a future release. To migrate, either add 'semver-increment: "minor"' to a pre-existing 'type: changelog' category, or move the labels from 'version-resolver.minor.labels' to a new category with type 'version-resolver' and 'semver-increment' set to 'minor'.`)); + parsedCategories.push({ + type: "version-resolver", + "semver-increment": "minor", + when: [{ + labels: deprecatedConfig["version-resolver"].minor.labels || [], + "labels-mode": "any", + paths: [], + "paths-mode": "any" + }], + exclusive: false + }); + } + if (deprecatedConfig["version-resolver"].patch.labels !== configSchemaDefaults["version-resolver"].patch.labels && deprecatedConfig["version-resolver"].patch.labels.length > 0) { + warning(withMigrationDocumentationLink(`Use of deprecated 'version-resolver.patch.labels' field detected. Please migrate. This field will be removed in a future release. To migrate, either add 'semver-increment: "patch"' to a pre-existing 'type: changelog' category, or move the labels from 'version-resolver.patch.labels' to a new category with type 'version-resolver' and 'semver-increment' set to 'patch'.`)); + parsedCategories.push({ + type: "version-resolver", + "semver-increment": "patch", + when: [{ + labels: deprecatedConfig["version-resolver"].patch.labels || [], + "labels-mode": "any", + paths: [], + "paths-mode": "any" + }], + exclusive: false + }); + } + return parsedCategories; +} +//#endregion +//#region src/actions/drafter/config/merge-input-and-config.ts +/** +* Returns a copy of `config`, updated with values from `input`. +* +* Also performs some validation. +* +* Input takes precedence, because it's more easy to change at runtime +*/ +var mergeInputAndConfig = (params) => { + const { config: originalConfig, input } = params; + const { "exclude-labels": excludeLabels, "include-labels": includeLabels, "include-paths": includePaths, "exclude-paths": excludePaths, "version-resolver": versionResolver, ...config } = structuredClone(originalConfig); + const deprecatedCategoryConfig = { + "exclude-labels": excludeLabels, + "include-labels": includeLabels, + "include-paths": includePaths, + "exclude-paths": excludePaths, + "version-resolver": versionResolver + }; + applyOverrides(config, input); + const { commitish, latest, prerelease } = getParsedDefaults(config); + const replacers = getTransformedReplacers(config); + const categories = getTransformedCategories(config, deprecatedCategoryConfig); + const parsedConfig = { + ...config, + commitish, + latest, + prerelease, + replacers, + categories + }; + validateParsedConfig(parsedConfig); + return parsedConfig; +}; +var applyOverrides = (config, input) => { + applyStringOverride(config, input, "commitish"); + applyStringOverride(config, input, "header"); + applyStringOverride(config, input, "footer"); + applyStringOverride(config, input, "prerelease-identifier"); + applyBooleanOverride(config, input, "prerelease"); + applyBooleanOverride(config, input, "include-pre-releases"); + applyBooleanOverride(config, input, "latest"); + applyStringOverride(config, input, "filter-by-range"); + applyReleaseModeOverrides(config, input); +}; +var applyReleaseModeOverrides = (config, input) => { + if (config.latest && config.prerelease) { + warning("'prerelease' and 'latest' cannot be both true. Switch 'latest' to false - release will be a pre-release."); + config.latest = false; + } + const hasInputPrerelease = typeof input.prerelease === "boolean"; + const hasInputPrereleaseIdentifier = !!input["prerelease-identifier"]; + if (config["prerelease-identifier"] && !config.prerelease && (!hasInputPrerelease || hasInputPrereleaseIdentifier)) { + warning(`You specified a 'prerelease-identifier' (${config["prerelease-identifier"]}), but 'prerelease' is set to false. Switching to true.`); + config.prerelease = true; + } +}; +var applyBooleanOverride = (config, input, key) => { + const inputValue = input[key]; + if (typeof inputValue !== "boolean") return; + const configValue = config[key]; + if (typeof configValue === "boolean" && configValue !== inputValue) info(`Input's ${key} "${inputValue}" overrides config's ${key} "${configValue}"`); + config[key] = inputValue; +}; +var applyStringOverride = (config, input, key) => { + const inputValue = input[key]; + if (!inputValue) return; + const configValue = config[key]; + if (configValue && configValue !== inputValue) info(`Input's ${key} "${inputValue}" overrides config's ${key} "${configValue}"`); + config[key] = inputValue; +}; +var getParsedDefaults = (config) => ({ + commitish: config.commitish || context.ref || context.payload.ref, + latest: typeof config.latest !== "boolean" ? true : config.latest, + prerelease: typeof config.prerelease !== "boolean" ? false : config.prerelease +}); +var getTransformedReplacers = (config) => config.replacers.map((r) => { + try { + return { + ...r, + search: stringToRegex(r.search) + }; + } catch { + warning(`Bad replacer regex: '${r.search}'`); + return false; + } +}).filter((r) => !!r); +var getTransformedCategories = (config, deprecatedCategoryConfig) => parseCategories(config, deprecatedCategoryConfig); +var validateParsedConfig = (parsedConfig) => { + if (!parsedConfig.commitish) throw new Error("'commitish' is required. Please set 'commitish' to a valid value. (defaults to the current ref, but it seems to be undefined in this context)"); + if (parsedConfig.categories.filter((category) => category.type === "changelog" && !category.title).length > 0) throw new Error("Every 'type: \"changelog\"' category must define a non-empty 'title'."); + if (parsedConfig.categories.filter((category) => category.type === "changelog" && category.when.length === 0).length > 1) throw new Error("Multiple 'type: \"changelog\"' categories detected with no 'when' condition. Only one such category is supported for uncategorized changes."); + if (parsedConfig["filter-by-range"] && !(0, import_valid.default)(parsedConfig["filter-by-range"])) throw new Error(`'filter-by-range' value "${parsedConfig["filter-by-range"]}" could not be parsed as a valid semver range.`); +}; +//#endregion +//#region src/actions/drafter/config/set-action-output.ts +var setActionOutput = (params) => { + const { releasePayload, upsertedRelease } = params; + info("Set action outputs..."); + const { resolvedVersion, majorVersion, minorVersion, patchVersion, body, name: releaseName, tag: releaseTagName } = releasePayload; + const outputName = upsertedRelease?.data.name ?? releaseName; + const outputTagName = upsertedRelease?.data.tag_name ?? releaseTagName; + if (upsertedRelease) { + const { data: { id: releaseId, html_url: htmlUrl, upload_url: uploadUrl } } = upsertedRelease; + if (releaseId && Number.isInteger(releaseId)) setOutput("id", releaseId.toString()); + if (htmlUrl) setOutput("html_url", htmlUrl); + if (uploadUrl) setOutput("upload_url", uploadUrl); + } + if (outputTagName) setOutput("tag_name", outputTagName); + if (outputName) setOutput("name", outputName); + if (resolvedVersion) setOutput("resolved_version", resolvedVersion); + if (majorVersion) setOutput("major_version", majorVersion); + if (minorVersion) setOutput("minor_version", minorVersion); + if (patchVersion) setOutput("patch_version", patchVersion); + setOutput("body", body); + info("Outputs set!"); +}; +//#endregion +//#region node_modules/conventional-commits-parser/dist/regex.js +var nomatchRegex = /(?!.*)/; +function escape(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} +function joinOr(parts) { + return parts.map((val) => typeof val === "string" ? escape(val.trim()) : val.source).filter(Boolean).join("|"); +} +function getNotesRegex(noteKeywords, notesPattern) { + if (!noteKeywords) return nomatchRegex; + const noteKeywordsSelection = joinOr(noteKeywords); + if (!notesPattern) return new RegExp(`^[\\s|*]*(${noteKeywordsSelection})[:\\s]+(.*)`, "i"); + return notesPattern(noteKeywordsSelection); +} +function getReferencePartsRegex(issuePrefixes, issuePrefixesCaseSensitive) { + if (!issuePrefixes) return nomatchRegex; + const flags = issuePrefixesCaseSensitive ? "g" : "gi"; + return new RegExp(`(?:.*?)??\\s*([\\w-\\.\\/]*?)??(${joinOr(issuePrefixes)})([\\w-]+)(?=\\s|$|[,;)\\]])`, flags); +} +function getReferencesRegex(referenceActions) { + if (!referenceActions) return /()(.+)/gi; + const joinedKeywords = joinOr(referenceActions); + return new RegExp(`(${joinedKeywords})(?:\\s+(.*?))(?=(?:${joinedKeywords})|$)`, "gi"); +} +/** +* Make the regexes used to parse a commit. +* @param options +* @returns Regexes. +*/ +function getParserRegexes(options = {}) { + return { + notes: getNotesRegex(options.noteKeywords, options.notesPattern), + referenceParts: getReferencePartsRegex(options.issuePrefixes, options.issuePrefixesCaseSensitive), + references: getReferencesRegex(options.referenceActions), + mentions: /@([\w-]+)/g, + url: /\b(?:https?):\/\/(?:www\.)?([-a-zA-Z0-9@:%_+.~#?&//=])+\b/ + }; +} +//#endregion +//#region node_modules/conventional-commits-parser/dist/utils.js +var SCISSOR = "------------------------ >8 ------------------------"; +/** +* Remove leading and trailing newlines. +* @param input +* @returns String without leading and trailing newlines. +*/ +function trimNewLines(input) { + const matches = input.match(/[^\r\n]/); + if (typeof matches?.index !== "number") return ""; + const firstIndex = matches.index; + let lastIndex = input.length - 1; + while (input[lastIndex] === "\r" || input[lastIndex] === "\n") lastIndex--; + return input.substring(firstIndex, lastIndex + 1); +} +/** +* Append a newline to a string. +* @param src +* @param line +* @returns String with appended newline. +*/ +function appendLine(src, line) { + return src ? `${src}\n${line || ""}` : line || ""; +} +/** +* Creates a function that filters out comments lines. +* @param char +* @returns Comment filter function. +*/ +function getCommentFilter(char) { + return char ? (line) => !line.startsWith(char) : () => true; +} +/** +* Select lines before the scissor. +* @param lines +* @param commentChar +* @returns Lines before the scissor. +*/ +function truncateToScissor(lines, commentChar) { + const scissorIndex = lines.indexOf(`${commentChar} ${SCISSOR}`); + if (scissorIndex === -1) return lines; + return lines.slice(0, scissorIndex); +} +/** +* Filter out GPG sign lines. +* @param line +* @returns True if the line is not a GPG sign line. +*/ +function gpgFilter(line) { + return !line.match(/^\s*gpg:/); +} +/** +* Assign matched correspondence to the target object. +* @param target - The target object to assign values to. +* @param matches - The RegExp match array containing the matched groups. +* @param correspondence - An array of keys that correspond to the matched groups. +* @returns The target object with assigned values. +*/ +function assignMatchedCorrespondence(target, matches, correspondence) { + const { groups } = matches; + for (let i = 0, len = correspondence.length, key; i < len; i++) { + key = correspondence[i]; + target[key] = (groups ? groups[key] : matches[i + 1]) || null; + } + return target; +} +//#endregion +//#region node_modules/conventional-commits-parser/dist/options.js +var defaultOptions = { + noteKeywords: ["BREAKING CHANGE", "BREAKING-CHANGE"], + issuePrefixes: ["#"], + referenceActions: [ + "close", + "closes", + "closed", + "fix", + "fixes", + "fixed", + "resolve", + "resolves", + "resolved" + ], + headerPattern: /^(\w*)(?:\(([\w$@.\-*/ ]*)\))?: (.*)$/, + headerCorrespondence: [ + "type", + "scope", + "subject" + ], + revertPattern: /^Revert\s"([\s\S]*)"\s*This reverts commit (\w*)\.?/, + revertCorrespondence: ["header", "hash"], + fieldPattern: /^-(.*?)-$/ +}; +//#endregion +//#region node_modules/conventional-commits-parser/dist/CommitParser.js +/** +* Helper to create commit object. +* @param initialData - Initial commit data. +* @returns Commit object with empty data. +*/ +function createCommitObject(initialData = {}) { + return { + merge: null, + revert: null, + header: null, + body: null, + footer: null, + notes: [], + mentions: [], + references: [], + ...initialData + }; +} +/** +* Commit message parser. +*/ +var CommitParser = class { + options; + regexes; + lines = []; + lineIndex = 0; + commit = createCommitObject(); + constructor(options = {}) { + this.options = { + ...defaultOptions, + ...options + }; + this.regexes = getParserRegexes(this.options); + } + currentLine() { + return this.lines[this.lineIndex]; + } + nextLine() { + return this.lines[this.lineIndex++]; + } + isLineAvailable() { + return this.lineIndex < this.lines.length; + } + parseReference(input, action) { + const { regexes } = this; + if (regexes.url.test(input)) return null; + const matches = regexes.referenceParts.exec(input); + if (!matches) return null; + let [raw, repository = null, prefix, issue] = matches; + let owner = null; + if (repository) { + const slashIndex = repository.indexOf("/"); + if (slashIndex !== -1) { + owner = repository.slice(0, slashIndex); + repository = repository.slice(slashIndex + 1); + } + } + return { + raw, + action, + owner, + repository, + prefix, + issue + }; + } + parseReferences(input) { + const { regexes } = this; + const regex = input.match(regexes.references) ? regexes.references : /()(.+)/gi; + const references = []; + let matches; + let action; + let sentence; + let reference; + while (true) { + matches = regex.exec(input); + if (!matches) break; + action = matches[1] || null; + sentence = matches[2] || ""; + while (true) { + reference = this.parseReference(sentence, action); + if (!reference) break; + references.push(reference); + } + } + return references; + } + skipEmptyLines() { + let line = this.currentLine(); + while (line !== void 0 && !line.trim()) { + this.nextLine(); + line = this.currentLine(); + } + } + parseMerge() { + const { commit, options } = this; + const correspondence = options.mergeCorrespondence || []; + const merge = this.currentLine(); + const matches = merge && options.mergePattern ? merge.match(options.mergePattern) : null; + if (matches) { + this.nextLine(); + commit.merge = matches[0] || null; + assignMatchedCorrespondence(commit, matches, correspondence); + return true; + } + return false; + } + parseHeader(isMergeCommit) { + if (isMergeCommit) this.skipEmptyLines(); + const { commit, options } = this; + const correspondence = options.headerCorrespondence || []; + const header = commit.header ?? this.nextLine(); + let matches = null; + if (header) { + if (options.breakingHeaderPattern) matches = header.match(options.breakingHeaderPattern); + if (!matches && options.headerPattern) matches = header.match(options.headerPattern); + } + if (header) commit.header = header; + if (matches) assignMatchedCorrespondence(commit, matches, correspondence); + } + parseMeta() { + const { options, commit } = this; + if (!options.fieldPattern || !this.isLineAvailable()) return false; + let matches; + let field = null; + let parsed = false; + while (this.isLineAvailable()) { + matches = this.currentLine().match(options.fieldPattern); + if (matches) { + field = matches[1] || null; + this.nextLine(); + continue; + } + if (field) { + parsed = true; + commit[field] = appendLine(commit[field], this.currentLine()); + this.nextLine(); + } else break; + } + return parsed; + } + parseNotes() { + const { regexes, commit } = this; + if (!this.isLineAvailable()) return false; + const matches = this.currentLine().match(regexes.notes); + let references = []; + if (matches) { + const note = { + title: matches[1], + text: matches[2] + }; + commit.notes.push(note); + commit.footer = appendLine(commit.footer, this.currentLine()); + this.nextLine(); + while (this.isLineAvailable()) { + if (this.parseMeta()) return true; + if (this.parseNotes()) return true; + references = this.parseReferences(this.currentLine()); + if (references.length) commit.references.push(...references); + else note.text = appendLine(note.text, this.currentLine()); + commit.footer = appendLine(commit.footer, this.currentLine()); + this.nextLine(); + if (references.length) break; + } + return true; + } + return false; + } + parseBodyAndFooter(isBody) { + const { commit } = this; + if (!this.isLineAvailable()) return isBody; + const references = this.parseReferences(this.currentLine()); + const isStillBody = !references.length && isBody; + if (isStillBody) commit.body = appendLine(commit.body, this.currentLine()); + else { + commit.references.push(...references); + commit.footer = appendLine(commit.footer, this.currentLine()); + } + this.nextLine(); + return isStillBody; + } + parseBreakingHeader() { + const { commit, options } = this; + if (!options.breakingHeaderPattern || commit.notes.length || !commit.header) return; + const matches = commit.header.match(options.breakingHeaderPattern); + if (matches) commit.notes.push({ + title: "BREAKING CHANGE", + text: matches[3] + }); + } + parseMentions(input) { + const { commit, regexes } = this; + let matches; + for (;;) { + matches = regexes.mentions.exec(input); + if (!matches) break; + commit.mentions.push(matches[1]); + } + } + parseRevert(input) { + const { commit, options } = this; + const correspondence = options.revertCorrespondence || []; + const matches = options.revertPattern ? input.match(options.revertPattern) : null; + if (matches) commit.revert = assignMatchedCorrespondence({}, matches, correspondence); + } + cleanupCommit() { + const { commit } = this; + if (commit.body) commit.body = trimNewLines(commit.body); + if (commit.footer) commit.footer = trimNewLines(commit.footer); + commit.notes.forEach((note) => { + note.text = trimNewLines(note.text); + }); + const referencesSet = /* @__PURE__ */ new Set(); + commit.references = commit.references.filter((reference) => { + const uid = `${reference.action} ${reference.raw}`.toLocaleLowerCase(); + const ok = !referencesSet.has(uid); + if (ok) referencesSet.add(uid); + return ok; + }); + } + /** + * Parse commit message string into an object. + * @param input - Commit message string. + * @returns Commit object. + */ + parse(input) { + if (!input.trim()) throw new TypeError("Expected a raw commit"); + const { commentChar } = this.options; + const commentFilter = getCommentFilter(commentChar); + const rawLines = trimNewLines(input).split(/\r?\n/); + const lines = commentChar ? truncateToScissor(rawLines, commentChar).filter((line) => commentFilter(line) && gpgFilter(line)) : rawLines.filter((line) => gpgFilter(line)); + const commit = createCommitObject(); + this.lines = lines; + this.lineIndex = 0; + this.commit = commit; + const isMergeCommit = this.parseMerge(); + this.parseHeader(isMergeCommit); + if (commit.header) commit.references = this.parseReferences(commit.header); + let isBody = true; + while (this.isLineAvailable()) { + this.parseMeta(); + if (this.parseNotes()) isBody = false; + if (!this.parseBodyAndFooter(isBody)) isBody = false; + } + this.parseBreakingHeader(); + this.parseMentions(input); + this.parseRevert(input); + this.cleanupCommit(); + return commit; + } +}; +//#endregion +//#region src/actions/drafter/common/category-matching.ts +var import_ignore = /* @__PURE__ */ __toESM(require_ignore(), 1); +var conventionalParser = new CommitParser({ + headerPattern: /^(\w*)(?:\((.*)\))?!?: (.*)$/, + breakingHeaderPattern: /^(\w*)(?:\((.*)\))?!: (.*)$/ +}); +var getPullRequestLabels = (pullRequest) => (pullRequest.labels?.nodes ?? []).filter((label) => Boolean(label?.name)).map((label) => label.name); +var unique = (values) => [...new Set(values)]; +var matchesValues = (actualValues, expectedValues, mode) => { + const actual = unique(actualValues); + const expected = unique(expectedValues); + if (expected.length === 0) return true; + switch (mode) { + case "all": return expected.every((value) => actual.includes(value)); + case "only": return actual.length > 0 && actual.every((value) => expected.includes(value)); + case "exactly": return actual.length === expected.length && actual.every((value) => expected.includes(value)); + default: return expected.length === 0 || expected.some((value) => actual.includes(value)); + } +}; +var matchesPullRequestPaths = (condition, pullRequest) => { + if (condition.paths.length === 0) return true; + const changedFiles = unique(pullRequest.changedFiles ?? []); + if (changedFiles.length === 0) return false; + const expectedMatchers = unique(condition.paths).map((path) => ({ + path, + matcher: (0, import_ignore.default)().add(path) + })); + const matchesAllConfiguredPaths = expectedMatchers.every(({ matcher }) => changedFiles.some((file) => matcher.ignores(file))); + const matchesOnlyConfiguredPaths = changedFiles.length > 0 && changedFiles.every((file) => expectedMatchers.some(({ matcher }) => matcher.ignores(file))); + switch (condition["paths-mode"]) { + case "all": return matchesAllConfiguredPaths; + case "only": return matchesOnlyConfiguredPaths; + case "exactly": return matchesAllConfiguredPaths && matchesOnlyConfiguredPaths; + default: return changedFiles.some((file) => expectedMatchers.some(({ matcher }) => matcher.ignores(file))); + } +}; +var parseConventionalTitle = (title) => { + if (!title) return void 0; + const parsed = conventionalParser.parse(title); + if (typeof parsed.type !== "string") return void 0; + return { + type: parsed.type, + scope: typeof parsed.scope === "string" ? parsed.scope : void 0, + breaking: parsed.notes.length > 0 + }; +}; +var matchesConventionalTitle = (condition, pullRequest) => { + if (!condition.conventional) return true; + const parsed = parseConventionalTitle(pullRequest.title); + if (!parsed) return false; + const { types, scopes, breaking } = condition.conventional; + return (types.length === 0 || types.includes(parsed.type)) && (scopes.length === 0 || parsed.scope !== void 0 && scopes.includes(parsed.scope)) && (breaking === void 0 || breaking === parsed.breaking); +}; +var matchesCategoryCondition = (condition, pullRequest) => matchesValues(getPullRequestLabels(pullRequest), condition.labels, condition["labels-mode"]) && matchesPullRequestPaths(condition, pullRequest) && matchesConventionalTitle(condition, pullRequest); +var matchesCategory = (category, pullRequest) => category.when.length === 0 || category.when.some((condition) => matchesCategoryCondition(condition, pullRequest)); +var filterPullRequestsByPreCategories = (pullRequests, categories) => { + const preIncludeCategories = categories.filter((category) => category.type === "pre-include"); + const preExcludeCategories = categories.filter((category) => category.type === "pre-exclude"); + return pullRequests.filter((pullRequest) => { + if (!(preIncludeCategories.length === 0 || preIncludeCategories.some((category) => matchesCategory(category, pullRequest)))) return false; + return !preExcludeCategories.some((category) => matchesCategory(category, pullRequest)); + }); +}; +/** +* Determines if any of the categories require loading pull request changed files. +*/ +var needsPullRequestChangedFiles = (categories) => categories.some((category) => category.when.some((condition) => condition.paths.length > 0)); +var getChangelogCategories = (categories) => categories.filter((category) => category.type === "changelog"); +var getVersionResolverCategories = (categories) => categories.filter((category) => category.type === "version-resolver"); +//#endregion +//#region src/actions/drafter/lib/build-release-payload/categorize-pull-requests.ts +var categorizePullRequests = (params) => { + const { pullRequests, config } = params; + const changelogCategories = getChangelogCategories(config.categories); + const uncategorizedPullRequests = []; + const categorizedPullRequests = changelogCategories.map((category) => { + return { + ...category, + pullRequests: [] + }; + }); + const uncategorizedCategoryIndex = changelogCategories.findIndex((category) => category.when.length === 0); + const filteredPullRequests = filterPullRequestsByPreCategories(pullRequests, config.categories); + for (const pullRequest of filteredPullRequests) { + let matchedAnyCategory = false; + for (const category of categorizedPullRequests) { + if (category.when.length === 0) continue; + if (matchesCategory(category, pullRequest)) { + category.pullRequests.push(pullRequest); + matchedAnyCategory = true; + if (category.exclusive) break; + } + } + if (!matchedAnyCategory) if (uncategorizedCategoryIndex === -1) uncategorizedPullRequests.push(pullRequest); + else categorizedPullRequests[uncategorizedCategoryIndex].pullRequests.push(pullRequest); + } + return [uncategorizedPullRequests, categorizedPullRequests]; +}; +//#endregion +//#region src/actions/drafter/lib/build-release-payload/render-template/util/charCode.ts +var CharCode = /* @__PURE__ */ function(CharCode) { + CharCode[CharCode["Backslash"] = 92] = "Backslash"; + CharCode[CharCode["Tab"] = 9] = "Tab"; + CharCode[CharCode["LineFeed"] = 10] = "LineFeed"; + CharCode[CharCode["CarriageReturn"] = 13] = "CarriageReturn"; + CharCode[CharCode["Space"] = 32] = "Space"; + CharCode[CharCode["Ampersand"] = 38] = "Ampersand"; + CharCode[CharCode["DollarSign"] = 36] = "DollarSign"; + CharCode[CharCode["Digit0"] = 48] = "Digit0"; + CharCode[CharCode["Digit1"] = 49] = "Digit1"; + CharCode[CharCode["Digit2"] = 50] = "Digit2"; + CharCode[CharCode["Digit3"] = 51] = "Digit3"; + CharCode[CharCode["Digit4"] = 52] = "Digit4"; + CharCode[CharCode["Digit5"] = 53] = "Digit5"; + CharCode[CharCode["Digit6"] = 54] = "Digit6"; + CharCode[CharCode["Digit7"] = 55] = "Digit7"; + CharCode[CharCode["Digit8"] = 56] = "Digit8"; + CharCode[CharCode["Digit9"] = 57] = "Digit9"; + CharCode[CharCode["A"] = 65] = "A"; + CharCode[CharCode["E"] = 69] = "E"; + CharCode[CharCode["L"] = 76] = "L"; + CharCode[CharCode["U"] = 85] = "U"; + CharCode[CharCode["a"] = 97] = "a"; + CharCode[CharCode["l"] = 108] = "l"; + CharCode[CharCode["n"] = 110] = "n"; + CharCode[CharCode["t"] = 116] = "t"; + CharCode[CharCode["u"] = 117] = "u"; + return CharCode; +}({}); +//#endregion +//#region src/actions/drafter/lib/build-release-payload/render-template/util/search.ts +function containsUppercaseCharacter(target) { + if (!target) return false; + return target.toLowerCase() !== target; +} +function buildReplaceStringWithCasePreserved(matches, pattern) { + if (matches && matches[0] !== "") { + const containsHyphens = validateSpecificSpecialCharacter(matches, pattern, "-"); + const containsUnderscores = validateSpecificSpecialCharacter(matches, pattern, "_"); + if (containsHyphens && !containsUnderscores) return buildReplaceStringForSpecificSpecialCharacter(matches, pattern, "-"); + else if (!containsHyphens && containsUnderscores) return buildReplaceStringForSpecificSpecialCharacter(matches, pattern, "_"); + if (matches[0].toUpperCase() === matches[0]) return pattern.toUpperCase(); + else if (matches[0].toLowerCase() === matches[0]) return pattern.toLowerCase(); + else if (containsUppercaseCharacter(matches[0][0]) && pattern.length > 0) return pattern[0].toUpperCase() + pattern.substring(1); + else if (matches[0][0].toUpperCase() !== matches[0][0] && pattern.length > 0) return pattern[0].toLowerCase() + pattern.substring(1); + else return pattern; + } else return pattern; +} +function validateSpecificSpecialCharacter(matches, pattern, specialCharacter) { + return matches[0].indexOf(specialCharacter) !== -1 && pattern.indexOf(specialCharacter) !== -1 && matches[0].split(specialCharacter).length === pattern.split(specialCharacter).length; +} +function buildReplaceStringForSpecificSpecialCharacter(matches, pattern, specialCharacter) { + const splitPatternAtSpecialCharacter = pattern.split(specialCharacter); + const splitMatchAtSpecialCharacter = matches[0].split(specialCharacter); + let replaceString = ""; + splitPatternAtSpecialCharacter.forEach((splitValue, index) => { + replaceString += buildReplaceStringWithCasePreserved([splitMatchAtSpecialCharacter[index]], splitValue) + specialCharacter; + }); + return replaceString.slice(0, -1); +} +//#endregion +//#region src/actions/drafter/lib/build-release-payload/render-template/util/replacePattern.ts +/** +* Assigned when the replace pattern is entirely static. +*/ +var StaticValueReplacePattern = class { + staticValue; + kind = 0; + constructor(staticValue) { + this.staticValue = staticValue; + } +}; +/** +* Assigned when the replace pattern has replacement patterns. +*/ +var DynamicPiecesReplacePattern = class { + pieces; + kind = 1; + constructor(pieces) { + this.pieces = pieces; + } +}; +var ReplacePattern = class ReplacePattern { + static fromStaticValue(value) { + return new ReplacePattern([ReplacePiece.staticValue(value)]); + } + _state; + get hasReplacementPatterns() { + return this._state.kind === 1; + } + constructor(pieces) { + if (!pieces || pieces.length === 0) this._state = new StaticValueReplacePattern(""); + else if (pieces.length === 1 && pieces[0].staticValue !== null) this._state = new StaticValueReplacePattern(pieces[0].staticValue); + else this._state = new DynamicPiecesReplacePattern(pieces); + } + buildReplaceString(matches, preserveCase) { + if (this._state.kind === 0) if (preserveCase) return buildReplaceStringWithCasePreserved(matches, this._state.staticValue); + else return this._state.staticValue; + let result = ""; + for (let i = 0, len = this._state.pieces.length; i < len; i++) { + const piece = this._state.pieces[i]; + if (piece.staticValue !== null) { + result += piece.staticValue; + continue; + } + let match = ReplacePattern._substitute(piece.matchIndex, matches); + if (piece.caseOps !== null && piece.caseOps.length > 0) { + const repl = []; + const lenOps = piece.caseOps.length; + let opIdx = 0; + for (let idx = 0, len = match.length; idx < len; idx++) { + if (opIdx >= lenOps) { + repl.push(match.slice(idx)); + break; + } + switch (piece.caseOps[opIdx]) { + case "U": + repl.push(match[idx].toUpperCase()); + break; + case "u": + repl.push(match[idx].toUpperCase()); + opIdx++; + break; + case "L": + repl.push(match[idx].toLowerCase()); + break; + case "l": + repl.push(match[idx].toLowerCase()); + opIdx++; + break; + case "E": + repl.push(match.slice(idx)); + idx = len; + break; + default: repl.push(match[idx]); + } + } + match = repl.join(""); + } + result += match; + } + return result; + } + static _substitute(matchIndex, matches) { + if (matches === null) return ""; + if (matchIndex === 0) return matches[0]; + let remainder = ""; + while (matchIndex > 0) { + if (matchIndex < matches.length) return (matches[matchIndex] || "") + remainder; + remainder = String(matchIndex % 10) + remainder; + matchIndex = Math.floor(matchIndex / 10); + } + return `$${remainder}`; + } +}; +/** +* A replace piece can either be a static string or an index to a specific match. +*/ +var ReplacePiece = class ReplacePiece { + static staticValue(value) { + return new ReplacePiece(value, -1, null); + } + static matchIndex(index) { + return new ReplacePiece(null, index, null); + } + static caseOps(index, caseOps) { + return new ReplacePiece(null, index, caseOps); + } + staticValue; + matchIndex; + caseOps; + constructor(staticValue, matchIndex, caseOps) { + this.staticValue = staticValue; + this.matchIndex = matchIndex; + if (!caseOps || caseOps.length === 0) this.caseOps = null; + else this.caseOps = caseOps.slice(0); + } +}; +var ReplacePieceBuilder = class { + _source; + _lastCharIndex; + _result; + _resultLen; + _currentStaticPiece; + constructor(source) { + this._source = source; + this._lastCharIndex = 0; + this._result = []; + this._resultLen = 0; + this._currentStaticPiece = ""; + } + emitUnchanged(toCharIndex) { + this._emitStatic(this._source.substring(this._lastCharIndex, toCharIndex)); + this._lastCharIndex = toCharIndex; + } + emitStatic(value, toCharIndex) { + this._emitStatic(value); + this._lastCharIndex = toCharIndex; + } + _emitStatic(value) { + if (value.length === 0) return; + this._currentStaticPiece += value; + } + emitMatchIndex(index, toCharIndex, caseOps) { + if (this._currentStaticPiece.length !== 0) { + this._result[this._resultLen++] = ReplacePiece.staticValue(this._currentStaticPiece); + this._currentStaticPiece = ""; + } + this._result[this._resultLen++] = ReplacePiece.caseOps(index, caseOps); + this._lastCharIndex = toCharIndex; + } + finalize() { + this.emitUnchanged(this._source.length); + if (this._currentStaticPiece.length !== 0) { + this._result[this._resultLen++] = ReplacePiece.staticValue(this._currentStaticPiece); + this._currentStaticPiece = ""; + } + return new ReplacePattern(this._result); + } +}; +/** +* \n => inserts a LF +* \t => inserts a TAB +* \\ => inserts a "\\". +* \u => upper-cases one character in a match. +* \U => upper-cases ALL remaining characters in a match. +* \l => lower-cases one character in a match. +* \L => lower-cases ALL remaining characters in a match. +* \E => ends a \U or \L case-change sequence. +* $$ => inserts a "$". +* $& and $0 => inserts the matched substring. +* $n => Where n is a non-negative integer lesser than 100, inserts the nth parenthesized submatch string +* everything else stays untouched +* +* Also see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_string_as_a_parameter +*/ +function parseReplaceString(replaceString) { + if (!replaceString || replaceString.length === 0) return new ReplacePattern(null); + const caseOps = []; + const result = new ReplacePieceBuilder(replaceString); + for (let i = 0, len = replaceString.length; i < len; i++) { + const chCode = replaceString.charCodeAt(i); + if (chCode === CharCode.Backslash) { + i++; + if (i >= len) break; + const nextChCode = replaceString.charCodeAt(i); + switch (nextChCode) { + case CharCode.Backslash: + result.emitUnchanged(i - 1); + result.emitStatic("\\", i + 1); + break; + case CharCode.n: + result.emitUnchanged(i - 1); + result.emitStatic("\n", i + 1); + break; + case CharCode.t: + result.emitUnchanged(i - 1); + result.emitStatic(" ", i + 1); + break; + case CharCode.u: + case CharCode.U: + case CharCode.l: + case CharCode.L: + case CharCode.E: + result.emitUnchanged(i - 1); + result.emitStatic("", i + 1); + caseOps.push(String.fromCharCode(nextChCode)); + break; + } + continue; + } + if (chCode === CharCode.DollarSign) { + i++; + if (i >= len) break; + const nextChCode = replaceString.charCodeAt(i); + if (nextChCode === CharCode.DollarSign) { + result.emitUnchanged(i - 1); + result.emitStatic("$", i + 1); + continue; + } + if (nextChCode === CharCode.Digit0 || nextChCode === CharCode.Ampersand) { + result.emitUnchanged(i - 1); + result.emitMatchIndex(0, i + 1, caseOps); + caseOps.length = 0; + continue; + } + if (CharCode.Digit1 <= nextChCode && nextChCode <= CharCode.Digit9) { + let matchIndex = nextChCode - CharCode.Digit0; + if (i + 1 < len) { + const nextNextChCode = replaceString.charCodeAt(i + 1); + if (CharCode.Digit0 <= nextNextChCode && nextNextChCode <= CharCode.Digit9) { + i++; + matchIndex = matchIndex * 10 + (nextNextChCode - CharCode.Digit0); + result.emitUnchanged(i - 2); + result.emitMatchIndex(matchIndex, i + 1, caseOps); + caseOps.length = 0; + continue; + } + } + result.emitUnchanged(i - 1); + result.emitMatchIndex(matchIndex, i + 1, caseOps); + caseOps.length = 0; + } + } + } + return result.finalize(); +} +//#endregion +//#region src/actions/drafter/lib/build-release-payload/render-template/render-template.ts +var getReplaceMatches = (args) => { + const lastArg = args[args.length - 1]; + const hasGroups = typeof lastArg === "object" && lastArg !== null; + const matchCount = args.length - (hasGroups ? 3 : 2); + return args.slice(0, matchCount); +}; +var applyReplacer = (input, replacer) => { + const replacePattern = parseReplaceString(replacer.replace); + return input.replace(replacer.search, (...args) => { + const matches = getReplaceMatches(args); + return replacePattern.buildReplaceString(matches); + }); +}; +/** +* replaces all uppercase dollar templates with their string representation from object +* if replacement is undefined in object the dollar template string is left untouched +*/ +var renderTemplate = (params) => { + const { template, object, replacers } = params; + let input = template.replace(/(\$[A-Z_]+)/g, (_, k) => { + let result; + const isValidKey = (key) => key in object && object[key] !== void 0 && object[key] !== null; + if (!isValidKey(k)) result = k; + else if (typeof object[k] === "object") { + const nested = object[k]; + result = renderTemplate({ + template: nested.template, + object: nested + }); + } else result = `${object[k]}`; + return result; + }); + if (replacers) for (const replacer of replacers) input = applyReplacer(input, replacer); + return input; +}; +//#endregion +//#region src/actions/drafter/lib/build-release-payload/generate-contributors-sentence.ts +var botSuffix = "[bot]"; +var pullRequestKey = (pullRequest) => `${pullRequest.baseRepository?.nameWithOwner}#${pullRequest.number}`; +var normalizeLogin = (login, isBot = false) => isBot && !login.endsWith(botSuffix) ? `${login}${botSuffix}` : login; +var renderAuthorMention = (contributor) => { + if ("name" in contributor) return contributor.name; + const botUrl = contributor.login.endsWith(botSuffix) ? contributor.botUrl ?? `${context.serverUrl.replace(/\/$/, "")}/apps/${contributor.login.slice(0, -5)}` : void 0; + if (botUrl) return `[@${contributor.login}](${botUrl})`; + return `@${contributor.login}`; +}; +var generateContributorsSentence = (params) => { + const { commits, pullRequests, config } = params; + return generateAuthorsSentence({ + commits, + pullRequests: filterPullRequestsByPreCategories(pullRequests, config.categories), + excludeContributors: config["exclude-contributors"], + noAuthorsTemplate: config["no-contributors-template"] + }); +}; +var generateAuthorsSentence = (params) => { + const { commits, pullRequests } = params; + const includedPullRequestKeys = new Set(pullRequests.map(pullRequestKey)); + const includedMergeCommitOids = new Set(pullRequests.flatMap((pullRequest) => "mergeCommit" in pullRequest && pullRequest.mergeCommit?.oid ? [pullRequest.mergeCommit.oid] : [])); + const contributors = /* @__PURE__ */ new Map(); + const pullRequestAuthorLogins = /* @__PURE__ */ new Set(); + for (const commit of commits) { + if (!includedMergeCommitOids.has(commit.oid) && !commit.associatedPullRequests?.nodes?.some((pullRequest) => pullRequest && includedPullRequestKeys.has(pullRequestKey(pullRequest)))) continue; + for (const author of commit.authors?.nodes ?? (commit.author ? [commit.author] : [])) if (author?.user) { + const login = normalizeLogin(author.user.login); + contributors.set(`login:${login}`, { login }); + } else if (author?.name) contributors.set(`name:${author.name}`, { name: author.name }); + } + for (const pullRequest of pullRequests) if (pullRequest.author) { + const isBot = pullRequest.author.__typename === "Bot"; + const login = normalizeLogin(pullRequest.author.login, isBot); + pullRequestAuthorLogins.add(login); + contributors.set(`login:${login}`, { + login, + botUrl: isBot ? pullRequest.author.url : void 0 + }); + } + const sortedContributors = [...contributors.values()].filter((contributor) => "name" in contributor || !(params.excludeContributors ?? []).some((excluded) => excluded === contributor.login || `${excluded}${botSuffix}` === contributor.login)).sort((a, b) => { + const aIsPullRequestAuthor = "login" in a && pullRequestAuthorLogins.has(a.login); + if (aIsPullRequestAuthor !== ("login" in b && pullRequestAuthorLogins.has(b.login))) return aIsPullRequestAuthor ? -1 : 1; + const aIsBot = "login" in a && (a.botUrl !== void 0 || a.login.endsWith(botSuffix)); + if (aIsBot !== ("login" in b && (b.botUrl !== void 0 || b.login.endsWith(botSuffix)))) return aIsBot ? 1 : -1; + const aName = "name" in a ? a.name : a.login; + const bName = "name" in b ? b.name : b.login; + return aName.localeCompare(bName); + }); + if (sortedContributors.length === 0) return params.noAuthorsTemplate ?? ""; + if (params.authorTemplate !== void 0) { + const authorTemplate = params.authorTemplate; + const authors = sortedContributors.map((contributor) => { + const author = "name" in contributor ? contributor.name : contributor.login; + return renderTemplate({ + template: authorTemplate, + object: { + $AUTHOR: author, + $AUTHOR_MENTION: renderAuthorMention(contributor) + } + }); + }); + const separator = params.authorsSeparator ?? ", "; + if (params.authorsFinalSeparator !== void 0 && authors.length > 1) return `${authors.slice(0, -1).join(separator)}${params.authorsFinalSeparator}${authors.at(-1)}`; + return authors.join(separator); + } + const mentions = sortedContributors.map(renderAuthorMention); + if (mentions.length > 1) return `${mentions.slice(0, -1).join(", ")} and ${mentions.slice(-1)}`; + return mentions[0]; +}; +var generateNewContributorsList = (params) => { + const { pullRequests, newContributorLogins, config } = params; + const firstPullRequestByLogin = /* @__PURE__ */ new Map(); + const includedPullRequestKeys = new Set(filterPullRequestsByPreCategories(pullRequests, config.categories).map(pullRequestKey)); + for (const pullRequest of pullRequests) { + if (!pullRequest.author || !newContributorLogins.has(pullRequest.author.login) || config["exclude-contributors"].includes(pullRequest.author.login)) continue; + const previous = firstPullRequestByLogin.get(pullRequest.author.login); + if (!previous || (pullRequest.mergedAt ?? "") < (previous.mergedAt ?? "")) firstPullRequestByLogin.set(pullRequest.author.login, pullRequest); + } + const entries = [...firstPullRequestByLogin.entries()].filter(([, pullRequest]) => includedPullRequestKeys.has(pullRequestKey(pullRequest))).sort(([, a], [, b]) => (a.mergedAt ?? "").localeCompare(b.mergedAt ?? "") || a.number - b.number); + if (entries.length === 0) return ""; + return entries.map(([login, pullRequest]) => renderTemplate({ + template: config["new-contributor-template"], + object: { + $AUTHOR: login, + $AUTHOR_MENTION: `@${login}`, + $AUTHOR_URL: pullRequest.author?.url, + $NUMBER: pullRequest.number, + $URL: pullRequest.url + } + })).join("\n"); +}; +//#endregion +//#region src/actions/drafter/lib/build-release-payload/pull-request-to-string.ts +var pullRequestToString = (params) => params.pullRequests.map((pullRequest) => { + let pullAuthor = "ghost"; + if (pullRequest.author) pullAuthor = pullRequest.author.__typename && pullRequest.author.__typename === "Bot" ? `[${pullRequest.author.login}[bot]](${pullRequest.author.url})` : pullRequest.author.login; + const authorTemplate = params.config["change-author-template"]; + return renderTemplate({ + template: params.config["change-template"], + object: { + $CATEGORY: params.category ?? "", + $TITLE: escapeTitle({ + title: pullRequest.title, + escapes: params.config["change-title-escapes"] + }), + $NUMBER: pullRequest.number.toString(), + $AUTHORS: generateAuthorsSentence({ + commits: params.commits, + pullRequests: [pullRequest], + noAuthorsTemplate: renderTemplate({ + template: authorTemplate, + object: { + $AUTHOR: "ghost", + $AUTHOR_MENTION: "@ghost" + } + }), + authorTemplate, + authorsSeparator: params.config["change-authors-separator"], + authorsFinalSeparator: params.config["change-authors-final-separator"] + }), + $AUTHOR: pullAuthor, + $AUTHOR_URL: pullRequest.author?.url ?? "", + $BODY: pullRequest.body, + $URL: pullRequest.url, + $BASE_REF_NAME: pullRequest.baseRefName, + $HEAD_REF_NAME: pullRequest.headRefName + } + }); +}).join("\n"); +var escapeTitle = (params) => params.title.replace(new RegExp(`[${escapeStringRegexp(params.escapes || "")}]|\`.*?\``, "g"), (match) => { + if (match.length > 1) return match; + if (match === "@" || match === "#") return `${match}`; + return `\\${match}`; +}); +//#endregion +//#region src/actions/drafter/lib/build-release-payload/generate-changelog.ts +var generateChangeLog = (params) => { + const { commits = [], pullRequests, config } = params; + const [uncategorizedPullRequests, categorizedPullRequests] = categorizePullRequests({ + pullRequests, + config + }); + if (categorizedPullRequests.reduce((sum, category) => sum + category.pullRequests.length, 0) + uncategorizedPullRequests.length === 0) return config["no-changes-template"]; + const changeLog = []; + if (uncategorizedPullRequests.length > 0) changeLog.push(pullRequestToString({ + commits, + pullRequests: uncategorizedPullRequests, + config + }), "\n\n"); + for (const [index, category] of categorizedPullRequests.entries()) { + if (category.pullRequests.length === 0) continue; + const categoryTitle = renderTemplate({ + template: config["category-template"], + object: { $TITLE: category.title } + }); + if (categoryTitle) changeLog.push(categoryTitle, "\n\n"); + const pullRequestString = pullRequestToString({ + category: category.title, + commits, + pullRequests: category.pullRequests, + config + }); + if (category["collapse-after"] !== -1 && category.pullRequests.length > category["collapse-after"]) changeLog.push("
", "\n", `${category.pullRequests.length} change${category.pullRequests.length > 1 ? "s" : ""}`, "\n\n", pullRequestString, "\n", "
"); + else changeLog.push(pullRequestString); + if (index + 1 !== categorizedPullRequests.length) changeLog.push("\n\n"); + } + return changeLog.join("").trim(); +}; +//#endregion +//#region node_modules/semver/functions/parse.js +var require_parse = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var SemVer = require_semver(); + var parse = (version, options, throwErrors = false) => { + if (version instanceof SemVer) return version; + try { + return new SemVer(version, options); + } catch (er) { + if (!throwErrors) return null; + throw er; + } + }; + module.exports = parse; +})); +//#endregion +//#region node_modules/semver/functions/coerce.js +var require_coerce = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var SemVer = require_semver(); + var parse = require_parse(); + var { safeRe: re, t } = require_re(); + var coerce = (version, options) => { + if (version instanceof SemVer) return version; + if (typeof version === "number") version = String(version); + if (typeof version !== "string") return null; + options = options || {}; + let match = null; + if (!options.rtl) match = version.match(options.includePrerelease ? re[t.COERCEFULL] : re[t.COERCE]); + else { + const coerceRtlRegex = options.includePrerelease ? re[t.COERCERTLFULL] : re[t.COERCERTL]; + let next; + while ((next = coerceRtlRegex.exec(version)) && (!match || match.index + match[0].length !== version.length)) { + if (!match || next.index + next[0].length !== match.index + match[0].length) match = next; + coerceRtlRegex.lastIndex = next.index + next[1].length + next[2].length; + } + coerceRtlRegex.lastIndex = -1; + } + if (match === null) return null; + const major = match[2]; + return parse(`${major}.${match[3] || "0"}.${match[4] || "0"}${options.includePrerelease && match[5] ? `-${match[5]}` : ""}${options.includePrerelease && match[6] ? `+${match[6]}` : ""}`, options); + }; + module.exports = coerce; +})); +//#endregion +//#region node_modules/semver/functions/inc.js +var require_inc = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var SemVer = require_semver(); + var inc = (version, release, options, identifier, identifierBase) => { + if (typeof options === "string") { + identifierBase = identifier; + identifier = options; + options = void 0; + } + try { + return new SemVer(version instanceof SemVer ? version.version : version, options).inc(release, identifier, identifierBase).version; + } catch (er) { + return null; + } + }; + module.exports = inc; +})); +//#endregion +//#region node_modules/semver/functions/major.js +var require_major = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var SemVer = require_semver(); + var major = (a, loose) => new SemVer(a, loose).major; + module.exports = major; +})); +//#endregion +//#region node_modules/semver/functions/minor.js +var require_minor = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var SemVer = require_semver(); + var minor = (a, loose) => new SemVer(a, loose).minor; + module.exports = minor; +})); +//#endregion +//#region node_modules/semver/functions/patch.js +var require_patch = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var SemVer = require_semver(); + var patch = (a, loose) => new SemVer(a, loose).patch; + module.exports = patch; +})); +//#endregion +//#region node_modules/semver/functions/prerelease.js +var require_prerelease = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var parse = require_parse(); + var prerelease = (version, options) => { + const parsed = parse(version, options); + return parsed && parsed.prerelease.length ? parsed.prerelease : null; + }; + module.exports = prerelease; +})); +//#endregion +//#region src/actions/drafter/lib/build-release-payload/version-descriptor.ts +var import_coerce = /* @__PURE__ */ __toESM(require_coerce(), 1); +var import_inc = /* @__PURE__ */ __toESM(require_inc(), 1); +var import_major = /* @__PURE__ */ __toESM(require_major(), 1); +var import_minor = /* @__PURE__ */ __toESM(require_minor(), 1); +var import_parse = /* @__PURE__ */ __toESM(require_parse(), 1); +var import_patch = /* @__PURE__ */ __toESM(require_patch(), 1); +var import_prerelease = /* @__PURE__ */ __toESM(require_prerelease(), 1); +var VersionDescriptor = class VersionDescriptor { + version = null; + major = null; + minor = null; + patch = null; + prerelease = null; + preReleaseIdentifier; + tagPrefix; + constructor(from, opt) { + this.preReleaseIdentifier = opt?.preReleaseIdentifier; + this.tagPrefix = opt?.tagPrefix; + this.version = this._coerce(from); + this.major = this.version ? (0, import_major.default)(this.version).toString() : null; + this.minor = this.version ? (0, import_minor.default)(this.version).toString() : null; + this.patch = this.version ? (0, import_patch.default)(this.version).toString() : null; + this.prerelease = this.version === null ? null : (0, import_prerelease.default)(this.version) ? `-${(0, import_prerelease.default)(this.version)?.join(".")}` : ""; + } + _coerce(from) { + if (from) { + const ver = typeof from === "object" ? this._isRelease(from) ? this._toSemver(this._stripTag(from.tag_name)) || this._toSemver(this._stripTag(from.name)) : this._toSemver(from) : this._toSemver(this._stripTag(from)); + if (!ver) { + warning(`Failed to parse version from input ${from}. Defaulting coerced version to null.`); + return null; + } + return ver; + } else { + debug(`Building version descriptor without version input. Defaulting coerced version to null.`); + return null; + } + } + _isRelease(input) { + return typeof input === "object" && input !== null && (typeof input?.tag_name === "string" || typeof input?.name === "string"); + } + _stripTag(input) { + return this.tagPrefix && input?.startsWith(this.tagPrefix) ? input.slice(this.tagPrefix.length) : input; + } + _toSemver(version) { + const result = (0, import_parse.default)(version); + if (result) return result; + return (0, import_coerce.default)(version); + } + /** + * Alters version in-place by incrementing it according to the specified release type (major, minor, patch, prerelease). + */ + incremented(increment) { + if (!this.version || increment === "no_increment") return this; + const _incrementedVersion = (0, import_inc.default)(this.version, increment, true, this.preReleaseIdentifier); + if (!_incrementedVersion) throw new Error(`Failed to increment version ${this.version} with increment ${increment}`); + const _incrementedSemver = this._toSemver(_incrementedVersion); + if (!_incrementedSemver) throw new Error(`Failed to parse version ${_incrementedVersion} after incrementing ${this.version} with increment ${increment}`); + return new VersionDescriptor(_incrementedSemver, { + tagPrefix: this.tagPrefix, + preReleaseIdentifier: this.preReleaseIdentifier + }); + } + rendered(template) { + return renderTemplate({ + template, + object: { + $MAJOR: this.major ?? void 0, + $MINOR: this.minor ?? void 0, + $PATCH: this.patch ?? void 0, + $PRERELEASE: this.prerelease ?? void 0 + } + }); + } +}; +//#endregion +//#region src/actions/drafter/lib/build-release-payload/get-version-info.ts +var getVersionInfo = (params) => { + const { lastRelease, config, input, versionKeyIncrement: _versionKeyIncrement } = params; + info(`Resolving version info based on:`); + info(` - last release: ${lastRelease?.tag_name || "none"}`); + info(` - version input: ${input.version || input.tag || input.name || "none"}`); + info(` - version key increment: ${_versionKeyIncrement}`); + let _localIncrement = structuredClone(_versionKeyIncrement); + info(`Coerce and parse versions from last release...`); + const versionFromLastRelease = new VersionDescriptor(lastRelease, { + tagPrefix: config["tag-prefix"], + preReleaseIdentifier: config["prerelease-identifier"] + }); + info(`Parsed version from last release: ${versionFromLastRelease.version?.format() || "none"}.`); + info(`Coerce and parse versions from input...`); + const versionFromInput = new VersionDescriptor(input.version || input.tag || input.name, { + tagPrefix: config["tag-prefix"], + preReleaseIdentifier: config["prerelease-identifier"] + }); + info(`Parsed version from input: ${versionFromInput.version?.format() || "none"}.`); + let referenceVersion; + if (versionFromInput.version) { + _localIncrement = "no_increment"; + referenceVersion = versionFromInput; + } else if (versionFromLastRelease.version) { + referenceVersion = versionFromLastRelease; + const incrementsToPrerelease = _localIncrement?.startsWith("pre"); + const lastReleaseIsPrerelease = referenceVersion?.prerelease?.length; + if (incrementsToPrerelease) { + if (lastReleaseIsPrerelease) { + if (_localIncrement !== "prerelease") { + info(`versionKeyIncrement is set to "${_localIncrement}", but the last release is already a prerelease (${referenceVersion.version?.format() || "none"}). The version will be incremented as a prerelease instead.`); + _localIncrement = "prerelease"; + } + } + } + } else referenceVersion = new VersionDescriptor("0.0.0", { + preReleaseIdentifier: config["prerelease-identifier"], + tagPrefix: config["tag-prefix"] + }); + return { + $NEXT_MAJOR_VERSION: referenceVersion.incremented("major").rendered(config["version-template"]), + $NEXT_MAJOR_VERSION_MAJOR: referenceVersion.incremented("major").major, + $NEXT_MAJOR_VERSION_MINOR: referenceVersion.incremented("major").minor, + $NEXT_MAJOR_VERSION_PATCH: referenceVersion.incremented("major").patch, + $NEXT_MINOR_VERSION: referenceVersion.incremented("minor").rendered(config["version-template"]), + $NEXT_MINOR_VERSION_MAJOR: referenceVersion.incremented("minor").major, + $NEXT_MINOR_VERSION_MINOR: referenceVersion.incremented("minor").minor, + $NEXT_MINOR_VERSION_PATCH: referenceVersion.incremented("minor").patch, + $NEXT_PATCH_VERSION: referenceVersion.incremented("patch").rendered(config["version-template"]), + $NEXT_PATCH_VERSION_MAJOR: referenceVersion.incremented("patch").major, + $NEXT_PATCH_VERSION_MINOR: referenceVersion.incremented("patch").minor, + $NEXT_PATCH_VERSION_PATCH: referenceVersion.incremented("patch").patch, + $NEXT_PRERELEASE_VERSION: referenceVersion.incremented("prerelease").rendered(config["version-template"]), + $NEXT_PRERELEASE_VERSION_PRERELEASE: referenceVersion.incremented("prerelease").prerelease, + $RESOLVED_VERSION: referenceVersion.incremented(_localIncrement).rendered(config["version-template"]), + $RESOLVED_VERSION_MAJOR: referenceVersion.incremented(_localIncrement).major, + $RESOLVED_VERSION_MINOR: referenceVersion.incremented(_localIncrement).minor, + $RESOLVED_VERSION_PATCH: referenceVersion.incremented(_localIncrement).patch, + $RESOLVED_VERSION_PRERELEASE: referenceVersion.incremented(_localIncrement).prerelease + }; +}; +//#endregion +//#region src/actions/drafter/lib/build-release-payload/render-release-name.ts +/** +* Renders the release name, +* based on the input and config. +*/ +var renderReleaseName = (params) => { + let name = structuredClone(params.inputName); + const { config, versionInfo } = params; + if (name === void 0) name = versionInfo ? renderTemplate({ + template: config["name-template"] || "", + object: versionInfo + }) : ""; + else if (versionInfo) name = renderTemplate({ + template: name, + object: versionInfo + }); + debug(`name: ${name}`); + return name; +}; +//#endregion +//#region src/actions/drafter/lib/build-release-payload/render-tag-name.ts +/** +* Renders the tag name for the release, +* based on the input and config. +*/ +var renderTagName = (params) => { + let tagName = structuredClone(params.inputTagName); + const { config, versionInfo } = params; + if (tagName === void 0) tagName = versionInfo ? renderTemplate({ + template: config["tag-template"] || "", + object: versionInfo + }) : ""; + else if (versionInfo) tagName = renderTemplate({ + template: tagName, + object: versionInfo + }); + debug(`tag: ${tagName}`); + return tagName; +}; +//#endregion +//#region src/actions/drafter/lib/build-release-payload/resolve-version-increment.ts +var priorityMap = { + patch: 1, + minor: 2, + major: 3 +}; +var getHighestPriority = (params) => { + const { pullRequests, categories, emptyWhenBehavior } = params; + const emptyWhenCategory = categories.find((category) => category.when.length === 0); + const matchedPullRequests = /* @__PURE__ */ new Set(); + let highestPriority; + let remainingPullRequests = [...pullRequests]; + for (const category of categories) { + if (category.when.length === 0) continue; + const matchingPullRequests = remainingPullRequests.filter((pullRequest) => matchesCategory(category, pullRequest)); + if (matchingPullRequests.length === 0) continue; + highestPriority = Math.max(highestPriority ?? 0, priorityMap[category["semver-increment"]]); + for (const pullRequest of matchingPullRequests) matchedPullRequests.add(pullRequest); + if (category.exclusive) { + const matchedPullRequestsSet = new Set(matchingPullRequests); + remainingPullRequests = remainingPullRequests.filter((pullRequest) => !matchedPullRequestsSet.has(pullRequest)); + } + } + if (!emptyWhenCategory) return highestPriority; + if (emptyWhenBehavior === "fallback") return highestPriority ?? priorityMap[emptyWhenCategory["semver-increment"]]; + if (!pullRequests.some((pullRequest) => !matchedPullRequests.has(pullRequest))) return highestPriority; + return Math.max(highestPriority ?? 0, priorityMap[emptyWhenCategory["semver-increment"]]); +}; +var resolveVersionKeyIncrement = (params) => { + const { pullRequests, config } = params; + const filteredPullRequests = filterPullRequestsByPreCategories(pullRequests, config.categories); + const changelogPriority = getHighestPriority({ + pullRequests: filteredPullRequests, + categories: getChangelogCategories(config.categories), + emptyWhenBehavior: "uncategorized" + }); + const versionResolverPriority = getHighestPriority({ + pullRequests: filteredPullRequests, + categories: getVersionResolverCategories(config.categories), + emptyWhenBehavior: "fallback" + }) ?? priorityMap.patch; + const resolvedPriority = Math.max(changelogPriority ?? 0, versionResolverPriority); + const versionKey = Object.entries(priorityMap).find(([, priority]) => priority === resolvedPriority)?.[0]; + debug(`versionKey: ${versionKey}`); + let versionKeyIncrement = versionKey; + if (config.prerelease && config["prerelease-identifier"]) versionKeyIncrement = `pre${versionKeyIncrement}`; + info(`Version increment: ${versionKeyIncrement}${!versionKey ? " (default)" : ""}`); + return versionKeyIncrement; +}; +//#endregion +//#region src/actions/drafter/lib/build-release-payload/sort-pull-requests.ts +var sortPullRequests = (params) => { + const { pullRequests, config: { "sort-by": sortBy, "sort-direction": sortDirection } } = params; + const getSortField = sortBy === "title" ? getTitle : getMergedAt; + const sort = sortDirection === "ascending" ? sortAscending : sortDescending; + return structuredClone(pullRequests).sort((a, b) => { + try { + return sort(getSortField(a), getSortField(b)); + } catch (error$1) { + warning(`Failed to sort pull-requests ${a.number} and ${b.number} by ${sortBy} in ${sortDirection} order. Returning unsorted.`); + error(error$1); + return 0; + } + }); +}; +var getTitle = (pr) => pr.title; +var getMergedAt = (pr) => pr.mergedAt; +var sortAscending = (a, b) => { + if (a == null && b == null) return 0; + if (a == null) return 1; + if (b == null) return -1; + if (a > b) return 1; + if (a < b) return -1; + return 0; +}; +var sortDescending = (a, b) => { + if (a == null && b == null) return 0; + if (a == null) return -1; + if (b == null) return 1; + if (a > b) return -1; + if (a < b) return 1; + return 0; +}; +//#endregion +//#region src/actions/drafter/lib/build-release-payload/static/last-not-found.md?raw +var last_not_found_default = "> [!WARNING]\n> Release Drafter could not find a previous **published release** for `$OWNER/$REPOSITORY`. This draft was created **without a comparison baseline**.\n\n> [!IMPORTANT]\n> Treat this draft as a manual starting point.\n> Review the proposed version, tag, and notes before publishing.\n\nIf you did not expect this to happen, [open an issue](https://github.com/release-drafter/release-drafter/issues/new?template=previous-published-release-not-found.yml).\n"; +//#endregion +//#region src/actions/drafter/lib/build-release-payload/build-release-payload.ts +/** +* Outputs the payload for creating or updating a release. +* +* Previously known as `generateReleaseInfo`. +*/ +var buildReleasePayload = async (params) => { + const { commits, config, input, lastRelease, newContributorLogins = /* @__PURE__ */ new Set(), pullRequests } = params; + info(`Building release payload and body...`); + const sortedPullRequests = sortPullRequests({ + pullRequests, + config + }); + let body = (config.header || "") + config.template + (!lastRelease ? `\n---\n${renderTemplate({ + template: last_not_found_default, + object: { + $OWNER: context.repo.owner, + $REPOSITORY: context.repo.repo + } + })}\n---\n` : "") + (config.footer || ""); + body = renderTemplate({ + template: body, + object: { + $PREVIOUS_TAG: lastRelease ? lastRelease.tag_name : "", + $CHANGES: generateChangeLog({ + commits, + pullRequests: sortedPullRequests, + config + }), + $CONTRIBUTORS: generateContributorsSentence({ + commits, + pullRequests: sortedPullRequests, + config + }), + $NEW_CONTRIBUTORS: generateNewContributorsList({ + pullRequests: sortedPullRequests, + newContributorLogins, + config + }), + $OWNER: context.repo.owner, + $REPOSITORY: context.repo.repo + }, + replacers: config.replacers + }); + const versionInfo = getVersionInfo({ + lastRelease, + config, + input, + versionKeyIncrement: resolveVersionKeyIncrement({ + pullRequests, + config + }) + }); + debug(`versionInfo: ${JSON.stringify(versionInfo, null, 2)}`); + if (versionInfo) body = renderTemplate({ + template: body, + object: versionInfo + }); + const res = { + name: renderReleaseName({ + inputName: input.name, + config, + versionInfo + }), + tag: renderTagName({ + inputTagName: input.tag, + config, + versionInfo + }), + body, + targetCommitish: await parseCommitishForRelease(config.commitish), + prerelease: config.prerelease, + make_latest: config.latest, + draft: !input.publish, + resolvedVersion: versionInfo?.$RESOLVED_VERSION, + majorVersion: versionInfo?.$RESOLVED_VERSION_MAJOR, + minorVersion: versionInfo?.$RESOLVED_VERSION_MINOR, + patchVersion: versionInfo?.$RESOLVED_VERSION_PATCH, + prereleaseVersion: versionInfo?.$RESOLVED_VERSION_PRERELEASE + }; + info(`Release payload built successfully`); + info(` name: ${res.name}`); + info(` tag: ${res.tag}`); + info(` body: ${res.body.length} characters long`); + info(` targetCommitish: ${res.targetCommitish}`); + info(` prerelease: ${res.prerelease}`); + info(` make_latest: ${res.make_latest}`); + info(` draft: ${res.draft}${!res.draft ? " (will be published !)" : ""}`); + info(` RESOLVED_VERSION: ${res.resolvedVersion}`); + info(` RESOLVED_VERSION_MAJOR: ${res.majorVersion}`); + info(` RESOLVED_VERSION_MINOR: ${res.minorVersion}`); + info(` RESOLVED_VERSION_PATCH: ${res.patchVersion}`); + info(` RESOLVED_VERSION_PRERELEASE: ${res.prereleaseVersion}`); + return res; +}; +//#endregion +//#region node_modules/semver/functions/satisfies.js +var require_satisfies = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var Range = require_range(); + var satisfies = (version, range, options) => { + try { + range = new Range(range, options); + } catch (er) { + return false; + } + return range.test(version); + }; + module.exports = satisfies; +})); +//#endregion +//#region node_modules/compare-versions/lib/esm/utils.js +var semver = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i; +var validateAndParse = (version) => { + if (typeof version !== "string") throw new TypeError("Invalid argument expected string"); + const match = version.match(semver); + if (!match) throw new Error(`Invalid argument not valid semver ('${version}' received)`); + match.shift(); + return match; +}; +var isWildcard = (s) => s === "*" || s === "x" || s === "X"; +var tryParse = (v) => { + const n = parseInt(v, 10); + return isNaN(n) ? v : n; +}; +var forceType = (a, b) => typeof a !== typeof b ? [String(a), String(b)] : [a, b]; +var compareStrings = (a, b) => { + if (isWildcard(a) || isWildcard(b)) return 0; + const [ap, bp] = forceType(tryParse(a), tryParse(b)); + if (ap > bp) return 1; + if (ap < bp) return -1; + return 0; +}; +var compareSegments = (a, b) => { + for (let i = 0; i < Math.max(a.length, b.length); i++) { + const r = compareStrings(a[i] || "0", b[i] || "0"); + if (r !== 0) return r; + } + return 0; +}; +//#endregion +//#region node_modules/compare-versions/lib/esm/compareVersions.js +/** +* Compare [semver](https://semver.org/) version strings to find greater, equal or lesser. +* This library supports the full semver specification, including comparing versions with different number of digits like `1.0.0`, `1.0`, `1`, and pre-release versions like `1.0.0-alpha`. +* @param v1 - First version to compare +* @param v2 - Second version to compare +* @returns Numeric value compatible with the [Array.sort(fn) interface](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Parameters). +*/ +var compareVersions = (v1, v2) => { + const n1 = validateAndParse(v1); + const n2 = validateAndParse(v2); + const p1 = n1.pop(); + const p2 = n2.pop(); + const r = compareSegments(n1, n2); + if (r !== 0) return r; + if (p1 && p2) return compareSegments(p1.split("."), p2.split(".")); + else if (p1 || p2) return p1 ? -1 : 1; + return 0; +}; +//#endregion +//#region src/actions/drafter/lib/find-previous-releases/sort-releases.ts +var import_satisfies = /* @__PURE__ */ __toESM(require_satisfies(), 1); +var sortReleases = (params) => { + const tagPrefixRexExp = params.tagPrefix ? new RegExp(`^${escapeStringRegexp(params.tagPrefix)}`) : void 0; + return params.releases.sort((r1, r2) => { + const tag_name_1 = tagPrefixRexExp ? r1.tag_name.replace(tagPrefixRexExp, "") : r1.tag_name; + const tag_name_2 = tagPrefixRexExp ? r2.tag_name.replace(tagPrefixRexExp, "") : r2.tag_name; + try { + return compareVersions(tag_name_1, tag_name_2); + } catch { + return new Date(r1.created_at).getTime() - new Date(r2.created_at).getTime(); + } + }); +}; +//#endregion +//#region src/actions/drafter/lib/find-previous-releases/find-previous-releases.ts +var RELEASE_COUNT_LIMIT = 1e3; +/** +* Lists every release and : +* - filters by commitish if specified +* - filters by tag-prefix if specified +* - filters out pre-releases unless specified +* - extracts the first draft releases (according to return-order of GitHub API) +* - get latest published release according to ./sort-releases.ts implementation +* +* Returns one of (or both) draft release and latest published release +* The last stable release is used to determine the range of commits to include in the changelog, +* and to resolve the next version number. +* +* The draft release is used to determine if we should create a new release or update the existing one. +*/ +var findPreviousReleases = async (params) => { + const { commitish, "filter-by-commitish": filterByCommitish, "tag-prefix": tagPrefix, prerelease: isPreRelease, "include-pre-releases": includePreReleases, "filter-by-range": filterByRange } = params; + const octokit = getOctokit(); + info("Fetching releases from GitHub..."); + let releaseCount = 0; + const releases = await octokit.paginate(octokit.rest.repos.listReleases, { + ...context.repo, + per_page: 100 + }, (response, done) => { + releaseCount += response.data.length; + if (releaseCount >= RELEASE_COUNT_LIMIT) done(); + return response.data; + }); + info(`Found ${releases.length} releases`); + const headRefRegex = /^refs\/heads\//; + const targetCommitishName = commitish.replace(headRefRegex, ""); + const commitishFilteredReleases = filterByCommitish ? releases.filter((r) => targetCommitishName === r.target_commitish.replace(headRefRegex, "")) : releases; + const semverRangeFilteredReleases = filterByRange && filterByRange !== "*" ? commitishFilteredReleases.filter((r) => { + const parsedRange = (0, import_valid.default)(filterByRange); + if (!parsedRange) return false; + const parsedVersion = (0, import_coerce.default)(r.tag_name, { loose: true })?.version; + if (!parsedVersion) { + warning(`Failed to coerce semver version for "${r.tag_name}" : will be excluded from releases considered for drafting.`); + return false; + } + const doesSatisfy = !!(0, import_satisfies.default)(parsedVersion, parsedRange, { loose: true }); + debug(`Range "${parsedRange}" ${doesSatisfy ? "satisfies" : "does not satisfy"} version "${parsedVersion}" `); + return doesSatisfy; + }) : commitishFilteredReleases; + const filteredReleases = tagPrefix ? semverRangeFilteredReleases.filter((r) => r.tag_name.startsWith(tagPrefix)) : semverRangeFilteredReleases; + let publishedReleases = filteredReleases.filter((r) => !r.draft); + let draftReleases = filteredReleases.filter((r) => r.draft); + publishedReleases = publishedReleases.filter((publishedRelease) => isPreRelease || includePreReleases ? publishedRelease.prerelease || !publishedRelease.prerelease : !publishedRelease.prerelease); + draftReleases = draftReleases.filter((draftRelease) => isPreRelease ? draftRelease.prerelease : !draftRelease.prerelease); + const draftRelease = draftReleases[0]; + const lastRelease = sortReleases({ + releases: publishedReleases, + tagPrefix + })?.at(-1); + if (draftRelease) { + if (draftReleases.length > 1) { + warning(`Multiple draft releases found : ${draftReleases.map((r) => r.tag_name).join(", ")}`); + warning(`Using the first one returned by GitHub API: ${draftRelease.tag_name}`); + } + info(`Draft release${isPreRelease ? " (which is a prerelease)" : ""}:`); + info(` tag_name: ${draftRelease.tag_name}`); + info(` name: ${draftRelease.name}`); + } else info(`No draft release found${isPreRelease ? " (among prerelease drafts)" : ""}`); + if (lastRelease) { + info(`Last release${isPreRelease ? " (including prerelease)" : ""}:`); + info(` tag_name: ${lastRelease.tag_name}`); + info(` name: ${lastRelease.name}`); + } else warning(`No published release found${isPreRelease ? " (including prerelease)" : ""}`); + return { + draftRelease, + lastRelease + }; +}; +//#endregion +//#region src/actions/drafter/lib/find-pull-requests/find-commits-in-comparison.ts +var findCommitsInComparison = async (params) => { + const data = await paginateGraphql(getOctokit().graphql, FindCommitsInComparisonDocument, params); + if (!data.repository?.ref?.compare) throw new Error("Query returned an unexpected result: ref or comparison not found"); + return (data.repository.ref.compare.commits.nodes || []).filter((commit) => commit != null); +}; +//#endregion +//#region src/actions/drafter/lib/find-pull-requests/find-recent-merged-pull-requests.ts +var RECENT_PR_LOOKBACK = 5; +var findRecentMergedPullRequests = async (params) => { + const octokit = getOctokit(); + const nameWithOwner = `${context.repo.owner}/${context.repo.repo}`; + const missingPRs = ((await executeGraphql(octokit.graphql, FindRecentMergedPullRequestsDocument, { + name: context.repo.repo, + owner: context.repo.owner, + baseRefName: params.baseRefName, + limit: RECENT_PR_LOOKBACK, + ...params.fieldFlags + })).repository?.pullRequests.nodes ?? []).filter((pr) => { + if (!pr?.mergeCommit?.oid) return false; + const prKey = `${nameWithOwner}#${pr.number}`; + return params.commitOids.has(pr.mergeCommit.oid) && !params.foundPrKeys.has(prKey); + }); + if (missingPRs.length === 0) return []; + info(`Found ${missingPRs.length} recently merged PR(s) missing from GraphQL index, recovering: ${missingPRs.map((pr) => `#${pr?.number}`).join(", ")}`); + return missingPRs.filter((pr) => pr != null); +}; +//#endregion +//#region src/actions/drafter/lib/find-pull-requests/find-pull-requests.ts +var findNewContributorLogins = async (pullRequests) => { + const firstMergedAtByLogin = /* @__PURE__ */ new Map(); + for (const pullRequest of pullRequests) { + if (pullRequest.author?.__typename !== "User" || !pullRequest.mergedAt) continue; + const previous = firstMergedAtByLogin.get(pullRequest.author.login); + if (!previous || pullRequest.mergedAt < previous) firstMergedAtByLogin.set(pullRequest.author.login, pullRequest.mergedAt); + } + const candidates = [...firstMergedAtByLogin]; + if (candidates.length === 0) return /* @__PURE__ */ new Set(); + const variables = Object.fromEntries(candidates.map(([login, mergedAt], index) => [`query${index}`, `repo:${context.repo.owner}/${context.repo.repo} is:pr is:merged author:${login} merged:<${mergedAt}`])); + const data = await getOctokit().graphql(`query findPreviousContributions(${candidates.map((_, index) => `$query${index}: String!`).join(", ")}) { + ${candidates.map((_, index) => `author${index}: search(query: $query${index}, type: ISSUE, first: 1) { issueCount }`).join("\n")} + }`, variables); + return new Set(candidates.flatMap(([login], index) => data[`author${index}`]?.issueCount === 0 ? [login] : [])); +}; +var findPullRequests = async (params) => { + const sharedComparisonParams = { + name: context.repo.repo, + owner: context.repo.owner, + headRef: params.config.commitish, + withPullRequestBody: params.config["change-template"].includes("$BODY"), + withPullRequestURL: params.config["change-template"].includes("$URL"), + withBaseRefName: params.config["change-template"].includes("$BASE_REF_NAME"), + withHeadRefName: params.config["change-template"].includes("$HEAD_REF_NAME"), + pullRequestLimit: params.config["pull-request-limit"], + historyLimit: params.config["history-limit"] + }; + if (!params.lastRelease?.tag_name) { + warning("A previous (published) release is required to find changes"); + return { + commits: [], + newContributorLogins: /* @__PURE__ */ new Set(), + pullRequests: [] + }; + } + info(`Finding commits between refs/tags/${params.lastRelease.tag_name} and ${params.config.commitish}...`); + const commits = await findCommitsInComparison({ + baseRef: `refs/tags/${params.lastRelease.tag_name}`, + ...sharedComparisonParams + }); + info(`Found ${commits.length} commits.`); + const pullRequestsByKey = new Map(commits.flatMap((commit) => commit.associatedPullRequests?.nodes ?? []).filter((pr) => pr != null).map((pr) => [`${pr.baseRepository?.nameWithOwner}#${pr.number}`, pr])); + const pullRequestsRaw = [...pullRequestsByKey.values()]; + const comparisonCommitOids = new Set(commits.flatMap((c) => c.oid ? [c.oid] : [])); + const { commitish } = params.config; + const isBranchRef = commitish.startsWith("refs/heads/"); + const isUnsupportedRef = commitish.startsWith("refs/tags/") || commitish.startsWith("refs/pull/"); + const recoveredPRs = comparisonCommitOids.size === 0 || isUnsupportedRef ? [] : await findRecentMergedPullRequests({ + baseRefName: isBranchRef ? commitish.replace(/^refs\/heads\//, "") : null, + commitOids: comparisonCommitOids, + foundPrKeys: new Set(pullRequestsByKey.keys()), + fieldFlags: { + withPullRequestBody: sharedComparisonParams.withPullRequestBody, + withPullRequestURL: sharedComparisonParams.withPullRequestURL, + withBaseRefName: sharedComparisonParams.withBaseRefName, + withHeadRefName: sharedComparisonParams.withHeadRefName + } + }); + const pullRequests = [...pullRequestsRaw, ...recoveredPRs].filter((pr) => pr.baseRepository?.nameWithOwner === `${context.repo.owner}/${context.repo.repo}` && pr.merged); + const shouldLoadPullRequestChangedFiles = needsPullRequestChangedFiles(params.config.categories); + const pullRequestChangedFiles = shouldLoadPullRequestChangedFiles ? await getPullRequestsChangedFiles({ + owner: context.repo.owner, + repo: context.repo.repo, + pullRequests + }) : /* @__PURE__ */ new Map(); + const newContributorLogins = [ + params.config.header, + params.config.template, + params.config.footer + ].some((template) => template?.includes("$NEW_CONTRIBUTORS")) ? await findNewContributorLogins(pullRequests) : /* @__PURE__ */ new Set(); + info(`Found ${pullRequests.length} merged pull requests targeting ${context.repo.owner}/${context.repo.repo}${pullRequests.length > 0 ? `: ${pullRequests.map((pr) => `#${pr.number}`).join(", ")}` : "."}`); + return { + commits, + newContributorLogins, + pullRequests: pullRequests.map((pullRequest) => shouldLoadPullRequestChangedFiles ? { + ...pullRequest, + changedFiles: pullRequestChangedFiles.get(`${pullRequest.baseRepository?.nameWithOwner}#${pullRequest.number}`) + } : pullRequest) + }; +}; +//#endregion +//#region src/actions/drafter/lib/upsert-release/create-release.ts +var createRelease = async (params) => { + const octokit = getOctokit(); + const { releasePayload } = params; + return octokit.rest.repos.createRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + target_commitish: releasePayload.targetCommitish, + name: releasePayload.name, + tag_name: releasePayload.tag, + body: releasePayload.body, + draft: releasePayload.draft, + prerelease: releasePayload.prerelease, + make_latest: releasePayload.prerelease ? "false" : releasePayload.make_latest.toString() + }); +}; +//#endregion +//#region src/actions/drafter/lib/upsert-release/update-release.ts +var updateRelease = async (params) => { + const octokit = getOctokit(); + const { draftRelease, releasePayload } = params; + const updateReleaseParameters = { + name: releasePayload.name || draftRelease.name || void 0, + tag_name: releasePayload.tag || draftRelease.tag_name, + target_commitish: releasePayload.targetCommitish + }; + if (!updateReleaseParameters.name) delete updateReleaseParameters.name; + if (!updateReleaseParameters.tag_name) delete updateReleaseParameters.tag_name; + if (!updateReleaseParameters.target_commitish) delete updateReleaseParameters.target_commitish; + return octokit.rest.repos.updateRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: draftRelease.id, + body: releasePayload.body, + draft: releasePayload.draft, + prerelease: releasePayload.prerelease, + make_latest: releasePayload.prerelease ? "false" : releasePayload.make_latest.toString(), + ...updateReleaseParameters + }); +}; +//#endregion +//#region src/actions/drafter/lib/upsert-release/upsert-release.ts +var upsertRelease = async (params) => { + const { draftRelease, releasePayload, dryRun } = params; + if (dryRun) { + if (!draftRelease) info(`[dry-run] Would create a new release with payload: ${JSON.stringify(releasePayload, null, 2)}`); + else info(`[dry-run] Would update existing release (id: ${draftRelease.id}) with payload: ${JSON.stringify(releasePayload, null, 2)}`); + return; + } + if (!draftRelease) { + info("Creating new release..."); + const res = await createRelease({ releasePayload }); + info("Release created!"); + return res; + } else { + info("Updating existing release..."); + const res = await updateRelease({ + draftRelease, + releasePayload + }); + info("Release updated!"); + return res; + } +}; +//#endregion +//#region src/actions/drafter/main.ts +var main = async (params) => { + /** + * 1. find previous releases - returns latest release + * 2. find commits since latest release, with their associated pull-requests + * 3. sort those pull-requests according to the desired config (for release-body) + * 4. generate release info + * 5. create a release (may be a draft) or update previous draft + * 6. set action outputs + */ + const { config, input } = params; + const isPullRequestMergeRef = /^refs\/pull\/\d+\/merge$/.test(config.commitish); + const effectiveInput = isPullRequestMergeRef ? { + ...input, + "dry-run": true, + publish: false + } : input; + if (isPullRequestMergeRef && !input["dry-run"]) warning(`${config.commitish} points to an ephemeral pull request merge commit; forcing dry-run mode and disabling publish. Set dry-run: true explicitly to suppress this warning.`); + const { draftRelease, lastRelease } = await findPreviousReleases(config); + const { commits, newContributorLogins, pullRequests } = await findPullRequests({ + lastRelease, + config + }); + const releasePayload = await buildReleasePayload({ + commits, + config, + input: effectiveInput, + lastRelease, + newContributorLogins, + pullRequests + }); + return { + upsertedRelease: await upsertRelease({ + draftRelease, + releasePayload, + dryRun: effectiveInput["dry-run"] + }), + releasePayload + }; +}; +//#endregion +//#region src/actions/drafter/runner.ts +/** +* The main function for the action. +* +* @returns Resolves when the action is complete. +*/ +async function run() { + try { + info("Parsing inputs and configuration..."); + const input = getActionInput(); + const { upsertedRelease, releasePayload } = await main({ + input, + config: mergeInputAndConfig({ + config: await getConfig(input["config-name"]), + input + }) + }); + setActionOutput({ + upsertedRelease, + releasePayload + }); + } catch (error) { + if (error instanceof Error) setFailed(error.message); + } +} +//#endregion +//#region src/actions/drafter/run.ts +/* node:coverage ignore file -- @preserve */ +/** +* The entrypoint for the action. This file simply imports and runs the action's +* main logic. +* +* Do not add any logic to this file; instead, add it to `runner.ts`. +* +* `runner.ts` is the entrypoint for tests and should contain all the action's +* main logic. +*/ +await run(); +//#endregion +export {}; diff --git a/dist/chunks/ignore.js b/dist/chunks/ignore.js new file mode 100644 index 0000000000..7d5e2ed7c1 --- /dev/null +++ b/dist/chunks/ignore.js @@ -0,0 +1,33347 @@ +import { createRequire } from "node:module"; +import * as os$1 from "node:os"; +import os, { EOL } from "node:os"; +import * as crypto from "node:crypto"; +import * as fs from "node:fs"; +import { constants, existsSync, promises, readFileSync } from "node:fs"; +import * as path$1 from "node:path"; +import * as http$1 from "node:http"; +import * as https$1 from "node:https"; +import * as events from "node:events"; +import { StringDecoder } from "node:string_decoder"; +import * as child from "node:child_process"; +import { setTimeout as setTimeout$1 } from "node:timers"; +import path, { basename, dirname, isAbsolute, join, normalize } from "node:path"; +import { existsSync as existsSync$1, readFileSync as readFileSync$1 } from "node:fs"; +import process$1 from "node:process"; +//#region \0rolldown/runtime.js +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports); +var __exportAll = (all, no_symbols) => { + let target = {}; + for (var name in all) __defProp(target, name, { + get: all[name], + enumerable: true + }); + if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" }); + return target; +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { + key = keys[i]; + if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { + get: ((k) => from[k]).bind(null, key), + enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable + }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { + value: mod, + enumerable: true +}) : target, mod)); +var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))(); +//#endregion +//#region node_modules/@actions/core/lib/utils.js +/** +* Sanitizes an input into a string so it can be passed into issueCommand safely +* @param input input to sanitize into a string +*/ +function toCommandValue(input) { + if (input === null || input === void 0) return ""; + else if (typeof input === "string" || input instanceof String) return input; + return JSON.stringify(input); +} +/** +* +* @param annotationProperties +* @returns The command properties to send with the actual annotation command +* See IssueCommandProperties: https://github.com/actions/runner/blob/main/src/Runner.Worker/ActionCommandManager.cs#L646 +*/ +function toCommandProperties(annotationProperties) { + if (!Object.keys(annotationProperties).length) return {}; + return { + title: annotationProperties.title, + file: annotationProperties.file, + line: annotationProperties.startLine, + endLine: annotationProperties.endLine, + col: annotationProperties.startColumn, + endColumn: annotationProperties.endColumn + }; +} +//#endregion +//#region node_modules/@actions/core/lib/command.js +/** +* Issues a command to the GitHub Actions runner +* +* @param command - The command name to issue +* @param properties - Additional properties for the command (key-value pairs) +* @param message - The message to include with the command +* @remarks +* This function outputs a specially formatted string to stdout that the Actions +* runner interprets as a command. These commands can control workflow behavior, +* set outputs, create annotations, mask values, and more. +* +* Command Format: +* ::name key=value,key=value::message +* +* @example +* ```typescript +* // Issue a warning annotation +* issueCommand('warning', {}, 'This is a warning message'); +* // Output: ::warning::This is a warning message +* +* // Set an environment variable +* issueCommand('set-env', { name: 'MY_VAR' }, 'some value'); +* // Output: ::set-env name=MY_VAR::some value +* +* // Add a secret mask +* issueCommand('add-mask', {}, 'secretValue123'); +* // Output: ::add-mask::secretValue123 +* ``` +* +* @internal +* This is an internal utility function that powers the public API functions +* such as setSecret, warning, error, and exportVariable. +*/ +function issueCommand(command, properties, message) { + const cmd = new Command(command, properties, message); + process.stdout.write(cmd.toString() + os$1.EOL); +} +function issue$1(name, message = "") { + issueCommand(name, {}, message); +} +var CMD_STRING = "::"; +var Command = class { + constructor(command, properties, message) { + if (!command) command = "missing.command"; + this.command = command; + this.properties = properties; + this.message = message; + } + toString() { + let cmdStr = CMD_STRING + this.command; + if (this.properties && Object.keys(this.properties).length > 0) { + cmdStr += " "; + let first = true; + for (const key in this.properties) if (this.properties.hasOwnProperty(key)) { + const val = this.properties[key]; + if (val) { + if (first) first = false; + else cmdStr += ","; + cmdStr += `${key}=${escapeProperty(val)}`; + } + } + } + cmdStr += `${CMD_STRING}${escapeData(this.message)}`; + return cmdStr; + } +}; +function escapeData(s) { + return toCommandValue(s).replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A"); +} +function escapeProperty(s) { + return toCommandValue(s).replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A").replace(/:/g, "%3A").replace(/,/g, "%2C"); +} +//#endregion +//#region node_modules/@actions/core/lib/file-command.js +function issueFileCommand(command, message) { + const filePath = process.env[`GITHUB_${command}`]; + if (!filePath) throw new Error(`Unable to find environment variable for file command ${command}`); + if (!fs.existsSync(filePath)) throw new Error(`Missing file at path: ${filePath}`); + fs.appendFileSync(filePath, `${toCommandValue(message)}${os$1.EOL}`, { encoding: "utf8" }); +} +function prepareKeyValueMessage(key, value) { + const delimiter = `ghadelimiter_${crypto.randomUUID()}`; + const convertedValue = toCommandValue(value); + if (key.includes(delimiter)) throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`); + if (convertedValue.includes(delimiter)) throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`); + return `${key}<<${delimiter}${os$1.EOL}${convertedValue}${os$1.EOL}${delimiter}`; +} +//#endregion +//#region node_modules/@actions/http-client/lib/proxy.js +function getProxyUrl(reqUrl) { + const usingSsl = reqUrl.protocol === "https:"; + if (checkBypass(reqUrl)) return; + const proxyVar = (() => { + if (usingSsl) return process.env["https_proxy"] || process.env["HTTPS_PROXY"]; + else return process.env["http_proxy"] || process.env["HTTP_PROXY"]; + })(); + if (proxyVar) try { + return new DecodedURL(proxyVar); + } catch (_a) { + if (!proxyVar.startsWith("http://") && !proxyVar.startsWith("https://")) return new DecodedURL(`http://${proxyVar}`); + } + else return; +} +function checkBypass(reqUrl) { + if (!reqUrl.hostname) return false; + const reqHost = reqUrl.hostname; + if (isLoopbackAddress(reqHost)) return true; + const noProxy = process.env["no_proxy"] || process.env["NO_PROXY"] || ""; + if (!noProxy) return false; + let reqPort; + if (reqUrl.port) reqPort = Number(reqUrl.port); + else if (reqUrl.protocol === "http:") reqPort = 80; + else if (reqUrl.protocol === "https:") reqPort = 443; + const upperReqHosts = [reqUrl.hostname.toUpperCase()]; + if (typeof reqPort === "number") upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`); + for (const upperNoProxyItem of noProxy.split(",").map((x) => x.trim().toUpperCase()).filter((x) => x)) if (upperNoProxyItem === "*" || upperReqHosts.some((x) => x === upperNoProxyItem || x.endsWith(`.${upperNoProxyItem}`) || upperNoProxyItem.startsWith(".") && x.endsWith(`${upperNoProxyItem}`))) return true; + return false; +} +function isLoopbackAddress(host) { + const hostLower = host.toLowerCase(); + return hostLower === "localhost" || hostLower.startsWith("127.") || hostLower.startsWith("[::1]") || hostLower.startsWith("[0:0:0:0:0:0:0:1]"); +} +var DecodedURL = class extends URL { + constructor(url, base) { + super(url, base); + this._decodedUsername = decodeURIComponent(super.username); + this._decodedPassword = decodeURIComponent(super.password); + } + get username() { + return this._decodedUsername; + } + get password() { + return this._decodedPassword; + } +}; +//#endregion +//#region node_modules/tunnel/lib/tunnel.js +var require_tunnel$1 = /* @__PURE__ */ __commonJSMin(((exports) => { + __require("node:net"); + var tls = __require("node:tls"); + var http$3 = __require("node:http"); + var https$2 = __require("node:https"); + var events$1 = __require("node:events"); + __require("node:assert"); + var util$2 = __require("node:util"); + exports.httpOverHttp = httpOverHttp; + exports.httpsOverHttp = httpsOverHttp; + exports.httpOverHttps = httpOverHttps; + exports.httpsOverHttps = httpsOverHttps; + function httpOverHttp(options) { + var agent = new TunnelingAgent(options); + agent.request = http$3.request; + return agent; + } + function httpsOverHttp(options) { + var agent = new TunnelingAgent(options); + agent.request = http$3.request; + agent.createSocket = createSecureSocket; + agent.defaultPort = 443; + return agent; + } + function httpOverHttps(options) { + var agent = new TunnelingAgent(options); + agent.request = https$2.request; + return agent; + } + function httpsOverHttps(options) { + var agent = new TunnelingAgent(options); + agent.request = https$2.request; + agent.createSocket = createSecureSocket; + agent.defaultPort = 443; + return agent; + } + function TunnelingAgent(options) { + var self = this; + self.options = options || {}; + self.proxyOptions = self.options.proxy || {}; + self.maxSockets = self.options.maxSockets || http$3.Agent.defaultMaxSockets; + self.requests = []; + self.sockets = []; + self.on("free", function onFree(socket, host, port, localAddress) { + var options = toOptions(host, port, localAddress); + for (var i = 0, len = self.requests.length; i < len; ++i) { + var pending = self.requests[i]; + if (pending.host === options.host && pending.port === options.port) { + self.requests.splice(i, 1); + pending.request.onSocket(socket); + return; + } + } + socket.destroy(); + self.removeSocket(socket); + }); + } + util$2.inherits(TunnelingAgent, events$1.EventEmitter); + TunnelingAgent.prototype.addRequest = function addRequest(req, host, port, localAddress) { + var self = this; + var options = mergeOptions({ request: req }, self.options, toOptions(host, port, localAddress)); + if (self.sockets.length >= this.maxSockets) { + self.requests.push(options); + return; + } + self.createSocket(options, function(socket) { + socket.on("free", onFree); + socket.on("close", onCloseOrRemove); + socket.on("agentRemove", onCloseOrRemove); + req.onSocket(socket); + function onFree() { + self.emit("free", socket, options); + } + function onCloseOrRemove(err) { + self.removeSocket(socket); + socket.removeListener("free", onFree); + socket.removeListener("close", onCloseOrRemove); + socket.removeListener("agentRemove", onCloseOrRemove); + } + }); + }; + TunnelingAgent.prototype.createSocket = function createSocket(options, cb) { + var self = this; + var placeholder = {}; + self.sockets.push(placeholder); + var connectOptions = mergeOptions({}, self.proxyOptions, { + method: "CONNECT", + path: options.host + ":" + options.port, + agent: false, + headers: { host: options.host + ":" + options.port } + }); + if (options.localAddress) connectOptions.localAddress = options.localAddress; + if (connectOptions.proxyAuth) { + connectOptions.headers = connectOptions.headers || {}; + connectOptions.headers["Proxy-Authorization"] = "Basic " + new Buffer(connectOptions.proxyAuth).toString("base64"); + } + debug("making CONNECT request"); + var connectReq = self.request(connectOptions); + connectReq.useChunkedEncodingByDefault = false; + connectReq.once("response", onResponse); + connectReq.once("upgrade", onUpgrade); + connectReq.once("connect", onConnect); + connectReq.once("error", onError); + connectReq.end(); + function onResponse(res) { + res.upgrade = true; + } + function onUpgrade(res, socket, head) { + process.nextTick(function() { + onConnect(res, socket, head); + }); + } + function onConnect(res, socket, head) { + connectReq.removeAllListeners(); + socket.removeAllListeners(); + if (res.statusCode !== 200) { + debug("tunneling socket could not be established, statusCode=%d", res.statusCode); + socket.destroy(); + var error = /* @__PURE__ */ new Error("tunneling socket could not be established, statusCode=" + res.statusCode); + error.code = "ECONNRESET"; + options.request.emit("error", error); + self.removeSocket(placeholder); + return; + } + if (head.length > 0) { + debug("got illegal response body from proxy"); + socket.destroy(); + var error = /* @__PURE__ */ new Error("got illegal response body from proxy"); + error.code = "ECONNRESET"; + options.request.emit("error", error); + self.removeSocket(placeholder); + return; + } + debug("tunneling connection has established"); + self.sockets[self.sockets.indexOf(placeholder)] = socket; + return cb(socket); + } + function onError(cause) { + connectReq.removeAllListeners(); + debug("tunneling socket could not be established, cause=%s\n", cause.message, cause.stack); + var error = /* @__PURE__ */ new Error("tunneling socket could not be established, cause=" + cause.message); + error.code = "ECONNRESET"; + options.request.emit("error", error); + self.removeSocket(placeholder); + } + }; + TunnelingAgent.prototype.removeSocket = function removeSocket(socket) { + var pos = this.sockets.indexOf(socket); + if (pos === -1) return; + this.sockets.splice(pos, 1); + var pending = this.requests.shift(); + if (pending) this.createSocket(pending, function(socket) { + pending.request.onSocket(socket); + }); + }; + function createSecureSocket(options, cb) { + var self = this; + TunnelingAgent.prototype.createSocket.call(self, options, function(socket) { + var hostHeader = options.request.getHeader("host"); + var tlsOptions = mergeOptions({}, self.options, { + socket, + servername: hostHeader ? hostHeader.replace(/:.*$/, "") : options.host + }); + var secureSocket = tls.connect(0, tlsOptions); + self.sockets[self.sockets.indexOf(socket)] = secureSocket; + cb(secureSocket); + }); + } + function toOptions(host, port, localAddress) { + if (typeof host === "string") return { + host, + port, + localAddress + }; + return host; + } + function mergeOptions(target) { + for (var i = 1, len = arguments.length; i < len; ++i) { + var overrides = arguments[i]; + if (typeof overrides === "object") { + var keys = Object.keys(overrides); + for (var j = 0, keyLen = keys.length; j < keyLen; ++j) { + var k = keys[j]; + if (overrides[k] !== void 0) target[k] = overrides[k]; + } + } + } + return target; + } + var debug; + if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) debug = function() { + var args = Array.prototype.slice.call(arguments); + if (typeof args[0] === "string") args[0] = "TUNNEL: " + args[0]; + else args.unshift("TUNNEL:"); + console.error.apply(console, args); + }; + else debug = function() {}; + exports.debug = debug; +})); +//#endregion +//#region node_modules/tunnel/index.js +var require_tunnel = /* @__PURE__ */ __commonJSMin(((exports, module) => { + module.exports = require_tunnel$1(); +})); +//#endregion +//#region node_modules/undici/lib/core/symbols.js +var require_symbols$4 = /* @__PURE__ */ __commonJSMin(((exports, module) => { + module.exports = { + kClose: Symbol("close"), + kDestroy: Symbol("destroy"), + kDispatch: Symbol("dispatch"), + kUrl: Symbol("url"), + kWriting: Symbol("writing"), + kResuming: Symbol("resuming"), + kQueue: Symbol("queue"), + kConnect: Symbol("connect"), + kConnecting: Symbol("connecting"), + kKeepAliveDefaultTimeout: Symbol("default keep alive timeout"), + kKeepAliveMaxTimeout: Symbol("max keep alive timeout"), + kKeepAliveTimeoutThreshold: Symbol("keep alive timeout threshold"), + kKeepAliveTimeoutValue: Symbol("keep alive timeout"), + kKeepAlive: Symbol("keep alive"), + kHeadersTimeout: Symbol("headers timeout"), + kBodyTimeout: Symbol("body timeout"), + kServerName: Symbol("server name"), + kLocalAddress: Symbol("local address"), + kHost: Symbol("host"), + kNoRef: Symbol("no ref"), + kBodyUsed: Symbol("used"), + kBody: Symbol("abstracted request body"), + kRunning: Symbol("running"), + kBlocking: Symbol("blocking"), + kPending: Symbol("pending"), + kSize: Symbol("size"), + kBusy: Symbol("busy"), + kQueued: Symbol("queued"), + kFree: Symbol("free"), + kConnected: Symbol("connected"), + kClosed: Symbol("closed"), + kNeedDrain: Symbol("need drain"), + kReset: Symbol("reset"), + kDestroyed: Symbol.for("nodejs.stream.destroyed"), + kResume: Symbol("resume"), + kOnError: Symbol("on error"), + kMaxHeadersSize: Symbol("max headers size"), + kRunningIdx: Symbol("running index"), + kPendingIdx: Symbol("pending index"), + kError: Symbol("error"), + kClients: Symbol("clients"), + kClient: Symbol("client"), + kParser: Symbol("parser"), + kOnDestroyed: Symbol("destroy callbacks"), + kPipelining: Symbol("pipelining"), + kSocket: Symbol("socket"), + kHostHeader: Symbol("host header"), + kConnector: Symbol("connector"), + kStrictContentLength: Symbol("strict content length"), + kMaxRedirections: Symbol("maxRedirections"), + kMaxRequests: Symbol("maxRequestsPerClient"), + kProxy: Symbol("proxy agent options"), + kCounter: Symbol("socket request counter"), + kInterceptors: Symbol("dispatch interceptors"), + kMaxResponseSize: Symbol("max response size"), + kHTTP2Session: Symbol("http2Session"), + kHTTP2SessionState: Symbol("http2Session state"), + kRetryHandlerDefaultRetry: Symbol("retry agent default retry"), + kConstruct: Symbol("constructable"), + kListeners: Symbol("listeners"), + kHTTPContext: Symbol("http context"), + kMaxConcurrentStreams: Symbol("max concurrent streams"), + kNoProxyAgent: Symbol("no proxy agent"), + kHttpProxyAgent: Symbol("http proxy agent"), + kHttpsProxyAgent: Symbol("https proxy agent") + }; +})); +//#endregion +//#region node_modules/undici/lib/core/errors.js +var require_errors = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var kUndiciError = Symbol.for("undici.error.UND_ERR"); + var UndiciError = class extends Error { + constructor(message) { + super(message); + this.name = "UndiciError"; + this.code = "UND_ERR"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kUndiciError] === true; + } + [kUndiciError] = true; + }; + var kConnectTimeoutError = Symbol.for("undici.error.UND_ERR_CONNECT_TIMEOUT"); + var ConnectTimeoutError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "ConnectTimeoutError"; + this.message = message || "Connect Timeout Error"; + this.code = "UND_ERR_CONNECT_TIMEOUT"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kConnectTimeoutError] === true; + } + [kConnectTimeoutError] = true; + }; + var kHeadersTimeoutError = Symbol.for("undici.error.UND_ERR_HEADERS_TIMEOUT"); + var HeadersTimeoutError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "HeadersTimeoutError"; + this.message = message || "Headers Timeout Error"; + this.code = "UND_ERR_HEADERS_TIMEOUT"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kHeadersTimeoutError] === true; + } + [kHeadersTimeoutError] = true; + }; + var kHeadersOverflowError = Symbol.for("undici.error.UND_ERR_HEADERS_OVERFLOW"); + var HeadersOverflowError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "HeadersOverflowError"; + this.message = message || "Headers Overflow Error"; + this.code = "UND_ERR_HEADERS_OVERFLOW"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kHeadersOverflowError] === true; + } + [kHeadersOverflowError] = true; + }; + var kBodyTimeoutError = Symbol.for("undici.error.UND_ERR_BODY_TIMEOUT"); + var BodyTimeoutError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "BodyTimeoutError"; + this.message = message || "Body Timeout Error"; + this.code = "UND_ERR_BODY_TIMEOUT"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kBodyTimeoutError] === true; + } + [kBodyTimeoutError] = true; + }; + var kResponseStatusCodeError = Symbol.for("undici.error.UND_ERR_RESPONSE_STATUS_CODE"); + var ResponseStatusCodeError = class extends UndiciError { + constructor(message, statusCode, headers, body) { + super(message); + this.name = "ResponseStatusCodeError"; + this.message = message || "Response Status Code Error"; + this.code = "UND_ERR_RESPONSE_STATUS_CODE"; + this.body = body; + this.status = statusCode; + this.statusCode = statusCode; + this.headers = headers; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kResponseStatusCodeError] === true; + } + [kResponseStatusCodeError] = true; + }; + var kInvalidArgumentError = Symbol.for("undici.error.UND_ERR_INVALID_ARG"); + var InvalidArgumentError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "InvalidArgumentError"; + this.message = message || "Invalid Argument Error"; + this.code = "UND_ERR_INVALID_ARG"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kInvalidArgumentError] === true; + } + [kInvalidArgumentError] = true; + }; + var kInvalidReturnValueError = Symbol.for("undici.error.UND_ERR_INVALID_RETURN_VALUE"); + var InvalidReturnValueError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "InvalidReturnValueError"; + this.message = message || "Invalid Return Value Error"; + this.code = "UND_ERR_INVALID_RETURN_VALUE"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kInvalidReturnValueError] === true; + } + [kInvalidReturnValueError] = true; + }; + var kAbortError = Symbol.for("undici.error.UND_ERR_ABORT"); + var AbortError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "AbortError"; + this.message = message || "The operation was aborted"; + this.code = "UND_ERR_ABORT"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kAbortError] === true; + } + [kAbortError] = true; + }; + var kRequestAbortedError = Symbol.for("undici.error.UND_ERR_ABORTED"); + var RequestAbortedError = class extends AbortError { + constructor(message) { + super(message); + this.name = "AbortError"; + this.message = message || "Request aborted"; + this.code = "UND_ERR_ABORTED"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kRequestAbortedError] === true; + } + [kRequestAbortedError] = true; + }; + var kInformationalError = Symbol.for("undici.error.UND_ERR_INFO"); + var InformationalError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "InformationalError"; + this.message = message || "Request information"; + this.code = "UND_ERR_INFO"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kInformationalError] === true; + } + [kInformationalError] = true; + }; + var kRequestContentLengthMismatchError = Symbol.for("undici.error.UND_ERR_REQ_CONTENT_LENGTH_MISMATCH"); + var RequestContentLengthMismatchError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "RequestContentLengthMismatchError"; + this.message = message || "Request body length does not match content-length header"; + this.code = "UND_ERR_REQ_CONTENT_LENGTH_MISMATCH"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kRequestContentLengthMismatchError] === true; + } + [kRequestContentLengthMismatchError] = true; + }; + var kResponseContentLengthMismatchError = Symbol.for("undici.error.UND_ERR_RES_CONTENT_LENGTH_MISMATCH"); + var ResponseContentLengthMismatchError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "ResponseContentLengthMismatchError"; + this.message = message || "Response body length does not match content-length header"; + this.code = "UND_ERR_RES_CONTENT_LENGTH_MISMATCH"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kResponseContentLengthMismatchError] === true; + } + [kResponseContentLengthMismatchError] = true; + }; + var kClientDestroyedError = Symbol.for("undici.error.UND_ERR_DESTROYED"); + var ClientDestroyedError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "ClientDestroyedError"; + this.message = message || "The client is destroyed"; + this.code = "UND_ERR_DESTROYED"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kClientDestroyedError] === true; + } + [kClientDestroyedError] = true; + }; + var kClientClosedError = Symbol.for("undici.error.UND_ERR_CLOSED"); + var ClientClosedError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "ClientClosedError"; + this.message = message || "The client is closed"; + this.code = "UND_ERR_CLOSED"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kClientClosedError] === true; + } + [kClientClosedError] = true; + }; + var kSocketError = Symbol.for("undici.error.UND_ERR_SOCKET"); + var SocketError = class extends UndiciError { + constructor(message, socket) { + super(message); + this.name = "SocketError"; + this.message = message || "Socket error"; + this.code = "UND_ERR_SOCKET"; + this.socket = socket; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kSocketError] === true; + } + [kSocketError] = true; + }; + var kNotSupportedError = Symbol.for("undici.error.UND_ERR_NOT_SUPPORTED"); + var NotSupportedError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "NotSupportedError"; + this.message = message || "Not supported error"; + this.code = "UND_ERR_NOT_SUPPORTED"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kNotSupportedError] === true; + } + [kNotSupportedError] = true; + }; + var kBalancedPoolMissingUpstreamError = Symbol.for("undici.error.UND_ERR_BPL_MISSING_UPSTREAM"); + var BalancedPoolMissingUpstreamError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "MissingUpstreamError"; + this.message = message || "No upstream has been added to the BalancedPool"; + this.code = "UND_ERR_BPL_MISSING_UPSTREAM"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kBalancedPoolMissingUpstreamError] === true; + } + [kBalancedPoolMissingUpstreamError] = true; + }; + var kHTTPParserError = Symbol.for("undici.error.UND_ERR_HTTP_PARSER"); + var HTTPParserError = class extends Error { + constructor(message, code, data) { + super(message); + this.name = "HTTPParserError"; + this.code = code ? `HPE_${code}` : void 0; + this.data = data ? data.toString() : void 0; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kHTTPParserError] === true; + } + [kHTTPParserError] = true; + }; + var kResponseExceededMaxSizeError = Symbol.for("undici.error.UND_ERR_RES_EXCEEDED_MAX_SIZE"); + var ResponseExceededMaxSizeError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "ResponseExceededMaxSizeError"; + this.message = message || "Response content exceeded max size"; + this.code = "UND_ERR_RES_EXCEEDED_MAX_SIZE"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kResponseExceededMaxSizeError] === true; + } + [kResponseExceededMaxSizeError] = true; + }; + var kRequestRetryError = Symbol.for("undici.error.UND_ERR_REQ_RETRY"); + var RequestRetryError = class extends UndiciError { + constructor(message, code, { headers, data }) { + super(message); + this.name = "RequestRetryError"; + this.message = message || "Request retry error"; + this.code = "UND_ERR_REQ_RETRY"; + this.statusCode = code; + this.data = data; + this.headers = headers; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kRequestRetryError] === true; + } + [kRequestRetryError] = true; + }; + var kResponseError = Symbol.for("undici.error.UND_ERR_RESPONSE"); + var ResponseError = class extends UndiciError { + constructor(message, code, { headers, data }) { + super(message); + this.name = "ResponseError"; + this.message = message || "Response error"; + this.code = "UND_ERR_RESPONSE"; + this.statusCode = code; + this.data = data; + this.headers = headers; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kResponseError] === true; + } + [kResponseError] = true; + }; + var kSecureProxyConnectionError = Symbol.for("undici.error.UND_ERR_PRX_TLS"); + var SecureProxyConnectionError = class extends UndiciError { + constructor(cause, message, options) { + super(message, { + cause, + ...options ?? {} + }); + this.name = "SecureProxyConnectionError"; + this.message = message || "Secure Proxy Connection failed"; + this.code = "UND_ERR_PRX_TLS"; + this.cause = cause; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kSecureProxyConnectionError] === true; + } + [kSecureProxyConnectionError] = true; + }; + var kMessageSizeExceededError = Symbol.for("undici.error.UND_ERR_WS_MESSAGE_SIZE_EXCEEDED"); + var MessageSizeExceededError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "MessageSizeExceededError"; + this.message = message || "Max decompressed message size exceeded"; + this.code = "UND_ERR_WS_MESSAGE_SIZE_EXCEEDED"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kMessageSizeExceededError] === true; + } + get [kMessageSizeExceededError]() { + return true; + } + }; + module.exports = { + AbortError, + HTTPParserError, + UndiciError, + HeadersTimeoutError, + HeadersOverflowError, + BodyTimeoutError, + RequestContentLengthMismatchError, + ConnectTimeoutError, + ResponseStatusCodeError, + InvalidArgumentError, + InvalidReturnValueError, + RequestAbortedError, + ClientDestroyedError, + ClientClosedError, + InformationalError, + SocketError, + NotSupportedError, + ResponseContentLengthMismatchError, + BalancedPoolMissingUpstreamError, + ResponseExceededMaxSizeError, + RequestRetryError, + ResponseError, + SecureProxyConnectionError, + MessageSizeExceededError + }; +})); +//#endregion +//#region node_modules/undici/lib/core/constants.js +var require_constants$4 = /* @__PURE__ */ __commonJSMin(((exports, module) => { + /** @type {Record} */ + var headerNameLowerCasedRecord = {}; + var wellknownHeaderNames = [ + "Accept", + "Accept-Encoding", + "Accept-Language", + "Accept-Ranges", + "Access-Control-Allow-Credentials", + "Access-Control-Allow-Headers", + "Access-Control-Allow-Methods", + "Access-Control-Allow-Origin", + "Access-Control-Expose-Headers", + "Access-Control-Max-Age", + "Access-Control-Request-Headers", + "Access-Control-Request-Method", + "Age", + "Allow", + "Alt-Svc", + "Alt-Used", + "Authorization", + "Cache-Control", + "Clear-Site-Data", + "Connection", + "Content-Disposition", + "Content-Encoding", + "Content-Language", + "Content-Length", + "Content-Location", + "Content-Range", + "Content-Security-Policy", + "Content-Security-Policy-Report-Only", + "Content-Type", + "Cookie", + "Cross-Origin-Embedder-Policy", + "Cross-Origin-Opener-Policy", + "Cross-Origin-Resource-Policy", + "Date", + "Device-Memory", + "Downlink", + "ECT", + "ETag", + "Expect", + "Expect-CT", + "Expires", + "Forwarded", + "From", + "Host", + "If-Match", + "If-Modified-Since", + "If-None-Match", + "If-Range", + "If-Unmodified-Since", + "Keep-Alive", + "Last-Modified", + "Link", + "Location", + "Max-Forwards", + "Origin", + "Permissions-Policy", + "Pragma", + "Proxy-Authenticate", + "Proxy-Authorization", + "RTT", + "Range", + "Referer", + "Referrer-Policy", + "Refresh", + "Retry-After", + "Sec-WebSocket-Accept", + "Sec-WebSocket-Extensions", + "Sec-WebSocket-Key", + "Sec-WebSocket-Protocol", + "Sec-WebSocket-Version", + "Server", + "Server-Timing", + "Service-Worker-Allowed", + "Service-Worker-Navigation-Preload", + "Set-Cookie", + "SourceMap", + "Strict-Transport-Security", + "Supports-Loading-Mode", + "TE", + "Timing-Allow-Origin", + "Trailer", + "Transfer-Encoding", + "Upgrade", + "Upgrade-Insecure-Requests", + "User-Agent", + "Vary", + "Via", + "WWW-Authenticate", + "X-Content-Type-Options", + "X-DNS-Prefetch-Control", + "X-Frame-Options", + "X-Permitted-Cross-Domain-Policies", + "X-Powered-By", + "X-Requested-With", + "X-XSS-Protection" + ]; + for (let i = 0; i < wellknownHeaderNames.length; ++i) { + const key = wellknownHeaderNames[i]; + const lowerCasedKey = key.toLowerCase(); + headerNameLowerCasedRecord[key] = headerNameLowerCasedRecord[lowerCasedKey] = lowerCasedKey; + } + Object.setPrototypeOf(headerNameLowerCasedRecord, null); + module.exports = { + wellknownHeaderNames, + headerNameLowerCasedRecord + }; +})); +//#endregion +//#region node_modules/undici/lib/core/tree.js +var require_tree = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { wellknownHeaderNames, headerNameLowerCasedRecord } = require_constants$4(); + var TstNode = class TstNode { + /** @type {any} */ + value = null; + /** @type {null | TstNode} */ + left = null; + /** @type {null | TstNode} */ + middle = null; + /** @type {null | TstNode} */ + right = null; + /** @type {number} */ + code; + /** + * @param {string} key + * @param {any} value + * @param {number} index + */ + constructor(key, value, index) { + if (index === void 0 || index >= key.length) throw new TypeError("Unreachable"); + if ((this.code = key.charCodeAt(index)) > 127) throw new TypeError("key must be ascii string"); + if (key.length !== ++index) this.middle = new TstNode(key, value, index); + else this.value = value; + } + /** + * @param {string} key + * @param {any} value + */ + add(key, value) { + const length = key.length; + if (length === 0) throw new TypeError("Unreachable"); + let index = 0; + let node = this; + while (true) { + const code = key.charCodeAt(index); + if (code > 127) throw new TypeError("key must be ascii string"); + if (node.code === code) if (length === ++index) { + node.value = value; + break; + } else if (node.middle !== null) node = node.middle; + else { + node.middle = new TstNode(key, value, index); + break; + } + else if (node.code < code) if (node.left !== null) node = node.left; + else { + node.left = new TstNode(key, value, index); + break; + } + else if (node.right !== null) node = node.right; + else { + node.right = new TstNode(key, value, index); + break; + } + } + } + /** + * @param {Uint8Array} key + * @return {TstNode | null} + */ + search(key) { + const keylength = key.length; + let index = 0; + let node = this; + while (node !== null && index < keylength) { + let code = key[index]; + if (code <= 90 && code >= 65) code |= 32; + while (node !== null) { + if (code === node.code) { + if (keylength === ++index) return node; + node = node.middle; + break; + } + node = node.code < code ? node.left : node.right; + } + } + return null; + } + }; + var TernarySearchTree = class { + /** @type {TstNode | null} */ + node = null; + /** + * @param {string} key + * @param {any} value + * */ + insert(key, value) { + if (this.node === null) this.node = new TstNode(key, value, 0); + else this.node.add(key, value); + } + /** + * @param {Uint8Array} key + * @return {any} + */ + lookup(key) { + return this.node?.search(key)?.value ?? null; + } + }; + var tree = new TernarySearchTree(); + for (let i = 0; i < wellknownHeaderNames.length; ++i) { + const key = headerNameLowerCasedRecord[wellknownHeaderNames[i]]; + tree.insert(key, key); + } + module.exports = { + TernarySearchTree, + tree + }; +})); +//#endregion +//#region node_modules/undici/lib/core/util.js +var require_util$7 = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var assert$26 = __require("node:assert"); + var { kDestroyed, kBodyUsed, kListeners, kBody } = require_symbols$4(); + var { IncomingMessage } = __require("node:http"); + var stream = __require("node:stream"); + var net$2 = __require("node:net"); + var { Blob: Blob$3 } = __require("node:buffer"); + var nodeUtil$3 = __require("node:util"); + var { stringify: stringify$1 } = __require("node:querystring"); + var { EventEmitter: EE$2 } = __require("node:events"); + var { InvalidArgumentError } = require_errors(); + var { headerNameLowerCasedRecord } = require_constants$4(); + var { tree } = require_tree(); + var [nodeMajor, nodeMinor] = process.versions.node.split(".").map((v) => Number(v)); + var BodyAsyncIterable = class { + constructor(body) { + this[kBody] = body; + this[kBodyUsed] = false; + } + async *[Symbol.asyncIterator]() { + assert$26(!this[kBodyUsed], "disturbed"); + this[kBodyUsed] = true; + yield* this[kBody]; + } + }; + function wrapRequestBody(body) { + if (isStream(body)) { + if (bodyLength(body) === 0) body.on("data", function() { + assert$26(false); + }); + if (typeof body.readableDidRead !== "boolean") { + body[kBodyUsed] = false; + EE$2.prototype.on.call(body, "data", function() { + this[kBodyUsed] = true; + }); + } + return body; + } else if (body && typeof body.pipeTo === "function") return new BodyAsyncIterable(body); + else if (body && typeof body !== "string" && !ArrayBuffer.isView(body) && isIterable(body)) return new BodyAsyncIterable(body); + else return body; + } + function nop() {} + function isStream(obj) { + return obj && typeof obj === "object" && typeof obj.pipe === "function" && typeof obj.on === "function"; + } + function isBlobLike(object) { + if (object === null) return false; + else if (object instanceof Blob$3) return true; + else if (typeof object !== "object") return false; + else { + const sTag = object[Symbol.toStringTag]; + return (sTag === "Blob" || sTag === "File") && ("stream" in object && typeof object.stream === "function" || "arrayBuffer" in object && typeof object.arrayBuffer === "function"); + } + } + function buildURL(url, queryParams) { + if (url.includes("?") || url.includes("#")) throw new Error("Query params cannot be passed when url already contains \"?\" or \"#\"."); + const stringified = stringify$1(queryParams); + if (stringified) url += "?" + stringified; + return url; + } + function isValidPort(port) { + const value = parseInt(port, 10); + return value === Number(port) && value >= 0 && value <= 65535; + } + function isHttpOrHttpsPrefixed(value) { + return value != null && value[0] === "h" && value[1] === "t" && value[2] === "t" && value[3] === "p" && (value[4] === ":" || value[4] === "s" && value[5] === ":"); + } + function parseURL(url) { + if (typeof url === "string") { + url = new URL(url); + if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) throw new InvalidArgumentError("Invalid URL protocol: the URL must start with `http:` or `https:`."); + return url; + } + if (!url || typeof url !== "object") throw new InvalidArgumentError("Invalid URL: The URL argument must be a non-null object."); + if (!(url instanceof URL)) { + if (url.port != null && url.port !== "" && isValidPort(url.port) === false) throw new InvalidArgumentError("Invalid URL: port must be a valid integer or a string representation of an integer."); + if (url.path != null && typeof url.path !== "string") throw new InvalidArgumentError("Invalid URL path: the path must be a string or null/undefined."); + if (url.pathname != null && typeof url.pathname !== "string") throw new InvalidArgumentError("Invalid URL pathname: the pathname must be a string or null/undefined."); + if (url.hostname != null && typeof url.hostname !== "string") throw new InvalidArgumentError("Invalid URL hostname: the hostname must be a string or null/undefined."); + if (url.origin != null && typeof url.origin !== "string") throw new InvalidArgumentError("Invalid URL origin: the origin must be a string or null/undefined."); + if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) throw new InvalidArgumentError("Invalid URL protocol: the URL must start with `http:` or `https:`."); + const port = url.port != null ? url.port : url.protocol === "https:" ? 443 : 80; + let origin = url.origin != null ? url.origin : `${url.protocol || ""}//${url.hostname || ""}:${port}`; + let path = url.path != null ? url.path : `${url.pathname || ""}${url.search || ""}`; + if (origin[origin.length - 1] === "/") origin = origin.slice(0, origin.length - 1); + if (path && path[0] !== "/") path = `/${path}`; + return new URL(`${origin}${path}`); + } + if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) throw new InvalidArgumentError("Invalid URL protocol: the URL must start with `http:` or `https:`."); + return url; + } + function parseOrigin(url) { + url = parseURL(url); + if (url.pathname !== "/" || url.search || url.hash) throw new InvalidArgumentError("invalid url"); + return url; + } + function getHostname(host) { + if (host[0] === "[") { + const idx = host.indexOf("]"); + assert$26(idx !== -1); + return host.substring(1, idx); + } + const idx = host.indexOf(":"); + if (idx === -1) return host; + return host.substring(0, idx); + } + function getServerName(host) { + if (!host) return null; + assert$26(typeof host === "string"); + const servername = getHostname(host); + if (net$2.isIP(servername)) return ""; + return servername; + } + function deepClone(obj) { + return JSON.parse(JSON.stringify(obj)); + } + function isAsyncIterable(obj) { + return !!(obj != null && typeof obj[Symbol.asyncIterator] === "function"); + } + function isIterable(obj) { + return !!(obj != null && (typeof obj[Symbol.iterator] === "function" || typeof obj[Symbol.asyncIterator] === "function")); + } + function bodyLength(body) { + if (body == null) return 0; + else if (isStream(body)) { + const state = body._readableState; + return state && state.objectMode === false && state.ended === true && Number.isFinite(state.length) ? state.length : null; + } else if (isBlobLike(body)) return body.size != null ? body.size : null; + else if (isBuffer(body)) return body.byteLength; + return null; + } + function isDestroyed(body) { + return body && !!(body.destroyed || body[kDestroyed] || stream.isDestroyed?.(body)); + } + function destroy(stream, err) { + if (stream == null || !isStream(stream) || isDestroyed(stream)) return; + if (typeof stream.destroy === "function") { + if (Object.getPrototypeOf(stream).constructor === IncomingMessage) stream.socket = null; + stream.destroy(err); + } else if (err) queueMicrotask(() => { + stream.emit("error", err); + }); + if (stream.destroyed !== true) stream[kDestroyed] = true; + } + var KEEPALIVE_TIMEOUT_EXPR = /timeout=(\d+)/; + function parseKeepAliveTimeout(val) { + const m = val.toString().match(KEEPALIVE_TIMEOUT_EXPR); + return m ? parseInt(m[1], 10) * 1e3 : null; + } + /** + * Retrieves a header name and returns its lowercase value. + * @param {string | Buffer} value Header name + * @returns {string} + */ + function headerNameToString(value) { + return typeof value === "string" ? headerNameLowerCasedRecord[value] ?? value.toLowerCase() : tree.lookup(value) ?? value.toString("latin1").toLowerCase(); + } + /** + * Receive the buffer as a string and return its lowercase value. + * @param {Buffer} value Header name + * @returns {string} + */ + function bufferToLowerCasedHeaderName(value) { + return tree.lookup(value) ?? value.toString("latin1").toLowerCase(); + } + /** + * @param {Record | (Buffer | string | (Buffer | string)[])[]} headers + * @param {Record} [obj] + * @returns {Record} + */ + function parseHeaders(headers, obj) { + if (obj === void 0) obj = {}; + for (let i = 0; i < headers.length; i += 2) { + const key = headerNameToString(headers[i]); + let val = obj[key]; + if (val) { + if (typeof val === "string") { + val = [val]; + obj[key] = val; + } + val.push(headers[i + 1].toString("utf8")); + } else { + const headersValue = headers[i + 1]; + if (typeof headersValue === "string") obj[key] = headersValue; + else obj[key] = Array.isArray(headersValue) ? headersValue.map((x) => x.toString("utf8")) : headersValue.toString("utf8"); + } + } + if ("content-length" in obj && "content-disposition" in obj) obj["content-disposition"] = Buffer.from(obj["content-disposition"]).toString("latin1"); + return obj; + } + function parseRawHeaders(headers) { + const len = headers.length; + const ret = new Array(len); + let hasContentLength = false; + let contentDispositionIdx = -1; + let key; + let val; + let kLen = 0; + for (let n = 0; n < headers.length; n += 2) { + key = headers[n]; + val = headers[n + 1]; + typeof key !== "string" && (key = key.toString()); + typeof val !== "string" && (val = val.toString("utf8")); + kLen = key.length; + if (kLen === 14 && key[7] === "-" && (key === "content-length" || key.toLowerCase() === "content-length")) hasContentLength = true; + else if (kLen === 19 && key[7] === "-" && (key === "content-disposition" || key.toLowerCase() === "content-disposition")) contentDispositionIdx = n + 1; + ret[n] = key; + ret[n + 1] = val; + } + if (hasContentLength && contentDispositionIdx !== -1) ret[contentDispositionIdx] = Buffer.from(ret[contentDispositionIdx]).toString("latin1"); + return ret; + } + function isBuffer(buffer) { + return buffer instanceof Uint8Array || Buffer.isBuffer(buffer); + } + function validateHandler(handler, method, upgrade) { + if (!handler || typeof handler !== "object") throw new InvalidArgumentError("handler must be an object"); + if (typeof handler.onConnect !== "function") throw new InvalidArgumentError("invalid onConnect method"); + if (typeof handler.onError !== "function") throw new InvalidArgumentError("invalid onError method"); + if (typeof handler.onBodySent !== "function" && handler.onBodySent !== void 0) throw new InvalidArgumentError("invalid onBodySent method"); + if (upgrade || method === "CONNECT") { + if (typeof handler.onUpgrade !== "function") throw new InvalidArgumentError("invalid onUpgrade method"); + } else { + if (typeof handler.onHeaders !== "function") throw new InvalidArgumentError("invalid onHeaders method"); + if (typeof handler.onData !== "function") throw new InvalidArgumentError("invalid onData method"); + if (typeof handler.onComplete !== "function") throw new InvalidArgumentError("invalid onComplete method"); + } + } + function isDisturbed(body) { + return !!(body && (stream.isDisturbed(body) || body[kBodyUsed])); + } + function isErrored(body) { + return !!(body && stream.isErrored(body)); + } + function isReadable(body) { + return !!(body && stream.isReadable(body)); + } + function getSocketInfo(socket) { + return { + localAddress: socket.localAddress, + localPort: socket.localPort, + remoteAddress: socket.remoteAddress, + remotePort: socket.remotePort, + remoteFamily: socket.remoteFamily, + timeout: socket.timeout, + bytesWritten: socket.bytesWritten, + bytesRead: socket.bytesRead + }; + } + /** @type {globalThis['ReadableStream']} */ + function ReadableStreamFrom(iterable) { + let iterator; + return new ReadableStream({ + async start() { + iterator = iterable[Symbol.asyncIterator](); + }, + async pull(controller) { + const { done, value } = await iterator.next(); + if (done) queueMicrotask(() => { + controller.close(); + controller.byobRequest?.respond(0); + }); + else { + const buf = Buffer.isBuffer(value) ? value : Buffer.from(value); + if (buf.byteLength) controller.enqueue(new Uint8Array(buf)); + } + return controller.desiredSize > 0; + }, + async cancel(reason) { + await iterator.return(); + }, + type: "bytes" + }); + } + function isFormDataLike(object) { + return object && typeof object === "object" && typeof object.append === "function" && typeof object.delete === "function" && typeof object.get === "function" && typeof object.getAll === "function" && typeof object.has === "function" && typeof object.set === "function" && object[Symbol.toStringTag] === "FormData"; + } + function addAbortListener(signal, listener) { + if ("addEventListener" in signal) { + signal.addEventListener("abort", listener, { once: true }); + return () => signal.removeEventListener("abort", listener); + } + signal.addListener("abort", listener); + return () => signal.removeListener("abort", listener); + } + var hasToWellFormed = typeof String.prototype.toWellFormed === "function"; + var hasIsWellFormed = typeof String.prototype.isWellFormed === "function"; + /** + * @param {string} val + */ + function toUSVString(val) { + return hasToWellFormed ? `${val}`.toWellFormed() : nodeUtil$3.toUSVString(val); + } + /** + * @param {string} val + */ + function isUSVString(val) { + return hasIsWellFormed ? `${val}`.isWellFormed() : toUSVString(val) === `${val}`; + } + /** + * @see https://tools.ietf.org/html/rfc7230#section-3.2.6 + * @param {number} c + */ + function isTokenCharCode(c) { + switch (c) { + case 34: + case 40: + case 41: + case 44: + case 47: + case 58: + case 59: + case 60: + case 61: + case 62: + case 63: + case 64: + case 91: + case 92: + case 93: + case 123: + case 125: return false; + default: return c >= 33 && c <= 126; + } + } + /** + * @param {string} characters + */ + function isValidHTTPToken(characters) { + if (characters.length === 0) return false; + for (let i = 0; i < characters.length; ++i) if (!isTokenCharCode(characters.charCodeAt(i))) return false; + return true; + } + /** + * Matches if val contains an invalid field-vchar + * field-value = *( field-content / obs-fold ) + * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] + * field-vchar = VCHAR / obs-text + */ + var headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/; + /** + * @param {string} characters + */ + function isValidHeaderValue(characters) { + return !headerCharRegex.test(characters); + } + function parseRangeHeader(range) { + if (range == null || range === "") return { + start: 0, + end: null, + size: null + }; + const m = range ? range.match(/^bytes (\d+)-(\d+)\/(\d+)?$/) : null; + return m ? { + start: parseInt(m[1]), + end: m[2] ? parseInt(m[2]) : null, + size: m[3] ? parseInt(m[3]) : null + } : null; + } + function addListener(obj, name, listener) { + (obj[kListeners] ??= []).push([name, listener]); + obj.on(name, listener); + return obj; + } + function removeAllListeners(obj) { + for (const [name, listener] of obj[kListeners] ?? []) obj.removeListener(name, listener); + obj[kListeners] = null; + } + function errorRequest(client, request, err) { + try { + request.onError(err); + assert$26(request.aborted); + } catch (err) { + client.emit("error", err); + } + } + var kEnumerableProperty = Object.create(null); + kEnumerableProperty.enumerable = true; + var normalizedMethodRecordsBase = { + delete: "DELETE", + DELETE: "DELETE", + get: "GET", + GET: "GET", + head: "HEAD", + HEAD: "HEAD", + options: "OPTIONS", + OPTIONS: "OPTIONS", + post: "POST", + POST: "POST", + put: "PUT", + PUT: "PUT" + }; + var normalizedMethodRecords = { + ...normalizedMethodRecordsBase, + patch: "patch", + PATCH: "PATCH" + }; + Object.setPrototypeOf(normalizedMethodRecordsBase, null); + Object.setPrototypeOf(normalizedMethodRecords, null); + module.exports = { + kEnumerableProperty, + nop, + isDisturbed, + isErrored, + isReadable, + toUSVString, + isUSVString, + isBlobLike, + parseOrigin, + parseURL, + getServerName, + isStream, + isIterable, + isAsyncIterable, + isDestroyed, + headerNameToString, + bufferToLowerCasedHeaderName, + addListener, + removeAllListeners, + errorRequest, + parseRawHeaders, + parseHeaders, + parseKeepAliveTimeout, + destroy, + bodyLength, + deepClone, + ReadableStreamFrom, + isBuffer, + validateHandler, + getSocketInfo, + isFormDataLike, + buildURL, + addAbortListener, + isValidHTTPToken, + isValidHeaderValue, + isTokenCharCode, + parseRangeHeader, + normalizedMethodRecordsBase, + normalizedMethodRecords, + isValidPort, + isHttpOrHttpsPrefixed, + nodeMajor, + nodeMinor, + safeHTTPMethods: [ + "GET", + "HEAD", + "OPTIONS", + "TRACE" + ], + wrapRequestBody + }; +})); +//#endregion +//#region node_modules/undici/lib/core/diagnostics.js +var require_diagnostics = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var diagnosticsChannel = __require("node:diagnostics_channel"); + var util$1 = __require("node:util"); + var undiciDebugLog = util$1.debuglog("undici"); + var fetchDebuglog = util$1.debuglog("fetch"); + var websocketDebuglog = util$1.debuglog("websocket"); + var isClientSet = false; + var channels = { + beforeConnect: diagnosticsChannel.channel("undici:client:beforeConnect"), + connected: diagnosticsChannel.channel("undici:client:connected"), + connectError: diagnosticsChannel.channel("undici:client:connectError"), + sendHeaders: diagnosticsChannel.channel("undici:client:sendHeaders"), + create: diagnosticsChannel.channel("undici:request:create"), + bodySent: diagnosticsChannel.channel("undici:request:bodySent"), + headers: diagnosticsChannel.channel("undici:request:headers"), + trailers: diagnosticsChannel.channel("undici:request:trailers"), + error: diagnosticsChannel.channel("undici:request:error"), + open: diagnosticsChannel.channel("undici:websocket:open"), + close: diagnosticsChannel.channel("undici:websocket:close"), + socketError: diagnosticsChannel.channel("undici:websocket:socket_error"), + ping: diagnosticsChannel.channel("undici:websocket:ping"), + pong: diagnosticsChannel.channel("undici:websocket:pong") + }; + if (undiciDebugLog.enabled || fetchDebuglog.enabled) { + const debuglog = fetchDebuglog.enabled ? fetchDebuglog : undiciDebugLog; + diagnosticsChannel.channel("undici:client:beforeConnect").subscribe((evt) => { + const { connectParams: { version, protocol, port, host } } = evt; + debuglog("connecting to %s using %s%s", `${host}${port ? `:${port}` : ""}`, protocol, version); + }); + diagnosticsChannel.channel("undici:client:connected").subscribe((evt) => { + const { connectParams: { version, protocol, port, host } } = evt; + debuglog("connected to %s using %s%s", `${host}${port ? `:${port}` : ""}`, protocol, version); + }); + diagnosticsChannel.channel("undici:client:connectError").subscribe((evt) => { + const { connectParams: { version, protocol, port, host }, error } = evt; + debuglog("connection to %s using %s%s errored - %s", `${host}${port ? `:${port}` : ""}`, protocol, version, error.message); + }); + diagnosticsChannel.channel("undici:client:sendHeaders").subscribe((evt) => { + const { request: { method, path, origin } } = evt; + debuglog("sending request to %s %s/%s", method, origin, path); + }); + diagnosticsChannel.channel("undici:request:headers").subscribe((evt) => { + const { request: { method, path, origin }, response: { statusCode } } = evt; + debuglog("received response to %s %s/%s - HTTP %d", method, origin, path, statusCode); + }); + diagnosticsChannel.channel("undici:request:trailers").subscribe((evt) => { + const { request: { method, path, origin } } = evt; + debuglog("trailers received from %s %s/%s", method, origin, path); + }); + diagnosticsChannel.channel("undici:request:error").subscribe((evt) => { + const { request: { method, path, origin }, error } = evt; + debuglog("request to %s %s/%s errored - %s", method, origin, path, error.message); + }); + isClientSet = true; + } + if (websocketDebuglog.enabled) { + if (!isClientSet) { + const debuglog = undiciDebugLog.enabled ? undiciDebugLog : websocketDebuglog; + diagnosticsChannel.channel("undici:client:beforeConnect").subscribe((evt) => { + const { connectParams: { version, protocol, port, host } } = evt; + debuglog("connecting to %s%s using %s%s", host, port ? `:${port}` : "", protocol, version); + }); + diagnosticsChannel.channel("undici:client:connected").subscribe((evt) => { + const { connectParams: { version, protocol, port, host } } = evt; + debuglog("connected to %s%s using %s%s", host, port ? `:${port}` : "", protocol, version); + }); + diagnosticsChannel.channel("undici:client:connectError").subscribe((evt) => { + const { connectParams: { version, protocol, port, host }, error } = evt; + debuglog("connection to %s%s using %s%s errored - %s", host, port ? `:${port}` : "", protocol, version, error.message); + }); + diagnosticsChannel.channel("undici:client:sendHeaders").subscribe((evt) => { + const { request: { method, path, origin } } = evt; + debuglog("sending request to %s %s/%s", method, origin, path); + }); + } + diagnosticsChannel.channel("undici:websocket:open").subscribe((evt) => { + const { address: { address, port } } = evt; + websocketDebuglog("connection opened %s%s", address, port ? `:${port}` : ""); + }); + diagnosticsChannel.channel("undici:websocket:close").subscribe((evt) => { + const { websocket, code, reason } = evt; + websocketDebuglog("closed connection to %s - %s %s", websocket.url, code, reason); + }); + diagnosticsChannel.channel("undici:websocket:socket_error").subscribe((err) => { + websocketDebuglog("connection errored - %s", err.message); + }); + diagnosticsChannel.channel("undici:websocket:ping").subscribe((evt) => { + websocketDebuglog("ping received"); + }); + diagnosticsChannel.channel("undici:websocket:pong").subscribe((evt) => { + websocketDebuglog("pong received"); + }); + } + module.exports = { channels }; +})); +//#endregion +//#region node_modules/undici/lib/core/request.js +var require_request$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { InvalidArgumentError, NotSupportedError } = require_errors(); + var assert$25 = __require("node:assert"); + var { isValidHTTPToken, isValidHeaderValue, isStream, destroy, isBuffer, isFormDataLike, isIterable, isBlobLike, buildURL, validateHandler, getServerName, normalizedMethodRecords } = require_util$7(); + var { channels } = require_diagnostics(); + var { headerNameLowerCasedRecord } = require_constants$4(); + var invalidPathRegex = /[^\u0021-\u00ff]/; + var kHandler = Symbol("handler"); + var Request = class { + constructor(origin, { path, method, body, headers, query, idempotent, blocking, upgrade, headersTimeout, bodyTimeout, reset, throwOnError, expectContinue, servername }, handler) { + if (typeof path !== "string") throw new InvalidArgumentError("path must be a string"); + else if (path[0] !== "/" && !(path.startsWith("http://") || path.startsWith("https://")) && method !== "CONNECT") throw new InvalidArgumentError("path must be an absolute URL or start with a slash"); + else if (invalidPathRegex.test(path)) throw new InvalidArgumentError("invalid request path"); + if (typeof method !== "string") throw new InvalidArgumentError("method must be a string"); + else if (normalizedMethodRecords[method] === void 0 && !isValidHTTPToken(method)) throw new InvalidArgumentError("invalid request method"); + if (upgrade && typeof upgrade !== "string") throw new InvalidArgumentError("upgrade must be a string"); + if (upgrade && !isValidHeaderValue(upgrade)) throw new InvalidArgumentError("invalid upgrade header"); + if (headersTimeout != null && (!Number.isFinite(headersTimeout) || headersTimeout < 0)) throw new InvalidArgumentError("invalid headersTimeout"); + if (bodyTimeout != null && (!Number.isFinite(bodyTimeout) || bodyTimeout < 0)) throw new InvalidArgumentError("invalid bodyTimeout"); + if (reset != null && typeof reset !== "boolean") throw new InvalidArgumentError("invalid reset"); + if (expectContinue != null && typeof expectContinue !== "boolean") throw new InvalidArgumentError("invalid expectContinue"); + this.headersTimeout = headersTimeout; + this.bodyTimeout = bodyTimeout; + this.throwOnError = throwOnError === true; + this.method = method; + this.abort = null; + if (body == null) this.body = null; + else if (isStream(body)) { + this.body = body; + const rState = this.body._readableState; + if (!rState || !rState.autoDestroy) { + this.endHandler = function autoDestroy() { + destroy(this); + }; + this.body.on("end", this.endHandler); + } + this.errorHandler = (err) => { + if (this.abort) this.abort(err); + else this.error = err; + }; + this.body.on("error", this.errorHandler); + } else if (isBuffer(body)) this.body = body.byteLength ? body : null; + else if (ArrayBuffer.isView(body)) this.body = body.buffer.byteLength ? Buffer.from(body.buffer, body.byteOffset, body.byteLength) : null; + else if (body instanceof ArrayBuffer) this.body = body.byteLength ? Buffer.from(body) : null; + else if (typeof body === "string") this.body = body.length ? Buffer.from(body) : null; + else if (isFormDataLike(body) || isIterable(body) || isBlobLike(body)) this.body = body; + else throw new InvalidArgumentError("body must be a string, a Buffer, a Readable stream, an iterable, or an async iterable"); + this.completed = false; + this.aborted = false; + this.upgrade = upgrade || null; + this.path = query ? buildURL(path, query) : path; + this.origin = origin; + this.idempotent = idempotent == null ? method === "HEAD" || method === "GET" : idempotent; + this.blocking = blocking == null ? false : blocking; + this.reset = reset == null ? null : reset; + this.host = null; + this.contentLength = null; + this.contentType = null; + this.headers = []; + this.expectContinue = expectContinue != null ? expectContinue : false; + if (Array.isArray(headers)) { + if (headers.length % 2 !== 0) throw new InvalidArgumentError("headers array must be even"); + for (let i = 0; i < headers.length; i += 2) processHeader(this, headers[i], headers[i + 1]); + } else if (headers && typeof headers === "object") if (headers[Symbol.iterator]) for (const header of headers) { + if (!Array.isArray(header) || header.length !== 2) throw new InvalidArgumentError("headers must be in key-value pair format"); + processHeader(this, header[0], header[1]); + } + else { + const keys = Object.keys(headers); + for (let i = 0; i < keys.length; ++i) processHeader(this, keys[i], headers[keys[i]]); + } + else if (headers != null) throw new InvalidArgumentError("headers must be an object or an array"); + validateHandler(handler, method, upgrade); + this.servername = servername || getServerName(this.host); + this[kHandler] = handler; + if (channels.create.hasSubscribers) channels.create.publish({ request: this }); + } + onBodySent(chunk) { + if (this[kHandler].onBodySent) try { + return this[kHandler].onBodySent(chunk); + } catch (err) { + this.abort(err); + } + } + onRequestSent() { + if (channels.bodySent.hasSubscribers) channels.bodySent.publish({ request: this }); + if (this[kHandler].onRequestSent) try { + return this[kHandler].onRequestSent(); + } catch (err) { + this.abort(err); + } + } + onConnect(abort) { + assert$25(!this.aborted); + assert$25(!this.completed); + if (this.error) abort(this.error); + else { + this.abort = abort; + return this[kHandler].onConnect(abort); + } + } + onResponseStarted() { + return this[kHandler].onResponseStarted?.(); + } + onHeaders(statusCode, headers, resume, statusText) { + assert$25(!this.aborted); + assert$25(!this.completed); + if (channels.headers.hasSubscribers) channels.headers.publish({ + request: this, + response: { + statusCode, + headers, + statusText + } + }); + try { + return this[kHandler].onHeaders(statusCode, headers, resume, statusText); + } catch (err) { + this.abort(err); + } + } + onData(chunk) { + assert$25(!this.aborted); + assert$25(!this.completed); + try { + return this[kHandler].onData(chunk); + } catch (err) { + this.abort(err); + return false; + } + } + onUpgrade(statusCode, headers, socket) { + assert$25(!this.aborted); + assert$25(!this.completed); + return this[kHandler].onUpgrade(statusCode, headers, socket); + } + onComplete(trailers) { + this.onFinally(); + assert$25(!this.aborted); + this.completed = true; + if (channels.trailers.hasSubscribers) channels.trailers.publish({ + request: this, + trailers + }); + try { + return this[kHandler].onComplete(trailers); + } catch (err) { + this.onError(err); + } + } + onError(error) { + this.onFinally(); + if (channels.error.hasSubscribers) channels.error.publish({ + request: this, + error + }); + if (this.aborted) return; + this.aborted = true; + return this[kHandler].onError(error); + } + onFinally() { + if (this.errorHandler) { + this.body.off("error", this.errorHandler); + this.errorHandler = null; + } + if (this.endHandler) { + this.body.off("end", this.endHandler); + this.endHandler = null; + } + } + addHeader(key, value) { + processHeader(this, key, value); + return this; + } + }; + function processHeader(request, key, val) { + if (val && typeof val === "object" && !Array.isArray(val)) throw new InvalidArgumentError(`invalid ${key} header`); + else if (val === void 0) return; + let headerName = headerNameLowerCasedRecord[key]; + if (headerName === void 0) { + headerName = key.toLowerCase(); + if (headerNameLowerCasedRecord[headerName] === void 0 && !isValidHTTPToken(headerName)) throw new InvalidArgumentError("invalid header key"); + } + if (Array.isArray(val)) { + const arr = []; + for (let i = 0; i < val.length; i++) if (typeof val[i] === "string") { + if (!isValidHeaderValue(val[i])) throw new InvalidArgumentError(`invalid ${key} header`); + arr.push(val[i]); + } else if (val[i] === null) arr.push(""); + else if (typeof val[i] === "object") throw new InvalidArgumentError(`invalid ${key} header`); + else arr.push(`${val[i]}`); + val = arr; + } else if (typeof val === "string") { + if (!isValidHeaderValue(val)) throw new InvalidArgumentError(`invalid ${key} header`); + } else if (val === null) val = ""; + else val = `${val}`; + if (headerName === "host") { + if (request.host !== null) throw new InvalidArgumentError("duplicate host header"); + if (typeof val !== "string") throw new InvalidArgumentError("invalid host header"); + request.host = val; + } else if (headerName === "content-length") { + if (request.contentLength !== null) throw new InvalidArgumentError("duplicate content-length header"); + request.contentLength = parseInt(val, 10); + if (!Number.isFinite(request.contentLength)) throw new InvalidArgumentError("invalid content-length header"); + } else if (request.contentType === null && headerName === "content-type") { + request.contentType = val; + request.headers.push(key, val); + } else if (headerName === "transfer-encoding" || headerName === "keep-alive" || headerName === "upgrade") throw new InvalidArgumentError(`invalid ${headerName} header`); + else if (headerName === "connection") { + const value = typeof val === "string" ? val.toLowerCase() : null; + if (value !== "close" && value !== "keep-alive") throw new InvalidArgumentError("invalid connection header"); + if (value === "close") request.reset = true; + } else if (headerName === "expect") throw new NotSupportedError("expect header not supported"); + else request.headers.push(key, val); + } + module.exports = Request; +})); +//#endregion +//#region node_modules/undici/lib/dispatcher/dispatcher.js +var require_dispatcher = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var EventEmitter = __require("node:events"); + var Dispatcher = class extends EventEmitter { + dispatch() { + throw new Error("not implemented"); + } + close() { + throw new Error("not implemented"); + } + destroy() { + throw new Error("not implemented"); + } + compose(...args) { + const interceptors = Array.isArray(args[0]) ? args[0] : args; + let dispatch = this.dispatch.bind(this); + for (const interceptor of interceptors) { + if (interceptor == null) continue; + if (typeof interceptor !== "function") throw new TypeError(`invalid interceptor, expected function received ${typeof interceptor}`); + dispatch = interceptor(dispatch); + if (dispatch == null || typeof dispatch !== "function" || dispatch.length !== 2) throw new TypeError("invalid interceptor"); + } + return new ComposedDispatcher(this, dispatch); + } + }; + var ComposedDispatcher = class extends Dispatcher { + #dispatcher = null; + #dispatch = null; + constructor(dispatcher, dispatch) { + super(); + this.#dispatcher = dispatcher; + this.#dispatch = dispatch; + } + dispatch(...args) { + this.#dispatch(...args); + } + close(...args) { + return this.#dispatcher.close(...args); + } + destroy(...args) { + return this.#dispatcher.destroy(...args); + } + }; + module.exports = Dispatcher; +})); +//#endregion +//#region node_modules/undici/lib/dispatcher/dispatcher-base.js +var require_dispatcher_base = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var Dispatcher = require_dispatcher(); + var { ClientDestroyedError, ClientClosedError, InvalidArgumentError } = require_errors(); + var { kDestroy, kClose, kClosed, kDestroyed, kDispatch, kInterceptors } = require_symbols$4(); + var kOnDestroyed = Symbol("onDestroyed"); + var kOnClosed = Symbol("onClosed"); + var kInterceptedDispatch = Symbol("Intercepted Dispatch"); + var kWebSocketOptions = Symbol("webSocketOptions"); + var DispatcherBase = class extends Dispatcher { + constructor(opts) { + super(); + this[kDestroyed] = false; + this[kOnDestroyed] = null; + this[kClosed] = false; + this[kOnClosed] = []; + this[kWebSocketOptions] = opts?.webSocket ?? {}; + } + get webSocketOptions() { + return { + maxFragments: this[kWebSocketOptions].maxFragments ?? 131072, + maxPayloadSize: this[kWebSocketOptions].maxPayloadSize ?? 128 * 1024 * 1024 + }; + } + get destroyed() { + return this[kDestroyed]; + } + get closed() { + return this[kClosed]; + } + get interceptors() { + return this[kInterceptors]; + } + set interceptors(newInterceptors) { + if (newInterceptors) { + for (let i = newInterceptors.length - 1; i >= 0; i--) if (typeof this[kInterceptors][i] !== "function") throw new InvalidArgumentError("interceptor must be an function"); + } + this[kInterceptors] = newInterceptors; + } + close(callback) { + if (callback === void 0) return new Promise((resolve, reject) => { + this.close((err, data) => { + return err ? reject(err) : resolve(data); + }); + }); + if (typeof callback !== "function") throw new InvalidArgumentError("invalid callback"); + if (this[kDestroyed]) { + queueMicrotask(() => callback(new ClientDestroyedError(), null)); + return; + } + if (this[kClosed]) { + if (this[kOnClosed]) this[kOnClosed].push(callback); + else queueMicrotask(() => callback(null, null)); + return; + } + this[kClosed] = true; + this[kOnClosed].push(callback); + const onClosed = () => { + const callbacks = this[kOnClosed]; + this[kOnClosed] = null; + for (let i = 0; i < callbacks.length; i++) callbacks[i](null, null); + }; + this[kClose]().then(() => this.destroy()).then(() => { + queueMicrotask(onClosed); + }); + } + destroy(err, callback) { + if (typeof err === "function") { + callback = err; + err = null; + } + if (callback === void 0) return new Promise((resolve, reject) => { + this.destroy(err, (err, data) => { + return err ? reject(err) : resolve(data); + }); + }); + if (typeof callback !== "function") throw new InvalidArgumentError("invalid callback"); + if (this[kDestroyed]) { + if (this[kOnDestroyed]) this[kOnDestroyed].push(callback); + else queueMicrotask(() => callback(null, null)); + return; + } + if (!err) err = new ClientDestroyedError(); + this[kDestroyed] = true; + this[kOnDestroyed] = this[kOnDestroyed] || []; + this[kOnDestroyed].push(callback); + const onDestroyed = () => { + const callbacks = this[kOnDestroyed]; + this[kOnDestroyed] = null; + for (let i = 0; i < callbacks.length; i++) callbacks[i](null, null); + }; + this[kDestroy](err).then(() => { + queueMicrotask(onDestroyed); + }); + } + [kInterceptedDispatch](opts, handler) { + if (!this[kInterceptors] || this[kInterceptors].length === 0) { + this[kInterceptedDispatch] = this[kDispatch]; + return this[kDispatch](opts, handler); + } + let dispatch = this[kDispatch].bind(this); + for (let i = this[kInterceptors].length - 1; i >= 0; i--) dispatch = this[kInterceptors][i](dispatch); + this[kInterceptedDispatch] = dispatch; + return dispatch(opts, handler); + } + dispatch(opts, handler) { + if (!handler || typeof handler !== "object") throw new InvalidArgumentError("handler must be an object"); + try { + if (!opts || typeof opts !== "object") throw new InvalidArgumentError("opts must be an object."); + if (this[kDestroyed] || this[kOnDestroyed]) throw new ClientDestroyedError(); + if (this[kClosed]) throw new ClientClosedError(); + return this[kInterceptedDispatch](opts, handler); + } catch (err) { + if (typeof handler.onError !== "function") throw new InvalidArgumentError("invalid onError method"); + handler.onError(err); + return false; + } + } + }; + module.exports = DispatcherBase; +})); +//#endregion +//#region node_modules/undici/lib/util/timers.js +var require_timers = /* @__PURE__ */ __commonJSMin(((exports, module) => { + /** + * This module offers an optimized timer implementation designed for scenarios + * where high precision is not critical. + * + * The timer achieves faster performance by using a low-resolution approach, + * with an accuracy target of within 500ms. This makes it particularly useful + * for timers with delays of 1 second or more, where exact timing is less + * crucial. + * + * It's important to note that Node.js timers are inherently imprecise, as + * delays can occur due to the event loop being blocked by other operations. + * Consequently, timers may trigger later than their scheduled time. + */ + /** + * The fastNow variable contains the internal fast timer clock value. + * + * @type {number} + */ + var fastNow = 0; + /** + * RESOLUTION_MS represents the target resolution time in milliseconds. + * + * @type {number} + * @default 1000 + */ + var RESOLUTION_MS = 1e3; + /** + * TICK_MS defines the desired interval in milliseconds between each tick. + * The target value is set to half the resolution time, minus 1 ms, to account + * for potential event loop overhead. + * + * @type {number} + * @default 499 + */ + var TICK_MS = 499; + /** + * fastNowTimeout is a Node.js timer used to manage and process + * the FastTimers stored in the `fastTimers` array. + * + * @type {NodeJS.Timeout} + */ + var fastNowTimeout; + /** + * The kFastTimer symbol is used to identify FastTimer instances. + * + * @type {Symbol} + */ + var kFastTimer = Symbol("kFastTimer"); + /** + * The fastTimers array contains all active FastTimers. + * + * @type {FastTimer[]} + */ + var fastTimers = []; + /** + * These constants represent the various states of a FastTimer. + */ + /** + * The `NOT_IN_LIST` constant indicates that the FastTimer is not included + * in the `fastTimers` array. Timers with this status will not be processed + * during the next tick by the `onTick` function. + * + * A FastTimer can be re-added to the `fastTimers` array by invoking the + * `refresh` method on the FastTimer instance. + * + * @type {-2} + */ + var NOT_IN_LIST = -2; + /** + * The `TO_BE_CLEARED` constant indicates that the FastTimer is scheduled + * for removal from the `fastTimers` array. A FastTimer in this state will + * be removed in the next tick by the `onTick` function and will no longer + * be processed. + * + * This status is also set when the `clear` method is called on the FastTimer instance. + * + * @type {-1} + */ + var TO_BE_CLEARED = -1; + /** + * The `PENDING` constant signifies that the FastTimer is awaiting processing + * in the next tick by the `onTick` function. Timers with this status will have + * their `_idleStart` value set and their status updated to `ACTIVE` in the next tick. + * + * @type {0} + */ + var PENDING = 0; + /** + * The `ACTIVE` constant indicates that the FastTimer is active and waiting + * for its timer to expire. During the next tick, the `onTick` function will + * check if the timer has expired, and if so, it will execute the associated callback. + * + * @type {1} + */ + var ACTIVE = 1; + /** + * The onTick function processes the fastTimers array. + * + * @returns {void} + */ + function onTick() { + /** + * Increment the fastNow value by the TICK_MS value, despite the actual time + * that has passed since the last tick. This approach ensures independence + * from the system clock and delays caused by a blocked event loop. + * + * @type {number} + */ + fastNow += TICK_MS; + /** + * The `idx` variable is used to iterate over the `fastTimers` array. + * Expired timers are removed by replacing them with the last element in the array. + * Consequently, `idx` is only incremented when the current element is not removed. + * + * @type {number} + */ + let idx = 0; + /** + * The len variable will contain the length of the fastTimers array + * and will be decremented when a FastTimer should be removed from the + * fastTimers array. + * + * @type {number} + */ + let len = fastTimers.length; + while (idx < len) { + /** + * @type {FastTimer} + */ + const timer = fastTimers[idx]; + if (timer._state === PENDING) { + timer._idleStart = fastNow - TICK_MS; + timer._state = ACTIVE; + } else if (timer._state === ACTIVE && fastNow >= timer._idleStart + timer._idleTimeout) { + timer._state = TO_BE_CLEARED; + timer._idleStart = -1; + timer._onTimeout(timer._timerArg); + } + if (timer._state === TO_BE_CLEARED) { + timer._state = NOT_IN_LIST; + if (--len !== 0) fastTimers[idx] = fastTimers[len]; + } else ++idx; + } + fastTimers.length = len; + if (fastTimers.length !== 0) refreshTimeout(); + } + function refreshTimeout() { + if (fastNowTimeout) fastNowTimeout.refresh(); + else { + clearTimeout(fastNowTimeout); + fastNowTimeout = setTimeout(onTick, TICK_MS); + if (fastNowTimeout.unref) fastNowTimeout.unref(); + } + } + /** + * The `FastTimer` class is a data structure designed to store and manage + * timer information. + */ + var FastTimer = class { + [kFastTimer] = true; + /** + * The state of the timer, which can be one of the following: + * - NOT_IN_LIST (-2) + * - TO_BE_CLEARED (-1) + * - PENDING (0) + * - ACTIVE (1) + * + * @type {-2|-1|0|1} + * @private + */ + _state = NOT_IN_LIST; + /** + * The number of milliseconds to wait before calling the callback. + * + * @type {number} + * @private + */ + _idleTimeout = -1; + /** + * The time in milliseconds when the timer was started. This value is used to + * calculate when the timer should expire. + * + * @type {number} + * @default -1 + * @private + */ + _idleStart = -1; + /** + * The function to be executed when the timer expires. + * @type {Function} + * @private + */ + _onTimeout; + /** + * The argument to be passed to the callback when the timer expires. + * + * @type {*} + * @private + */ + _timerArg; + /** + * @constructor + * @param {Function} callback A function to be executed after the timer + * expires. + * @param {number} delay The time, in milliseconds that the timer should wait + * before the specified function or code is executed. + * @param {*} arg + */ + constructor(callback, delay, arg) { + this._onTimeout = callback; + this._idleTimeout = delay; + this._timerArg = arg; + this.refresh(); + } + /** + * Sets the timer's start time to the current time, and reschedules the timer + * to call its callback at the previously specified duration adjusted to the + * current time. + * Using this on a timer that has already called its callback will reactivate + * the timer. + * + * @returns {void} + */ + refresh() { + if (this._state === NOT_IN_LIST) fastTimers.push(this); + if (!fastNowTimeout || fastTimers.length === 1) refreshTimeout(); + this._state = PENDING; + } + /** + * The `clear` method cancels the timer, preventing it from executing. + * + * @returns {void} + * @private + */ + clear() { + this._state = TO_BE_CLEARED; + this._idleStart = -1; + } + }; + /** + * This module exports a setTimeout and clearTimeout function that can be + * used as a drop-in replacement for the native functions. + */ + module.exports = { + /** + * The setTimeout() method sets a timer which executes a function once the + * timer expires. + * @param {Function} callback A function to be executed after the timer + * expires. + * @param {number} delay The time, in milliseconds that the timer should + * wait before the specified function or code is executed. + * @param {*} [arg] An optional argument to be passed to the callback function + * when the timer expires. + * @returns {NodeJS.Timeout|FastTimer} + */ + setTimeout(callback, delay, arg) { + return delay <= RESOLUTION_MS ? setTimeout(callback, delay, arg) : new FastTimer(callback, delay, arg); + }, + /** + * The clearTimeout method cancels an instantiated Timer previously created + * by calling setTimeout. + * + * @param {NodeJS.Timeout|FastTimer} timeout + */ + clearTimeout(timeout) { + if (timeout[kFastTimer]) + /** + * @type {FastTimer} + */ + timeout.clear(); + else clearTimeout(timeout); + }, + /** + * The setFastTimeout() method sets a fastTimer which executes a function once + * the timer expires. + * @param {Function} callback A function to be executed after the timer + * expires. + * @param {number} delay The time, in milliseconds that the timer should + * wait before the specified function or code is executed. + * @param {*} [arg] An optional argument to be passed to the callback function + * when the timer expires. + * @returns {FastTimer} + */ + setFastTimeout(callback, delay, arg) { + return new FastTimer(callback, delay, arg); + }, + /** + * The clearTimeout method cancels an instantiated FastTimer previously + * created by calling setFastTimeout. + * + * @param {FastTimer} timeout + */ + clearFastTimeout(timeout) { + timeout.clear(); + }, + /** + * The now method returns the value of the internal fast timer clock. + * + * @returns {number} + */ + now() { + return fastNow; + }, + /** + * Trigger the onTick function to process the fastTimers array. + * Exported for testing purposes only. + * Marking as deprecated to discourage any use outside of testing. + * @deprecated + * @param {number} [delay=0] The delay in milliseconds to add to the now value. + */ + tick(delay = 0) { + fastNow += delay - RESOLUTION_MS + 1; + onTick(); + onTick(); + }, + /** + * Reset FastTimers. + * Exported for testing purposes only. + * Marking as deprecated to discourage any use outside of testing. + * @deprecated + */ + reset() { + fastNow = 0; + fastTimers.length = 0; + clearTimeout(fastNowTimeout); + fastNowTimeout = null; + }, + /** + * Exporting for testing purposes only. + * Marking as deprecated to discourage any use outside of testing. + * @deprecated + */ + kFastTimer + }; +})); +//#endregion +//#region node_modules/undici/lib/core/connect.js +var require_connect = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var net$1 = __require("node:net"); + var assert$24 = __require("node:assert"); + var util = require_util$7(); + var { InvalidArgumentError, ConnectTimeoutError } = require_errors(); + var timers = require_timers(); + function noop() {} + var tls; + var SessionCache; + if (global.FinalizationRegistry && !(process.env.NODE_V8_COVERAGE || process.env.UNDICI_NO_FG)) SessionCache = class WeakSessionCache { + constructor(maxCachedSessions) { + this._maxCachedSessions = maxCachedSessions; + this._sessionCache = /* @__PURE__ */ new Map(); + this._sessionRegistry = new global.FinalizationRegistry((key) => { + if (this._sessionCache.size < this._maxCachedSessions) return; + const ref = this._sessionCache.get(key); + if (ref !== void 0 && ref.deref() === void 0) this._sessionCache.delete(key); + }); + } + get(sessionKey) { + const ref = this._sessionCache.get(sessionKey); + return ref ? ref.deref() : null; + } + set(sessionKey, session) { + if (this._maxCachedSessions === 0) return; + this._sessionCache.set(sessionKey, new WeakRef(session)); + this._sessionRegistry.register(session, sessionKey); + } + }; + else SessionCache = class SimpleSessionCache { + constructor(maxCachedSessions) { + this._maxCachedSessions = maxCachedSessions; + this._sessionCache = /* @__PURE__ */ new Map(); + } + get(sessionKey) { + return this._sessionCache.get(sessionKey); + } + set(sessionKey, session) { + if (this._maxCachedSessions === 0) return; + if (this._sessionCache.size >= this._maxCachedSessions) { + const { value: oldestKey } = this._sessionCache.keys().next(); + this._sessionCache.delete(oldestKey); + } + this._sessionCache.set(sessionKey, session); + } + }; + function buildConnector({ allowH2, maxCachedSessions, socketPath, timeout, session: customSession, ...opts }) { + if (maxCachedSessions != null && (!Number.isInteger(maxCachedSessions) || maxCachedSessions < 0)) throw new InvalidArgumentError("maxCachedSessions must be a positive integer or zero"); + const options = { + path: socketPath, + ...opts + }; + const sessionCache = new SessionCache(maxCachedSessions == null ? 100 : maxCachedSessions); + timeout = timeout == null ? 1e4 : timeout; + allowH2 = allowH2 != null ? allowH2 : false; + return function connect({ hostname, host, protocol, port, servername, localAddress, httpSocket }, callback) { + let socket; + if (protocol === "https:") { + if (!tls) tls = __require("node:tls"); + servername = servername || options.servername || util.getServerName(host) || null; + const sessionKey = servername || hostname; + assert$24(sessionKey); + const session = customSession || sessionCache.get(sessionKey) || null; + port = port || 443; + socket = tls.connect({ + highWaterMark: 16384, + ...options, + servername, + session, + localAddress, + ALPNProtocols: allowH2 ? ["http/1.1", "h2"] : ["http/1.1"], + socket: httpSocket, + port, + host: hostname + }); + socket.on("session", function(session) { + sessionCache.set(sessionKey, session); + }); + } else { + assert$24(!httpSocket, "httpSocket can only be sent on TLS update"); + port = port || 80; + socket = net$1.connect({ + highWaterMark: 64 * 1024, + ...options, + localAddress, + port, + host: hostname + }); + } + if (options.keepAlive == null || options.keepAlive) { + const keepAliveInitialDelay = options.keepAliveInitialDelay === void 0 ? 6e4 : options.keepAliveInitialDelay; + socket.setKeepAlive(true, keepAliveInitialDelay); + } + const clearConnectTimeout = setupConnectTimeout(new WeakRef(socket), { + timeout, + hostname, + port + }); + socket.setNoDelay(true).once(protocol === "https:" ? "secureConnect" : "connect", function() { + queueMicrotask(clearConnectTimeout); + if (callback) { + const cb = callback; + callback = null; + cb(null, this); + } + }).on("error", function(err) { + queueMicrotask(clearConnectTimeout); + if (callback) { + const cb = callback; + callback = null; + cb(err); + } + }); + return socket; + }; + } + /** + * @param {WeakRef} socketWeakRef + * @param {object} opts + * @param {number} opts.timeout + * @param {string} opts.hostname + * @param {number} opts.port + * @returns {() => void} + */ + var setupConnectTimeout = process.platform === "win32" ? (socketWeakRef, opts) => { + if (!opts.timeout) return noop; + let s1 = null; + let s2 = null; + const fastTimer = timers.setFastTimeout(() => { + s1 = setImmediate(() => { + s2 = setImmediate(() => onConnectTimeout(socketWeakRef.deref(), opts)); + }); + }, opts.timeout); + return () => { + timers.clearFastTimeout(fastTimer); + clearImmediate(s1); + clearImmediate(s2); + }; + } : (socketWeakRef, opts) => { + if (!opts.timeout) return noop; + let s1 = null; + const fastTimer = timers.setFastTimeout(() => { + s1 = setImmediate(() => { + onConnectTimeout(socketWeakRef.deref(), opts); + }); + }, opts.timeout); + return () => { + timers.clearFastTimeout(fastTimer); + clearImmediate(s1); + }; + }; + /** + * @param {net.Socket} socket + * @param {object} opts + * @param {number} opts.timeout + * @param {string} opts.hostname + * @param {number} opts.port + */ + function onConnectTimeout(socket, opts) { + if (socket == null) return; + let message = "Connect Timeout Error"; + if (Array.isArray(socket.autoSelectFamilyAttemptedAddresses)) message += ` (attempted addresses: ${socket.autoSelectFamilyAttemptedAddresses.join(", ")},`; + else message += ` (attempted address: ${opts.hostname}:${opts.port},`; + message += ` timeout: ${opts.timeout}ms)`; + util.destroy(socket, new ConnectTimeoutError(message)); + } + module.exports = buildConnector; +})); +//#endregion +//#region node_modules/undici/lib/llhttp/utils.js +var require_utils = /* @__PURE__ */ __commonJSMin(((exports) => { + Object.defineProperty(exports, "__esModule", { value: true }); + exports.enumToMap = void 0; + function enumToMap(obj) { + const res = {}; + Object.keys(obj).forEach((key) => { + const value = obj[key]; + if (typeof value === "number") res[key] = value; + }); + return res; + } + exports.enumToMap = enumToMap; +})); +//#endregion +//#region node_modules/undici/lib/llhttp/constants.js +var require_constants$3 = /* @__PURE__ */ __commonJSMin(((exports) => { + Object.defineProperty(exports, "__esModule", { value: true }); + exports.SPECIAL_HEADERS = exports.HEADER_STATE = exports.MINOR = exports.MAJOR = exports.CONNECTION_TOKEN_CHARS = exports.HEADER_CHARS = exports.TOKEN = exports.STRICT_TOKEN = exports.HEX = exports.URL_CHAR = exports.STRICT_URL_CHAR = exports.USERINFO_CHARS = exports.MARK = exports.ALPHANUM = exports.NUM = exports.HEX_MAP = exports.NUM_MAP = exports.ALPHA = exports.FINISH = exports.H_METHOD_MAP = exports.METHOD_MAP = exports.METHODS_RTSP = exports.METHODS_ICE = exports.METHODS_HTTP = exports.METHODS = exports.LENIENT_FLAGS = exports.FLAGS = exports.TYPE = exports.ERROR = void 0; + var utils_1 = require_utils(); + (function(ERROR) { + ERROR[ERROR["OK"] = 0] = "OK"; + ERROR[ERROR["INTERNAL"] = 1] = "INTERNAL"; + ERROR[ERROR["STRICT"] = 2] = "STRICT"; + ERROR[ERROR["LF_EXPECTED"] = 3] = "LF_EXPECTED"; + ERROR[ERROR["UNEXPECTED_CONTENT_LENGTH"] = 4] = "UNEXPECTED_CONTENT_LENGTH"; + ERROR[ERROR["CLOSED_CONNECTION"] = 5] = "CLOSED_CONNECTION"; + ERROR[ERROR["INVALID_METHOD"] = 6] = "INVALID_METHOD"; + ERROR[ERROR["INVALID_URL"] = 7] = "INVALID_URL"; + ERROR[ERROR["INVALID_CONSTANT"] = 8] = "INVALID_CONSTANT"; + ERROR[ERROR["INVALID_VERSION"] = 9] = "INVALID_VERSION"; + ERROR[ERROR["INVALID_HEADER_TOKEN"] = 10] = "INVALID_HEADER_TOKEN"; + ERROR[ERROR["INVALID_CONTENT_LENGTH"] = 11] = "INVALID_CONTENT_LENGTH"; + ERROR[ERROR["INVALID_CHUNK_SIZE"] = 12] = "INVALID_CHUNK_SIZE"; + ERROR[ERROR["INVALID_STATUS"] = 13] = "INVALID_STATUS"; + ERROR[ERROR["INVALID_EOF_STATE"] = 14] = "INVALID_EOF_STATE"; + ERROR[ERROR["INVALID_TRANSFER_ENCODING"] = 15] = "INVALID_TRANSFER_ENCODING"; + ERROR[ERROR["CB_MESSAGE_BEGIN"] = 16] = "CB_MESSAGE_BEGIN"; + ERROR[ERROR["CB_HEADERS_COMPLETE"] = 17] = "CB_HEADERS_COMPLETE"; + ERROR[ERROR["CB_MESSAGE_COMPLETE"] = 18] = "CB_MESSAGE_COMPLETE"; + ERROR[ERROR["CB_CHUNK_HEADER"] = 19] = "CB_CHUNK_HEADER"; + ERROR[ERROR["CB_CHUNK_COMPLETE"] = 20] = "CB_CHUNK_COMPLETE"; + ERROR[ERROR["PAUSED"] = 21] = "PAUSED"; + ERROR[ERROR["PAUSED_UPGRADE"] = 22] = "PAUSED_UPGRADE"; + ERROR[ERROR["PAUSED_H2_UPGRADE"] = 23] = "PAUSED_H2_UPGRADE"; + ERROR[ERROR["USER"] = 24] = "USER"; + })(exports.ERROR || (exports.ERROR = {})); + (function(TYPE) { + TYPE[TYPE["BOTH"] = 0] = "BOTH"; + TYPE[TYPE["REQUEST"] = 1] = "REQUEST"; + TYPE[TYPE["RESPONSE"] = 2] = "RESPONSE"; + })(exports.TYPE || (exports.TYPE = {})); + (function(FLAGS) { + FLAGS[FLAGS["CONNECTION_KEEP_ALIVE"] = 1] = "CONNECTION_KEEP_ALIVE"; + FLAGS[FLAGS["CONNECTION_CLOSE"] = 2] = "CONNECTION_CLOSE"; + FLAGS[FLAGS["CONNECTION_UPGRADE"] = 4] = "CONNECTION_UPGRADE"; + FLAGS[FLAGS["CHUNKED"] = 8] = "CHUNKED"; + FLAGS[FLAGS["UPGRADE"] = 16] = "UPGRADE"; + FLAGS[FLAGS["CONTENT_LENGTH"] = 32] = "CONTENT_LENGTH"; + FLAGS[FLAGS["SKIPBODY"] = 64] = "SKIPBODY"; + FLAGS[FLAGS["TRAILING"] = 128] = "TRAILING"; + FLAGS[FLAGS["TRANSFER_ENCODING"] = 512] = "TRANSFER_ENCODING"; + })(exports.FLAGS || (exports.FLAGS = {})); + (function(LENIENT_FLAGS) { + LENIENT_FLAGS[LENIENT_FLAGS["HEADERS"] = 1] = "HEADERS"; + LENIENT_FLAGS[LENIENT_FLAGS["CHUNKED_LENGTH"] = 2] = "CHUNKED_LENGTH"; + LENIENT_FLAGS[LENIENT_FLAGS["KEEP_ALIVE"] = 4] = "KEEP_ALIVE"; + })(exports.LENIENT_FLAGS || (exports.LENIENT_FLAGS = {})); + var METHODS; + (function(METHODS) { + METHODS[METHODS["DELETE"] = 0] = "DELETE"; + METHODS[METHODS["GET"] = 1] = "GET"; + METHODS[METHODS["HEAD"] = 2] = "HEAD"; + METHODS[METHODS["POST"] = 3] = "POST"; + METHODS[METHODS["PUT"] = 4] = "PUT"; + METHODS[METHODS["CONNECT"] = 5] = "CONNECT"; + METHODS[METHODS["OPTIONS"] = 6] = "OPTIONS"; + METHODS[METHODS["TRACE"] = 7] = "TRACE"; + METHODS[METHODS["COPY"] = 8] = "COPY"; + METHODS[METHODS["LOCK"] = 9] = "LOCK"; + METHODS[METHODS["MKCOL"] = 10] = "MKCOL"; + METHODS[METHODS["MOVE"] = 11] = "MOVE"; + METHODS[METHODS["PROPFIND"] = 12] = "PROPFIND"; + METHODS[METHODS["PROPPATCH"] = 13] = "PROPPATCH"; + METHODS[METHODS["SEARCH"] = 14] = "SEARCH"; + METHODS[METHODS["UNLOCK"] = 15] = "UNLOCK"; + METHODS[METHODS["BIND"] = 16] = "BIND"; + METHODS[METHODS["REBIND"] = 17] = "REBIND"; + METHODS[METHODS["UNBIND"] = 18] = "UNBIND"; + METHODS[METHODS["ACL"] = 19] = "ACL"; + METHODS[METHODS["REPORT"] = 20] = "REPORT"; + METHODS[METHODS["MKACTIVITY"] = 21] = "MKACTIVITY"; + METHODS[METHODS["CHECKOUT"] = 22] = "CHECKOUT"; + METHODS[METHODS["MERGE"] = 23] = "MERGE"; + METHODS[METHODS["M-SEARCH"] = 24] = "M-SEARCH"; + METHODS[METHODS["NOTIFY"] = 25] = "NOTIFY"; + METHODS[METHODS["SUBSCRIBE"] = 26] = "SUBSCRIBE"; + METHODS[METHODS["UNSUBSCRIBE"] = 27] = "UNSUBSCRIBE"; + METHODS[METHODS["PATCH"] = 28] = "PATCH"; + METHODS[METHODS["PURGE"] = 29] = "PURGE"; + METHODS[METHODS["MKCALENDAR"] = 30] = "MKCALENDAR"; + METHODS[METHODS["LINK"] = 31] = "LINK"; + METHODS[METHODS["UNLINK"] = 32] = "UNLINK"; + METHODS[METHODS["SOURCE"] = 33] = "SOURCE"; + METHODS[METHODS["PRI"] = 34] = "PRI"; + METHODS[METHODS["DESCRIBE"] = 35] = "DESCRIBE"; + METHODS[METHODS["ANNOUNCE"] = 36] = "ANNOUNCE"; + METHODS[METHODS["SETUP"] = 37] = "SETUP"; + METHODS[METHODS["PLAY"] = 38] = "PLAY"; + METHODS[METHODS["PAUSE"] = 39] = "PAUSE"; + METHODS[METHODS["TEARDOWN"] = 40] = "TEARDOWN"; + METHODS[METHODS["GET_PARAMETER"] = 41] = "GET_PARAMETER"; + METHODS[METHODS["SET_PARAMETER"] = 42] = "SET_PARAMETER"; + METHODS[METHODS["REDIRECT"] = 43] = "REDIRECT"; + METHODS[METHODS["RECORD"] = 44] = "RECORD"; + METHODS[METHODS["FLUSH"] = 45] = "FLUSH"; + })(METHODS = exports.METHODS || (exports.METHODS = {})); + exports.METHODS_HTTP = [ + METHODS.DELETE, + METHODS.GET, + METHODS.HEAD, + METHODS.POST, + METHODS.PUT, + METHODS.CONNECT, + METHODS.OPTIONS, + METHODS.TRACE, + METHODS.COPY, + METHODS.LOCK, + METHODS.MKCOL, + METHODS.MOVE, + METHODS.PROPFIND, + METHODS.PROPPATCH, + METHODS.SEARCH, + METHODS.UNLOCK, + METHODS.BIND, + METHODS.REBIND, + METHODS.UNBIND, + METHODS.ACL, + METHODS.REPORT, + METHODS.MKACTIVITY, + METHODS.CHECKOUT, + METHODS.MERGE, + METHODS["M-SEARCH"], + METHODS.NOTIFY, + METHODS.SUBSCRIBE, + METHODS.UNSUBSCRIBE, + METHODS.PATCH, + METHODS.PURGE, + METHODS.MKCALENDAR, + METHODS.LINK, + METHODS.UNLINK, + METHODS.PRI, + METHODS.SOURCE + ]; + exports.METHODS_ICE = [METHODS.SOURCE]; + exports.METHODS_RTSP = [ + METHODS.OPTIONS, + METHODS.DESCRIBE, + METHODS.ANNOUNCE, + METHODS.SETUP, + METHODS.PLAY, + METHODS.PAUSE, + METHODS.TEARDOWN, + METHODS.GET_PARAMETER, + METHODS.SET_PARAMETER, + METHODS.REDIRECT, + METHODS.RECORD, + METHODS.FLUSH, + METHODS.GET, + METHODS.POST + ]; + exports.METHOD_MAP = utils_1.enumToMap(METHODS); + exports.H_METHOD_MAP = {}; + Object.keys(exports.METHOD_MAP).forEach((key) => { + if (/^H/.test(key)) exports.H_METHOD_MAP[key] = exports.METHOD_MAP[key]; + }); + (function(FINISH) { + FINISH[FINISH["SAFE"] = 0] = "SAFE"; + FINISH[FINISH["SAFE_WITH_CB"] = 1] = "SAFE_WITH_CB"; + FINISH[FINISH["UNSAFE"] = 2] = "UNSAFE"; + })(exports.FINISH || (exports.FINISH = {})); + exports.ALPHA = []; + for (let i = "A".charCodeAt(0); i <= "Z".charCodeAt(0); i++) { + exports.ALPHA.push(String.fromCharCode(i)); + exports.ALPHA.push(String.fromCharCode(i + 32)); + } + exports.NUM_MAP = { + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 6: 6, + 7: 7, + 8: 8, + 9: 9 + }; + exports.HEX_MAP = { + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 6: 6, + 7: 7, + 8: 8, + 9: 9, + A: 10, + B: 11, + C: 12, + D: 13, + E: 14, + F: 15, + a: 10, + b: 11, + c: 12, + d: 13, + e: 14, + f: 15 + }; + exports.NUM = [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9" + ]; + exports.ALPHANUM = exports.ALPHA.concat(exports.NUM); + exports.MARK = [ + "-", + "_", + ".", + "!", + "~", + "*", + "'", + "(", + ")" + ]; + exports.USERINFO_CHARS = exports.ALPHANUM.concat(exports.MARK).concat([ + "%", + ";", + ":", + "&", + "=", + "+", + "$", + "," + ]); + exports.STRICT_URL_CHAR = [ + "!", + "\"", + "$", + "%", + "&", + "'", + "(", + ")", + "*", + "+", + ",", + "-", + ".", + "/", + ":", + ";", + "<", + "=", + ">", + "@", + "[", + "\\", + "]", + "^", + "_", + "`", + "{", + "|", + "}", + "~" + ].concat(exports.ALPHANUM); + exports.URL_CHAR = exports.STRICT_URL_CHAR.concat([" ", "\f"]); + for (let i = 128; i <= 255; i++) exports.URL_CHAR.push(i); + exports.HEX = exports.NUM.concat([ + "a", + "b", + "c", + "d", + "e", + "f", + "A", + "B", + "C", + "D", + "E", + "F" + ]); + exports.STRICT_TOKEN = [ + "!", + "#", + "$", + "%", + "&", + "'", + "*", + "+", + "-", + ".", + "^", + "_", + "`", + "|", + "~" + ].concat(exports.ALPHANUM); + exports.TOKEN = exports.STRICT_TOKEN.concat([" "]); + exports.HEADER_CHARS = [" "]; + for (let i = 32; i <= 255; i++) if (i !== 127) exports.HEADER_CHARS.push(i); + exports.CONNECTION_TOKEN_CHARS = exports.HEADER_CHARS.filter((c) => c !== 44); + exports.MAJOR = exports.NUM_MAP; + exports.MINOR = exports.MAJOR; + var HEADER_STATE; + (function(HEADER_STATE) { + HEADER_STATE[HEADER_STATE["GENERAL"] = 0] = "GENERAL"; + HEADER_STATE[HEADER_STATE["CONNECTION"] = 1] = "CONNECTION"; + HEADER_STATE[HEADER_STATE["CONTENT_LENGTH"] = 2] = "CONTENT_LENGTH"; + HEADER_STATE[HEADER_STATE["TRANSFER_ENCODING"] = 3] = "TRANSFER_ENCODING"; + HEADER_STATE[HEADER_STATE["UPGRADE"] = 4] = "UPGRADE"; + HEADER_STATE[HEADER_STATE["CONNECTION_KEEP_ALIVE"] = 5] = "CONNECTION_KEEP_ALIVE"; + HEADER_STATE[HEADER_STATE["CONNECTION_CLOSE"] = 6] = "CONNECTION_CLOSE"; + HEADER_STATE[HEADER_STATE["CONNECTION_UPGRADE"] = 7] = "CONNECTION_UPGRADE"; + HEADER_STATE[HEADER_STATE["TRANSFER_ENCODING_CHUNKED"] = 8] = "TRANSFER_ENCODING_CHUNKED"; + })(HEADER_STATE = exports.HEADER_STATE || (exports.HEADER_STATE = {})); + exports.SPECIAL_HEADERS = { + "connection": HEADER_STATE.CONNECTION, + "content-length": HEADER_STATE.CONTENT_LENGTH, + "proxy-connection": HEADER_STATE.CONNECTION, + "transfer-encoding": HEADER_STATE.TRANSFER_ENCODING, + "upgrade": HEADER_STATE.UPGRADE + }; +})); +//#endregion +//#region node_modules/undici/lib/llhttp/llhttp-wasm.js +var require_llhttp_wasm = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { Buffer: Buffer$2 } = __require("node:buffer"); + module.exports = Buffer$2.from("AGFzbQEAAAABJwdgAX8Bf2ADf39/AX9gAX8AYAJ/fwBgBH9/f38Bf2AAAGADf39/AALLAQgDZW52GHdhc21fb25faGVhZGVyc19jb21wbGV0ZQAEA2VudhV3YXNtX29uX21lc3NhZ2VfYmVnaW4AAANlbnYLd2FzbV9vbl91cmwAAQNlbnYOd2FzbV9vbl9zdGF0dXMAAQNlbnYUd2FzbV9vbl9oZWFkZXJfZmllbGQAAQNlbnYUd2FzbV9vbl9oZWFkZXJfdmFsdWUAAQNlbnYMd2FzbV9vbl9ib2R5AAEDZW52GHdhc21fb25fbWVzc2FnZV9jb21wbGV0ZQAAAy0sBQYAAAIAAAAAAAACAQIAAgICAAADAAAAAAMDAwMBAQEBAQEBAQEAAAIAAAAEBQFwARISBQMBAAIGCAF/AUGA1AQLB9EFIgZtZW1vcnkCAAtfaW5pdGlhbGl6ZQAIGV9faW5kaXJlY3RfZnVuY3Rpb25fdGFibGUBAAtsbGh0dHBfaW5pdAAJGGxsaHR0cF9zaG91bGRfa2VlcF9hbGl2ZQAvDGxsaHR0cF9hbGxvYwALBm1hbGxvYwAxC2xsaHR0cF9mcmVlAAwEZnJlZQAMD2xsaHR0cF9nZXRfdHlwZQANFWxsaHR0cF9nZXRfaHR0cF9tYWpvcgAOFWxsaHR0cF9nZXRfaHR0cF9taW5vcgAPEWxsaHR0cF9nZXRfbWV0aG9kABAWbGxodHRwX2dldF9zdGF0dXNfY29kZQAREmxsaHR0cF9nZXRfdXBncmFkZQASDGxsaHR0cF9yZXNldAATDmxsaHR0cF9leGVjdXRlABQUbGxodHRwX3NldHRpbmdzX2luaXQAFQ1sbGh0dHBfZmluaXNoABYMbGxodHRwX3BhdXNlABcNbGxodHRwX3Jlc3VtZQAYG2xsaHR0cF9yZXN1bWVfYWZ0ZXJfdXBncmFkZQAZEGxsaHR0cF9nZXRfZXJybm8AGhdsbGh0dHBfZ2V0X2Vycm9yX3JlYXNvbgAbF2xsaHR0cF9zZXRfZXJyb3JfcmVhc29uABwUbGxodHRwX2dldF9lcnJvcl9wb3MAHRFsbGh0dHBfZXJybm9fbmFtZQAeEmxsaHR0cF9tZXRob2RfbmFtZQAfEmxsaHR0cF9zdGF0dXNfbmFtZQAgGmxsaHR0cF9zZXRfbGVuaWVudF9oZWFkZXJzACEhbGxodHRwX3NldF9sZW5pZW50X2NodW5rZWRfbGVuZ3RoACIdbGxodHRwX3NldF9sZW5pZW50X2tlZXBfYWxpdmUAIyRsbGh0dHBfc2V0X2xlbmllbnRfdHJhbnNmZXJfZW5jb2RpbmcAJBhsbGh0dHBfbWVzc2FnZV9uZWVkc19lb2YALgkXAQBBAQsRAQIDBAUKBgcrLSwqKSglJyYK07MCLBYAQYjQACgCAARAAAtBiNAAQQE2AgALFAAgABAwIAAgAjYCOCAAIAE6ACgLFAAgACAALwEyIAAtAC4gABAvEAALHgEBf0HAABAyIgEQMCABQYAINgI4IAEgADoAKCABC48MAQd/AkAgAEUNACAAQQhrIgEgAEEEaygCACIAQXhxIgRqIQUCQCAAQQFxDQAgAEEDcUUNASABIAEoAgAiAGsiAUGc0AAoAgBJDQEgACAEaiEEAkACQEGg0AAoAgAgAUcEQCAAQf8BTQRAIABBA3YhAyABKAIIIgAgASgCDCICRgRAQYzQAEGM0AAoAgBBfiADd3E2AgAMBQsgAiAANgIIIAAgAjYCDAwECyABKAIYIQYgASABKAIMIgBHBEAgACABKAIIIgI2AgggAiAANgIMDAMLIAFBFGoiAygCACICRQRAIAEoAhAiAkUNAiABQRBqIQMLA0AgAyEHIAIiAEEUaiIDKAIAIgINACAAQRBqIQMgACgCECICDQALIAdBADYCAAwCCyAFKAIEIgBBA3FBA0cNAiAFIABBfnE2AgRBlNAAIAQ2AgAgBSAENgIAIAEgBEEBcjYCBAwDC0EAIQALIAZFDQACQCABKAIcIgJBAnRBvNIAaiIDKAIAIAFGBEAgAyAANgIAIAANAUGQ0ABBkNAAKAIAQX4gAndxNgIADAILIAZBEEEUIAYoAhAgAUYbaiAANgIAIABFDQELIAAgBjYCGCABKAIQIgIEQCAAIAI2AhAgAiAANgIYCyABQRRqKAIAIgJFDQAgAEEUaiACNgIAIAIgADYCGAsgASAFTw0AIAUoAgQiAEEBcUUNAAJAAkACQAJAIABBAnFFBEBBpNAAKAIAIAVGBEBBpNAAIAE2AgBBmNAAQZjQACgCACAEaiIANgIAIAEgAEEBcjYCBCABQaDQACgCAEcNBkGU0ABBADYCAEGg0ABBADYCAAwGC0Gg0AAoAgAgBUYEQEGg0AAgATYCAEGU0ABBlNAAKAIAIARqIgA2AgAgASAAQQFyNgIEIAAgAWogADYCAAwGCyAAQXhxIARqIQQgAEH/AU0EQCAAQQN2IQMgBSgCCCIAIAUoAgwiAkYEQEGM0ABBjNAAKAIAQX4gA3dxNgIADAULIAIgADYCCCAAIAI2AgwMBAsgBSgCGCEGIAUgBSgCDCIARwRAQZzQACgCABogACAFKAIIIgI2AgggAiAANgIMDAMLIAVBFGoiAygCACICRQRAIAUoAhAiAkUNAiAFQRBqIQMLA0AgAyEHIAIiAEEUaiIDKAIAIgINACAAQRBqIQMgACgCECICDQALIAdBADYCAAwCCyAFIABBfnE2AgQgASAEaiAENgIAIAEgBEEBcjYCBAwDC0EAIQALIAZFDQACQCAFKAIcIgJBAnRBvNIAaiIDKAIAIAVGBEAgAyAANgIAIAANAUGQ0ABBkNAAKAIAQX4gAndxNgIADAILIAZBEEEUIAYoAhAgBUYbaiAANgIAIABFDQELIAAgBjYCGCAFKAIQIgIEQCAAIAI2AhAgAiAANgIYCyAFQRRqKAIAIgJFDQAgAEEUaiACNgIAIAIgADYCGAsgASAEaiAENgIAIAEgBEEBcjYCBCABQaDQACgCAEcNAEGU0AAgBDYCAAwBCyAEQf8BTQRAIARBeHFBtNAAaiEAAn9BjNAAKAIAIgJBASAEQQN2dCIDcUUEQEGM0AAgAiADcjYCACAADAELIAAoAggLIgIgATYCDCAAIAE2AgggASAANgIMIAEgAjYCCAwBC0EfIQIgBEH///8HTQRAIARBJiAEQQh2ZyIAa3ZBAXEgAEEBdGtBPmohAgsgASACNgIcIAFCADcCECACQQJ0QbzSAGohAAJAQZDQACgCACIDQQEgAnQiB3FFBEAgACABNgIAQZDQACADIAdyNgIAIAEgADYCGCABIAE2AgggASABNgIMDAELIARBGSACQQF2a0EAIAJBH0cbdCECIAAoAgAhAAJAA0AgACIDKAIEQXhxIARGDQEgAkEddiEAIAJBAXQhAiADIABBBHFqQRBqIgcoAgAiAA0ACyAHIAE2AgAgASADNgIYIAEgATYCDCABIAE2AggMAQsgAygCCCIAIAE2AgwgAyABNgIIIAFBADYCGCABIAM2AgwgASAANgIIC0Gs0ABBrNAAKAIAQQFrIgBBfyAAGzYCAAsLBwAgAC0AKAsHACAALQAqCwcAIAAtACsLBwAgAC0AKQsHACAALwEyCwcAIAAtAC4LQAEEfyAAKAIYIQEgAC0ALSECIAAtACghAyAAKAI4IQQgABAwIAAgBDYCOCAAIAM6ACggACACOgAtIAAgATYCGAu74gECB38DfiABIAJqIQQCQCAAIgIoAgwiAA0AIAIoAgQEQCACIAE2AgQLIwBBEGsiCCQAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIAIoAhwiA0EBaw7dAdoBAdkBAgMEBQYHCAkKCwwNDtgBDxDXARES1gETFBUWFxgZGhvgAd8BHB0e1QEfICEiIyQl1AEmJygpKiss0wHSAS0u0QHQAS8wMTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRtsBR0hJSs8BzgFLzQFMzAFNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AAYEBggGDAYQBhQGGAYcBiAGJAYoBiwGMAY0BjgGPAZABkQGSAZMBlAGVAZYBlwGYAZkBmgGbAZwBnQGeAZ8BoAGhAaIBowGkAaUBpgGnAagBqQGqAasBrAGtAa4BrwGwAbEBsgGzAbQBtQG2AbcBywHKAbgByQG5AcgBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgEA3AELQQAMxgELQQ4MxQELQQ0MxAELQQ8MwwELQRAMwgELQRMMwQELQRQMwAELQRUMvwELQRYMvgELQRgMvQELQRkMvAELQRoMuwELQRsMugELQRwMuQELQR0MuAELQQgMtwELQR4MtgELQSAMtQELQR8MtAELQQcMswELQSEMsgELQSIMsQELQSMMsAELQSQMrwELQRIMrgELQREMrQELQSUMrAELQSYMqwELQScMqgELQSgMqQELQcMBDKgBC0EqDKcBC0ErDKYBC0EsDKUBC0EtDKQBC0EuDKMBC0EvDKIBC0HEAQyhAQtBMAygAQtBNAyfAQtBDAyeAQtBMQydAQtBMgycAQtBMwybAQtBOQyaAQtBNQyZAQtBxQEMmAELQQsMlwELQToMlgELQTYMlQELQQoMlAELQTcMkwELQTgMkgELQTwMkQELQTsMkAELQT0MjwELQQkMjgELQSkMjQELQT4MjAELQT8MiwELQcAADIoBC0HBAAyJAQtBwgAMiAELQcMADIcBC0HEAAyGAQtBxQAMhQELQcYADIQBC0EXDIMBC0HHAAyCAQtByAAMgQELQckADIABC0HKAAx/C0HLAAx+C0HNAAx9C0HMAAx8C0HOAAx7C0HPAAx6C0HQAAx5C0HRAAx4C0HSAAx3C0HTAAx2C0HUAAx1C0HWAAx0C0HVAAxzC0EGDHILQdcADHELQQUMcAtB2AAMbwtBBAxuC0HZAAxtC0HaAAxsC0HbAAxrC0HcAAxqC0EDDGkLQd0ADGgLQd4ADGcLQd8ADGYLQeEADGULQeAADGQLQeIADGMLQeMADGILQQIMYQtB5AAMYAtB5QAMXwtB5gAMXgtB5wAMXQtB6AAMXAtB6QAMWwtB6gAMWgtB6wAMWQtB7AAMWAtB7QAMVwtB7gAMVgtB7wAMVQtB8AAMVAtB8QAMUwtB8gAMUgtB8wAMUQtB9AAMUAtB9QAMTwtB9gAMTgtB9wAMTQtB+AAMTAtB+QAMSwtB+gAMSgtB+wAMSQtB/AAMSAtB/QAMRwtB/gAMRgtB/wAMRQtBgAEMRAtBgQEMQwtBggEMQgtBgwEMQQtBhAEMQAtBhQEMPwtBhgEMPgtBhwEMPQtBiAEMPAtBiQEMOwtBigEMOgtBiwEMOQtBjAEMOAtBjQEMNwtBjgEMNgtBjwEMNQtBkAEMNAtBkQEMMwtBkgEMMgtBkwEMMQtBlAEMMAtBlQEMLwtBlgEMLgtBlwEMLQtBmAEMLAtBmQEMKwtBmgEMKgtBmwEMKQtBnAEMKAtBnQEMJwtBngEMJgtBnwEMJQtBoAEMJAtBoQEMIwtBogEMIgtBowEMIQtBpAEMIAtBpQEMHwtBpgEMHgtBpwEMHQtBqAEMHAtBqQEMGwtBqgEMGgtBqwEMGQtBrAEMGAtBrQEMFwtBrgEMFgtBAQwVC0GvAQwUC0GwAQwTC0GxAQwSC0GzAQwRC0GyAQwQC0G0AQwPC0G1AQwOC0G2AQwNC0G3AQwMC0G4AQwLC0G5AQwKC0G6AQwJC0G7AQwIC0HGAQwHC0G8AQwGC0G9AQwFC0G+AQwEC0G/AQwDC0HAAQwCC0HCAQwBC0HBAQshAwNAAkACQAJAAkACQAJAAkACQAJAIAICfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJ/AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAgJ/AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACfwJAAkACfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACfwJAAkACQAJAAn8CQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCADDsYBAAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHyAhIyUmKCorLC8wMTIzNDU2Nzk6Ozw9lANAQkRFRklLTk9QUVJTVFVWWFpbXF1eX2BhYmNkZWZnaGpsb3Bxc3V2eHl6e3x/gAGBAYIBgwGEAYUBhgGHAYgBiQGKAYsBjAGNAY4BjwGQAZEBkgGTAZQBlQGWAZcBmAGZAZoBmwGcAZ0BngGfAaABoQGiAaMBpAGlAaYBpwGoAakBqgGrAawBrQGuAa8BsAGxAbIBswG0AbUBtgG3AbgBuQG6AbsBvAG9Ab4BvwHAAcEBwgHDAcQBxQHGAccByAHJAcsBzAHNAc4BzwGKA4kDiAOHA4QDgwOAA/sC+gL5AvgC9wL0AvMC8gLLAsECsALZAQsgASAERw3wAkHdASEDDLMDCyABIARHDcgBQcMBIQMMsgMLIAEgBEcNe0H3ACEDDLEDCyABIARHDXBB7wAhAwywAwsgASAERw1pQeoAIQMMrwMLIAEgBEcNZUHoACEDDK4DCyABIARHDWJB5gAhAwytAwsgASAERw0aQRghAwysAwsgASAERw0VQRIhAwyrAwsgASAERw1CQcUAIQMMqgMLIAEgBEcNNEE/IQMMqQMLIAEgBEcNMkE8IQMMqAMLIAEgBEcNK0ExIQMMpwMLIAItAC5BAUYNnwMMwQILQQAhAAJAAkACQCACLQAqRQ0AIAItACtFDQAgAi8BMCIDQQJxRQ0BDAILIAIvATAiA0EBcUUNAQtBASEAIAItAChBAUYNACACLwEyIgVB5ABrQeQASQ0AIAVBzAFGDQAgBUGwAkYNACADQcAAcQ0AQQAhACADQYgEcUGABEYNACADQShxQQBHIQALIAJBADsBMCACQQA6AC8gAEUN3wIgAkIANwMgDOACC0EAIQACQCACKAI4IgNFDQAgAygCLCIDRQ0AIAIgAxEAACEACyAARQ3MASAAQRVHDd0CIAJBBDYCHCACIAE2AhQgAkGwGDYCECACQRU2AgxBACEDDKQDCyABIARGBEBBBiEDDKQDCyABQQFqIQFBACEAAkAgAigCOCIDRQ0AIAMoAlQiA0UNACACIAMRAAAhAAsgAA3ZAgwcCyACQgA3AyBBEiEDDIkDCyABIARHDRZBHSEDDKEDCyABIARHBEAgAUEBaiEBQRAhAwyIAwtBByEDDKADCyACIAIpAyAiCiAEIAFrrSILfSIMQgAgCiAMWhs3AyAgCiALWA3UAkEIIQMMnwMLIAEgBEcEQCACQQk2AgggAiABNgIEQRQhAwyGAwtBCSEDDJ4DCyACKQMgQgBSDccBIAIgAi8BMEGAAXI7ATAMQgsgASAERw0/QdAAIQMMnAMLIAEgBEYEQEELIQMMnAMLIAFBAWohAUEAIQACQCACKAI4IgNFDQAgAygCUCIDRQ0AIAIgAxEAACEACyAADc8CDMYBC0EAIQACQCACKAI4IgNFDQAgAygCSCIDRQ0AIAIgAxEAACEACyAARQ3GASAAQRVHDc0CIAJBCzYCHCACIAE2AhQgAkGCGTYCECACQRU2AgxBACEDDJoDC0EAIQACQCACKAI4IgNFDQAgAygCSCIDRQ0AIAIgAxEAACEACyAARQ0MIABBFUcNygIgAkEaNgIcIAIgATYCFCACQYIZNgIQIAJBFTYCDEEAIQMMmQMLQQAhAAJAIAIoAjgiA0UNACADKAJMIgNFDQAgAiADEQAAIQALIABFDcQBIABBFUcNxwIgAkELNgIcIAIgATYCFCACQZEXNgIQIAJBFTYCDEEAIQMMmAMLIAEgBEYEQEEPIQMMmAMLIAEtAAAiAEE7Rg0HIABBDUcNxAIgAUEBaiEBDMMBC0EAIQACQCACKAI4IgNFDQAgAygCTCIDRQ0AIAIgAxEAACEACyAARQ3DASAAQRVHDcICIAJBDzYCHCACIAE2AhQgAkGRFzYCECACQRU2AgxBACEDDJYDCwNAIAEtAABB8DVqLQAAIgBBAUcEQCAAQQJHDcECIAIoAgQhAEEAIQMgAkEANgIEIAIgACABQQFqIgEQLSIADcICDMUBCyAEIAFBAWoiAUcNAAtBEiEDDJUDC0EAIQACQCACKAI4IgNFDQAgAygCTCIDRQ0AIAIgAxEAACEACyAARQ3FASAAQRVHDb0CIAJBGzYCHCACIAE2AhQgAkGRFzYCECACQRU2AgxBACEDDJQDCyABIARGBEBBFiEDDJQDCyACQQo2AgggAiABNgIEQQAhAAJAIAIoAjgiA0UNACADKAJIIgNFDQAgAiADEQAAIQALIABFDcIBIABBFUcNuQIgAkEVNgIcIAIgATYCFCACQYIZNgIQIAJBFTYCDEEAIQMMkwMLIAEgBEcEQANAIAEtAABB8DdqLQAAIgBBAkcEQAJAIABBAWsOBMQCvQIAvgK9AgsgAUEBaiEBQQghAwz8AgsgBCABQQFqIgFHDQALQRUhAwyTAwtBFSEDDJIDCwNAIAEtAABB8DlqLQAAIgBBAkcEQCAAQQFrDgTFArcCwwK4ArcCCyAEIAFBAWoiAUcNAAtBGCEDDJEDCyABIARHBEAgAkELNgIIIAIgATYCBEEHIQMM+AILQRkhAwyQAwsgAUEBaiEBDAILIAEgBEYEQEEaIQMMjwMLAkAgAS0AAEENaw4UtQG/Ab8BvwG/Ab8BvwG/Ab8BvwG/Ab8BvwG/Ab8BvwG/Ab8BvwEAvwELQQAhAyACQQA2AhwgAkGvCzYCECACQQI2AgwgAiABQQFqNgIUDI4DCyABIARGBEBBGyEDDI4DCyABLQAAIgBBO0cEQCAAQQ1HDbECIAFBAWohAQy6AQsgAUEBaiEBC0EiIQMM8wILIAEgBEYEQEEcIQMMjAMLQgAhCgJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAS0AAEEwaw43wQLAAgABAgMEBQYH0AHQAdAB0AHQAdAB0AEICQoLDA3QAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdABDg8QERIT0AELQgIhCgzAAgtCAyEKDL8CC0IEIQoMvgILQgUhCgy9AgtCBiEKDLwCC0IHIQoMuwILQgghCgy6AgtCCSEKDLkCC0IKIQoMuAILQgshCgy3AgtCDCEKDLYCC0INIQoMtQILQg4hCgy0AgtCDyEKDLMCC0IKIQoMsgILQgshCgyxAgtCDCEKDLACC0INIQoMrwILQg4hCgyuAgtCDyEKDK0CC0IAIQoCQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIAEtAABBMGsON8ACvwIAAQIDBAUGB74CvgK+Ar4CvgK+Ar4CCAkKCwwNvgK+Ar4CvgK+Ar4CvgK+Ar4CvgK+Ar4CvgK+Ar4CvgK+Ar4CvgK+Ar4CvgK+Ar4CvgK+Ag4PEBESE74CC0ICIQoMvwILQgMhCgy+AgtCBCEKDL0CC0IFIQoMvAILQgYhCgy7AgtCByEKDLoCC0IIIQoMuQILQgkhCgy4AgtCCiEKDLcCC0ILIQoMtgILQgwhCgy1AgtCDSEKDLQCC0IOIQoMswILQg8hCgyyAgtCCiEKDLECC0ILIQoMsAILQgwhCgyvAgtCDSEKDK4CC0IOIQoMrQILQg8hCgysAgsgAiACKQMgIgogBCABa60iC30iDEIAIAogDFobNwMgIAogC1gNpwJBHyEDDIkDCyABIARHBEAgAkEJNgIIIAIgATYCBEElIQMM8AILQSAhAwyIAwtBASEFIAIvATAiA0EIcUUEQCACKQMgQgBSIQULAkAgAi0ALgRAQQEhACACLQApQQVGDQEgA0HAAHFFIAVxRQ0BC0EAIQAgA0HAAHENAEECIQAgA0EIcQ0AIANBgARxBEACQCACLQAoQQFHDQAgAi0ALUEKcQ0AQQUhAAwCC0EEIQAMAQsgA0EgcUUEQAJAIAItAChBAUYNACACLwEyIgBB5ABrQeQASQ0AIABBzAFGDQAgAEGwAkYNAEEEIQAgA0EocUUNAiADQYgEcUGABEYNAgtBACEADAELQQBBAyACKQMgUBshAAsgAEEBaw4FvgIAsAEBpAKhAgtBESEDDO0CCyACQQE6AC8MhAMLIAEgBEcNnQJBJCEDDIQDCyABIARHDRxBxgAhAwyDAwtBACEAAkAgAigCOCIDRQ0AIAMoAkQiA0UNACACIAMRAAAhAAsgAEUNJyAAQRVHDZgCIAJB0AA2AhwgAiABNgIUIAJBkRg2AhAgAkEVNgIMQQAhAwyCAwsgASAERgRAQSghAwyCAwtBACEDIAJBADYCBCACQQw2AgggAiABIAEQKiIARQ2UAiACQSc2AhwgAiABNgIUIAIgADYCDAyBAwsgASAERgRAQSkhAwyBAwsgAS0AACIAQSBGDRMgAEEJRw2VAiABQQFqIQEMFAsgASAERwRAIAFBAWohAQwWC0EqIQMM/wILIAEgBEYEQEErIQMM/wILIAEtAAAiAEEJRyAAQSBHcQ2QAiACLQAsQQhHDd0CIAJBADoALAzdAgsgASAERgRAQSwhAwz+AgsgAS0AAEEKRw2OAiABQQFqIQEMsAELIAEgBEcNigJBLyEDDPwCCwNAIAEtAAAiAEEgRwRAIABBCmsOBIQCiAKIAoQChgILIAQgAUEBaiIBRw0AC0ExIQMM+wILQTIhAyABIARGDfoCIAIoAgAiACAEIAFraiEHIAEgAGtBA2ohBgJAA0AgAEHwO2otAAAgAS0AACIFQSByIAUgBUHBAGtB/wFxQRpJG0H/AXFHDQEgAEEDRgRAQQYhAQziAgsgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAc2AgAM+wILIAJBADYCAAyGAgtBMyEDIAQgASIARg35AiAEIAFrIAIoAgAiAWohByAAIAFrQQhqIQYCQANAIAFB9DtqLQAAIAAtAAAiBUEgciAFIAVBwQBrQf8BcUEaSRtB/wFxRw0BIAFBCEYEQEEFIQEM4QILIAFBAWohASAEIABBAWoiAEcNAAsgAiAHNgIADPoCCyACQQA2AgAgACEBDIUCC0E0IQMgBCABIgBGDfgCIAQgAWsgAigCACIBaiEHIAAgAWtBBWohBgJAA0AgAUHQwgBqLQAAIAAtAAAiBUEgciAFIAVBwQBrQf8BcUEaSRtB/wFxRw0BIAFBBUYEQEEHIQEM4AILIAFBAWohASAEIABBAWoiAEcNAAsgAiAHNgIADPkCCyACQQA2AgAgACEBDIQCCyABIARHBEADQCABLQAAQYA+ai0AACIAQQFHBEAgAEECRg0JDIECCyAEIAFBAWoiAUcNAAtBMCEDDPgCC0EwIQMM9wILIAEgBEcEQANAIAEtAAAiAEEgRwRAIABBCmsOBP8B/gH+Af8B/gELIAQgAUEBaiIBRw0AC0E4IQMM9wILQTghAwz2AgsDQCABLQAAIgBBIEcgAEEJR3EN9gEgBCABQQFqIgFHDQALQTwhAwz1AgsDQCABLQAAIgBBIEcEQAJAIABBCmsOBPkBBAT5AQALIABBLEYN9QEMAwsgBCABQQFqIgFHDQALQT8hAwz0AgtBwAAhAyABIARGDfMCIAIoAgAiACAEIAFraiEFIAEgAGtBBmohBgJAA0AgAEGAQGstAAAgAS0AAEEgckcNASAAQQZGDdsCIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADPQCCyACQQA2AgALQTYhAwzZAgsgASAERgRAQcEAIQMM8gILIAJBDDYCCCACIAE2AgQgAi0ALEEBaw4E+wHuAewB6wHUAgsgAUEBaiEBDPoBCyABIARHBEADQAJAIAEtAAAiAEEgciAAIABBwQBrQf8BcUEaSRtB/wFxIgBBCUYNACAAQSBGDQACQAJAAkACQCAAQeMAaw4TAAMDAwMDAwMBAwMDAwMDAwMDAgMLIAFBAWohAUExIQMM3AILIAFBAWohAUEyIQMM2wILIAFBAWohAUEzIQMM2gILDP4BCyAEIAFBAWoiAUcNAAtBNSEDDPACC0E1IQMM7wILIAEgBEcEQANAIAEtAABBgDxqLQAAQQFHDfcBIAQgAUEBaiIBRw0AC0E9IQMM7wILQT0hAwzuAgtBACEAAkAgAigCOCIDRQ0AIAMoAkAiA0UNACACIAMRAAAhAAsgAEUNASAAQRVHDeYBIAJBwgA2AhwgAiABNgIUIAJB4xg2AhAgAkEVNgIMQQAhAwztAgsgAUEBaiEBC0E8IQMM0gILIAEgBEYEQEHCACEDDOsCCwJAA0ACQCABLQAAQQlrDhgAAswCzALRAswCzALMAswCzALMAswCzALMAswCzALMAswCzALMAswCzALMAgDMAgsgBCABQQFqIgFHDQALQcIAIQMM6wILIAFBAWohASACLQAtQQFxRQ3+AQtBLCEDDNACCyABIARHDd4BQcQAIQMM6AILA0AgAS0AAEGQwABqLQAAQQFHDZwBIAQgAUEBaiIBRw0AC0HFACEDDOcCCyABLQAAIgBBIEYN/gEgAEE6Rw3AAiACKAIEIQBBACEDIAJBADYCBCACIAAgARApIgAN3gEM3QELQccAIQMgBCABIgBGDeUCIAQgAWsgAigCACIBaiEHIAAgAWtBBWohBgNAIAFBkMIAai0AACAALQAAIgVBIHIgBSAFQcEAa0H/AXFBGkkbQf8BcUcNvwIgAUEFRg3CAiABQQFqIQEgBCAAQQFqIgBHDQALIAIgBzYCAAzlAgtByAAhAyAEIAEiAEYN5AIgBCABayACKAIAIgFqIQcgACABa0EJaiEGA0AgAUGWwgBqLQAAIAAtAAAiBUEgciAFIAVBwQBrQf8BcUEaSRtB/wFxRw2+AkECIAFBCUYNwgIaIAFBAWohASAEIABBAWoiAEcNAAsgAiAHNgIADOQCCyABIARGBEBByQAhAwzkAgsCQAJAIAEtAAAiAEEgciAAIABBwQBrQf8BcUEaSRtB/wFxQe4Aaw4HAL8CvwK/Ar8CvwIBvwILIAFBAWohAUE+IQMMywILIAFBAWohAUE/IQMMygILQcoAIQMgBCABIgBGDeICIAQgAWsgAigCACIBaiEGIAAgAWtBAWohBwNAIAFBoMIAai0AACAALQAAIgVBIHIgBSAFQcEAa0H/AXFBGkkbQf8BcUcNvAIgAUEBRg2+AiABQQFqIQEgBCAAQQFqIgBHDQALIAIgBjYCAAziAgtBywAhAyAEIAEiAEYN4QIgBCABayACKAIAIgFqIQcgACABa0EOaiEGA0AgAUGiwgBqLQAAIAAtAAAiBUEgciAFIAVBwQBrQf8BcUEaSRtB/wFxRw27AiABQQ5GDb4CIAFBAWohASAEIABBAWoiAEcNAAsgAiAHNgIADOECC0HMACEDIAQgASIARg3gAiAEIAFrIAIoAgAiAWohByAAIAFrQQ9qIQYDQCABQcDCAGotAAAgAC0AACIFQSByIAUgBUHBAGtB/wFxQRpJG0H/AXFHDboCQQMgAUEPRg2+AhogAUEBaiEBIAQgAEEBaiIARw0ACyACIAc2AgAM4AILQc0AIQMgBCABIgBGDd8CIAQgAWsgAigCACIBaiEHIAAgAWtBBWohBgNAIAFB0MIAai0AACAALQAAIgVBIHIgBSAFQcEAa0H/AXFBGkkbQf8BcUcNuQJBBCABQQVGDb0CGiABQQFqIQEgBCAAQQFqIgBHDQALIAIgBzYCAAzfAgsgASAERgRAQc4AIQMM3wILAkACQAJAAkAgAS0AACIAQSByIAAgAEHBAGtB/wFxQRpJG0H/AXFB4wBrDhMAvAK8ArwCvAK8ArwCvAK8ArwCvAK8ArwCAbwCvAK8AgIDvAILIAFBAWohAUHBACEDDMgCCyABQQFqIQFBwgAhAwzHAgsgAUEBaiEBQcMAIQMMxgILIAFBAWohAUHEACEDDMUCCyABIARHBEAgAkENNgIIIAIgATYCBEHFACEDDMUCC0HPACEDDN0CCwJAAkAgAS0AAEEKaw4EAZABkAEAkAELIAFBAWohAQtBKCEDDMMCCyABIARGBEBB0QAhAwzcAgsgAS0AAEEgRw0AIAFBAWohASACLQAtQQFxRQ3QAQtBFyEDDMECCyABIARHDcsBQdIAIQMM2QILQdMAIQMgASAERg3YAiACKAIAIgAgBCABa2ohBiABIABrQQFqIQUDQCABLQAAIABB1sIAai0AAEcNxwEgAEEBRg3KASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBjYCAAzYAgsgASAERgRAQdUAIQMM2AILIAEtAABBCkcNwgEgAUEBaiEBDMoBCyABIARGBEBB1gAhAwzXAgsCQAJAIAEtAABBCmsOBADDAcMBAcMBCyABQQFqIQEMygELIAFBAWohAUHKACEDDL0CC0EAIQACQCACKAI4IgNFDQAgAygCPCIDRQ0AIAIgAxEAACEACyAADb8BQc0AIQMMvAILIAItAClBIkYNzwIMiQELIAQgASIFRgRAQdsAIQMM1AILQQAhAEEBIQFBASEGQQAhAwJAAn8CQAJAAkACQAJAAkACQCAFLQAAQTBrDgrFAcQBAAECAwQFBgjDAQtBAgwGC0EDDAULQQQMBAtBBQwDC0EGDAILQQcMAQtBCAshA0EAIQFBACEGDL0BC0EJIQNBASEAQQAhAUEAIQYMvAELIAEgBEYEQEHdACEDDNMCCyABLQAAQS5HDbgBIAFBAWohAQyIAQsgASAERw22AUHfACEDDNECCyABIARHBEAgAkEONgIIIAIgATYCBEHQACEDDLgCC0HgACEDDNACC0HhACEDIAEgBEYNzwIgAigCACIAIAQgAWtqIQUgASAAa0EDaiEGA0AgAS0AACAAQeLCAGotAABHDbEBIABBA0YNswEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMzwILQeIAIQMgASAERg3OAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYDQCABLQAAIABB5sIAai0AAEcNsAEgAEECRg2vASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAzOAgtB4wAhAyABIARGDc0CIAIoAgAiACAEIAFraiEFIAEgAGtBA2ohBgNAIAEtAAAgAEHpwgBqLQAARw2vASAAQQNGDa0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADM0CCyABIARGBEBB5QAhAwzNAgsgAUEBaiEBQQAhAAJAIAIoAjgiA0UNACADKAIwIgNFDQAgAiADEQAAIQALIAANqgFB1gAhAwyzAgsgASAERwRAA0AgAS0AACIAQSBHBEACQAJAAkAgAEHIAGsOCwABswGzAbMBswGzAbMBswGzAQKzAQsgAUEBaiEBQdIAIQMMtwILIAFBAWohAUHTACEDDLYCCyABQQFqIQFB1AAhAwy1AgsgBCABQQFqIgFHDQALQeQAIQMMzAILQeQAIQMMywILA0AgAS0AAEHwwgBqLQAAIgBBAUcEQCAAQQJrDgOnAaYBpQGkAQsgBCABQQFqIgFHDQALQeYAIQMMygILIAFBAWogASAERw0CGkHnACEDDMkCCwNAIAEtAABB8MQAai0AACIAQQFHBEACQCAAQQJrDgSiAaEBoAEAnwELQdcAIQMMsQILIAQgAUEBaiIBRw0AC0HoACEDDMgCCyABIARGBEBB6QAhAwzIAgsCQCABLQAAIgBBCmsOGrcBmwGbAbQBmwGbAZsBmwGbAZsBmwGbAZsBmwGbAZsBmwGbAZsBmwGbAZsBpAGbAZsBAJkBCyABQQFqCyEBQQYhAwytAgsDQCABLQAAQfDGAGotAABBAUcNfSAEIAFBAWoiAUcNAAtB6gAhAwzFAgsgAUEBaiABIARHDQIaQesAIQMMxAILIAEgBEYEQEHsACEDDMQCCyABQQFqDAELIAEgBEYEQEHtACEDDMMCCyABQQFqCyEBQQQhAwyoAgsgASAERgRAQe4AIQMMwQILAkACQAJAIAEtAABB8MgAai0AAEEBaw4HkAGPAY4BAHwBAo0BCyABQQFqIQEMCwsgAUEBagyTAQtBACEDIAJBADYCHCACQZsSNgIQIAJBBzYCDCACIAFBAWo2AhQMwAILAkADQCABLQAAQfDIAGotAAAiAEEERwRAAkACQCAAQQFrDgeUAZMBkgGNAQAEAY0BC0HaACEDDKoCCyABQQFqIQFB3AAhAwypAgsgBCABQQFqIgFHDQALQe8AIQMMwAILIAFBAWoMkQELIAQgASIARgRAQfAAIQMMvwILIAAtAABBL0cNASAAQQFqIQEMBwsgBCABIgBGBEBB8QAhAwy+AgsgAC0AACIBQS9GBEAgAEEBaiEBQd0AIQMMpQILIAFBCmsiA0EWSw0AIAAhAUEBIAN0QYmAgAJxDfkBC0EAIQMgAkEANgIcIAIgADYCFCACQYwcNgIQIAJBBzYCDAy8AgsgASAERwRAIAFBAWohAUHeACEDDKMCC0HyACEDDLsCCyABIARGBEBB9AAhAwy7AgsCQCABLQAAQfDMAGotAABBAWsOA/cBcwCCAQtB4QAhAwyhAgsgASAERwRAA0AgAS0AAEHwygBqLQAAIgBBA0cEQAJAIABBAWsOAvkBAIUBC0HfACEDDKMCCyAEIAFBAWoiAUcNAAtB8wAhAwy6AgtB8wAhAwy5AgsgASAERwRAIAJBDzYCCCACIAE2AgRB4AAhAwygAgtB9QAhAwy4AgsgASAERgRAQfYAIQMMuAILIAJBDzYCCCACIAE2AgQLQQMhAwydAgsDQCABLQAAQSBHDY4CIAQgAUEBaiIBRw0AC0H3ACEDDLUCCyABIARGBEBB+AAhAwy1AgsgAS0AAEEgRw16IAFBAWohAQxbC0EAIQACQCACKAI4IgNFDQAgAygCOCIDRQ0AIAIgAxEAACEACyAADXgMgAILIAEgBEYEQEH6ACEDDLMCCyABLQAAQcwARw10IAFBAWohAUETDHYLQfsAIQMgASAERg2xAiACKAIAIgAgBCABa2ohBSABIABrQQVqIQYDQCABLQAAIABB8M4Aai0AAEcNcyAAQQVGDXUgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMsQILIAEgBEYEQEH8ACEDDLECCwJAAkAgAS0AAEHDAGsODAB0dHR0dHR0dHR0AXQLIAFBAWohAUHmACEDDJgCCyABQQFqIQFB5wAhAwyXAgtB/QAhAyABIARGDa8CIAIoAgAiACAEIAFraiEFIAEgAGtBAmohBgJAA0AgAS0AACAAQe3PAGotAABHDXIgAEECRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADLACCyACQQA2AgAgBkEBaiEBQRAMcwtB/gAhAyABIARGDa4CIAIoAgAiACAEIAFraiEFIAEgAGtBBWohBgJAA0AgAS0AACAAQfbOAGotAABHDXEgAEEFRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADK8CCyACQQA2AgAgBkEBaiEBQRYMcgtB/wAhAyABIARGDa0CIAIoAgAiACAEIAFraiEFIAEgAGtBA2ohBgJAA0AgAS0AACAAQfzOAGotAABHDXAgAEEDRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADK4CCyACQQA2AgAgBkEBaiEBQQUMcQsgASAERgRAQYABIQMMrQILIAEtAABB2QBHDW4gAUEBaiEBQQgMcAsgASAERgRAQYEBIQMMrAILAkACQCABLQAAQc4Aaw4DAG8BbwsgAUEBaiEBQesAIQMMkwILIAFBAWohAUHsACEDDJICCyABIARGBEBBggEhAwyrAgsCQAJAIAEtAABByABrDggAbm5ubm5uAW4LIAFBAWohAUHqACEDDJICCyABQQFqIQFB7QAhAwyRAgtBgwEhAyABIARGDakCIAIoAgAiACAEIAFraiEFIAEgAGtBAmohBgJAA0AgAS0AACAAQYDPAGotAABHDWwgAEECRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADKoCCyACQQA2AgAgBkEBaiEBQQAMbQtBhAEhAyABIARGDagCIAIoAgAiACAEIAFraiEFIAEgAGtBBGohBgJAA0AgAS0AACAAQYPPAGotAABHDWsgAEEERg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADKkCCyACQQA2AgAgBkEBaiEBQSMMbAsgASAERgRAQYUBIQMMqAILAkACQCABLQAAQcwAaw4IAGtra2trawFrCyABQQFqIQFB7wAhAwyPAgsgAUEBaiEBQfAAIQMMjgILIAEgBEYEQEGGASEDDKcCCyABLQAAQcUARw1oIAFBAWohAQxgC0GHASEDIAEgBEYNpQIgAigCACIAIAQgAWtqIQUgASAAa0EDaiEGAkADQCABLQAAIABBiM8Aai0AAEcNaCAAQQNGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMpgILIAJBADYCACAGQQFqIQFBLQxpC0GIASEDIAEgBEYNpAIgAigCACIAIAQgAWtqIQUgASAAa0EIaiEGAkADQCABLQAAIABB0M8Aai0AAEcNZyAAQQhGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMpQILIAJBADYCACAGQQFqIQFBKQxoCyABIARGBEBBiQEhAwykAgtBASABLQAAQd8ARw1nGiABQQFqIQEMXgtBigEhAyABIARGDaICIAIoAgAiACAEIAFraiEFIAEgAGtBAWohBgNAIAEtAAAgAEGMzwBqLQAARw1kIABBAUYN+gEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMogILQYsBIQMgASAERg2hAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEGOzwBqLQAARw1kIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyiAgsgAkEANgIAIAZBAWohAUECDGULQYwBIQMgASAERg2gAiACKAIAIgAgBCABa2ohBSABIABrQQFqIQYCQANAIAEtAAAgAEHwzwBqLQAARw1jIABBAUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyhAgsgAkEANgIAIAZBAWohAUEfDGQLQY0BIQMgASAERg2fAiACKAIAIgAgBCABa2ohBSABIABrQQFqIQYCQANAIAEtAAAgAEHyzwBqLQAARw1iIABBAUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAygAgsgAkEANgIAIAZBAWohAUEJDGMLIAEgBEYEQEGOASEDDJ8CCwJAAkAgAS0AAEHJAGsOBwBiYmJiYgFiCyABQQFqIQFB+AAhAwyGAgsgAUEBaiEBQfkAIQMMhQILQY8BIQMgASAERg2dAiACKAIAIgAgBCABa2ohBSABIABrQQVqIQYCQANAIAEtAAAgAEGRzwBqLQAARw1gIABBBUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyeAgsgAkEANgIAIAZBAWohAUEYDGELQZABIQMgASAERg2cAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEGXzwBqLQAARw1fIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAydAgsgAkEANgIAIAZBAWohAUEXDGALQZEBIQMgASAERg2bAiACKAIAIgAgBCABa2ohBSABIABrQQZqIQYCQANAIAEtAAAgAEGazwBqLQAARw1eIABBBkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAycAgsgAkEANgIAIAZBAWohAUEVDF8LQZIBIQMgASAERg2aAiACKAIAIgAgBCABa2ohBSABIABrQQVqIQYCQANAIAEtAAAgAEGhzwBqLQAARw1dIABBBUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAybAgsgAkEANgIAIAZBAWohAUEeDF4LIAEgBEYEQEGTASEDDJoCCyABLQAAQcwARw1bIAFBAWohAUEKDF0LIAEgBEYEQEGUASEDDJkCCwJAAkAgAS0AAEHBAGsODwBcXFxcXFxcXFxcXFxcAVwLIAFBAWohAUH+ACEDDIACCyABQQFqIQFB/wAhAwz/AQsgASAERgRAQZUBIQMMmAILAkACQCABLQAAQcEAaw4DAFsBWwsgAUEBaiEBQf0AIQMM/wELIAFBAWohAUGAASEDDP4BC0GWASEDIAEgBEYNlgIgAigCACIAIAQgAWtqIQUgASAAa0EBaiEGAkADQCABLQAAIABBp88Aai0AAEcNWSAAQQFGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMlwILIAJBADYCACAGQQFqIQFBCwxaCyABIARGBEBBlwEhAwyWAgsCQAJAAkACQCABLQAAQS1rDiMAW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1sBW1tbW1sCW1tbA1sLIAFBAWohAUH7ACEDDP8BCyABQQFqIQFB/AAhAwz+AQsgAUEBaiEBQYEBIQMM/QELIAFBAWohAUGCASEDDPwBC0GYASEDIAEgBEYNlAIgAigCACIAIAQgAWtqIQUgASAAa0EEaiEGAkADQCABLQAAIABBqc8Aai0AAEcNVyAAQQRGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMlQILIAJBADYCACAGQQFqIQFBGQxYC0GZASEDIAEgBEYNkwIgAigCACIAIAQgAWtqIQUgASAAa0EFaiEGAkADQCABLQAAIABBrs8Aai0AAEcNViAAQQVGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMlAILIAJBADYCACAGQQFqIQFBBgxXC0GaASEDIAEgBEYNkgIgAigCACIAIAQgAWtqIQUgASAAa0EBaiEGAkADQCABLQAAIABBtM8Aai0AAEcNVSAAQQFGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMkwILIAJBADYCACAGQQFqIQFBHAxWC0GbASEDIAEgBEYNkQIgAigCACIAIAQgAWtqIQUgASAAa0EBaiEGAkADQCABLQAAIABBts8Aai0AAEcNVCAAQQFGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMkgILIAJBADYCACAGQQFqIQFBJwxVCyABIARGBEBBnAEhAwyRAgsCQAJAIAEtAABB1ABrDgIAAVQLIAFBAWohAUGGASEDDPgBCyABQQFqIQFBhwEhAwz3AQtBnQEhAyABIARGDY8CIAIoAgAiACAEIAFraiEFIAEgAGtBAWohBgJAA0AgAS0AACAAQbjPAGotAABHDVIgAEEBRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADJACCyACQQA2AgAgBkEBaiEBQSYMUwtBngEhAyABIARGDY4CIAIoAgAiACAEIAFraiEFIAEgAGtBAWohBgJAA0AgAS0AACAAQbrPAGotAABHDVEgAEEBRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADI8CCyACQQA2AgAgBkEBaiEBQQMMUgtBnwEhAyABIARGDY0CIAIoAgAiACAEIAFraiEFIAEgAGtBAmohBgJAA0AgAS0AACAAQe3PAGotAABHDVAgAEECRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADI4CCyACQQA2AgAgBkEBaiEBQQwMUQtBoAEhAyABIARGDYwCIAIoAgAiACAEIAFraiEFIAEgAGtBA2ohBgJAA0AgAS0AACAAQbzPAGotAABHDU8gAEEDRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADI0CCyACQQA2AgAgBkEBaiEBQQ0MUAsgASAERgRAQaEBIQMMjAILAkACQCABLQAAQcYAaw4LAE9PT09PT09PTwFPCyABQQFqIQFBiwEhAwzzAQsgAUEBaiEBQYwBIQMM8gELIAEgBEYEQEGiASEDDIsCCyABLQAAQdAARw1MIAFBAWohAQxGCyABIARGBEBBowEhAwyKAgsCQAJAIAEtAABByQBrDgcBTU1NTU0ATQsgAUEBaiEBQY4BIQMM8QELIAFBAWohAUEiDE0LQaQBIQMgASAERg2IAiACKAIAIgAgBCABa2ohBSABIABrQQFqIQYCQANAIAEtAAAgAEHAzwBqLQAARw1LIABBAUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyJAgsgAkEANgIAIAZBAWohAUEdDEwLIAEgBEYEQEGlASEDDIgCCwJAAkAgAS0AAEHSAGsOAwBLAUsLIAFBAWohAUGQASEDDO8BCyABQQFqIQFBBAxLCyABIARGBEBBpgEhAwyHAgsCQAJAAkACQAJAIAEtAABBwQBrDhUATU1NTU1NTU1NTQFNTQJNTQNNTQRNCyABQQFqIQFBiAEhAwzxAQsgAUEBaiEBQYkBIQMM8AELIAFBAWohAUGKASEDDO8BCyABQQFqIQFBjwEhAwzuAQsgAUEBaiEBQZEBIQMM7QELQacBIQMgASAERg2FAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEHtzwBqLQAARw1IIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyGAgsgAkEANgIAIAZBAWohAUERDEkLQagBIQMgASAERg2EAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEHCzwBqLQAARw1HIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyFAgsgAkEANgIAIAZBAWohAUEsDEgLQakBIQMgASAERg2DAiACKAIAIgAgBCABa2ohBSABIABrQQRqIQYCQANAIAEtAAAgAEHFzwBqLQAARw1GIABBBEYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyEAgsgAkEANgIAIAZBAWohAUErDEcLQaoBIQMgASAERg2CAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEHKzwBqLQAARw1FIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyDAgsgAkEANgIAIAZBAWohAUEUDEYLIAEgBEYEQEGrASEDDIICCwJAAkACQAJAIAEtAABBwgBrDg8AAQJHR0dHR0dHR0dHRwNHCyABQQFqIQFBkwEhAwzrAQsgAUEBaiEBQZQBIQMM6gELIAFBAWohAUGVASEDDOkBCyABQQFqIQFBlgEhAwzoAQsgASAERgRAQawBIQMMgQILIAEtAABBxQBHDUIgAUEBaiEBDD0LQa0BIQMgASAERg3/ASACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEHNzwBqLQAARw1CIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyAAgsgAkEANgIAIAZBAWohAUEODEMLIAEgBEYEQEGuASEDDP8BCyABLQAAQdAARw1AIAFBAWohAUElDEILQa8BIQMgASAERg39ASACKAIAIgAgBCABa2ohBSABIABrQQhqIQYCQANAIAEtAAAgAEHQzwBqLQAARw1AIABBCEYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAz+AQsgAkEANgIAIAZBAWohAUEqDEELIAEgBEYEQEGwASEDDP0BCwJAAkAgAS0AAEHVAGsOCwBAQEBAQEBAQEABQAsgAUEBaiEBQZoBIQMM5AELIAFBAWohAUGbASEDDOMBCyABIARGBEBBsQEhAwz8AQsCQAJAIAEtAABBwQBrDhQAPz8/Pz8/Pz8/Pz8/Pz8/Pz8/AT8LIAFBAWohAUGZASEDDOMBCyABQQFqIQFBnAEhAwziAQtBsgEhAyABIARGDfoBIAIoAgAiACAEIAFraiEFIAEgAGtBA2ohBgJAA0AgAS0AACAAQdnPAGotAABHDT0gAEEDRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADPsBCyACQQA2AgAgBkEBaiEBQSEMPgtBswEhAyABIARGDfkBIAIoAgAiACAEIAFraiEFIAEgAGtBBmohBgJAA0AgAS0AACAAQd3PAGotAABHDTwgAEEGRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADPoBCyACQQA2AgAgBkEBaiEBQRoMPQsgASAERgRAQbQBIQMM+QELAkACQAJAIAEtAABBxQBrDhEAPT09PT09PT09AT09PT09Aj0LIAFBAWohAUGdASEDDOEBCyABQQFqIQFBngEhAwzgAQsgAUEBaiEBQZ8BIQMM3wELQbUBIQMgASAERg33ASACKAIAIgAgBCABa2ohBSABIABrQQVqIQYCQANAIAEtAAAgAEHkzwBqLQAARw06IABBBUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAz4AQsgAkEANgIAIAZBAWohAUEoDDsLQbYBIQMgASAERg32ASACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEHqzwBqLQAARw05IABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAz3AQsgAkEANgIAIAZBAWohAUEHDDoLIAEgBEYEQEG3ASEDDPYBCwJAAkAgAS0AAEHFAGsODgA5OTk5OTk5OTk5OTkBOQsgAUEBaiEBQaEBIQMM3QELIAFBAWohAUGiASEDDNwBC0G4ASEDIAEgBEYN9AEgAigCACIAIAQgAWtqIQUgASAAa0ECaiEGAkADQCABLQAAIABB7c8Aai0AAEcNNyAAQQJGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAM9QELIAJBADYCACAGQQFqIQFBEgw4C0G5ASEDIAEgBEYN8wEgAigCACIAIAQgAWtqIQUgASAAa0EBaiEGAkADQCABLQAAIABB8M8Aai0AAEcNNiAAQQFGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAM9AELIAJBADYCACAGQQFqIQFBIAw3C0G6ASEDIAEgBEYN8gEgAigCACIAIAQgAWtqIQUgASAAa0EBaiEGAkADQCABLQAAIABB8s8Aai0AAEcNNSAAQQFGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAM8wELIAJBADYCACAGQQFqIQFBDww2CyABIARGBEBBuwEhAwzyAQsCQAJAIAEtAABByQBrDgcANTU1NTUBNQsgAUEBaiEBQaUBIQMM2QELIAFBAWohAUGmASEDDNgBC0G8ASEDIAEgBEYN8AEgAigCACIAIAQgAWtqIQUgASAAa0EHaiEGAkADQCABLQAAIABB9M8Aai0AAEcNMyAAQQdGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAM8QELIAJBADYCACAGQQFqIQFBGww0CyABIARGBEBBvQEhAwzwAQsCQAJAAkAgAS0AAEHCAGsOEgA0NDQ0NDQ0NDQBNDQ0NDQ0AjQLIAFBAWohAUGkASEDDNgBCyABQQFqIQFBpwEhAwzXAQsgAUEBaiEBQagBIQMM1gELIAEgBEYEQEG+ASEDDO8BCyABLQAAQc4ARw0wIAFBAWohAQwsCyABIARGBEBBvwEhAwzuAQsCQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCABLQAAQcEAaw4VAAECAz8EBQY/Pz8HCAkKCz8MDQ4PPwsgAUEBaiEBQegAIQMM4wELIAFBAWohAUHpACEDDOIBCyABQQFqIQFB7gAhAwzhAQsgAUEBaiEBQfIAIQMM4AELIAFBAWohAUHzACEDDN8BCyABQQFqIQFB9gAhAwzeAQsgAUEBaiEBQfcAIQMM3QELIAFBAWohAUH6ACEDDNwBCyABQQFqIQFBgwEhAwzbAQsgAUEBaiEBQYQBIQMM2gELIAFBAWohAUGFASEDDNkBCyABQQFqIQFBkgEhAwzYAQsgAUEBaiEBQZgBIQMM1wELIAFBAWohAUGgASEDDNYBCyABQQFqIQFBowEhAwzVAQsgAUEBaiEBQaoBIQMM1AELIAEgBEcEQCACQRA2AgggAiABNgIEQasBIQMM1AELQcABIQMM7AELQQAhAAJAIAIoAjgiA0UNACADKAI0IgNFDQAgAiADEQAAIQALIABFDV4gAEEVRw0HIAJB0QA2AhwgAiABNgIUIAJBsBc2AhAgAkEVNgIMQQAhAwzrAQsgAUEBaiABIARHDQgaQcIBIQMM6gELA0ACQCABLQAAQQprDgQIAAALAAsgBCABQQFqIgFHDQALQcMBIQMM6QELIAEgBEcEQCACQRE2AgggAiABNgIEQQEhAwzQAQtBxAEhAwzoAQsgASAERgRAQcUBIQMM6AELAkACQCABLQAAQQprDgQBKCgAKAsgAUEBagwJCyABQQFqDAULIAEgBEYEQEHGASEDDOcBCwJAAkAgAS0AAEEKaw4XAQsLAQsLCwsLCwsLCwsLCwsLCwsLCwALCyABQQFqIQELQbABIQMMzQELIAEgBEYEQEHIASEDDOYBCyABLQAAQSBHDQkgAkEAOwEyIAFBAWohAUGzASEDDMwBCwNAIAEhAAJAIAEgBEcEQCABLQAAQTBrQf8BcSIDQQpJDQEMJwtBxwEhAwzmAQsCQCACLwEyIgFBmTNLDQAgAiABQQpsIgU7ATIgBUH+/wNxIANB//8Dc0sNACAAQQFqIQEgAiADIAVqIgM7ATIgA0H//wNxQegHSQ0BCwtBACEDIAJBADYCHCACQcEJNgIQIAJBDTYCDCACIABBAWo2AhQM5AELIAJBADYCHCACIAE2AhQgAkHwDDYCECACQRs2AgxBACEDDOMBCyACKAIEIQAgAkEANgIEIAIgACABECYiAA0BIAFBAWoLIQFBrQEhAwzIAQsgAkHBATYCHCACIAA2AgwgAiABQQFqNgIUQQAhAwzgAQsgAigCBCEAIAJBADYCBCACIAAgARAmIgANASABQQFqCyEBQa4BIQMMxQELIAJBwgE2AhwgAiAANgIMIAIgAUEBajYCFEEAIQMM3QELIAJBADYCHCACIAE2AhQgAkGXCzYCECACQQ02AgxBACEDDNwBCyACQQA2AhwgAiABNgIUIAJB4xA2AhAgAkEJNgIMQQAhAwzbAQsgAkECOgAoDKwBC0EAIQMgAkEANgIcIAJBrws2AhAgAkECNgIMIAIgAUEBajYCFAzZAQtBAiEDDL8BC0ENIQMMvgELQSYhAwy9AQtBFSEDDLwBC0EWIQMMuwELQRghAwy6AQtBHCEDDLkBC0EdIQMMuAELQSAhAwy3AQtBISEDDLYBC0EjIQMMtQELQcYAIQMMtAELQS4hAwyzAQtBPSEDDLIBC0HLACEDDLEBC0HOACEDDLABC0HYACEDDK8BC0HZACEDDK4BC0HbACEDDK0BC0HxACEDDKwBC0H0ACEDDKsBC0GNASEDDKoBC0GXASEDDKkBC0GpASEDDKgBC0GvASEDDKcBC0GxASEDDKYBCyACQQA2AgALQQAhAyACQQA2AhwgAiABNgIUIAJB8Rs2AhAgAkEGNgIMDL0BCyACQQA2AgAgBkEBaiEBQSQLOgApIAIoAgQhACACQQA2AgQgAiAAIAEQJyIARQRAQeUAIQMMowELIAJB+QA2AhwgAiABNgIUIAIgADYCDEEAIQMMuwELIABBFUcEQCACQQA2AhwgAiABNgIUIAJBzA42AhAgAkEgNgIMQQAhAwy7AQsgAkH4ADYCHCACIAE2AhQgAkHKGDYCECACQRU2AgxBACEDDLoBCyACQQA2AhwgAiABNgIUIAJBjhs2AhAgAkEGNgIMQQAhAwy5AQsgAkEANgIcIAIgATYCFCACQf4RNgIQIAJBBzYCDEEAIQMMuAELIAJBADYCHCACIAE2AhQgAkGMHDYCECACQQc2AgxBACEDDLcBCyACQQA2AhwgAiABNgIUIAJBww82AhAgAkEHNgIMQQAhAwy2AQsgAkEANgIcIAIgATYCFCACQcMPNgIQIAJBBzYCDEEAIQMMtQELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0RIAJB5QA2AhwgAiABNgIUIAIgADYCDEEAIQMMtAELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0gIAJB0wA2AhwgAiABNgIUIAIgADYCDEEAIQMMswELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0iIAJB0gA2AhwgAiABNgIUIAIgADYCDEEAIQMMsgELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0OIAJB5QA2AhwgAiABNgIUIAIgADYCDEEAIQMMsQELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0dIAJB0wA2AhwgAiABNgIUIAIgADYCDEEAIQMMsAELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0fIAJB0gA2AhwgAiABNgIUIAIgADYCDEEAIQMMrwELIABBP0cNASABQQFqCyEBQQUhAwyUAQtBACEDIAJBADYCHCACIAE2AhQgAkH9EjYCECACQQc2AgwMrAELIAJBADYCHCACIAE2AhQgAkHcCDYCECACQQc2AgxBACEDDKsBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNByACQeUANgIcIAIgATYCFCACIAA2AgxBACEDDKoBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNFiACQdMANgIcIAIgATYCFCACIAA2AgxBACEDDKkBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNGCACQdIANgIcIAIgATYCFCACIAA2AgxBACEDDKgBCyACQQA2AhwgAiABNgIUIAJBxgo2AhAgAkEHNgIMQQAhAwynAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDQMgAkHlADYCHCACIAE2AhQgAiAANgIMQQAhAwymAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDRIgAkHTADYCHCACIAE2AhQgAiAANgIMQQAhAwylAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDRQgAkHSADYCHCACIAE2AhQgAiAANgIMQQAhAwykAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDQAgAkHlADYCHCACIAE2AhQgAiAANgIMQQAhAwyjAQtB1QAhAwyJAQsgAEEVRwRAIAJBADYCHCACIAE2AhQgAkG5DTYCECACQRo2AgxBACEDDKIBCyACQeQANgIcIAIgATYCFCACQeMXNgIQIAJBFTYCDEEAIQMMoQELIAJBADYCACAGQQFqIQEgAi0AKSIAQSNrQQtJDQQCQCAAQQZLDQBBASAAdEHKAHFFDQAMBQtBACEDIAJBADYCHCACIAE2AhQgAkH3CTYCECACQQg2AgwMoAELIAJBADYCACAGQQFqIQEgAi0AKUEhRg0DIAJBADYCHCACIAE2AhQgAkGbCjYCECACQQg2AgxBACEDDJ8BCyACQQA2AgALQQAhAyACQQA2AhwgAiABNgIUIAJBkDM2AhAgAkEINgIMDJ0BCyACQQA2AgAgBkEBaiEBIAItAClBI0kNACACQQA2AhwgAiABNgIUIAJB0wk2AhAgAkEINgIMQQAhAwycAQtB0QAhAwyCAQsgAS0AAEEwayIAQf8BcUEKSQRAIAIgADoAKiABQQFqIQFBzwAhAwyCAQsgAigCBCEAIAJBADYCBCACIAAgARAoIgBFDYYBIAJB3gA2AhwgAiABNgIUIAIgADYCDEEAIQMMmgELIAIoAgQhACACQQA2AgQgAiAAIAEQKCIARQ2GASACQdwANgIcIAIgATYCFCACIAA2AgxBACEDDJkBCyACKAIEIQAgAkEANgIEIAIgACAFECgiAEUEQCAFIQEMhwELIAJB2gA2AhwgAiAFNgIUIAIgADYCDAyYAQtBACEBQQEhAwsgAiADOgArIAVBAWohAwJAAkACQCACLQAtQRBxDQACQAJAAkAgAi0AKg4DAQACBAsgBkUNAwwCCyAADQEMAgsgAUUNAQsgAigCBCEAIAJBADYCBCACIAAgAxAoIgBFBEAgAyEBDAILIAJB2AA2AhwgAiADNgIUIAIgADYCDEEAIQMMmAELIAIoAgQhACACQQA2AgQgAiAAIAMQKCIARQRAIAMhAQyHAQsgAkHZADYCHCACIAM2AhQgAiAANgIMQQAhAwyXAQtBzAAhAwx9CyAAQRVHBEAgAkEANgIcIAIgATYCFCACQZQNNgIQIAJBITYCDEEAIQMMlgELIAJB1wA2AhwgAiABNgIUIAJByRc2AhAgAkEVNgIMQQAhAwyVAQtBACEDIAJBADYCHCACIAE2AhQgAkGAETYCECACQQk2AgwMlAELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0AIAJB0wA2AhwgAiABNgIUIAIgADYCDEEAIQMMkwELQckAIQMMeQsgAkEANgIcIAIgATYCFCACQcEoNgIQIAJBBzYCDCACQQA2AgBBACEDDJEBCyACKAIEIQBBACEDIAJBADYCBCACIAAgARAlIgBFDQAgAkHSADYCHCACIAE2AhQgAiAANgIMDJABC0HIACEDDHYLIAJBADYCACAFIQELIAJBgBI7ASogAUEBaiEBQQAhAAJAIAIoAjgiA0UNACADKAIwIgNFDQAgAiADEQAAIQALIAANAQtBxwAhAwxzCyAAQRVGBEAgAkHRADYCHCACIAE2AhQgAkHjFzYCECACQRU2AgxBACEDDIwBC0EAIQMgAkEANgIcIAIgATYCFCACQbkNNgIQIAJBGjYCDAyLAQtBACEDIAJBADYCHCACIAE2AhQgAkGgGTYCECACQR42AgwMigELIAEtAABBOkYEQCACKAIEIQBBACEDIAJBADYCBCACIAAgARApIgBFDQEgAkHDADYCHCACIAA2AgwgAiABQQFqNgIUDIoBC0EAIQMgAkEANgIcIAIgATYCFCACQbERNgIQIAJBCjYCDAyJAQsgAUEBaiEBQTshAwxvCyACQcMANgIcIAIgADYCDCACIAFBAWo2AhQMhwELQQAhAyACQQA2AhwgAiABNgIUIAJB8A42AhAgAkEcNgIMDIYBCyACIAIvATBBEHI7ATAMZgsCQCACLwEwIgBBCHFFDQAgAi0AKEEBRw0AIAItAC1BCHFFDQMLIAIgAEH3+wNxQYAEcjsBMAwECyABIARHBEACQANAIAEtAABBMGsiAEH/AXFBCk8EQEE1IQMMbgsgAikDICIKQpmz5syZs+bMGVYNASACIApCCn4iCjcDICAKIACtQv8BgyILQn+FVg0BIAIgCiALfDcDICAEIAFBAWoiAUcNAAtBOSEDDIUBCyACKAIEIQBBACEDIAJBADYCBCACIAAgAUEBaiIBECoiAA0MDHcLQTkhAwyDAQsgAi0AMEEgcQ0GQcUBIQMMaQtBACEDIAJBADYCBCACIAEgARAqIgBFDQQgAkE6NgIcIAIgADYCDCACIAFBAWo2AhQMgQELIAItAChBAUcNACACLQAtQQhxRQ0BC0E3IQMMZgsgAigCBCEAQQAhAyACQQA2AgQgAiAAIAEQKiIABEAgAkE7NgIcIAIgADYCDCACIAFBAWo2AhQMfwsgAUEBaiEBDG4LIAJBCDoALAwECyABQQFqIQEMbQtBACEDIAJBADYCHCACIAE2AhQgAkHkEjYCECACQQQ2AgwMewsgAigCBCEAQQAhAyACQQA2AgQgAiAAIAEQKiIARQ1sIAJBNzYCHCACIAE2AhQgAiAANgIMDHoLIAIgAi8BMEEgcjsBMAtBMCEDDF8LIAJBNjYCHCACIAE2AhQgAiAANgIMDHcLIABBLEcNASABQQFqIQBBASEBAkACQAJAAkACQCACLQAsQQVrDgQDAQIEAAsgACEBDAQLQQIhAQwBC0EEIQELIAJBAToALCACIAIvATAgAXI7ATAgACEBDAELIAIgAi8BMEEIcjsBMCAAIQELQTkhAwxcCyACQQA6ACwLQTQhAwxaCyABIARGBEBBLSEDDHMLAkACQANAAkAgAS0AAEEKaw4EAgAAAwALIAQgAUEBaiIBRw0AC0EtIQMMdAsgAigCBCEAQQAhAyACQQA2AgQgAiAAIAEQKiIARQ0CIAJBLDYCHCACIAE2AhQgAiAANgIMDHMLIAIoAgQhAEEAIQMgAkEANgIEIAIgACABECoiAEUEQCABQQFqIQEMAgsgAkEsNgIcIAIgADYCDCACIAFBAWo2AhQMcgsgAS0AAEENRgRAIAIoAgQhAEEAIQMgAkEANgIEIAIgACABECoiAEUEQCABQQFqIQEMAgsgAkEsNgIcIAIgADYCDCACIAFBAWo2AhQMcgsgAi0ALUEBcQRAQcQBIQMMWQsgAigCBCEAQQAhAyACQQA2AgQgAiAAIAEQKiIADQEMZQtBLyEDDFcLIAJBLjYCHCACIAE2AhQgAiAANgIMDG8LQQAhAyACQQA2AhwgAiABNgIUIAJB8BQ2AhAgAkEDNgIMDG4LQQEhAwJAAkACQAJAIAItACxBBWsOBAMBAgAECyACIAIvATBBCHI7ATAMAwtBAiEDDAELQQQhAwsgAkEBOgAsIAIgAi8BMCADcjsBMAtBKiEDDFMLQQAhAyACQQA2AhwgAiABNgIUIAJB4Q82AhAgAkEKNgIMDGsLQQEhAwJAAkACQAJAAkACQCACLQAsQQJrDgcFBAQDAQIABAsgAiACLwEwQQhyOwEwDAMLQQIhAwwBC0EEIQMLIAJBAToALCACIAIvATAgA3I7ATALQSshAwxSC0EAIQMgAkEANgIcIAIgATYCFCACQasSNgIQIAJBCzYCDAxqC0EAIQMgAkEANgIcIAIgATYCFCACQf0NNgIQIAJBHTYCDAxpCyABIARHBEADQCABLQAAQSBHDUggBCABQQFqIgFHDQALQSUhAwxpC0ElIQMMaAsgAi0ALUEBcQRAQcMBIQMMTwsgAigCBCEAQQAhAyACQQA2AgQgAiAAIAEQKSIABEAgAkEmNgIcIAIgADYCDCACIAFBAWo2AhQMaAsgAUEBaiEBDFwLIAFBAWohASACLwEwIgBBgAFxBEBBACEAAkAgAigCOCIDRQ0AIAMoAlQiA0UNACACIAMRAAAhAAsgAEUNBiAAQRVHDR8gAkEFNgIcIAIgATYCFCACQfkXNgIQIAJBFTYCDEEAIQMMZwsCQCAAQaAEcUGgBEcNACACLQAtQQJxDQBBACEDIAJBADYCHCACIAE2AhQgAkGWEzYCECACQQQ2AgwMZwsgAgJ/IAIvATBBFHFBFEYEQEEBIAItAChBAUYNARogAi8BMkHlAEYMAQsgAi0AKUEFRgs6AC5BACEAAkAgAigCOCIDRQ0AIAMoAiQiA0UNACACIAMRAAAhAAsCQAJAAkACQAJAIAAOFgIBAAQEBAQEBAQEBAQEBAQEBAQEBAMECyACQQE6AC4LIAIgAi8BMEHAAHI7ATALQSchAwxPCyACQSM2AhwgAiABNgIUIAJBpRY2AhAgAkEVNgIMQQAhAwxnC0EAIQMgAkEANgIcIAIgATYCFCACQdULNgIQIAJBETYCDAxmC0EAIQACQCACKAI4IgNFDQAgAygCLCIDRQ0AIAIgAxEAACEACyAADQELQQ4hAwxLCyAAQRVGBEAgAkECNgIcIAIgATYCFCACQbAYNgIQIAJBFTYCDEEAIQMMZAtBACEDIAJBADYCHCACIAE2AhQgAkGnDjYCECACQRI2AgwMYwtBACEDIAJBADYCHCACIAE2AhQgAkGqHDYCECACQQ82AgwMYgsgAigCBCEAQQAhAyACQQA2AgQgAiAAIAEgCqdqIgEQKyIARQ0AIAJBBTYCHCACIAE2AhQgAiAANgIMDGELQQ8hAwxHC0EAIQMgAkEANgIcIAIgATYCFCACQc0TNgIQIAJBDDYCDAxfC0IBIQoLIAFBAWohAQJAIAIpAyAiC0L//////////w9YBEAgAiALQgSGIAqENwMgDAELQQAhAyACQQA2AhwgAiABNgIUIAJBrQk2AhAgAkEMNgIMDF4LQSQhAwxEC0EAIQMgAkEANgIcIAIgATYCFCACQc0TNgIQIAJBDDYCDAxcCyACKAIEIQBBACEDIAJBADYCBCACIAAgARAsIgBFBEAgAUEBaiEBDFILIAJBFzYCHCACIAA2AgwgAiABQQFqNgIUDFsLIAIoAgQhAEEAIQMgAkEANgIEAkAgAiAAIAEQLCIARQRAIAFBAWohAQwBCyACQRY2AhwgAiAANgIMIAIgAUEBajYCFAxbC0EfIQMMQQtBACEDIAJBADYCHCACIAE2AhQgAkGaDzYCECACQSI2AgwMWQsgAigCBCEAQQAhAyACQQA2AgQgAiAAIAEQLSIARQRAIAFBAWohAQxQCyACQRQ2AhwgAiAANgIMIAIgAUEBajYCFAxYCyACKAIEIQBBACEDIAJBADYCBAJAIAIgACABEC0iAEUEQCABQQFqIQEMAQsgAkETNgIcIAIgADYCDCACIAFBAWo2AhQMWAtBHiEDDD4LQQAhAyACQQA2AhwgAiABNgIUIAJBxgw2AhAgAkEjNgIMDFYLIAIoAgQhAEEAIQMgAkEANgIEIAIgACABEC0iAEUEQCABQQFqIQEMTgsgAkERNgIcIAIgADYCDCACIAFBAWo2AhQMVQsgAkEQNgIcIAIgATYCFCACIAA2AgwMVAtBACEDIAJBADYCHCACIAE2AhQgAkHGDDYCECACQSM2AgwMUwtBACEDIAJBADYCHCACIAE2AhQgAkHAFTYCECACQQI2AgwMUgsgAigCBCEAQQAhAyACQQA2AgQCQCACIAAgARAtIgBFBEAgAUEBaiEBDAELIAJBDjYCHCACIAA2AgwgAiABQQFqNgIUDFILQRshAww4C0EAIQMgAkEANgIcIAIgATYCFCACQcYMNgIQIAJBIzYCDAxQCyACKAIEIQBBACEDIAJBADYCBAJAIAIgACABECwiAEUEQCABQQFqIQEMAQsgAkENNgIcIAIgADYCDCACIAFBAWo2AhQMUAtBGiEDDDYLQQAhAyACQQA2AhwgAiABNgIUIAJBmg82AhAgAkEiNgIMDE4LIAIoAgQhAEEAIQMgAkEANgIEAkAgAiAAIAEQLCIARQRAIAFBAWohAQwBCyACQQw2AhwgAiAANgIMIAIgAUEBajYCFAxOC0EZIQMMNAtBACEDIAJBADYCHCACIAE2AhQgAkGaDzYCECACQSI2AgwMTAsgAEEVRwRAQQAhAyACQQA2AhwgAiABNgIUIAJBgww2AhAgAkETNgIMDEwLIAJBCjYCHCACIAE2AhQgAkHkFjYCECACQRU2AgxBACEDDEsLIAIoAgQhAEEAIQMgAkEANgIEIAIgACABIAqnaiIBECsiAARAIAJBBzYCHCACIAE2AhQgAiAANgIMDEsLQRMhAwwxCyAAQRVHBEBBACEDIAJBADYCHCACIAE2AhQgAkHaDTYCECACQRQ2AgwMSgsgAkEeNgIcIAIgATYCFCACQfkXNgIQIAJBFTYCDEEAIQMMSQtBACEAAkAgAigCOCIDRQ0AIAMoAiwiA0UNACACIAMRAAAhAAsgAEUNQSAAQRVGBEAgAkEDNgIcIAIgATYCFCACQbAYNgIQIAJBFTYCDEEAIQMMSQtBACEDIAJBADYCHCACIAE2AhQgAkGnDjYCECACQRI2AgwMSAtBACEDIAJBADYCHCACIAE2AhQgAkHaDTYCECACQRQ2AgwMRwtBACEDIAJBADYCHCACIAE2AhQgAkGnDjYCECACQRI2AgwMRgsgAkEAOgAvIAItAC1BBHFFDT8LIAJBADoALyACQQE6ADRBACEDDCsLQQAhAyACQQA2AhwgAkHkETYCECACQQc2AgwgAiABQQFqNgIUDEMLAkADQAJAIAEtAABBCmsOBAACAgACCyAEIAFBAWoiAUcNAAtB3QEhAwxDCwJAAkAgAi0ANEEBRw0AQQAhAAJAIAIoAjgiA0UNACADKAJYIgNFDQAgAiADEQAAIQALIABFDQAgAEEVRw0BIAJB3AE2AhwgAiABNgIUIAJB1RY2AhAgAkEVNgIMQQAhAwxEC0HBASEDDCoLIAJBADYCHCACIAE2AhQgAkHpCzYCECACQR82AgxBACEDDEILAkACQCACLQAoQQFrDgIEAQALQcABIQMMKQtBuQEhAwwoCyACQQI6AC9BACEAAkAgAigCOCIDRQ0AIAMoAgAiA0UNACACIAMRAAAhAAsgAEUEQEHCASEDDCgLIABBFUcEQCACQQA2AhwgAiABNgIUIAJBpAw2AhAgAkEQNgIMQQAhAwxBCyACQdsBNgIcIAIgATYCFCACQfoWNgIQIAJBFTYCDEEAIQMMQAsgASAERgRAQdoBIQMMQAsgAS0AAEHIAEYNASACQQE6ACgLQawBIQMMJQtBvwEhAwwkCyABIARHBEAgAkEQNgIIIAIgATYCBEG+ASEDDCQLQdkBIQMMPAsgASAERgRAQdgBIQMMPAsgAS0AAEHIAEcNBCABQQFqIQFBvQEhAwwiCyABIARGBEBB1wEhAww7CwJAAkAgAS0AAEHFAGsOEAAFBQUFBQUFBQUFBQUFBQEFCyABQQFqIQFBuwEhAwwiCyABQQFqIQFBvAEhAwwhC0HWASEDIAEgBEYNOSACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEGD0ABqLQAARw0DIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAw6CyACKAIEIQAgAkIANwMAIAIgACAGQQFqIgEQJyIARQRAQcYBIQMMIQsgAkHVATYCHCACIAE2AhQgAiAANgIMQQAhAww5C0HUASEDIAEgBEYNOCACKAIAIgAgBCABa2ohBSABIABrQQFqIQYCQANAIAEtAAAgAEGB0ABqLQAARw0CIABBAUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAw5CyACQYEEOwEoIAIoAgQhACACQgA3AwAgAiAAIAZBAWoiARAnIgANAwwCCyACQQA2AgALQQAhAyACQQA2AhwgAiABNgIUIAJB2Bs2AhAgAkEINgIMDDYLQboBIQMMHAsgAkHTATYCHCACIAE2AhQgAiAANgIMQQAhAww0C0EAIQACQCACKAI4IgNFDQAgAygCOCIDRQ0AIAIgAxEAACEACyAARQ0AIABBFUYNASACQQA2AhwgAiABNgIUIAJBzA42AhAgAkEgNgIMQQAhAwwzC0HkACEDDBkLIAJB+AA2AhwgAiABNgIUIAJByhg2AhAgAkEVNgIMQQAhAwwxC0HSASEDIAQgASIARg0wIAQgAWsgAigCACIBaiEFIAAgAWtBBGohBgJAA0AgAC0AACABQfzPAGotAABHDQEgAUEERg0DIAFBAWohASAEIABBAWoiAEcNAAsgAiAFNgIADDELIAJBADYCHCACIAA2AhQgAkGQMzYCECACQQg2AgwgAkEANgIAQQAhAwwwCyABIARHBEAgAkEONgIIIAIgATYCBEG3ASEDDBcLQdEBIQMMLwsgAkEANgIAIAZBAWohAQtBuAEhAwwUCyABIARGBEBB0AEhAwwtCyABLQAAQTBrIgBB/wFxQQpJBEAgAiAAOgAqIAFBAWohAUG2ASEDDBQLIAIoAgQhACACQQA2AgQgAiAAIAEQKCIARQ0UIAJBzwE2AhwgAiABNgIUIAIgADYCDEEAIQMMLAsgASAERgRAQc4BIQMMLAsCQCABLQAAQS5GBEAgAUEBaiEBDAELIAIoAgQhACACQQA2AgQgAiAAIAEQKCIARQ0VIAJBzQE2AhwgAiABNgIUIAIgADYCDEEAIQMMLAtBtQEhAwwSCyAEIAEiBUYEQEHMASEDDCsLQQAhAEEBIQFBASEGQQAhAwJAAkACQAJAAkACfwJAAkACQAJAAkACQAJAIAUtAABBMGsOCgoJAAECAwQFBggLC0ECDAYLQQMMBQtBBAwEC0EFDAMLQQYMAgtBBwwBC0EICyEDQQAhAUEAIQYMAgtBCSEDQQEhAEEAIQFBACEGDAELQQAhAUEBIQMLIAIgAzoAKyAFQQFqIQMCQAJAIAItAC1BEHENAAJAAkACQCACLQAqDgMBAAIECyAGRQ0DDAILIAANAQwCCyABRQ0BCyACKAIEIQAgAkEANgIEIAIgACADECgiAEUEQCADIQEMAwsgAkHJATYCHCACIAM2AhQgAiAANgIMQQAhAwwtCyACKAIEIQAgAkEANgIEIAIgACADECgiAEUEQCADIQEMGAsgAkHKATYCHCACIAM2AhQgAiAANgIMQQAhAwwsCyACKAIEIQAgAkEANgIEIAIgACAFECgiAEUEQCAFIQEMFgsgAkHLATYCHCACIAU2AhQgAiAANgIMDCsLQbQBIQMMEQtBACEAAkAgAigCOCIDRQ0AIAMoAjwiA0UNACACIAMRAAAhAAsCQCAABEAgAEEVRg0BIAJBADYCHCACIAE2AhQgAkGUDTYCECACQSE2AgxBACEDDCsLQbIBIQMMEQsgAkHIATYCHCACIAE2AhQgAkHJFzYCECACQRU2AgxBACEDDCkLIAJBADYCACAGQQFqIQFB9QAhAwwPCyACLQApQQVGBEBB4wAhAwwPC0HiACEDDA4LIAAhASACQQA2AgALIAJBADoALEEJIQMMDAsgAkEANgIAIAdBAWohAUHAACEDDAsLQQELOgAsIAJBADYCACAGQQFqIQELQSkhAwwIC0E4IQMMBwsCQCABIARHBEADQCABLQAAQYA+ai0AACIAQQFHBEAgAEECRw0DIAFBAWohAQwFCyAEIAFBAWoiAUcNAAtBPiEDDCELQT4hAwwgCwsgAkEAOgAsDAELQQshAwwEC0E6IQMMAwsgAUEBaiEBQS0hAwwCCyACIAE6ACwgAkEANgIAIAZBAWohAUEMIQMMAQsgAkEANgIAIAZBAWohAUEKIQMMAAsAC0EAIQMgAkEANgIcIAIgATYCFCACQc0QNgIQIAJBCTYCDAwXC0EAIQMgAkEANgIcIAIgATYCFCACQekKNgIQIAJBCTYCDAwWC0EAIQMgAkEANgIcIAIgATYCFCACQbcQNgIQIAJBCTYCDAwVC0EAIQMgAkEANgIcIAIgATYCFCACQZwRNgIQIAJBCTYCDAwUC0EAIQMgAkEANgIcIAIgATYCFCACQc0QNgIQIAJBCTYCDAwTC0EAIQMgAkEANgIcIAIgATYCFCACQekKNgIQIAJBCTYCDAwSC0EAIQMgAkEANgIcIAIgATYCFCACQbcQNgIQIAJBCTYCDAwRC0EAIQMgAkEANgIcIAIgATYCFCACQZwRNgIQIAJBCTYCDAwQC0EAIQMgAkEANgIcIAIgATYCFCACQZcVNgIQIAJBDzYCDAwPC0EAIQMgAkEANgIcIAIgATYCFCACQZcVNgIQIAJBDzYCDAwOC0EAIQMgAkEANgIcIAIgATYCFCACQcASNgIQIAJBCzYCDAwNC0EAIQMgAkEANgIcIAIgATYCFCACQZUJNgIQIAJBCzYCDAwMC0EAIQMgAkEANgIcIAIgATYCFCACQeEPNgIQIAJBCjYCDAwLC0EAIQMgAkEANgIcIAIgATYCFCACQfsPNgIQIAJBCjYCDAwKC0EAIQMgAkEANgIcIAIgATYCFCACQfEZNgIQIAJBAjYCDAwJC0EAIQMgAkEANgIcIAIgATYCFCACQcQUNgIQIAJBAjYCDAwIC0EAIQMgAkEANgIcIAIgATYCFCACQfIVNgIQIAJBAjYCDAwHCyACQQI2AhwgAiABNgIUIAJBnBo2AhAgAkEWNgIMQQAhAwwGC0EBIQMMBQtB1AAhAyABIARGDQQgCEEIaiEJIAIoAgAhBQJAAkAgASAERwRAIAVB2MIAaiEHIAQgBWogAWshACAFQX9zQQpqIgUgAWohBgNAIAEtAAAgBy0AAEcEQEECIQcMAwsgBUUEQEEAIQcgBiEBDAMLIAVBAWshBSAHQQFqIQcgBCABQQFqIgFHDQALIAAhBSAEIQELIAlBATYCACACIAU2AgAMAQsgAkEANgIAIAkgBzYCAAsgCSABNgIEIAgoAgwhACAIKAIIDgMBBAIACwALIAJBADYCHCACQbUaNgIQIAJBFzYCDCACIABBAWo2AhRBACEDDAILIAJBADYCHCACIAA2AhQgAkHKGjYCECACQQk2AgxBACEDDAELIAEgBEYEQEEiIQMMAQsgAkEJNgIIIAIgATYCBEEhIQMLIAhBEGokACADRQRAIAIoAgwhAAwBCyACIAM2AhxBACEAIAIoAgQiAUUNACACIAEgBCACKAIIEQEAIgFFDQAgAiAENgIUIAIgATYCDCABIQALIAALvgIBAn8gAEEAOgAAIABB3ABqIgFBAWtBADoAACAAQQA6AAIgAEEAOgABIAFBA2tBADoAACABQQJrQQA6AAAgAEEAOgADIAFBBGtBADoAAEEAIABrQQNxIgEgAGoiAEEANgIAQdwAIAFrQXxxIgIgAGoiAUEEa0EANgIAAkAgAkEJSQ0AIABBADYCCCAAQQA2AgQgAUEIa0EANgIAIAFBDGtBADYCACACQRlJDQAgAEEANgIYIABBADYCFCAAQQA2AhAgAEEANgIMIAFBEGtBADYCACABQRRrQQA2AgAgAUEYa0EANgIAIAFBHGtBADYCACACIABBBHFBGHIiAmsiAUEgSQ0AIAAgAmohAANAIABCADcDGCAAQgA3AxAgAEIANwMIIABCADcDACAAQSBqIQAgAUEgayIBQR9LDQALCwtWAQF/AkAgACgCDA0AAkACQAJAAkAgAC0ALw4DAQADAgsgACgCOCIBRQ0AIAEoAiwiAUUNACAAIAERAAAiAQ0DC0EADwsACyAAQcMWNgIQQQ4hAQsgAQsaACAAKAIMRQRAIABB0Rs2AhAgAEEVNgIMCwsUACAAKAIMQRVGBEAgAEEANgIMCwsUACAAKAIMQRZGBEAgAEEANgIMCwsHACAAKAIMCwcAIAAoAhALCQAgACABNgIQCwcAIAAoAhQLFwAgAEEkTwRAAAsgAEECdEGgM2ooAgALFwAgAEEuTwRAAAsgAEECdEGwNGooAgALvwkBAX9B6yghAQJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIABB5ABrDvQDY2IAAWFhYWFhYQIDBAVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhBgcICQoLDA0OD2FhYWFhEGFhYWFhYWFhYWFhEWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYRITFBUWFxgZGhthYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2YTc4OTphYWFhYWFhYTthYWE8YWFhYT0+P2FhYWFhYWFhQGFhQWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYUJDREVGR0hJSktMTU5PUFFSU2FhYWFhYWFhVFVWV1hZWlthXF1hYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFeYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhX2BhC0HhJw8LQaQhDwtByywPC0H+MQ8LQcAkDwtBqyQPC0GNKA8LQeImDwtBgDAPC0G5Lw8LQdckDwtB7x8PC0HhHw8LQfofDwtB8iAPC0GoLw8LQa4yDwtBiDAPC0HsJw8LQYIiDwtBjh0PC0HQLg8LQcojDwtBxTIPC0HfHA8LQdIcDwtBxCAPC0HXIA8LQaIfDwtB7S4PC0GrMA8LQdQlDwtBzC4PC0H6Lg8LQfwrDwtB0jAPC0HxHQ8LQbsgDwtB9ysPC0GQMQ8LQdcxDwtBoi0PC0HUJw8LQeArDwtBnywPC0HrMQ8LQdUfDwtByjEPC0HeJQ8LQdQeDwtB9BwPC0GnMg8LQbEdDwtBoB0PC0G5MQ8LQbwwDwtBkiEPC0GzJg8LQeksDwtBrB4PC0HUKw8LQfcmDwtBgCYPC0GwIQ8LQf4eDwtBjSMPC0GJLQ8LQfciDwtBoDEPC0GuHw8LQcYlDwtB6B4PC0GTIg8LQcIvDwtBwx0PC0GLLA8LQeEdDwtBjS8PC0HqIQ8LQbQtDwtB0i8PC0HfMg8LQdIyDwtB8DAPC0GpIg8LQfkjDwtBmR4PC0G1LA8LQZswDwtBkjIPC0G2Kw8LQcIiDwtB+DIPC0GeJQ8LQdAiDwtBuh4PC0GBHg8LAAtB1iEhAQsgAQsWACAAIAAtAC1B/gFxIAFBAEdyOgAtCxkAIAAgAC0ALUH9AXEgAUEAR0EBdHI6AC0LGQAgACAALQAtQfsBcSABQQBHQQJ0cjoALQsZACAAIAAtAC1B9wFxIAFBAEdBA3RyOgAtCz4BAn8CQCAAKAI4IgNFDQAgAygCBCIDRQ0AIAAgASACIAFrIAMRAQAiBEF/Rw0AIABBxhE2AhBBGCEECyAECz4BAn8CQCAAKAI4IgNFDQAgAygCCCIDRQ0AIAAgASACIAFrIAMRAQAiBEF/Rw0AIABB9go2AhBBGCEECyAECz4BAn8CQCAAKAI4IgNFDQAgAygCDCIDRQ0AIAAgASACIAFrIAMRAQAiBEF/Rw0AIABB7Ro2AhBBGCEECyAECz4BAn8CQCAAKAI4IgNFDQAgAygCECIDRQ0AIAAgASACIAFrIAMRAQAiBEF/Rw0AIABBlRA2AhBBGCEECyAECz4BAn8CQCAAKAI4IgNFDQAgAygCFCIDRQ0AIAAgASACIAFrIAMRAQAiBEF/Rw0AIABBqhs2AhBBGCEECyAECz4BAn8CQCAAKAI4IgNFDQAgAygCGCIDRQ0AIAAgASACIAFrIAMRAQAiBEF/Rw0AIABB7RM2AhBBGCEECyAECz4BAn8CQCAAKAI4IgNFDQAgAygCKCIDRQ0AIAAgASACIAFrIAMRAQAiBEF/Rw0AIABB9gg2AhBBGCEECyAECz4BAn8CQCAAKAI4IgNFDQAgAygCHCIDRQ0AIAAgASACIAFrIAMRAQAiBEF/Rw0AIABBwhk2AhBBGCEECyAECz4BAn8CQCAAKAI4IgNFDQAgAygCICIDRQ0AIAAgASACIAFrIAMRAQAiBEF/Rw0AIABBlBQ2AhBBGCEECyAEC1kBAn8CQCAALQAoQQFGDQAgAC8BMiIBQeQAa0HkAEkNACABQcwBRg0AIAFBsAJGDQAgAC8BMCIAQcAAcQ0AQQEhAiAAQYgEcUGABEYNACAAQShxRSECCyACC4wBAQJ/AkACQAJAIAAtACpFDQAgAC0AK0UNACAALwEwIgFBAnFFDQEMAgsgAC8BMCIBQQFxRQ0BC0EBIQIgAC0AKEEBRg0AIAAvATIiAEHkAGtB5ABJDQAgAEHMAUYNACAAQbACRg0AIAFBwABxDQBBACECIAFBiARxQYAERg0AIAFBKHFBAEchAgsgAgtXACAAQRhqQgA3AwAgAEIANwMAIABBOGpCADcDACAAQTBqQgA3AwAgAEEoakIANwMAIABBIGpCADcDACAAQRBqQgA3AwAgAEEIakIANwMAIABB3QE2AhwLBgAgABAyC5otAQt/IwBBEGsiCiQAQaTQACgCACIJRQRAQeTTACgCACIFRQRAQfDTAEJ/NwIAQejTAEKAgISAgIDAADcCAEHk0wAgCkEIakFwcUHYqtWqBXMiBTYCAEH40wBBADYCAEHI0wBBADYCAAtBzNMAQYDUBDYCAEGc0ABBgNQENgIAQbDQACAFNgIAQazQAEF/NgIAQdDTAEGArAM2AgADQCABQcjQAGogAUG80ABqIgI2AgAgAiABQbTQAGoiAzYCACABQcDQAGogAzYCACABQdDQAGogAUHE0ABqIgM2AgAgAyACNgIAIAFB2NAAaiABQczQAGoiAjYCACACIAM2AgAgAUHU0ABqIAI2AgAgAUEgaiIBQYACRw0AC0GM1ARBwasDNgIAQajQAEH00wAoAgA2AgBBmNAAQcCrAzYCAEGk0ABBiNQENgIAQcz/B0E4NgIAQYjUBCEJCwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIABB7AFNBEBBjNAAKAIAIgZBECAAQRNqQXBxIABBC0kbIgRBA3YiAHYiAUEDcQRAAkAgAUEBcSAAckEBcyICQQN0IgBBtNAAaiIBIABBvNAAaigCACIAKAIIIgNGBEBBjNAAIAZBfiACd3E2AgAMAQsgASADNgIIIAMgATYCDAsgAEEIaiEBIAAgAkEDdCICQQNyNgIEIAAgAmoiACAAKAIEQQFyNgIEDBELQZTQACgCACIIIARPDQEgAQRAAkBBAiAAdCICQQAgAmtyIAEgAHRxaCIAQQN0IgJBtNAAaiIBIAJBvNAAaigCACICKAIIIgNGBEBBjNAAIAZBfiAAd3EiBjYCAAwBCyABIAM2AgggAyABNgIMCyACIARBA3I2AgQgAEEDdCIAIARrIQUgACACaiAFNgIAIAIgBGoiBCAFQQFyNgIEIAgEQCAIQXhxQbTQAGohAEGg0AAoAgAhAwJ/QQEgCEEDdnQiASAGcUUEQEGM0AAgASAGcjYCACAADAELIAAoAggLIgEgAzYCDCAAIAM2AgggAyAANgIMIAMgATYCCAsgAkEIaiEBQaDQACAENgIAQZTQACAFNgIADBELQZDQACgCACILRQ0BIAtoQQJ0QbzSAGooAgAiACgCBEF4cSAEayEFIAAhAgNAAkAgAigCECIBRQRAIAJBFGooAgAiAUUNAQsgASgCBEF4cSAEayIDIAVJIQIgAyAFIAIbIQUgASAAIAIbIQAgASECDAELCyAAKAIYIQkgACgCDCIDIABHBEBBnNAAKAIAGiADIAAoAggiATYCCCABIAM2AgwMEAsgAEEUaiICKAIAIgFFBEAgACgCECIBRQ0DIABBEGohAgsDQCACIQcgASIDQRRqIgIoAgAiAQ0AIANBEGohAiADKAIQIgENAAsgB0EANgIADA8LQX8hBCAAQb9/Sw0AIABBE2oiAUFwcSEEQZDQACgCACIIRQ0AQQAgBGshBQJAAkACQAJ/QQAgBEGAAkkNABpBHyAEQf///wdLDQAaIARBJiABQQh2ZyIAa3ZBAXEgAEEBdGtBPmoLIgZBAnRBvNIAaigCACICRQRAQQAhAUEAIQMMAQtBACEBIARBGSAGQQF2a0EAIAZBH0cbdCEAQQAhAwNAAkAgAigCBEF4cSAEayIHIAVPDQAgAiEDIAciBQ0AQQAhBSACIQEMAwsgASACQRRqKAIAIgcgByACIABBHXZBBHFqQRBqKAIAIgJGGyABIAcbIQEgAEEBdCEAIAINAAsLIAEgA3JFBEBBACEDQQIgBnQiAEEAIABrciAIcSIARQ0DIABoQQJ0QbzSAGooAgAhAQsgAUUNAQsDQCABKAIEQXhxIARrIgIgBUkhACACIAUgABshBSABIAMgABshAyABKAIQIgAEfyAABSABQRRqKAIACyIBDQALCyADRQ0AIAVBlNAAKAIAIARrTw0AIAMoAhghByADIAMoAgwiAEcEQEGc0AAoAgAaIAAgAygCCCIBNgIIIAEgADYCDAwOCyADQRRqIgIoAgAiAUUEQCADKAIQIgFFDQMgA0EQaiECCwNAIAIhBiABIgBBFGoiAigCACIBDQAgAEEQaiECIAAoAhAiAQ0ACyAGQQA2AgAMDQtBlNAAKAIAIgMgBE8EQEGg0AAoAgAhAQJAIAMgBGsiAkEQTwRAIAEgBGoiACACQQFyNgIEIAEgA2ogAjYCACABIARBA3I2AgQMAQsgASADQQNyNgIEIAEgA2oiACAAKAIEQQFyNgIEQQAhAEEAIQILQZTQACACNgIAQaDQACAANgIAIAFBCGohAQwPC0GY0AAoAgAiAyAESwRAIAQgCWoiACADIARrIgFBAXI2AgRBpNAAIAA2AgBBmNAAIAE2AgAgCSAEQQNyNgIEIAlBCGohAQwPC0EAIQEgBAJ/QeTTACgCAARAQezTACgCAAwBC0Hw0wBCfzcCAEHo0wBCgICEgICAwAA3AgBB5NMAIApBDGpBcHFB2KrVqgVzNgIAQfjTAEEANgIAQcjTAEEANgIAQYCABAsiACAEQccAaiIFaiIGQQAgAGsiB3EiAk8EQEH80wBBMDYCAAwPCwJAQcTTACgCACIBRQ0AQbzTACgCACIIIAJqIQAgACABTSAAIAhLcQ0AQQAhAUH80wBBMDYCAAwPC0HI0wAtAABBBHENBAJAAkAgCQRAQczTACEBA0AgASgCACIAIAlNBEAgACABKAIEaiAJSw0DCyABKAIIIgENAAsLQQAQMyIAQX9GDQUgAiEGQejTACgCACIBQQFrIgMgAHEEQCACIABrIAAgA2pBACABa3FqIQYLIAQgBk8NBSAGQf7///8HSw0FQcTTACgCACIDBEBBvNMAKAIAIgcgBmohASABIAdNDQYgASADSw0GCyAGEDMiASAARw0BDAcLIAYgA2sgB3EiBkH+////B0sNBCAGEDMhACAAIAEoAgAgASgCBGpGDQMgACEBCwJAIAYgBEHIAGpPDQAgAUF/Rg0AQezTACgCACIAIAUgBmtqQQAgAGtxIgBB/v///wdLBEAgASEADAcLIAAQM0F/RwRAIAAgBmohBiABIQAMBwtBACAGaxAzGgwECyABIgBBf0cNBQwDC0EAIQMMDAtBACEADAoLIABBf0cNAgtByNMAQcjTACgCAEEEcjYCAAsgAkH+////B0sNASACEDMhAEEAEDMhASAAQX9GDQEgAUF/Rg0BIAAgAU8NASABIABrIgYgBEE4ak0NAQtBvNMAQbzTACgCACAGaiIBNgIAQcDTACgCACABSQRAQcDTACABNgIACwJAAkACQEGk0AAoAgAiAgRAQczTACEBA0AgACABKAIAIgMgASgCBCIFakYNAiABKAIIIgENAAsMAgtBnNAAKAIAIgFBAEcgACABT3FFBEBBnNAAIAA2AgALQQAhAUHQ0wAgBjYCAEHM0wAgADYCAEGs0ABBfzYCAEGw0ABB5NMAKAIANgIAQdjTAEEANgIAA0AgAUHI0ABqIAFBvNAAaiICNgIAIAIgAUG00ABqIgM2AgAgAUHA0ABqIAM2AgAgAUHQ0ABqIAFBxNAAaiIDNgIAIAMgAjYCACABQdjQAGogAUHM0ABqIgI2AgAgAiADNgIAIAFB1NAAaiACNgIAIAFBIGoiAUGAAkcNAAtBeCAAa0EPcSIBIABqIgIgBkE4ayIDIAFrIgFBAXI2AgRBqNAAQfTTACgCADYCAEGY0AAgATYCAEGk0AAgAjYCACAAIANqQTg2AgQMAgsgACACTQ0AIAIgA0kNACABKAIMQQhxDQBBeCACa0EPcSIAIAJqIgNBmNAAKAIAIAZqIgcgAGsiAEEBcjYCBCABIAUgBmo2AgRBqNAAQfTTACgCADYCAEGY0AAgADYCAEGk0AAgAzYCACACIAdqQTg2AgQMAQsgAEGc0AAoAgBJBEBBnNAAIAA2AgALIAAgBmohA0HM0wAhAQJAAkACQANAIAMgASgCAEcEQCABKAIIIgENAQwCCwsgAS0ADEEIcUUNAQtBzNMAIQEDQCABKAIAIgMgAk0EQCADIAEoAgRqIgUgAksNAwsgASgCCCEBDAALAAsgASAANgIAIAEgASgCBCAGajYCBCAAQXggAGtBD3FqIgkgBEEDcjYCBCADQXggA2tBD3FqIgYgBCAJaiIEayEBIAIgBkYEQEGk0AAgBDYCAEGY0ABBmNAAKAIAIAFqIgA2AgAgBCAAQQFyNgIEDAgLQaDQACgCACAGRgRAQaDQACAENgIAQZTQAEGU0AAoAgAgAWoiADYCACAEIABBAXI2AgQgACAEaiAANgIADAgLIAYoAgQiBUEDcUEBRw0GIAVBeHEhCCAFQf8BTQRAIAVBA3YhAyAGKAIIIgAgBigCDCICRgRAQYzQAEGM0AAoAgBBfiADd3E2AgAMBwsgAiAANgIIIAAgAjYCDAwGCyAGKAIYIQcgBiAGKAIMIgBHBEAgACAGKAIIIgI2AgggAiAANgIMDAULIAZBFGoiAigCACIFRQRAIAYoAhAiBUUNBCAGQRBqIQILA0AgAiEDIAUiAEEUaiICKAIAIgUNACAAQRBqIQIgACgCECIFDQALIANBADYCAAwEC0F4IABrQQ9xIgEgAGoiByAGQThrIgMgAWsiAUEBcjYCBCAAIANqQTg2AgQgAiAFQTcgBWtBD3FqQT9rIgMgAyACQRBqSRsiA0EjNgIEQajQAEH00wAoAgA2AgBBmNAAIAE2AgBBpNAAIAc2AgAgA0EQakHU0wApAgA3AgAgA0HM0wApAgA3AghB1NMAIANBCGo2AgBB0NMAIAY2AgBBzNMAIAA2AgBB2NMAQQA2AgAgA0EkaiEBA0AgAUEHNgIAIAUgAUEEaiIBSw0ACyACIANGDQAgAyADKAIEQX5xNgIEIAMgAyACayIFNgIAIAIgBUEBcjYCBCAFQf8BTQRAIAVBeHFBtNAAaiEAAn9BjNAAKAIAIgFBASAFQQN2dCIDcUUEQEGM0AAgASADcjYCACAADAELIAAoAggLIgEgAjYCDCAAIAI2AgggAiAANgIMIAIgATYCCAwBC0EfIQEgBUH///8HTQRAIAVBJiAFQQh2ZyIAa3ZBAXEgAEEBdGtBPmohAQsgAiABNgIcIAJCADcCECABQQJ0QbzSAGohAEGQ0AAoAgAiA0EBIAF0IgZxRQRAIAAgAjYCAEGQ0AAgAyAGcjYCACACIAA2AhggAiACNgIIIAIgAjYCDAwBCyAFQRkgAUEBdmtBACABQR9HG3QhASAAKAIAIQMCQANAIAMiACgCBEF4cSAFRg0BIAFBHXYhAyABQQF0IQEgACADQQRxakEQaiIGKAIAIgMNAAsgBiACNgIAIAIgADYCGCACIAI2AgwgAiACNgIIDAELIAAoAggiASACNgIMIAAgAjYCCCACQQA2AhggAiAANgIMIAIgATYCCAtBmNAAKAIAIgEgBE0NAEGk0AAoAgAiACAEaiICIAEgBGsiAUEBcjYCBEGY0AAgATYCAEGk0AAgAjYCACAAIARBA3I2AgQgAEEIaiEBDAgLQQAhAUH80wBBMDYCAAwHC0EAIQALIAdFDQACQCAGKAIcIgJBAnRBvNIAaiIDKAIAIAZGBEAgAyAANgIAIAANAUGQ0ABBkNAAKAIAQX4gAndxNgIADAILIAdBEEEUIAcoAhAgBkYbaiAANgIAIABFDQELIAAgBzYCGCAGKAIQIgIEQCAAIAI2AhAgAiAANgIYCyAGQRRqKAIAIgJFDQAgAEEUaiACNgIAIAIgADYCGAsgASAIaiEBIAYgCGoiBigCBCEFCyAGIAVBfnE2AgQgASAEaiABNgIAIAQgAUEBcjYCBCABQf8BTQRAIAFBeHFBtNAAaiEAAn9BjNAAKAIAIgJBASABQQN2dCIBcUUEQEGM0AAgASACcjYCACAADAELIAAoAggLIgEgBDYCDCAAIAQ2AgggBCAANgIMIAQgATYCCAwBC0EfIQUgAUH///8HTQRAIAFBJiABQQh2ZyIAa3ZBAXEgAEEBdGtBPmohBQsgBCAFNgIcIARCADcCECAFQQJ0QbzSAGohAEGQ0AAoAgAiAkEBIAV0IgNxRQRAIAAgBDYCAEGQ0AAgAiADcjYCACAEIAA2AhggBCAENgIIIAQgBDYCDAwBCyABQRkgBUEBdmtBACAFQR9HG3QhBSAAKAIAIQACQANAIAAiAigCBEF4cSABRg0BIAVBHXYhACAFQQF0IQUgAiAAQQRxakEQaiIDKAIAIgANAAsgAyAENgIAIAQgAjYCGCAEIAQ2AgwgBCAENgIIDAELIAIoAggiACAENgIMIAIgBDYCCCAEQQA2AhggBCACNgIMIAQgADYCCAsgCUEIaiEBDAILAkAgB0UNAAJAIAMoAhwiAUECdEG80gBqIgIoAgAgA0YEQCACIAA2AgAgAA0BQZDQACAIQX4gAXdxIgg2AgAMAgsgB0EQQRQgBygCECADRhtqIAA2AgAgAEUNAQsgACAHNgIYIAMoAhAiAQRAIAAgATYCECABIAA2AhgLIANBFGooAgAiAUUNACAAQRRqIAE2AgAgASAANgIYCwJAIAVBD00EQCADIAQgBWoiAEEDcjYCBCAAIANqIgAgACgCBEEBcjYCBAwBCyADIARqIgIgBUEBcjYCBCADIARBA3I2AgQgAiAFaiAFNgIAIAVB/wFNBEAgBUF4cUG00ABqIQACf0GM0AAoAgAiAUEBIAVBA3Z0IgVxRQRAQYzQACABIAVyNgIAIAAMAQsgACgCCAsiASACNgIMIAAgAjYCCCACIAA2AgwgAiABNgIIDAELQR8hASAFQf///wdNBEAgBUEmIAVBCHZnIgBrdkEBcSAAQQF0a0E+aiEBCyACIAE2AhwgAkIANwIQIAFBAnRBvNIAaiEAQQEgAXQiBCAIcUUEQCAAIAI2AgBBkNAAIAQgCHI2AgAgAiAANgIYIAIgAjYCCCACIAI2AgwMAQsgBUEZIAFBAXZrQQAgAUEfRxt0IQEgACgCACEEAkADQCAEIgAoAgRBeHEgBUYNASABQR12IQQgAUEBdCEBIAAgBEEEcWpBEGoiBigCACIEDQALIAYgAjYCACACIAA2AhggAiACNgIMIAIgAjYCCAwBCyAAKAIIIgEgAjYCDCAAIAI2AgggAkEANgIYIAIgADYCDCACIAE2AggLIANBCGohAQwBCwJAIAlFDQACQCAAKAIcIgFBAnRBvNIAaiICKAIAIABGBEAgAiADNgIAIAMNAUGQ0AAgC0F+IAF3cTYCAAwCCyAJQRBBFCAJKAIQIABGG2ogAzYCACADRQ0BCyADIAk2AhggACgCECIBBEAgAyABNgIQIAEgAzYCGAsgAEEUaigCACIBRQ0AIANBFGogATYCACABIAM2AhgLAkAgBUEPTQRAIAAgBCAFaiIBQQNyNgIEIAAgAWoiASABKAIEQQFyNgIEDAELIAAgBGoiByAFQQFyNgIEIAAgBEEDcjYCBCAFIAdqIAU2AgAgCARAIAhBeHFBtNAAaiEBQaDQACgCACEDAn9BASAIQQN2dCICIAZxRQRAQYzQACACIAZyNgIAIAEMAQsgASgCCAsiAiADNgIMIAEgAzYCCCADIAE2AgwgAyACNgIIC0Gg0AAgBzYCAEGU0AAgBTYCAAsgAEEIaiEBCyAKQRBqJAAgAQtDACAARQRAPwBBEHQPCwJAIABB//8DcQ0AIABBAEgNACAAQRB2QAAiAEF/RgRAQfzTAEEwNgIAQX8PCyAAQRB0DwsACwvcPyIAQYAICwkBAAAAAgAAAAMAQZQICwUEAAAABQBBpAgLCQYAAAAHAAAACABB3AgLii1JbnZhbGlkIGNoYXIgaW4gdXJsIHF1ZXJ5AFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fYm9keQBDb250ZW50LUxlbmd0aCBvdmVyZmxvdwBDaHVuayBzaXplIG92ZXJmbG93AFJlc3BvbnNlIG92ZXJmbG93AEludmFsaWQgbWV0aG9kIGZvciBIVFRQL3gueCByZXF1ZXN0AEludmFsaWQgbWV0aG9kIGZvciBSVFNQL3gueCByZXF1ZXN0AEV4cGVjdGVkIFNPVVJDRSBtZXRob2QgZm9yIElDRS94LnggcmVxdWVzdABJbnZhbGlkIGNoYXIgaW4gdXJsIGZyYWdtZW50IHN0YXJ0AEV4cGVjdGVkIGRvdABTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX3N0YXR1cwBJbnZhbGlkIHJlc3BvbnNlIHN0YXR1cwBJbnZhbGlkIGNoYXJhY3RlciBpbiBjaHVuayBleHRlbnNpb25zAFVzZXIgY2FsbGJhY2sgZXJyb3IAYG9uX3Jlc2V0YCBjYWxsYmFjayBlcnJvcgBgb25fY2h1bmtfaGVhZGVyYCBjYWxsYmFjayBlcnJvcgBgb25fbWVzc2FnZV9iZWdpbmAgY2FsbGJhY2sgZXJyb3IAYG9uX2NodW5rX2V4dGVuc2lvbl92YWx1ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX3N0YXR1c19jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX3ZlcnNpb25fY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl91cmxfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9jaHVua19jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX2hlYWRlcl92YWx1ZV9jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX21lc3NhZ2VfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9tZXRob2RfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9oZWFkZXJfZmllbGRfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9jaHVua19leHRlbnNpb25fbmFtZWAgY2FsbGJhY2sgZXJyb3IAVW5leHBlY3RlZCBjaGFyIGluIHVybCBzZXJ2ZXIASW52YWxpZCBoZWFkZXIgdmFsdWUgY2hhcgBJbnZhbGlkIGhlYWRlciBmaWVsZCBjaGFyAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fdmVyc2lvbgBJbnZhbGlkIG1pbm9yIHZlcnNpb24ASW52YWxpZCBtYWpvciB2ZXJzaW9uAEV4cGVjdGVkIHNwYWNlIGFmdGVyIHZlcnNpb24ARXhwZWN0ZWQgQ1JMRiBhZnRlciB2ZXJzaW9uAEludmFsaWQgSFRUUCB2ZXJzaW9uAEludmFsaWQgaGVhZGVyIHRva2VuAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fdXJsAEludmFsaWQgY2hhcmFjdGVycyBpbiB1cmwAVW5leHBlY3RlZCBzdGFydCBjaGFyIGluIHVybABEb3VibGUgQCBpbiB1cmwARW1wdHkgQ29udGVudC1MZW5ndGgASW52YWxpZCBjaGFyYWN0ZXIgaW4gQ29udGVudC1MZW5ndGgARHVwbGljYXRlIENvbnRlbnQtTGVuZ3RoAEludmFsaWQgY2hhciBpbiB1cmwgcGF0aABDb250ZW50LUxlbmd0aCBjYW4ndCBiZSBwcmVzZW50IHdpdGggVHJhbnNmZXItRW5jb2RpbmcASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgc2l6ZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2hlYWRlcl92YWx1ZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2NodW5rX2V4dGVuc2lvbl92YWx1ZQBJbnZhbGlkIGNoYXJhY3RlciBpbiBjaHVuayBleHRlbnNpb25zIHZhbHVlAE1pc3NpbmcgZXhwZWN0ZWQgTEYgYWZ0ZXIgaGVhZGVyIHZhbHVlAEludmFsaWQgYFRyYW5zZmVyLUVuY29kaW5nYCBoZWFkZXIgdmFsdWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyBxdW90ZSB2YWx1ZQBJbnZhbGlkIGNoYXJhY3RlciBpbiBjaHVuayBleHRlbnNpb25zIHF1b3RlZCB2YWx1ZQBQYXVzZWQgYnkgb25faGVhZGVyc19jb21wbGV0ZQBJbnZhbGlkIEVPRiBzdGF0ZQBvbl9yZXNldCBwYXVzZQBvbl9jaHVua19oZWFkZXIgcGF1c2UAb25fbWVzc2FnZV9iZWdpbiBwYXVzZQBvbl9jaHVua19leHRlbnNpb25fdmFsdWUgcGF1c2UAb25fc3RhdHVzX2NvbXBsZXRlIHBhdXNlAG9uX3ZlcnNpb25fY29tcGxldGUgcGF1c2UAb25fdXJsX2NvbXBsZXRlIHBhdXNlAG9uX2NodW5rX2NvbXBsZXRlIHBhdXNlAG9uX2hlYWRlcl92YWx1ZV9jb21wbGV0ZSBwYXVzZQBvbl9tZXNzYWdlX2NvbXBsZXRlIHBhdXNlAG9uX21ldGhvZF9jb21wbGV0ZSBwYXVzZQBvbl9oZWFkZXJfZmllbGRfY29tcGxldGUgcGF1c2UAb25fY2h1bmtfZXh0ZW5zaW9uX25hbWUgcGF1c2UAVW5leHBlY3RlZCBzcGFjZSBhZnRlciBzdGFydCBsaW5lAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fY2h1bmtfZXh0ZW5zaW9uX25hbWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyBuYW1lAFBhdXNlIG9uIENPTk5FQ1QvVXBncmFkZQBQYXVzZSBvbiBQUkkvVXBncmFkZQBFeHBlY3RlZCBIVFRQLzIgQ29ubmVjdGlvbiBQcmVmYWNlAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fbWV0aG9kAEV4cGVjdGVkIHNwYWNlIGFmdGVyIG1ldGhvZABTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2hlYWRlcl9maWVsZABQYXVzZWQASW52YWxpZCB3b3JkIGVuY291bnRlcmVkAEludmFsaWQgbWV0aG9kIGVuY291bnRlcmVkAFVuZXhwZWN0ZWQgY2hhciBpbiB1cmwgc2NoZW1hAFJlcXVlc3QgaGFzIGludmFsaWQgYFRyYW5zZmVyLUVuY29kaW5nYABTV0lUQ0hfUFJPWFkAVVNFX1BST1hZAE1LQUNUSVZJVFkAVU5QUk9DRVNTQUJMRV9FTlRJVFkAQ09QWQBNT1ZFRF9QRVJNQU5FTlRMWQBUT09fRUFSTFkATk9USUZZAEZBSUxFRF9ERVBFTkRFTkNZAEJBRF9HQVRFV0FZAFBMQVkAUFVUAENIRUNLT1VUAEdBVEVXQVlfVElNRU9VVABSRVFVRVNUX1RJTUVPVVQATkVUV09SS19DT05ORUNUX1RJTUVPVVQAQ09OTkVDVElPTl9USU1FT1VUAExPR0lOX1RJTUVPVVQATkVUV09SS19SRUFEX1RJTUVPVVQAUE9TVABNSVNESVJFQ1RFRF9SRVFVRVNUAENMSUVOVF9DTE9TRURfUkVRVUVTVABDTElFTlRfQ0xPU0VEX0xPQURfQkFMQU5DRURfUkVRVUVTVABCQURfUkVRVUVTVABIVFRQX1JFUVVFU1RfU0VOVF9UT19IVFRQU19QT1JUAFJFUE9SVABJTV9BX1RFQVBPVABSRVNFVF9DT05URU5UAE5PX0NPTlRFTlQAUEFSVElBTF9DT05URU5UAEhQRV9JTlZBTElEX0NPTlNUQU5UAEhQRV9DQl9SRVNFVABHRVQASFBFX1NUUklDVABDT05GTElDVABURU1QT1JBUllfUkVESVJFQ1QAUEVSTUFORU5UX1JFRElSRUNUAENPTk5FQ1QATVVMVElfU1RBVFVTAEhQRV9JTlZBTElEX1NUQVRVUwBUT09fTUFOWV9SRVFVRVNUUwBFQVJMWV9ISU5UUwBVTkFWQUlMQUJMRV9GT1JfTEVHQUxfUkVBU09OUwBPUFRJT05TAFNXSVRDSElOR19QUk9UT0NPTFMAVkFSSUFOVF9BTFNPX05FR09USUFURVMATVVMVElQTEVfQ0hPSUNFUwBJTlRFUk5BTF9TRVJWRVJfRVJST1IAV0VCX1NFUlZFUl9VTktOT1dOX0VSUk9SAFJBSUxHVU5fRVJST1IASURFTlRJVFlfUFJPVklERVJfQVVUSEVOVElDQVRJT05fRVJST1IAU1NMX0NFUlRJRklDQVRFX0VSUk9SAElOVkFMSURfWF9GT1JXQVJERURfRk9SAFNFVF9QQVJBTUVURVIAR0VUX1BBUkFNRVRFUgBIUEVfVVNFUgBTRUVfT1RIRVIASFBFX0NCX0NIVU5LX0hFQURFUgBNS0NBTEVOREFSAFNFVFVQAFdFQl9TRVJWRVJfSVNfRE9XTgBURUFSRE9XTgBIUEVfQ0xPU0VEX0NPTk5FQ1RJT04ASEVVUklTVElDX0VYUElSQVRJT04ARElTQ09OTkVDVEVEX09QRVJBVElPTgBOT05fQVVUSE9SSVRBVElWRV9JTkZPUk1BVElPTgBIUEVfSU5WQUxJRF9WRVJTSU9OAEhQRV9DQl9NRVNTQUdFX0JFR0lOAFNJVEVfSVNfRlJPWkVOAEhQRV9JTlZBTElEX0hFQURFUl9UT0tFTgBJTlZBTElEX1RPS0VOAEZPUkJJRERFTgBFTkhBTkNFX1lPVVJfQ0FMTQBIUEVfSU5WQUxJRF9VUkwAQkxPQ0tFRF9CWV9QQVJFTlRBTF9DT05UUk9MAE1LQ09MAEFDTABIUEVfSU5URVJOQUwAUkVRVUVTVF9IRUFERVJfRklFTERTX1RPT19MQVJHRV9VTk9GRklDSUFMAEhQRV9PSwBVTkxJTksAVU5MT0NLAFBSSQBSRVRSWV9XSVRIAEhQRV9JTlZBTElEX0NPTlRFTlRfTEVOR1RIAEhQRV9VTkVYUEVDVEVEX0NPTlRFTlRfTEVOR1RIAEZMVVNIAFBST1BQQVRDSABNLVNFQVJDSABVUklfVE9PX0xPTkcAUFJPQ0VTU0lORwBNSVNDRUxMQU5FT1VTX1BFUlNJU1RFTlRfV0FSTklORwBNSVNDRUxMQU5FT1VTX1dBUk5JTkcASFBFX0lOVkFMSURfVFJBTlNGRVJfRU5DT0RJTkcARXhwZWN0ZWQgQ1JMRgBIUEVfSU5WQUxJRF9DSFVOS19TSVpFAE1PVkUAQ09OVElOVUUASFBFX0NCX1NUQVRVU19DT01QTEVURQBIUEVfQ0JfSEVBREVSU19DT01QTEVURQBIUEVfQ0JfVkVSU0lPTl9DT01QTEVURQBIUEVfQ0JfVVJMX0NPTVBMRVRFAEhQRV9DQl9DSFVOS19DT01QTEVURQBIUEVfQ0JfSEVBREVSX1ZBTFVFX0NPTVBMRVRFAEhQRV9DQl9DSFVOS19FWFRFTlNJT05fVkFMVUVfQ09NUExFVEUASFBFX0NCX0NIVU5LX0VYVEVOU0lPTl9OQU1FX0NPTVBMRVRFAEhQRV9DQl9NRVNTQUdFX0NPTVBMRVRFAEhQRV9DQl9NRVRIT0RfQ09NUExFVEUASFBFX0NCX0hFQURFUl9GSUVMRF9DT01QTEVURQBERUxFVEUASFBFX0lOVkFMSURfRU9GX1NUQVRFAElOVkFMSURfU1NMX0NFUlRJRklDQVRFAFBBVVNFAE5PX1JFU1BPTlNFAFVOU1VQUE9SVEVEX01FRElBX1RZUEUAR09ORQBOT1RfQUNDRVBUQUJMRQBTRVJWSUNFX1VOQVZBSUxBQkxFAFJBTkdFX05PVF9TQVRJU0ZJQUJMRQBPUklHSU5fSVNfVU5SRUFDSEFCTEUAUkVTUE9OU0VfSVNfU1RBTEUAUFVSR0UATUVSR0UAUkVRVUVTVF9IRUFERVJfRklFTERTX1RPT19MQVJHRQBSRVFVRVNUX0hFQURFUl9UT09fTEFSR0UAUEFZTE9BRF9UT09fTEFSR0UASU5TVUZGSUNJRU5UX1NUT1JBR0UASFBFX1BBVVNFRF9VUEdSQURFAEhQRV9QQVVTRURfSDJfVVBHUkFERQBTT1VSQ0UAQU5OT1VOQ0UAVFJBQ0UASFBFX1VORVhQRUNURURfU1BBQ0UAREVTQ1JJQkUAVU5TVUJTQ1JJQkUAUkVDT1JEAEhQRV9JTlZBTElEX01FVEhPRABOT1RfRk9VTkQAUFJPUEZJTkQAVU5CSU5EAFJFQklORABVTkFVVEhPUklaRUQATUVUSE9EX05PVF9BTExPV0VEAEhUVFBfVkVSU0lPTl9OT1RfU1VQUE9SVEVEAEFMUkVBRFlfUkVQT1JURUQAQUNDRVBURUQATk9UX0lNUExFTUVOVEVEAExPT1BfREVURUNURUQASFBFX0NSX0VYUEVDVEVEAEhQRV9MRl9FWFBFQ1RFRABDUkVBVEVEAElNX1VTRUQASFBFX1BBVVNFRABUSU1FT1VUX09DQ1VSRUQAUEFZTUVOVF9SRVFVSVJFRABQUkVDT05ESVRJT05fUkVRVUlSRUQAUFJPWFlfQVVUSEVOVElDQVRJT05fUkVRVUlSRUQATkVUV09SS19BVVRIRU5USUNBVElPTl9SRVFVSVJFRABMRU5HVEhfUkVRVUlSRUQAU1NMX0NFUlRJRklDQVRFX1JFUVVJUkVEAFVQR1JBREVfUkVRVUlSRUQAUEFHRV9FWFBJUkVEAFBSRUNPTkRJVElPTl9GQUlMRUQARVhQRUNUQVRJT05fRkFJTEVEAFJFVkFMSURBVElPTl9GQUlMRUQAU1NMX0hBTkRTSEFLRV9GQUlMRUQATE9DS0VEAFRSQU5TRk9STUFUSU9OX0FQUExJRUQATk9UX01PRElGSUVEAE5PVF9FWFRFTkRFRABCQU5EV0lEVEhfTElNSVRfRVhDRUVERUQAU0lURV9JU19PVkVSTE9BREVEAEhFQUQARXhwZWN0ZWQgSFRUUC8AAF4TAAAmEwAAMBAAAPAXAACdEwAAFRIAADkXAADwEgAAChAAAHUSAACtEgAAghMAAE8UAAB/EAAAoBUAACMUAACJEgAAixQAAE0VAADUEQAAzxQAABAYAADJFgAA3BYAAMERAADgFwAAuxQAAHQUAAB8FQAA5RQAAAgXAAAfEAAAZRUAAKMUAAAoFQAAAhUAAJkVAAAsEAAAixkAAE8PAADUDgAAahAAAM4QAAACFwAAiQ4AAG4TAAAcEwAAZhQAAFYXAADBEwAAzRMAAGwTAABoFwAAZhcAAF8XAAAiEwAAzg8AAGkOAADYDgAAYxYAAMsTAACqDgAAKBcAACYXAADFEwAAXRYAAOgRAABnEwAAZRMAAPIWAABzEwAAHRcAAPkWAADzEQAAzw4AAM4VAAAMEgAAsxEAAKURAABhEAAAMhcAALsTAEH5NQsBAQBBkDYL4AEBAQIBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBB/TcLAQEAQZE4C14CAwICAgICAAACAgACAgACAgICAgICAgICAAQAAAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAgACAEH9OQsBAQBBkToLXgIAAgICAgIAAAICAAICAAICAgICAgICAgIAAwAEAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgICAgACAAIAQfA7Cw1sb3NlZWVwLWFsaXZlAEGJPAsBAQBBoDwL4AEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBBiT4LAQEAQaA+C+cBAQEBAQEBAQEBAQEBAgEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQFjaHVua2VkAEGwwAALXwEBAAEBAQEBAAABAQABAQABAQEBAQEBAQEBAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQABAEGQwgALIWVjdGlvbmVudC1sZW5ndGhvbnJveHktY29ubmVjdGlvbgBBwMIACy1yYW5zZmVyLWVuY29kaW5ncGdyYWRlDQoNCg0KU00NCg0KVFRQL0NFL1RTUC8AQfnCAAsFAQIAAQMAQZDDAAvgAQQBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAEH5xAALBQECAAEDAEGQxQAL4AEEAQEFAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBB+cYACwQBAAABAEGRxwAL3wEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAEH6yAALBAEAAAIAQZDJAAtfAwQAAAQEBAQEBAQEBAQEBQQEBAQEBAQEBAQEBAAEAAYHBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQABAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAQAQfrKAAsEAQAAAQBBkMsACwEBAEGqywALQQIAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAEH6zAALBAEAAAEAQZDNAAsBAQBBms0ACwYCAAAAAAIAQbHNAAs6AwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBB8M4AC5YBTk9VTkNFRUNLT1VUTkVDVEVURUNSSUJFTFVTSEVURUFEU0VBUkNIUkdFQ1RJVklUWUxFTkRBUlZFT1RJRllQVElPTlNDSFNFQVlTVEFUQ0hHRU9SRElSRUNUT1JUUkNIUEFSQU1FVEVSVVJDRUJTQ1JJQkVBUkRPV05BQ0VJTkROS0NLVUJTQ1JJQkVIVFRQL0FEVFAv", "base64"); +})); +//#endregion +//#region node_modules/undici/lib/llhttp/llhttp_simd-wasm.js +var require_llhttp_simd_wasm = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { Buffer: Buffer$1 } = __require("node:buffer"); + module.exports = Buffer$1.from("AGFzbQEAAAABJwdgAX8Bf2ADf39/AX9gAX8AYAJ/fwBgBH9/f38Bf2AAAGADf39/AALLAQgDZW52GHdhc21fb25faGVhZGVyc19jb21wbGV0ZQAEA2VudhV3YXNtX29uX21lc3NhZ2VfYmVnaW4AAANlbnYLd2FzbV9vbl91cmwAAQNlbnYOd2FzbV9vbl9zdGF0dXMAAQNlbnYUd2FzbV9vbl9oZWFkZXJfZmllbGQAAQNlbnYUd2FzbV9vbl9oZWFkZXJfdmFsdWUAAQNlbnYMd2FzbV9vbl9ib2R5AAEDZW52GHdhc21fb25fbWVzc2FnZV9jb21wbGV0ZQAAAy0sBQYAAAIAAAAAAAACAQIAAgICAAADAAAAAAMDAwMBAQEBAQEBAQEAAAIAAAAEBQFwARISBQMBAAIGCAF/AUGA1AQLB9EFIgZtZW1vcnkCAAtfaW5pdGlhbGl6ZQAIGV9faW5kaXJlY3RfZnVuY3Rpb25fdGFibGUBAAtsbGh0dHBfaW5pdAAJGGxsaHR0cF9zaG91bGRfa2VlcF9hbGl2ZQAvDGxsaHR0cF9hbGxvYwALBm1hbGxvYwAxC2xsaHR0cF9mcmVlAAwEZnJlZQAMD2xsaHR0cF9nZXRfdHlwZQANFWxsaHR0cF9nZXRfaHR0cF9tYWpvcgAOFWxsaHR0cF9nZXRfaHR0cF9taW5vcgAPEWxsaHR0cF9nZXRfbWV0aG9kABAWbGxodHRwX2dldF9zdGF0dXNfY29kZQAREmxsaHR0cF9nZXRfdXBncmFkZQASDGxsaHR0cF9yZXNldAATDmxsaHR0cF9leGVjdXRlABQUbGxodHRwX3NldHRpbmdzX2luaXQAFQ1sbGh0dHBfZmluaXNoABYMbGxodHRwX3BhdXNlABcNbGxodHRwX3Jlc3VtZQAYG2xsaHR0cF9yZXN1bWVfYWZ0ZXJfdXBncmFkZQAZEGxsaHR0cF9nZXRfZXJybm8AGhdsbGh0dHBfZ2V0X2Vycm9yX3JlYXNvbgAbF2xsaHR0cF9zZXRfZXJyb3JfcmVhc29uABwUbGxodHRwX2dldF9lcnJvcl9wb3MAHRFsbGh0dHBfZXJybm9fbmFtZQAeEmxsaHR0cF9tZXRob2RfbmFtZQAfEmxsaHR0cF9zdGF0dXNfbmFtZQAgGmxsaHR0cF9zZXRfbGVuaWVudF9oZWFkZXJzACEhbGxodHRwX3NldF9sZW5pZW50X2NodW5rZWRfbGVuZ3RoACIdbGxodHRwX3NldF9sZW5pZW50X2tlZXBfYWxpdmUAIyRsbGh0dHBfc2V0X2xlbmllbnRfdHJhbnNmZXJfZW5jb2RpbmcAJBhsbGh0dHBfbWVzc2FnZV9uZWVkc19lb2YALgkXAQBBAQsRAQIDBAUKBgcrLSwqKSglJyYK77MCLBYAQYjQACgCAARAAAtBiNAAQQE2AgALFAAgABAwIAAgAjYCOCAAIAE6ACgLFAAgACAALwEyIAAtAC4gABAvEAALHgEBf0HAABAyIgEQMCABQYAINgI4IAEgADoAKCABC48MAQd/AkAgAEUNACAAQQhrIgEgAEEEaygCACIAQXhxIgRqIQUCQCAAQQFxDQAgAEEDcUUNASABIAEoAgAiAGsiAUGc0AAoAgBJDQEgACAEaiEEAkACQEGg0AAoAgAgAUcEQCAAQf8BTQRAIABBA3YhAyABKAIIIgAgASgCDCICRgRAQYzQAEGM0AAoAgBBfiADd3E2AgAMBQsgAiAANgIIIAAgAjYCDAwECyABKAIYIQYgASABKAIMIgBHBEAgACABKAIIIgI2AgggAiAANgIMDAMLIAFBFGoiAygCACICRQRAIAEoAhAiAkUNAiABQRBqIQMLA0AgAyEHIAIiAEEUaiIDKAIAIgINACAAQRBqIQMgACgCECICDQALIAdBADYCAAwCCyAFKAIEIgBBA3FBA0cNAiAFIABBfnE2AgRBlNAAIAQ2AgAgBSAENgIAIAEgBEEBcjYCBAwDC0EAIQALIAZFDQACQCABKAIcIgJBAnRBvNIAaiIDKAIAIAFGBEAgAyAANgIAIAANAUGQ0ABBkNAAKAIAQX4gAndxNgIADAILIAZBEEEUIAYoAhAgAUYbaiAANgIAIABFDQELIAAgBjYCGCABKAIQIgIEQCAAIAI2AhAgAiAANgIYCyABQRRqKAIAIgJFDQAgAEEUaiACNgIAIAIgADYCGAsgASAFTw0AIAUoAgQiAEEBcUUNAAJAAkACQAJAIABBAnFFBEBBpNAAKAIAIAVGBEBBpNAAIAE2AgBBmNAAQZjQACgCACAEaiIANgIAIAEgAEEBcjYCBCABQaDQACgCAEcNBkGU0ABBADYCAEGg0ABBADYCAAwGC0Gg0AAoAgAgBUYEQEGg0AAgATYCAEGU0ABBlNAAKAIAIARqIgA2AgAgASAAQQFyNgIEIAAgAWogADYCAAwGCyAAQXhxIARqIQQgAEH/AU0EQCAAQQN2IQMgBSgCCCIAIAUoAgwiAkYEQEGM0ABBjNAAKAIAQX4gA3dxNgIADAULIAIgADYCCCAAIAI2AgwMBAsgBSgCGCEGIAUgBSgCDCIARwRAQZzQACgCABogACAFKAIIIgI2AgggAiAANgIMDAMLIAVBFGoiAygCACICRQRAIAUoAhAiAkUNAiAFQRBqIQMLA0AgAyEHIAIiAEEUaiIDKAIAIgINACAAQRBqIQMgACgCECICDQALIAdBADYCAAwCCyAFIABBfnE2AgQgASAEaiAENgIAIAEgBEEBcjYCBAwDC0EAIQALIAZFDQACQCAFKAIcIgJBAnRBvNIAaiIDKAIAIAVGBEAgAyAANgIAIAANAUGQ0ABBkNAAKAIAQX4gAndxNgIADAILIAZBEEEUIAYoAhAgBUYbaiAANgIAIABFDQELIAAgBjYCGCAFKAIQIgIEQCAAIAI2AhAgAiAANgIYCyAFQRRqKAIAIgJFDQAgAEEUaiACNgIAIAIgADYCGAsgASAEaiAENgIAIAEgBEEBcjYCBCABQaDQACgCAEcNAEGU0AAgBDYCAAwBCyAEQf8BTQRAIARBeHFBtNAAaiEAAn9BjNAAKAIAIgJBASAEQQN2dCIDcUUEQEGM0AAgAiADcjYCACAADAELIAAoAggLIgIgATYCDCAAIAE2AgggASAANgIMIAEgAjYCCAwBC0EfIQIgBEH///8HTQRAIARBJiAEQQh2ZyIAa3ZBAXEgAEEBdGtBPmohAgsgASACNgIcIAFCADcCECACQQJ0QbzSAGohAAJAQZDQACgCACIDQQEgAnQiB3FFBEAgACABNgIAQZDQACADIAdyNgIAIAEgADYCGCABIAE2AgggASABNgIMDAELIARBGSACQQF2a0EAIAJBH0cbdCECIAAoAgAhAAJAA0AgACIDKAIEQXhxIARGDQEgAkEddiEAIAJBAXQhAiADIABBBHFqQRBqIgcoAgAiAA0ACyAHIAE2AgAgASADNgIYIAEgATYCDCABIAE2AggMAQsgAygCCCIAIAE2AgwgAyABNgIIIAFBADYCGCABIAM2AgwgASAANgIIC0Gs0ABBrNAAKAIAQQFrIgBBfyAAGzYCAAsLBwAgAC0AKAsHACAALQAqCwcAIAAtACsLBwAgAC0AKQsHACAALwEyCwcAIAAtAC4LQAEEfyAAKAIYIQEgAC0ALSECIAAtACghAyAAKAI4IQQgABAwIAAgBDYCOCAAIAM6ACggACACOgAtIAAgATYCGAu74gECB38DfiABIAJqIQQCQCAAIgIoAgwiAA0AIAIoAgQEQCACIAE2AgQLIwBBEGsiCCQAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIAIoAhwiA0EBaw7dAdoBAdkBAgMEBQYHCAkKCwwNDtgBDxDXARES1gETFBUWFxgZGhvgAd8BHB0e1QEfICEiIyQl1AEmJygpKiss0wHSAS0u0QHQAS8wMTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRtsBR0hJSs8BzgFLzQFMzAFNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AAYEBggGDAYQBhQGGAYcBiAGJAYoBiwGMAY0BjgGPAZABkQGSAZMBlAGVAZYBlwGYAZkBmgGbAZwBnQGeAZ8BoAGhAaIBowGkAaUBpgGnAagBqQGqAasBrAGtAa4BrwGwAbEBsgGzAbQBtQG2AbcBywHKAbgByQG5AcgBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgEA3AELQQAMxgELQQ4MxQELQQ0MxAELQQ8MwwELQRAMwgELQRMMwQELQRQMwAELQRUMvwELQRYMvgELQRgMvQELQRkMvAELQRoMuwELQRsMugELQRwMuQELQR0MuAELQQgMtwELQR4MtgELQSAMtQELQR8MtAELQQcMswELQSEMsgELQSIMsQELQSMMsAELQSQMrwELQRIMrgELQREMrQELQSUMrAELQSYMqwELQScMqgELQSgMqQELQcMBDKgBC0EqDKcBC0ErDKYBC0EsDKUBC0EtDKQBC0EuDKMBC0EvDKIBC0HEAQyhAQtBMAygAQtBNAyfAQtBDAyeAQtBMQydAQtBMgycAQtBMwybAQtBOQyaAQtBNQyZAQtBxQEMmAELQQsMlwELQToMlgELQTYMlQELQQoMlAELQTcMkwELQTgMkgELQTwMkQELQTsMkAELQT0MjwELQQkMjgELQSkMjQELQT4MjAELQT8MiwELQcAADIoBC0HBAAyJAQtBwgAMiAELQcMADIcBC0HEAAyGAQtBxQAMhQELQcYADIQBC0EXDIMBC0HHAAyCAQtByAAMgQELQckADIABC0HKAAx/C0HLAAx+C0HNAAx9C0HMAAx8C0HOAAx7C0HPAAx6C0HQAAx5C0HRAAx4C0HSAAx3C0HTAAx2C0HUAAx1C0HWAAx0C0HVAAxzC0EGDHILQdcADHELQQUMcAtB2AAMbwtBBAxuC0HZAAxtC0HaAAxsC0HbAAxrC0HcAAxqC0EDDGkLQd0ADGgLQd4ADGcLQd8ADGYLQeEADGULQeAADGQLQeIADGMLQeMADGILQQIMYQtB5AAMYAtB5QAMXwtB5gAMXgtB5wAMXQtB6AAMXAtB6QAMWwtB6gAMWgtB6wAMWQtB7AAMWAtB7QAMVwtB7gAMVgtB7wAMVQtB8AAMVAtB8QAMUwtB8gAMUgtB8wAMUQtB9AAMUAtB9QAMTwtB9gAMTgtB9wAMTQtB+AAMTAtB+QAMSwtB+gAMSgtB+wAMSQtB/AAMSAtB/QAMRwtB/gAMRgtB/wAMRQtBgAEMRAtBgQEMQwtBggEMQgtBgwEMQQtBhAEMQAtBhQEMPwtBhgEMPgtBhwEMPQtBiAEMPAtBiQEMOwtBigEMOgtBiwEMOQtBjAEMOAtBjQEMNwtBjgEMNgtBjwEMNQtBkAEMNAtBkQEMMwtBkgEMMgtBkwEMMQtBlAEMMAtBlQEMLwtBlgEMLgtBlwEMLQtBmAEMLAtBmQEMKwtBmgEMKgtBmwEMKQtBnAEMKAtBnQEMJwtBngEMJgtBnwEMJQtBoAEMJAtBoQEMIwtBogEMIgtBowEMIQtBpAEMIAtBpQEMHwtBpgEMHgtBpwEMHQtBqAEMHAtBqQEMGwtBqgEMGgtBqwEMGQtBrAEMGAtBrQEMFwtBrgEMFgtBAQwVC0GvAQwUC0GwAQwTC0GxAQwSC0GzAQwRC0GyAQwQC0G0AQwPC0G1AQwOC0G2AQwNC0G3AQwMC0G4AQwLC0G5AQwKC0G6AQwJC0G7AQwIC0HGAQwHC0G8AQwGC0G9AQwFC0G+AQwEC0G/AQwDC0HAAQwCC0HCAQwBC0HBAQshAwNAAkACQAJAAkACQAJAAkACQAJAIAICfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJ/AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAgJ/AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACfwJAAkACfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACfwJAAkACQAJAAn8CQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCADDsYBAAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHyAhIyUmKCorLC8wMTIzNDU2Nzk6Ozw9lANAQkRFRklLTk9QUVJTVFVWWFpbXF1eX2BhYmNkZWZnaGpsb3Bxc3V2eHl6e3x/gAGBAYIBgwGEAYUBhgGHAYgBiQGKAYsBjAGNAY4BjwGQAZEBkgGTAZQBlQGWAZcBmAGZAZoBmwGcAZ0BngGfAaABoQGiAaMBpAGlAaYBpwGoAakBqgGrAawBrQGuAa8BsAGxAbIBswG0AbUBtgG3AbgBuQG6AbsBvAG9Ab4BvwHAAcEBwgHDAcQBxQHGAccByAHJAcsBzAHNAc4BzwGKA4kDiAOHA4QDgwOAA/sC+gL5AvgC9wL0AvMC8gLLAsECsALZAQsgASAERw3wAkHdASEDDLMDCyABIARHDcgBQcMBIQMMsgMLIAEgBEcNe0H3ACEDDLEDCyABIARHDXBB7wAhAwywAwsgASAERw1pQeoAIQMMrwMLIAEgBEcNZUHoACEDDK4DCyABIARHDWJB5gAhAwytAwsgASAERw0aQRghAwysAwsgASAERw0VQRIhAwyrAwsgASAERw1CQcUAIQMMqgMLIAEgBEcNNEE/IQMMqQMLIAEgBEcNMkE8IQMMqAMLIAEgBEcNK0ExIQMMpwMLIAItAC5BAUYNnwMMwQILQQAhAAJAAkACQCACLQAqRQ0AIAItACtFDQAgAi8BMCIDQQJxRQ0BDAILIAIvATAiA0EBcUUNAQtBASEAIAItAChBAUYNACACLwEyIgVB5ABrQeQASQ0AIAVBzAFGDQAgBUGwAkYNACADQcAAcQ0AQQAhACADQYgEcUGABEYNACADQShxQQBHIQALIAJBADsBMCACQQA6AC8gAEUN3wIgAkIANwMgDOACC0EAIQACQCACKAI4IgNFDQAgAygCLCIDRQ0AIAIgAxEAACEACyAARQ3MASAAQRVHDd0CIAJBBDYCHCACIAE2AhQgAkGwGDYCECACQRU2AgxBACEDDKQDCyABIARGBEBBBiEDDKQDCyABQQFqIQFBACEAAkAgAigCOCIDRQ0AIAMoAlQiA0UNACACIAMRAAAhAAsgAA3ZAgwcCyACQgA3AyBBEiEDDIkDCyABIARHDRZBHSEDDKEDCyABIARHBEAgAUEBaiEBQRAhAwyIAwtBByEDDKADCyACIAIpAyAiCiAEIAFrrSILfSIMQgAgCiAMWhs3AyAgCiALWA3UAkEIIQMMnwMLIAEgBEcEQCACQQk2AgggAiABNgIEQRQhAwyGAwtBCSEDDJ4DCyACKQMgQgBSDccBIAIgAi8BMEGAAXI7ATAMQgsgASAERw0/QdAAIQMMnAMLIAEgBEYEQEELIQMMnAMLIAFBAWohAUEAIQACQCACKAI4IgNFDQAgAygCUCIDRQ0AIAIgAxEAACEACyAADc8CDMYBC0EAIQACQCACKAI4IgNFDQAgAygCSCIDRQ0AIAIgAxEAACEACyAARQ3GASAAQRVHDc0CIAJBCzYCHCACIAE2AhQgAkGCGTYCECACQRU2AgxBACEDDJoDC0EAIQACQCACKAI4IgNFDQAgAygCSCIDRQ0AIAIgAxEAACEACyAARQ0MIABBFUcNygIgAkEaNgIcIAIgATYCFCACQYIZNgIQIAJBFTYCDEEAIQMMmQMLQQAhAAJAIAIoAjgiA0UNACADKAJMIgNFDQAgAiADEQAAIQALIABFDcQBIABBFUcNxwIgAkELNgIcIAIgATYCFCACQZEXNgIQIAJBFTYCDEEAIQMMmAMLIAEgBEYEQEEPIQMMmAMLIAEtAAAiAEE7Rg0HIABBDUcNxAIgAUEBaiEBDMMBC0EAIQACQCACKAI4IgNFDQAgAygCTCIDRQ0AIAIgAxEAACEACyAARQ3DASAAQRVHDcICIAJBDzYCHCACIAE2AhQgAkGRFzYCECACQRU2AgxBACEDDJYDCwNAIAEtAABB8DVqLQAAIgBBAUcEQCAAQQJHDcECIAIoAgQhAEEAIQMgAkEANgIEIAIgACABQQFqIgEQLSIADcICDMUBCyAEIAFBAWoiAUcNAAtBEiEDDJUDC0EAIQACQCACKAI4IgNFDQAgAygCTCIDRQ0AIAIgAxEAACEACyAARQ3FASAAQRVHDb0CIAJBGzYCHCACIAE2AhQgAkGRFzYCECACQRU2AgxBACEDDJQDCyABIARGBEBBFiEDDJQDCyACQQo2AgggAiABNgIEQQAhAAJAIAIoAjgiA0UNACADKAJIIgNFDQAgAiADEQAAIQALIABFDcIBIABBFUcNuQIgAkEVNgIcIAIgATYCFCACQYIZNgIQIAJBFTYCDEEAIQMMkwMLIAEgBEcEQANAIAEtAABB8DdqLQAAIgBBAkcEQAJAIABBAWsOBMQCvQIAvgK9AgsgAUEBaiEBQQghAwz8AgsgBCABQQFqIgFHDQALQRUhAwyTAwtBFSEDDJIDCwNAIAEtAABB8DlqLQAAIgBBAkcEQCAAQQFrDgTFArcCwwK4ArcCCyAEIAFBAWoiAUcNAAtBGCEDDJEDCyABIARHBEAgAkELNgIIIAIgATYCBEEHIQMM+AILQRkhAwyQAwsgAUEBaiEBDAILIAEgBEYEQEEaIQMMjwMLAkAgAS0AAEENaw4UtQG/Ab8BvwG/Ab8BvwG/Ab8BvwG/Ab8BvwG/Ab8BvwG/Ab8BvwEAvwELQQAhAyACQQA2AhwgAkGvCzYCECACQQI2AgwgAiABQQFqNgIUDI4DCyABIARGBEBBGyEDDI4DCyABLQAAIgBBO0cEQCAAQQ1HDbECIAFBAWohAQy6AQsgAUEBaiEBC0EiIQMM8wILIAEgBEYEQEEcIQMMjAMLQgAhCgJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAS0AAEEwaw43wQLAAgABAgMEBQYH0AHQAdAB0AHQAdAB0AEICQoLDA3QAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdABDg8QERIT0AELQgIhCgzAAgtCAyEKDL8CC0IEIQoMvgILQgUhCgy9AgtCBiEKDLwCC0IHIQoMuwILQgghCgy6AgtCCSEKDLkCC0IKIQoMuAILQgshCgy3AgtCDCEKDLYCC0INIQoMtQILQg4hCgy0AgtCDyEKDLMCC0IKIQoMsgILQgshCgyxAgtCDCEKDLACC0INIQoMrwILQg4hCgyuAgtCDyEKDK0CC0IAIQoCQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIAEtAABBMGsON8ACvwIAAQIDBAUGB74CvgK+Ar4CvgK+Ar4CCAkKCwwNvgK+Ar4CvgK+Ar4CvgK+Ar4CvgK+Ar4CvgK+Ar4CvgK+Ar4CvgK+Ar4CvgK+Ar4CvgK+Ag4PEBESE74CC0ICIQoMvwILQgMhCgy+AgtCBCEKDL0CC0IFIQoMvAILQgYhCgy7AgtCByEKDLoCC0IIIQoMuQILQgkhCgy4AgtCCiEKDLcCC0ILIQoMtgILQgwhCgy1AgtCDSEKDLQCC0IOIQoMswILQg8hCgyyAgtCCiEKDLECC0ILIQoMsAILQgwhCgyvAgtCDSEKDK4CC0IOIQoMrQILQg8hCgysAgsgAiACKQMgIgogBCABa60iC30iDEIAIAogDFobNwMgIAogC1gNpwJBHyEDDIkDCyABIARHBEAgAkEJNgIIIAIgATYCBEElIQMM8AILQSAhAwyIAwtBASEFIAIvATAiA0EIcUUEQCACKQMgQgBSIQULAkAgAi0ALgRAQQEhACACLQApQQVGDQEgA0HAAHFFIAVxRQ0BC0EAIQAgA0HAAHENAEECIQAgA0EIcQ0AIANBgARxBEACQCACLQAoQQFHDQAgAi0ALUEKcQ0AQQUhAAwCC0EEIQAMAQsgA0EgcUUEQAJAIAItAChBAUYNACACLwEyIgBB5ABrQeQASQ0AIABBzAFGDQAgAEGwAkYNAEEEIQAgA0EocUUNAiADQYgEcUGABEYNAgtBACEADAELQQBBAyACKQMgUBshAAsgAEEBaw4FvgIAsAEBpAKhAgtBESEDDO0CCyACQQE6AC8MhAMLIAEgBEcNnQJBJCEDDIQDCyABIARHDRxBxgAhAwyDAwtBACEAAkAgAigCOCIDRQ0AIAMoAkQiA0UNACACIAMRAAAhAAsgAEUNJyAAQRVHDZgCIAJB0AA2AhwgAiABNgIUIAJBkRg2AhAgAkEVNgIMQQAhAwyCAwsgASAERgRAQSghAwyCAwtBACEDIAJBADYCBCACQQw2AgggAiABIAEQKiIARQ2UAiACQSc2AhwgAiABNgIUIAIgADYCDAyBAwsgASAERgRAQSkhAwyBAwsgAS0AACIAQSBGDRMgAEEJRw2VAiABQQFqIQEMFAsgASAERwRAIAFBAWohAQwWC0EqIQMM/wILIAEgBEYEQEErIQMM/wILIAEtAAAiAEEJRyAAQSBHcQ2QAiACLQAsQQhHDd0CIAJBADoALAzdAgsgASAERgRAQSwhAwz+AgsgAS0AAEEKRw2OAiABQQFqIQEMsAELIAEgBEcNigJBLyEDDPwCCwNAIAEtAAAiAEEgRwRAIABBCmsOBIQCiAKIAoQChgILIAQgAUEBaiIBRw0AC0ExIQMM+wILQTIhAyABIARGDfoCIAIoAgAiACAEIAFraiEHIAEgAGtBA2ohBgJAA0AgAEHwO2otAAAgAS0AACIFQSByIAUgBUHBAGtB/wFxQRpJG0H/AXFHDQEgAEEDRgRAQQYhAQziAgsgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAc2AgAM+wILIAJBADYCAAyGAgtBMyEDIAQgASIARg35AiAEIAFrIAIoAgAiAWohByAAIAFrQQhqIQYCQANAIAFB9DtqLQAAIAAtAAAiBUEgciAFIAVBwQBrQf8BcUEaSRtB/wFxRw0BIAFBCEYEQEEFIQEM4QILIAFBAWohASAEIABBAWoiAEcNAAsgAiAHNgIADPoCCyACQQA2AgAgACEBDIUCC0E0IQMgBCABIgBGDfgCIAQgAWsgAigCACIBaiEHIAAgAWtBBWohBgJAA0AgAUHQwgBqLQAAIAAtAAAiBUEgciAFIAVBwQBrQf8BcUEaSRtB/wFxRw0BIAFBBUYEQEEHIQEM4AILIAFBAWohASAEIABBAWoiAEcNAAsgAiAHNgIADPkCCyACQQA2AgAgACEBDIQCCyABIARHBEADQCABLQAAQYA+ai0AACIAQQFHBEAgAEECRg0JDIECCyAEIAFBAWoiAUcNAAtBMCEDDPgCC0EwIQMM9wILIAEgBEcEQANAIAEtAAAiAEEgRwRAIABBCmsOBP8B/gH+Af8B/gELIAQgAUEBaiIBRw0AC0E4IQMM9wILQTghAwz2AgsDQCABLQAAIgBBIEcgAEEJR3EN9gEgBCABQQFqIgFHDQALQTwhAwz1AgsDQCABLQAAIgBBIEcEQAJAIABBCmsOBPkBBAT5AQALIABBLEYN9QEMAwsgBCABQQFqIgFHDQALQT8hAwz0AgtBwAAhAyABIARGDfMCIAIoAgAiACAEIAFraiEFIAEgAGtBBmohBgJAA0AgAEGAQGstAAAgAS0AAEEgckcNASAAQQZGDdsCIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADPQCCyACQQA2AgALQTYhAwzZAgsgASAERgRAQcEAIQMM8gILIAJBDDYCCCACIAE2AgQgAi0ALEEBaw4E+wHuAewB6wHUAgsgAUEBaiEBDPoBCyABIARHBEADQAJAIAEtAAAiAEEgciAAIABBwQBrQf8BcUEaSRtB/wFxIgBBCUYNACAAQSBGDQACQAJAAkACQCAAQeMAaw4TAAMDAwMDAwMBAwMDAwMDAwMDAgMLIAFBAWohAUExIQMM3AILIAFBAWohAUEyIQMM2wILIAFBAWohAUEzIQMM2gILDP4BCyAEIAFBAWoiAUcNAAtBNSEDDPACC0E1IQMM7wILIAEgBEcEQANAIAEtAABBgDxqLQAAQQFHDfcBIAQgAUEBaiIBRw0AC0E9IQMM7wILQT0hAwzuAgtBACEAAkAgAigCOCIDRQ0AIAMoAkAiA0UNACACIAMRAAAhAAsgAEUNASAAQRVHDeYBIAJBwgA2AhwgAiABNgIUIAJB4xg2AhAgAkEVNgIMQQAhAwztAgsgAUEBaiEBC0E8IQMM0gILIAEgBEYEQEHCACEDDOsCCwJAA0ACQCABLQAAQQlrDhgAAswCzALRAswCzALMAswCzALMAswCzALMAswCzALMAswCzALMAswCzALMAgDMAgsgBCABQQFqIgFHDQALQcIAIQMM6wILIAFBAWohASACLQAtQQFxRQ3+AQtBLCEDDNACCyABIARHDd4BQcQAIQMM6AILA0AgAS0AAEGQwABqLQAAQQFHDZwBIAQgAUEBaiIBRw0AC0HFACEDDOcCCyABLQAAIgBBIEYN/gEgAEE6Rw3AAiACKAIEIQBBACEDIAJBADYCBCACIAAgARApIgAN3gEM3QELQccAIQMgBCABIgBGDeUCIAQgAWsgAigCACIBaiEHIAAgAWtBBWohBgNAIAFBkMIAai0AACAALQAAIgVBIHIgBSAFQcEAa0H/AXFBGkkbQf8BcUcNvwIgAUEFRg3CAiABQQFqIQEgBCAAQQFqIgBHDQALIAIgBzYCAAzlAgtByAAhAyAEIAEiAEYN5AIgBCABayACKAIAIgFqIQcgACABa0EJaiEGA0AgAUGWwgBqLQAAIAAtAAAiBUEgciAFIAVBwQBrQf8BcUEaSRtB/wFxRw2+AkECIAFBCUYNwgIaIAFBAWohASAEIABBAWoiAEcNAAsgAiAHNgIADOQCCyABIARGBEBByQAhAwzkAgsCQAJAIAEtAAAiAEEgciAAIABBwQBrQf8BcUEaSRtB/wFxQe4Aaw4HAL8CvwK/Ar8CvwIBvwILIAFBAWohAUE+IQMMywILIAFBAWohAUE/IQMMygILQcoAIQMgBCABIgBGDeICIAQgAWsgAigCACIBaiEGIAAgAWtBAWohBwNAIAFBoMIAai0AACAALQAAIgVBIHIgBSAFQcEAa0H/AXFBGkkbQf8BcUcNvAIgAUEBRg2+AiABQQFqIQEgBCAAQQFqIgBHDQALIAIgBjYCAAziAgtBywAhAyAEIAEiAEYN4QIgBCABayACKAIAIgFqIQcgACABa0EOaiEGA0AgAUGiwgBqLQAAIAAtAAAiBUEgciAFIAVBwQBrQf8BcUEaSRtB/wFxRw27AiABQQ5GDb4CIAFBAWohASAEIABBAWoiAEcNAAsgAiAHNgIADOECC0HMACEDIAQgASIARg3gAiAEIAFrIAIoAgAiAWohByAAIAFrQQ9qIQYDQCABQcDCAGotAAAgAC0AACIFQSByIAUgBUHBAGtB/wFxQRpJG0H/AXFHDboCQQMgAUEPRg2+AhogAUEBaiEBIAQgAEEBaiIARw0ACyACIAc2AgAM4AILQc0AIQMgBCABIgBGDd8CIAQgAWsgAigCACIBaiEHIAAgAWtBBWohBgNAIAFB0MIAai0AACAALQAAIgVBIHIgBSAFQcEAa0H/AXFBGkkbQf8BcUcNuQJBBCABQQVGDb0CGiABQQFqIQEgBCAAQQFqIgBHDQALIAIgBzYCAAzfAgsgASAERgRAQc4AIQMM3wILAkACQAJAAkAgAS0AACIAQSByIAAgAEHBAGtB/wFxQRpJG0H/AXFB4wBrDhMAvAK8ArwCvAK8ArwCvAK8ArwCvAK8ArwCAbwCvAK8AgIDvAILIAFBAWohAUHBACEDDMgCCyABQQFqIQFBwgAhAwzHAgsgAUEBaiEBQcMAIQMMxgILIAFBAWohAUHEACEDDMUCCyABIARHBEAgAkENNgIIIAIgATYCBEHFACEDDMUCC0HPACEDDN0CCwJAAkAgAS0AAEEKaw4EAZABkAEAkAELIAFBAWohAQtBKCEDDMMCCyABIARGBEBB0QAhAwzcAgsgAS0AAEEgRw0AIAFBAWohASACLQAtQQFxRQ3QAQtBFyEDDMECCyABIARHDcsBQdIAIQMM2QILQdMAIQMgASAERg3YAiACKAIAIgAgBCABa2ohBiABIABrQQFqIQUDQCABLQAAIABB1sIAai0AAEcNxwEgAEEBRg3KASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBjYCAAzYAgsgASAERgRAQdUAIQMM2AILIAEtAABBCkcNwgEgAUEBaiEBDMoBCyABIARGBEBB1gAhAwzXAgsCQAJAIAEtAABBCmsOBADDAcMBAcMBCyABQQFqIQEMygELIAFBAWohAUHKACEDDL0CC0EAIQACQCACKAI4IgNFDQAgAygCPCIDRQ0AIAIgAxEAACEACyAADb8BQc0AIQMMvAILIAItAClBIkYNzwIMiQELIAQgASIFRgRAQdsAIQMM1AILQQAhAEEBIQFBASEGQQAhAwJAAn8CQAJAAkACQAJAAkACQCAFLQAAQTBrDgrFAcQBAAECAwQFBgjDAQtBAgwGC0EDDAULQQQMBAtBBQwDC0EGDAILQQcMAQtBCAshA0EAIQFBACEGDL0BC0EJIQNBASEAQQAhAUEAIQYMvAELIAEgBEYEQEHdACEDDNMCCyABLQAAQS5HDbgBIAFBAWohAQyIAQsgASAERw22AUHfACEDDNECCyABIARHBEAgAkEONgIIIAIgATYCBEHQACEDDLgCC0HgACEDDNACC0HhACEDIAEgBEYNzwIgAigCACIAIAQgAWtqIQUgASAAa0EDaiEGA0AgAS0AACAAQeLCAGotAABHDbEBIABBA0YNswEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMzwILQeIAIQMgASAERg3OAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYDQCABLQAAIABB5sIAai0AAEcNsAEgAEECRg2vASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAzOAgtB4wAhAyABIARGDc0CIAIoAgAiACAEIAFraiEFIAEgAGtBA2ohBgNAIAEtAAAgAEHpwgBqLQAARw2vASAAQQNGDa0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADM0CCyABIARGBEBB5QAhAwzNAgsgAUEBaiEBQQAhAAJAIAIoAjgiA0UNACADKAIwIgNFDQAgAiADEQAAIQALIAANqgFB1gAhAwyzAgsgASAERwRAA0AgAS0AACIAQSBHBEACQAJAAkAgAEHIAGsOCwABswGzAbMBswGzAbMBswGzAQKzAQsgAUEBaiEBQdIAIQMMtwILIAFBAWohAUHTACEDDLYCCyABQQFqIQFB1AAhAwy1AgsgBCABQQFqIgFHDQALQeQAIQMMzAILQeQAIQMMywILA0AgAS0AAEHwwgBqLQAAIgBBAUcEQCAAQQJrDgOnAaYBpQGkAQsgBCABQQFqIgFHDQALQeYAIQMMygILIAFBAWogASAERw0CGkHnACEDDMkCCwNAIAEtAABB8MQAai0AACIAQQFHBEACQCAAQQJrDgSiAaEBoAEAnwELQdcAIQMMsQILIAQgAUEBaiIBRw0AC0HoACEDDMgCCyABIARGBEBB6QAhAwzIAgsCQCABLQAAIgBBCmsOGrcBmwGbAbQBmwGbAZsBmwGbAZsBmwGbAZsBmwGbAZsBmwGbAZsBmwGbAZsBpAGbAZsBAJkBCyABQQFqCyEBQQYhAwytAgsDQCABLQAAQfDGAGotAABBAUcNfSAEIAFBAWoiAUcNAAtB6gAhAwzFAgsgAUEBaiABIARHDQIaQesAIQMMxAILIAEgBEYEQEHsACEDDMQCCyABQQFqDAELIAEgBEYEQEHtACEDDMMCCyABQQFqCyEBQQQhAwyoAgsgASAERgRAQe4AIQMMwQILAkACQAJAIAEtAABB8MgAai0AAEEBaw4HkAGPAY4BAHwBAo0BCyABQQFqIQEMCwsgAUEBagyTAQtBACEDIAJBADYCHCACQZsSNgIQIAJBBzYCDCACIAFBAWo2AhQMwAILAkADQCABLQAAQfDIAGotAAAiAEEERwRAAkACQCAAQQFrDgeUAZMBkgGNAQAEAY0BC0HaACEDDKoCCyABQQFqIQFB3AAhAwypAgsgBCABQQFqIgFHDQALQe8AIQMMwAILIAFBAWoMkQELIAQgASIARgRAQfAAIQMMvwILIAAtAABBL0cNASAAQQFqIQEMBwsgBCABIgBGBEBB8QAhAwy+AgsgAC0AACIBQS9GBEAgAEEBaiEBQd0AIQMMpQILIAFBCmsiA0EWSw0AIAAhAUEBIAN0QYmAgAJxDfkBC0EAIQMgAkEANgIcIAIgADYCFCACQYwcNgIQIAJBBzYCDAy8AgsgASAERwRAIAFBAWohAUHeACEDDKMCC0HyACEDDLsCCyABIARGBEBB9AAhAwy7AgsCQCABLQAAQfDMAGotAABBAWsOA/cBcwCCAQtB4QAhAwyhAgsgASAERwRAA0AgAS0AAEHwygBqLQAAIgBBA0cEQAJAIABBAWsOAvkBAIUBC0HfACEDDKMCCyAEIAFBAWoiAUcNAAtB8wAhAwy6AgtB8wAhAwy5AgsgASAERwRAIAJBDzYCCCACIAE2AgRB4AAhAwygAgtB9QAhAwy4AgsgASAERgRAQfYAIQMMuAILIAJBDzYCCCACIAE2AgQLQQMhAwydAgsDQCABLQAAQSBHDY4CIAQgAUEBaiIBRw0AC0H3ACEDDLUCCyABIARGBEBB+AAhAwy1AgsgAS0AAEEgRw16IAFBAWohAQxbC0EAIQACQCACKAI4IgNFDQAgAygCOCIDRQ0AIAIgAxEAACEACyAADXgMgAILIAEgBEYEQEH6ACEDDLMCCyABLQAAQcwARw10IAFBAWohAUETDHYLQfsAIQMgASAERg2xAiACKAIAIgAgBCABa2ohBSABIABrQQVqIQYDQCABLQAAIABB8M4Aai0AAEcNcyAAQQVGDXUgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMsQILIAEgBEYEQEH8ACEDDLECCwJAAkAgAS0AAEHDAGsODAB0dHR0dHR0dHR0AXQLIAFBAWohAUHmACEDDJgCCyABQQFqIQFB5wAhAwyXAgtB/QAhAyABIARGDa8CIAIoAgAiACAEIAFraiEFIAEgAGtBAmohBgJAA0AgAS0AACAAQe3PAGotAABHDXIgAEECRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADLACCyACQQA2AgAgBkEBaiEBQRAMcwtB/gAhAyABIARGDa4CIAIoAgAiACAEIAFraiEFIAEgAGtBBWohBgJAA0AgAS0AACAAQfbOAGotAABHDXEgAEEFRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADK8CCyACQQA2AgAgBkEBaiEBQRYMcgtB/wAhAyABIARGDa0CIAIoAgAiACAEIAFraiEFIAEgAGtBA2ohBgJAA0AgAS0AACAAQfzOAGotAABHDXAgAEEDRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADK4CCyACQQA2AgAgBkEBaiEBQQUMcQsgASAERgRAQYABIQMMrQILIAEtAABB2QBHDW4gAUEBaiEBQQgMcAsgASAERgRAQYEBIQMMrAILAkACQCABLQAAQc4Aaw4DAG8BbwsgAUEBaiEBQesAIQMMkwILIAFBAWohAUHsACEDDJICCyABIARGBEBBggEhAwyrAgsCQAJAIAEtAABByABrDggAbm5ubm5uAW4LIAFBAWohAUHqACEDDJICCyABQQFqIQFB7QAhAwyRAgtBgwEhAyABIARGDakCIAIoAgAiACAEIAFraiEFIAEgAGtBAmohBgJAA0AgAS0AACAAQYDPAGotAABHDWwgAEECRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADKoCCyACQQA2AgAgBkEBaiEBQQAMbQtBhAEhAyABIARGDagCIAIoAgAiACAEIAFraiEFIAEgAGtBBGohBgJAA0AgAS0AACAAQYPPAGotAABHDWsgAEEERg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADKkCCyACQQA2AgAgBkEBaiEBQSMMbAsgASAERgRAQYUBIQMMqAILAkACQCABLQAAQcwAaw4IAGtra2trawFrCyABQQFqIQFB7wAhAwyPAgsgAUEBaiEBQfAAIQMMjgILIAEgBEYEQEGGASEDDKcCCyABLQAAQcUARw1oIAFBAWohAQxgC0GHASEDIAEgBEYNpQIgAigCACIAIAQgAWtqIQUgASAAa0EDaiEGAkADQCABLQAAIABBiM8Aai0AAEcNaCAAQQNGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMpgILIAJBADYCACAGQQFqIQFBLQxpC0GIASEDIAEgBEYNpAIgAigCACIAIAQgAWtqIQUgASAAa0EIaiEGAkADQCABLQAAIABB0M8Aai0AAEcNZyAAQQhGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMpQILIAJBADYCACAGQQFqIQFBKQxoCyABIARGBEBBiQEhAwykAgtBASABLQAAQd8ARw1nGiABQQFqIQEMXgtBigEhAyABIARGDaICIAIoAgAiACAEIAFraiEFIAEgAGtBAWohBgNAIAEtAAAgAEGMzwBqLQAARw1kIABBAUYN+gEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMogILQYsBIQMgASAERg2hAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEGOzwBqLQAARw1kIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyiAgsgAkEANgIAIAZBAWohAUECDGULQYwBIQMgASAERg2gAiACKAIAIgAgBCABa2ohBSABIABrQQFqIQYCQANAIAEtAAAgAEHwzwBqLQAARw1jIABBAUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyhAgsgAkEANgIAIAZBAWohAUEfDGQLQY0BIQMgASAERg2fAiACKAIAIgAgBCABa2ohBSABIABrQQFqIQYCQANAIAEtAAAgAEHyzwBqLQAARw1iIABBAUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAygAgsgAkEANgIAIAZBAWohAUEJDGMLIAEgBEYEQEGOASEDDJ8CCwJAAkAgAS0AAEHJAGsOBwBiYmJiYgFiCyABQQFqIQFB+AAhAwyGAgsgAUEBaiEBQfkAIQMMhQILQY8BIQMgASAERg2dAiACKAIAIgAgBCABa2ohBSABIABrQQVqIQYCQANAIAEtAAAgAEGRzwBqLQAARw1gIABBBUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyeAgsgAkEANgIAIAZBAWohAUEYDGELQZABIQMgASAERg2cAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEGXzwBqLQAARw1fIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAydAgsgAkEANgIAIAZBAWohAUEXDGALQZEBIQMgASAERg2bAiACKAIAIgAgBCABa2ohBSABIABrQQZqIQYCQANAIAEtAAAgAEGazwBqLQAARw1eIABBBkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAycAgsgAkEANgIAIAZBAWohAUEVDF8LQZIBIQMgASAERg2aAiACKAIAIgAgBCABa2ohBSABIABrQQVqIQYCQANAIAEtAAAgAEGhzwBqLQAARw1dIABBBUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAybAgsgAkEANgIAIAZBAWohAUEeDF4LIAEgBEYEQEGTASEDDJoCCyABLQAAQcwARw1bIAFBAWohAUEKDF0LIAEgBEYEQEGUASEDDJkCCwJAAkAgAS0AAEHBAGsODwBcXFxcXFxcXFxcXFxcAVwLIAFBAWohAUH+ACEDDIACCyABQQFqIQFB/wAhAwz/AQsgASAERgRAQZUBIQMMmAILAkACQCABLQAAQcEAaw4DAFsBWwsgAUEBaiEBQf0AIQMM/wELIAFBAWohAUGAASEDDP4BC0GWASEDIAEgBEYNlgIgAigCACIAIAQgAWtqIQUgASAAa0EBaiEGAkADQCABLQAAIABBp88Aai0AAEcNWSAAQQFGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMlwILIAJBADYCACAGQQFqIQFBCwxaCyABIARGBEBBlwEhAwyWAgsCQAJAAkACQCABLQAAQS1rDiMAW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1sBW1tbW1sCW1tbA1sLIAFBAWohAUH7ACEDDP8BCyABQQFqIQFB/AAhAwz+AQsgAUEBaiEBQYEBIQMM/QELIAFBAWohAUGCASEDDPwBC0GYASEDIAEgBEYNlAIgAigCACIAIAQgAWtqIQUgASAAa0EEaiEGAkADQCABLQAAIABBqc8Aai0AAEcNVyAAQQRGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMlQILIAJBADYCACAGQQFqIQFBGQxYC0GZASEDIAEgBEYNkwIgAigCACIAIAQgAWtqIQUgASAAa0EFaiEGAkADQCABLQAAIABBrs8Aai0AAEcNViAAQQVGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMlAILIAJBADYCACAGQQFqIQFBBgxXC0GaASEDIAEgBEYNkgIgAigCACIAIAQgAWtqIQUgASAAa0EBaiEGAkADQCABLQAAIABBtM8Aai0AAEcNVSAAQQFGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMkwILIAJBADYCACAGQQFqIQFBHAxWC0GbASEDIAEgBEYNkQIgAigCACIAIAQgAWtqIQUgASAAa0EBaiEGAkADQCABLQAAIABBts8Aai0AAEcNVCAAQQFGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMkgILIAJBADYCACAGQQFqIQFBJwxVCyABIARGBEBBnAEhAwyRAgsCQAJAIAEtAABB1ABrDgIAAVQLIAFBAWohAUGGASEDDPgBCyABQQFqIQFBhwEhAwz3AQtBnQEhAyABIARGDY8CIAIoAgAiACAEIAFraiEFIAEgAGtBAWohBgJAA0AgAS0AACAAQbjPAGotAABHDVIgAEEBRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADJACCyACQQA2AgAgBkEBaiEBQSYMUwtBngEhAyABIARGDY4CIAIoAgAiACAEIAFraiEFIAEgAGtBAWohBgJAA0AgAS0AACAAQbrPAGotAABHDVEgAEEBRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADI8CCyACQQA2AgAgBkEBaiEBQQMMUgtBnwEhAyABIARGDY0CIAIoAgAiACAEIAFraiEFIAEgAGtBAmohBgJAA0AgAS0AACAAQe3PAGotAABHDVAgAEECRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADI4CCyACQQA2AgAgBkEBaiEBQQwMUQtBoAEhAyABIARGDYwCIAIoAgAiACAEIAFraiEFIAEgAGtBA2ohBgJAA0AgAS0AACAAQbzPAGotAABHDU8gAEEDRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADI0CCyACQQA2AgAgBkEBaiEBQQ0MUAsgASAERgRAQaEBIQMMjAILAkACQCABLQAAQcYAaw4LAE9PT09PT09PTwFPCyABQQFqIQFBiwEhAwzzAQsgAUEBaiEBQYwBIQMM8gELIAEgBEYEQEGiASEDDIsCCyABLQAAQdAARw1MIAFBAWohAQxGCyABIARGBEBBowEhAwyKAgsCQAJAIAEtAABByQBrDgcBTU1NTU0ATQsgAUEBaiEBQY4BIQMM8QELIAFBAWohAUEiDE0LQaQBIQMgASAERg2IAiACKAIAIgAgBCABa2ohBSABIABrQQFqIQYCQANAIAEtAAAgAEHAzwBqLQAARw1LIABBAUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyJAgsgAkEANgIAIAZBAWohAUEdDEwLIAEgBEYEQEGlASEDDIgCCwJAAkAgAS0AAEHSAGsOAwBLAUsLIAFBAWohAUGQASEDDO8BCyABQQFqIQFBBAxLCyABIARGBEBBpgEhAwyHAgsCQAJAAkACQAJAIAEtAABBwQBrDhUATU1NTU1NTU1NTQFNTQJNTQNNTQRNCyABQQFqIQFBiAEhAwzxAQsgAUEBaiEBQYkBIQMM8AELIAFBAWohAUGKASEDDO8BCyABQQFqIQFBjwEhAwzuAQsgAUEBaiEBQZEBIQMM7QELQacBIQMgASAERg2FAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEHtzwBqLQAARw1IIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyGAgsgAkEANgIAIAZBAWohAUERDEkLQagBIQMgASAERg2EAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEHCzwBqLQAARw1HIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyFAgsgAkEANgIAIAZBAWohAUEsDEgLQakBIQMgASAERg2DAiACKAIAIgAgBCABa2ohBSABIABrQQRqIQYCQANAIAEtAAAgAEHFzwBqLQAARw1GIABBBEYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyEAgsgAkEANgIAIAZBAWohAUErDEcLQaoBIQMgASAERg2CAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEHKzwBqLQAARw1FIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyDAgsgAkEANgIAIAZBAWohAUEUDEYLIAEgBEYEQEGrASEDDIICCwJAAkACQAJAIAEtAABBwgBrDg8AAQJHR0dHR0dHR0dHRwNHCyABQQFqIQFBkwEhAwzrAQsgAUEBaiEBQZQBIQMM6gELIAFBAWohAUGVASEDDOkBCyABQQFqIQFBlgEhAwzoAQsgASAERgRAQawBIQMMgQILIAEtAABBxQBHDUIgAUEBaiEBDD0LQa0BIQMgASAERg3/ASACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEHNzwBqLQAARw1CIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyAAgsgAkEANgIAIAZBAWohAUEODEMLIAEgBEYEQEGuASEDDP8BCyABLQAAQdAARw1AIAFBAWohAUElDEILQa8BIQMgASAERg39ASACKAIAIgAgBCABa2ohBSABIABrQQhqIQYCQANAIAEtAAAgAEHQzwBqLQAARw1AIABBCEYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAz+AQsgAkEANgIAIAZBAWohAUEqDEELIAEgBEYEQEGwASEDDP0BCwJAAkAgAS0AAEHVAGsOCwBAQEBAQEBAQEABQAsgAUEBaiEBQZoBIQMM5AELIAFBAWohAUGbASEDDOMBCyABIARGBEBBsQEhAwz8AQsCQAJAIAEtAABBwQBrDhQAPz8/Pz8/Pz8/Pz8/Pz8/Pz8/AT8LIAFBAWohAUGZASEDDOMBCyABQQFqIQFBnAEhAwziAQtBsgEhAyABIARGDfoBIAIoAgAiACAEIAFraiEFIAEgAGtBA2ohBgJAA0AgAS0AACAAQdnPAGotAABHDT0gAEEDRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADPsBCyACQQA2AgAgBkEBaiEBQSEMPgtBswEhAyABIARGDfkBIAIoAgAiACAEIAFraiEFIAEgAGtBBmohBgJAA0AgAS0AACAAQd3PAGotAABHDTwgAEEGRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADPoBCyACQQA2AgAgBkEBaiEBQRoMPQsgASAERgRAQbQBIQMM+QELAkACQAJAIAEtAABBxQBrDhEAPT09PT09PT09AT09PT09Aj0LIAFBAWohAUGdASEDDOEBCyABQQFqIQFBngEhAwzgAQsgAUEBaiEBQZ8BIQMM3wELQbUBIQMgASAERg33ASACKAIAIgAgBCABa2ohBSABIABrQQVqIQYCQANAIAEtAAAgAEHkzwBqLQAARw06IABBBUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAz4AQsgAkEANgIAIAZBAWohAUEoDDsLQbYBIQMgASAERg32ASACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEHqzwBqLQAARw05IABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAz3AQsgAkEANgIAIAZBAWohAUEHDDoLIAEgBEYEQEG3ASEDDPYBCwJAAkAgAS0AAEHFAGsODgA5OTk5OTk5OTk5OTkBOQsgAUEBaiEBQaEBIQMM3QELIAFBAWohAUGiASEDDNwBC0G4ASEDIAEgBEYN9AEgAigCACIAIAQgAWtqIQUgASAAa0ECaiEGAkADQCABLQAAIABB7c8Aai0AAEcNNyAAQQJGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAM9QELIAJBADYCACAGQQFqIQFBEgw4C0G5ASEDIAEgBEYN8wEgAigCACIAIAQgAWtqIQUgASAAa0EBaiEGAkADQCABLQAAIABB8M8Aai0AAEcNNiAAQQFGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAM9AELIAJBADYCACAGQQFqIQFBIAw3C0G6ASEDIAEgBEYN8gEgAigCACIAIAQgAWtqIQUgASAAa0EBaiEGAkADQCABLQAAIABB8s8Aai0AAEcNNSAAQQFGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAM8wELIAJBADYCACAGQQFqIQFBDww2CyABIARGBEBBuwEhAwzyAQsCQAJAIAEtAABByQBrDgcANTU1NTUBNQsgAUEBaiEBQaUBIQMM2QELIAFBAWohAUGmASEDDNgBC0G8ASEDIAEgBEYN8AEgAigCACIAIAQgAWtqIQUgASAAa0EHaiEGAkADQCABLQAAIABB9M8Aai0AAEcNMyAAQQdGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAM8QELIAJBADYCACAGQQFqIQFBGww0CyABIARGBEBBvQEhAwzwAQsCQAJAAkAgAS0AAEHCAGsOEgA0NDQ0NDQ0NDQBNDQ0NDQ0AjQLIAFBAWohAUGkASEDDNgBCyABQQFqIQFBpwEhAwzXAQsgAUEBaiEBQagBIQMM1gELIAEgBEYEQEG+ASEDDO8BCyABLQAAQc4ARw0wIAFBAWohAQwsCyABIARGBEBBvwEhAwzuAQsCQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCABLQAAQcEAaw4VAAECAz8EBQY/Pz8HCAkKCz8MDQ4PPwsgAUEBaiEBQegAIQMM4wELIAFBAWohAUHpACEDDOIBCyABQQFqIQFB7gAhAwzhAQsgAUEBaiEBQfIAIQMM4AELIAFBAWohAUHzACEDDN8BCyABQQFqIQFB9gAhAwzeAQsgAUEBaiEBQfcAIQMM3QELIAFBAWohAUH6ACEDDNwBCyABQQFqIQFBgwEhAwzbAQsgAUEBaiEBQYQBIQMM2gELIAFBAWohAUGFASEDDNkBCyABQQFqIQFBkgEhAwzYAQsgAUEBaiEBQZgBIQMM1wELIAFBAWohAUGgASEDDNYBCyABQQFqIQFBowEhAwzVAQsgAUEBaiEBQaoBIQMM1AELIAEgBEcEQCACQRA2AgggAiABNgIEQasBIQMM1AELQcABIQMM7AELQQAhAAJAIAIoAjgiA0UNACADKAI0IgNFDQAgAiADEQAAIQALIABFDV4gAEEVRw0HIAJB0QA2AhwgAiABNgIUIAJBsBc2AhAgAkEVNgIMQQAhAwzrAQsgAUEBaiABIARHDQgaQcIBIQMM6gELA0ACQCABLQAAQQprDgQIAAALAAsgBCABQQFqIgFHDQALQcMBIQMM6QELIAEgBEcEQCACQRE2AgggAiABNgIEQQEhAwzQAQtBxAEhAwzoAQsgASAERgRAQcUBIQMM6AELAkACQCABLQAAQQprDgQBKCgAKAsgAUEBagwJCyABQQFqDAULIAEgBEYEQEHGASEDDOcBCwJAAkAgAS0AAEEKaw4XAQsLAQsLCwsLCwsLCwsLCwsLCwsLCwALCyABQQFqIQELQbABIQMMzQELIAEgBEYEQEHIASEDDOYBCyABLQAAQSBHDQkgAkEAOwEyIAFBAWohAUGzASEDDMwBCwNAIAEhAAJAIAEgBEcEQCABLQAAQTBrQf8BcSIDQQpJDQEMJwtBxwEhAwzmAQsCQCACLwEyIgFBmTNLDQAgAiABQQpsIgU7ATIgBUH+/wNxIANB//8Dc0sNACAAQQFqIQEgAiADIAVqIgM7ATIgA0H//wNxQegHSQ0BCwtBACEDIAJBADYCHCACQcEJNgIQIAJBDTYCDCACIABBAWo2AhQM5AELIAJBADYCHCACIAE2AhQgAkHwDDYCECACQRs2AgxBACEDDOMBCyACKAIEIQAgAkEANgIEIAIgACABECYiAA0BIAFBAWoLIQFBrQEhAwzIAQsgAkHBATYCHCACIAA2AgwgAiABQQFqNgIUQQAhAwzgAQsgAigCBCEAIAJBADYCBCACIAAgARAmIgANASABQQFqCyEBQa4BIQMMxQELIAJBwgE2AhwgAiAANgIMIAIgAUEBajYCFEEAIQMM3QELIAJBADYCHCACIAE2AhQgAkGXCzYCECACQQ02AgxBACEDDNwBCyACQQA2AhwgAiABNgIUIAJB4xA2AhAgAkEJNgIMQQAhAwzbAQsgAkECOgAoDKwBC0EAIQMgAkEANgIcIAJBrws2AhAgAkECNgIMIAIgAUEBajYCFAzZAQtBAiEDDL8BC0ENIQMMvgELQSYhAwy9AQtBFSEDDLwBC0EWIQMMuwELQRghAwy6AQtBHCEDDLkBC0EdIQMMuAELQSAhAwy3AQtBISEDDLYBC0EjIQMMtQELQcYAIQMMtAELQS4hAwyzAQtBPSEDDLIBC0HLACEDDLEBC0HOACEDDLABC0HYACEDDK8BC0HZACEDDK4BC0HbACEDDK0BC0HxACEDDKwBC0H0ACEDDKsBC0GNASEDDKoBC0GXASEDDKkBC0GpASEDDKgBC0GvASEDDKcBC0GxASEDDKYBCyACQQA2AgALQQAhAyACQQA2AhwgAiABNgIUIAJB8Rs2AhAgAkEGNgIMDL0BCyACQQA2AgAgBkEBaiEBQSQLOgApIAIoAgQhACACQQA2AgQgAiAAIAEQJyIARQRAQeUAIQMMowELIAJB+QA2AhwgAiABNgIUIAIgADYCDEEAIQMMuwELIABBFUcEQCACQQA2AhwgAiABNgIUIAJBzA42AhAgAkEgNgIMQQAhAwy7AQsgAkH4ADYCHCACIAE2AhQgAkHKGDYCECACQRU2AgxBACEDDLoBCyACQQA2AhwgAiABNgIUIAJBjhs2AhAgAkEGNgIMQQAhAwy5AQsgAkEANgIcIAIgATYCFCACQf4RNgIQIAJBBzYCDEEAIQMMuAELIAJBADYCHCACIAE2AhQgAkGMHDYCECACQQc2AgxBACEDDLcBCyACQQA2AhwgAiABNgIUIAJBww82AhAgAkEHNgIMQQAhAwy2AQsgAkEANgIcIAIgATYCFCACQcMPNgIQIAJBBzYCDEEAIQMMtQELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0RIAJB5QA2AhwgAiABNgIUIAIgADYCDEEAIQMMtAELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0gIAJB0wA2AhwgAiABNgIUIAIgADYCDEEAIQMMswELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0iIAJB0gA2AhwgAiABNgIUIAIgADYCDEEAIQMMsgELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0OIAJB5QA2AhwgAiABNgIUIAIgADYCDEEAIQMMsQELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0dIAJB0wA2AhwgAiABNgIUIAIgADYCDEEAIQMMsAELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0fIAJB0gA2AhwgAiABNgIUIAIgADYCDEEAIQMMrwELIABBP0cNASABQQFqCyEBQQUhAwyUAQtBACEDIAJBADYCHCACIAE2AhQgAkH9EjYCECACQQc2AgwMrAELIAJBADYCHCACIAE2AhQgAkHcCDYCECACQQc2AgxBACEDDKsBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNByACQeUANgIcIAIgATYCFCACIAA2AgxBACEDDKoBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNFiACQdMANgIcIAIgATYCFCACIAA2AgxBACEDDKkBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNGCACQdIANgIcIAIgATYCFCACIAA2AgxBACEDDKgBCyACQQA2AhwgAiABNgIUIAJBxgo2AhAgAkEHNgIMQQAhAwynAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDQMgAkHlADYCHCACIAE2AhQgAiAANgIMQQAhAwymAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDRIgAkHTADYCHCACIAE2AhQgAiAANgIMQQAhAwylAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDRQgAkHSADYCHCACIAE2AhQgAiAANgIMQQAhAwykAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDQAgAkHlADYCHCACIAE2AhQgAiAANgIMQQAhAwyjAQtB1QAhAwyJAQsgAEEVRwRAIAJBADYCHCACIAE2AhQgAkG5DTYCECACQRo2AgxBACEDDKIBCyACQeQANgIcIAIgATYCFCACQeMXNgIQIAJBFTYCDEEAIQMMoQELIAJBADYCACAGQQFqIQEgAi0AKSIAQSNrQQtJDQQCQCAAQQZLDQBBASAAdEHKAHFFDQAMBQtBACEDIAJBADYCHCACIAE2AhQgAkH3CTYCECACQQg2AgwMoAELIAJBADYCACAGQQFqIQEgAi0AKUEhRg0DIAJBADYCHCACIAE2AhQgAkGbCjYCECACQQg2AgxBACEDDJ8BCyACQQA2AgALQQAhAyACQQA2AhwgAiABNgIUIAJBkDM2AhAgAkEINgIMDJ0BCyACQQA2AgAgBkEBaiEBIAItAClBI0kNACACQQA2AhwgAiABNgIUIAJB0wk2AhAgAkEINgIMQQAhAwycAQtB0QAhAwyCAQsgAS0AAEEwayIAQf8BcUEKSQRAIAIgADoAKiABQQFqIQFBzwAhAwyCAQsgAigCBCEAIAJBADYCBCACIAAgARAoIgBFDYYBIAJB3gA2AhwgAiABNgIUIAIgADYCDEEAIQMMmgELIAIoAgQhACACQQA2AgQgAiAAIAEQKCIARQ2GASACQdwANgIcIAIgATYCFCACIAA2AgxBACEDDJkBCyACKAIEIQAgAkEANgIEIAIgACAFECgiAEUEQCAFIQEMhwELIAJB2gA2AhwgAiAFNgIUIAIgADYCDAyYAQtBACEBQQEhAwsgAiADOgArIAVBAWohAwJAAkACQCACLQAtQRBxDQACQAJAAkAgAi0AKg4DAQACBAsgBkUNAwwCCyAADQEMAgsgAUUNAQsgAigCBCEAIAJBADYCBCACIAAgAxAoIgBFBEAgAyEBDAILIAJB2AA2AhwgAiADNgIUIAIgADYCDEEAIQMMmAELIAIoAgQhACACQQA2AgQgAiAAIAMQKCIARQRAIAMhAQyHAQsgAkHZADYCHCACIAM2AhQgAiAANgIMQQAhAwyXAQtBzAAhAwx9CyAAQRVHBEAgAkEANgIcIAIgATYCFCACQZQNNgIQIAJBITYCDEEAIQMMlgELIAJB1wA2AhwgAiABNgIUIAJByRc2AhAgAkEVNgIMQQAhAwyVAQtBACEDIAJBADYCHCACIAE2AhQgAkGAETYCECACQQk2AgwMlAELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0AIAJB0wA2AhwgAiABNgIUIAIgADYCDEEAIQMMkwELQckAIQMMeQsgAkEANgIcIAIgATYCFCACQcEoNgIQIAJBBzYCDCACQQA2AgBBACEDDJEBCyACKAIEIQBBACEDIAJBADYCBCACIAAgARAlIgBFDQAgAkHSADYCHCACIAE2AhQgAiAANgIMDJABC0HIACEDDHYLIAJBADYCACAFIQELIAJBgBI7ASogAUEBaiEBQQAhAAJAIAIoAjgiA0UNACADKAIwIgNFDQAgAiADEQAAIQALIAANAQtBxwAhAwxzCyAAQRVGBEAgAkHRADYCHCACIAE2AhQgAkHjFzYCECACQRU2AgxBACEDDIwBC0EAIQMgAkEANgIcIAIgATYCFCACQbkNNgIQIAJBGjYCDAyLAQtBACEDIAJBADYCHCACIAE2AhQgAkGgGTYCECACQR42AgwMigELIAEtAABBOkYEQCACKAIEIQBBACEDIAJBADYCBCACIAAgARApIgBFDQEgAkHDADYCHCACIAA2AgwgAiABQQFqNgIUDIoBC0EAIQMgAkEANgIcIAIgATYCFCACQbERNgIQIAJBCjYCDAyJAQsgAUEBaiEBQTshAwxvCyACQcMANgIcIAIgADYCDCACIAFBAWo2AhQMhwELQQAhAyACQQA2AhwgAiABNgIUIAJB8A42AhAgAkEcNgIMDIYBCyACIAIvATBBEHI7ATAMZgsCQCACLwEwIgBBCHFFDQAgAi0AKEEBRw0AIAItAC1BCHFFDQMLIAIgAEH3+wNxQYAEcjsBMAwECyABIARHBEACQANAIAEtAABBMGsiAEH/AXFBCk8EQEE1IQMMbgsgAikDICIKQpmz5syZs+bMGVYNASACIApCCn4iCjcDICAKIACtQv8BgyILQn+FVg0BIAIgCiALfDcDICAEIAFBAWoiAUcNAAtBOSEDDIUBCyACKAIEIQBBACEDIAJBADYCBCACIAAgAUEBaiIBECoiAA0MDHcLQTkhAwyDAQsgAi0AMEEgcQ0GQcUBIQMMaQtBACEDIAJBADYCBCACIAEgARAqIgBFDQQgAkE6NgIcIAIgADYCDCACIAFBAWo2AhQMgQELIAItAChBAUcNACACLQAtQQhxRQ0BC0E3IQMMZgsgAigCBCEAQQAhAyACQQA2AgQgAiAAIAEQKiIABEAgAkE7NgIcIAIgADYCDCACIAFBAWo2AhQMfwsgAUEBaiEBDG4LIAJBCDoALAwECyABQQFqIQEMbQtBACEDIAJBADYCHCACIAE2AhQgAkHkEjYCECACQQQ2AgwMewsgAigCBCEAQQAhAyACQQA2AgQgAiAAIAEQKiIARQ1sIAJBNzYCHCACIAE2AhQgAiAANgIMDHoLIAIgAi8BMEEgcjsBMAtBMCEDDF8LIAJBNjYCHCACIAE2AhQgAiAANgIMDHcLIABBLEcNASABQQFqIQBBASEBAkACQAJAAkACQCACLQAsQQVrDgQDAQIEAAsgACEBDAQLQQIhAQwBC0EEIQELIAJBAToALCACIAIvATAgAXI7ATAgACEBDAELIAIgAi8BMEEIcjsBMCAAIQELQTkhAwxcCyACQQA6ACwLQTQhAwxaCyABIARGBEBBLSEDDHMLAkACQANAAkAgAS0AAEEKaw4EAgAAAwALIAQgAUEBaiIBRw0AC0EtIQMMdAsgAigCBCEAQQAhAyACQQA2AgQgAiAAIAEQKiIARQ0CIAJBLDYCHCACIAE2AhQgAiAANgIMDHMLIAIoAgQhAEEAIQMgAkEANgIEIAIgACABECoiAEUEQCABQQFqIQEMAgsgAkEsNgIcIAIgADYCDCACIAFBAWo2AhQMcgsgAS0AAEENRgRAIAIoAgQhAEEAIQMgAkEANgIEIAIgACABECoiAEUEQCABQQFqIQEMAgsgAkEsNgIcIAIgADYCDCACIAFBAWo2AhQMcgsgAi0ALUEBcQRAQcQBIQMMWQsgAigCBCEAQQAhAyACQQA2AgQgAiAAIAEQKiIADQEMZQtBLyEDDFcLIAJBLjYCHCACIAE2AhQgAiAANgIMDG8LQQAhAyACQQA2AhwgAiABNgIUIAJB8BQ2AhAgAkEDNgIMDG4LQQEhAwJAAkACQAJAIAItACxBBWsOBAMBAgAECyACIAIvATBBCHI7ATAMAwtBAiEDDAELQQQhAwsgAkEBOgAsIAIgAi8BMCADcjsBMAtBKiEDDFMLQQAhAyACQQA2AhwgAiABNgIUIAJB4Q82AhAgAkEKNgIMDGsLQQEhAwJAAkACQAJAAkACQCACLQAsQQJrDgcFBAQDAQIABAsgAiACLwEwQQhyOwEwDAMLQQIhAwwBC0EEIQMLIAJBAToALCACIAIvATAgA3I7ATALQSshAwxSC0EAIQMgAkEANgIcIAIgATYCFCACQasSNgIQIAJBCzYCDAxqC0EAIQMgAkEANgIcIAIgATYCFCACQf0NNgIQIAJBHTYCDAxpCyABIARHBEADQCABLQAAQSBHDUggBCABQQFqIgFHDQALQSUhAwxpC0ElIQMMaAsgAi0ALUEBcQRAQcMBIQMMTwsgAigCBCEAQQAhAyACQQA2AgQgAiAAIAEQKSIABEAgAkEmNgIcIAIgADYCDCACIAFBAWo2AhQMaAsgAUEBaiEBDFwLIAFBAWohASACLwEwIgBBgAFxBEBBACEAAkAgAigCOCIDRQ0AIAMoAlQiA0UNACACIAMRAAAhAAsgAEUNBiAAQRVHDR8gAkEFNgIcIAIgATYCFCACQfkXNgIQIAJBFTYCDEEAIQMMZwsCQCAAQaAEcUGgBEcNACACLQAtQQJxDQBBACEDIAJBADYCHCACIAE2AhQgAkGWEzYCECACQQQ2AgwMZwsgAgJ/IAIvATBBFHFBFEYEQEEBIAItAChBAUYNARogAi8BMkHlAEYMAQsgAi0AKUEFRgs6AC5BACEAAkAgAigCOCIDRQ0AIAMoAiQiA0UNACACIAMRAAAhAAsCQAJAAkACQAJAIAAOFgIBAAQEBAQEBAQEBAQEBAQEBAQEBAMECyACQQE6AC4LIAIgAi8BMEHAAHI7ATALQSchAwxPCyACQSM2AhwgAiABNgIUIAJBpRY2AhAgAkEVNgIMQQAhAwxnC0EAIQMgAkEANgIcIAIgATYCFCACQdULNgIQIAJBETYCDAxmC0EAIQACQCACKAI4IgNFDQAgAygCLCIDRQ0AIAIgAxEAACEACyAADQELQQ4hAwxLCyAAQRVGBEAgAkECNgIcIAIgATYCFCACQbAYNgIQIAJBFTYCDEEAIQMMZAtBACEDIAJBADYCHCACIAE2AhQgAkGnDjYCECACQRI2AgwMYwtBACEDIAJBADYCHCACIAE2AhQgAkGqHDYCECACQQ82AgwMYgsgAigCBCEAQQAhAyACQQA2AgQgAiAAIAEgCqdqIgEQKyIARQ0AIAJBBTYCHCACIAE2AhQgAiAANgIMDGELQQ8hAwxHC0EAIQMgAkEANgIcIAIgATYCFCACQc0TNgIQIAJBDDYCDAxfC0IBIQoLIAFBAWohAQJAIAIpAyAiC0L//////////w9YBEAgAiALQgSGIAqENwMgDAELQQAhAyACQQA2AhwgAiABNgIUIAJBrQk2AhAgAkEMNgIMDF4LQSQhAwxEC0EAIQMgAkEANgIcIAIgATYCFCACQc0TNgIQIAJBDDYCDAxcCyACKAIEIQBBACEDIAJBADYCBCACIAAgARAsIgBFBEAgAUEBaiEBDFILIAJBFzYCHCACIAA2AgwgAiABQQFqNgIUDFsLIAIoAgQhAEEAIQMgAkEANgIEAkAgAiAAIAEQLCIARQRAIAFBAWohAQwBCyACQRY2AhwgAiAANgIMIAIgAUEBajYCFAxbC0EfIQMMQQtBACEDIAJBADYCHCACIAE2AhQgAkGaDzYCECACQSI2AgwMWQsgAigCBCEAQQAhAyACQQA2AgQgAiAAIAEQLSIARQRAIAFBAWohAQxQCyACQRQ2AhwgAiAANgIMIAIgAUEBajYCFAxYCyACKAIEIQBBACEDIAJBADYCBAJAIAIgACABEC0iAEUEQCABQQFqIQEMAQsgAkETNgIcIAIgADYCDCACIAFBAWo2AhQMWAtBHiEDDD4LQQAhAyACQQA2AhwgAiABNgIUIAJBxgw2AhAgAkEjNgIMDFYLIAIoAgQhAEEAIQMgAkEANgIEIAIgACABEC0iAEUEQCABQQFqIQEMTgsgAkERNgIcIAIgADYCDCACIAFBAWo2AhQMVQsgAkEQNgIcIAIgATYCFCACIAA2AgwMVAtBACEDIAJBADYCHCACIAE2AhQgAkHGDDYCECACQSM2AgwMUwtBACEDIAJBADYCHCACIAE2AhQgAkHAFTYCECACQQI2AgwMUgsgAigCBCEAQQAhAyACQQA2AgQCQCACIAAgARAtIgBFBEAgAUEBaiEBDAELIAJBDjYCHCACIAA2AgwgAiABQQFqNgIUDFILQRshAww4C0EAIQMgAkEANgIcIAIgATYCFCACQcYMNgIQIAJBIzYCDAxQCyACKAIEIQBBACEDIAJBADYCBAJAIAIgACABECwiAEUEQCABQQFqIQEMAQsgAkENNgIcIAIgADYCDCACIAFBAWo2AhQMUAtBGiEDDDYLQQAhAyACQQA2AhwgAiABNgIUIAJBmg82AhAgAkEiNgIMDE4LIAIoAgQhAEEAIQMgAkEANgIEAkAgAiAAIAEQLCIARQRAIAFBAWohAQwBCyACQQw2AhwgAiAANgIMIAIgAUEBajYCFAxOC0EZIQMMNAtBACEDIAJBADYCHCACIAE2AhQgAkGaDzYCECACQSI2AgwMTAsgAEEVRwRAQQAhAyACQQA2AhwgAiABNgIUIAJBgww2AhAgAkETNgIMDEwLIAJBCjYCHCACIAE2AhQgAkHkFjYCECACQRU2AgxBACEDDEsLIAIoAgQhAEEAIQMgAkEANgIEIAIgACABIAqnaiIBECsiAARAIAJBBzYCHCACIAE2AhQgAiAANgIMDEsLQRMhAwwxCyAAQRVHBEBBACEDIAJBADYCHCACIAE2AhQgAkHaDTYCECACQRQ2AgwMSgsgAkEeNgIcIAIgATYCFCACQfkXNgIQIAJBFTYCDEEAIQMMSQtBACEAAkAgAigCOCIDRQ0AIAMoAiwiA0UNACACIAMRAAAhAAsgAEUNQSAAQRVGBEAgAkEDNgIcIAIgATYCFCACQbAYNgIQIAJBFTYCDEEAIQMMSQtBACEDIAJBADYCHCACIAE2AhQgAkGnDjYCECACQRI2AgwMSAtBACEDIAJBADYCHCACIAE2AhQgAkHaDTYCECACQRQ2AgwMRwtBACEDIAJBADYCHCACIAE2AhQgAkGnDjYCECACQRI2AgwMRgsgAkEAOgAvIAItAC1BBHFFDT8LIAJBADoALyACQQE6ADRBACEDDCsLQQAhAyACQQA2AhwgAkHkETYCECACQQc2AgwgAiABQQFqNgIUDEMLAkADQAJAIAEtAABBCmsOBAACAgACCyAEIAFBAWoiAUcNAAtB3QEhAwxDCwJAAkAgAi0ANEEBRw0AQQAhAAJAIAIoAjgiA0UNACADKAJYIgNFDQAgAiADEQAAIQALIABFDQAgAEEVRw0BIAJB3AE2AhwgAiABNgIUIAJB1RY2AhAgAkEVNgIMQQAhAwxEC0HBASEDDCoLIAJBADYCHCACIAE2AhQgAkHpCzYCECACQR82AgxBACEDDEILAkACQCACLQAoQQFrDgIEAQALQcABIQMMKQtBuQEhAwwoCyACQQI6AC9BACEAAkAgAigCOCIDRQ0AIAMoAgAiA0UNACACIAMRAAAhAAsgAEUEQEHCASEDDCgLIABBFUcEQCACQQA2AhwgAiABNgIUIAJBpAw2AhAgAkEQNgIMQQAhAwxBCyACQdsBNgIcIAIgATYCFCACQfoWNgIQIAJBFTYCDEEAIQMMQAsgASAERgRAQdoBIQMMQAsgAS0AAEHIAEYNASACQQE6ACgLQawBIQMMJQtBvwEhAwwkCyABIARHBEAgAkEQNgIIIAIgATYCBEG+ASEDDCQLQdkBIQMMPAsgASAERgRAQdgBIQMMPAsgAS0AAEHIAEcNBCABQQFqIQFBvQEhAwwiCyABIARGBEBB1wEhAww7CwJAAkAgAS0AAEHFAGsOEAAFBQUFBQUFBQUFBQUFBQEFCyABQQFqIQFBuwEhAwwiCyABQQFqIQFBvAEhAwwhC0HWASEDIAEgBEYNOSACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEGD0ABqLQAARw0DIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAw6CyACKAIEIQAgAkIANwMAIAIgACAGQQFqIgEQJyIARQRAQcYBIQMMIQsgAkHVATYCHCACIAE2AhQgAiAANgIMQQAhAww5C0HUASEDIAEgBEYNOCACKAIAIgAgBCABa2ohBSABIABrQQFqIQYCQANAIAEtAAAgAEGB0ABqLQAARw0CIABBAUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAw5CyACQYEEOwEoIAIoAgQhACACQgA3AwAgAiAAIAZBAWoiARAnIgANAwwCCyACQQA2AgALQQAhAyACQQA2AhwgAiABNgIUIAJB2Bs2AhAgAkEINgIMDDYLQboBIQMMHAsgAkHTATYCHCACIAE2AhQgAiAANgIMQQAhAww0C0EAIQACQCACKAI4IgNFDQAgAygCOCIDRQ0AIAIgAxEAACEACyAARQ0AIABBFUYNASACQQA2AhwgAiABNgIUIAJBzA42AhAgAkEgNgIMQQAhAwwzC0HkACEDDBkLIAJB+AA2AhwgAiABNgIUIAJByhg2AhAgAkEVNgIMQQAhAwwxC0HSASEDIAQgASIARg0wIAQgAWsgAigCACIBaiEFIAAgAWtBBGohBgJAA0AgAC0AACABQfzPAGotAABHDQEgAUEERg0DIAFBAWohASAEIABBAWoiAEcNAAsgAiAFNgIADDELIAJBADYCHCACIAA2AhQgAkGQMzYCECACQQg2AgwgAkEANgIAQQAhAwwwCyABIARHBEAgAkEONgIIIAIgATYCBEG3ASEDDBcLQdEBIQMMLwsgAkEANgIAIAZBAWohAQtBuAEhAwwUCyABIARGBEBB0AEhAwwtCyABLQAAQTBrIgBB/wFxQQpJBEAgAiAAOgAqIAFBAWohAUG2ASEDDBQLIAIoAgQhACACQQA2AgQgAiAAIAEQKCIARQ0UIAJBzwE2AhwgAiABNgIUIAIgADYCDEEAIQMMLAsgASAERgRAQc4BIQMMLAsCQCABLQAAQS5GBEAgAUEBaiEBDAELIAIoAgQhACACQQA2AgQgAiAAIAEQKCIARQ0VIAJBzQE2AhwgAiABNgIUIAIgADYCDEEAIQMMLAtBtQEhAwwSCyAEIAEiBUYEQEHMASEDDCsLQQAhAEEBIQFBASEGQQAhAwJAAkACQAJAAkACfwJAAkACQAJAAkACQAJAIAUtAABBMGsOCgoJAAECAwQFBggLC0ECDAYLQQMMBQtBBAwEC0EFDAMLQQYMAgtBBwwBC0EICyEDQQAhAUEAIQYMAgtBCSEDQQEhAEEAIQFBACEGDAELQQAhAUEBIQMLIAIgAzoAKyAFQQFqIQMCQAJAIAItAC1BEHENAAJAAkACQCACLQAqDgMBAAIECyAGRQ0DDAILIAANAQwCCyABRQ0BCyACKAIEIQAgAkEANgIEIAIgACADECgiAEUEQCADIQEMAwsgAkHJATYCHCACIAM2AhQgAiAANgIMQQAhAwwtCyACKAIEIQAgAkEANgIEIAIgACADECgiAEUEQCADIQEMGAsgAkHKATYCHCACIAM2AhQgAiAANgIMQQAhAwwsCyACKAIEIQAgAkEANgIEIAIgACAFECgiAEUEQCAFIQEMFgsgAkHLATYCHCACIAU2AhQgAiAANgIMDCsLQbQBIQMMEQtBACEAAkAgAigCOCIDRQ0AIAMoAjwiA0UNACACIAMRAAAhAAsCQCAABEAgAEEVRg0BIAJBADYCHCACIAE2AhQgAkGUDTYCECACQSE2AgxBACEDDCsLQbIBIQMMEQsgAkHIATYCHCACIAE2AhQgAkHJFzYCECACQRU2AgxBACEDDCkLIAJBADYCACAGQQFqIQFB9QAhAwwPCyACLQApQQVGBEBB4wAhAwwPC0HiACEDDA4LIAAhASACQQA2AgALIAJBADoALEEJIQMMDAsgAkEANgIAIAdBAWohAUHAACEDDAsLQQELOgAsIAJBADYCACAGQQFqIQELQSkhAwwIC0E4IQMMBwsCQCABIARHBEADQCABLQAAQYA+ai0AACIAQQFHBEAgAEECRw0DIAFBAWohAQwFCyAEIAFBAWoiAUcNAAtBPiEDDCELQT4hAwwgCwsgAkEAOgAsDAELQQshAwwEC0E6IQMMAwsgAUEBaiEBQS0hAwwCCyACIAE6ACwgAkEANgIAIAZBAWohAUEMIQMMAQsgAkEANgIAIAZBAWohAUEKIQMMAAsAC0EAIQMgAkEANgIcIAIgATYCFCACQc0QNgIQIAJBCTYCDAwXC0EAIQMgAkEANgIcIAIgATYCFCACQekKNgIQIAJBCTYCDAwWC0EAIQMgAkEANgIcIAIgATYCFCACQbcQNgIQIAJBCTYCDAwVC0EAIQMgAkEANgIcIAIgATYCFCACQZwRNgIQIAJBCTYCDAwUC0EAIQMgAkEANgIcIAIgATYCFCACQc0QNgIQIAJBCTYCDAwTC0EAIQMgAkEANgIcIAIgATYCFCACQekKNgIQIAJBCTYCDAwSC0EAIQMgAkEANgIcIAIgATYCFCACQbcQNgIQIAJBCTYCDAwRC0EAIQMgAkEANgIcIAIgATYCFCACQZwRNgIQIAJBCTYCDAwQC0EAIQMgAkEANgIcIAIgATYCFCACQZcVNgIQIAJBDzYCDAwPC0EAIQMgAkEANgIcIAIgATYCFCACQZcVNgIQIAJBDzYCDAwOC0EAIQMgAkEANgIcIAIgATYCFCACQcASNgIQIAJBCzYCDAwNC0EAIQMgAkEANgIcIAIgATYCFCACQZUJNgIQIAJBCzYCDAwMC0EAIQMgAkEANgIcIAIgATYCFCACQeEPNgIQIAJBCjYCDAwLC0EAIQMgAkEANgIcIAIgATYCFCACQfsPNgIQIAJBCjYCDAwKC0EAIQMgAkEANgIcIAIgATYCFCACQfEZNgIQIAJBAjYCDAwJC0EAIQMgAkEANgIcIAIgATYCFCACQcQUNgIQIAJBAjYCDAwIC0EAIQMgAkEANgIcIAIgATYCFCACQfIVNgIQIAJBAjYCDAwHCyACQQI2AhwgAiABNgIUIAJBnBo2AhAgAkEWNgIMQQAhAwwGC0EBIQMMBQtB1AAhAyABIARGDQQgCEEIaiEJIAIoAgAhBQJAAkAgASAERwRAIAVB2MIAaiEHIAQgBWogAWshACAFQX9zQQpqIgUgAWohBgNAIAEtAAAgBy0AAEcEQEECIQcMAwsgBUUEQEEAIQcgBiEBDAMLIAVBAWshBSAHQQFqIQcgBCABQQFqIgFHDQALIAAhBSAEIQELIAlBATYCACACIAU2AgAMAQsgAkEANgIAIAkgBzYCAAsgCSABNgIEIAgoAgwhACAIKAIIDgMBBAIACwALIAJBADYCHCACQbUaNgIQIAJBFzYCDCACIABBAWo2AhRBACEDDAILIAJBADYCHCACIAA2AhQgAkHKGjYCECACQQk2AgxBACEDDAELIAEgBEYEQEEiIQMMAQsgAkEJNgIIIAIgATYCBEEhIQMLIAhBEGokACADRQRAIAIoAgwhAAwBCyACIAM2AhxBACEAIAIoAgQiAUUNACACIAEgBCACKAIIEQEAIgFFDQAgAiAENgIUIAIgATYCDCABIQALIAALvgIBAn8gAEEAOgAAIABB3ABqIgFBAWtBADoAACAAQQA6AAIgAEEAOgABIAFBA2tBADoAACABQQJrQQA6AAAgAEEAOgADIAFBBGtBADoAAEEAIABrQQNxIgEgAGoiAEEANgIAQdwAIAFrQXxxIgIgAGoiAUEEa0EANgIAAkAgAkEJSQ0AIABBADYCCCAAQQA2AgQgAUEIa0EANgIAIAFBDGtBADYCACACQRlJDQAgAEEANgIYIABBADYCFCAAQQA2AhAgAEEANgIMIAFBEGtBADYCACABQRRrQQA2AgAgAUEYa0EANgIAIAFBHGtBADYCACACIABBBHFBGHIiAmsiAUEgSQ0AIAAgAmohAANAIABCADcDGCAAQgA3AxAgAEIANwMIIABCADcDACAAQSBqIQAgAUEgayIBQR9LDQALCwtWAQF/AkAgACgCDA0AAkACQAJAAkAgAC0ALw4DAQADAgsgACgCOCIBRQ0AIAEoAiwiAUUNACAAIAERAAAiAQ0DC0EADwsACyAAQcMWNgIQQQ4hAQsgAQsaACAAKAIMRQRAIABB0Rs2AhAgAEEVNgIMCwsUACAAKAIMQRVGBEAgAEEANgIMCwsUACAAKAIMQRZGBEAgAEEANgIMCwsHACAAKAIMCwcAIAAoAhALCQAgACABNgIQCwcAIAAoAhQLFwAgAEEkTwRAAAsgAEECdEGgM2ooAgALFwAgAEEuTwRAAAsgAEECdEGwNGooAgALvwkBAX9B6yghAQJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIABB5ABrDvQDY2IAAWFhYWFhYQIDBAVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhBgcICQoLDA0OD2FhYWFhEGFhYWFhYWFhYWFhEWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYRITFBUWFxgZGhthYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2YTc4OTphYWFhYWFhYTthYWE8YWFhYT0+P2FhYWFhYWFhQGFhQWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYUJDREVGR0hJSktMTU5PUFFSU2FhYWFhYWFhVFVWV1hZWlthXF1hYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFeYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhX2BhC0HhJw8LQaQhDwtByywPC0H+MQ8LQcAkDwtBqyQPC0GNKA8LQeImDwtBgDAPC0G5Lw8LQdckDwtB7x8PC0HhHw8LQfofDwtB8iAPC0GoLw8LQa4yDwtBiDAPC0HsJw8LQYIiDwtBjh0PC0HQLg8LQcojDwtBxTIPC0HfHA8LQdIcDwtBxCAPC0HXIA8LQaIfDwtB7S4PC0GrMA8LQdQlDwtBzC4PC0H6Lg8LQfwrDwtB0jAPC0HxHQ8LQbsgDwtB9ysPC0GQMQ8LQdcxDwtBoi0PC0HUJw8LQeArDwtBnywPC0HrMQ8LQdUfDwtByjEPC0HeJQ8LQdQeDwtB9BwPC0GnMg8LQbEdDwtBoB0PC0G5MQ8LQbwwDwtBkiEPC0GzJg8LQeksDwtBrB4PC0HUKw8LQfcmDwtBgCYPC0GwIQ8LQf4eDwtBjSMPC0GJLQ8LQfciDwtBoDEPC0GuHw8LQcYlDwtB6B4PC0GTIg8LQcIvDwtBwx0PC0GLLA8LQeEdDwtBjS8PC0HqIQ8LQbQtDwtB0i8PC0HfMg8LQdIyDwtB8DAPC0GpIg8LQfkjDwtBmR4PC0G1LA8LQZswDwtBkjIPC0G2Kw8LQcIiDwtB+DIPC0GeJQ8LQdAiDwtBuh4PC0GBHg8LAAtB1iEhAQsgAQsWACAAIAAtAC1B/gFxIAFBAEdyOgAtCxkAIAAgAC0ALUH9AXEgAUEAR0EBdHI6AC0LGQAgACAALQAtQfsBcSABQQBHQQJ0cjoALQsZACAAIAAtAC1B9wFxIAFBAEdBA3RyOgAtCz4BAn8CQCAAKAI4IgNFDQAgAygCBCIDRQ0AIAAgASACIAFrIAMRAQAiBEF/Rw0AIABBxhE2AhBBGCEECyAECz4BAn8CQCAAKAI4IgNFDQAgAygCCCIDRQ0AIAAgASACIAFrIAMRAQAiBEF/Rw0AIABB9go2AhBBGCEECyAECz4BAn8CQCAAKAI4IgNFDQAgAygCDCIDRQ0AIAAgASACIAFrIAMRAQAiBEF/Rw0AIABB7Ro2AhBBGCEECyAECz4BAn8CQCAAKAI4IgNFDQAgAygCECIDRQ0AIAAgASACIAFrIAMRAQAiBEF/Rw0AIABBlRA2AhBBGCEECyAECz4BAn8CQCAAKAI4IgNFDQAgAygCFCIDRQ0AIAAgASACIAFrIAMRAQAiBEF/Rw0AIABBqhs2AhBBGCEECyAECz4BAn8CQCAAKAI4IgNFDQAgAygCGCIDRQ0AIAAgASACIAFrIAMRAQAiBEF/Rw0AIABB7RM2AhBBGCEECyAECz4BAn8CQCAAKAI4IgNFDQAgAygCKCIDRQ0AIAAgASACIAFrIAMRAQAiBEF/Rw0AIABB9gg2AhBBGCEECyAECz4BAn8CQCAAKAI4IgNFDQAgAygCHCIDRQ0AIAAgASACIAFrIAMRAQAiBEF/Rw0AIABBwhk2AhBBGCEECyAECz4BAn8CQCAAKAI4IgNFDQAgAygCICIDRQ0AIAAgASACIAFrIAMRAQAiBEF/Rw0AIABBlBQ2AhBBGCEECyAEC1kBAn8CQCAALQAoQQFGDQAgAC8BMiIBQeQAa0HkAEkNACABQcwBRg0AIAFBsAJGDQAgAC8BMCIAQcAAcQ0AQQEhAiAAQYgEcUGABEYNACAAQShxRSECCyACC4wBAQJ/AkACQAJAIAAtACpFDQAgAC0AK0UNACAALwEwIgFBAnFFDQEMAgsgAC8BMCIBQQFxRQ0BC0EBIQIgAC0AKEEBRg0AIAAvATIiAEHkAGtB5ABJDQAgAEHMAUYNACAAQbACRg0AIAFBwABxDQBBACECIAFBiARxQYAERg0AIAFBKHFBAEchAgsgAgtzACAAQRBq/QwAAAAAAAAAAAAAAAAAAAAA/QsDACAA/QwAAAAAAAAAAAAAAAAAAAAA/QsDACAAQTBq/QwAAAAAAAAAAAAAAAAAAAAA/QsDACAAQSBq/QwAAAAAAAAAAAAAAAAAAAAA/QsDACAAQd0BNgIcCwYAIAAQMguaLQELfyMAQRBrIgokAEGk0AAoAgAiCUUEQEHk0wAoAgAiBUUEQEHw0wBCfzcCAEHo0wBCgICEgICAwAA3AgBB5NMAIApBCGpBcHFB2KrVqgVzIgU2AgBB+NMAQQA2AgBByNMAQQA2AgALQczTAEGA1AQ2AgBBnNAAQYDUBDYCAEGw0AAgBTYCAEGs0ABBfzYCAEHQ0wBBgKwDNgIAA0AgAUHI0ABqIAFBvNAAaiICNgIAIAIgAUG00ABqIgM2AgAgAUHA0ABqIAM2AgAgAUHQ0ABqIAFBxNAAaiIDNgIAIAMgAjYCACABQdjQAGogAUHM0ABqIgI2AgAgAiADNgIAIAFB1NAAaiACNgIAIAFBIGoiAUGAAkcNAAtBjNQEQcGrAzYCAEGo0ABB9NMAKAIANgIAQZjQAEHAqwM2AgBBpNAAQYjUBDYCAEHM/wdBODYCAEGI1AQhCQsCQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCAAQewBTQRAQYzQACgCACIGQRAgAEETakFwcSAAQQtJGyIEQQN2IgB2IgFBA3EEQAJAIAFBAXEgAHJBAXMiAkEDdCIAQbTQAGoiASAAQbzQAGooAgAiACgCCCIDRgRAQYzQACAGQX4gAndxNgIADAELIAEgAzYCCCADIAE2AgwLIABBCGohASAAIAJBA3QiAkEDcjYCBCAAIAJqIgAgACgCBEEBcjYCBAwRC0GU0AAoAgAiCCAETw0BIAEEQAJAQQIgAHQiAkEAIAJrciABIAB0cWgiAEEDdCICQbTQAGoiASACQbzQAGooAgAiAigCCCIDRgRAQYzQACAGQX4gAHdxIgY2AgAMAQsgASADNgIIIAMgATYCDAsgAiAEQQNyNgIEIABBA3QiACAEayEFIAAgAmogBTYCACACIARqIgQgBUEBcjYCBCAIBEAgCEF4cUG00ABqIQBBoNAAKAIAIQMCf0EBIAhBA3Z0IgEgBnFFBEBBjNAAIAEgBnI2AgAgAAwBCyAAKAIICyIBIAM2AgwgACADNgIIIAMgADYCDCADIAE2AggLIAJBCGohAUGg0AAgBDYCAEGU0AAgBTYCAAwRC0GQ0AAoAgAiC0UNASALaEECdEG80gBqKAIAIgAoAgRBeHEgBGshBSAAIQIDQAJAIAIoAhAiAUUEQCACQRRqKAIAIgFFDQELIAEoAgRBeHEgBGsiAyAFSSECIAMgBSACGyEFIAEgACACGyEAIAEhAgwBCwsgACgCGCEJIAAoAgwiAyAARwRAQZzQACgCABogAyAAKAIIIgE2AgggASADNgIMDBALIABBFGoiAigCACIBRQRAIAAoAhAiAUUNAyAAQRBqIQILA0AgAiEHIAEiA0EUaiICKAIAIgENACADQRBqIQIgAygCECIBDQALIAdBADYCAAwPC0F/IQQgAEG/f0sNACAAQRNqIgFBcHEhBEGQ0AAoAgAiCEUNAEEAIARrIQUCQAJAAkACf0EAIARBgAJJDQAaQR8gBEH///8HSw0AGiAEQSYgAUEIdmciAGt2QQFxIABBAXRrQT5qCyIGQQJ0QbzSAGooAgAiAkUEQEEAIQFBACEDDAELQQAhASAEQRkgBkEBdmtBACAGQR9HG3QhAEEAIQMDQAJAIAIoAgRBeHEgBGsiByAFTw0AIAIhAyAHIgUNAEEAIQUgAiEBDAMLIAEgAkEUaigCACIHIAcgAiAAQR12QQRxakEQaigCACICRhsgASAHGyEBIABBAXQhACACDQALCyABIANyRQRAQQAhA0ECIAZ0IgBBACAAa3IgCHEiAEUNAyAAaEECdEG80gBqKAIAIQELIAFFDQELA0AgASgCBEF4cSAEayICIAVJIQAgAiAFIAAbIQUgASADIAAbIQMgASgCECIABH8gAAUgAUEUaigCAAsiAQ0ACwsgA0UNACAFQZTQACgCACAEa08NACADKAIYIQcgAyADKAIMIgBHBEBBnNAAKAIAGiAAIAMoAggiATYCCCABIAA2AgwMDgsgA0EUaiICKAIAIgFFBEAgAygCECIBRQ0DIANBEGohAgsDQCACIQYgASIAQRRqIgIoAgAiAQ0AIABBEGohAiAAKAIQIgENAAsgBkEANgIADA0LQZTQACgCACIDIARPBEBBoNAAKAIAIQECQCADIARrIgJBEE8EQCABIARqIgAgAkEBcjYCBCABIANqIAI2AgAgASAEQQNyNgIEDAELIAEgA0EDcjYCBCABIANqIgAgACgCBEEBcjYCBEEAIQBBACECC0GU0AAgAjYCAEGg0AAgADYCACABQQhqIQEMDwtBmNAAKAIAIgMgBEsEQCAEIAlqIgAgAyAEayIBQQFyNgIEQaTQACAANgIAQZjQACABNgIAIAkgBEEDcjYCBCAJQQhqIQEMDwtBACEBIAQCf0Hk0wAoAgAEQEHs0wAoAgAMAQtB8NMAQn83AgBB6NMAQoCAhICAgMAANwIAQeTTACAKQQxqQXBxQdiq1aoFczYCAEH40wBBADYCAEHI0wBBADYCAEGAgAQLIgAgBEHHAGoiBWoiBkEAIABrIgdxIgJPBEBB/NMAQTA2AgAMDwsCQEHE0wAoAgAiAUUNAEG80wAoAgAiCCACaiEAIAAgAU0gACAIS3ENAEEAIQFB/NMAQTA2AgAMDwtByNMALQAAQQRxDQQCQAJAIAkEQEHM0wAhAQNAIAEoAgAiACAJTQRAIAAgASgCBGogCUsNAwsgASgCCCIBDQALC0EAEDMiAEF/Rg0FIAIhBkHo0wAoAgAiAUEBayIDIABxBEAgAiAAayAAIANqQQAgAWtxaiEGCyAEIAZPDQUgBkH+////B0sNBUHE0wAoAgAiAwRAQbzTACgCACIHIAZqIQEgASAHTQ0GIAEgA0sNBgsgBhAzIgEgAEcNAQwHCyAGIANrIAdxIgZB/v///wdLDQQgBhAzIQAgACABKAIAIAEoAgRqRg0DIAAhAQsCQCAGIARByABqTw0AIAFBf0YNAEHs0wAoAgAiACAFIAZrakEAIABrcSIAQf7///8HSwRAIAEhAAwHCyAAEDNBf0cEQCAAIAZqIQYgASEADAcLQQAgBmsQMxoMBAsgASIAQX9HDQUMAwtBACEDDAwLQQAhAAwKCyAAQX9HDQILQcjTAEHI0wAoAgBBBHI2AgALIAJB/v///wdLDQEgAhAzIQBBABAzIQEgAEF/Rg0BIAFBf0YNASAAIAFPDQEgASAAayIGIARBOGpNDQELQbzTAEG80wAoAgAgBmoiATYCAEHA0wAoAgAgAUkEQEHA0wAgATYCAAsCQAJAAkBBpNAAKAIAIgIEQEHM0wAhAQNAIAAgASgCACIDIAEoAgQiBWpGDQIgASgCCCIBDQALDAILQZzQACgCACIBQQBHIAAgAU9xRQRAQZzQACAANgIAC0EAIQFB0NMAIAY2AgBBzNMAIAA2AgBBrNAAQX82AgBBsNAAQeTTACgCADYCAEHY0wBBADYCAANAIAFByNAAaiABQbzQAGoiAjYCACACIAFBtNAAaiIDNgIAIAFBwNAAaiADNgIAIAFB0NAAaiABQcTQAGoiAzYCACADIAI2AgAgAUHY0ABqIAFBzNAAaiICNgIAIAIgAzYCACABQdTQAGogAjYCACABQSBqIgFBgAJHDQALQXggAGtBD3EiASAAaiICIAZBOGsiAyABayIBQQFyNgIEQajQAEH00wAoAgA2AgBBmNAAIAE2AgBBpNAAIAI2AgAgACADakE4NgIEDAILIAAgAk0NACACIANJDQAgASgCDEEIcQ0AQXggAmtBD3EiACACaiIDQZjQACgCACAGaiIHIABrIgBBAXI2AgQgASAFIAZqNgIEQajQAEH00wAoAgA2AgBBmNAAIAA2AgBBpNAAIAM2AgAgAiAHakE4NgIEDAELIABBnNAAKAIASQRAQZzQACAANgIACyAAIAZqIQNBzNMAIQECQAJAAkADQCADIAEoAgBHBEAgASgCCCIBDQEMAgsLIAEtAAxBCHFFDQELQczTACEBA0AgASgCACIDIAJNBEAgAyABKAIEaiIFIAJLDQMLIAEoAgghAQwACwALIAEgADYCACABIAEoAgQgBmo2AgQgAEF4IABrQQ9xaiIJIARBA3I2AgQgA0F4IANrQQ9xaiIGIAQgCWoiBGshASACIAZGBEBBpNAAIAQ2AgBBmNAAQZjQACgCACABaiIANgIAIAQgAEEBcjYCBAwIC0Gg0AAoAgAgBkYEQEGg0AAgBDYCAEGU0ABBlNAAKAIAIAFqIgA2AgAgBCAAQQFyNgIEIAAgBGogADYCAAwICyAGKAIEIgVBA3FBAUcNBiAFQXhxIQggBUH/AU0EQCAFQQN2IQMgBigCCCIAIAYoAgwiAkYEQEGM0ABBjNAAKAIAQX4gA3dxNgIADAcLIAIgADYCCCAAIAI2AgwMBgsgBigCGCEHIAYgBigCDCIARwRAIAAgBigCCCICNgIIIAIgADYCDAwFCyAGQRRqIgIoAgAiBUUEQCAGKAIQIgVFDQQgBkEQaiECCwNAIAIhAyAFIgBBFGoiAigCACIFDQAgAEEQaiECIAAoAhAiBQ0ACyADQQA2AgAMBAtBeCAAa0EPcSIBIABqIgcgBkE4ayIDIAFrIgFBAXI2AgQgACADakE4NgIEIAIgBUE3IAVrQQ9xakE/ayIDIAMgAkEQakkbIgNBIzYCBEGo0ABB9NMAKAIANgIAQZjQACABNgIAQaTQACAHNgIAIANBEGpB1NMAKQIANwIAIANBzNMAKQIANwIIQdTTACADQQhqNgIAQdDTACAGNgIAQczTACAANgIAQdjTAEEANgIAIANBJGohAQNAIAFBBzYCACAFIAFBBGoiAUsNAAsgAiADRg0AIAMgAygCBEF+cTYCBCADIAMgAmsiBTYCACACIAVBAXI2AgQgBUH/AU0EQCAFQXhxQbTQAGohAAJ/QYzQACgCACIBQQEgBUEDdnQiA3FFBEBBjNAAIAEgA3I2AgAgAAwBCyAAKAIICyIBIAI2AgwgACACNgIIIAIgADYCDCACIAE2AggMAQtBHyEBIAVB////B00EQCAFQSYgBUEIdmciAGt2QQFxIABBAXRrQT5qIQELIAIgATYCHCACQgA3AhAgAUECdEG80gBqIQBBkNAAKAIAIgNBASABdCIGcUUEQCAAIAI2AgBBkNAAIAMgBnI2AgAgAiAANgIYIAIgAjYCCCACIAI2AgwMAQsgBUEZIAFBAXZrQQAgAUEfRxt0IQEgACgCACEDAkADQCADIgAoAgRBeHEgBUYNASABQR12IQMgAUEBdCEBIAAgA0EEcWpBEGoiBigCACIDDQALIAYgAjYCACACIAA2AhggAiACNgIMIAIgAjYCCAwBCyAAKAIIIgEgAjYCDCAAIAI2AgggAkEANgIYIAIgADYCDCACIAE2AggLQZjQACgCACIBIARNDQBBpNAAKAIAIgAgBGoiAiABIARrIgFBAXI2AgRBmNAAIAE2AgBBpNAAIAI2AgAgACAEQQNyNgIEIABBCGohAQwIC0EAIQFB/NMAQTA2AgAMBwtBACEACyAHRQ0AAkAgBigCHCICQQJ0QbzSAGoiAygCACAGRgRAIAMgADYCACAADQFBkNAAQZDQACgCAEF+IAJ3cTYCAAwCCyAHQRBBFCAHKAIQIAZGG2ogADYCACAARQ0BCyAAIAc2AhggBigCECICBEAgACACNgIQIAIgADYCGAsgBkEUaigCACICRQ0AIABBFGogAjYCACACIAA2AhgLIAEgCGohASAGIAhqIgYoAgQhBQsgBiAFQX5xNgIEIAEgBGogATYCACAEIAFBAXI2AgQgAUH/AU0EQCABQXhxQbTQAGohAAJ/QYzQACgCACICQQEgAUEDdnQiAXFFBEBBjNAAIAEgAnI2AgAgAAwBCyAAKAIICyIBIAQ2AgwgACAENgIIIAQgADYCDCAEIAE2AggMAQtBHyEFIAFB////B00EQCABQSYgAUEIdmciAGt2QQFxIABBAXRrQT5qIQULIAQgBTYCHCAEQgA3AhAgBUECdEG80gBqIQBBkNAAKAIAIgJBASAFdCIDcUUEQCAAIAQ2AgBBkNAAIAIgA3I2AgAgBCAANgIYIAQgBDYCCCAEIAQ2AgwMAQsgAUEZIAVBAXZrQQAgBUEfRxt0IQUgACgCACEAAkADQCAAIgIoAgRBeHEgAUYNASAFQR12IQAgBUEBdCEFIAIgAEEEcWpBEGoiAygCACIADQALIAMgBDYCACAEIAI2AhggBCAENgIMIAQgBDYCCAwBCyACKAIIIgAgBDYCDCACIAQ2AgggBEEANgIYIAQgAjYCDCAEIAA2AggLIAlBCGohAQwCCwJAIAdFDQACQCADKAIcIgFBAnRBvNIAaiICKAIAIANGBEAgAiAANgIAIAANAUGQ0AAgCEF+IAF3cSIINgIADAILIAdBEEEUIAcoAhAgA0YbaiAANgIAIABFDQELIAAgBzYCGCADKAIQIgEEQCAAIAE2AhAgASAANgIYCyADQRRqKAIAIgFFDQAgAEEUaiABNgIAIAEgADYCGAsCQCAFQQ9NBEAgAyAEIAVqIgBBA3I2AgQgACADaiIAIAAoAgRBAXI2AgQMAQsgAyAEaiICIAVBAXI2AgQgAyAEQQNyNgIEIAIgBWogBTYCACAFQf8BTQRAIAVBeHFBtNAAaiEAAn9BjNAAKAIAIgFBASAFQQN2dCIFcUUEQEGM0AAgASAFcjYCACAADAELIAAoAggLIgEgAjYCDCAAIAI2AgggAiAANgIMIAIgATYCCAwBC0EfIQEgBUH///8HTQRAIAVBJiAFQQh2ZyIAa3ZBAXEgAEEBdGtBPmohAQsgAiABNgIcIAJCADcCECABQQJ0QbzSAGohAEEBIAF0IgQgCHFFBEAgACACNgIAQZDQACAEIAhyNgIAIAIgADYCGCACIAI2AgggAiACNgIMDAELIAVBGSABQQF2a0EAIAFBH0cbdCEBIAAoAgAhBAJAA0AgBCIAKAIEQXhxIAVGDQEgAUEddiEEIAFBAXQhASAAIARBBHFqQRBqIgYoAgAiBA0ACyAGIAI2AgAgAiAANgIYIAIgAjYCDCACIAI2AggMAQsgACgCCCIBIAI2AgwgACACNgIIIAJBADYCGCACIAA2AgwgAiABNgIICyADQQhqIQEMAQsCQCAJRQ0AAkAgACgCHCIBQQJ0QbzSAGoiAigCACAARgRAIAIgAzYCACADDQFBkNAAIAtBfiABd3E2AgAMAgsgCUEQQRQgCSgCECAARhtqIAM2AgAgA0UNAQsgAyAJNgIYIAAoAhAiAQRAIAMgATYCECABIAM2AhgLIABBFGooAgAiAUUNACADQRRqIAE2AgAgASADNgIYCwJAIAVBD00EQCAAIAQgBWoiAUEDcjYCBCAAIAFqIgEgASgCBEEBcjYCBAwBCyAAIARqIgcgBUEBcjYCBCAAIARBA3I2AgQgBSAHaiAFNgIAIAgEQCAIQXhxQbTQAGohAUGg0AAoAgAhAwJ/QQEgCEEDdnQiAiAGcUUEQEGM0AAgAiAGcjYCACABDAELIAEoAggLIgIgAzYCDCABIAM2AgggAyABNgIMIAMgAjYCCAtBoNAAIAc2AgBBlNAAIAU2AgALIABBCGohAQsgCkEQaiQAIAELQwAgAEUEQD8AQRB0DwsCQCAAQf//A3ENACAAQQBIDQAgAEEQdkAAIgBBf0YEQEH80wBBMDYCAEF/DwsgAEEQdA8LAAsL3D8iAEGACAsJAQAAAAIAAAADAEGUCAsFBAAAAAUAQaQICwkGAAAABwAAAAgAQdwIC4otSW52YWxpZCBjaGFyIGluIHVybCBxdWVyeQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2JvZHkAQ29udGVudC1MZW5ndGggb3ZlcmZsb3cAQ2h1bmsgc2l6ZSBvdmVyZmxvdwBSZXNwb25zZSBvdmVyZmxvdwBJbnZhbGlkIG1ldGhvZCBmb3IgSFRUUC94LnggcmVxdWVzdABJbnZhbGlkIG1ldGhvZCBmb3IgUlRTUC94LnggcmVxdWVzdABFeHBlY3RlZCBTT1VSQ0UgbWV0aG9kIGZvciBJQ0UveC54IHJlcXVlc3QASW52YWxpZCBjaGFyIGluIHVybCBmcmFnbWVudCBzdGFydABFeHBlY3RlZCBkb3QAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9zdGF0dXMASW52YWxpZCByZXNwb25zZSBzdGF0dXMASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucwBVc2VyIGNhbGxiYWNrIGVycm9yAGBvbl9yZXNldGAgY2FsbGJhY2sgZXJyb3IAYG9uX2NodW5rX2hlYWRlcmAgY2FsbGJhY2sgZXJyb3IAYG9uX21lc3NhZ2VfYmVnaW5gIGNhbGxiYWNrIGVycm9yAGBvbl9jaHVua19leHRlbnNpb25fdmFsdWVgIGNhbGxiYWNrIGVycm9yAGBvbl9zdGF0dXNfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl92ZXJzaW9uX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fdXJsX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fY2h1bmtfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9oZWFkZXJfdmFsdWVfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9tZXNzYWdlX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fbWV0aG9kX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25faGVhZGVyX2ZpZWxkX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fY2h1bmtfZXh0ZW5zaW9uX25hbWVgIGNhbGxiYWNrIGVycm9yAFVuZXhwZWN0ZWQgY2hhciBpbiB1cmwgc2VydmVyAEludmFsaWQgaGVhZGVyIHZhbHVlIGNoYXIASW52YWxpZCBoZWFkZXIgZmllbGQgY2hhcgBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX3ZlcnNpb24ASW52YWxpZCBtaW5vciB2ZXJzaW9uAEludmFsaWQgbWFqb3IgdmVyc2lvbgBFeHBlY3RlZCBzcGFjZSBhZnRlciB2ZXJzaW9uAEV4cGVjdGVkIENSTEYgYWZ0ZXIgdmVyc2lvbgBJbnZhbGlkIEhUVFAgdmVyc2lvbgBJbnZhbGlkIGhlYWRlciB0b2tlbgBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX3VybABJbnZhbGlkIGNoYXJhY3RlcnMgaW4gdXJsAFVuZXhwZWN0ZWQgc3RhcnQgY2hhciBpbiB1cmwARG91YmxlIEAgaW4gdXJsAEVtcHR5IENvbnRlbnQtTGVuZ3RoAEludmFsaWQgY2hhcmFjdGVyIGluIENvbnRlbnQtTGVuZ3RoAER1cGxpY2F0ZSBDb250ZW50LUxlbmd0aABJbnZhbGlkIGNoYXIgaW4gdXJsIHBhdGgAQ29udGVudC1MZW5ndGggY2FuJ3QgYmUgcHJlc2VudCB3aXRoIFRyYW5zZmVyLUVuY29kaW5nAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIHNpemUAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9oZWFkZXJfdmFsdWUAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9jaHVua19leHRlbnNpb25fdmFsdWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyB2YWx1ZQBNaXNzaW5nIGV4cGVjdGVkIExGIGFmdGVyIGhlYWRlciB2YWx1ZQBJbnZhbGlkIGBUcmFuc2Zlci1FbmNvZGluZ2AgaGVhZGVyIHZhbHVlAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMgcXVvdGUgdmFsdWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyBxdW90ZWQgdmFsdWUAUGF1c2VkIGJ5IG9uX2hlYWRlcnNfY29tcGxldGUASW52YWxpZCBFT0Ygc3RhdGUAb25fcmVzZXQgcGF1c2UAb25fY2h1bmtfaGVhZGVyIHBhdXNlAG9uX21lc3NhZ2VfYmVnaW4gcGF1c2UAb25fY2h1bmtfZXh0ZW5zaW9uX3ZhbHVlIHBhdXNlAG9uX3N0YXR1c19jb21wbGV0ZSBwYXVzZQBvbl92ZXJzaW9uX2NvbXBsZXRlIHBhdXNlAG9uX3VybF9jb21wbGV0ZSBwYXVzZQBvbl9jaHVua19jb21wbGV0ZSBwYXVzZQBvbl9oZWFkZXJfdmFsdWVfY29tcGxldGUgcGF1c2UAb25fbWVzc2FnZV9jb21wbGV0ZSBwYXVzZQBvbl9tZXRob2RfY29tcGxldGUgcGF1c2UAb25faGVhZGVyX2ZpZWxkX2NvbXBsZXRlIHBhdXNlAG9uX2NodW5rX2V4dGVuc2lvbl9uYW1lIHBhdXNlAFVuZXhwZWN0ZWQgc3BhY2UgYWZ0ZXIgc3RhcnQgbGluZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2NodW5rX2V4dGVuc2lvbl9uYW1lAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMgbmFtZQBQYXVzZSBvbiBDT05ORUNUL1VwZ3JhZGUAUGF1c2Ugb24gUFJJL1VwZ3JhZGUARXhwZWN0ZWQgSFRUUC8yIENvbm5lY3Rpb24gUHJlZmFjZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX21ldGhvZABFeHBlY3RlZCBzcGFjZSBhZnRlciBtZXRob2QAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9oZWFkZXJfZmllbGQAUGF1c2VkAEludmFsaWQgd29yZCBlbmNvdW50ZXJlZABJbnZhbGlkIG1ldGhvZCBlbmNvdW50ZXJlZABVbmV4cGVjdGVkIGNoYXIgaW4gdXJsIHNjaGVtYQBSZXF1ZXN0IGhhcyBpbnZhbGlkIGBUcmFuc2Zlci1FbmNvZGluZ2AAU1dJVENIX1BST1hZAFVTRV9QUk9YWQBNS0FDVElWSVRZAFVOUFJPQ0VTU0FCTEVfRU5USVRZAENPUFkATU9WRURfUEVSTUFORU5UTFkAVE9PX0VBUkxZAE5PVElGWQBGQUlMRURfREVQRU5ERU5DWQBCQURfR0FURVdBWQBQTEFZAFBVVABDSEVDS09VVABHQVRFV0FZX1RJTUVPVVQAUkVRVUVTVF9USU1FT1VUAE5FVFdPUktfQ09OTkVDVF9USU1FT1VUAENPTk5FQ1RJT05fVElNRU9VVABMT0dJTl9USU1FT1VUAE5FVFdPUktfUkVBRF9USU1FT1VUAFBPU1QATUlTRElSRUNURURfUkVRVUVTVABDTElFTlRfQ0xPU0VEX1JFUVVFU1QAQ0xJRU5UX0NMT1NFRF9MT0FEX0JBTEFOQ0VEX1JFUVVFU1QAQkFEX1JFUVVFU1QASFRUUF9SRVFVRVNUX1NFTlRfVE9fSFRUUFNfUE9SVABSRVBPUlQASU1fQV9URUFQT1QAUkVTRVRfQ09OVEVOVABOT19DT05URU5UAFBBUlRJQUxfQ09OVEVOVABIUEVfSU5WQUxJRF9DT05TVEFOVABIUEVfQ0JfUkVTRVQAR0VUAEhQRV9TVFJJQ1QAQ09ORkxJQ1QAVEVNUE9SQVJZX1JFRElSRUNUAFBFUk1BTkVOVF9SRURJUkVDVABDT05ORUNUAE1VTFRJX1NUQVRVUwBIUEVfSU5WQUxJRF9TVEFUVVMAVE9PX01BTllfUkVRVUVTVFMARUFSTFlfSElOVFMAVU5BVkFJTEFCTEVfRk9SX0xFR0FMX1JFQVNPTlMAT1BUSU9OUwBTV0lUQ0hJTkdfUFJPVE9DT0xTAFZBUklBTlRfQUxTT19ORUdPVElBVEVTAE1VTFRJUExFX0NIT0lDRVMASU5URVJOQUxfU0VSVkVSX0VSUk9SAFdFQl9TRVJWRVJfVU5LTk9XTl9FUlJPUgBSQUlMR1VOX0VSUk9SAElERU5USVRZX1BST1ZJREVSX0FVVEhFTlRJQ0FUSU9OX0VSUk9SAFNTTF9DRVJUSUZJQ0FURV9FUlJPUgBJTlZBTElEX1hfRk9SV0FSREVEX0ZPUgBTRVRfUEFSQU1FVEVSAEdFVF9QQVJBTUVURVIASFBFX1VTRVIAU0VFX09USEVSAEhQRV9DQl9DSFVOS19IRUFERVIATUtDQUxFTkRBUgBTRVRVUABXRUJfU0VSVkVSX0lTX0RPV04AVEVBUkRPV04ASFBFX0NMT1NFRF9DT05ORUNUSU9OAEhFVVJJU1RJQ19FWFBJUkFUSU9OAERJU0NPTk5FQ1RFRF9PUEVSQVRJT04ATk9OX0FVVEhPUklUQVRJVkVfSU5GT1JNQVRJT04ASFBFX0lOVkFMSURfVkVSU0lPTgBIUEVfQ0JfTUVTU0FHRV9CRUdJTgBTSVRFX0lTX0ZST1pFTgBIUEVfSU5WQUxJRF9IRUFERVJfVE9LRU4ASU5WQUxJRF9UT0tFTgBGT1JCSURERU4ARU5IQU5DRV9ZT1VSX0NBTE0ASFBFX0lOVkFMSURfVVJMAEJMT0NLRURfQllfUEFSRU5UQUxfQ09OVFJPTABNS0NPTABBQ0wASFBFX0lOVEVSTkFMAFJFUVVFU1RfSEVBREVSX0ZJRUxEU19UT09fTEFSR0VfVU5PRkZJQ0lBTABIUEVfT0sAVU5MSU5LAFVOTE9DSwBQUkkAUkVUUllfV0lUSABIUEVfSU5WQUxJRF9DT05URU5UX0xFTkdUSABIUEVfVU5FWFBFQ1RFRF9DT05URU5UX0xFTkdUSABGTFVTSABQUk9QUEFUQ0gATS1TRUFSQ0gAVVJJX1RPT19MT05HAFBST0NFU1NJTkcATUlTQ0VMTEFORU9VU19QRVJTSVNURU5UX1dBUk5JTkcATUlTQ0VMTEFORU9VU19XQVJOSU5HAEhQRV9JTlZBTElEX1RSQU5TRkVSX0VOQ09ESU5HAEV4cGVjdGVkIENSTEYASFBFX0lOVkFMSURfQ0hVTktfU0laRQBNT1ZFAENPTlRJTlVFAEhQRV9DQl9TVEFUVVNfQ09NUExFVEUASFBFX0NCX0hFQURFUlNfQ09NUExFVEUASFBFX0NCX1ZFUlNJT05fQ09NUExFVEUASFBFX0NCX1VSTF9DT01QTEVURQBIUEVfQ0JfQ0hVTktfQ09NUExFVEUASFBFX0NCX0hFQURFUl9WQUxVRV9DT01QTEVURQBIUEVfQ0JfQ0hVTktfRVhURU5TSU9OX1ZBTFVFX0NPTVBMRVRFAEhQRV9DQl9DSFVOS19FWFRFTlNJT05fTkFNRV9DT01QTEVURQBIUEVfQ0JfTUVTU0FHRV9DT01QTEVURQBIUEVfQ0JfTUVUSE9EX0NPTVBMRVRFAEhQRV9DQl9IRUFERVJfRklFTERfQ09NUExFVEUAREVMRVRFAEhQRV9JTlZBTElEX0VPRl9TVEFURQBJTlZBTElEX1NTTF9DRVJUSUZJQ0FURQBQQVVTRQBOT19SRVNQT05TRQBVTlNVUFBPUlRFRF9NRURJQV9UWVBFAEdPTkUATk9UX0FDQ0VQVEFCTEUAU0VSVklDRV9VTkFWQUlMQUJMRQBSQU5HRV9OT1RfU0FUSVNGSUFCTEUAT1JJR0lOX0lTX1VOUkVBQ0hBQkxFAFJFU1BPTlNFX0lTX1NUQUxFAFBVUkdFAE1FUkdFAFJFUVVFU1RfSEVBREVSX0ZJRUxEU19UT09fTEFSR0UAUkVRVUVTVF9IRUFERVJfVE9PX0xBUkdFAFBBWUxPQURfVE9PX0xBUkdFAElOU1VGRklDSUVOVF9TVE9SQUdFAEhQRV9QQVVTRURfVVBHUkFERQBIUEVfUEFVU0VEX0gyX1VQR1JBREUAU09VUkNFAEFOTk9VTkNFAFRSQUNFAEhQRV9VTkVYUEVDVEVEX1NQQUNFAERFU0NSSUJFAFVOU1VCU0NSSUJFAFJFQ09SRABIUEVfSU5WQUxJRF9NRVRIT0QATk9UX0ZPVU5EAFBST1BGSU5EAFVOQklORABSRUJJTkQAVU5BVVRIT1JJWkVEAE1FVEhPRF9OT1RfQUxMT1dFRABIVFRQX1ZFUlNJT05fTk9UX1NVUFBPUlRFRABBTFJFQURZX1JFUE9SVEVEAEFDQ0VQVEVEAE5PVF9JTVBMRU1FTlRFRABMT09QX0RFVEVDVEVEAEhQRV9DUl9FWFBFQ1RFRABIUEVfTEZfRVhQRUNURUQAQ1JFQVRFRABJTV9VU0VEAEhQRV9QQVVTRUQAVElNRU9VVF9PQ0NVUkVEAFBBWU1FTlRfUkVRVUlSRUQAUFJFQ09ORElUSU9OX1JFUVVJUkVEAFBST1hZX0FVVEhFTlRJQ0FUSU9OX1JFUVVJUkVEAE5FVFdPUktfQVVUSEVOVElDQVRJT05fUkVRVUlSRUQATEVOR1RIX1JFUVVJUkVEAFNTTF9DRVJUSUZJQ0FURV9SRVFVSVJFRABVUEdSQURFX1JFUVVJUkVEAFBBR0VfRVhQSVJFRABQUkVDT05ESVRJT05fRkFJTEVEAEVYUEVDVEFUSU9OX0ZBSUxFRABSRVZBTElEQVRJT05fRkFJTEVEAFNTTF9IQU5EU0hBS0VfRkFJTEVEAExPQ0tFRABUUkFOU0ZPUk1BVElPTl9BUFBMSUVEAE5PVF9NT0RJRklFRABOT1RfRVhURU5ERUQAQkFORFdJRFRIX0xJTUlUX0VYQ0VFREVEAFNJVEVfSVNfT1ZFUkxPQURFRABIRUFEAEV4cGVjdGVkIEhUVFAvAABeEwAAJhMAADAQAADwFwAAnRMAABUSAAA5FwAA8BIAAAoQAAB1EgAArRIAAIITAABPFAAAfxAAAKAVAAAjFAAAiRIAAIsUAABNFQAA1BEAAM8UAAAQGAAAyRYAANwWAADBEQAA4BcAALsUAAB0FAAAfBUAAOUUAAAIFwAAHxAAAGUVAACjFAAAKBUAAAIVAACZFQAALBAAAIsZAABPDwAA1A4AAGoQAADOEAAAAhcAAIkOAABuEwAAHBMAAGYUAABWFwAAwRMAAM0TAABsEwAAaBcAAGYXAABfFwAAIhMAAM4PAABpDgAA2A4AAGMWAADLEwAAqg4AACgXAAAmFwAAxRMAAF0WAADoEQAAZxMAAGUTAADyFgAAcxMAAB0XAAD5FgAA8xEAAM8OAADOFQAADBIAALMRAAClEQAAYRAAADIXAAC7EwBB+TULAQEAQZA2C+ABAQECAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAQf03CwEBAEGROAteAgMCAgICAgAAAgIAAgIAAgICAgICAgICAgAEAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAgICAAIAAgBB/TkLAQEAQZE6C14CAAICAgICAAACAgACAgACAgICAgICAgICAAMABAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAgACAEHwOwsNbG9zZWVlcC1hbGl2ZQBBiTwLAQEAQaA8C+ABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAQYk+CwEBAEGgPgvnAQEBAQEBAQEBAQEBAQIBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBY2h1bmtlZABBsMAAC18BAQABAQEBAQAAAQEAAQEAAQEBAQEBAQEBAQAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEAAQBBkMIACyFlY3Rpb25lbnQtbGVuZ3Rob25yb3h5LWNvbm5lY3Rpb24AQcDCAAstcmFuc2Zlci1lbmNvZGluZ3BncmFkZQ0KDQoNClNNDQoNClRUUC9DRS9UU1AvAEH5wgALBQECAAEDAEGQwwAL4AEEAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBB+cQACwUBAgABAwBBkMUAC+ABBAEBBQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAQfnGAAsEAQAAAQBBkccAC98BAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBB+sgACwQBAAACAEGQyQALXwMEAAAEBAQEBAQEBAQEBAUEBAQEBAQEBAQEBAQABAAGBwQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEAAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAEAEH6ygALBAEAAAEAQZDLAAsBAQBBqssAC0ECAAAAAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBB+swACwQBAAABAEGQzQALAQEAQZrNAAsGAgAAAAACAEGxzQALOgMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAQfDOAAuWAU5PVU5DRUVDS09VVE5FQ1RFVEVDUklCRUxVU0hFVEVBRFNFQVJDSFJHRUNUSVZJVFlMRU5EQVJWRU9USUZZUFRJT05TQ0hTRUFZU1RBVENIR0VPUkRJUkVDVE9SVFJDSFBBUkFNRVRFUlVSQ0VCU0NSSUJFQVJET1dOQUNFSU5ETktDS1VCU0NSSUJFSFRUUC9BRFRQLw==", "base64"); +})); +//#endregion +//#region node_modules/undici/lib/web/fetch/constants.js +var require_constants$2 = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var corsSafeListedMethods = [ + "GET", + "HEAD", + "POST" + ]; + var corsSafeListedMethodsSet = new Set(corsSafeListedMethods); + var nullBodyStatus = [ + 101, + 204, + 205, + 304 + ]; + var redirectStatus = [ + 301, + 302, + 303, + 307, + 308 + ]; + var redirectStatusSet = new Set(redirectStatus); + /** + * @see https://fetch.spec.whatwg.org/#block-bad-port + */ + var badPorts = [ + "1", + "7", + "9", + "11", + "13", + "15", + "17", + "19", + "20", + "21", + "22", + "23", + "25", + "37", + "42", + "43", + "53", + "69", + "77", + "79", + "87", + "95", + "101", + "102", + "103", + "104", + "109", + "110", + "111", + "113", + "115", + "117", + "119", + "123", + "135", + "137", + "139", + "143", + "161", + "179", + "389", + "427", + "465", + "512", + "513", + "514", + "515", + "526", + "530", + "531", + "532", + "540", + "548", + "554", + "556", + "563", + "587", + "601", + "636", + "989", + "990", + "993", + "995", + "1719", + "1720", + "1723", + "2049", + "3659", + "4045", + "4190", + "5060", + "5061", + "6000", + "6566", + "6665", + "6666", + "6667", + "6668", + "6669", + "6679", + "6697", + "10080" + ]; + var badPortsSet = new Set(badPorts); + /** + * @see https://w3c.github.io/webappsec-referrer-policy/#referrer-policies + */ + var referrerPolicy = [ + "", + "no-referrer", + "no-referrer-when-downgrade", + "same-origin", + "origin", + "strict-origin", + "origin-when-cross-origin", + "strict-origin-when-cross-origin", + "unsafe-url" + ]; + var referrerPolicySet = new Set(referrerPolicy); + var requestRedirect = [ + "follow", + "manual", + "error" + ]; + var safeMethods = [ + "GET", + "HEAD", + "OPTIONS", + "TRACE" + ]; + var safeMethodsSet = new Set(safeMethods); + var requestMode = [ + "navigate", + "same-origin", + "no-cors", + "cors" + ]; + var requestCredentials = [ + "omit", + "same-origin", + "include" + ]; + var requestCache = [ + "default", + "no-store", + "reload", + "no-cache", + "force-cache", + "only-if-cached" + ]; + /** + * @see https://fetch.spec.whatwg.org/#request-body-header-name + */ + var requestBodyHeader = [ + "content-encoding", + "content-language", + "content-location", + "content-type", + "content-length" + ]; + /** + * @see https://fetch.spec.whatwg.org/#enumdef-requestduplex + */ + var requestDuplex = ["half"]; + /** + * @see http://fetch.spec.whatwg.org/#forbidden-method + */ + var forbiddenMethods = [ + "CONNECT", + "TRACE", + "TRACK" + ]; + var forbiddenMethodsSet = new Set(forbiddenMethods); + var subresource = [ + "audio", + "audioworklet", + "font", + "image", + "manifest", + "paintworklet", + "script", + "style", + "track", + "video", + "xslt", + "" + ]; + module.exports = { + subresource, + forbiddenMethods, + requestBodyHeader, + referrerPolicy, + requestRedirect, + requestMode, + requestCredentials, + requestCache, + redirectStatus, + corsSafeListedMethods, + nullBodyStatus, + safeMethods, + badPorts, + requestDuplex, + subresourceSet: new Set(subresource), + badPortsSet, + redirectStatusSet, + corsSafeListedMethodsSet, + safeMethodsSet, + forbiddenMethodsSet, + referrerPolicySet + }; +})); +//#endregion +//#region node_modules/undici/lib/web/fetch/global.js +var require_global$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var globalOrigin = Symbol.for("undici.globalOrigin.1"); + function getGlobalOrigin() { + return globalThis[globalOrigin]; + } + function setGlobalOrigin(newOrigin) { + if (newOrigin === void 0) { + Object.defineProperty(globalThis, globalOrigin, { + value: void 0, + writable: true, + enumerable: false, + configurable: false + }); + return; + } + const parsedURL = new URL(newOrigin); + if (parsedURL.protocol !== "http:" && parsedURL.protocol !== "https:") throw new TypeError(`Only http & https urls are allowed, received ${parsedURL.protocol}`); + Object.defineProperty(globalThis, globalOrigin, { + value: parsedURL, + writable: true, + enumerable: false, + configurable: false + }); + } + module.exports = { + getGlobalOrigin, + setGlobalOrigin + }; +})); +//#endregion +//#region node_modules/undici/lib/web/fetch/data-url.js +var require_data_url = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var assert$23 = __require("node:assert"); + var encoder = new TextEncoder(); + /** + * @see https://mimesniff.spec.whatwg.org/#http-token-code-point + */ + var HTTP_TOKEN_CODEPOINTS = /^[!#$%&'*+\-.^_|~A-Za-z0-9]+$/; + var HTTP_WHITESPACE_REGEX = /[\u000A\u000D\u0009\u0020]/; + var ASCII_WHITESPACE_REPLACE_REGEX = /[\u0009\u000A\u000C\u000D\u0020]/g; + /** + * @see https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point + */ + var HTTP_QUOTED_STRING_TOKENS = /^[\u0009\u0020-\u007E\u0080-\u00FF]+$/; + /** @param {URL} dataURL */ + function dataURLProcessor(dataURL) { + assert$23(dataURL.protocol === "data:"); + let input = URLSerializer(dataURL, true); + input = input.slice(5); + const position = { position: 0 }; + let mimeType = collectASequenceOfCodePointsFast(",", input, position); + const mimeTypeLength = mimeType.length; + mimeType = removeASCIIWhitespace(mimeType, true, true); + if (position.position >= input.length) return "failure"; + position.position++; + let body = stringPercentDecode(input.slice(mimeTypeLength + 1)); + if (/;(\u0020){0,}base64$/i.test(mimeType)) { + body = forgivingBase64(isomorphicDecode(body)); + if (body === "failure") return "failure"; + mimeType = mimeType.slice(0, -6); + mimeType = mimeType.replace(/(\u0020)+$/, ""); + mimeType = mimeType.slice(0, -1); + } + if (mimeType.startsWith(";")) mimeType = "text/plain" + mimeType; + let mimeTypeRecord = parseMIMEType(mimeType); + if (mimeTypeRecord === "failure") mimeTypeRecord = parseMIMEType("text/plain;charset=US-ASCII"); + return { + mimeType: mimeTypeRecord, + body + }; + } + /** + * @param {URL} url + * @param {boolean} excludeFragment + */ + function URLSerializer(url, excludeFragment = false) { + if (!excludeFragment) return url.href; + const href = url.href; + const hashLength = url.hash.length; + const serialized = hashLength === 0 ? href : href.substring(0, href.length - hashLength); + if (!hashLength && href.endsWith("#")) return serialized.slice(0, -1); + return serialized; + } + /** + * @param {(char: string) => boolean} condition + * @param {string} input + * @param {{ position: number }} position + */ + function collectASequenceOfCodePoints(condition, input, position) { + let result = ""; + while (position.position < input.length && condition(input[position.position])) { + result += input[position.position]; + position.position++; + } + return result; + } + /** + * A faster collectASequenceOfCodePoints that only works when comparing a single character. + * @param {string} char + * @param {string} input + * @param {{ position: number }} position + */ + function collectASequenceOfCodePointsFast(char, input, position) { + const idx = input.indexOf(char, position.position); + const start = position.position; + if (idx === -1) { + position.position = input.length; + return input.slice(start); + } + position.position = idx; + return input.slice(start, position.position); + } + /** @param {string} input */ + function stringPercentDecode(input) { + return percentDecode(encoder.encode(input)); + } + /** + * @param {number} byte + */ + function isHexCharByte(byte) { + return byte >= 48 && byte <= 57 || byte >= 65 && byte <= 70 || byte >= 97 && byte <= 102; + } + /** + * @param {number} byte + */ + function hexByteToNumber(byte) { + return byte >= 48 && byte <= 57 ? byte - 48 : (byte & 223) - 55; + } + /** @param {Uint8Array} input */ + function percentDecode(input) { + const length = input.length; + /** @type {Uint8Array} */ + const output = new Uint8Array(length); + let j = 0; + for (let i = 0; i < length; ++i) { + const byte = input[i]; + if (byte !== 37) output[j++] = byte; + else if (byte === 37 && !(isHexCharByte(input[i + 1]) && isHexCharByte(input[i + 2]))) output[j++] = 37; + else { + output[j++] = hexByteToNumber(input[i + 1]) << 4 | hexByteToNumber(input[i + 2]); + i += 2; + } + } + return length === j ? output : output.subarray(0, j); + } + /** @param {string} input */ + function parseMIMEType(input) { + input = removeHTTPWhitespace(input, true, true); + const position = { position: 0 }; + const type = collectASequenceOfCodePointsFast("/", input, position); + if (type.length === 0 || !HTTP_TOKEN_CODEPOINTS.test(type)) return "failure"; + if (position.position > input.length) return "failure"; + position.position++; + let subtype = collectASequenceOfCodePointsFast(";", input, position); + subtype = removeHTTPWhitespace(subtype, false, true); + if (subtype.length === 0 || !HTTP_TOKEN_CODEPOINTS.test(subtype)) return "failure"; + const typeLowercase = type.toLowerCase(); + const subtypeLowercase = subtype.toLowerCase(); + const mimeType = { + type: typeLowercase, + subtype: subtypeLowercase, + /** @type {Map} */ + parameters: /* @__PURE__ */ new Map(), + essence: `${typeLowercase}/${subtypeLowercase}` + }; + while (position.position < input.length) { + position.position++; + collectASequenceOfCodePoints((char) => HTTP_WHITESPACE_REGEX.test(char), input, position); + let parameterName = collectASequenceOfCodePoints((char) => char !== ";" && char !== "=", input, position); + parameterName = parameterName.toLowerCase(); + if (position.position < input.length) { + if (input[position.position] === ";") continue; + position.position++; + } + if (position.position > input.length) break; + let parameterValue = null; + if (input[position.position] === "\"") { + parameterValue = collectAnHTTPQuotedString(input, position, true); + collectASequenceOfCodePointsFast(";", input, position); + } else { + parameterValue = collectASequenceOfCodePointsFast(";", input, position); + parameterValue = removeHTTPWhitespace(parameterValue, false, true); + if (parameterValue.length === 0) continue; + } + if (parameterName.length !== 0 && HTTP_TOKEN_CODEPOINTS.test(parameterName) && (parameterValue.length === 0 || HTTP_QUOTED_STRING_TOKENS.test(parameterValue)) && !mimeType.parameters.has(parameterName)) mimeType.parameters.set(parameterName, parameterValue); + } + return mimeType; + } + /** @param {string} data */ + function forgivingBase64(data) { + data = data.replace(ASCII_WHITESPACE_REPLACE_REGEX, ""); + let dataLength = data.length; + if (dataLength % 4 === 0) { + if (data.charCodeAt(dataLength - 1) === 61) { + --dataLength; + if (data.charCodeAt(dataLength - 1) === 61) --dataLength; + } + } + if (dataLength % 4 === 1) return "failure"; + if (/[^+/0-9A-Za-z]/.test(data.length === dataLength ? data : data.substring(0, dataLength))) return "failure"; + const buffer = Buffer.from(data, "base64"); + return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength); + } + /** + * @param {string} input + * @param {{ position: number }} position + * @param {boolean?} extractValue + */ + function collectAnHTTPQuotedString(input, position, extractValue) { + const positionStart = position.position; + let value = ""; + assert$23(input[position.position] === "\""); + position.position++; + while (true) { + value += collectASequenceOfCodePoints((char) => char !== "\"" && char !== "\\", input, position); + if (position.position >= input.length) break; + const quoteOrBackslash = input[position.position]; + position.position++; + if (quoteOrBackslash === "\\") { + if (position.position >= input.length) { + value += "\\"; + break; + } + value += input[position.position]; + position.position++; + } else { + assert$23(quoteOrBackslash === "\""); + break; + } + } + if (extractValue) return value; + return input.slice(positionStart, position.position); + } + /** + * @see https://mimesniff.spec.whatwg.org/#serialize-a-mime-type + */ + function serializeAMimeType(mimeType) { + assert$23(mimeType !== "failure"); + const { parameters, essence } = mimeType; + let serialization = essence; + for (let [name, value] of parameters.entries()) { + serialization += ";"; + serialization += name; + serialization += "="; + if (!HTTP_TOKEN_CODEPOINTS.test(value)) { + value = value.replace(/(\\|")/g, "\\$1"); + value = "\"" + value; + value += "\""; + } + serialization += value; + } + return serialization; + } + /** + * @see https://fetch.spec.whatwg.org/#http-whitespace + * @param {number} char + */ + function isHTTPWhiteSpace(char) { + return char === 13 || char === 10 || char === 9 || char === 32; + } + /** + * @see https://fetch.spec.whatwg.org/#http-whitespace + * @param {string} str + * @param {boolean} [leading=true] + * @param {boolean} [trailing=true] + */ + function removeHTTPWhitespace(str, leading = true, trailing = true) { + return removeChars(str, leading, trailing, isHTTPWhiteSpace); + } + /** + * @see https://infra.spec.whatwg.org/#ascii-whitespace + * @param {number} char + */ + function isASCIIWhitespace(char) { + return char === 13 || char === 10 || char === 9 || char === 12 || char === 32; + } + /** + * @see https://infra.spec.whatwg.org/#strip-leading-and-trailing-ascii-whitespace + * @param {string} str + * @param {boolean} [leading=true] + * @param {boolean} [trailing=true] + */ + function removeASCIIWhitespace(str, leading = true, trailing = true) { + return removeChars(str, leading, trailing, isASCIIWhitespace); + } + /** + * @param {string} str + * @param {boolean} leading + * @param {boolean} trailing + * @param {(charCode: number) => boolean} predicate + * @returns + */ + function removeChars(str, leading, trailing, predicate) { + let lead = 0; + let trail = str.length - 1; + if (leading) while (lead < str.length && predicate(str.charCodeAt(lead))) lead++; + if (trailing) while (trail > 0 && predicate(str.charCodeAt(trail))) trail--; + return lead === 0 && trail === str.length - 1 ? str : str.slice(lead, trail + 1); + } + /** + * @see https://infra.spec.whatwg.org/#isomorphic-decode + * @param {Uint8Array} input + * @returns {string} + */ + function isomorphicDecode(input) { + const length = input.length; + if (65535 > length) return String.fromCharCode.apply(null, input); + let result = ""; + let i = 0; + let addition = 65535; + while (i < length) { + if (i + addition > length) addition = length - i; + result += String.fromCharCode.apply(null, input.subarray(i, i += addition)); + } + return result; + } + /** + * @see https://mimesniff.spec.whatwg.org/#minimize-a-supported-mime-type + * @param {Exclude, 'failure'>} mimeType + */ + function minimizeSupportedMimeType(mimeType) { + switch (mimeType.essence) { + case "application/ecmascript": + case "application/javascript": + case "application/x-ecmascript": + case "application/x-javascript": + case "text/ecmascript": + case "text/javascript": + case "text/javascript1.0": + case "text/javascript1.1": + case "text/javascript1.2": + case "text/javascript1.3": + case "text/javascript1.4": + case "text/javascript1.5": + case "text/jscript": + case "text/livescript": + case "text/x-ecmascript": + case "text/x-javascript": return "text/javascript"; + case "application/json": + case "text/json": return "application/json"; + case "image/svg+xml": return "image/svg+xml"; + case "text/xml": + case "application/xml": return "application/xml"; + } + if (mimeType.subtype.endsWith("+json")) return "application/json"; + if (mimeType.subtype.endsWith("+xml")) return "application/xml"; + return ""; + } + module.exports = { + dataURLProcessor, + URLSerializer, + collectASequenceOfCodePoints, + collectASequenceOfCodePointsFast, + stringPercentDecode, + parseMIMEType, + collectAnHTTPQuotedString, + serializeAMimeType, + removeChars, + removeHTTPWhitespace, + minimizeSupportedMimeType, + HTTP_TOKEN_CODEPOINTS, + isomorphicDecode + }; +})); +//#endregion +//#region node_modules/undici/lib/web/fetch/webidl.js +var require_webidl = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { types: types$3, inspect } = __require("node:util"); + var { markAsUncloneable } = __require("node:worker_threads"); + var { toUSVString } = require_util$7(); + /** @type {import('../../../types/webidl').Webidl} */ + var webidl = {}; + webidl.converters = {}; + webidl.util = {}; + webidl.errors = {}; + webidl.errors.exception = function(message) { + return /* @__PURE__ */ new TypeError(`${message.header}: ${message.message}`); + }; + webidl.errors.conversionFailed = function(context) { + const plural = context.types.length === 1 ? "" : " one of"; + const message = `${context.argument} could not be converted to${plural}: ${context.types.join(", ")}.`; + return webidl.errors.exception({ + header: context.prefix, + message + }); + }; + webidl.errors.invalidArgument = function(context) { + return webidl.errors.exception({ + header: context.prefix, + message: `"${context.value}" is an invalid ${context.type}.` + }); + }; + webidl.brandCheck = function(V, I, opts) { + if (opts?.strict !== false) { + if (!(V instanceof I)) { + const err = /* @__PURE__ */ new TypeError("Illegal invocation"); + err.code = "ERR_INVALID_THIS"; + throw err; + } + } else if (V?.[Symbol.toStringTag] !== I.prototype[Symbol.toStringTag]) { + const err = /* @__PURE__ */ new TypeError("Illegal invocation"); + err.code = "ERR_INVALID_THIS"; + throw err; + } + }; + webidl.argumentLengthCheck = function({ length }, min, ctx) { + if (length < min) throw webidl.errors.exception({ + message: `${min} argument${min !== 1 ? "s" : ""} required, but${length ? " only" : ""} ${length} found.`, + header: ctx + }); + }; + webidl.illegalConstructor = function() { + throw webidl.errors.exception({ + header: "TypeError", + message: "Illegal constructor" + }); + }; + webidl.util.Type = function(V) { + switch (typeof V) { + case "undefined": return "Undefined"; + case "boolean": return "Boolean"; + case "string": return "String"; + case "symbol": return "Symbol"; + case "number": return "Number"; + case "bigint": return "BigInt"; + case "function": + case "object": + if (V === null) return "Null"; + return "Object"; + } + }; + webidl.util.markAsUncloneable = markAsUncloneable || (() => {}); + webidl.util.ConvertToInt = function(V, bitLength, signedness, opts) { + let upperBound; + let lowerBound; + if (bitLength === 64) { + upperBound = Math.pow(2, 53) - 1; + if (signedness === "unsigned") lowerBound = 0; + else lowerBound = Math.pow(-2, 53) + 1; + } else if (signedness === "unsigned") { + lowerBound = 0; + upperBound = Math.pow(2, bitLength) - 1; + } else { + lowerBound = Math.pow(-2, bitLength) - 1; + upperBound = Math.pow(2, bitLength - 1) - 1; + } + let x = Number(V); + if (x === 0) x = 0; + if (opts?.enforceRange === true) { + if (Number.isNaN(x) || x === Number.POSITIVE_INFINITY || x === Number.NEGATIVE_INFINITY) throw webidl.errors.exception({ + header: "Integer conversion", + message: `Could not convert ${webidl.util.Stringify(V)} to an integer.` + }); + x = webidl.util.IntegerPart(x); + if (x < lowerBound || x > upperBound) throw webidl.errors.exception({ + header: "Integer conversion", + message: `Value must be between ${lowerBound}-${upperBound}, got ${x}.` + }); + return x; + } + if (!Number.isNaN(x) && opts?.clamp === true) { + x = Math.min(Math.max(x, lowerBound), upperBound); + if (Math.floor(x) % 2 === 0) x = Math.floor(x); + else x = Math.ceil(x); + return x; + } + if (Number.isNaN(x) || x === 0 && Object.is(0, x) || x === Number.POSITIVE_INFINITY || x === Number.NEGATIVE_INFINITY) return 0; + x = webidl.util.IntegerPart(x); + x = x % Math.pow(2, bitLength); + if (signedness === "signed" && x >= Math.pow(2, bitLength) - 1) return x - Math.pow(2, bitLength); + return x; + }; + webidl.util.IntegerPart = function(n) { + const r = Math.floor(Math.abs(n)); + if (n < 0) return -1 * r; + return r; + }; + webidl.util.Stringify = function(V) { + switch (webidl.util.Type(V)) { + case "Symbol": return `Symbol(${V.description})`; + case "Object": return inspect(V); + case "String": return `"${V}"`; + default: return `${V}`; + } + }; + webidl.sequenceConverter = function(converter) { + return (V, prefix, argument, Iterable) => { + if (webidl.util.Type(V) !== "Object") throw webidl.errors.exception({ + header: prefix, + message: `${argument} (${webidl.util.Stringify(V)}) is not iterable.` + }); + /** @type {Generator} */ + const method = typeof Iterable === "function" ? Iterable() : V?.[Symbol.iterator]?.(); + const seq = []; + let index = 0; + if (method === void 0 || typeof method.next !== "function") throw webidl.errors.exception({ + header: prefix, + message: `${argument} is not iterable.` + }); + while (true) { + const { done, value } = method.next(); + if (done) break; + seq.push(converter(value, prefix, `${argument}[${index++}]`)); + } + return seq; + }; + }; + webidl.recordConverter = function(keyConverter, valueConverter) { + return (O, prefix, argument) => { + if (webidl.util.Type(O) !== "Object") throw webidl.errors.exception({ + header: prefix, + message: `${argument} ("${webidl.util.Type(O)}") is not an Object.` + }); + const result = {}; + if (!types$3.isProxy(O)) { + const keys = [...Object.getOwnPropertyNames(O), ...Object.getOwnPropertySymbols(O)]; + for (const key of keys) { + const typedKey = keyConverter(key, prefix, argument); + result[typedKey] = valueConverter(O[key], prefix, argument); + } + return result; + } + const keys = Reflect.ownKeys(O); + for (const key of keys) if (Reflect.getOwnPropertyDescriptor(O, key)?.enumerable) { + const typedKey = keyConverter(key, prefix, argument); + result[typedKey] = valueConverter(O[key], prefix, argument); + } + return result; + }; + }; + webidl.interfaceConverter = function(i) { + return (V, prefix, argument, opts) => { + if (opts?.strict !== false && !(V instanceof i)) throw webidl.errors.exception({ + header: prefix, + message: `Expected ${argument} ("${webidl.util.Stringify(V)}") to be an instance of ${i.name}.` + }); + return V; + }; + }; + webidl.dictionaryConverter = function(converters) { + return (dictionary, prefix, argument) => { + const type = webidl.util.Type(dictionary); + const dict = {}; + if (type === "Null" || type === "Undefined") return dict; + else if (type !== "Object") throw webidl.errors.exception({ + header: prefix, + message: `Expected ${dictionary} to be one of: Null, Undefined, Object.` + }); + for (const options of converters) { + const { key, defaultValue, required, converter } = options; + if (required === true) { + if (!Object.hasOwn(dictionary, key)) throw webidl.errors.exception({ + header: prefix, + message: `Missing required key "${key}".` + }); + } + let value = dictionary[key]; + const hasDefault = Object.hasOwn(options, "defaultValue"); + if (hasDefault && value !== null) value ??= defaultValue(); + if (required || hasDefault || value !== void 0) { + value = converter(value, prefix, `${argument}.${key}`); + if (options.allowedValues && !options.allowedValues.includes(value)) throw webidl.errors.exception({ + header: prefix, + message: `${value} is not an accepted type. Expected one of ${options.allowedValues.join(", ")}.` + }); + dict[key] = value; + } + } + return dict; + }; + }; + webidl.nullableConverter = function(converter) { + return (V, prefix, argument) => { + if (V === null) return V; + return converter(V, prefix, argument); + }; + }; + webidl.converters.DOMString = function(V, prefix, argument, opts) { + if (V === null && opts?.legacyNullToEmptyString) return ""; + if (typeof V === "symbol") throw webidl.errors.exception({ + header: prefix, + message: `${argument} is a symbol, which cannot be converted to a DOMString.` + }); + return String(V); + }; + webidl.converters.ByteString = function(V, prefix, argument) { + const x = webidl.converters.DOMString(V, prefix, argument); + for (let index = 0; index < x.length; index++) if (x.charCodeAt(index) > 255) throw new TypeError(`Cannot convert argument to a ByteString because the character at index ${index} has a value of ${x.charCodeAt(index)} which is greater than 255.`); + return x; + }; + webidl.converters.USVString = toUSVString; + webidl.converters.boolean = function(V) { + return Boolean(V); + }; + webidl.converters.any = function(V) { + return V; + }; + webidl.converters["long long"] = function(V, prefix, argument) { + return webidl.util.ConvertToInt(V, 64, "signed", void 0, prefix, argument); + }; + webidl.converters["unsigned long long"] = function(V, prefix, argument) { + return webidl.util.ConvertToInt(V, 64, "unsigned", void 0, prefix, argument); + }; + webidl.converters["unsigned long"] = function(V, prefix, argument) { + return webidl.util.ConvertToInt(V, 32, "unsigned", void 0, prefix, argument); + }; + webidl.converters["unsigned short"] = function(V, prefix, argument, opts) { + return webidl.util.ConvertToInt(V, 16, "unsigned", opts, prefix, argument); + }; + webidl.converters.ArrayBuffer = function(V, prefix, argument, opts) { + if (webidl.util.Type(V) !== "Object" || !types$3.isAnyArrayBuffer(V)) throw webidl.errors.conversionFailed({ + prefix, + argument: `${argument} ("${webidl.util.Stringify(V)}")`, + types: ["ArrayBuffer"] + }); + if (opts?.allowShared === false && types$3.isSharedArrayBuffer(V)) throw webidl.errors.exception({ + header: "ArrayBuffer", + message: "SharedArrayBuffer is not allowed." + }); + if (V.resizable || V.growable) throw webidl.errors.exception({ + header: "ArrayBuffer", + message: "Received a resizable ArrayBuffer." + }); + return V; + }; + webidl.converters.TypedArray = function(V, T, prefix, name, opts) { + if (webidl.util.Type(V) !== "Object" || !types$3.isTypedArray(V) || V.constructor.name !== T.name) throw webidl.errors.conversionFailed({ + prefix, + argument: `${name} ("${webidl.util.Stringify(V)}")`, + types: [T.name] + }); + if (opts?.allowShared === false && types$3.isSharedArrayBuffer(V.buffer)) throw webidl.errors.exception({ + header: "ArrayBuffer", + message: "SharedArrayBuffer is not allowed." + }); + if (V.buffer.resizable || V.buffer.growable) throw webidl.errors.exception({ + header: "ArrayBuffer", + message: "Received a resizable ArrayBuffer." + }); + return V; + }; + webidl.converters.DataView = function(V, prefix, name, opts) { + if (webidl.util.Type(V) !== "Object" || !types$3.isDataView(V)) throw webidl.errors.exception({ + header: prefix, + message: `${name} is not a DataView.` + }); + if (opts?.allowShared === false && types$3.isSharedArrayBuffer(V.buffer)) throw webidl.errors.exception({ + header: "ArrayBuffer", + message: "SharedArrayBuffer is not allowed." + }); + if (V.buffer.resizable || V.buffer.growable) throw webidl.errors.exception({ + header: "ArrayBuffer", + message: "Received a resizable ArrayBuffer." + }); + return V; + }; + webidl.converters.BufferSource = function(V, prefix, name, opts) { + if (types$3.isAnyArrayBuffer(V)) return webidl.converters.ArrayBuffer(V, prefix, name, { + ...opts, + allowShared: false + }); + if (types$3.isTypedArray(V)) return webidl.converters.TypedArray(V, V.constructor, prefix, name, { + ...opts, + allowShared: false + }); + if (types$3.isDataView(V)) return webidl.converters.DataView(V, prefix, name, { + ...opts, + allowShared: false + }); + throw webidl.errors.conversionFailed({ + prefix, + argument: `${name} ("${webidl.util.Stringify(V)}")`, + types: ["BufferSource"] + }); + }; + webidl.converters["sequence"] = webidl.sequenceConverter(webidl.converters.ByteString); + webidl.converters["sequence>"] = webidl.sequenceConverter(webidl.converters["sequence"]); + webidl.converters["record"] = webidl.recordConverter(webidl.converters.ByteString, webidl.converters.ByteString); + module.exports = { webidl }; +})); +//#endregion +//#region node_modules/undici/lib/web/fetch/util.js +var require_util$6 = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { Transform: Transform$2 } = __require("node:stream"); + var zlib$1 = __require("node:zlib"); + var { redirectStatusSet, referrerPolicySet: referrerPolicyTokens, badPortsSet } = require_constants$2(); + var { getGlobalOrigin } = require_global$1(); + var { collectASequenceOfCodePoints, collectAnHTTPQuotedString, removeChars, parseMIMEType } = require_data_url(); + var { performance: performance$1 } = __require("node:perf_hooks"); + var { isBlobLike, ReadableStreamFrom, isValidHTTPToken, normalizedMethodRecordsBase } = require_util$7(); + var assert$22 = __require("node:assert"); + var { isUint8Array } = __require("node:util/types"); + var { webidl } = require_webidl(); + var supportedHashes = []; + /** @type {import('crypto')} */ + var crypto; + try { + crypto = __require("node:crypto"); + const possibleRelevantHashes = [ + "sha256", + "sha384", + "sha512" + ]; + supportedHashes = crypto.getHashes().filter((hash) => possibleRelevantHashes.includes(hash)); + } catch {} + function responseURL(response) { + const urlList = response.urlList; + const length = urlList.length; + return length === 0 ? null : urlList[length - 1].toString(); + } + function responseLocationURL(response, requestFragment) { + if (!redirectStatusSet.has(response.status)) return null; + let location = response.headersList.get("location", true); + if (location !== null && isValidHeaderValue(location)) { + if (!isValidEncodedURL(location)) location = normalizeBinaryStringToUtf8(location); + location = new URL(location, responseURL(response)); + } + if (location && !location.hash) location.hash = requestFragment; + return location; + } + /** + * @see https://www.rfc-editor.org/rfc/rfc1738#section-2.2 + * @param {string} url + * @returns {boolean} + */ + function isValidEncodedURL(url) { + for (let i = 0; i < url.length; ++i) { + const code = url.charCodeAt(i); + if (code > 126 || code < 32) return false; + } + return true; + } + /** + * If string contains non-ASCII characters, assumes it's UTF-8 encoded and decodes it. + * Since UTF-8 is a superset of ASCII, this will work for ASCII strings as well. + * @param {string} value + * @returns {string} + */ + function normalizeBinaryStringToUtf8(value) { + return Buffer.from(value, "binary").toString("utf8"); + } + /** @returns {URL} */ + function requestCurrentURL(request) { + return request.urlList[request.urlList.length - 1]; + } + function requestBadPort(request) { + const url = requestCurrentURL(request); + if (urlIsHttpHttpsScheme(url) && badPortsSet.has(url.port)) return "blocked"; + return "allowed"; + } + function isErrorLike(object) { + return object instanceof Error || object?.constructor?.name === "Error" || object?.constructor?.name === "DOMException"; + } + function isValidReasonPhrase(statusText) { + for (let i = 0; i < statusText.length; ++i) { + const c = statusText.charCodeAt(i); + if (!(c === 9 || c >= 32 && c <= 126 || c >= 128 && c <= 255)) return false; + } + return true; + } + /** + * @see https://fetch.spec.whatwg.org/#header-name + * @param {string} potentialValue + */ + var isValidHeaderName = isValidHTTPToken; + /** + * @see https://fetch.spec.whatwg.org/#header-value + * @param {string} potentialValue + */ + function isValidHeaderValue(potentialValue) { + return (potentialValue[0] === " " || potentialValue[0] === " " || potentialValue[potentialValue.length - 1] === " " || potentialValue[potentialValue.length - 1] === " " || potentialValue.includes("\n") || potentialValue.includes("\r") || potentialValue.includes("\0")) === false; + } + function setRequestReferrerPolicyOnRedirect(request, actualResponse) { + const { headersList } = actualResponse; + const policyHeader = (headersList.get("referrer-policy", true) ?? "").split(","); + let policy = ""; + if (policyHeader.length > 0) for (let i = policyHeader.length; i !== 0; i--) { + const token = policyHeader[i - 1].trim(); + if (referrerPolicyTokens.has(token)) { + policy = token; + break; + } + } + if (policy !== "") request.referrerPolicy = policy; + } + function crossOriginResourcePolicyCheck() { + return "allowed"; + } + function corsCheck() { + return "success"; + } + function TAOCheck() { + return "success"; + } + function appendFetchMetadata(httpRequest) { + let header = null; + header = httpRequest.mode; + httpRequest.headersList.set("sec-fetch-mode", header, true); + } + function appendRequestOriginHeader(request) { + let serializedOrigin = request.origin; + if (serializedOrigin === "client" || serializedOrigin === void 0) return; + if (request.responseTainting === "cors" || request.mode === "websocket") request.headersList.append("origin", serializedOrigin, true); + else if (request.method !== "GET" && request.method !== "HEAD") { + switch (request.referrerPolicy) { + case "no-referrer": + serializedOrigin = null; + break; + case "no-referrer-when-downgrade": + case "strict-origin": + case "strict-origin-when-cross-origin": + if (request.origin && urlHasHttpsScheme(request.origin) && !urlHasHttpsScheme(requestCurrentURL(request))) serializedOrigin = null; + break; + case "same-origin": + if (!sameOrigin(request, requestCurrentURL(request))) serializedOrigin = null; + break; + default: + } + request.headersList.append("origin", serializedOrigin, true); + } + } + function coarsenTime(timestamp, crossOriginIsolatedCapability) { + return timestamp; + } + function clampAndCoarsenConnectionTimingInfo(connectionTimingInfo, defaultStartTime, crossOriginIsolatedCapability) { + if (!connectionTimingInfo?.startTime || connectionTimingInfo.startTime < defaultStartTime) return { + domainLookupStartTime: defaultStartTime, + domainLookupEndTime: defaultStartTime, + connectionStartTime: defaultStartTime, + connectionEndTime: defaultStartTime, + secureConnectionStartTime: defaultStartTime, + ALPNNegotiatedProtocol: connectionTimingInfo?.ALPNNegotiatedProtocol + }; + return { + domainLookupStartTime: coarsenTime(connectionTimingInfo.domainLookupStartTime, crossOriginIsolatedCapability), + domainLookupEndTime: coarsenTime(connectionTimingInfo.domainLookupEndTime, crossOriginIsolatedCapability), + connectionStartTime: coarsenTime(connectionTimingInfo.connectionStartTime, crossOriginIsolatedCapability), + connectionEndTime: coarsenTime(connectionTimingInfo.connectionEndTime, crossOriginIsolatedCapability), + secureConnectionStartTime: coarsenTime(connectionTimingInfo.secureConnectionStartTime, crossOriginIsolatedCapability), + ALPNNegotiatedProtocol: connectionTimingInfo.ALPNNegotiatedProtocol + }; + } + function coarsenedSharedCurrentTime(crossOriginIsolatedCapability) { + return coarsenTime(performance$1.now(), crossOriginIsolatedCapability); + } + function createOpaqueTimingInfo(timingInfo) { + return { + startTime: timingInfo.startTime ?? 0, + redirectStartTime: 0, + redirectEndTime: 0, + postRedirectStartTime: timingInfo.startTime ?? 0, + finalServiceWorkerStartTime: 0, + finalNetworkResponseStartTime: 0, + finalNetworkRequestStartTime: 0, + endTime: 0, + encodedBodySize: 0, + decodedBodySize: 0, + finalConnectionTimingInfo: null + }; + } + function makePolicyContainer() { + return { referrerPolicy: "strict-origin-when-cross-origin" }; + } + function clonePolicyContainer(policyContainer) { + return { referrerPolicy: policyContainer.referrerPolicy }; + } + function determineRequestsReferrer(request) { + const policy = request.referrerPolicy; + assert$22(policy); + let referrerSource = null; + if (request.referrer === "client") { + const globalOrigin = getGlobalOrigin(); + if (!globalOrigin || globalOrigin.origin === "null") return "no-referrer"; + referrerSource = new URL(globalOrigin); + } else if (request.referrer instanceof URL) referrerSource = request.referrer; + let referrerURL = stripURLForReferrer(referrerSource); + const referrerOrigin = stripURLForReferrer(referrerSource, true); + if (referrerURL.toString().length > 4096) referrerURL = referrerOrigin; + const areSameOrigin = sameOrigin(request, referrerURL); + const isNonPotentiallyTrustWorthy = isURLPotentiallyTrustworthy(referrerURL) && !isURLPotentiallyTrustworthy(request.url); + switch (policy) { + case "origin": return referrerOrigin != null ? referrerOrigin : stripURLForReferrer(referrerSource, true); + case "unsafe-url": return referrerURL; + case "same-origin": return areSameOrigin ? referrerOrigin : "no-referrer"; + case "origin-when-cross-origin": return areSameOrigin ? referrerURL : referrerOrigin; + case "strict-origin-when-cross-origin": { + const currentURL = requestCurrentURL(request); + if (sameOrigin(referrerURL, currentURL)) return referrerURL; + if (isURLPotentiallyTrustworthy(referrerURL) && !isURLPotentiallyTrustworthy(currentURL)) return "no-referrer"; + return referrerOrigin; + } + /** + * 1. If referrerURL is a potentially trustworthy URL and + * request’s current URL is not a potentially trustworthy URL, + * then return no referrer. + * 2. Return referrerOrigin + */ + default: return isNonPotentiallyTrustWorthy ? "no-referrer" : referrerOrigin; + } + } + /** + * @see https://w3c.github.io/webappsec-referrer-policy/#strip-url + * @param {URL} url + * @param {boolean|undefined} originOnly + */ + function stripURLForReferrer(url, originOnly) { + assert$22(url instanceof URL); + url = new URL(url); + if (url.protocol === "file:" || url.protocol === "about:" || url.protocol === "blank:") return "no-referrer"; + url.username = ""; + url.password = ""; + url.hash = ""; + if (originOnly) { + url.pathname = ""; + url.search = ""; + } + return url; + } + function isURLPotentiallyTrustworthy(url) { + if (!(url instanceof URL)) return false; + if (url.href === "about:blank" || url.href === "about:srcdoc") return true; + if (url.protocol === "data:") return true; + if (url.protocol === "file:") return true; + return isOriginPotentiallyTrustworthy(url.origin); + function isOriginPotentiallyTrustworthy(origin) { + if (origin == null || origin === "null") return false; + const originAsURL = new URL(origin); + if (originAsURL.protocol === "https:" || originAsURL.protocol === "wss:") return true; + if (/^127(?:\.[0-9]+){0,2}\.[0-9]+$|^\[(?:0*:)*?:?0*1\]$/.test(originAsURL.hostname) || originAsURL.hostname === "localhost" || originAsURL.hostname.includes("localhost.") || originAsURL.hostname.endsWith(".localhost")) return true; + return false; + } + } + /** + * @see https://w3c.github.io/webappsec-subresource-integrity/#does-response-match-metadatalist + * @param {Uint8Array} bytes + * @param {string} metadataList + */ + function bytesMatch(bytes, metadataList) { + /* istanbul ignore if: only if node is built with --without-ssl */ + if (crypto === void 0) return true; + const parsedMetadata = parseMetadata(metadataList); + if (parsedMetadata === "no metadata") return true; + if (parsedMetadata.length === 0) return true; + const metadata = filterMetadataListByAlgorithm(parsedMetadata, getStrongestMetadata(parsedMetadata)); + for (const item of metadata) { + const algorithm = item.algo; + const expectedValue = item.hash; + let actualValue = crypto.createHash(algorithm).update(bytes).digest("base64"); + if (actualValue[actualValue.length - 1] === "=") if (actualValue[actualValue.length - 2] === "=") actualValue = actualValue.slice(0, -2); + else actualValue = actualValue.slice(0, -1); + if (compareBase64Mixed(actualValue, expectedValue)) return true; + } + return false; + } + var parseHashWithOptions = /(?sha256|sha384|sha512)-((?[A-Za-z0-9+/]+|[A-Za-z0-9_-]+)={0,2}(?:\s|$)( +[!-~]*)?)?/i; + /** + * @see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata + * @param {string} metadata + */ + function parseMetadata(metadata) { + /** @type {{ algo: string, hash: string }[]} */ + const result = []; + let empty = true; + for (const token of metadata.split(" ")) { + empty = false; + const parsedToken = parseHashWithOptions.exec(token); + if (parsedToken === null || parsedToken.groups === void 0 || parsedToken.groups.algo === void 0) continue; + const algorithm = parsedToken.groups.algo.toLowerCase(); + if (supportedHashes.includes(algorithm)) result.push(parsedToken.groups); + } + if (empty === true) return "no metadata"; + return result; + } + /** + * @param {{ algo: 'sha256' | 'sha384' | 'sha512' }[]} metadataList + */ + function getStrongestMetadata(metadataList) { + let algorithm = metadataList[0].algo; + if (algorithm[3] === "5") return algorithm; + for (let i = 1; i < metadataList.length; ++i) { + const metadata = metadataList[i]; + if (metadata.algo[3] === "5") { + algorithm = "sha512"; + break; + } else if (algorithm[3] === "3") continue; + else if (metadata.algo[3] === "3") algorithm = "sha384"; + } + return algorithm; + } + function filterMetadataListByAlgorithm(metadataList, algorithm) { + if (metadataList.length === 1) return metadataList; + let pos = 0; + for (let i = 0; i < metadataList.length; ++i) if (metadataList[i].algo === algorithm) metadataList[pos++] = metadataList[i]; + metadataList.length = pos; + return metadataList; + } + /** + * Compares two base64 strings, allowing for base64url + * in the second string. + * + * @param {string} actualValue always base64 + * @param {string} expectedValue base64 or base64url + * @returns {boolean} + */ + function compareBase64Mixed(actualValue, expectedValue) { + if (actualValue.length !== expectedValue.length) return false; + for (let i = 0; i < actualValue.length; ++i) if (actualValue[i] !== expectedValue[i]) { + if (actualValue[i] === "+" && expectedValue[i] === "-" || actualValue[i] === "/" && expectedValue[i] === "_") continue; + return false; + } + return true; + } + function tryUpgradeRequestToAPotentiallyTrustworthyURL(request) {} + /** + * @link {https://html.spec.whatwg.org/multipage/origin.html#same-origin} + * @param {URL} A + * @param {URL} B + */ + function sameOrigin(A, B) { + if (A.origin === B.origin && A.origin === "null") return true; + if (A.protocol === B.protocol && A.hostname === B.hostname && A.port === B.port) return true; + return false; + } + function createDeferredPromise() { + let res; + let rej; + return { + promise: new Promise((resolve, reject) => { + res = resolve; + rej = reject; + }), + resolve: res, + reject: rej + }; + } + function isAborted(fetchParams) { + return fetchParams.controller.state === "aborted"; + } + function isCancelled(fetchParams) { + return fetchParams.controller.state === "aborted" || fetchParams.controller.state === "terminated"; + } + /** + * @see https://fetch.spec.whatwg.org/#concept-method-normalize + * @param {string} method + */ + function normalizeMethod(method) { + return normalizedMethodRecordsBase[method.toLowerCase()] ?? method; + } + function serializeJavascriptValueToJSONString(value) { + const result = JSON.stringify(value); + if (result === void 0) throw new TypeError("Value is not JSON serializable"); + assert$22(typeof result === "string"); + return result; + } + var esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())); + /** + * @see https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object + * @param {string} name name of the instance + * @param {symbol} kInternalIterator + * @param {string | number} [keyIndex] + * @param {string | number} [valueIndex] + */ + function createIterator(name, kInternalIterator, keyIndex = 0, valueIndex = 1) { + class FastIterableIterator { + /** @type {any} */ + #target; + /** @type {'key' | 'value' | 'key+value'} */ + #kind; + /** @type {number} */ + #index; + /** + * @see https://webidl.spec.whatwg.org/#dfn-default-iterator-object + * @param {unknown} target + * @param {'key' | 'value' | 'key+value'} kind + */ + constructor(target, kind) { + this.#target = target; + this.#kind = kind; + this.#index = 0; + } + next() { + if (typeof this !== "object" || this === null || !(#target in this)) throw new TypeError(`'next' called on an object that does not implement interface ${name} Iterator.`); + const index = this.#index; + const values = this.#target[kInternalIterator]; + if (index >= values.length) return { + value: void 0, + done: true + }; + const { [keyIndex]: key, [valueIndex]: value } = values[index]; + this.#index = index + 1; + let result; + switch (this.#kind) { + case "key": + result = key; + break; + case "value": + result = value; + break; + case "key+value": + result = [key, value]; + break; + } + return { + value: result, + done: false + }; + } + } + delete FastIterableIterator.prototype.constructor; + Object.setPrototypeOf(FastIterableIterator.prototype, esIteratorPrototype); + Object.defineProperties(FastIterableIterator.prototype, { + [Symbol.toStringTag]: { + writable: false, + enumerable: false, + configurable: true, + value: `${name} Iterator` + }, + next: { + writable: true, + enumerable: true, + configurable: true + } + }); + /** + * @param {unknown} target + * @param {'key' | 'value' | 'key+value'} kind + * @returns {IterableIterator} + */ + return function(target, kind) { + return new FastIterableIterator(target, kind); + }; + } + /** + * @see https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object + * @param {string} name name of the instance + * @param {any} object class + * @param {symbol} kInternalIterator + * @param {string | number} [keyIndex] + * @param {string | number} [valueIndex] + */ + function iteratorMixin(name, object, kInternalIterator, keyIndex = 0, valueIndex = 1) { + const makeIterator = createIterator(name, kInternalIterator, keyIndex, valueIndex); + const properties = { + keys: { + writable: true, + enumerable: true, + configurable: true, + value: function keys() { + webidl.brandCheck(this, object); + return makeIterator(this, "key"); + } + }, + values: { + writable: true, + enumerable: true, + configurable: true, + value: function values() { + webidl.brandCheck(this, object); + return makeIterator(this, "value"); + } + }, + entries: { + writable: true, + enumerable: true, + configurable: true, + value: function entries() { + webidl.brandCheck(this, object); + return makeIterator(this, "key+value"); + } + }, + forEach: { + writable: true, + enumerable: true, + configurable: true, + value: function forEach(callbackfn, thisArg = globalThis) { + webidl.brandCheck(this, object); + webidl.argumentLengthCheck(arguments, 1, `${name}.forEach`); + if (typeof callbackfn !== "function") throw new TypeError(`Failed to execute 'forEach' on '${name}': parameter 1 is not of type 'Function'.`); + for (const { 0: key, 1: value } of makeIterator(this, "key+value")) callbackfn.call(thisArg, value, key, this); + } + } + }; + return Object.defineProperties(object.prototype, { + ...properties, + [Symbol.iterator]: { + writable: true, + enumerable: false, + configurable: true, + value: properties.entries.value + } + }); + } + /** + * @see https://fetch.spec.whatwg.org/#body-fully-read + */ + async function fullyReadBody(body, processBody, processBodyError) { + const successSteps = processBody; + const errorSteps = processBodyError; + let reader; + try { + reader = body.stream.getReader(); + } catch (e) { + errorSteps(e); + return; + } + try { + successSteps(await readAllBytes(reader)); + } catch (e) { + errorSteps(e); + } + } + function isReadableStreamLike(stream) { + return stream instanceof ReadableStream || stream[Symbol.toStringTag] === "ReadableStream" && typeof stream.tee === "function"; + } + /** + * @param {ReadableStreamController} controller + */ + function readableStreamClose(controller) { + try { + controller.close(); + controller.byobRequest?.respond(0); + } catch (err) { + if (!err.message.includes("Controller is already closed") && !err.message.includes("ReadableStream is already closed")) throw err; + } + } + var invalidIsomorphicEncodeValueRegex = /[^\x00-\xFF]/; + /** + * @see https://infra.spec.whatwg.org/#isomorphic-encode + * @param {string} input + */ + function isomorphicEncode(input) { + assert$22(!invalidIsomorphicEncodeValueRegex.test(input)); + return input; + } + /** + * @see https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-all-bytes + * @see https://streams.spec.whatwg.org/#read-loop + * @param {ReadableStreamDefaultReader} reader + */ + async function readAllBytes(reader) { + const bytes = []; + let byteLength = 0; + while (true) { + const { done, value: chunk } = await reader.read(); + if (done) return Buffer.concat(bytes, byteLength); + if (!isUint8Array(chunk)) throw new TypeError("Received non-Uint8Array chunk"); + bytes.push(chunk); + byteLength += chunk.length; + } + } + /** + * @see https://fetch.spec.whatwg.org/#is-local + * @param {URL} url + */ + function urlIsLocal(url) { + assert$22("protocol" in url); + const protocol = url.protocol; + return protocol === "about:" || protocol === "blob:" || protocol === "data:"; + } + /** + * @param {string|URL} url + * @returns {boolean} + */ + function urlHasHttpsScheme(url) { + return typeof url === "string" && url[5] === ":" && url[0] === "h" && url[1] === "t" && url[2] === "t" && url[3] === "p" && url[4] === "s" || url.protocol === "https:"; + } + /** + * @see https://fetch.spec.whatwg.org/#http-scheme + * @param {URL} url + */ + function urlIsHttpHttpsScheme(url) { + assert$22("protocol" in url); + const protocol = url.protocol; + return protocol === "http:" || protocol === "https:"; + } + /** + * @see https://fetch.spec.whatwg.org/#simple-range-header-value + * @param {string} value + * @param {boolean} allowWhitespace + */ + function simpleRangeHeaderValue(value, allowWhitespace) { + const data = value; + if (!data.startsWith("bytes")) return "failure"; + const position = { position: 5 }; + if (allowWhitespace) collectASequenceOfCodePoints((char) => char === " " || char === " ", data, position); + if (data.charCodeAt(position.position) !== 61) return "failure"; + position.position++; + if (allowWhitespace) collectASequenceOfCodePoints((char) => char === " " || char === " ", data, position); + const rangeStart = collectASequenceOfCodePoints((char) => { + const code = char.charCodeAt(0); + return code >= 48 && code <= 57; + }, data, position); + const rangeStartValue = rangeStart.length ? Number(rangeStart) : null; + if (allowWhitespace) collectASequenceOfCodePoints((char) => char === " " || char === " ", data, position); + if (data.charCodeAt(position.position) !== 45) return "failure"; + position.position++; + if (allowWhitespace) collectASequenceOfCodePoints((char) => char === " " || char === " ", data, position); + const rangeEnd = collectASequenceOfCodePoints((char) => { + const code = char.charCodeAt(0); + return code >= 48 && code <= 57; + }, data, position); + const rangeEndValue = rangeEnd.length ? Number(rangeEnd) : null; + if (position.position < data.length) return "failure"; + if (rangeEndValue === null && rangeStartValue === null) return "failure"; + if (rangeStartValue > rangeEndValue) return "failure"; + return { + rangeStartValue, + rangeEndValue + }; + } + /** + * @see https://fetch.spec.whatwg.org/#build-a-content-range + * @param {number} rangeStart + * @param {number} rangeEnd + * @param {number} fullLength + */ + function buildContentRange(rangeStart, rangeEnd, fullLength) { + let contentRange = "bytes "; + contentRange += isomorphicEncode(`${rangeStart}`); + contentRange += "-"; + contentRange += isomorphicEncode(`${rangeEnd}`); + contentRange += "/"; + contentRange += isomorphicEncode(`${fullLength}`); + return contentRange; + } + var InflateStream = class extends Transform$2 { + #zlibOptions; + /** @param {zlib.ZlibOptions} [zlibOptions] */ + constructor(zlibOptions) { + super(); + this.#zlibOptions = zlibOptions; + } + _transform(chunk, encoding, callback) { + if (!this._inflateStream) { + if (chunk.length === 0) { + callback(); + return; + } + this._inflateStream = (chunk[0] & 15) === 8 ? zlib$1.createInflate(this.#zlibOptions) : zlib$1.createInflateRaw(this.#zlibOptions); + this._inflateStream.on("data", this.push.bind(this)); + this._inflateStream.on("end", () => this.push(null)); + this._inflateStream.on("error", (err) => this.destroy(err)); + } + this._inflateStream.write(chunk, encoding, callback); + } + _final(callback) { + if (this._inflateStream) { + this._inflateStream.end(); + this._inflateStream = null; + } + callback(); + } + }; + /** + * @param {zlib.ZlibOptions} [zlibOptions] + * @returns {InflateStream} + */ + function createInflate(zlibOptions) { + return new InflateStream(zlibOptions); + } + /** + * @see https://fetch.spec.whatwg.org/#concept-header-extract-mime-type + * @param {import('./headers').HeadersList} headers + */ + function extractMimeType(headers) { + let charset = null; + let essence = null; + let mimeType = null; + const values = getDecodeSplit("content-type", headers); + if (values === null) return "failure"; + for (const value of values) { + const temporaryMimeType = parseMIMEType(value); + if (temporaryMimeType === "failure" || temporaryMimeType.essence === "*/*") continue; + mimeType = temporaryMimeType; + if (mimeType.essence !== essence) { + charset = null; + if (mimeType.parameters.has("charset")) charset = mimeType.parameters.get("charset"); + essence = mimeType.essence; + } else if (!mimeType.parameters.has("charset") && charset !== null) mimeType.parameters.set("charset", charset); + } + if (mimeType == null) return "failure"; + return mimeType; + } + /** + * @see https://fetch.spec.whatwg.org/#header-value-get-decode-and-split + * @param {string|null} value + */ + function gettingDecodingSplitting(value) { + const input = value; + const position = { position: 0 }; + const values = []; + let temporaryValue = ""; + while (position.position < input.length) { + temporaryValue += collectASequenceOfCodePoints((char) => char !== "\"" && char !== ",", input, position); + if (position.position < input.length) if (input.charCodeAt(position.position) === 34) { + temporaryValue += collectAnHTTPQuotedString(input, position); + if (position.position < input.length) continue; + } else { + assert$22(input.charCodeAt(position.position) === 44); + position.position++; + } + temporaryValue = removeChars(temporaryValue, true, true, (char) => char === 9 || char === 32); + values.push(temporaryValue); + temporaryValue = ""; + } + return values; + } + /** + * @see https://fetch.spec.whatwg.org/#concept-header-list-get-decode-split + * @param {string} name lowercase header name + * @param {import('./headers').HeadersList} list + */ + function getDecodeSplit(name, list) { + const value = list.get(name, true); + if (value === null) return null; + return gettingDecodingSplitting(value); + } + var textDecoder = new TextDecoder(); + /** + * @see https://encoding.spec.whatwg.org/#utf-8-decode + * @param {Buffer} buffer + */ + function utf8DecodeBytes(buffer) { + if (buffer.length === 0) return ""; + if (buffer[0] === 239 && buffer[1] === 187 && buffer[2] === 191) buffer = buffer.subarray(3); + return textDecoder.decode(buffer); + } + var EnvironmentSettingsObjectBase = class { + get baseUrl() { + return getGlobalOrigin(); + } + get origin() { + return this.baseUrl?.origin; + } + policyContainer = makePolicyContainer(); + }; + var EnvironmentSettingsObject = class { + settingsObject = new EnvironmentSettingsObjectBase(); + }; + module.exports = { + isAborted, + isCancelled, + isValidEncodedURL, + createDeferredPromise, + ReadableStreamFrom, + tryUpgradeRequestToAPotentiallyTrustworthyURL, + clampAndCoarsenConnectionTimingInfo, + coarsenedSharedCurrentTime, + determineRequestsReferrer, + makePolicyContainer, + clonePolicyContainer, + appendFetchMetadata, + appendRequestOriginHeader, + TAOCheck, + corsCheck, + crossOriginResourcePolicyCheck, + createOpaqueTimingInfo, + setRequestReferrerPolicyOnRedirect, + isValidHTTPToken, + requestBadPort, + requestCurrentURL, + responseURL, + responseLocationURL, + isBlobLike, + isURLPotentiallyTrustworthy, + isValidReasonPhrase, + sameOrigin, + normalizeMethod, + serializeJavascriptValueToJSONString, + iteratorMixin, + createIterator, + isValidHeaderName, + isValidHeaderValue, + isErrorLike, + fullyReadBody, + bytesMatch, + isReadableStreamLike, + readableStreamClose, + isomorphicEncode, + urlIsLocal, + urlHasHttpsScheme, + urlIsHttpHttpsScheme, + readAllBytes, + simpleRangeHeaderValue, + buildContentRange, + parseMetadata, + createInflate, + extractMimeType, + getDecodeSplit, + utf8DecodeBytes, + environmentSettingsObject: new EnvironmentSettingsObject() + }; +})); +//#endregion +//#region node_modules/undici/lib/web/fetch/symbols.js +var require_symbols$3 = /* @__PURE__ */ __commonJSMin(((exports, module) => { + module.exports = { + kUrl: Symbol("url"), + kHeaders: Symbol("headers"), + kSignal: Symbol("signal"), + kState: Symbol("state"), + kDispatcher: Symbol("dispatcher") + }; +})); +//#endregion +//#region node_modules/undici/lib/web/fetch/file.js +var require_file = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { Blob: Blob$2, File: File$1 } = __require("node:buffer"); + var { kState } = require_symbols$3(); + var { webidl } = require_webidl(); + var FileLike = class FileLike { + constructor(blobLike, fileName, options = {}) { + const n = fileName; + const t = options.type; + const d = options.lastModified ?? Date.now(); + this[kState] = { + blobLike, + name: n, + type: t, + lastModified: d + }; + } + stream(...args) { + webidl.brandCheck(this, FileLike); + return this[kState].blobLike.stream(...args); + } + arrayBuffer(...args) { + webidl.brandCheck(this, FileLike); + return this[kState].blobLike.arrayBuffer(...args); + } + slice(...args) { + webidl.brandCheck(this, FileLike); + return this[kState].blobLike.slice(...args); + } + text(...args) { + webidl.brandCheck(this, FileLike); + return this[kState].blobLike.text(...args); + } + get size() { + webidl.brandCheck(this, FileLike); + return this[kState].blobLike.size; + } + get type() { + webidl.brandCheck(this, FileLike); + return this[kState].blobLike.type; + } + get name() { + webidl.brandCheck(this, FileLike); + return this[kState].name; + } + get lastModified() { + webidl.brandCheck(this, FileLike); + return this[kState].lastModified; + } + get [Symbol.toStringTag]() { + return "File"; + } + }; + webidl.converters.Blob = webidl.interfaceConverter(Blob$2); + function isFileLike(object) { + return object instanceof File$1 || object && (typeof object.stream === "function" || typeof object.arrayBuffer === "function") && object[Symbol.toStringTag] === "File"; + } + module.exports = { + FileLike, + isFileLike + }; +})); +//#endregion +//#region node_modules/undici/lib/web/fetch/formdata.js +var require_formdata = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { isBlobLike, iteratorMixin } = require_util$6(); + var { kState } = require_symbols$3(); + var { kEnumerableProperty } = require_util$7(); + var { FileLike, isFileLike } = require_file(); + var { webidl } = require_webidl(); + var { File: NativeFile } = __require("node:buffer"); + var nodeUtil$2 = __require("node:util"); + /** @type {globalThis['File']} */ + var File = globalThis.File ?? NativeFile; + var FormData = class FormData { + constructor(form) { + webidl.util.markAsUncloneable(this); + if (form !== void 0) throw webidl.errors.conversionFailed({ + prefix: "FormData constructor", + argument: "Argument 1", + types: ["undefined"] + }); + this[kState] = []; + } + append(name, value, filename = void 0) { + webidl.brandCheck(this, FormData); + const prefix = "FormData.append"; + webidl.argumentLengthCheck(arguments, 2, prefix); + if (arguments.length === 3 && !isBlobLike(value)) throw new TypeError("Failed to execute 'append' on 'FormData': parameter 2 is not of type 'Blob'"); + name = webidl.converters.USVString(name, prefix, "name"); + value = isBlobLike(value) ? webidl.converters.Blob(value, prefix, "value", { strict: false }) : webidl.converters.USVString(value, prefix, "value"); + filename = arguments.length === 3 ? webidl.converters.USVString(filename, prefix, "filename") : void 0; + const entry = makeEntry(name, value, filename); + this[kState].push(entry); + } + delete(name) { + webidl.brandCheck(this, FormData); + const prefix = "FormData.delete"; + webidl.argumentLengthCheck(arguments, 1, prefix); + name = webidl.converters.USVString(name, prefix, "name"); + this[kState] = this[kState].filter((entry) => entry.name !== name); + } + get(name) { + webidl.brandCheck(this, FormData); + const prefix = "FormData.get"; + webidl.argumentLengthCheck(arguments, 1, prefix); + name = webidl.converters.USVString(name, prefix, "name"); + const idx = this[kState].findIndex((entry) => entry.name === name); + if (idx === -1) return null; + return this[kState][idx].value; + } + getAll(name) { + webidl.brandCheck(this, FormData); + const prefix = "FormData.getAll"; + webidl.argumentLengthCheck(arguments, 1, prefix); + name = webidl.converters.USVString(name, prefix, "name"); + return this[kState].filter((entry) => entry.name === name).map((entry) => entry.value); + } + has(name) { + webidl.brandCheck(this, FormData); + const prefix = "FormData.has"; + webidl.argumentLengthCheck(arguments, 1, prefix); + name = webidl.converters.USVString(name, prefix, "name"); + return this[kState].findIndex((entry) => entry.name === name) !== -1; + } + set(name, value, filename = void 0) { + webidl.brandCheck(this, FormData); + const prefix = "FormData.set"; + webidl.argumentLengthCheck(arguments, 2, prefix); + if (arguments.length === 3 && !isBlobLike(value)) throw new TypeError("Failed to execute 'set' on 'FormData': parameter 2 is not of type 'Blob'"); + name = webidl.converters.USVString(name, prefix, "name"); + value = isBlobLike(value) ? webidl.converters.Blob(value, prefix, "name", { strict: false }) : webidl.converters.USVString(value, prefix, "name"); + filename = arguments.length === 3 ? webidl.converters.USVString(filename, prefix, "name") : void 0; + const entry = makeEntry(name, value, filename); + const idx = this[kState].findIndex((entry) => entry.name === name); + if (idx !== -1) this[kState] = [ + ...this[kState].slice(0, idx), + entry, + ...this[kState].slice(idx + 1).filter((entry) => entry.name !== name) + ]; + else this[kState].push(entry); + } + [nodeUtil$2.inspect.custom](depth, options) { + const state = this[kState].reduce((a, b) => { + if (a[b.name]) if (Array.isArray(a[b.name])) a[b.name].push(b.value); + else a[b.name] = [a[b.name], b.value]; + else a[b.name] = b.value; + return a; + }, { __proto__: null }); + options.depth ??= depth; + options.colors ??= true; + const output = nodeUtil$2.formatWithOptions(options, state); + return `FormData ${output.slice(output.indexOf("]") + 2)}`; + } + }; + iteratorMixin("FormData", FormData, kState, "name", "value"); + Object.defineProperties(FormData.prototype, { + append: kEnumerableProperty, + delete: kEnumerableProperty, + get: kEnumerableProperty, + getAll: kEnumerableProperty, + has: kEnumerableProperty, + set: kEnumerableProperty, + [Symbol.toStringTag]: { + value: "FormData", + configurable: true + } + }); + /** + * @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#create-an-entry + * @param {string} name + * @param {string|Blob} value + * @param {?string} filename + * @returns + */ + function makeEntry(name, value, filename) { + if (typeof value === "string") {} else { + if (!isFileLike(value)) value = value instanceof Blob ? new File([value], "blob", { type: value.type }) : new FileLike(value, "blob", { type: value.type }); + if (filename !== void 0) { + /** @type {FilePropertyBag} */ + const options = { + type: value.type, + lastModified: value.lastModified + }; + value = value instanceof NativeFile ? new File([value], filename, options) : new FileLike(value, filename, options); + } + } + return { + name, + value + }; + } + module.exports = { + FormData, + makeEntry + }; +})); +//#endregion +//#region node_modules/undici/lib/web/fetch/formdata-parser.js +var require_formdata_parser = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { isUSVString, bufferToLowerCasedHeaderName } = require_util$7(); + var { utf8DecodeBytes } = require_util$6(); + var { HTTP_TOKEN_CODEPOINTS, isomorphicDecode } = require_data_url(); + var { isFileLike } = require_file(); + var { makeEntry } = require_formdata(); + var assert$21 = __require("node:assert"); + var { File: NodeFile } = __require("node:buffer"); + var File = globalThis.File ?? NodeFile; + var formDataNameBuffer = Buffer.from("form-data; name=\""); + var filenameBuffer = Buffer.from("; filename"); + var dd = Buffer.from("--"); + var ddcrlf = Buffer.from("--\r\n"); + /** + * @param {string} chars + */ + function isAsciiString(chars) { + for (let i = 0; i < chars.length; ++i) if ((chars.charCodeAt(i) & -128) !== 0) return false; + return true; + } + /** + * @see https://andreubotella.github.io/multipart-form-data/#multipart-form-data-boundary + * @param {string} boundary + */ + function validateBoundary(boundary) { + const length = boundary.length; + if (length < 27 || length > 70) return false; + for (let i = 0; i < length; ++i) { + const cp = boundary.charCodeAt(i); + if (!(cp >= 48 && cp <= 57 || cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp === 39 || cp === 45 || cp === 95)) return false; + } + return true; + } + /** + * @see https://andreubotella.github.io/multipart-form-data/#multipart-form-data-parser + * @param {Buffer} input + * @param {ReturnType} mimeType + */ + function multipartFormDataParser(input, mimeType) { + assert$21(mimeType !== "failure" && mimeType.essence === "multipart/form-data"); + const boundaryString = mimeType.parameters.get("boundary"); + if (boundaryString === void 0) return "failure"; + const boundary = Buffer.from(`--${boundaryString}`, "utf8"); + const entryList = []; + const position = { position: 0 }; + while (input[position.position] === 13 && input[position.position + 1] === 10) position.position += 2; + let trailing = input.length; + while (input[trailing - 1] === 10 && input[trailing - 2] === 13) trailing -= 2; + if (trailing !== input.length) input = input.subarray(0, trailing); + while (true) { + if (input.subarray(position.position, position.position + boundary.length).equals(boundary)) position.position += boundary.length; + else return "failure"; + if (position.position === input.length - 2 && bufferStartsWith(input, dd, position) || position.position === input.length - 4 && bufferStartsWith(input, ddcrlf, position)) return entryList; + if (input[position.position] !== 13 || input[position.position + 1] !== 10) return "failure"; + position.position += 2; + const result = parseMultipartFormDataHeaders(input, position); + if (result === "failure") return "failure"; + let { name, filename, contentType, encoding } = result; + position.position += 2; + let body; + { + const boundaryIndex = input.indexOf(boundary.subarray(2), position.position); + if (boundaryIndex === -1) return "failure"; + body = input.subarray(position.position, boundaryIndex - 4); + position.position += body.length; + if (encoding === "base64") body = Buffer.from(body.toString(), "base64"); + } + if (input[position.position] !== 13 || input[position.position + 1] !== 10) return "failure"; + else position.position += 2; + let value; + if (filename !== null) { + contentType ??= "text/plain"; + if (!isAsciiString(contentType)) contentType = ""; + value = new File([body], filename, { type: contentType }); + } else value = utf8DecodeBytes(Buffer.from(body)); + assert$21(isUSVString(name)); + assert$21(typeof value === "string" && isUSVString(value) || isFileLike(value)); + entryList.push(makeEntry(name, value, filename)); + } + } + /** + * @see https://andreubotella.github.io/multipart-form-data/#parse-multipart-form-data-headers + * @param {Buffer} input + * @param {{ position: number }} position + */ + function parseMultipartFormDataHeaders(input, position) { + let name = null; + let filename = null; + let contentType = null; + let encoding = null; + while (true) { + if (input[position.position] === 13 && input[position.position + 1] === 10) { + if (name === null) return "failure"; + return { + name, + filename, + contentType, + encoding + }; + } + let headerName = collectASequenceOfBytes((char) => char !== 10 && char !== 13 && char !== 58, input, position); + headerName = removeChars(headerName, true, true, (char) => char === 9 || char === 32); + if (!HTTP_TOKEN_CODEPOINTS.test(headerName.toString())) return "failure"; + if (input[position.position] !== 58) return "failure"; + position.position++; + collectASequenceOfBytes((char) => char === 32 || char === 9, input, position); + switch (bufferToLowerCasedHeaderName(headerName)) { + case "content-disposition": + name = filename = null; + if (!bufferStartsWith(input, formDataNameBuffer, position)) return "failure"; + position.position += 17; + name = parseMultipartFormDataName(input, position); + if (name === null) return "failure"; + if (bufferStartsWith(input, filenameBuffer, position)) { + let check = position.position + filenameBuffer.length; + if (input[check] === 42) { + position.position += 1; + check += 1; + } + if (input[check] !== 61 || input[check + 1] !== 34) return "failure"; + position.position += 12; + filename = parseMultipartFormDataName(input, position); + if (filename === null) return "failure"; + } + break; + case "content-type": { + let headerValue = collectASequenceOfBytes((char) => char !== 10 && char !== 13, input, position); + headerValue = removeChars(headerValue, false, true, (char) => char === 9 || char === 32); + contentType = isomorphicDecode(headerValue); + break; + } + case "content-transfer-encoding": { + let headerValue = collectASequenceOfBytes((char) => char !== 10 && char !== 13, input, position); + headerValue = removeChars(headerValue, false, true, (char) => char === 9 || char === 32); + encoding = isomorphicDecode(headerValue); + break; + } + default: collectASequenceOfBytes((char) => char !== 10 && char !== 13, input, position); + } + if (input[position.position] !== 13 && input[position.position + 1] !== 10) return "failure"; + else position.position += 2; + } + } + /** + * @see https://andreubotella.github.io/multipart-form-data/#parse-a-multipart-form-data-name + * @param {Buffer} input + * @param {{ position: number }} position + */ + function parseMultipartFormDataName(input, position) { + assert$21(input[position.position - 1] === 34); + /** @type {string | Buffer} */ + let name = collectASequenceOfBytes((char) => char !== 10 && char !== 13 && char !== 34, input, position); + if (input[position.position] !== 34) return null; + else position.position++; + name = new TextDecoder().decode(name).replace(/%0A/gi, "\n").replace(/%0D/gi, "\r").replace(/%22/g, "\""); + return name; + } + /** + * @param {(char: number) => boolean} condition + * @param {Buffer} input + * @param {{ position: number }} position + */ + function collectASequenceOfBytes(condition, input, position) { + let start = position.position; + while (start < input.length && condition(input[start])) ++start; + return input.subarray(position.position, position.position = start); + } + /** + * @param {Buffer} buf + * @param {boolean} leading + * @param {boolean} trailing + * @param {(charCode: number) => boolean} predicate + * @returns {Buffer} + */ + function removeChars(buf, leading, trailing, predicate) { + let lead = 0; + let trail = buf.length - 1; + if (leading) while (lead < buf.length && predicate(buf[lead])) lead++; + if (trailing) while (trail > 0 && predicate(buf[trail])) trail--; + return lead === 0 && trail === buf.length - 1 ? buf : buf.subarray(lead, trail + 1); + } + /** + * Checks if {@param buffer} starts with {@param start} + * @param {Buffer} buffer + * @param {Buffer} start + * @param {{ position: number }} position + */ + function bufferStartsWith(buffer, start, position) { + if (buffer.length < start.length) return false; + for (let i = 0; i < start.length; i++) if (start[i] !== buffer[position.position + i]) return false; + return true; + } + module.exports = { + multipartFormDataParser, + validateBoundary + }; +})); +//#endregion +//#region node_modules/undici/lib/web/fetch/body.js +var require_body = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var util = require_util$7(); + var { ReadableStreamFrom, isBlobLike, isReadableStreamLike, readableStreamClose, createDeferredPromise, fullyReadBody, extractMimeType, utf8DecodeBytes } = require_util$6(); + var { FormData } = require_formdata(); + var { kState } = require_symbols$3(); + var { webidl } = require_webidl(); + var { Blob: Blob$1 } = __require("node:buffer"); + var assert$20 = __require("node:assert"); + var { isErrored, isDisturbed } = __require("node:stream"); + var { isArrayBuffer } = __require("node:util/types"); + var { serializeAMimeType } = require_data_url(); + var { multipartFormDataParser } = require_formdata_parser(); + var random; + try { + const crypto = __require("node:crypto"); + random = (max) => crypto.randomInt(0, max); + } catch { + random = (max) => Math.floor(Math.random(max)); + } + var textEncoder = new TextEncoder(); + function noop() {} + var hasFinalizationRegistry = globalThis.FinalizationRegistry && process.version.indexOf("v18") !== 0; + var streamRegistry; + if (hasFinalizationRegistry) streamRegistry = new FinalizationRegistry((weakRef) => { + const stream = weakRef.deref(); + if (stream && !stream.locked && !isDisturbed(stream) && !isErrored(stream)) stream.cancel("Response object has been garbage collected").catch(noop); + }); + function extractBody(object, keepalive = false) { + let stream = null; + if (object instanceof ReadableStream) stream = object; + else if (isBlobLike(object)) stream = object.stream(); + else stream = new ReadableStream({ + async pull(controller) { + const buffer = typeof source === "string" ? textEncoder.encode(source) : source; + if (buffer.byteLength) controller.enqueue(buffer); + queueMicrotask(() => readableStreamClose(controller)); + }, + start() {}, + type: "bytes" + }); + assert$20(isReadableStreamLike(stream)); + let action = null; + let source = null; + let length = null; + let type = null; + if (typeof object === "string") { + source = object; + type = "text/plain;charset=UTF-8"; + } else if (object instanceof URLSearchParams) { + source = object.toString(); + type = "application/x-www-form-urlencoded;charset=UTF-8"; + } else if (isArrayBuffer(object)) source = new Uint8Array(object.slice()); + else if (ArrayBuffer.isView(object)) source = new Uint8Array(object.buffer.slice(object.byteOffset, object.byteOffset + object.byteLength)); + else if (util.isFormDataLike(object)) { + const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, "0")}`; + const prefix = `--${boundary}\r\nContent-Disposition: form-data`; + /*! formdata-polyfill. MIT License. Jimmy Wärting */ + const escape = (str) => str.replace(/\n/g, "%0A").replace(/\r/g, "%0D").replace(/"/g, "%22"); + const normalizeLinefeeds = (value) => value.replace(/\r?\n|\r/g, "\r\n"); + const blobParts = []; + const rn = new Uint8Array([13, 10]); + length = 0; + let hasUnknownSizeValue = false; + for (const [name, value] of object) if (typeof value === "string") { + const chunk = textEncoder.encode(prefix + `; name="${escape(normalizeLinefeeds(name))}"\r\n\r\n${normalizeLinefeeds(value)}\r\n`); + blobParts.push(chunk); + length += chunk.byteLength; + } else { + const chunk = textEncoder.encode(`${prefix}; name="${escape(normalizeLinefeeds(name))}"` + (value.name ? `; filename="${escape(value.name)}"` : "") + `\r +Content-Type: ${value.type || "application/octet-stream"}\r\n\r\n`); + blobParts.push(chunk, value, rn); + if (typeof value.size === "number") length += chunk.byteLength + value.size + rn.byteLength; + else hasUnknownSizeValue = true; + } + const chunk = textEncoder.encode(`--${boundary}--\r\n`); + blobParts.push(chunk); + length += chunk.byteLength; + if (hasUnknownSizeValue) length = null; + source = object; + action = async function* () { + for (const part of blobParts) if (part.stream) yield* part.stream(); + else yield part; + }; + type = `multipart/form-data; boundary=${boundary}`; + } else if (isBlobLike(object)) { + source = object; + length = object.size; + if (object.type) type = object.type; + } else if (typeof object[Symbol.asyncIterator] === "function") { + if (keepalive) throw new TypeError("keepalive"); + if (util.isDisturbed(object) || object.locked) throw new TypeError("Response body object should not be disturbed or locked"); + stream = object instanceof ReadableStream ? object : ReadableStreamFrom(object); + } + if (typeof source === "string" || util.isBuffer(source)) length = Buffer.byteLength(source); + if (action != null) { + let iterator; + stream = new ReadableStream({ + async start() { + iterator = action(object)[Symbol.asyncIterator](); + }, + async pull(controller) { + const { value, done } = await iterator.next(); + if (done) queueMicrotask(() => { + controller.close(); + controller.byobRequest?.respond(0); + }); + else if (!isErrored(stream)) { + const buffer = new Uint8Array(value); + if (buffer.byteLength) controller.enqueue(buffer); + } + return controller.desiredSize > 0; + }, + async cancel(reason) { + await iterator.return(); + }, + type: "bytes" + }); + } + return [{ + stream, + source, + length + }, type]; + } + function safelyExtractBody(object, keepalive = false) { + if (object instanceof ReadableStream) { + // istanbul ignore next + assert$20(!util.isDisturbed(object), "The body has already been consumed."); + // istanbul ignore next + assert$20(!object.locked, "The stream is locked."); + } + return extractBody(object, keepalive); + } + function cloneBody(instance, body) { + const [out1, out2] = body.stream.tee(); + body.stream = out1; + return { + stream: out2, + length: body.length, + source: body.source + }; + } + function throwIfAborted(state) { + if (state.aborted) throw new DOMException("The operation was aborted.", "AbortError"); + } + function bodyMixinMethods(instance) { + return { + blob() { + return consumeBody(this, (bytes) => { + let mimeType = bodyMimeType(this); + if (mimeType === null) mimeType = ""; + else if (mimeType) mimeType = serializeAMimeType(mimeType); + return new Blob$1([bytes], { type: mimeType }); + }, instance); + }, + arrayBuffer() { + return consumeBody(this, (bytes) => { + return new Uint8Array(bytes).buffer; + }, instance); + }, + text() { + return consumeBody(this, utf8DecodeBytes, instance); + }, + json() { + return consumeBody(this, parseJSONFromBytes, instance); + }, + formData() { + return consumeBody(this, (value) => { + const mimeType = bodyMimeType(this); + if (mimeType !== null) switch (mimeType.essence) { + case "multipart/form-data": { + const parsed = multipartFormDataParser(value, mimeType); + if (parsed === "failure") throw new TypeError("Failed to parse body as FormData."); + const fd = new FormData(); + fd[kState] = parsed; + return fd; + } + case "application/x-www-form-urlencoded": { + const entries = new URLSearchParams(value.toString()); + const fd = new FormData(); + for (const [name, value] of entries) fd.append(name, value); + return fd; + } + } + throw new TypeError("Content-Type was not one of \"multipart/form-data\" or \"application/x-www-form-urlencoded\"."); + }, instance); + }, + bytes() { + return consumeBody(this, (bytes) => { + return new Uint8Array(bytes); + }, instance); + } + }; + } + function mixinBody(prototype) { + Object.assign(prototype.prototype, bodyMixinMethods(prototype)); + } + /** + * @see https://fetch.spec.whatwg.org/#concept-body-consume-body + * @param {Response|Request} object + * @param {(value: unknown) => unknown} convertBytesToJSValue + * @param {Response|Request} instance + */ + async function consumeBody(object, convertBytesToJSValue, instance) { + webidl.brandCheck(object, instance); + if (bodyUnusable(object)) throw new TypeError("Body is unusable: Body has already been read"); + throwIfAborted(object[kState]); + const promise = createDeferredPromise(); + const errorSteps = (error) => promise.reject(error); + const successSteps = (data) => { + try { + promise.resolve(convertBytesToJSValue(data)); + } catch (e) { + errorSteps(e); + } + }; + if (object[kState].body == null) { + successSteps(Buffer.allocUnsafe(0)); + return promise.promise; + } + await fullyReadBody(object[kState].body, successSteps, errorSteps); + return promise.promise; + } + function bodyUnusable(object) { + const body = object[kState].body; + return body != null && (body.stream.locked || util.isDisturbed(body.stream)); + } + /** + * @see https://infra.spec.whatwg.org/#parse-json-bytes-to-a-javascript-value + * @param {Uint8Array} bytes + */ + function parseJSONFromBytes(bytes) { + return JSON.parse(utf8DecodeBytes(bytes)); + } + /** + * @see https://fetch.spec.whatwg.org/#concept-body-mime-type + * @param {import('./response').Response|import('./request').Request} requestOrResponse + */ + function bodyMimeType(requestOrResponse) { + /** @type {import('./headers').HeadersList} */ + const headers = requestOrResponse[kState].headersList; + const mimeType = extractMimeType(headers); + if (mimeType === "failure") return null; + return mimeType; + } + module.exports = { + extractBody, + safelyExtractBody, + cloneBody, + mixinBody, + streamRegistry, + hasFinalizationRegistry, + bodyUnusable + }; +})); +//#endregion +//#region node_modules/undici/lib/dispatcher/client-h1.js +var require_client_h1 = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var assert$19 = __require("node:assert"); + var util = require_util$7(); + var { channels } = require_diagnostics(); + var timers = require_timers(); + var { RequestContentLengthMismatchError, ResponseContentLengthMismatchError, RequestAbortedError, HeadersTimeoutError, HeadersOverflowError, SocketError, InformationalError, BodyTimeoutError, HTTPParserError, ResponseExceededMaxSizeError } = require_errors(); + var { kUrl, kReset, kClient, kParser, kBlocking, kRunning, kPending, kSize, kWriting, kQueue, kNoRef, kKeepAliveDefaultTimeout, kHostHeader, kPendingIdx, kRunningIdx, kError, kPipelining, kSocket, kKeepAliveTimeoutValue, kMaxHeadersSize, kKeepAliveMaxTimeout, kKeepAliveTimeoutThreshold, kHeadersTimeout, kBodyTimeout, kStrictContentLength, kMaxRequests, kCounter, kMaxResponseSize, kOnError, kResume, kHTTPContext } = require_symbols$4(); + var constants = require_constants$3(); + var EMPTY_BUF = Buffer.alloc(0); + var FastBuffer = Buffer[Symbol.species]; + var addListener = util.addListener; + var removeAllListeners = util.removeAllListeners; + var kIdleSocketValidation = Symbol("kIdleSocketValidation"); + var kIdleSocketValidationTimeout = Symbol("kIdleSocketValidationTimeout"); + var kSocketUsed = Symbol("kSocketUsed"); + var extractBody; + async function lazyllhttp() { + const llhttpWasmData = process.env.JEST_WORKER_ID ? require_llhttp_wasm() : void 0; + let mod; + try { + mod = await WebAssembly.compile(require_llhttp_simd_wasm()); + } catch (e) { + /* istanbul ignore next */ + mod = await WebAssembly.compile(llhttpWasmData || require_llhttp_wasm()); + } + return await WebAssembly.instantiate(mod, { env: { + wasm_on_url: (p, at, len) => { + /* istanbul ignore next */ + return 0; + }, + wasm_on_status: (p, at, len) => { + assert$19(currentParser.ptr === p); + const start = at - currentBufferPtr + currentBufferRef.byteOffset; + return currentParser.onStatus(new FastBuffer(currentBufferRef.buffer, start, len)) || 0; + }, + wasm_on_message_begin: (p) => { + assert$19(currentParser.ptr === p); + return currentParser.onMessageBegin() || 0; + }, + wasm_on_header_field: (p, at, len) => { + assert$19(currentParser.ptr === p); + const start = at - currentBufferPtr + currentBufferRef.byteOffset; + return currentParser.onHeaderField(new FastBuffer(currentBufferRef.buffer, start, len)) || 0; + }, + wasm_on_header_value: (p, at, len) => { + assert$19(currentParser.ptr === p); + const start = at - currentBufferPtr + currentBufferRef.byteOffset; + return currentParser.onHeaderValue(new FastBuffer(currentBufferRef.buffer, start, len)) || 0; + }, + wasm_on_headers_complete: (p, statusCode, upgrade, shouldKeepAlive) => { + assert$19(currentParser.ptr === p); + return currentParser.onHeadersComplete(statusCode, Boolean(upgrade), Boolean(shouldKeepAlive)) || 0; + }, + wasm_on_body: (p, at, len) => { + assert$19(currentParser.ptr === p); + const start = at - currentBufferPtr + currentBufferRef.byteOffset; + return currentParser.onBody(new FastBuffer(currentBufferRef.buffer, start, len)) || 0; + }, + wasm_on_message_complete: (p) => { + assert$19(currentParser.ptr === p); + return currentParser.onMessageComplete() || 0; + } + } }); + } + var llhttpInstance = null; + var llhttpPromise = lazyllhttp(); + llhttpPromise.catch(); + var currentParser = null; + var currentBufferRef = null; + var currentBufferSize = 0; + var currentBufferPtr = null; + var USE_FAST_TIMER = 1; + var TIMEOUT_HEADERS = 3; + var TIMEOUT_BODY = 5; + var TIMEOUT_KEEP_ALIVE = 8; + var Parser = class { + constructor(client, socket, { exports: exports$1 }) { + assert$19(Number.isFinite(client[kMaxHeadersSize]) && client[kMaxHeadersSize] > 0); + this.llhttp = exports$1; + this.ptr = this.llhttp.llhttp_alloc(constants.TYPE.RESPONSE); + this.client = client; + this.socket = socket; + this.timeout = null; + this.timeoutValue = null; + this.timeoutType = null; + this.statusCode = null; + this.statusText = ""; + this.upgrade = false; + this.headers = []; + this.headersSize = 0; + this.headersMaxSize = client[kMaxHeadersSize]; + this.shouldKeepAlive = false; + this.paused = false; + this.resume = this.resume.bind(this); + this.bytesRead = 0; + this.keepAlive = ""; + this.contentLength = ""; + this.connection = ""; + this.maxResponseSize = client[kMaxResponseSize]; + } + setTimeout(delay, type) { + if (delay !== this.timeoutValue || type & USE_FAST_TIMER ^ this.timeoutType & USE_FAST_TIMER) { + if (this.timeout) { + timers.clearTimeout(this.timeout); + this.timeout = null; + } + if (delay) if (type & USE_FAST_TIMER) this.timeout = timers.setFastTimeout(onParserTimeout, delay, new WeakRef(this)); + else { + this.timeout = setTimeout(onParserTimeout, delay, new WeakRef(this)); + this.timeout.unref(); + } + this.timeoutValue = delay; + } else if (this.timeout) { + // istanbul ignore else: only for jest + if (this.timeout.refresh) this.timeout.refresh(); + } + this.timeoutType = type; + } + resume() { + if (this.socket.destroyed || !this.paused) return; + assert$19(this.ptr != null); + assert$19(currentParser == null); + this.llhttp.llhttp_resume(this.ptr); + assert$19(this.timeoutType === TIMEOUT_BODY); + if (this.timeout) { + // istanbul ignore else: only for jest + if (this.timeout.refresh) this.timeout.refresh(); + } + this.paused = false; + this.execute(this.socket.read() || EMPTY_BUF); + this.readMore(); + } + readMore() { + while (!this.paused && this.ptr) { + const chunk = this.socket.read(); + if (chunk === null) break; + this.execute(chunk); + } + } + execute(data) { + assert$19(this.ptr != null); + assert$19(currentParser == null); + assert$19(!this.paused); + const { socket, llhttp } = this; + if (data.length > currentBufferSize) { + if (currentBufferPtr) llhttp.free(currentBufferPtr); + currentBufferSize = Math.ceil(data.length / 4096) * 4096; + currentBufferPtr = llhttp.malloc(currentBufferSize); + } + new Uint8Array(llhttp.memory.buffer, currentBufferPtr, currentBufferSize).set(data); + try { + let ret; + try { + currentBufferRef = data; + currentParser = this; + ret = llhttp.llhttp_execute(this.ptr, currentBufferPtr, data.length); + } catch (err) { + /* istanbul ignore next: difficult to make a test case for */ + throw err; + } finally { + currentParser = null; + currentBufferRef = null; + } + const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr; + if (ret !== constants.ERROR.OK) { + const body = data.subarray(offset); + if (ret === constants.ERROR.PAUSED_UPGRADE) this.onUpgrade(body); + else if (ret === constants.ERROR.PAUSED) { + this.paused = true; + socket.unshift(body); + } else throw this.createError(ret, body); + } + } catch (err) { + util.destroy(socket, err); + } + } + finish() { + assert$19(currentParser === null); + assert$19(this.ptr != null); + assert$19(!this.paused); + const { llhttp } = this; + let ret; + try { + currentParser = this; + ret = llhttp.llhttp_finish(this.ptr); + } finally { + currentParser = null; + } + if (ret === constants.ERROR.OK) return null; + if (ret === constants.ERROR.PAUSED || ret === constants.ERROR.PAUSED_UPGRADE) { + this.paused = true; + return null; + } + return this.createError(ret, EMPTY_BUF); + } + createError(ret, data) { + const { llhttp, contentLength, bytesRead } = this; + if (contentLength && bytesRead !== parseInt(contentLength, 10)) return new ResponseContentLengthMismatchError(); + const ptr = llhttp.llhttp_get_error_reason(this.ptr); + let message = ""; + if (ptr) { + const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0); + message = "Response does not match the HTTP/1.1 protocol (" + Buffer.from(llhttp.memory.buffer, ptr, len).toString() + ")"; + } + return new HTTPParserError(message, constants.ERROR[ret], data); + } + destroy() { + assert$19(this.ptr != null); + assert$19(currentParser == null); + this.llhttp.llhttp_free(this.ptr); + this.ptr = null; + this.timeout && timers.clearTimeout(this.timeout); + this.timeout = null; + this.timeoutValue = null; + this.timeoutType = null; + this.paused = false; + } + onStatus(buf) { + this.statusText = buf.toString(); + } + onMessageBegin() { + const { socket, client } = this; + /* istanbul ignore next: difficult to make a test case for */ + if (socket.destroyed) return -1; + if (client[kRunning] === 0) { + util.destroy(socket, new SocketError("bad response", util.getSocketInfo(socket))); + return -1; + } + const request = client[kQueue][client[kRunningIdx]]; + if (!request) return -1; + request.onResponseStarted(); + } + onHeaderField(buf) { + const len = this.headers.length; + if ((len & 1) === 0) this.headers.push(buf); + else this.headers[len - 1] = Buffer.concat([this.headers[len - 1], buf]); + this.trackHeader(buf.length); + } + onHeaderValue(buf) { + let len = this.headers.length; + if ((len & 1) === 1) { + this.headers.push(buf); + len += 1; + } else this.headers[len - 1] = Buffer.concat([this.headers[len - 1], buf]); + const key = this.headers[len - 2]; + if (key.length === 10) { + const headerName = util.bufferToLowerCasedHeaderName(key); + if (headerName === "keep-alive") this.keepAlive += buf.toString(); + else if (headerName === "connection") this.connection += buf.toString(); + } else if (key.length === 14 && util.bufferToLowerCasedHeaderName(key) === "content-length") this.contentLength += buf.toString(); + this.trackHeader(buf.length); + } + trackHeader(len) { + this.headersSize += len; + if (this.headersSize >= this.headersMaxSize) util.destroy(this.socket, new HeadersOverflowError()); + } + onUpgrade(head) { + const { upgrade, client, socket, headers, statusCode } = this; + assert$19(upgrade); + assert$19(client[kSocket] === socket); + assert$19(!socket.destroyed); + assert$19(!this.paused); + assert$19((headers.length & 1) === 0); + const request = client[kQueue][client[kRunningIdx]]; + assert$19(request); + assert$19(request.upgrade || request.method === "CONNECT"); + this.statusCode = null; + this.statusText = ""; + this.shouldKeepAlive = null; + this.headers = []; + this.headersSize = 0; + socket.unshift(head); + socket[kParser].destroy(); + socket[kParser] = null; + socket[kClient] = null; + socket[kError] = null; + removeAllListeners(socket); + client[kSocket] = null; + client[kHTTPContext] = null; + client[kQueue][client[kRunningIdx]++] = null; + client.emit("disconnect", client[kUrl], [client], new InformationalError("upgrade")); + try { + request.onUpgrade(statusCode, headers, socket); + } catch (err) { + util.destroy(socket, err); + } + client[kResume](); + } + onHeadersComplete(statusCode, upgrade, shouldKeepAlive) { + const { client, socket, headers, statusText } = this; + /* istanbul ignore next: difficult to make a test case for */ + if (socket.destroyed) return -1; + if (client[kRunning] === 0) { + util.destroy(socket, new SocketError("bad response", util.getSocketInfo(socket))); + return -1; + } + const request = client[kQueue][client[kRunningIdx]]; + /* istanbul ignore next: difficult to make a test case for */ + if (!request) return -1; + assert$19(!this.upgrade); + assert$19(this.statusCode < 200); + if (statusCode === 100) { + util.destroy(socket, new SocketError("bad response", util.getSocketInfo(socket))); + return -1; + } + if (upgrade && !request.upgrade) { + util.destroy(socket, new SocketError("bad upgrade", util.getSocketInfo(socket))); + return -1; + } + assert$19(this.timeoutType === TIMEOUT_HEADERS); + this.statusCode = statusCode; + this.shouldKeepAlive = shouldKeepAlive || request.method === "HEAD" && !socket[kReset] && this.connection.toLowerCase() === "keep-alive"; + if (this.statusCode >= 200) { + const bodyTimeout = request.bodyTimeout != null ? request.bodyTimeout : client[kBodyTimeout]; + this.setTimeout(bodyTimeout, TIMEOUT_BODY); + } else if (this.timeout) { + // istanbul ignore else: only for jest + if (this.timeout.refresh) this.timeout.refresh(); + } + if (request.method === "CONNECT") { + assert$19(client[kRunning] === 1); + this.upgrade = true; + return 2; + } + if (upgrade) { + assert$19(client[kRunning] === 1); + this.upgrade = true; + return 2; + } + assert$19((this.headers.length & 1) === 0); + this.headers = []; + this.headersSize = 0; + if (this.shouldKeepAlive && client[kPipelining]) { + const keepAliveTimeout = this.keepAlive ? util.parseKeepAliveTimeout(this.keepAlive) : null; + if (keepAliveTimeout != null) { + const timeout = Math.min(keepAliveTimeout - client[kKeepAliveTimeoutThreshold], client[kKeepAliveMaxTimeout]); + if (timeout <= 0) socket[kReset] = true; + else client[kKeepAliveTimeoutValue] = timeout; + } else client[kKeepAliveTimeoutValue] = client[kKeepAliveDefaultTimeout]; + } else socket[kReset] = true; + const pause = request.onHeaders(statusCode, headers, this.resume, statusText) === false; + if (request.aborted) return -1; + if (request.method === "HEAD") return 1; + if (statusCode < 200) return 1; + if (socket[kBlocking]) { + socket[kBlocking] = false; + client[kResume](); + } + return pause ? constants.ERROR.PAUSED : 0; + } + onBody(buf) { + const { client, socket, statusCode, maxResponseSize } = this; + if (socket.destroyed) return -1; + const request = client[kQueue][client[kRunningIdx]]; + assert$19(request); + assert$19(this.timeoutType === TIMEOUT_BODY); + if (this.timeout) { + // istanbul ignore else: only for jest + if (this.timeout.refresh) this.timeout.refresh(); + } + assert$19(statusCode >= 200); + if (maxResponseSize > -1 && this.bytesRead + buf.length > maxResponseSize) { + util.destroy(socket, new ResponseExceededMaxSizeError()); + return -1; + } + this.bytesRead += buf.length; + if (request.onData(buf) === false) return constants.ERROR.PAUSED; + } + onMessageComplete() { + const { client, socket, statusCode, upgrade, headers, contentLength, bytesRead, shouldKeepAlive } = this; + if (socket.destroyed && (!statusCode || shouldKeepAlive)) return -1; + if (upgrade) return; + assert$19(statusCode >= 100); + assert$19((this.headers.length & 1) === 0); + const request = client[kQueue][client[kRunningIdx]]; + assert$19(request); + this.statusCode = null; + this.statusText = ""; + this.bytesRead = 0; + this.contentLength = ""; + this.keepAlive = ""; + this.connection = ""; + this.headers = []; + this.headersSize = 0; + if (statusCode < 200) return; + /* istanbul ignore next: should be handled by llhttp? */ + if (request.method !== "HEAD" && contentLength && bytesRead !== parseInt(contentLength, 10)) { + util.destroy(socket, new ResponseContentLengthMismatchError()); + return -1; + } + request.onComplete(headers); + client[kQueue][client[kRunningIdx]++] = null; + socket[kSocketUsed] = true; + if (socket[kWriting]) { + assert$19(client[kRunning] === 0); + util.destroy(socket, new InformationalError("reset")); + return constants.ERROR.PAUSED; + } else if (!shouldKeepAlive) { + util.destroy(socket, new InformationalError("reset")); + return constants.ERROR.PAUSED; + } else if (socket[kReset] && client[kRunning] === 0) { + util.destroy(socket, new InformationalError("reset")); + return constants.ERROR.PAUSED; + } else if (client[kPipelining] == null || client[kPipelining] === 1) setImmediate(() => client[kResume]()); + else client[kResume](); + } + }; + function onParserTimeout(parser) { + const { socket, timeoutType, client, paused } = parser.deref(); + /* istanbul ignore else */ + if (timeoutType === TIMEOUT_HEADERS) { + if (!socket[kWriting] || socket.writableNeedDrain || client[kRunning] > 1) { + assert$19(!paused, "cannot be paused while waiting for headers"); + util.destroy(socket, new HeadersTimeoutError()); + } + } else if (timeoutType === TIMEOUT_BODY) { + if (!paused) util.destroy(socket, new BodyTimeoutError()); + } else if (timeoutType === TIMEOUT_KEEP_ALIVE) { + assert$19(client[kRunning] === 0 && client[kKeepAliveTimeoutValue]); + util.destroy(socket, new InformationalError("socket idle timeout")); + } + } + async function connectH1(client, socket) { + client[kSocket] = socket; + if (!llhttpInstance) { + llhttpInstance = await llhttpPromise; + llhttpPromise = null; + } + socket[kNoRef] = false; + socket[kWriting] = false; + socket[kReset] = false; + socket[kBlocking] = false; + socket[kIdleSocketValidation] = 0; + socket[kIdleSocketValidationTimeout] = null; + socket[kSocketUsed] = false; + socket[kParser] = new Parser(client, socket, llhttpInstance); + addListener(socket, "error", function(err) { + assert$19(err.code !== "ERR_TLS_CERT_ALTNAME_INVALID"); + const parser = this[kParser]; + if (err.code === "ECONNRESET" && parser.statusCode && !parser.shouldKeepAlive) { + const parserErr = parser.finish(); + if (parserErr) { + this[kError] = parserErr; + this[kClient][kOnError](parserErr); + } + return; + } + this[kError] = err; + this[kClient][kOnError](err); + }); + addListener(socket, "readable", function() { + const parser = this[kParser]; + if (parser) parser.readMore(); + }); + addListener(socket, "end", function() { + const parser = this[kParser]; + if (parser.statusCode && !parser.shouldKeepAlive) { + const parserErr = parser.finish(); + if (parserErr) util.destroy(this, parserErr); + return; + } + util.destroy(this, new SocketError("other side closed", util.getSocketInfo(this))); + }); + addListener(socket, "close", function() { + const client = this[kClient]; + const parser = this[kParser]; + clearIdleSocketValidation(this); + if (parser) { + if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) this[kError] = parser.finish() || this[kError]; + this[kParser].destroy(); + this[kParser] = null; + } + const err = this[kError] || new SocketError("closed", util.getSocketInfo(this)); + client[kSocket] = null; + client[kHTTPContext] = null; + if (client.destroyed) { + assert$19(client[kPending] === 0); + const requests = client[kQueue].splice(client[kRunningIdx]); + for (let i = 0; i < requests.length; i++) { + const request = requests[i]; + util.errorRequest(client, request, err); + } + } else if (client[kRunning] > 0 && err.code !== "UND_ERR_INFO") { + const request = client[kQueue][client[kRunningIdx]]; + client[kQueue][client[kRunningIdx]++] = null; + util.errorRequest(client, request, err); + } + client[kPendingIdx] = client[kRunningIdx]; + assert$19(client[kRunning] === 0); + client.emit("disconnect", client[kUrl], [client], err); + client[kResume](); + }); + let closed = false; + socket.on("close", () => { + closed = true; + }); + return { + version: "h1", + defaultPipelining: 1, + write(...args) { + return writeH1(client, ...args); + }, + resume() { + resumeH1(client); + }, + destroy(err, callback) { + if (closed) queueMicrotask(callback); + else socket.destroy(err).on("close", callback); + }, + get destroyed() { + return socket.destroyed; + }, + busy(request) { + if (socket[kWriting] || socket[kReset] || socket[kBlocking] || socket[kIdleSocketValidation] === 1) return true; + if (request) { + if (client[kRunning] > 0 && !request.idempotent) return true; + if (client[kRunning] > 0 && (request.upgrade || request.method === "CONNECT")) return true; + if (client[kRunning] > 0 && util.bodyLength(request.body) !== 0 && (util.isStream(request.body) || util.isAsyncIterable(request.body) || util.isFormDataLike(request.body))) return true; + } + return false; + } + }; + } + function clearIdleSocketValidation(socket) { + if (socket[kIdleSocketValidationTimeout]) { + clearTimeout(socket[kIdleSocketValidationTimeout]); + socket[kIdleSocketValidationTimeout] = null; + } + socket[kIdleSocketValidation] = 0; + } + function scheduleIdleSocketValidation(client, socket) { + socket[kIdleSocketValidation] = 1; + socket[kIdleSocketValidationTimeout] = setTimeout(() => { + socket[kIdleSocketValidationTimeout] = null; + socket[kIdleSocketValidation] = 2; + if (client[kSocket] === socket && !socket.destroyed) client[kResume](); + }, 0); + socket[kIdleSocketValidationTimeout].unref?.(); + } + /** + * @param {import('./client.js')} client + */ + function resumeH1(client) { + const socket = client[kSocket]; + if (socket && !socket.destroyed) { + if (client[kSize] === 0) { + if (!socket[kNoRef] && socket.unref) { + socket.unref(); + socket[kNoRef] = true; + } + } else if (socket[kNoRef] && socket.ref) { + socket.ref(); + socket[kNoRef] = false; + } + if (client[kRunning] === 0 && client[kPending] > 0 && socket[kSocketUsed]) { + if (socket[kIdleSocketValidation] === 0) { + scheduleIdleSocketValidation(client, socket); + socket[kParser].readMore(); + if (socket.destroyed) return; + return; + } + if (socket[kIdleSocketValidation] === 1) { + socket[kParser].readMore(); + if (socket.destroyed) return; + return; + } + } + if (client[kRunning] === 0) { + socket[kParser].readMore(); + if (socket.destroyed) return; + } + if (client[kSize] === 0) { + if (socket[kParser].timeoutType !== TIMEOUT_KEEP_ALIVE) socket[kParser].setTimeout(client[kKeepAliveTimeoutValue], TIMEOUT_KEEP_ALIVE); + } else if (client[kRunning] > 0 && socket[kParser].statusCode < 200) { + if (socket[kParser].timeoutType !== TIMEOUT_HEADERS) { + const request = client[kQueue][client[kRunningIdx]]; + const headersTimeout = request.headersTimeout != null ? request.headersTimeout : client[kHeadersTimeout]; + socket[kParser].setTimeout(headersTimeout, TIMEOUT_HEADERS); + } + } + } + } + function shouldSendContentLength(method) { + return method !== "GET" && method !== "HEAD" && method !== "OPTIONS" && method !== "TRACE" && method !== "CONNECT"; + } + function writeH1(client, request) { + const { method, path, host, upgrade, blocking, reset } = request; + let { body, headers, contentLength } = request; + const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH" || method === "QUERY" || method === "PROPFIND" || method === "PROPPATCH"; + if (util.isFormDataLike(body)) { + if (!extractBody) extractBody = require_body().extractBody; + const [bodyStream, contentType] = extractBody(body); + if (request.contentType == null) headers.push("content-type", contentType); + body = bodyStream.stream; + contentLength = bodyStream.length; + } else if (util.isBlobLike(body) && request.contentType == null && body.type) headers.push("content-type", body.type); + if (body && typeof body.read === "function") body.read(0); + const bodyLength = util.bodyLength(body); + contentLength = bodyLength ?? contentLength; + if (contentLength === null) contentLength = request.contentLength; + if (contentLength === 0 && !expectsPayload) contentLength = null; + if (shouldSendContentLength(method) && contentLength > 0 && request.contentLength !== null && request.contentLength !== contentLength) { + if (client[kStrictContentLength]) { + util.errorRequest(client, request, new RequestContentLengthMismatchError()); + return false; + } + process.emitWarning(new RequestContentLengthMismatchError()); + } + const socket = client[kSocket]; + clearIdleSocketValidation(socket); + const abort = (err) => { + if (request.aborted || request.completed) return; + util.errorRequest(client, request, err || new RequestAbortedError()); + util.destroy(body); + util.destroy(socket, new InformationalError("aborted")); + }; + try { + request.onConnect(abort); + } catch (err) { + util.errorRequest(client, request, err); + } + if (request.aborted) return false; + if (method === "HEAD") socket[kReset] = true; + if (upgrade || method === "CONNECT") socket[kReset] = true; + if (reset != null) socket[kReset] = reset; + if (client[kMaxRequests] && socket[kCounter]++ >= client[kMaxRequests]) socket[kReset] = true; + if (blocking) socket[kBlocking] = true; + let header = `${method} ${path} HTTP/1.1\r\n`; + if (typeof host === "string") header += `host: ${host}\r\n`; + else header += client[kHostHeader]; + if (upgrade) header += `connection: upgrade\r\nupgrade: ${upgrade}\r\n`; + else if (client[kPipelining] && !socket[kReset]) header += "connection: keep-alive\r\n"; + else header += "connection: close\r\n"; + if (Array.isArray(headers)) for (let n = 0; n < headers.length; n += 2) { + const key = headers[n + 0]; + const val = headers[n + 1]; + if (Array.isArray(val)) for (let i = 0; i < val.length; i++) header += `${key}: ${val[i]}\r\n`; + else header += `${key}: ${val}\r\n`; + } + if (channels.sendHeaders.hasSubscribers) channels.sendHeaders.publish({ + request, + headers: header, + socket + }); + /* istanbul ignore else: assertion */ + if (!body || bodyLength === 0) writeBuffer(abort, null, client, request, socket, contentLength, header, expectsPayload); + else if (util.isBuffer(body)) writeBuffer(abort, body, client, request, socket, contentLength, header, expectsPayload); + else if (util.isBlobLike(body)) if (typeof body.stream === "function") writeIterable(abort, body.stream(), client, request, socket, contentLength, header, expectsPayload); + else writeBlob(abort, body, client, request, socket, contentLength, header, expectsPayload); + else if (util.isStream(body)) writeStream(abort, body, client, request, socket, contentLength, header, expectsPayload); + else if (util.isIterable(body)) writeIterable(abort, body, client, request, socket, contentLength, header, expectsPayload); + else assert$19(false); + return true; + } + function writeStream(abort, body, client, request, socket, contentLength, header, expectsPayload) { + assert$19(contentLength !== 0 || client[kRunning] === 0, "stream body cannot be pipelined"); + let finished = false; + const writer = new AsyncWriter({ + abort, + socket, + request, + contentLength, + client, + expectsPayload, + header + }); + const onData = function(chunk) { + if (finished) return; + try { + if (!writer.write(chunk) && this.pause) this.pause(); + } catch (err) { + util.destroy(this, err); + } + }; + const onDrain = function() { + if (finished) return; + if (body.resume) body.resume(); + }; + const onClose = function() { + queueMicrotask(() => { + body.removeListener("error", onFinished); + }); + if (!finished) { + const err = new RequestAbortedError(); + queueMicrotask(() => onFinished(err)); + } + }; + const onFinished = function(err) { + if (finished) return; + finished = true; + assert$19(socket.destroyed || socket[kWriting] && client[kRunning] <= 1); + socket.off("drain", onDrain).off("error", onFinished); + body.removeListener("data", onData).removeListener("end", onFinished).removeListener("close", onClose); + if (!err) try { + writer.end(); + } catch (er) { + err = er; + } + writer.destroy(err); + if (err && (err.code !== "UND_ERR_INFO" || err.message !== "reset")) util.destroy(body, err); + else util.destroy(body); + }; + body.on("data", onData).on("end", onFinished).on("error", onFinished).on("close", onClose); + if (body.resume) body.resume(); + socket.on("drain", onDrain).on("error", onFinished); + if (body.errorEmitted ?? body.errored) setImmediate(() => onFinished(body.errored)); + else if (body.endEmitted ?? body.readableEnded) setImmediate(() => onFinished(null)); + if (body.closeEmitted ?? body.closed) setImmediate(onClose); + } + function writeBuffer(abort, body, client, request, socket, contentLength, header, expectsPayload) { + try { + if (!body) if (contentLength === 0) socket.write(`${header}content-length: 0\r\n\r\n`, "latin1"); + else { + assert$19(contentLength === null, "no body must not have content length"); + socket.write(`${header}\r\n`, "latin1"); + } + else if (util.isBuffer(body)) { + assert$19(contentLength === body.byteLength, "buffer body must have content length"); + socket.cork(); + socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, "latin1"); + socket.write(body); + socket.uncork(); + request.onBodySent(body); + if (!expectsPayload && request.reset !== false) socket[kReset] = true; + } + request.onRequestSent(); + client[kResume](); + } catch (err) { + abort(err); + } + } + async function writeBlob(abort, body, client, request, socket, contentLength, header, expectsPayload) { + assert$19(contentLength === body.size, "blob body must have content length"); + try { + if (contentLength != null && contentLength !== body.size) throw new RequestContentLengthMismatchError(); + const buffer = Buffer.from(await body.arrayBuffer()); + socket.cork(); + socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, "latin1"); + socket.write(buffer); + socket.uncork(); + request.onBodySent(buffer); + request.onRequestSent(); + if (!expectsPayload && request.reset !== false) socket[kReset] = true; + client[kResume](); + } catch (err) { + abort(err); + } + } + async function writeIterable(abort, body, client, request, socket, contentLength, header, expectsPayload) { + assert$19(contentLength !== 0 || client[kRunning] === 0, "iterator body cannot be pipelined"); + let callback = null; + function onDrain() { + if (callback) { + const cb = callback; + callback = null; + cb(); + } + } + const waitForDrain = () => new Promise((resolve, reject) => { + assert$19(callback === null); + if (socket[kError]) reject(socket[kError]); + else callback = resolve; + }); + socket.on("close", onDrain).on("drain", onDrain); + const writer = new AsyncWriter({ + abort, + socket, + request, + contentLength, + client, + expectsPayload, + header + }); + try { + for await (const chunk of body) { + if (socket[kError]) throw socket[kError]; + if (!writer.write(chunk)) await waitForDrain(); + } + writer.end(); + } catch (err) { + writer.destroy(err); + } finally { + socket.off("close", onDrain).off("drain", onDrain); + } + } + var AsyncWriter = class { + constructor({ abort, socket, request, contentLength, client, expectsPayload, header }) { + this.socket = socket; + this.request = request; + this.contentLength = contentLength; + this.client = client; + this.bytesWritten = 0; + this.expectsPayload = expectsPayload; + this.header = header; + this.abort = abort; + socket[kWriting] = true; + } + write(chunk) { + const { socket, request, contentLength, client, bytesWritten, expectsPayload, header } = this; + if (socket[kError]) throw socket[kError]; + if (socket.destroyed) return false; + const len = Buffer.byteLength(chunk); + if (!len) return true; + if (contentLength !== null && bytesWritten + len > contentLength) { + if (client[kStrictContentLength]) throw new RequestContentLengthMismatchError(); + process.emitWarning(new RequestContentLengthMismatchError()); + } + socket.cork(); + if (bytesWritten === 0) { + if (!expectsPayload && request.reset !== false) socket[kReset] = true; + if (contentLength === null) socket.write(`${header}transfer-encoding: chunked\r\n`, "latin1"); + else socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, "latin1"); + } + if (contentLength === null) socket.write(`\r\n${len.toString(16)}\r\n`, "latin1"); + this.bytesWritten += len; + const ret = socket.write(chunk); + socket.uncork(); + request.onBodySent(chunk); + if (!ret) { + if (socket[kParser].timeout && socket[kParser].timeoutType === TIMEOUT_HEADERS) { + // istanbul ignore else: only for jest + if (socket[kParser].timeout.refresh) socket[kParser].timeout.refresh(); + } + } + return ret; + } + end() { + const { socket, contentLength, client, bytesWritten, expectsPayload, header, request } = this; + request.onRequestSent(); + socket[kWriting] = false; + if (socket[kError]) throw socket[kError]; + if (socket.destroyed) return; + if (bytesWritten === 0) if (expectsPayload) socket.write(`${header}content-length: 0\r\n\r\n`, "latin1"); + else socket.write(`${header}\r\n`, "latin1"); + else if (contentLength === null) socket.write("\r\n0\r\n\r\n", "latin1"); + if (contentLength !== null && bytesWritten !== contentLength) if (client[kStrictContentLength]) throw new RequestContentLengthMismatchError(); + else process.emitWarning(new RequestContentLengthMismatchError()); + if (socket[kParser].timeout && socket[kParser].timeoutType === TIMEOUT_HEADERS) { + // istanbul ignore else: only for jest + if (socket[kParser].timeout.refresh) socket[kParser].timeout.refresh(); + } + client[kResume](); + } + destroy(err) { + const { socket, client, abort } = this; + socket[kWriting] = false; + if (err) { + assert$19(client[kRunning] <= 1, "pipeline should only contain this request"); + abort(err); + } + } + }; + module.exports = connectH1; +})); +//#endregion +//#region node_modules/undici/lib/dispatcher/client-h2.js +var require_client_h2 = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var assert$18 = __require("node:assert"); + var { pipeline: pipeline$2 } = __require("node:stream"); + var util = require_util$7(); + var { RequestContentLengthMismatchError, RequestAbortedError, SocketError, InformationalError } = require_errors(); + var { kUrl, kReset, kClient, kRunning, kPending, kQueue, kPendingIdx, kRunningIdx, kError, kSocket, kStrictContentLength, kOnError, kMaxConcurrentStreams, kHTTP2Session, kResume, kSize, kHTTPContext } = require_symbols$4(); + var kOpenStreams = Symbol("open streams"); + var extractBody; + var h2ExperimentalWarned = false; + /** @type {import('http2')} */ + var http2; + try { + http2 = __require("node:http2"); + } catch { + http2 = { constants: {} }; + } + var { constants: { HTTP2_HEADER_AUTHORITY, HTTP2_HEADER_METHOD, HTTP2_HEADER_PATH, HTTP2_HEADER_SCHEME, HTTP2_HEADER_CONTENT_LENGTH, HTTP2_HEADER_EXPECT, HTTP2_HEADER_STATUS } } = http2; + function parseH2Headers(headers) { + const result = []; + for (const [name, value] of Object.entries(headers)) if (Array.isArray(value)) for (const subvalue of value) result.push(Buffer.from(name), Buffer.from(subvalue)); + else result.push(Buffer.from(name), Buffer.from(value)); + return result; + } + async function connectH2(client, socket) { + client[kSocket] = socket; + if (!h2ExperimentalWarned) { + h2ExperimentalWarned = true; + process.emitWarning("H2 support is experimental, expect them to change at any time.", { code: "UNDICI-H2" }); + } + const session = http2.connect(client[kUrl], { + createConnection: () => socket, + peerMaxConcurrentStreams: client[kMaxConcurrentStreams] + }); + session[kOpenStreams] = 0; + session[kClient] = client; + session[kSocket] = socket; + util.addListener(session, "error", onHttp2SessionError); + util.addListener(session, "frameError", onHttp2FrameError); + util.addListener(session, "end", onHttp2SessionEnd); + util.addListener(session, "goaway", onHTTP2GoAway); + util.addListener(session, "close", function() { + const { [kClient]: client } = this; + const { [kSocket]: socket } = client; + const err = this[kSocket][kError] || this[kError] || new SocketError("closed", util.getSocketInfo(socket)); + client[kHTTP2Session] = null; + if (client.destroyed) { + assert$18(client[kPending] === 0); + const requests = client[kQueue].splice(client[kRunningIdx]); + for (let i = 0; i < requests.length; i++) { + const request = requests[i]; + util.errorRequest(client, request, err); + } + } + }); + session.unref(); + client[kHTTP2Session] = session; + socket[kHTTP2Session] = session; + util.addListener(socket, "error", function(err) { + assert$18(err.code !== "ERR_TLS_CERT_ALTNAME_INVALID"); + this[kError] = err; + this[kClient][kOnError](err); + }); + util.addListener(socket, "end", function() { + util.destroy(this, new SocketError("other side closed", util.getSocketInfo(this))); + }); + util.addListener(socket, "close", function() { + const err = this[kError] || new SocketError("closed", util.getSocketInfo(this)); + client[kSocket] = null; + if (this[kHTTP2Session] != null) this[kHTTP2Session].destroy(err); + client[kPendingIdx] = client[kRunningIdx]; + assert$18(client[kRunning] === 0); + client.emit("disconnect", client[kUrl], [client], err); + client[kResume](); + }); + let closed = false; + socket.on("close", () => { + closed = true; + }); + return { + version: "h2", + defaultPipelining: Infinity, + write(...args) { + return writeH2(client, ...args); + }, + resume() { + resumeH2(client); + }, + destroy(err, callback) { + if (closed) queueMicrotask(callback); + else socket.destroy(err).on("close", callback); + }, + get destroyed() { + return socket.destroyed; + }, + busy() { + return false; + } + }; + } + function resumeH2(client) { + const socket = client[kSocket]; + if (socket?.destroyed === false) if (client[kSize] === 0 && client[kMaxConcurrentStreams] === 0) { + socket.unref(); + client[kHTTP2Session].unref(); + } else { + socket.ref(); + client[kHTTP2Session].ref(); + } + } + function onHttp2SessionError(err) { + assert$18(err.code !== "ERR_TLS_CERT_ALTNAME_INVALID"); + this[kSocket][kError] = err; + this[kClient][kOnError](err); + } + function onHttp2FrameError(type, code, id) { + if (id === 0) { + const err = new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`); + this[kSocket][kError] = err; + this[kClient][kOnError](err); + } + } + function onHttp2SessionEnd() { + const err = new SocketError("other side closed", util.getSocketInfo(this[kSocket])); + this.destroy(err); + util.destroy(this[kSocket], err); + } + /** + * This is the root cause of #3011 + * We need to handle GOAWAY frames properly, and trigger the session close + * along with the socket right away + */ + function onHTTP2GoAway(code) { + const err = this[kError] || new SocketError(`HTTP/2: "GOAWAY" frame received with code ${code}`, util.getSocketInfo(this)); + const client = this[kClient]; + client[kSocket] = null; + client[kHTTPContext] = null; + if (this[kHTTP2Session] != null) { + this[kHTTP2Session].destroy(err); + this[kHTTP2Session] = null; + } + util.destroy(this[kSocket], err); + if (client[kRunningIdx] < client[kQueue].length) { + const request = client[kQueue][client[kRunningIdx]]; + client[kQueue][client[kRunningIdx]++] = null; + util.errorRequest(client, request, err); + client[kPendingIdx] = client[kRunningIdx]; + } + assert$18(client[kRunning] === 0); + client.emit("disconnect", client[kUrl], [client], err); + client[kResume](); + } + function shouldSendContentLength(method) { + return method !== "GET" && method !== "HEAD" && method !== "OPTIONS" && method !== "TRACE" && method !== "CONNECT"; + } + function writeH2(client, request) { + const session = client[kHTTP2Session]; + const { method, path, host, upgrade, expectContinue, signal, headers: reqHeaders } = request; + let { body } = request; + if (upgrade) { + util.errorRequest(client, request, /* @__PURE__ */ new Error("Upgrade not supported for H2")); + return false; + } + const headers = {}; + for (let n = 0; n < reqHeaders.length; n += 2) { + const key = reqHeaders[n + 0]; + const val = reqHeaders[n + 1]; + if (Array.isArray(val)) for (let i = 0; i < val.length; i++) if (headers[key]) headers[key] += `,${val[i]}`; + else headers[key] = val[i]; + else headers[key] = val; + } + /** @type {import('node:http2').ClientHttp2Stream} */ + let stream; + const { hostname, port } = client[kUrl]; + headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ""}`; + headers[HTTP2_HEADER_METHOD] = method; + const abort = (err) => { + if (request.aborted || request.completed) return; + err = err || new RequestAbortedError(); + util.errorRequest(client, request, err); + if (stream != null) util.destroy(stream, err); + util.destroy(body, err); + client[kQueue][client[kRunningIdx]++] = null; + client[kResume](); + }; + try { + request.onConnect(abort); + } catch (err) { + util.errorRequest(client, request, err); + } + if (request.aborted) return false; + if (method === "CONNECT") { + session.ref(); + stream = session.request(headers, { + endStream: false, + signal + }); + if (stream.id && !stream.pending) { + request.onUpgrade(null, null, stream); + ++session[kOpenStreams]; + client[kQueue][client[kRunningIdx]++] = null; + } else stream.once("ready", () => { + request.onUpgrade(null, null, stream); + ++session[kOpenStreams]; + client[kQueue][client[kRunningIdx]++] = null; + }); + stream.once("close", () => { + session[kOpenStreams] -= 1; + if (session[kOpenStreams] === 0) session.unref(); + }); + return true; + } + headers[HTTP2_HEADER_PATH] = path; + headers[HTTP2_HEADER_SCHEME] = "https"; + const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH"; + if (body && typeof body.read === "function") body.read(0); + let contentLength = util.bodyLength(body); + if (util.isFormDataLike(body)) { + extractBody ??= require_body().extractBody; + const [bodyStream, contentType] = extractBody(body); + headers["content-type"] = contentType; + body = bodyStream.stream; + contentLength = bodyStream.length; + } + if (contentLength == null) contentLength = request.contentLength; + if (contentLength === 0 || !expectsPayload) contentLength = null; + if (shouldSendContentLength(method) && contentLength > 0 && request.contentLength != null && request.contentLength !== contentLength) { + if (client[kStrictContentLength]) { + util.errorRequest(client, request, new RequestContentLengthMismatchError()); + return false; + } + process.emitWarning(new RequestContentLengthMismatchError()); + } + if (contentLength != null) { + assert$18(body, "no body must not have content length"); + headers[HTTP2_HEADER_CONTENT_LENGTH] = `${contentLength}`; + } + session.ref(); + const shouldEndStream = method === "GET" || method === "HEAD" || body === null; + if (expectContinue) { + headers[HTTP2_HEADER_EXPECT] = "100-continue"; + stream = session.request(headers, { + endStream: shouldEndStream, + signal + }); + stream.once("continue", writeBodyH2); + } else { + stream = session.request(headers, { + endStream: shouldEndStream, + signal + }); + writeBodyH2(); + } + ++session[kOpenStreams]; + stream.once("response", (headers) => { + const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers; + request.onResponseStarted(); + if (request.aborted) { + const err = new RequestAbortedError(); + util.errorRequest(client, request, err); + util.destroy(stream, err); + return; + } + if (request.onHeaders(Number(statusCode), parseH2Headers(realHeaders), stream.resume.bind(stream), "") === false) stream.pause(); + stream.on("data", (chunk) => { + if (request.onData(chunk) === false) stream.pause(); + }); + }); + stream.once("end", () => { + if (stream.state?.state == null || stream.state.state < 6) request.onComplete([]); + if (session[kOpenStreams] === 0) session.unref(); + abort(new InformationalError("HTTP/2: stream half-closed (remote)")); + client[kQueue][client[kRunningIdx]++] = null; + client[kPendingIdx] = client[kRunningIdx]; + client[kResume](); + }); + stream.once("close", () => { + session[kOpenStreams] -= 1; + if (session[kOpenStreams] === 0) session.unref(); + }); + stream.once("error", function(err) { + abort(err); + }); + stream.once("frameError", (type, code) => { + abort(new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`)); + }); + return true; + function writeBodyH2() { + /* istanbul ignore else: assertion */ + if (!body || contentLength === 0) writeBuffer(abort, stream, null, client, request, client[kSocket], contentLength, expectsPayload); + else if (util.isBuffer(body)) writeBuffer(abort, stream, body, client, request, client[kSocket], contentLength, expectsPayload); + else if (util.isBlobLike(body)) if (typeof body.stream === "function") writeIterable(abort, stream, body.stream(), client, request, client[kSocket], contentLength, expectsPayload); + else writeBlob(abort, stream, body, client, request, client[kSocket], contentLength, expectsPayload); + else if (util.isStream(body)) writeStream(abort, client[kSocket], expectsPayload, stream, body, client, request, contentLength); + else if (util.isIterable(body)) writeIterable(abort, stream, body, client, request, client[kSocket], contentLength, expectsPayload); + else assert$18(false); + } + } + function writeBuffer(abort, h2stream, body, client, request, socket, contentLength, expectsPayload) { + try { + if (body != null && util.isBuffer(body)) { + assert$18(contentLength === body.byteLength, "buffer body must have content length"); + h2stream.cork(); + h2stream.write(body); + h2stream.uncork(); + h2stream.end(); + request.onBodySent(body); + } + if (!expectsPayload) socket[kReset] = true; + request.onRequestSent(); + client[kResume](); + } catch (error) { + abort(error); + } + } + function writeStream(abort, socket, expectsPayload, h2stream, body, client, request, contentLength) { + assert$18(contentLength !== 0 || client[kRunning] === 0, "stream body cannot be pipelined"); + const pipe = pipeline$2(body, h2stream, (err) => { + if (err) { + util.destroy(pipe, err); + abort(err); + } else { + util.removeAllListeners(pipe); + request.onRequestSent(); + if (!expectsPayload) socket[kReset] = true; + client[kResume](); + } + }); + util.addListener(pipe, "data", onPipeData); + function onPipeData(chunk) { + request.onBodySent(chunk); + } + } + async function writeBlob(abort, h2stream, body, client, request, socket, contentLength, expectsPayload) { + assert$18(contentLength === body.size, "blob body must have content length"); + try { + if (contentLength != null && contentLength !== body.size) throw new RequestContentLengthMismatchError(); + const buffer = Buffer.from(await body.arrayBuffer()); + h2stream.cork(); + h2stream.write(buffer); + h2stream.uncork(); + h2stream.end(); + request.onBodySent(buffer); + request.onRequestSent(); + if (!expectsPayload) socket[kReset] = true; + client[kResume](); + } catch (err) { + abort(err); + } + } + async function writeIterable(abort, h2stream, body, client, request, socket, contentLength, expectsPayload) { + assert$18(contentLength !== 0 || client[kRunning] === 0, "iterator body cannot be pipelined"); + let callback = null; + function onDrain() { + if (callback) { + const cb = callback; + callback = null; + cb(); + } + } + const waitForDrain = () => new Promise((resolve, reject) => { + assert$18(callback === null); + if (socket[kError]) reject(socket[kError]); + else callback = resolve; + }); + h2stream.on("close", onDrain).on("drain", onDrain); + try { + for await (const chunk of body) { + if (socket[kError]) throw socket[kError]; + const res = h2stream.write(chunk); + request.onBodySent(chunk); + if (!res) await waitForDrain(); + } + h2stream.end(); + request.onRequestSent(); + if (!expectsPayload) socket[kReset] = true; + client[kResume](); + } catch (err) { + abort(err); + } finally { + h2stream.off("close", onDrain).off("drain", onDrain); + } + } + module.exports = connectH2; +})); +//#endregion +//#region node_modules/undici/lib/handler/redirect-handler.js +var require_redirect_handler = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var util = require_util$7(); + var { kBodyUsed } = require_symbols$4(); + var assert$17 = __require("node:assert"); + var { InvalidArgumentError } = require_errors(); + var EE$1 = __require("node:events"); + var redirectableStatusCodes = [ + 300, + 301, + 302, + 303, + 307, + 308 + ]; + var kBody = Symbol("body"); + var BodyAsyncIterable = class { + constructor(body) { + this[kBody] = body; + this[kBodyUsed] = false; + } + async *[Symbol.asyncIterator]() { + assert$17(!this[kBodyUsed], "disturbed"); + this[kBodyUsed] = true; + yield* this[kBody]; + } + }; + var RedirectHandler = class { + constructor(dispatch, maxRedirections, opts, handler) { + if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) throw new InvalidArgumentError("maxRedirections must be a positive number"); + util.validateHandler(handler, opts.method, opts.upgrade); + this.dispatch = dispatch; + this.location = null; + this.abort = null; + this.opts = { + ...opts, + maxRedirections: 0 + }; + this.maxRedirections = maxRedirections; + this.handler = handler; + this.history = []; + this.redirectionLimitReached = false; + if (util.isStream(this.opts.body)) { + if (util.bodyLength(this.opts.body) === 0) this.opts.body.on("data", function() { + assert$17(false); + }); + if (typeof this.opts.body.readableDidRead !== "boolean") { + this.opts.body[kBodyUsed] = false; + EE$1.prototype.on.call(this.opts.body, "data", function() { + this[kBodyUsed] = true; + }); + } + } else if (this.opts.body && typeof this.opts.body.pipeTo === "function") this.opts.body = new BodyAsyncIterable(this.opts.body); + else if (this.opts.body && typeof this.opts.body !== "string" && !ArrayBuffer.isView(this.opts.body) && util.isIterable(this.opts.body)) this.opts.body = new BodyAsyncIterable(this.opts.body); + } + onConnect(abort) { + this.abort = abort; + this.handler.onConnect(abort, { history: this.history }); + } + onUpgrade(statusCode, headers, socket) { + this.handler.onUpgrade(statusCode, headers, socket); + } + onError(error) { + this.handler.onError(error); + } + onHeaders(statusCode, headers, resume, statusText) { + this.location = this.history.length >= this.maxRedirections || util.isDisturbed(this.opts.body) ? null : parseLocation(statusCode, headers); + if (this.opts.throwOnMaxRedirect && this.history.length >= this.maxRedirections) { + if (this.request) this.request.abort(/* @__PURE__ */ new Error("max redirects")); + this.redirectionLimitReached = true; + this.abort(/* @__PURE__ */ new Error("max redirects")); + return; + } + if (this.opts.origin) this.history.push(new URL(this.opts.path, this.opts.origin)); + if (!this.location) return this.handler.onHeaders(statusCode, headers, resume, statusText); + const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin))); + const path = search ? `${pathname}${search}` : pathname; + this.opts.headers = cleanRequestHeaders(this.opts.headers, statusCode === 303, this.opts.origin !== origin); + this.opts.path = path; + this.opts.origin = origin; + this.opts.maxRedirections = 0; + this.opts.query = null; + if (statusCode === 303 && this.opts.method !== "HEAD") { + this.opts.method = "GET"; + this.opts.body = null; + } + } + onData(chunk) { + if (this.location) {} else return this.handler.onData(chunk); + } + onComplete(trailers) { + if (this.location) { + this.location = null; + this.abort = null; + this.dispatch(this.opts, this); + } else this.handler.onComplete(trailers); + } + onBodySent(chunk) { + if (this.handler.onBodySent) this.handler.onBodySent(chunk); + } + }; + function parseLocation(statusCode, headers) { + if (redirectableStatusCodes.indexOf(statusCode) === -1) return null; + for (let i = 0; i < headers.length; i += 2) if (headers[i].length === 8 && util.headerNameToString(headers[i]) === "location") return headers[i + 1]; + } + function shouldRemoveHeader(header, removeContent, unknownOrigin) { + if (header.length === 4) return util.headerNameToString(header) === "host"; + if (removeContent && util.headerNameToString(header).startsWith("content-")) return true; + if (unknownOrigin && (header.length === 13 || header.length === 6 || header.length === 19)) { + const name = util.headerNameToString(header); + return name === "authorization" || name === "cookie" || name === "proxy-authorization"; + } + return false; + } + function cleanRequestHeaders(headers, removeContent, unknownOrigin) { + const ret = []; + if (Array.isArray(headers)) { + for (let i = 0; i < headers.length; i += 2) if (!shouldRemoveHeader(headers[i], removeContent, unknownOrigin)) ret.push(headers[i], headers[i + 1]); + } else if (headers && typeof headers === "object") { + for (const key of Object.keys(headers)) if (!shouldRemoveHeader(key, removeContent, unknownOrigin)) ret.push(key, headers[key]); + } else assert$17(headers == null, "headers must be an object or an array"); + return ret; + } + module.exports = RedirectHandler; +})); +//#endregion +//#region node_modules/undici/lib/interceptor/redirect-interceptor.js +var require_redirect_interceptor = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var RedirectHandler = require_redirect_handler(); + function createRedirectInterceptor({ maxRedirections: defaultMaxRedirections }) { + return (dispatch) => { + return function Intercept(opts, handler) { + const { maxRedirections = defaultMaxRedirections } = opts; + if (!maxRedirections) return dispatch(opts, handler); + const redirectHandler = new RedirectHandler(dispatch, maxRedirections, opts, handler); + opts = { + ...opts, + maxRedirections: 0 + }; + return dispatch(opts, redirectHandler); + }; + }; + } + module.exports = createRedirectInterceptor; +})); +//#endregion +//#region node_modules/undici/lib/dispatcher/client.js +var require_client = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var assert$16 = __require("node:assert"); + var net = __require("node:net"); + var http$2 = __require("node:http"); + var util = require_util$7(); + var { channels } = require_diagnostics(); + var Request = require_request$1(); + var DispatcherBase = require_dispatcher_base(); + var { InvalidArgumentError, InformationalError, ClientDestroyedError } = require_errors(); + var buildConnector = require_connect(); + var { kUrl, kServerName, kClient, kBusy, kConnect, kResuming, kRunning, kPending, kSize, kQueue, kConnected, kConnecting, kNeedDrain, kKeepAliveDefaultTimeout, kHostHeader, kPendingIdx, kRunningIdx, kError, kPipelining, kKeepAliveTimeoutValue, kMaxHeadersSize, kKeepAliveMaxTimeout, kKeepAliveTimeoutThreshold, kHeadersTimeout, kBodyTimeout, kStrictContentLength, kConnector, kMaxRedirections, kMaxRequests, kCounter, kClose, kDestroy, kDispatch, kInterceptors, kLocalAddress, kMaxResponseSize, kOnError, kHTTPContext, kMaxConcurrentStreams, kResume } = require_symbols$4(); + var connectH1 = require_client_h1(); + var connectH2 = require_client_h2(); + var deprecatedInterceptorWarned = false; + var kClosedResolve = Symbol("kClosedResolve"); + var noop = () => {}; + function getPipelining(client) { + return client[kPipelining] ?? client[kHTTPContext]?.defaultPipelining ?? 1; + } + /** + * @type {import('../../types/client.js').default} + */ + var Client = class extends DispatcherBase { + /** + * + * @param {string|URL} url + * @param {import('../../types/client.js').Client.Options} options + */ + constructor(url, { interceptors, maxHeaderSize, headersTimeout, socketTimeout, requestTimeout, connectTimeout, bodyTimeout, idleTimeout, keepAlive, keepAliveTimeout, maxKeepAliveTimeout, keepAliveMaxTimeout, keepAliveTimeoutThreshold, socketPath, pipelining, tls, strictContentLength, maxCachedSessions, maxRedirections, connect, maxRequestsPerClient, localAddress, maxResponseSize, autoSelectFamily, autoSelectFamilyAttemptTimeout, maxConcurrentStreams, allowH2, webSocket } = {}) { + super({ webSocket }); + if (keepAlive !== void 0) throw new InvalidArgumentError("unsupported keepAlive, use pipelining=0 instead"); + if (socketTimeout !== void 0) throw new InvalidArgumentError("unsupported socketTimeout, use headersTimeout & bodyTimeout instead"); + if (requestTimeout !== void 0) throw new InvalidArgumentError("unsupported requestTimeout, use headersTimeout & bodyTimeout instead"); + if (idleTimeout !== void 0) throw new InvalidArgumentError("unsupported idleTimeout, use keepAliveTimeout instead"); + if (maxKeepAliveTimeout !== void 0) throw new InvalidArgumentError("unsupported maxKeepAliveTimeout, use keepAliveMaxTimeout instead"); + if (maxHeaderSize != null && !Number.isFinite(maxHeaderSize)) throw new InvalidArgumentError("invalid maxHeaderSize"); + if (socketPath != null && typeof socketPath !== "string") throw new InvalidArgumentError("invalid socketPath"); + if (connectTimeout != null && (!Number.isFinite(connectTimeout) || connectTimeout < 0)) throw new InvalidArgumentError("invalid connectTimeout"); + if (keepAliveTimeout != null && (!Number.isFinite(keepAliveTimeout) || keepAliveTimeout <= 0)) throw new InvalidArgumentError("invalid keepAliveTimeout"); + if (keepAliveMaxTimeout != null && (!Number.isFinite(keepAliveMaxTimeout) || keepAliveMaxTimeout <= 0)) throw new InvalidArgumentError("invalid keepAliveMaxTimeout"); + if (keepAliveTimeoutThreshold != null && !Number.isFinite(keepAliveTimeoutThreshold)) throw new InvalidArgumentError("invalid keepAliveTimeoutThreshold"); + if (headersTimeout != null && (!Number.isInteger(headersTimeout) || headersTimeout < 0)) throw new InvalidArgumentError("headersTimeout must be a positive integer or zero"); + if (bodyTimeout != null && (!Number.isInteger(bodyTimeout) || bodyTimeout < 0)) throw new InvalidArgumentError("bodyTimeout must be a positive integer or zero"); + if (connect != null && typeof connect !== "function" && typeof connect !== "object") throw new InvalidArgumentError("connect must be a function or an object"); + if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) throw new InvalidArgumentError("maxRedirections must be a positive number"); + if (maxRequestsPerClient != null && (!Number.isInteger(maxRequestsPerClient) || maxRequestsPerClient < 0)) throw new InvalidArgumentError("maxRequestsPerClient must be a positive number"); + if (localAddress != null && (typeof localAddress !== "string" || net.isIP(localAddress) === 0)) throw new InvalidArgumentError("localAddress must be valid string IP address"); + if (maxResponseSize != null && (!Number.isInteger(maxResponseSize) || maxResponseSize < -1)) throw new InvalidArgumentError("maxResponseSize must be a positive number"); + if (autoSelectFamilyAttemptTimeout != null && (!Number.isInteger(autoSelectFamilyAttemptTimeout) || autoSelectFamilyAttemptTimeout < -1)) throw new InvalidArgumentError("autoSelectFamilyAttemptTimeout must be a positive number"); + if (allowH2 != null && typeof allowH2 !== "boolean") throw new InvalidArgumentError("allowH2 must be a valid boolean value"); + if (maxConcurrentStreams != null && (typeof maxConcurrentStreams !== "number" || maxConcurrentStreams < 1)) throw new InvalidArgumentError("maxConcurrentStreams must be a positive integer, greater than 0"); + if (typeof connect !== "function") connect = buildConnector({ + ...tls, + maxCachedSessions, + allowH2, + socketPath, + timeout: connectTimeout, + ...autoSelectFamily ? { + autoSelectFamily, + autoSelectFamilyAttemptTimeout + } : void 0, + ...connect + }); + if (interceptors?.Client && Array.isArray(interceptors.Client)) { + this[kInterceptors] = interceptors.Client; + if (!deprecatedInterceptorWarned) { + deprecatedInterceptorWarned = true; + process.emitWarning("Client.Options#interceptor is deprecated. Use Dispatcher#compose instead.", { code: "UNDICI-CLIENT-INTERCEPTOR-DEPRECATED" }); + } + } else this[kInterceptors] = [createRedirectInterceptor({ maxRedirections })]; + this[kUrl] = util.parseOrigin(url); + this[kConnector] = connect; + this[kPipelining] = pipelining != null ? pipelining : 1; + this[kMaxHeadersSize] = maxHeaderSize || http$2.maxHeaderSize; + this[kKeepAliveDefaultTimeout] = keepAliveTimeout == null ? 4e3 : keepAliveTimeout; + this[kKeepAliveMaxTimeout] = keepAliveMaxTimeout == null ? 6e5 : keepAliveMaxTimeout; + this[kKeepAliveTimeoutThreshold] = keepAliveTimeoutThreshold == null ? 2e3 : keepAliveTimeoutThreshold; + this[kKeepAliveTimeoutValue] = this[kKeepAliveDefaultTimeout]; + this[kServerName] = null; + this[kLocalAddress] = localAddress != null ? localAddress : null; + this[kResuming] = 0; + this[kNeedDrain] = 0; + this[kHostHeader] = `host: ${this[kUrl].hostname}${this[kUrl].port ? `:${this[kUrl].port}` : ""}\r\n`; + this[kBodyTimeout] = bodyTimeout != null ? bodyTimeout : 3e5; + this[kHeadersTimeout] = headersTimeout != null ? headersTimeout : 3e5; + this[kStrictContentLength] = strictContentLength == null ? true : strictContentLength; + this[kMaxRedirections] = maxRedirections; + this[kMaxRequests] = maxRequestsPerClient; + this[kClosedResolve] = null; + this[kMaxResponseSize] = maxResponseSize > -1 ? maxResponseSize : -1; + this[kMaxConcurrentStreams] = maxConcurrentStreams != null ? maxConcurrentStreams : 100; + this[kHTTPContext] = null; + this[kQueue] = []; + this[kRunningIdx] = 0; + this[kPendingIdx] = 0; + this[kResume] = (sync) => resume(this, sync); + this[kOnError] = (err) => onError(this, err); + } + get pipelining() { + return this[kPipelining]; + } + set pipelining(value) { + this[kPipelining] = value; + this[kResume](true); + } + get [kPending]() { + return this[kQueue].length - this[kPendingIdx]; + } + get [kRunning]() { + return this[kPendingIdx] - this[kRunningIdx]; + } + get [kSize]() { + return this[kQueue].length - this[kRunningIdx]; + } + get [kConnected]() { + return !!this[kHTTPContext] && !this[kConnecting] && !this[kHTTPContext].destroyed; + } + get [kBusy]() { + return Boolean(this[kHTTPContext]?.busy(null) || this[kSize] >= (getPipelining(this) || 1) || this[kPending] > 0); + } + /* istanbul ignore: only used for test */ + [kConnect](cb) { + connect(this); + this.once("connect", cb); + } + [kDispatch](opts, handler) { + const request = new Request(opts.origin || this[kUrl].origin, opts, handler); + this[kQueue].push(request); + if (this[kResuming]) {} else if (util.bodyLength(request.body) == null && util.isIterable(request.body)) { + this[kResuming] = 1; + queueMicrotask(() => resume(this)); + } else this[kResume](true); + if (this[kResuming] && this[kNeedDrain] !== 2 && this[kBusy]) this[kNeedDrain] = 2; + return this[kNeedDrain] < 2; + } + async [kClose]() { + return new Promise((resolve) => { + if (this[kSize]) this[kClosedResolve] = resolve; + else resolve(null); + }); + } + async [kDestroy](err) { + return new Promise((resolve) => { + const requests = this[kQueue].splice(this[kPendingIdx]); + for (let i = 0; i < requests.length; i++) { + const request = requests[i]; + util.errorRequest(this, request, err); + } + const callback = () => { + if (this[kClosedResolve]) { + this[kClosedResolve](); + this[kClosedResolve] = null; + } + resolve(null); + }; + if (this[kHTTPContext]) { + this[kHTTPContext].destroy(err, callback); + this[kHTTPContext] = null; + } else queueMicrotask(callback); + this[kResume](); + }); + } + }; + var createRedirectInterceptor = require_redirect_interceptor(); + function onError(client, err) { + if (client[kRunning] === 0 && err.code !== "UND_ERR_INFO" && err.code !== "UND_ERR_SOCKET") { + assert$16(client[kPendingIdx] === client[kRunningIdx]); + const requests = client[kQueue].splice(client[kRunningIdx]); + for (let i = 0; i < requests.length; i++) { + const request = requests[i]; + util.errorRequest(client, request, err); + } + assert$16(client[kSize] === 0); + } + } + /** + * @param {Client} client + * @returns + */ + async function connect(client) { + assert$16(!client[kConnecting]); + assert$16(!client[kHTTPContext]); + let { host, hostname, protocol, port } = client[kUrl]; + if (hostname[0] === "[") { + const idx = hostname.indexOf("]"); + assert$16(idx !== -1); + const ip = hostname.substring(1, idx); + assert$16(net.isIP(ip)); + hostname = ip; + } + client[kConnecting] = true; + if (channels.beforeConnect.hasSubscribers) channels.beforeConnect.publish({ + connectParams: { + host, + hostname, + protocol, + port, + version: client[kHTTPContext]?.version, + servername: client[kServerName], + localAddress: client[kLocalAddress] + }, + connector: client[kConnector] + }); + try { + const socket = await new Promise((resolve, reject) => { + client[kConnector]({ + host, + hostname, + protocol, + port, + servername: client[kServerName], + localAddress: client[kLocalAddress] + }, (err, socket) => { + if (err) reject(err); + else resolve(socket); + }); + }); + if (client.destroyed) { + util.destroy(socket.on("error", noop), new ClientDestroyedError()); + return; + } + assert$16(socket); + try { + client[kHTTPContext] = socket.alpnProtocol === "h2" ? await connectH2(client, socket) : await connectH1(client, socket); + } catch (err) { + socket.destroy().on("error", noop); + throw err; + } + client[kConnecting] = false; + socket[kCounter] = 0; + socket[kMaxRequests] = client[kMaxRequests]; + socket[kClient] = client; + socket[kError] = null; + if (channels.connected.hasSubscribers) channels.connected.publish({ + connectParams: { + host, + hostname, + protocol, + port, + version: client[kHTTPContext]?.version, + servername: client[kServerName], + localAddress: client[kLocalAddress] + }, + connector: client[kConnector], + socket + }); + client.emit("connect", client[kUrl], [client]); + } catch (err) { + if (client.destroyed) return; + client[kConnecting] = false; + if (channels.connectError.hasSubscribers) channels.connectError.publish({ + connectParams: { + host, + hostname, + protocol, + port, + version: client[kHTTPContext]?.version, + servername: client[kServerName], + localAddress: client[kLocalAddress] + }, + connector: client[kConnector], + error: err + }); + if (err.code === "ERR_TLS_CERT_ALTNAME_INVALID") { + assert$16(client[kRunning] === 0); + while (client[kPending] > 0 && client[kQueue][client[kPendingIdx]].servername === client[kServerName]) { + const request = client[kQueue][client[kPendingIdx]++]; + util.errorRequest(client, request, err); + } + } else onError(client, err); + client.emit("connectionError", client[kUrl], [client], err); + } + client[kResume](); + } + function emitDrain(client) { + client[kNeedDrain] = 0; + client.emit("drain", client[kUrl], [client]); + } + function resume(client, sync) { + if (client[kResuming] === 2) return; + client[kResuming] = 2; + _resume(client, sync); + client[kResuming] = 0; + if (client[kRunningIdx] > 256) { + client[kQueue].splice(0, client[kRunningIdx]); + client[kPendingIdx] -= client[kRunningIdx]; + client[kRunningIdx] = 0; + } + } + function _resume(client, sync) { + while (true) { + if (client.destroyed) { + assert$16(client[kPending] === 0); + return; + } + if (client[kClosedResolve] && !client[kSize]) { + client[kClosedResolve](); + client[kClosedResolve] = null; + return; + } + if (client[kHTTPContext]) client[kHTTPContext].resume(); + if (client[kBusy]) client[kNeedDrain] = 2; + else if (client[kNeedDrain] === 2) { + if (sync) { + client[kNeedDrain] = 1; + queueMicrotask(() => emitDrain(client)); + } else emitDrain(client); + continue; + } + if (client[kPending] === 0) return; + if (client[kRunning] >= (getPipelining(client) || 1)) return; + const request = client[kQueue][client[kPendingIdx]]; + if (client[kUrl].protocol === "https:" && client[kServerName] !== request.servername) { + if (client[kRunning] > 0) return; + client[kServerName] = request.servername; + client[kHTTPContext]?.destroy(new InformationalError("servername changed"), () => { + client[kHTTPContext] = null; + resume(client); + }); + } + if (client[kConnecting]) return; + if (!client[kHTTPContext]) { + connect(client); + return; + } + if (client[kHTTPContext].destroyed) return; + if (client[kHTTPContext].busy(request)) return; + if (!request.aborted && client[kHTTPContext].write(request)) client[kPendingIdx]++; + else client[kQueue].splice(client[kPendingIdx], 1); + } + } + module.exports = Client; +})); +//#endregion +//#region node_modules/undici/lib/dispatcher/fixed-queue.js +var require_fixed_queue = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var kSize = 2048; + var kMask = kSize - 1; + var FixedCircularBuffer = class { + constructor() { + this.bottom = 0; + this.top = 0; + this.list = new Array(kSize); + this.next = null; + } + isEmpty() { + return this.top === this.bottom; + } + isFull() { + return (this.top + 1 & kMask) === this.bottom; + } + push(data) { + this.list[this.top] = data; + this.top = this.top + 1 & kMask; + } + shift() { + const nextItem = this.list[this.bottom]; + if (nextItem === void 0) return null; + this.list[this.bottom] = void 0; + this.bottom = this.bottom + 1 & kMask; + return nextItem; + } + }; + module.exports = class FixedQueue { + constructor() { + this.head = this.tail = new FixedCircularBuffer(); + } + isEmpty() { + return this.head.isEmpty(); + } + push(data) { + if (this.head.isFull()) this.head = this.head.next = new FixedCircularBuffer(); + this.head.push(data); + } + shift() { + const tail = this.tail; + const next = tail.shift(); + if (tail.isEmpty() && tail.next !== null) this.tail = tail.next; + return next; + } + }; +})); +//#endregion +//#region node_modules/undici/lib/dispatcher/pool-stats.js +var require_pool_stats = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { kFree, kConnected, kPending, kQueued, kRunning, kSize } = require_symbols$4(); + var kPool = Symbol("pool"); + var PoolStats = class { + constructor(pool) { + this[kPool] = pool; + } + get connected() { + return this[kPool][kConnected]; + } + get free() { + return this[kPool][kFree]; + } + get pending() { + return this[kPool][kPending]; + } + get queued() { + return this[kPool][kQueued]; + } + get running() { + return this[kPool][kRunning]; + } + get size() { + return this[kPool][kSize]; + } + }; + module.exports = PoolStats; +})); +//#endregion +//#region node_modules/undici/lib/dispatcher/pool-base.js +var require_pool_base = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var DispatcherBase = require_dispatcher_base(); + var FixedQueue = require_fixed_queue(); + var { kConnected, kSize, kRunning, kPending, kQueued, kBusy, kFree, kUrl, kClose, kDestroy, kDispatch } = require_symbols$4(); + var PoolStats = require_pool_stats(); + var kClients = Symbol("clients"); + var kNeedDrain = Symbol("needDrain"); + var kQueue = Symbol("queue"); + var kClosedResolve = Symbol("closed resolve"); + var kOnDrain = Symbol("onDrain"); + var kOnConnect = Symbol("onConnect"); + var kOnDisconnect = Symbol("onDisconnect"); + var kOnConnectionError = Symbol("onConnectionError"); + var kGetDispatcher = Symbol("get dispatcher"); + var kAddClient = Symbol("add client"); + var kRemoveClient = Symbol("remove client"); + var kStats = Symbol("stats"); + var PoolBase = class extends DispatcherBase { + constructor(opts) { + super(opts); + this[kQueue] = new FixedQueue(); + this[kClients] = []; + this[kQueued] = 0; + const pool = this; + this[kOnDrain] = function onDrain(origin, targets) { + const queue = pool[kQueue]; + let needDrain = false; + while (!needDrain) { + const item = queue.shift(); + if (!item) break; + pool[kQueued]--; + needDrain = !this.dispatch(item.opts, item.handler); + } + this[kNeedDrain] = needDrain; + if (!this[kNeedDrain] && pool[kNeedDrain]) { + pool[kNeedDrain] = false; + pool.emit("drain", origin, [pool, ...targets]); + } + if (pool[kClosedResolve] && queue.isEmpty()) Promise.all(pool[kClients].map((c) => c.close())).then(pool[kClosedResolve]); + }; + this[kOnConnect] = (origin, targets) => { + pool.emit("connect", origin, [pool, ...targets]); + }; + this[kOnDisconnect] = (origin, targets, err) => { + pool.emit("disconnect", origin, [pool, ...targets], err); + }; + this[kOnConnectionError] = (origin, targets, err) => { + pool.emit("connectionError", origin, [pool, ...targets], err); + }; + this[kStats] = new PoolStats(this); + } + get [kBusy]() { + return this[kNeedDrain]; + } + get [kConnected]() { + return this[kClients].filter((client) => client[kConnected]).length; + } + get [kFree]() { + return this[kClients].filter((client) => client[kConnected] && !client[kNeedDrain]).length; + } + get [kPending]() { + let ret = this[kQueued]; + for (const { [kPending]: pending } of this[kClients]) ret += pending; + return ret; + } + get [kRunning]() { + let ret = 0; + for (const { [kRunning]: running } of this[kClients]) ret += running; + return ret; + } + get [kSize]() { + let ret = this[kQueued]; + for (const { [kSize]: size } of this[kClients]) ret += size; + return ret; + } + get stats() { + return this[kStats]; + } + async [kClose]() { + if (this[kQueue].isEmpty()) await Promise.all(this[kClients].map((c) => c.close())); + else await new Promise((resolve) => { + this[kClosedResolve] = resolve; + }); + } + async [kDestroy](err) { + while (true) { + const item = this[kQueue].shift(); + if (!item) break; + item.handler.onError(err); + } + await Promise.all(this[kClients].map((c) => c.destroy(err))); + } + [kDispatch](opts, handler) { + const dispatcher = this[kGetDispatcher](); + if (!dispatcher) { + this[kNeedDrain] = true; + this[kQueue].push({ + opts, + handler + }); + this[kQueued]++; + } else if (!dispatcher.dispatch(opts, handler)) { + dispatcher[kNeedDrain] = true; + this[kNeedDrain] = !this[kGetDispatcher](); + } + return !this[kNeedDrain]; + } + [kAddClient](client) { + client.on("drain", this[kOnDrain]).on("connect", this[kOnConnect]).on("disconnect", this[kOnDisconnect]).on("connectionError", this[kOnConnectionError]); + this[kClients].push(client); + if (this[kNeedDrain]) queueMicrotask(() => { + if (this[kNeedDrain]) this[kOnDrain](client[kUrl], [this, client]); + }); + return this; + } + [kRemoveClient](client) { + client.close(() => { + const idx = this[kClients].indexOf(client); + if (idx !== -1) this[kClients].splice(idx, 1); + }); + this[kNeedDrain] = this[kClients].some((dispatcher) => !dispatcher[kNeedDrain] && dispatcher.closed !== true && dispatcher.destroyed !== true); + } + }; + module.exports = { + PoolBase, + kClients, + kNeedDrain, + kAddClient, + kRemoveClient, + kGetDispatcher + }; +})); +//#endregion +//#region node_modules/undici/lib/dispatcher/pool.js +var require_pool = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { PoolBase, kClients, kNeedDrain, kAddClient, kGetDispatcher } = require_pool_base(); + var Client = require_client(); + var { InvalidArgumentError } = require_errors(); + var util = require_util$7(); + var { kUrl, kInterceptors } = require_symbols$4(); + var buildConnector = require_connect(); + var kOptions = Symbol("options"); + var kConnections = Symbol("connections"); + var kFactory = Symbol("factory"); + function defaultFactory(origin, opts) { + return new Client(origin, opts); + } + var Pool = class extends PoolBase { + constructor(origin, { connections, factory = defaultFactory, connect, connectTimeout, tls, maxCachedSessions, socketPath, autoSelectFamily, autoSelectFamilyAttemptTimeout, allowH2, ...options } = {}) { + if (connections != null && (!Number.isFinite(connections) || connections < 0)) throw new InvalidArgumentError("invalid connections"); + if (typeof factory !== "function") throw new InvalidArgumentError("factory must be a function."); + if (connect != null && typeof connect !== "function" && typeof connect !== "object") throw new InvalidArgumentError("connect must be a function or an object"); + if (typeof connect !== "function") connect = buildConnector({ + ...tls, + maxCachedSessions, + allowH2, + socketPath, + timeout: connectTimeout, + ...autoSelectFamily ? { + autoSelectFamily, + autoSelectFamilyAttemptTimeout + } : void 0, + ...connect + }); + super(options); + this[kInterceptors] = options.interceptors?.Pool && Array.isArray(options.interceptors.Pool) ? options.interceptors.Pool : []; + this[kConnections] = connections || null; + this[kUrl] = util.parseOrigin(origin); + this[kOptions] = { + ...util.deepClone(options), + connect, + allowH2 + }; + this[kOptions].interceptors = options.interceptors ? { ...options.interceptors } : void 0; + this[kFactory] = factory; + this.on("connectionError", (origin, targets, error) => { + for (const target of targets) { + const idx = this[kClients].indexOf(target); + if (idx !== -1) this[kClients].splice(idx, 1); + } + }); + } + [kGetDispatcher]() { + for (const client of this[kClients]) if (!client[kNeedDrain]) return client; + if (!this[kConnections] || this[kClients].length < this[kConnections]) { + const dispatcher = this[kFactory](this[kUrl], this[kOptions]); + this[kAddClient](dispatcher); + return dispatcher; + } + } + }; + module.exports = Pool; +})); +//#endregion +//#region node_modules/undici/lib/dispatcher/balanced-pool.js +var require_balanced_pool = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { BalancedPoolMissingUpstreamError, InvalidArgumentError } = require_errors(); + var { PoolBase, kClients, kNeedDrain, kAddClient, kRemoveClient, kGetDispatcher } = require_pool_base(); + var Pool = require_pool(); + var { kUrl, kInterceptors } = require_symbols$4(); + var { parseOrigin } = require_util$7(); + var kFactory = Symbol("factory"); + var kOptions = Symbol("options"); + var kGreatestCommonDivisor = Symbol("kGreatestCommonDivisor"); + var kCurrentWeight = Symbol("kCurrentWeight"); + var kIndex = Symbol("kIndex"); + var kWeight = Symbol("kWeight"); + var kMaxWeightPerServer = Symbol("kMaxWeightPerServer"); + var kErrorPenalty = Symbol("kErrorPenalty"); + /** + * Calculate the greatest common divisor of two numbers by + * using the Euclidean algorithm. + * + * @param {number} a + * @param {number} b + * @returns {number} + */ + function getGreatestCommonDivisor(a, b) { + if (a === 0) return b; + while (b !== 0) { + const t = b; + b = a % b; + a = t; + } + return a; + } + function defaultFactory(origin, opts) { + return new Pool(origin, opts); + } + var BalancedPool = class extends PoolBase { + constructor(upstreams = [], { factory = defaultFactory, ...opts } = {}) { + super(); + this[kOptions] = opts; + this[kIndex] = -1; + this[kCurrentWeight] = 0; + this[kMaxWeightPerServer] = this[kOptions].maxWeightPerServer || 100; + this[kErrorPenalty] = this[kOptions].errorPenalty || 15; + if (!Array.isArray(upstreams)) upstreams = [upstreams]; + if (typeof factory !== "function") throw new InvalidArgumentError("factory must be a function."); + this[kInterceptors] = opts.interceptors?.BalancedPool && Array.isArray(opts.interceptors.BalancedPool) ? opts.interceptors.BalancedPool : []; + this[kFactory] = factory; + for (const upstream of upstreams) this.addUpstream(upstream); + this._updateBalancedPoolStats(); + } + addUpstream(upstream) { + const upstreamOrigin = parseOrigin(upstream).origin; + if (this[kClients].find((pool) => pool[kUrl].origin === upstreamOrigin && pool.closed !== true && pool.destroyed !== true)) return this; + const pool = this[kFactory](upstreamOrigin, Object.assign({}, this[kOptions])); + this[kAddClient](pool); + pool.on("connect", () => { + pool[kWeight] = Math.min(this[kMaxWeightPerServer], pool[kWeight] + this[kErrorPenalty]); + }); + pool.on("connectionError", () => { + pool[kWeight] = Math.max(1, pool[kWeight] - this[kErrorPenalty]); + this._updateBalancedPoolStats(); + }); + pool.on("disconnect", (...args) => { + const err = args[2]; + if (err && err.code === "UND_ERR_SOCKET") { + pool[kWeight] = Math.max(1, pool[kWeight] - this[kErrorPenalty]); + this._updateBalancedPoolStats(); + } + }); + for (const client of this[kClients]) client[kWeight] = this[kMaxWeightPerServer]; + this._updateBalancedPoolStats(); + return this; + } + _updateBalancedPoolStats() { + let result = 0; + for (let i = 0; i < this[kClients].length; i++) result = getGreatestCommonDivisor(this[kClients][i][kWeight], result); + this[kGreatestCommonDivisor] = result; + } + removeUpstream(upstream) { + const upstreamOrigin = parseOrigin(upstream).origin; + const pool = this[kClients].find((pool) => pool[kUrl].origin === upstreamOrigin && pool.closed !== true && pool.destroyed !== true); + if (pool) this[kRemoveClient](pool); + return this; + } + get upstreams() { + return this[kClients].filter((dispatcher) => dispatcher.closed !== true && dispatcher.destroyed !== true).map((p) => p[kUrl].origin); + } + [kGetDispatcher]() { + if (this[kClients].length === 0) throw new BalancedPoolMissingUpstreamError(); + if (!this[kClients].find((dispatcher) => !dispatcher[kNeedDrain] && dispatcher.closed !== true && dispatcher.destroyed !== true)) return; + if (this[kClients].map((pool) => pool[kNeedDrain]).reduce((a, b) => a && b, true)) return; + let counter = 0; + let maxWeightIndex = this[kClients].findIndex((pool) => !pool[kNeedDrain]); + while (counter++ < this[kClients].length) { + this[kIndex] = (this[kIndex] + 1) % this[kClients].length; + const pool = this[kClients][this[kIndex]]; + if (pool[kWeight] > this[kClients][maxWeightIndex][kWeight] && !pool[kNeedDrain]) maxWeightIndex = this[kIndex]; + if (this[kIndex] === 0) { + this[kCurrentWeight] = this[kCurrentWeight] - this[kGreatestCommonDivisor]; + if (this[kCurrentWeight] <= 0) this[kCurrentWeight] = this[kMaxWeightPerServer]; + } + if (pool[kWeight] >= this[kCurrentWeight] && !pool[kNeedDrain]) return pool; + } + this[kCurrentWeight] = this[kClients][maxWeightIndex][kWeight]; + this[kIndex] = maxWeightIndex; + return this[kClients][maxWeightIndex]; + } + }; + module.exports = BalancedPool; +})); +//#endregion +//#region node_modules/undici/lib/dispatcher/agent.js +var require_agent = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { InvalidArgumentError } = require_errors(); + var { kClients, kRunning, kClose, kDestroy, kDispatch, kInterceptors } = require_symbols$4(); + var DispatcherBase = require_dispatcher_base(); + var Pool = require_pool(); + var Client = require_client(); + var util = require_util$7(); + var createRedirectInterceptor = require_redirect_interceptor(); + var kOnConnect = Symbol("onConnect"); + var kOnDisconnect = Symbol("onDisconnect"); + var kOnConnectionError = Symbol("onConnectionError"); + var kMaxRedirections = Symbol("maxRedirections"); + var kOnDrain = Symbol("onDrain"); + var kFactory = Symbol("factory"); + var kOptions = Symbol("options"); + function defaultFactory(origin, opts) { + return opts && opts.connections === 1 ? new Client(origin, opts) : new Pool(origin, opts); + } + var Agent = class extends DispatcherBase { + constructor({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) { + if (typeof factory !== "function") throw new InvalidArgumentError("factory must be a function."); + if (connect != null && typeof connect !== "function" && typeof connect !== "object") throw new InvalidArgumentError("connect must be a function or an object"); + if (!Number.isInteger(maxRedirections) || maxRedirections < 0) throw new InvalidArgumentError("maxRedirections must be a positive number"); + super(options); + if (connect && typeof connect !== "function") connect = { ...connect }; + this[kInterceptors] = options.interceptors?.Agent && Array.isArray(options.interceptors.Agent) ? options.interceptors.Agent : [createRedirectInterceptor({ maxRedirections })]; + this[kOptions] = { + ...util.deepClone(options), + connect + }; + this[kOptions].interceptors = options.interceptors ? { ...options.interceptors } : void 0; + this[kMaxRedirections] = maxRedirections; + this[kFactory] = factory; + this[kClients] = /* @__PURE__ */ new Map(); + this[kOnDrain] = (origin, targets) => { + this.emit("drain", origin, [this, ...targets]); + }; + this[kOnConnect] = (origin, targets) => { + this.emit("connect", origin, [this, ...targets]); + }; + this[kOnDisconnect] = (origin, targets, err) => { + this.emit("disconnect", origin, [this, ...targets], err); + }; + this[kOnConnectionError] = (origin, targets, err) => { + this.emit("connectionError", origin, [this, ...targets], err); + }; + } + get [kRunning]() { + let ret = 0; + for (const client of this[kClients].values()) ret += client[kRunning]; + return ret; + } + [kDispatch](opts, handler) { + let key; + if (opts.origin && (typeof opts.origin === "string" || opts.origin instanceof URL)) key = String(opts.origin); + else throw new InvalidArgumentError("opts.origin must be a non-empty string or URL."); + let dispatcher = this[kClients].get(key); + if (!dispatcher) { + dispatcher = this[kFactory](opts.origin, this[kOptions]).on("drain", this[kOnDrain]).on("connect", this[kOnConnect]).on("disconnect", this[kOnDisconnect]).on("connectionError", this[kOnConnectionError]); + this[kClients].set(key, dispatcher); + } + return dispatcher.dispatch(opts, handler); + } + async [kClose]() { + const closePromises = []; + for (const client of this[kClients].values()) closePromises.push(client.close()); + this[kClients].clear(); + await Promise.all(closePromises); + } + async [kDestroy](err) { + const destroyPromises = []; + for (const client of this[kClients].values()) destroyPromises.push(client.destroy(err)); + this[kClients].clear(); + await Promise.all(destroyPromises); + } + }; + module.exports = Agent; +})); +//#endregion +//#region node_modules/undici/lib/dispatcher/proxy-agent.js +var require_proxy_agent = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { kProxy, kClose, kDestroy, kDispatch, kInterceptors } = require_symbols$4(); + var { URL: URL$1 } = __require("node:url"); + var Agent = require_agent(); + var Pool = require_pool(); + var DispatcherBase = require_dispatcher_base(); + var { InvalidArgumentError, RequestAbortedError, SecureProxyConnectionError } = require_errors(); + var buildConnector = require_connect(); + var Client = require_client(); + var kAgent = Symbol("proxy agent"); + var kClient = Symbol("proxy client"); + var kProxyHeaders = Symbol("proxy headers"); + var kRequestTls = Symbol("request tls settings"); + var kProxyTls = Symbol("proxy tls settings"); + var kConnectEndpoint = Symbol("connect endpoint function"); + var kTunnelProxy = Symbol("tunnel proxy"); + function defaultProtocolPort(protocol) { + return protocol === "https:" ? 443 : 80; + } + function defaultFactory(origin, opts) { + return new Pool(origin, opts); + } + var noop = () => {}; + function defaultAgentFactory(origin, opts) { + if (opts.connections === 1) return new Client(origin, opts); + return new Pool(origin, opts); + } + var Http1ProxyWrapper = class extends DispatcherBase { + #client; + constructor(proxyUrl, { headers = {}, connect, factory }) { + super(); + if (!proxyUrl) throw new InvalidArgumentError("Proxy URL is mandatory"); + this[kProxyHeaders] = headers; + if (factory) this.#client = factory(proxyUrl, { connect }); + else this.#client = new Client(proxyUrl, { connect }); + } + [kDispatch](opts, handler) { + const onHeaders = handler.onHeaders; + handler.onHeaders = function(statusCode, data, resume) { + if (statusCode === 407) { + if (typeof handler.onError === "function") handler.onError(new InvalidArgumentError("Proxy Authentication Required (407)")); + return; + } + if (onHeaders) onHeaders.call(this, statusCode, data, resume); + }; + const { origin, path = "/", headers = {} } = opts; + opts.path = origin + path; + if (!("host" in headers) && !("Host" in headers)) { + const { host } = new URL$1(origin); + headers.host = host; + } + opts.headers = { + ...this[kProxyHeaders], + ...headers + }; + return this.#client[kDispatch](opts, handler); + } + async [kClose]() { + return this.#client.close(); + } + async [kDestroy](err) { + return this.#client.destroy(err); + } + }; + var ProxyAgent = class extends DispatcherBase { + constructor(opts) { + super(); + if (!opts || typeof opts === "object" && !(opts instanceof URL$1) && !opts.uri) throw new InvalidArgumentError("Proxy uri is mandatory"); + const { clientFactory = defaultFactory } = opts; + if (typeof clientFactory !== "function") throw new InvalidArgumentError("Proxy opts.clientFactory must be a function."); + const { proxyTunnel = true } = opts; + const url = this.#getUrl(opts); + const { href, origin, port, protocol, username, password, hostname: proxyHostname } = url; + this[kProxy] = { + uri: href, + protocol + }; + this[kInterceptors] = opts.interceptors?.ProxyAgent && Array.isArray(opts.interceptors.ProxyAgent) ? opts.interceptors.ProxyAgent : []; + this[kRequestTls] = opts.requestTls; + this[kProxyTls] = opts.proxyTls; + this[kProxyHeaders] = opts.headers || {}; + this[kTunnelProxy] = proxyTunnel; + if (opts.auth && opts.token) throw new InvalidArgumentError("opts.auth cannot be used in combination with opts.token"); + else if (opts.auth) this[kProxyHeaders]["proxy-authorization"] = `Basic ${opts.auth}`; + else if (opts.token) this[kProxyHeaders]["proxy-authorization"] = opts.token; + else if (username && password) this[kProxyHeaders]["proxy-authorization"] = `Basic ${Buffer.from(`${decodeURIComponent(username)}:${decodeURIComponent(password)}`).toString("base64")}`; + const connect = buildConnector({ ...opts.proxyTls }); + this[kConnectEndpoint] = buildConnector({ ...opts.requestTls }); + const agentFactory = opts.factory || defaultAgentFactory; + const factory = (origin, options) => { + const { protocol } = new URL$1(origin); + if (!this[kTunnelProxy] && protocol === "http:" && this[kProxy].protocol === "http:") return new Http1ProxyWrapper(this[kProxy].uri, { + headers: this[kProxyHeaders], + connect, + factory: agentFactory + }); + return agentFactory(origin, options); + }; + this[kClient] = clientFactory(url, { connect }); + this[kAgent] = new Agent({ + ...opts, + factory, + connect: async (opts, callback) => { + let requestedPath = opts.host; + if (!opts.port) requestedPath += `:${defaultProtocolPort(opts.protocol)}`; + try { + const { socket, statusCode } = await this[kClient].connect({ + origin, + port, + path: requestedPath, + signal: opts.signal, + headers: { + ...this[kProxyHeaders], + host: opts.host + }, + servername: this[kProxyTls]?.servername || proxyHostname + }); + if (statusCode !== 200) { + socket.on("error", noop).destroy(); + callback(new RequestAbortedError(`Proxy response (${statusCode}) !== 200 when HTTP Tunneling`)); + } + if (opts.protocol !== "https:") { + callback(null, socket); + return; + } + let servername; + if (this[kRequestTls]) servername = this[kRequestTls].servername; + else servername = opts.servername; + this[kConnectEndpoint]({ + ...opts, + servername, + httpSocket: socket + }, callback); + } catch (err) { + if (err.code === "ERR_TLS_CERT_ALTNAME_INVALID") callback(new SecureProxyConnectionError(err)); + else callback(err); + } + } + }); + } + dispatch(opts, handler) { + const headers = buildHeaders(opts.headers); + throwIfProxyAuthIsSent(headers); + if (headers && !("host" in headers) && !("Host" in headers)) { + const { host } = new URL$1(opts.origin); + headers.host = host; + } + return this[kAgent].dispatch({ + ...opts, + headers + }, handler); + } + /** + * @param {import('../types/proxy-agent').ProxyAgent.Options | string | URL} opts + * @returns {URL} + */ + #getUrl(opts) { + if (typeof opts === "string") return new URL$1(opts); + else if (opts instanceof URL$1) return opts; + else return new URL$1(opts.uri); + } + async [kClose]() { + await this[kAgent].close(); + await this[kClient].close(); + } + async [kDestroy]() { + await this[kAgent].destroy(); + await this[kClient].destroy(); + } + }; + /** + * @param {string[] | Record} headers + * @returns {Record} + */ + function buildHeaders(headers) { + if (Array.isArray(headers)) { + /** @type {Record} */ + const headersPair = {}; + for (let i = 0; i < headers.length; i += 2) headersPair[headers[i]] = headers[i + 1]; + return headersPair; + } + return headers; + } + /** + * @param {Record} headers + * + * Previous versions of ProxyAgent suggests the Proxy-Authorization in request headers + * Nevertheless, it was changed and to avoid a security vulnerability by end users + * this check was created. + * It should be removed in the next major version for performance reasons + */ + function throwIfProxyAuthIsSent(headers) { + if (headers && Object.keys(headers).find((key) => key.toLowerCase() === "proxy-authorization")) throw new InvalidArgumentError("Proxy-Authorization should be sent in ProxyAgent constructor"); + } + module.exports = ProxyAgent; +})); +//#endregion +//#region node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +var require_env_http_proxy_agent = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var DispatcherBase = require_dispatcher_base(); + var { kClose, kDestroy, kClosed, kDestroyed, kDispatch, kNoProxyAgent, kHttpProxyAgent, kHttpsProxyAgent } = require_symbols$4(); + var ProxyAgent = require_proxy_agent(); + var Agent = require_agent(); + var DEFAULT_PORTS = { + "http:": 80, + "https:": 443 + }; + var experimentalWarned = false; + var EnvHttpProxyAgent = class extends DispatcherBase { + #noProxyValue = null; + #noProxyEntries = null; + #opts = null; + constructor(opts = {}) { + super(); + this.#opts = opts; + if (!experimentalWarned) { + experimentalWarned = true; + process.emitWarning("EnvHttpProxyAgent is experimental, expect them to change at any time.", { code: "UNDICI-EHPA" }); + } + const { httpProxy, httpsProxy, noProxy, ...agentOpts } = opts; + this[kNoProxyAgent] = new Agent(agentOpts); + const HTTP_PROXY = httpProxy ?? process.env.http_proxy ?? process.env.HTTP_PROXY; + if (HTTP_PROXY) this[kHttpProxyAgent] = new ProxyAgent({ + ...agentOpts, + uri: HTTP_PROXY + }); + else this[kHttpProxyAgent] = this[kNoProxyAgent]; + const HTTPS_PROXY = httpsProxy ?? process.env.https_proxy ?? process.env.HTTPS_PROXY; + if (HTTPS_PROXY) this[kHttpsProxyAgent] = new ProxyAgent({ + ...agentOpts, + uri: HTTPS_PROXY + }); + else this[kHttpsProxyAgent] = this[kHttpProxyAgent]; + this.#parseNoProxy(); + } + [kDispatch](opts, handler) { + const url = new URL(opts.origin); + return this.#getProxyAgentForUrl(url).dispatch(opts, handler); + } + async [kClose]() { + await this[kNoProxyAgent].close(); + if (!this[kHttpProxyAgent][kClosed]) await this[kHttpProxyAgent].close(); + if (!this[kHttpsProxyAgent][kClosed]) await this[kHttpsProxyAgent].close(); + } + async [kDestroy](err) { + await this[kNoProxyAgent].destroy(err); + if (!this[kHttpProxyAgent][kDestroyed]) await this[kHttpProxyAgent].destroy(err); + if (!this[kHttpsProxyAgent][kDestroyed]) await this[kHttpsProxyAgent].destroy(err); + } + #getProxyAgentForUrl(url) { + let { protocol, host: hostname, port } = url; + hostname = hostname.replace(/:\d*$/, "").toLowerCase(); + port = Number.parseInt(port, 10) || DEFAULT_PORTS[protocol] || 0; + if (!this.#shouldProxy(hostname, port)) return this[kNoProxyAgent]; + if (protocol === "https:") return this[kHttpsProxyAgent]; + return this[kHttpProxyAgent]; + } + #shouldProxy(hostname, port) { + if (this.#noProxyChanged) this.#parseNoProxy(); + if (this.#noProxyEntries.length === 0) return true; + if (this.#noProxyValue === "*") return false; + for (let i = 0; i < this.#noProxyEntries.length; i++) { + const entry = this.#noProxyEntries[i]; + if (entry.port && entry.port !== port) continue; + if (!/^[.*]/.test(entry.hostname)) { + if (hostname === entry.hostname) return false; + } else if (hostname.endsWith(entry.hostname.replace(/^\*/, ""))) return false; + } + return true; + } + #parseNoProxy() { + const noProxyValue = this.#opts.noProxy ?? this.#noProxyEnv; + const noProxySplit = noProxyValue.split(/[,\s]/); + const noProxyEntries = []; + for (let i = 0; i < noProxySplit.length; i++) { + const entry = noProxySplit[i]; + if (!entry) continue; + const parsed = entry.match(/^(.+):(\d+)$/); + noProxyEntries.push({ + hostname: (parsed ? parsed[1] : entry).toLowerCase(), + port: parsed ? Number.parseInt(parsed[2], 10) : 0 + }); + } + this.#noProxyValue = noProxyValue; + this.#noProxyEntries = noProxyEntries; + } + get #noProxyChanged() { + if (this.#opts.noProxy !== void 0) return false; + return this.#noProxyValue !== this.#noProxyEnv; + } + get #noProxyEnv() { + return process.env.no_proxy ?? process.env.NO_PROXY ?? ""; + } + }; + module.exports = EnvHttpProxyAgent; +})); +//#endregion +//#region node_modules/undici/lib/handler/retry-handler.js +var require_retry_handler = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var assert$15 = __require("node:assert"); + var { kRetryHandlerDefaultRetry } = require_symbols$4(); + var { RequestRetryError } = require_errors(); + var { isDisturbed, parseHeaders, parseRangeHeader, wrapRequestBody } = require_util$7(); + function calculateRetryAfterHeader(retryAfter) { + const current = Date.now(); + return new Date(retryAfter).getTime() - current; + } + module.exports = class RetryHandler { + constructor(opts, handlers) { + const { retryOptions, ...dispatchOpts } = opts; + const { retry: retryFn, maxRetries, maxTimeout, minTimeout, timeoutFactor, methods, errorCodes, retryAfter, statusCodes } = retryOptions ?? {}; + this.dispatch = handlers.dispatch; + this.handler = handlers.handler; + this.opts = { + ...dispatchOpts, + body: wrapRequestBody(opts.body) + }; + this.abort = null; + this.aborted = false; + this.retryOpts = { + retry: retryFn ?? RetryHandler[kRetryHandlerDefaultRetry], + retryAfter: retryAfter ?? true, + maxTimeout: maxTimeout ?? 30 * 1e3, + minTimeout: minTimeout ?? 500, + timeoutFactor: timeoutFactor ?? 2, + maxRetries: maxRetries ?? 5, + methods: methods ?? [ + "GET", + "HEAD", + "OPTIONS", + "PUT", + "DELETE", + "TRACE" + ], + statusCodes: statusCodes ?? [ + 500, + 502, + 503, + 504, + 429 + ], + errorCodes: errorCodes ?? [ + "ECONNRESET", + "ECONNREFUSED", + "ENOTFOUND", + "ENETDOWN", + "ENETUNREACH", + "EHOSTDOWN", + "EHOSTUNREACH", + "EPIPE", + "UND_ERR_SOCKET" + ] + }; + this.retryCount = 0; + this.retryCountCheckpoint = 0; + this.start = 0; + this.end = null; + this.etag = null; + this.resume = null; + this.handler.onConnect((reason) => { + this.aborted = true; + if (this.abort) this.abort(reason); + else this.reason = reason; + }); + } + onRequestSent() { + if (this.handler.onRequestSent) this.handler.onRequestSent(); + } + onUpgrade(statusCode, headers, socket) { + if (this.handler.onUpgrade) this.handler.onUpgrade(statusCode, headers, socket); + } + onConnect(abort) { + if (this.aborted) abort(this.reason); + else this.abort = abort; + } + onBodySent(chunk) { + if (this.handler.onBodySent) return this.handler.onBodySent(chunk); + } + static [kRetryHandlerDefaultRetry](err, { state, opts }, cb) { + const { statusCode, code, headers } = err; + const { method, retryOptions } = opts; + const { maxRetries, minTimeout, maxTimeout, timeoutFactor, statusCodes, errorCodes, methods } = retryOptions; + const { counter } = state; + if (code && code !== "UND_ERR_REQ_RETRY" && !errorCodes.includes(code)) { + cb(err); + return; + } + if (Array.isArray(methods) && !methods.includes(method)) { + cb(err); + return; + } + if (statusCode != null && Array.isArray(statusCodes) && !statusCodes.includes(statusCode)) { + cb(err); + return; + } + if (counter > maxRetries) { + cb(err); + return; + } + let retryAfterHeader = headers?.["retry-after"]; + if (retryAfterHeader) { + retryAfterHeader = Number(retryAfterHeader); + retryAfterHeader = Number.isNaN(retryAfterHeader) ? calculateRetryAfterHeader(retryAfterHeader) : retryAfterHeader * 1e3; + } + const retryTimeout = retryAfterHeader > 0 ? Math.min(retryAfterHeader, maxTimeout) : Math.min(minTimeout * timeoutFactor ** (counter - 1), maxTimeout); + setTimeout(() => cb(null), retryTimeout); + } + onHeaders(statusCode, rawHeaders, resume, statusMessage) { + const headers = parseHeaders(rawHeaders); + this.retryCount += 1; + if (statusCode >= 300) if (this.retryOpts.statusCodes.includes(statusCode) === false) return this.handler.onHeaders(statusCode, rawHeaders, resume, statusMessage); + else { + this.abort(new RequestRetryError("Request failed", statusCode, { + headers, + data: { count: this.retryCount } + })); + return false; + } + if (this.resume != null) { + this.resume = null; + if (statusCode !== 206 && (this.start > 0 || statusCode !== 200)) { + this.abort(new RequestRetryError("server does not support the range header and the payload was partially consumed", statusCode, { + headers, + data: { count: this.retryCount } + })); + return false; + } + const contentRange = parseRangeHeader(headers["content-range"]); + if (!contentRange) { + this.abort(new RequestRetryError("Content-Range mismatch", statusCode, { + headers, + data: { count: this.retryCount } + })); + return false; + } + if (this.etag != null && this.etag !== headers.etag) { + this.abort(new RequestRetryError("ETag mismatch", statusCode, { + headers, + data: { count: this.retryCount } + })); + return false; + } + const { start, size, end = size - 1 } = contentRange; + assert$15(this.start === start, "content-range mismatch"); + assert$15(this.end == null || this.end === end, "content-range mismatch"); + this.resume = resume; + return true; + } + if (this.end == null) { + if (statusCode === 206) { + const range = parseRangeHeader(headers["content-range"]); + if (range == null) return this.handler.onHeaders(statusCode, rawHeaders, resume, statusMessage); + const { start, size, end = size - 1 } = range; + assert$15(start != null && Number.isFinite(start), "content-range mismatch"); + assert$15(end != null && Number.isFinite(end), "invalid content-length"); + this.start = start; + this.end = end; + } + if (this.end == null) { + const contentLength = headers["content-length"]; + this.end = contentLength != null ? Number(contentLength) - 1 : null; + } + assert$15(Number.isFinite(this.start)); + assert$15(this.end == null || Number.isFinite(this.end), "invalid content-length"); + this.resume = resume; + this.etag = headers.etag != null ? headers.etag : null; + if (this.etag != null && this.etag.startsWith("W/")) this.etag = null; + return this.handler.onHeaders(statusCode, rawHeaders, resume, statusMessage); + } + const err = new RequestRetryError("Request failed", statusCode, { + headers, + data: { count: this.retryCount } + }); + this.abort(err); + return false; + } + onData(chunk) { + this.start += chunk.length; + return this.handler.onData(chunk); + } + onComplete(rawTrailers) { + this.retryCount = 0; + return this.handler.onComplete(rawTrailers); + } + onError(err) { + if (this.aborted || isDisturbed(this.opts.body)) return this.handler.onError(err); + if (this.retryCount - this.retryCountCheckpoint > 0) this.retryCount = this.retryCountCheckpoint + (this.retryCount - this.retryCountCheckpoint); + else this.retryCount += 1; + this.retryOpts.retry(err, { + state: { counter: this.retryCount }, + opts: { + retryOptions: this.retryOpts, + ...this.opts + } + }, onRetry.bind(this)); + function onRetry(err) { + if (err != null || this.aborted || isDisturbed(this.opts.body)) return this.handler.onError(err); + if (this.start !== 0) { + const headers = { range: `bytes=${this.start}-${this.end ?? ""}` }; + if (this.etag != null) headers["if-match"] = this.etag; + this.opts = { + ...this.opts, + headers: { + ...this.opts.headers, + ...headers + } + }; + } + try { + this.retryCountCheckpoint = this.retryCount; + this.dispatch(this.opts, this); + } catch (err) { + this.handler.onError(err); + } + } + } + }; +})); +//#endregion +//#region node_modules/undici/lib/dispatcher/retry-agent.js +var require_retry_agent = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var Dispatcher = require_dispatcher(); + var RetryHandler = require_retry_handler(); + var RetryAgent = class extends Dispatcher { + #agent = null; + #options = null; + constructor(agent, options = {}) { + super(options); + this.#agent = agent; + this.#options = options; + } + dispatch(opts, handler) { + const retry = new RetryHandler({ + ...opts, + retryOptions: this.#options + }, { + dispatch: this.#agent.dispatch.bind(this.#agent), + handler + }); + return this.#agent.dispatch(opts, retry); + } + close() { + return this.#agent.close(); + } + destroy() { + return this.#agent.destroy(); + } + }; + module.exports = RetryAgent; +})); +//#endregion +//#region node_modules/undici/lib/api/readable.js +var require_readable = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var assert$14 = __require("node:assert"); + var { Readable: Readable$2 } = __require("node:stream"); + var { RequestAbortedError, NotSupportedError, InvalidArgumentError, AbortError } = require_errors(); + var util = require_util$7(); + var { ReadableStreamFrom } = require_util$7(); + var kConsume = Symbol("kConsume"); + var kReading = Symbol("kReading"); + var kBody = Symbol("kBody"); + var kAbort = Symbol("kAbort"); + var kContentType = Symbol("kContentType"); + var kContentLength = Symbol("kContentLength"); + var noop = () => {}; + var BodyReadable = class extends Readable$2 { + constructor({ resume, abort, contentType = "", contentLength, highWaterMark = 64 * 1024 }) { + super({ + autoDestroy: true, + read: resume, + highWaterMark + }); + this._readableState.dataEmitted = false; + this[kAbort] = abort; + this[kConsume] = null; + this[kBody] = null; + this[kContentType] = contentType; + this[kContentLength] = contentLength; + this[kReading] = false; + } + destroy(err) { + if (!err && !this._readableState.endEmitted) err = new RequestAbortedError(); + if (err) this[kAbort](); + return super.destroy(err); + } + _destroy(err, callback) { + if (!this[kReading]) setImmediate(() => { + callback(err); + }); + else callback(err); + } + on(ev, ...args) { + if (ev === "data" || ev === "readable") this[kReading] = true; + return super.on(ev, ...args); + } + addListener(ev, ...args) { + return this.on(ev, ...args); + } + off(ev, ...args) { + const ret = super.off(ev, ...args); + if (ev === "data" || ev === "readable") this[kReading] = this.listenerCount("data") > 0 || this.listenerCount("readable") > 0; + return ret; + } + removeListener(ev, ...args) { + return this.off(ev, ...args); + } + push(chunk) { + if (this[kConsume] && chunk !== null) { + consumePush(this[kConsume], chunk); + return this[kReading] ? super.push(chunk) : true; + } + return super.push(chunk); + } + async text() { + return consume(this, "text"); + } + async json() { + return consume(this, "json"); + } + async blob() { + return consume(this, "blob"); + } + async bytes() { + return consume(this, "bytes"); + } + async arrayBuffer() { + return consume(this, "arrayBuffer"); + } + async formData() { + throw new NotSupportedError(); + } + get bodyUsed() { + return util.isDisturbed(this); + } + get body() { + if (!this[kBody]) { + this[kBody] = ReadableStreamFrom(this); + if (this[kConsume]) { + this[kBody].getReader(); + assert$14(this[kBody].locked); + } + } + return this[kBody]; + } + async dump(opts) { + let limit = Number.isFinite(opts?.limit) ? opts.limit : 128 * 1024; + const signal = opts?.signal; + if (signal != null && (typeof signal !== "object" || !("aborted" in signal))) throw new InvalidArgumentError("signal must be an AbortSignal"); + signal?.throwIfAborted(); + if (this._readableState.closeEmitted) return null; + return await new Promise((resolve, reject) => { + if (this[kContentLength] > limit) this.destroy(new AbortError()); + const onAbort = () => { + this.destroy(signal.reason ?? new AbortError()); + }; + signal?.addEventListener("abort", onAbort); + this.on("close", function() { + signal?.removeEventListener("abort", onAbort); + if (signal?.aborted) reject(signal.reason ?? new AbortError()); + else resolve(null); + }).on("error", noop).on("data", function(chunk) { + limit -= chunk.length; + if (limit <= 0) this.destroy(); + }).resume(); + }); + } + }; + function isLocked(self) { + return self[kBody] && self[kBody].locked === true || self[kConsume]; + } + function isUnusable(self) { + return util.isDisturbed(self) || isLocked(self); + } + async function consume(stream, type) { + assert$14(!stream[kConsume]); + return new Promise((resolve, reject) => { + if (isUnusable(stream)) { + const rState = stream._readableState; + if (rState.destroyed && rState.closeEmitted === false) stream.on("error", (err) => { + reject(err); + }).on("close", () => { + reject(/* @__PURE__ */ new TypeError("unusable")); + }); + else reject(rState.errored ?? /* @__PURE__ */ new TypeError("unusable")); + } else queueMicrotask(() => { + stream[kConsume] = { + type, + stream, + resolve, + reject, + length: 0, + body: [] + }; + stream.on("error", function(err) { + consumeFinish(this[kConsume], err); + }).on("close", function() { + if (this[kConsume].body !== null) consumeFinish(this[kConsume], new RequestAbortedError()); + }); + consumeStart(stream[kConsume]); + }); + }); + } + function consumeStart(consume) { + if (consume.body === null) return; + const { _readableState: state } = consume.stream; + if (state.bufferIndex) { + const start = state.bufferIndex; + const end = state.buffer.length; + for (let n = start; n < end; n++) consumePush(consume, state.buffer[n]); + } else for (const chunk of state.buffer) consumePush(consume, chunk); + if (state.endEmitted) consumeEnd(this[kConsume]); + else consume.stream.on("end", function() { + consumeEnd(this[kConsume]); + }); + consume.stream.resume(); + while (consume.stream.read() != null); + } + /** + * @param {Buffer[]} chunks + * @param {number} length + */ + function chunksDecode(chunks, length) { + if (chunks.length === 0 || length === 0) return ""; + const buffer = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks, length); + const bufferLength = buffer.length; + const start = bufferLength > 2 && buffer[0] === 239 && buffer[1] === 187 && buffer[2] === 191 ? 3 : 0; + return buffer.utf8Slice(start, bufferLength); + } + /** + * @param {Buffer[]} chunks + * @param {number} length + * @returns {Uint8Array} + */ + function chunksConcat(chunks, length) { + if (chunks.length === 0 || length === 0) return /* @__PURE__ */ new Uint8Array(0); + if (chunks.length === 1) return new Uint8Array(chunks[0]); + const buffer = new Uint8Array(Buffer.allocUnsafeSlow(length).buffer); + let offset = 0; + for (let i = 0; i < chunks.length; ++i) { + const chunk = chunks[i]; + buffer.set(chunk, offset); + offset += chunk.length; + } + return buffer; + } + function consumeEnd(consume) { + const { type, body, resolve, stream, length } = consume; + try { + if (type === "text") resolve(chunksDecode(body, length)); + else if (type === "json") resolve(JSON.parse(chunksDecode(body, length))); + else if (type === "arrayBuffer") resolve(chunksConcat(body, length).buffer); + else if (type === "blob") resolve(new Blob(body, { type: stream[kContentType] })); + else if (type === "bytes") resolve(chunksConcat(body, length)); + consumeFinish(consume); + } catch (err) { + stream.destroy(err); + } + } + function consumePush(consume, chunk) { + consume.length += chunk.length; + consume.body.push(chunk); + } + function consumeFinish(consume, err) { + if (consume.body === null) return; + if (err) consume.reject(err); + else consume.resolve(); + consume.type = null; + consume.stream = null; + consume.resolve = null; + consume.reject = null; + consume.length = 0; + consume.body = null; + } + module.exports = { + Readable: BodyReadable, + chunksDecode + }; +})); +//#endregion +//#region node_modules/undici/lib/api/util.js +var require_util$5 = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var assert$13 = __require("node:assert"); + var { ResponseStatusCodeError } = require_errors(); + var { chunksDecode } = require_readable(); + var CHUNK_LIMIT = 128 * 1024; + async function getResolveErrorBodyCallback({ callback, body, contentType, statusCode, statusMessage, headers }) { + assert$13(body); + let chunks = []; + let length = 0; + try { + for await (const chunk of body) { + chunks.push(chunk); + length += chunk.length; + if (length > CHUNK_LIMIT) { + chunks = []; + length = 0; + break; + } + } + } catch { + chunks = []; + length = 0; + } + const message = `Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ""}`; + if (statusCode === 204 || !contentType || !length) { + queueMicrotask(() => callback(new ResponseStatusCodeError(message, statusCode, headers))); + return; + } + const stackTraceLimit = Error.stackTraceLimit; + Error.stackTraceLimit = 0; + let payload; + try { + if (isContentTypeApplicationJson(contentType)) payload = JSON.parse(chunksDecode(chunks, length)); + else if (isContentTypeText(contentType)) payload = chunksDecode(chunks, length); + } catch {} finally { + Error.stackTraceLimit = stackTraceLimit; + } + queueMicrotask(() => callback(new ResponseStatusCodeError(message, statusCode, headers, payload))); + } + var isContentTypeApplicationJson = (contentType) => { + return contentType.length > 15 && contentType[11] === "/" && contentType[0] === "a" && contentType[1] === "p" && contentType[2] === "p" && contentType[3] === "l" && contentType[4] === "i" && contentType[5] === "c" && contentType[6] === "a" && contentType[7] === "t" && contentType[8] === "i" && contentType[9] === "o" && contentType[10] === "n" && contentType[12] === "j" && contentType[13] === "s" && contentType[14] === "o" && contentType[15] === "n"; + }; + var isContentTypeText = (contentType) => { + return contentType.length > 4 && contentType[4] === "/" && contentType[0] === "t" && contentType[1] === "e" && contentType[2] === "x" && contentType[3] === "t"; + }; + module.exports = { + getResolveErrorBodyCallback, + isContentTypeApplicationJson, + isContentTypeText + }; +})); +//#endregion +//#region node_modules/undici/lib/api/api-request.js +var require_api_request = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var assert$12 = __require("node:assert"); + var { Readable } = require_readable(); + var { InvalidArgumentError, RequestAbortedError } = require_errors(); + var util = require_util$7(); + var { getResolveErrorBodyCallback } = require_util$5(); + var { AsyncResource: AsyncResource$4 } = __require("node:async_hooks"); + var RequestHandler = class extends AsyncResource$4 { + constructor(opts, callback) { + if (!opts || typeof opts !== "object") throw new InvalidArgumentError("invalid opts"); + const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError, highWaterMark } = opts; + try { + if (typeof callback !== "function") throw new InvalidArgumentError("invalid callback"); + if (highWaterMark && (typeof highWaterMark !== "number" || highWaterMark < 0)) throw new InvalidArgumentError("invalid highWaterMark"); + if (signal && typeof signal.on !== "function" && typeof signal.addEventListener !== "function") throw new InvalidArgumentError("signal must be an EventEmitter or EventTarget"); + if (method === "CONNECT") throw new InvalidArgumentError("invalid method"); + if (onInfo && typeof onInfo !== "function") throw new InvalidArgumentError("invalid onInfo callback"); + super("UNDICI_REQUEST"); + } catch (err) { + if (util.isStream(body)) util.destroy(body.on("error", util.nop), err); + throw err; + } + this.method = method; + this.responseHeaders = responseHeaders || null; + this.opaque = opaque || null; + this.callback = callback; + this.res = null; + this.abort = null; + this.body = body; + this.trailers = {}; + this.context = null; + this.onInfo = onInfo || null; + this.throwOnError = throwOnError; + this.highWaterMark = highWaterMark; + this.signal = signal; + this.reason = null; + this.removeAbortListener = null; + if (util.isStream(body)) body.on("error", (err) => { + this.onError(err); + }); + if (this.signal) if (this.signal.aborted) this.reason = this.signal.reason ?? new RequestAbortedError(); + else this.removeAbortListener = util.addAbortListener(this.signal, () => { + this.reason = this.signal.reason ?? new RequestAbortedError(); + if (this.res) util.destroy(this.res.on("error", util.nop), this.reason); + else if (this.abort) this.abort(this.reason); + if (this.removeAbortListener) { + this.res?.off("close", this.removeAbortListener); + this.removeAbortListener(); + this.removeAbortListener = null; + } + }); + } + onConnect(abort, context) { + if (this.reason) { + abort(this.reason); + return; + } + assert$12(this.callback); + this.abort = abort; + this.context = context; + } + onHeaders(statusCode, rawHeaders, resume, statusMessage) { + const { callback, opaque, abort, context, responseHeaders, highWaterMark } = this; + const headers = responseHeaders === "raw" ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders); + if (statusCode < 200) { + if (this.onInfo) this.onInfo({ + statusCode, + headers + }); + return; + } + const parsedHeaders = responseHeaders === "raw" ? util.parseHeaders(rawHeaders) : headers; + const contentType = parsedHeaders["content-type"]; + const contentLength = parsedHeaders["content-length"]; + const res = new Readable({ + resume, + abort, + contentType, + contentLength: this.method !== "HEAD" && contentLength ? Number(contentLength) : null, + highWaterMark + }); + if (this.removeAbortListener) res.on("close", this.removeAbortListener); + this.callback = null; + this.res = res; + if (callback !== null) if (this.throwOnError && statusCode >= 400) this.runInAsyncScope(getResolveErrorBodyCallback, null, { + callback, + body: res, + contentType, + statusCode, + statusMessage, + headers + }); + else this.runInAsyncScope(callback, null, null, { + statusCode, + headers, + trailers: this.trailers, + opaque, + body: res, + context + }); + } + onData(chunk) { + return this.res.push(chunk); + } + onComplete(trailers) { + util.parseHeaders(trailers, this.trailers); + this.res.push(null); + } + onError(err) { + const { res, callback, body, opaque } = this; + if (callback) { + this.callback = null; + queueMicrotask(() => { + this.runInAsyncScope(callback, null, err, { opaque }); + }); + } + if (res) { + this.res = null; + queueMicrotask(() => { + util.destroy(res, err); + }); + } + if (body) { + this.body = null; + util.destroy(body, err); + } + if (this.removeAbortListener) { + res?.off("close", this.removeAbortListener); + this.removeAbortListener(); + this.removeAbortListener = null; + } + } + }; + function request(opts, callback) { + if (callback === void 0) return new Promise((resolve, reject) => { + request.call(this, opts, (err, data) => { + return err ? reject(err) : resolve(data); + }); + }); + try { + this.dispatch(opts, new RequestHandler(opts, callback)); + } catch (err) { + if (typeof callback !== "function") throw err; + const opaque = opts?.opaque; + queueMicrotask(() => callback(err, { opaque })); + } + } + module.exports = request; + module.exports.RequestHandler = RequestHandler; +})); +//#endregion +//#region node_modules/undici/lib/api/abort-signal.js +var require_abort_signal = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { addAbortListener } = require_util$7(); + var { RequestAbortedError } = require_errors(); + var kListener = Symbol("kListener"); + var kSignal = Symbol("kSignal"); + function abort(self) { + if (self.abort) self.abort(self[kSignal]?.reason); + else self.reason = self[kSignal]?.reason ?? new RequestAbortedError(); + removeSignal(self); + } + function addSignal(self, signal) { + self.reason = null; + self[kSignal] = null; + self[kListener] = null; + if (!signal) return; + if (signal.aborted) { + abort(self); + return; + } + self[kSignal] = signal; + self[kListener] = () => { + abort(self); + }; + addAbortListener(self[kSignal], self[kListener]); + } + function removeSignal(self) { + if (!self[kSignal]) return; + if ("removeEventListener" in self[kSignal]) self[kSignal].removeEventListener("abort", self[kListener]); + else self[kSignal].removeListener("abort", self[kListener]); + self[kSignal] = null; + self[kListener] = null; + } + module.exports = { + addSignal, + removeSignal + }; +})); +//#endregion +//#region node_modules/undici/lib/api/api-stream.js +var require_api_stream = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var assert$11 = __require("node:assert"); + var { finished: finished$1, PassThrough: PassThrough$1 } = __require("node:stream"); + var { InvalidArgumentError, InvalidReturnValueError } = require_errors(); + var util = require_util$7(); + var { getResolveErrorBodyCallback } = require_util$5(); + var { AsyncResource: AsyncResource$3 } = __require("node:async_hooks"); + var { addSignal, removeSignal } = require_abort_signal(); + var StreamHandler = class extends AsyncResource$3 { + constructor(opts, factory, callback) { + if (!opts || typeof opts !== "object") throw new InvalidArgumentError("invalid opts"); + const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError } = opts; + try { + if (typeof callback !== "function") throw new InvalidArgumentError("invalid callback"); + if (typeof factory !== "function") throw new InvalidArgumentError("invalid factory"); + if (signal && typeof signal.on !== "function" && typeof signal.addEventListener !== "function") throw new InvalidArgumentError("signal must be an EventEmitter or EventTarget"); + if (method === "CONNECT") throw new InvalidArgumentError("invalid method"); + if (onInfo && typeof onInfo !== "function") throw new InvalidArgumentError("invalid onInfo callback"); + super("UNDICI_STREAM"); + } catch (err) { + if (util.isStream(body)) util.destroy(body.on("error", util.nop), err); + throw err; + } + this.responseHeaders = responseHeaders || null; + this.opaque = opaque || null; + this.factory = factory; + this.callback = callback; + this.res = null; + this.abort = null; + this.context = null; + this.trailers = null; + this.body = body; + this.onInfo = onInfo || null; + this.throwOnError = throwOnError || false; + if (util.isStream(body)) body.on("error", (err) => { + this.onError(err); + }); + addSignal(this, signal); + } + onConnect(abort, context) { + if (this.reason) { + abort(this.reason); + return; + } + assert$11(this.callback); + this.abort = abort; + this.context = context; + } + onHeaders(statusCode, rawHeaders, resume, statusMessage) { + const { factory, opaque, context, callback, responseHeaders } = this; + const headers = responseHeaders === "raw" ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders); + if (statusCode < 200) { + if (this.onInfo) this.onInfo({ + statusCode, + headers + }); + return; + } + this.factory = null; + let res; + if (this.throwOnError && statusCode >= 400) { + const contentType = (responseHeaders === "raw" ? util.parseHeaders(rawHeaders) : headers)["content-type"]; + res = new PassThrough$1(); + this.callback = null; + this.runInAsyncScope(getResolveErrorBodyCallback, null, { + callback, + body: res, + contentType, + statusCode, + statusMessage, + headers + }); + } else { + if (factory === null) return; + res = this.runInAsyncScope(factory, null, { + statusCode, + headers, + opaque, + context + }); + if (!res || typeof res.write !== "function" || typeof res.end !== "function" || typeof res.on !== "function") throw new InvalidReturnValueError("expected Writable"); + finished$1(res, { readable: false }, (err) => { + const { callback, res, opaque, trailers, abort } = this; + this.res = null; + if (err || !res.readable) util.destroy(res, err); + this.callback = null; + this.runInAsyncScope(callback, null, err || null, { + opaque, + trailers + }); + if (err) abort(); + }); + } + res.on("drain", resume); + this.res = res; + return (res.writableNeedDrain !== void 0 ? res.writableNeedDrain : res._writableState?.needDrain) !== true; + } + onData(chunk) { + const { res } = this; + return res ? res.write(chunk) : true; + } + onComplete(trailers) { + const { res } = this; + removeSignal(this); + if (!res) return; + this.trailers = util.parseHeaders(trailers); + res.end(); + } + onError(err) { + const { res, callback, opaque, body } = this; + removeSignal(this); + this.factory = null; + if (res) { + this.res = null; + util.destroy(res, err); + } else if (callback) { + this.callback = null; + queueMicrotask(() => { + this.runInAsyncScope(callback, null, err, { opaque }); + }); + } + if (body) { + this.body = null; + util.destroy(body, err); + } + } + }; + function stream(opts, factory, callback) { + if (callback === void 0) return new Promise((resolve, reject) => { + stream.call(this, opts, factory, (err, data) => { + return err ? reject(err) : resolve(data); + }); + }); + try { + this.dispatch(opts, new StreamHandler(opts, factory, callback)); + } catch (err) { + if (typeof callback !== "function") throw err; + const opaque = opts?.opaque; + queueMicrotask(() => callback(err, { opaque })); + } + } + module.exports = stream; +})); +//#endregion +//#region node_modules/undici/lib/api/api-pipeline.js +var require_api_pipeline = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { Readable: Readable$1, Duplex, PassThrough } = __require("node:stream"); + var { InvalidArgumentError, InvalidReturnValueError, RequestAbortedError } = require_errors(); + var util = require_util$7(); + var { AsyncResource: AsyncResource$2 } = __require("node:async_hooks"); + var { addSignal, removeSignal } = require_abort_signal(); + var assert$10 = __require("node:assert"); + var kResume = Symbol("resume"); + var PipelineRequest = class extends Readable$1 { + constructor() { + super({ autoDestroy: true }); + this[kResume] = null; + } + _read() { + const { [kResume]: resume } = this; + if (resume) { + this[kResume] = null; + resume(); + } + } + _destroy(err, callback) { + this._read(); + callback(err); + } + }; + var PipelineResponse = class extends Readable$1 { + constructor(resume) { + super({ autoDestroy: true }); + this[kResume] = resume; + } + _read() { + this[kResume](); + } + _destroy(err, callback) { + if (!err && !this._readableState.endEmitted) err = new RequestAbortedError(); + callback(err); + } + }; + var PipelineHandler = class extends AsyncResource$2 { + constructor(opts, handler) { + if (!opts || typeof opts !== "object") throw new InvalidArgumentError("invalid opts"); + if (typeof handler !== "function") throw new InvalidArgumentError("invalid handler"); + const { signal, method, opaque, onInfo, responseHeaders } = opts; + if (signal && typeof signal.on !== "function" && typeof signal.addEventListener !== "function") throw new InvalidArgumentError("signal must be an EventEmitter or EventTarget"); + if (method === "CONNECT") throw new InvalidArgumentError("invalid method"); + if (onInfo && typeof onInfo !== "function") throw new InvalidArgumentError("invalid onInfo callback"); + super("UNDICI_PIPELINE"); + this.opaque = opaque || null; + this.responseHeaders = responseHeaders || null; + this.handler = handler; + this.abort = null; + this.context = null; + this.onInfo = onInfo || null; + this.req = new PipelineRequest().on("error", util.nop); + this.ret = new Duplex({ + readableObjectMode: opts.objectMode, + autoDestroy: true, + read: () => { + const { body } = this; + if (body?.resume) body.resume(); + }, + write: (chunk, encoding, callback) => { + const { req } = this; + if (req.push(chunk, encoding) || req._readableState.destroyed) callback(); + else req[kResume] = callback; + }, + destroy: (err, callback) => { + const { body, req, res, ret, abort } = this; + if (!err && !ret._readableState.endEmitted) err = new RequestAbortedError(); + if (abort && err) abort(); + util.destroy(body, err); + util.destroy(req, err); + util.destroy(res, err); + removeSignal(this); + callback(err); + } + }).on("prefinish", () => { + const { req } = this; + req.push(null); + }); + this.res = null; + addSignal(this, signal); + } + onConnect(abort, context) { + const { ret, res } = this; + if (this.reason) { + abort(this.reason); + return; + } + assert$10(!res, "pipeline cannot be retried"); + assert$10(!ret.destroyed); + this.abort = abort; + this.context = context; + } + onHeaders(statusCode, rawHeaders, resume) { + const { opaque, handler, context } = this; + if (statusCode < 200) { + if (this.onInfo) { + const headers = this.responseHeaders === "raw" ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders); + this.onInfo({ + statusCode, + headers + }); + } + return; + } + this.res = new PipelineResponse(resume); + let body; + try { + this.handler = null; + const headers = this.responseHeaders === "raw" ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders); + body = this.runInAsyncScope(handler, null, { + statusCode, + headers, + opaque, + body: this.res, + context + }); + } catch (err) { + this.res.on("error", util.nop); + throw err; + } + if (!body || typeof body.on !== "function") throw new InvalidReturnValueError("expected Readable"); + body.on("data", (chunk) => { + const { ret, body } = this; + if (!ret.push(chunk) && body.pause) body.pause(); + }).on("error", (err) => { + const { ret } = this; + util.destroy(ret, err); + }).on("end", () => { + const { ret } = this; + ret.push(null); + }).on("close", () => { + const { ret } = this; + if (!ret._readableState.ended) util.destroy(ret, new RequestAbortedError()); + }); + this.body = body; + } + onData(chunk) { + const { res } = this; + return res.push(chunk); + } + onComplete(trailers) { + const { res } = this; + res.push(null); + } + onError(err) { + const { ret } = this; + this.handler = null; + util.destroy(ret, err); + } + }; + function pipeline(opts, handler) { + try { + const pipelineHandler = new PipelineHandler(opts, handler); + this.dispatch({ + ...opts, + body: pipelineHandler.req + }, pipelineHandler); + return pipelineHandler.ret; + } catch (err) { + return new PassThrough().destroy(err); + } + } + module.exports = pipeline; +})); +//#endregion +//#region node_modules/undici/lib/api/api-upgrade.js +var require_api_upgrade = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { InvalidArgumentError, SocketError } = require_errors(); + var { AsyncResource: AsyncResource$1 } = __require("node:async_hooks"); + var util = require_util$7(); + var { addSignal, removeSignal } = require_abort_signal(); + var assert$9 = __require("node:assert"); + var UpgradeHandler = class extends AsyncResource$1 { + constructor(opts, callback) { + if (!opts || typeof opts !== "object") throw new InvalidArgumentError("invalid opts"); + if (typeof callback !== "function") throw new InvalidArgumentError("invalid callback"); + const { signal, opaque, responseHeaders } = opts; + if (signal && typeof signal.on !== "function" && typeof signal.addEventListener !== "function") throw new InvalidArgumentError("signal must be an EventEmitter or EventTarget"); + super("UNDICI_UPGRADE"); + this.responseHeaders = responseHeaders || null; + this.opaque = opaque || null; + this.callback = callback; + this.abort = null; + this.context = null; + addSignal(this, signal); + } + onConnect(abort, context) { + if (this.reason) { + abort(this.reason); + return; + } + assert$9(this.callback); + this.abort = abort; + this.context = null; + } + onHeaders() { + throw new SocketError("bad upgrade", null); + } + onUpgrade(statusCode, rawHeaders, socket) { + assert$9(statusCode === 101); + const { callback, opaque, context } = this; + removeSignal(this); + this.callback = null; + const headers = this.responseHeaders === "raw" ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders); + this.runInAsyncScope(callback, null, null, { + headers, + socket, + opaque, + context + }); + } + onError(err) { + const { callback, opaque } = this; + removeSignal(this); + if (callback) { + this.callback = null; + queueMicrotask(() => { + this.runInAsyncScope(callback, null, err, { opaque }); + }); + } + } + }; + function upgrade(opts, callback) { + if (callback === void 0) return new Promise((resolve, reject) => { + upgrade.call(this, opts, (err, data) => { + return err ? reject(err) : resolve(data); + }); + }); + try { + const upgradeHandler = new UpgradeHandler(opts, callback); + this.dispatch({ + ...opts, + method: opts.method || "GET", + upgrade: opts.protocol || "Websocket" + }, upgradeHandler); + } catch (err) { + if (typeof callback !== "function") throw err; + const opaque = opts?.opaque; + queueMicrotask(() => callback(err, { opaque })); + } + } + module.exports = upgrade; +})); +//#endregion +//#region node_modules/undici/lib/api/api-connect.js +var require_api_connect = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var assert$8 = __require("node:assert"); + var { AsyncResource } = __require("node:async_hooks"); + var { InvalidArgumentError, SocketError } = require_errors(); + var util = require_util$7(); + var { addSignal, removeSignal } = require_abort_signal(); + var ConnectHandler = class extends AsyncResource { + constructor(opts, callback) { + if (!opts || typeof opts !== "object") throw new InvalidArgumentError("invalid opts"); + if (typeof callback !== "function") throw new InvalidArgumentError("invalid callback"); + const { signal, opaque, responseHeaders } = opts; + if (signal && typeof signal.on !== "function" && typeof signal.addEventListener !== "function") throw new InvalidArgumentError("signal must be an EventEmitter or EventTarget"); + super("UNDICI_CONNECT"); + this.opaque = opaque || null; + this.responseHeaders = responseHeaders || null; + this.callback = callback; + this.abort = null; + addSignal(this, signal); + } + onConnect(abort, context) { + if (this.reason) { + abort(this.reason); + return; + } + assert$8(this.callback); + this.abort = abort; + this.context = context; + } + onHeaders() { + throw new SocketError("bad connect", null); + } + onUpgrade(statusCode, rawHeaders, socket) { + const { callback, opaque, context } = this; + removeSignal(this); + this.callback = null; + let headers = rawHeaders; + if (headers != null) headers = this.responseHeaders === "raw" ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders); + this.runInAsyncScope(callback, null, null, { + statusCode, + headers, + socket, + opaque, + context + }); + } + onError(err) { + const { callback, opaque } = this; + removeSignal(this); + if (callback) { + this.callback = null; + queueMicrotask(() => { + this.runInAsyncScope(callback, null, err, { opaque }); + }); + } + } + }; + function connect(opts, callback) { + if (callback === void 0) return new Promise((resolve, reject) => { + connect.call(this, opts, (err, data) => { + return err ? reject(err) : resolve(data); + }); + }); + try { + const connectHandler = new ConnectHandler(opts, callback); + this.dispatch({ + ...opts, + method: "CONNECT" + }, connectHandler); + } catch (err) { + if (typeof callback !== "function") throw err; + const opaque = opts?.opaque; + queueMicrotask(() => callback(err, { opaque })); + } + } + module.exports = connect; +})); +//#endregion +//#region node_modules/undici/lib/api/index.js +var require_api = /* @__PURE__ */ __commonJSMin(((exports, module) => { + module.exports.request = require_api_request(); + module.exports.stream = require_api_stream(); + module.exports.pipeline = require_api_pipeline(); + module.exports.upgrade = require_api_upgrade(); + module.exports.connect = require_api_connect(); +})); +//#endregion +//#region node_modules/undici/lib/mock/mock-errors.js +var require_mock_errors = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { UndiciError } = require_errors(); + var kMockNotMatchedError = Symbol.for("undici.error.UND_MOCK_ERR_MOCK_NOT_MATCHED"); + module.exports = { MockNotMatchedError: class MockNotMatchedError extends UndiciError { + constructor(message) { + super(message); + Error.captureStackTrace(this, MockNotMatchedError); + this.name = "MockNotMatchedError"; + this.message = message || "The request does not match any registered mock dispatches"; + this.code = "UND_MOCK_ERR_MOCK_NOT_MATCHED"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kMockNotMatchedError] === true; + } + [kMockNotMatchedError] = true; + } }; +})); +//#endregion +//#region node_modules/undici/lib/mock/mock-symbols.js +var require_mock_symbols = /* @__PURE__ */ __commonJSMin(((exports, module) => { + module.exports = { + kAgent: Symbol("agent"), + kOptions: Symbol("options"), + kFactory: Symbol("factory"), + kDispatches: Symbol("dispatches"), + kDispatchKey: Symbol("dispatch key"), + kDefaultHeaders: Symbol("default headers"), + kDefaultTrailers: Symbol("default trailers"), + kContentLength: Symbol("content length"), + kMockAgent: Symbol("mock agent"), + kMockAgentSet: Symbol("mock agent set"), + kMockAgentGet: Symbol("mock agent get"), + kMockDispatch: Symbol("mock dispatch"), + kClose: Symbol("close"), + kOriginalClose: Symbol("original agent close"), + kOrigin: Symbol("origin"), + kIsMockActive: Symbol("is mock active"), + kNetConnect: Symbol("net connect"), + kGetNetConnect: Symbol("get net connect"), + kConnected: Symbol("connected") + }; +})); +//#endregion +//#region node_modules/undici/lib/mock/mock-utils.js +var require_mock_utils = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { MockNotMatchedError } = require_mock_errors(); + var { kDispatches, kMockAgent, kOriginalDispatch, kOrigin, kGetNetConnect } = require_mock_symbols(); + var { buildURL } = require_util$7(); + var { STATUS_CODES: STATUS_CODES$1 } = __require("node:http"); + var { types: { isPromise } } = __require("node:util"); + function matchValue(match, value) { + if (typeof match === "string") return match === value; + if (match instanceof RegExp) return match.test(value); + if (typeof match === "function") return match(value) === true; + return false; + } + function lowerCaseEntries(headers) { + return Object.fromEntries(Object.entries(headers).map(([headerName, headerValue]) => { + return [headerName.toLocaleLowerCase(), headerValue]; + })); + } + /** + * @param {import('../../index').Headers|string[]|Record} headers + * @param {string} key + */ + function getHeaderByName(headers, key) { + if (Array.isArray(headers)) { + for (let i = 0; i < headers.length; i += 2) if (headers[i].toLocaleLowerCase() === key.toLocaleLowerCase()) return headers[i + 1]; + return; + } else if (typeof headers.get === "function") return headers.get(key); + else return lowerCaseEntries(headers)[key.toLocaleLowerCase()]; + } + /** @param {string[]} headers */ + function buildHeadersFromArray(headers) { + const clone = headers.slice(); + const entries = []; + for (let index = 0; index < clone.length; index += 2) entries.push([clone[index], clone[index + 1]]); + return Object.fromEntries(entries); + } + function matchHeaders(mockDispatch, headers) { + if (typeof mockDispatch.headers === "function") { + if (Array.isArray(headers)) headers = buildHeadersFromArray(headers); + return mockDispatch.headers(headers ? lowerCaseEntries(headers) : {}); + } + if (typeof mockDispatch.headers === "undefined") return true; + if (typeof headers !== "object" || typeof mockDispatch.headers !== "object") return false; + for (const [matchHeaderName, matchHeaderValue] of Object.entries(mockDispatch.headers)) if (!matchValue(matchHeaderValue, getHeaderByName(headers, matchHeaderName))) return false; + return true; + } + function safeUrl(path) { + if (typeof path !== "string") return path; + const pathSegments = path.split("?"); + if (pathSegments.length !== 2) return path; + const qp = new URLSearchParams(pathSegments.pop()); + qp.sort(); + return [...pathSegments, qp.toString()].join("?"); + } + function matchKey(mockDispatch, { path, method, body, headers }) { + const pathMatch = matchValue(mockDispatch.path, path); + const methodMatch = matchValue(mockDispatch.method, method); + const bodyMatch = typeof mockDispatch.body !== "undefined" ? matchValue(mockDispatch.body, body) : true; + const headersMatch = matchHeaders(mockDispatch, headers); + return pathMatch && methodMatch && bodyMatch && headersMatch; + } + function getResponseData(data) { + if (Buffer.isBuffer(data)) return data; + else if (data instanceof Uint8Array) return data; + else if (data instanceof ArrayBuffer) return data; + else if (typeof data === "object") return JSON.stringify(data); + else return data.toString(); + } + function getMockDispatch(mockDispatches, key) { + const basePath = key.query ? buildURL(key.path, key.query) : key.path; + const resolvedPath = typeof basePath === "string" ? safeUrl(basePath) : basePath; + let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path }) => matchValue(safeUrl(path), resolvedPath)); + if (matchedMockDispatches.length === 0) throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`); + matchedMockDispatches = matchedMockDispatches.filter(({ method }) => matchValue(method, key.method)); + if (matchedMockDispatches.length === 0) throw new MockNotMatchedError(`Mock dispatch not matched for method '${key.method}' on path '${resolvedPath}'`); + matchedMockDispatches = matchedMockDispatches.filter(({ body }) => typeof body !== "undefined" ? matchValue(body, key.body) : true); + if (matchedMockDispatches.length === 0) throw new MockNotMatchedError(`Mock dispatch not matched for body '${key.body}' on path '${resolvedPath}'`); + matchedMockDispatches = matchedMockDispatches.filter((mockDispatch) => matchHeaders(mockDispatch, key.headers)); + if (matchedMockDispatches.length === 0) throw new MockNotMatchedError(`Mock dispatch not matched for headers '${typeof key.headers === "object" ? JSON.stringify(key.headers) : key.headers}' on path '${resolvedPath}'`); + return matchedMockDispatches[0]; + } + function addMockDispatch(mockDispatches, key, data) { + const baseData = { + timesInvoked: 0, + times: 1, + persist: false, + consumed: false + }; + const replyData = typeof data === "function" ? { callback: data } : { ...data }; + const newMockDispatch = { + ...baseData, + ...key, + pending: true, + data: { + error: null, + ...replyData + } + }; + mockDispatches.push(newMockDispatch); + return newMockDispatch; + } + function deleteMockDispatch(mockDispatches, key) { + const index = mockDispatches.findIndex((dispatch) => { + if (!dispatch.consumed) return false; + return matchKey(dispatch, key); + }); + if (index !== -1) mockDispatches.splice(index, 1); + } + function buildKey(opts) { + const { path, method, body, headers, query } = opts; + return { + path, + method, + body, + headers, + query + }; + } + function generateKeyValues(data) { + const keys = Object.keys(data); + const result = []; + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + const value = data[key]; + const name = Buffer.from(`${key}`); + if (Array.isArray(value)) for (let j = 0; j < value.length; ++j) result.push(name, Buffer.from(`${value[j]}`)); + else result.push(name, Buffer.from(`${value}`)); + } + return result; + } + /** + * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status + * @param {number} statusCode + */ + function getStatusText(statusCode) { + return STATUS_CODES$1[statusCode] || "unknown"; + } + async function getResponse(body) { + const buffers = []; + for await (const data of body) buffers.push(data); + return Buffer.concat(buffers).toString("utf8"); + } + /** + * Mock dispatch function used to simulate undici dispatches + */ + function mockDispatch(opts, handler) { + const key = buildKey(opts); + const mockDispatch = getMockDispatch(this[kDispatches], key); + mockDispatch.timesInvoked++; + if (mockDispatch.data.callback) mockDispatch.data = { + ...mockDispatch.data, + ...mockDispatch.data.callback(opts) + }; + const { data: { statusCode, data, headers, trailers, error }, delay, persist } = mockDispatch; + const { timesInvoked, times } = mockDispatch; + mockDispatch.consumed = !persist && timesInvoked >= times; + mockDispatch.pending = timesInvoked < times; + if (error !== null) { + deleteMockDispatch(this[kDispatches], key); + handler.onError(error); + return true; + } + if (typeof delay === "number" && delay > 0) setTimeout(() => { + handleReply(this[kDispatches]); + }, delay); + else handleReply(this[kDispatches]); + function handleReply(mockDispatches, _data = data) { + const optsHeaders = Array.isArray(opts.headers) ? buildHeadersFromArray(opts.headers) : opts.headers; + const body = typeof _data === "function" ? _data({ + ...opts, + headers: optsHeaders + }) : _data; + if (isPromise(body)) { + body.then((newData) => handleReply(mockDispatches, newData)); + return; + } + const responseData = getResponseData(body); + const responseHeaders = generateKeyValues(headers); + const responseTrailers = generateKeyValues(trailers); + handler.onConnect?.((err) => handler.onError(err), null); + handler.onHeaders?.(statusCode, responseHeaders, resume, getStatusText(statusCode)); + handler.onData?.(Buffer.from(responseData)); + handler.onComplete?.(responseTrailers); + deleteMockDispatch(mockDispatches, key); + } + function resume() {} + return true; + } + function buildMockDispatch() { + const agent = this[kMockAgent]; + const origin = this[kOrigin]; + const originalDispatch = this[kOriginalDispatch]; + return function dispatch(opts, handler) { + if (agent.isMockActive) try { + mockDispatch.call(this, opts, handler); + } catch (error) { + if (error instanceof MockNotMatchedError) { + const netConnect = agent[kGetNetConnect](); + if (netConnect === false) throw new MockNotMatchedError(`${error.message}: subsequent request to origin ${origin} was not allowed (net.connect disabled)`); + if (checkNetConnect(netConnect, origin)) originalDispatch.call(this, opts, handler); + else throw new MockNotMatchedError(`${error.message}: subsequent request to origin ${origin} was not allowed (net.connect is not enabled for this origin)`); + } else throw error; + } + else originalDispatch.call(this, opts, handler); + }; + } + function checkNetConnect(netConnect, origin) { + const url = new URL(origin); + if (netConnect === true) return true; + else if (Array.isArray(netConnect) && netConnect.some((matcher) => matchValue(matcher, url.host))) return true; + return false; + } + function buildMockOptions(opts) { + if (opts) { + const { agent, ...mockOptions } = opts; + return mockOptions; + } + } + module.exports = { + getResponseData, + getMockDispatch, + addMockDispatch, + deleteMockDispatch, + buildKey, + generateKeyValues, + matchValue, + getResponse, + getStatusText, + mockDispatch, + buildMockDispatch, + checkNetConnect, + buildMockOptions, + getHeaderByName, + buildHeadersFromArray + }; +})); +//#endregion +//#region node_modules/undici/lib/mock/mock-interceptor.js +var require_mock_interceptor = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { getResponseData, buildKey, addMockDispatch } = require_mock_utils(); + var { kDispatches, kDispatchKey, kDefaultHeaders, kDefaultTrailers, kContentLength, kMockDispatch } = require_mock_symbols(); + var { InvalidArgumentError } = require_errors(); + var { buildURL } = require_util$7(); + /** + * Defines the scope API for an interceptor reply + */ + var MockScope = class { + constructor(mockDispatch) { + this[kMockDispatch] = mockDispatch; + } + /** + * Delay a reply by a set amount in ms. + */ + delay(waitInMs) { + if (typeof waitInMs !== "number" || !Number.isInteger(waitInMs) || waitInMs <= 0) throw new InvalidArgumentError("waitInMs must be a valid integer > 0"); + this[kMockDispatch].delay = waitInMs; + return this; + } + /** + * For a defined reply, never mark as consumed. + */ + persist() { + this[kMockDispatch].persist = true; + return this; + } + /** + * Allow one to define a reply for a set amount of matching requests. + */ + times(repeatTimes) { + if (typeof repeatTimes !== "number" || !Number.isInteger(repeatTimes) || repeatTimes <= 0) throw new InvalidArgumentError("repeatTimes must be a valid integer > 0"); + this[kMockDispatch].times = repeatTimes; + return this; + } + }; + /** + * Defines an interceptor for a Mock + */ + var MockInterceptor = class { + constructor(opts, mockDispatches) { + if (typeof opts !== "object") throw new InvalidArgumentError("opts must be an object"); + if (typeof opts.path === "undefined") throw new InvalidArgumentError("opts.path must be defined"); + if (typeof opts.method === "undefined") opts.method = "GET"; + if (typeof opts.path === "string") if (opts.query) opts.path = buildURL(opts.path, opts.query); + else { + const parsedURL = new URL(opts.path, "data://"); + opts.path = parsedURL.pathname + parsedURL.search; + } + if (typeof opts.method === "string") opts.method = opts.method.toUpperCase(); + this[kDispatchKey] = buildKey(opts); + this[kDispatches] = mockDispatches; + this[kDefaultHeaders] = {}; + this[kDefaultTrailers] = {}; + this[kContentLength] = false; + } + createMockScopeDispatchData({ statusCode, data, responseOptions }) { + const responseData = getResponseData(data); + const contentLength = this[kContentLength] ? { "content-length": responseData.length } : {}; + return { + statusCode, + data, + headers: { + ...this[kDefaultHeaders], + ...contentLength, + ...responseOptions.headers + }, + trailers: { + ...this[kDefaultTrailers], + ...responseOptions.trailers + } + }; + } + validateReplyParameters(replyParameters) { + if (typeof replyParameters.statusCode === "undefined") throw new InvalidArgumentError("statusCode must be defined"); + if (typeof replyParameters.responseOptions !== "object" || replyParameters.responseOptions === null) throw new InvalidArgumentError("responseOptions must be an object"); + } + /** + * Mock an undici request with a defined reply. + */ + reply(replyOptionsCallbackOrStatusCode) { + if (typeof replyOptionsCallbackOrStatusCode === "function") { + const wrappedDefaultsCallback = (opts) => { + const resolvedData = replyOptionsCallbackOrStatusCode(opts); + if (typeof resolvedData !== "object" || resolvedData === null) throw new InvalidArgumentError("reply options callback must return an object"); + const replyParameters = { + data: "", + responseOptions: {}, + ...resolvedData + }; + this.validateReplyParameters(replyParameters); + return { ...this.createMockScopeDispatchData(replyParameters) }; + }; + return new MockScope(addMockDispatch(this[kDispatches], this[kDispatchKey], wrappedDefaultsCallback)); + } + const replyParameters = { + statusCode: replyOptionsCallbackOrStatusCode, + data: arguments[1] === void 0 ? "" : arguments[1], + responseOptions: arguments[2] === void 0 ? {} : arguments[2] + }; + this.validateReplyParameters(replyParameters); + const dispatchData = this.createMockScopeDispatchData(replyParameters); + return new MockScope(addMockDispatch(this[kDispatches], this[kDispatchKey], dispatchData)); + } + /** + * Mock an undici request with a defined error. + */ + replyWithError(error) { + if (typeof error === "undefined") throw new InvalidArgumentError("error must be defined"); + return new MockScope(addMockDispatch(this[kDispatches], this[kDispatchKey], { error })); + } + /** + * Set default reply headers on the interceptor for subsequent replies + */ + defaultReplyHeaders(headers) { + if (typeof headers === "undefined") throw new InvalidArgumentError("headers must be defined"); + this[kDefaultHeaders] = headers; + return this; + } + /** + * Set default reply trailers on the interceptor for subsequent replies + */ + defaultReplyTrailers(trailers) { + if (typeof trailers === "undefined") throw new InvalidArgumentError("trailers must be defined"); + this[kDefaultTrailers] = trailers; + return this; + } + /** + * Set reply content length header for replies on the interceptor + */ + replyContentLength() { + this[kContentLength] = true; + return this; + } + }; + module.exports.MockInterceptor = MockInterceptor; + module.exports.MockScope = MockScope; +})); +//#endregion +//#region node_modules/undici/lib/mock/mock-client.js +var require_mock_client = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { promisify: promisify$1 } = __require("node:util"); + var Client = require_client(); + var { buildMockDispatch } = require_mock_utils(); + var { kDispatches, kMockAgent, kClose, kOriginalClose, kOrigin, kOriginalDispatch, kConnected } = require_mock_symbols(); + var { MockInterceptor } = require_mock_interceptor(); + var Symbols = require_symbols$4(); + var { InvalidArgumentError } = require_errors(); + /** + * MockClient provides an API that extends the Client to influence the mockDispatches. + */ + var MockClient = class extends Client { + constructor(origin, opts) { + super(origin, opts); + if (!opts || !opts.agent || typeof opts.agent.dispatch !== "function") throw new InvalidArgumentError("Argument opts.agent must implement Agent"); + this[kMockAgent] = opts.agent; + this[kOrigin] = origin; + this[kDispatches] = []; + this[kConnected] = 1; + this[kOriginalDispatch] = this.dispatch; + this[kOriginalClose] = this.close.bind(this); + this.dispatch = buildMockDispatch.call(this); + this.close = this[kClose]; + } + get [Symbols.kConnected]() { + return this[kConnected]; + } + /** + * Sets up the base interceptor for mocking replies from undici. + */ + intercept(opts) { + return new MockInterceptor(opts, this[kDispatches]); + } + async [kClose]() { + await promisify$1(this[kOriginalClose])(); + this[kConnected] = 0; + this[kMockAgent][Symbols.kClients].delete(this[kOrigin]); + } + }; + module.exports = MockClient; +})); +//#endregion +//#region node_modules/undici/lib/mock/mock-pool.js +var require_mock_pool = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { promisify } = __require("node:util"); + var Pool = require_pool(); + var { buildMockDispatch } = require_mock_utils(); + var { kDispatches, kMockAgent, kClose, kOriginalClose, kOrigin, kOriginalDispatch, kConnected } = require_mock_symbols(); + var { MockInterceptor } = require_mock_interceptor(); + var Symbols = require_symbols$4(); + var { InvalidArgumentError } = require_errors(); + /** + * MockPool provides an API that extends the Pool to influence the mockDispatches. + */ + var MockPool = class extends Pool { + constructor(origin, opts) { + super(origin, opts); + if (!opts || !opts.agent || typeof opts.agent.dispatch !== "function") throw new InvalidArgumentError("Argument opts.agent must implement Agent"); + this[kMockAgent] = opts.agent; + this[kOrigin] = origin; + this[kDispatches] = []; + this[kConnected] = 1; + this[kOriginalDispatch] = this.dispatch; + this[kOriginalClose] = this.close.bind(this); + this.dispatch = buildMockDispatch.call(this); + this.close = this[kClose]; + } + get [Symbols.kConnected]() { + return this[kConnected]; + } + /** + * Sets up the base interceptor for mocking replies from undici. + */ + intercept(opts) { + return new MockInterceptor(opts, this[kDispatches]); + } + async [kClose]() { + await promisify(this[kOriginalClose])(); + this[kConnected] = 0; + this[kMockAgent][Symbols.kClients].delete(this[kOrigin]); + } + }; + module.exports = MockPool; +})); +//#endregion +//#region node_modules/undici/lib/mock/pluralizer.js +var require_pluralizer = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var singulars = { + pronoun: "it", + is: "is", + was: "was", + this: "this" + }; + var plurals = { + pronoun: "they", + is: "are", + was: "were", + this: "these" + }; + module.exports = class Pluralizer { + constructor(singular, plural) { + this.singular = singular; + this.plural = plural; + } + pluralize(count) { + const one = count === 1; + const keys = one ? singulars : plurals; + const noun = one ? this.singular : this.plural; + return { + ...keys, + count, + noun + }; + } + }; +})); +//#endregion +//#region node_modules/undici/lib/mock/pending-interceptors-formatter.js +var require_pending_interceptors_formatter = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { Transform: Transform$1 } = __require("node:stream"); + var { Console } = __require("node:console"); + var PERSISTENT = process.versions.icu ? "✅" : "Y "; + var NOT_PERSISTENT = process.versions.icu ? "❌" : "N "; + /** + * Gets the output of `console.table(…)` as a string. + */ + module.exports = class PendingInterceptorsFormatter { + constructor({ disableColors } = {}) { + this.transform = new Transform$1({ transform(chunk, _enc, cb) { + cb(null, chunk); + } }); + this.logger = new Console({ + stdout: this.transform, + inspectOptions: { colors: !disableColors && !process.env.CI } + }); + } + format(pendingInterceptors) { + const withPrettyHeaders = pendingInterceptors.map(({ method, path, data: { statusCode }, persist, times, timesInvoked, origin }) => ({ + Method: method, + Origin: origin, + Path: path, + "Status code": statusCode, + Persistent: persist ? PERSISTENT : NOT_PERSISTENT, + Invocations: timesInvoked, + Remaining: persist ? Infinity : times - timesInvoked + })); + this.logger.table(withPrettyHeaders); + return this.transform.read().toString(); + } + }; +})); +//#endregion +//#region node_modules/undici/lib/mock/mock-agent.js +var require_mock_agent = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { kClients } = require_symbols$4(); + var Agent = require_agent(); + var { kAgent, kMockAgentSet, kMockAgentGet, kDispatches, kIsMockActive, kNetConnect, kGetNetConnect, kOptions, kFactory } = require_mock_symbols(); + var MockClient = require_mock_client(); + var MockPool = require_mock_pool(); + var { matchValue, buildMockOptions } = require_mock_utils(); + var { InvalidArgumentError, UndiciError } = require_errors(); + var Dispatcher = require_dispatcher(); + var Pluralizer = require_pluralizer(); + var PendingInterceptorsFormatter = require_pending_interceptors_formatter(); + var MockAgent = class extends Dispatcher { + constructor(opts) { + super(opts); + this[kNetConnect] = true; + this[kIsMockActive] = true; + if (opts?.agent && typeof opts.agent.dispatch !== "function") throw new InvalidArgumentError("Argument opts.agent must implement Agent"); + const agent = opts?.agent ? opts.agent : new Agent(opts); + this[kAgent] = agent; + this[kClients] = agent[kClients]; + this[kOptions] = buildMockOptions(opts); + } + get(origin) { + let dispatcher = this[kMockAgentGet](origin); + if (!dispatcher) { + dispatcher = this[kFactory](origin); + this[kMockAgentSet](origin, dispatcher); + } + return dispatcher; + } + dispatch(opts, handler) { + this.get(opts.origin); + return this[kAgent].dispatch(opts, handler); + } + async close() { + await this[kAgent].close(); + this[kClients].clear(); + } + deactivate() { + this[kIsMockActive] = false; + } + activate() { + this[kIsMockActive] = true; + } + enableNetConnect(matcher) { + if (typeof matcher === "string" || typeof matcher === "function" || matcher instanceof RegExp) if (Array.isArray(this[kNetConnect])) this[kNetConnect].push(matcher); + else this[kNetConnect] = [matcher]; + else if (typeof matcher === "undefined") this[kNetConnect] = true; + else throw new InvalidArgumentError("Unsupported matcher. Must be one of String|Function|RegExp."); + } + disableNetConnect() { + this[kNetConnect] = false; + } + get isMockActive() { + return this[kIsMockActive]; + } + [kMockAgentSet](origin, dispatcher) { + this[kClients].set(origin, dispatcher); + } + [kFactory](origin) { + const mockOptions = Object.assign({ agent: this }, this[kOptions]); + return this[kOptions] && this[kOptions].connections === 1 ? new MockClient(origin, mockOptions) : new MockPool(origin, mockOptions); + } + [kMockAgentGet](origin) { + const client = this[kClients].get(origin); + if (client) return client; + if (typeof origin !== "string") { + const dispatcher = this[kFactory]("http://localhost:9999"); + this[kMockAgentSet](origin, dispatcher); + return dispatcher; + } + for (const [keyMatcher, nonExplicitDispatcher] of Array.from(this[kClients])) if (nonExplicitDispatcher && typeof keyMatcher !== "string" && matchValue(keyMatcher, origin)) { + const dispatcher = this[kFactory](origin); + this[kMockAgentSet](origin, dispatcher); + dispatcher[kDispatches] = nonExplicitDispatcher[kDispatches]; + return dispatcher; + } + } + [kGetNetConnect]() { + return this[kNetConnect]; + } + pendingInterceptors() { + const mockAgentClients = this[kClients]; + return Array.from(mockAgentClients.entries()).flatMap(([origin, scope]) => scope[kDispatches].map((dispatch) => ({ + ...dispatch, + origin + }))).filter(({ pending }) => pending); + } + assertNoPendingInterceptors({ pendingInterceptorsFormatter = new PendingInterceptorsFormatter() } = {}) { + const pending = this.pendingInterceptors(); + if (pending.length === 0) return; + const pluralizer = new Pluralizer("interceptor", "interceptors").pluralize(pending.length); + throw new UndiciError(` +${pluralizer.count} ${pluralizer.noun} ${pluralizer.is} pending: + +${pendingInterceptorsFormatter.format(pending)} +`.trim()); + } + }; + module.exports = MockAgent; +})); +//#endregion +//#region node_modules/undici/lib/global.js +var require_global = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var globalDispatcher = Symbol.for("undici.globalDispatcher.1"); + var { InvalidArgumentError } = require_errors(); + var Agent = require_agent(); + if (getGlobalDispatcher() === void 0) setGlobalDispatcher(new Agent()); + function setGlobalDispatcher(agent) { + if (!agent || typeof agent.dispatch !== "function") throw new InvalidArgumentError("Argument agent must implement Agent"); + Object.defineProperty(globalThis, globalDispatcher, { + value: agent, + writable: true, + enumerable: false, + configurable: false + }); + } + function getGlobalDispatcher() { + return globalThis[globalDispatcher]; + } + module.exports = { + setGlobalDispatcher, + getGlobalDispatcher + }; +})); +//#endregion +//#region node_modules/undici/lib/handler/decorator-handler.js +var require_decorator_handler = /* @__PURE__ */ __commonJSMin(((exports, module) => { + module.exports = class DecoratorHandler { + #handler; + constructor(handler) { + if (typeof handler !== "object" || handler === null) throw new TypeError("handler must be an object"); + this.#handler = handler; + } + onConnect(...args) { + return this.#handler.onConnect?.(...args); + } + onError(...args) { + return this.#handler.onError?.(...args); + } + onUpgrade(...args) { + return this.#handler.onUpgrade?.(...args); + } + onResponseStarted(...args) { + return this.#handler.onResponseStarted?.(...args); + } + onHeaders(...args) { + return this.#handler.onHeaders?.(...args); + } + onData(...args) { + return this.#handler.onData?.(...args); + } + onComplete(...args) { + return this.#handler.onComplete?.(...args); + } + onBodySent(...args) { + return this.#handler.onBodySent?.(...args); + } + }; +})); +//#endregion +//#region node_modules/undici/lib/interceptor/redirect.js +var require_redirect = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var RedirectHandler = require_redirect_handler(); + module.exports = (opts) => { + const globalMaxRedirections = opts?.maxRedirections; + return (dispatch) => { + return function redirectInterceptor(opts, handler) { + const { maxRedirections = globalMaxRedirections, ...baseOpts } = opts; + if (!maxRedirections) return dispatch(opts, handler); + return dispatch(baseOpts, new RedirectHandler(dispatch, maxRedirections, opts, handler)); + }; + }; + }; +})); +//#endregion +//#region node_modules/undici/lib/interceptor/retry.js +var require_retry = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var RetryHandler = require_retry_handler(); + module.exports = (globalOpts) => { + return (dispatch) => { + return function retryInterceptor(opts, handler) { + return dispatch(opts, new RetryHandler({ + ...opts, + retryOptions: { + ...globalOpts, + ...opts.retryOptions + } + }, { + handler, + dispatch + })); + }; + }; + }; +})); +//#endregion +//#region node_modules/undici/lib/interceptor/dump.js +var require_dump = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var util = require_util$7(); + var { InvalidArgumentError, RequestAbortedError } = require_errors(); + var DecoratorHandler = require_decorator_handler(); + var DumpHandler = class extends DecoratorHandler { + #maxSize = 1024 * 1024; + #abort = null; + #dumped = false; + #aborted = false; + #size = 0; + #reason = null; + #handler = null; + constructor({ maxSize }, handler) { + super(handler); + if (maxSize != null && (!Number.isFinite(maxSize) || maxSize < 1)) throw new InvalidArgumentError("maxSize must be a number greater than 0"); + this.#maxSize = maxSize ?? this.#maxSize; + this.#handler = handler; + } + onConnect(abort) { + this.#abort = abort; + this.#handler.onConnect(this.#customAbort.bind(this)); + } + #customAbort(reason) { + this.#aborted = true; + this.#reason = reason; + } + onHeaders(statusCode, rawHeaders, resume, statusMessage) { + const contentLength = util.parseHeaders(rawHeaders)["content-length"]; + if (contentLength != null && contentLength > this.#maxSize) throw new RequestAbortedError(`Response size (${contentLength}) larger than maxSize (${this.#maxSize})`); + if (this.#aborted) return true; + return this.#handler.onHeaders(statusCode, rawHeaders, resume, statusMessage); + } + onError(err) { + if (this.#dumped) return; + err = this.#reason ?? err; + this.#handler.onError(err); + } + onData(chunk) { + this.#size = this.#size + chunk.length; + if (this.#size >= this.#maxSize) { + this.#dumped = true; + if (this.#aborted) this.#handler.onError(this.#reason); + else this.#handler.onComplete([]); + } + return true; + } + onComplete(trailers) { + if (this.#dumped) return; + if (this.#aborted) { + this.#handler.onError(this.reason); + return; + } + this.#handler.onComplete(trailers); + } + }; + function createDumpInterceptor({ maxSize: defaultMaxSize } = { maxSize: 1024 * 1024 }) { + return (dispatch) => { + return function Intercept(opts, handler) { + const { dumpMaxSize = defaultMaxSize } = opts; + return dispatch(opts, new DumpHandler({ maxSize: dumpMaxSize }, handler)); + }; + }; + } + module.exports = createDumpInterceptor; +})); +//#endregion +//#region node_modules/undici/lib/interceptor/dns.js +var require_dns = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { isIP } = __require("node:net"); + var { lookup } = __require("node:dns"); + var DecoratorHandler = require_decorator_handler(); + var { InvalidArgumentError, InformationalError } = require_errors(); + var maxInt = Math.pow(2, 31) - 1; + var DNSInstance = class { + #maxTTL = 0; + #maxItems = 0; + #records = /* @__PURE__ */ new Map(); + dualStack = true; + affinity = null; + lookup = null; + pick = null; + constructor(opts) { + this.#maxTTL = opts.maxTTL; + this.#maxItems = opts.maxItems; + this.dualStack = opts.dualStack; + this.affinity = opts.affinity; + this.lookup = opts.lookup ?? this.#defaultLookup; + this.pick = opts.pick ?? this.#defaultPick; + } + get full() { + return this.#records.size === this.#maxItems; + } + runLookup(origin, opts, cb) { + const ips = this.#records.get(origin.hostname); + if (ips == null && this.full) { + cb(null, origin.origin); + return; + } + const newOpts = { + affinity: this.affinity, + dualStack: this.dualStack, + lookup: this.lookup, + pick: this.pick, + ...opts.dns, + maxTTL: this.#maxTTL, + maxItems: this.#maxItems + }; + if (ips == null) this.lookup(origin, newOpts, (err, addresses) => { + if (err || addresses == null || addresses.length === 0) { + cb(err ?? new InformationalError("No DNS entries found")); + return; + } + this.setRecords(origin, addresses); + const records = this.#records.get(origin.hostname); + const ip = this.pick(origin, records, newOpts.affinity); + let port; + if (typeof ip.port === "number") port = `:${ip.port}`; + else if (origin.port !== "") port = `:${origin.port}`; + else port = ""; + cb(null, `${origin.protocol}//${ip.family === 6 ? `[${ip.address}]` : ip.address}${port}`); + }); + else { + const ip = this.pick(origin, ips, newOpts.affinity); + if (ip == null) { + this.#records.delete(origin.hostname); + this.runLookup(origin, opts, cb); + return; + } + let port; + if (typeof ip.port === "number") port = `:${ip.port}`; + else if (origin.port !== "") port = `:${origin.port}`; + else port = ""; + cb(null, `${origin.protocol}//${ip.family === 6 ? `[${ip.address}]` : ip.address}${port}`); + } + } + #defaultLookup(origin, opts, cb) { + lookup(origin.hostname, { + all: true, + family: this.dualStack === false ? this.affinity : 0, + order: "ipv4first" + }, (err, addresses) => { + if (err) return cb(err); + const results = /* @__PURE__ */ new Map(); + for (const addr of addresses) results.set(`${addr.address}:${addr.family}`, addr); + cb(null, results.values()); + }); + } + #defaultPick(origin, hostnameRecords, affinity) { + let ip = null; + const { records, offset } = hostnameRecords; + let family; + if (this.dualStack) { + if (affinity == null) if (offset == null || offset === maxInt) { + hostnameRecords.offset = 0; + affinity = 4; + } else { + hostnameRecords.offset++; + affinity = (hostnameRecords.offset & 1) === 1 ? 6 : 4; + } + if (records[affinity] != null && records[affinity].ips.length > 0) family = records[affinity]; + else family = records[affinity === 4 ? 6 : 4]; + } else family = records[affinity]; + if (family == null || family.ips.length === 0) return ip; + if (family.offset == null || family.offset === maxInt) family.offset = 0; + else family.offset++; + const position = family.offset % family.ips.length; + ip = family.ips[position] ?? null; + if (ip == null) return ip; + if (Date.now() - ip.timestamp > ip.ttl) { + family.ips.splice(position, 1); + return this.pick(origin, hostnameRecords, affinity); + } + return ip; + } + setRecords(origin, addresses) { + const timestamp = Date.now(); + const records = { records: { + 4: null, + 6: null + } }; + for (const record of addresses) { + record.timestamp = timestamp; + if (typeof record.ttl === "number") record.ttl = Math.min(record.ttl, this.#maxTTL); + else record.ttl = this.#maxTTL; + const familyRecords = records.records[record.family] ?? { ips: [] }; + familyRecords.ips.push(record); + records.records[record.family] = familyRecords; + } + this.#records.set(origin.hostname, records); + } + getHandler(meta, opts) { + return new DNSDispatchHandler(this, meta, opts); + } + }; + var DNSDispatchHandler = class extends DecoratorHandler { + #state = null; + #opts = null; + #dispatch = null; + #handler = null; + #origin = null; + constructor(state, { origin, handler, dispatch }, opts) { + super(handler); + this.#origin = origin; + this.#handler = handler; + this.#opts = { ...opts }; + this.#state = state; + this.#dispatch = dispatch; + } + onError(err) { + switch (err.code) { + case "ETIMEDOUT": + case "ECONNREFUSED": + if (this.#state.dualStack) { + this.#state.runLookup(this.#origin, this.#opts, (err, newOrigin) => { + if (err) return this.#handler.onError(err); + const dispatchOpts = { + ...this.#opts, + origin: newOrigin + }; + this.#dispatch(dispatchOpts, this); + }); + return; + } + this.#handler.onError(err); + return; + case "ENOTFOUND": this.#state.deleteRecord(this.#origin); + default: + this.#handler.onError(err); + break; + } + } + }; + module.exports = (interceptorOpts) => { + if (interceptorOpts?.maxTTL != null && (typeof interceptorOpts?.maxTTL !== "number" || interceptorOpts?.maxTTL < 0)) throw new InvalidArgumentError("Invalid maxTTL. Must be a positive number"); + if (interceptorOpts?.maxItems != null && (typeof interceptorOpts?.maxItems !== "number" || interceptorOpts?.maxItems < 1)) throw new InvalidArgumentError("Invalid maxItems. Must be a positive number and greater than zero"); + if (interceptorOpts?.affinity != null && interceptorOpts?.affinity !== 4 && interceptorOpts?.affinity !== 6) throw new InvalidArgumentError("Invalid affinity. Must be either 4 or 6"); + if (interceptorOpts?.dualStack != null && typeof interceptorOpts?.dualStack !== "boolean") throw new InvalidArgumentError("Invalid dualStack. Must be a boolean"); + if (interceptorOpts?.lookup != null && typeof interceptorOpts?.lookup !== "function") throw new InvalidArgumentError("Invalid lookup. Must be a function"); + if (interceptorOpts?.pick != null && typeof interceptorOpts?.pick !== "function") throw new InvalidArgumentError("Invalid pick. Must be a function"); + const dualStack = interceptorOpts?.dualStack ?? true; + let affinity; + if (dualStack) affinity = interceptorOpts?.affinity ?? null; + else affinity = interceptorOpts?.affinity ?? 4; + const instance = new DNSInstance({ + maxTTL: interceptorOpts?.maxTTL ?? 1e4, + lookup: interceptorOpts?.lookup ?? null, + pick: interceptorOpts?.pick ?? null, + dualStack, + affinity, + maxItems: interceptorOpts?.maxItems ?? Infinity + }); + return (dispatch) => { + return function dnsInterceptor(origDispatchOpts, handler) { + const origin = origDispatchOpts.origin.constructor === URL ? origDispatchOpts.origin : new URL(origDispatchOpts.origin); + if (isIP(origin.hostname) !== 0) return dispatch(origDispatchOpts, handler); + instance.runLookup(origin, origDispatchOpts, (err, newOrigin) => { + if (err) return handler.onError(err); + let dispatchOpts = null; + dispatchOpts = { + ...origDispatchOpts, + servername: origin.hostname, + origin: newOrigin, + headers: { + host: origin.hostname, + ...origDispatchOpts.headers + } + }; + dispatch(dispatchOpts, instance.getHandler({ + origin, + dispatch, + handler + }, origDispatchOpts)); + }); + return true; + }; + }; + }; +})); +//#endregion +//#region node_modules/undici/lib/web/fetch/headers.js +var require_headers = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { kConstruct } = require_symbols$4(); + var { kEnumerableProperty } = require_util$7(); + var { iteratorMixin, isValidHeaderName, isValidHeaderValue } = require_util$6(); + var { webidl } = require_webidl(); + var assert$7 = __require("node:assert"); + var util = __require("node:util"); + var kHeadersMap = Symbol("headers map"); + var kHeadersSortedMap = Symbol("headers map sorted"); + /** + * @param {number} code + */ + function isHTTPWhiteSpaceCharCode(code) { + return code === 10 || code === 13 || code === 9 || code === 32; + } + /** + * @see https://fetch.spec.whatwg.org/#concept-header-value-normalize + * @param {string} potentialValue + */ + function headerValueNormalize(potentialValue) { + let i = 0; + let j = potentialValue.length; + while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(j - 1))) --j; + while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(i))) ++i; + return i === 0 && j === potentialValue.length ? potentialValue : potentialValue.substring(i, j); + } + function fill(headers, object) { + if (Array.isArray(object)) for (let i = 0; i < object.length; ++i) { + const header = object[i]; + if (header.length !== 2) throw webidl.errors.exception({ + header: "Headers constructor", + message: `expected name/value pair to be length 2, found ${header.length}.` + }); + appendHeader(headers, header[0], header[1]); + } + else if (typeof object === "object" && object !== null) { + const keys = Object.keys(object); + for (let i = 0; i < keys.length; ++i) appendHeader(headers, keys[i], object[keys[i]]); + } else throw webidl.errors.conversionFailed({ + prefix: "Headers constructor", + argument: "Argument 1", + types: ["sequence>", "record"] + }); + } + /** + * @see https://fetch.spec.whatwg.org/#concept-headers-append + */ + function appendHeader(headers, name, value) { + value = headerValueNormalize(value); + if (!isValidHeaderName(name)) throw webidl.errors.invalidArgument({ + prefix: "Headers.append", + value: name, + type: "header name" + }); + else if (!isValidHeaderValue(value)) throw webidl.errors.invalidArgument({ + prefix: "Headers.append", + value, + type: "header value" + }); + if (getHeadersGuard(headers) === "immutable") throw new TypeError("immutable"); + return getHeadersList(headers).append(name, value, false); + } + function compareHeaderName(a, b) { + return a[0] < b[0] ? -1 : 1; + } + var HeadersList = class HeadersList { + /** @type {[string, string][]|null} */ + cookies = null; + constructor(init) { + if (init instanceof HeadersList) { + this[kHeadersMap] = new Map(init[kHeadersMap]); + this[kHeadersSortedMap] = init[kHeadersSortedMap]; + this.cookies = init.cookies === null ? null : [...init.cookies]; + } else { + this[kHeadersMap] = new Map(init); + this[kHeadersSortedMap] = null; + } + } + /** + * @see https://fetch.spec.whatwg.org/#header-list-contains + * @param {string} name + * @param {boolean} isLowerCase + */ + contains(name, isLowerCase) { + return this[kHeadersMap].has(isLowerCase ? name : name.toLowerCase()); + } + clear() { + this[kHeadersMap].clear(); + this[kHeadersSortedMap] = null; + this.cookies = null; + } + /** + * @see https://fetch.spec.whatwg.org/#concept-header-list-append + * @param {string} name + * @param {string} value + * @param {boolean} isLowerCase + */ + append(name, value, isLowerCase) { + this[kHeadersSortedMap] = null; + const lowercaseName = isLowerCase ? name : name.toLowerCase(); + const exists = this[kHeadersMap].get(lowercaseName); + if (exists) { + const delimiter = lowercaseName === "cookie" ? "; " : ", "; + this[kHeadersMap].set(lowercaseName, { + name: exists.name, + value: `${exists.value}${delimiter}${value}` + }); + } else this[kHeadersMap].set(lowercaseName, { + name, + value + }); + if (lowercaseName === "set-cookie") (this.cookies ??= []).push(value); + } + /** + * @see https://fetch.spec.whatwg.org/#concept-header-list-set + * @param {string} name + * @param {string} value + * @param {boolean} isLowerCase + */ + set(name, value, isLowerCase) { + this[kHeadersSortedMap] = null; + const lowercaseName = isLowerCase ? name : name.toLowerCase(); + if (lowercaseName === "set-cookie") this.cookies = [value]; + this[kHeadersMap].set(lowercaseName, { + name, + value + }); + } + /** + * @see https://fetch.spec.whatwg.org/#concept-header-list-delete + * @param {string} name + * @param {boolean} isLowerCase + */ + delete(name, isLowerCase) { + this[kHeadersSortedMap] = null; + if (!isLowerCase) name = name.toLowerCase(); + if (name === "set-cookie") this.cookies = null; + this[kHeadersMap].delete(name); + } + /** + * @see https://fetch.spec.whatwg.org/#concept-header-list-get + * @param {string} name + * @param {boolean} isLowerCase + * @returns {string | null} + */ + get(name, isLowerCase) { + return this[kHeadersMap].get(isLowerCase ? name : name.toLowerCase())?.value ?? null; + } + *[Symbol.iterator]() { + for (const { 0: name, 1: { value } } of this[kHeadersMap]) yield [name, value]; + } + get entries() { + const headers = {}; + if (this[kHeadersMap].size !== 0) for (const { name, value } of this[kHeadersMap].values()) headers[name] = value; + return headers; + } + rawValues() { + return this[kHeadersMap].values(); + } + get entriesList() { + const headers = []; + if (this[kHeadersMap].size !== 0) for (const { 0: lowerName, 1: { name, value } } of this[kHeadersMap]) if (lowerName === "set-cookie") for (const cookie of this.cookies) headers.push([name, cookie]); + else headers.push([name, value]); + return headers; + } + toSortedArray() { + const size = this[kHeadersMap].size; + const array = new Array(size); + if (size <= 32) { + if (size === 0) return array; + const iterator = this[kHeadersMap][Symbol.iterator](); + const firstValue = iterator.next().value; + array[0] = [firstValue[0], firstValue[1].value]; + assert$7(firstValue[1].value !== null); + for (let i = 1, j = 0, right = 0, left = 0, pivot = 0, x, value; i < size; ++i) { + value = iterator.next().value; + x = array[i] = [value[0], value[1].value]; + assert$7(x[1] !== null); + left = 0; + right = i; + while (left < right) { + pivot = left + (right - left >> 1); + if (array[pivot][0] <= x[0]) left = pivot + 1; + else right = pivot; + } + if (i !== pivot) { + j = i; + while (j > left) array[j] = array[--j]; + array[left] = x; + } + } + /* c8 ignore next 4 */ + if (!iterator.next().done) throw new TypeError("Unreachable"); + return array; + } else { + let i = 0; + for (const { 0: name, 1: { value } } of this[kHeadersMap]) { + array[i++] = [name, value]; + assert$7(value !== null); + } + return array.sort(compareHeaderName); + } + } + }; + var Headers = class Headers { + #guard; + #headersList; + constructor(init = void 0) { + webidl.util.markAsUncloneable(this); + if (init === kConstruct) return; + this.#headersList = new HeadersList(); + this.#guard = "none"; + if (init !== void 0) { + init = webidl.converters.HeadersInit(init, "Headers contructor", "init"); + fill(this, init); + } + } + append(name, value) { + webidl.brandCheck(this, Headers); + webidl.argumentLengthCheck(arguments, 2, "Headers.append"); + const prefix = "Headers.append"; + name = webidl.converters.ByteString(name, prefix, "name"); + value = webidl.converters.ByteString(value, prefix, "value"); + return appendHeader(this, name, value); + } + delete(name) { + webidl.brandCheck(this, Headers); + webidl.argumentLengthCheck(arguments, 1, "Headers.delete"); + name = webidl.converters.ByteString(name, "Headers.delete", "name"); + if (!isValidHeaderName(name)) throw webidl.errors.invalidArgument({ + prefix: "Headers.delete", + value: name, + type: "header name" + }); + if (this.#guard === "immutable") throw new TypeError("immutable"); + if (!this.#headersList.contains(name, false)) return; + this.#headersList.delete(name, false); + } + get(name) { + webidl.brandCheck(this, Headers); + webidl.argumentLengthCheck(arguments, 1, "Headers.get"); + const prefix = "Headers.get"; + name = webidl.converters.ByteString(name, prefix, "name"); + if (!isValidHeaderName(name)) throw webidl.errors.invalidArgument({ + prefix, + value: name, + type: "header name" + }); + return this.#headersList.get(name, false); + } + has(name) { + webidl.brandCheck(this, Headers); + webidl.argumentLengthCheck(arguments, 1, "Headers.has"); + const prefix = "Headers.has"; + name = webidl.converters.ByteString(name, prefix, "name"); + if (!isValidHeaderName(name)) throw webidl.errors.invalidArgument({ + prefix, + value: name, + type: "header name" + }); + return this.#headersList.contains(name, false); + } + set(name, value) { + webidl.brandCheck(this, Headers); + webidl.argumentLengthCheck(arguments, 2, "Headers.set"); + const prefix = "Headers.set"; + name = webidl.converters.ByteString(name, prefix, "name"); + value = webidl.converters.ByteString(value, prefix, "value"); + value = headerValueNormalize(value); + if (!isValidHeaderName(name)) throw webidl.errors.invalidArgument({ + prefix, + value: name, + type: "header name" + }); + else if (!isValidHeaderValue(value)) throw webidl.errors.invalidArgument({ + prefix, + value, + type: "header value" + }); + if (this.#guard === "immutable") throw new TypeError("immutable"); + this.#headersList.set(name, value, false); + } + getSetCookie() { + webidl.brandCheck(this, Headers); + const list = this.#headersList.cookies; + if (list) return [...list]; + return []; + } + get [kHeadersSortedMap]() { + if (this.#headersList[kHeadersSortedMap]) return this.#headersList[kHeadersSortedMap]; + const headers = []; + const names = this.#headersList.toSortedArray(); + const cookies = this.#headersList.cookies; + if (cookies === null || cookies.length === 1) return this.#headersList[kHeadersSortedMap] = names; + for (let i = 0; i < names.length; ++i) { + const { 0: name, 1: value } = names[i]; + if (name === "set-cookie") for (let j = 0; j < cookies.length; ++j) headers.push([name, cookies[j]]); + else headers.push([name, value]); + } + return this.#headersList[kHeadersSortedMap] = headers; + } + [util.inspect.custom](depth, options) { + options.depth ??= depth; + return `Headers ${util.formatWithOptions(options, this.#headersList.entries)}`; + } + static getHeadersGuard(o) { + return o.#guard; + } + static setHeadersGuard(o, guard) { + o.#guard = guard; + } + static getHeadersList(o) { + return o.#headersList; + } + static setHeadersList(o, list) { + o.#headersList = list; + } + }; + var { getHeadersGuard, setHeadersGuard, getHeadersList, setHeadersList } = Headers; + Reflect.deleteProperty(Headers, "getHeadersGuard"); + Reflect.deleteProperty(Headers, "setHeadersGuard"); + Reflect.deleteProperty(Headers, "getHeadersList"); + Reflect.deleteProperty(Headers, "setHeadersList"); + iteratorMixin("Headers", Headers, kHeadersSortedMap, 0, 1); + Object.defineProperties(Headers.prototype, { + append: kEnumerableProperty, + delete: kEnumerableProperty, + get: kEnumerableProperty, + has: kEnumerableProperty, + set: kEnumerableProperty, + getSetCookie: kEnumerableProperty, + [Symbol.toStringTag]: { + value: "Headers", + configurable: true + }, + [util.inspect.custom]: { enumerable: false } + }); + webidl.converters.HeadersInit = function(V, prefix, argument) { + if (webidl.util.Type(V) === "Object") { + const iterator = Reflect.get(V, Symbol.iterator); + if (!util.types.isProxy(V) && iterator === Headers.prototype.entries) try { + return getHeadersList(V).entriesList; + } catch {} + if (typeof iterator === "function") return webidl.converters["sequence>"](V, prefix, argument, iterator.bind(V)); + return webidl.converters["record"](V, prefix, argument); + } + throw webidl.errors.conversionFailed({ + prefix: "Headers constructor", + argument: "Argument 1", + types: ["sequence>", "record"] + }); + }; + module.exports = { + fill, + compareHeaderName, + Headers, + HeadersList, + getHeadersGuard, + setHeadersGuard, + setHeadersList, + getHeadersList + }; +})); +//#endregion +//#region node_modules/undici/lib/web/fetch/response.js +var require_response = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { Headers, HeadersList, fill, getHeadersGuard, setHeadersGuard, setHeadersList } = require_headers(); + var { extractBody, cloneBody, mixinBody, hasFinalizationRegistry, streamRegistry, bodyUnusable } = require_body(); + var util = require_util$7(); + var nodeUtil$1 = __require("node:util"); + var { kEnumerableProperty } = util; + var { isValidReasonPhrase, isCancelled, isAborted, isBlobLike, serializeJavascriptValueToJSONString, isErrorLike, isomorphicEncode, environmentSettingsObject: relevantRealm } = require_util$6(); + var { redirectStatusSet, nullBodyStatus } = require_constants$2(); + var { kState, kHeaders } = require_symbols$3(); + var { webidl } = require_webidl(); + var { FormData } = require_formdata(); + var { URLSerializer } = require_data_url(); + var { kConstruct } = require_symbols$4(); + var assert$6 = __require("node:assert"); + var { types: types$2 } = __require("node:util"); + var textEncoder = new TextEncoder("utf-8"); + var Response = class Response { + static error() { + return fromInnerResponse(makeNetworkError(), "immutable"); + } + static json(data, init = {}) { + webidl.argumentLengthCheck(arguments, 1, "Response.json"); + if (init !== null) init = webidl.converters.ResponseInit(init); + const body = extractBody(textEncoder.encode(serializeJavascriptValueToJSONString(data))); + const responseObject = fromInnerResponse(makeResponse({}), "response"); + initializeResponse(responseObject, init, { + body: body[0], + type: "application/json" + }); + return responseObject; + } + static redirect(url, status = 302) { + webidl.argumentLengthCheck(arguments, 1, "Response.redirect"); + url = webidl.converters.USVString(url); + status = webidl.converters["unsigned short"](status); + let parsedURL; + try { + parsedURL = new URL(url, relevantRealm.settingsObject.baseUrl); + } catch (err) { + throw new TypeError(`Failed to parse URL from ${url}`, { cause: err }); + } + if (!redirectStatusSet.has(status)) throw new RangeError(`Invalid status code ${status}`); + const responseObject = fromInnerResponse(makeResponse({}), "immutable"); + responseObject[kState].status = status; + const value = isomorphicEncode(URLSerializer(parsedURL)); + responseObject[kState].headersList.append("location", value, true); + return responseObject; + } + constructor(body = null, init = {}) { + webidl.util.markAsUncloneable(this); + if (body === kConstruct) return; + if (body !== null) body = webidl.converters.BodyInit(body); + init = webidl.converters.ResponseInit(init); + this[kState] = makeResponse({}); + this[kHeaders] = new Headers(kConstruct); + setHeadersGuard(this[kHeaders], "response"); + setHeadersList(this[kHeaders], this[kState].headersList); + let bodyWithType = null; + if (body != null) { + const [extractedBody, type] = extractBody(body); + bodyWithType = { + body: extractedBody, + type + }; + } + initializeResponse(this, init, bodyWithType); + } + get type() { + webidl.brandCheck(this, Response); + return this[kState].type; + } + get url() { + webidl.brandCheck(this, Response); + const urlList = this[kState].urlList; + const url = urlList[urlList.length - 1] ?? null; + if (url === null) return ""; + return URLSerializer(url, true); + } + get redirected() { + webidl.brandCheck(this, Response); + return this[kState].urlList.length > 1; + } + get status() { + webidl.brandCheck(this, Response); + return this[kState].status; + } + get ok() { + webidl.brandCheck(this, Response); + return this[kState].status >= 200 && this[kState].status <= 299; + } + get statusText() { + webidl.brandCheck(this, Response); + return this[kState].statusText; + } + get headers() { + webidl.brandCheck(this, Response); + return this[kHeaders]; + } + get body() { + webidl.brandCheck(this, Response); + return this[kState].body ? this[kState].body.stream : null; + } + get bodyUsed() { + webidl.brandCheck(this, Response); + return !!this[kState].body && util.isDisturbed(this[kState].body.stream); + } + clone() { + webidl.brandCheck(this, Response); + if (bodyUnusable(this)) throw webidl.errors.exception({ + header: "Response.clone", + message: "Body has already been consumed." + }); + const clonedResponse = cloneResponse(this[kState]); + if (hasFinalizationRegistry && this[kState].body?.stream) streamRegistry.register(this, new WeakRef(this[kState].body.stream)); + return fromInnerResponse(clonedResponse, getHeadersGuard(this[kHeaders])); + } + [nodeUtil$1.inspect.custom](depth, options) { + if (options.depth === null) options.depth = 2; + options.colors ??= true; + const properties = { + status: this.status, + statusText: this.statusText, + headers: this.headers, + body: this.body, + bodyUsed: this.bodyUsed, + ok: this.ok, + redirected: this.redirected, + type: this.type, + url: this.url + }; + return `Response ${nodeUtil$1.formatWithOptions(options, properties)}`; + } + }; + mixinBody(Response); + Object.defineProperties(Response.prototype, { + type: kEnumerableProperty, + url: kEnumerableProperty, + status: kEnumerableProperty, + ok: kEnumerableProperty, + redirected: kEnumerableProperty, + statusText: kEnumerableProperty, + headers: kEnumerableProperty, + clone: kEnumerableProperty, + body: kEnumerableProperty, + bodyUsed: kEnumerableProperty, + [Symbol.toStringTag]: { + value: "Response", + configurable: true + } + }); + Object.defineProperties(Response, { + json: kEnumerableProperty, + redirect: kEnumerableProperty, + error: kEnumerableProperty + }); + function cloneResponse(response) { + if (response.internalResponse) return filterResponse(cloneResponse(response.internalResponse), response.type); + const newResponse = makeResponse({ + ...response, + body: null + }); + if (response.body != null) newResponse.body = cloneBody(newResponse, response.body); + return newResponse; + } + function makeResponse(init) { + return { + aborted: false, + rangeRequested: false, + timingAllowPassed: false, + requestIncludesCredentials: false, + type: "default", + status: 200, + timingInfo: null, + cacheState: "", + statusText: "", + ...init, + headersList: init?.headersList ? new HeadersList(init?.headersList) : new HeadersList(), + urlList: init?.urlList ? [...init.urlList] : [] + }; + } + function makeNetworkError(reason) { + return makeResponse({ + type: "error", + status: 0, + error: isErrorLike(reason) ? reason : new Error(reason ? String(reason) : reason), + aborted: reason && reason.name === "AbortError" + }); + } + function isNetworkError(response) { + return response.type === "error" && response.status === 0; + } + function makeFilteredResponse(response, state) { + state = { + internalResponse: response, + ...state + }; + return new Proxy(response, { + get(target, p) { + return p in state ? state[p] : target[p]; + }, + set(target, p, value) { + assert$6(!(p in state)); + target[p] = value; + return true; + } + }); + } + function filterResponse(response, type) { + if (type === "basic") return makeFilteredResponse(response, { + type: "basic", + headersList: response.headersList + }); + else if (type === "cors") return makeFilteredResponse(response, { + type: "cors", + headersList: response.headersList + }); + else if (type === "opaque") return makeFilteredResponse(response, { + type: "opaque", + urlList: Object.freeze([]), + status: 0, + statusText: "", + body: null + }); + else if (type === "opaqueredirect") return makeFilteredResponse(response, { + type: "opaqueredirect", + status: 0, + statusText: "", + headersList: [], + body: null + }); + else assert$6(false); + } + function makeAppropriateNetworkError(fetchParams, err = null) { + assert$6(isCancelled(fetchParams)); + return isAborted(fetchParams) ? makeNetworkError(Object.assign(new DOMException("The operation was aborted.", "AbortError"), { cause: err })) : makeNetworkError(Object.assign(new DOMException("Request was cancelled."), { cause: err })); + } + function initializeResponse(response, init, body) { + if (init.status !== null && (init.status < 200 || init.status > 599)) throw new RangeError("init[\"status\"] must be in the range of 200 to 599, inclusive."); + if ("statusText" in init && init.statusText != null) { + if (!isValidReasonPhrase(String(init.statusText))) throw new TypeError("Invalid statusText"); + } + if ("status" in init && init.status != null) response[kState].status = init.status; + if ("statusText" in init && init.statusText != null) response[kState].statusText = init.statusText; + if ("headers" in init && init.headers != null) fill(response[kHeaders], init.headers); + if (body) { + if (nullBodyStatus.includes(response.status)) throw webidl.errors.exception({ + header: "Response constructor", + message: `Invalid response status code ${response.status}` + }); + response[kState].body = body.body; + if (body.type != null && !response[kState].headersList.contains("content-type", true)) response[kState].headersList.append("content-type", body.type, true); + } + } + /** + * @see https://fetch.spec.whatwg.org/#response-create + * @param {any} innerResponse + * @param {'request' | 'immutable' | 'request-no-cors' | 'response' | 'none'} guard + * @returns {Response} + */ + function fromInnerResponse(innerResponse, guard) { + const response = new Response(kConstruct); + response[kState] = innerResponse; + response[kHeaders] = new Headers(kConstruct); + setHeadersList(response[kHeaders], innerResponse.headersList); + setHeadersGuard(response[kHeaders], guard); + if (hasFinalizationRegistry && innerResponse.body?.stream) streamRegistry.register(response, new WeakRef(innerResponse.body.stream)); + return response; + } + webidl.converters.ReadableStream = webidl.interfaceConverter(ReadableStream); + webidl.converters.FormData = webidl.interfaceConverter(FormData); + webidl.converters.URLSearchParams = webidl.interfaceConverter(URLSearchParams); + webidl.converters.XMLHttpRequestBodyInit = function(V, prefix, name) { + if (typeof V === "string") return webidl.converters.USVString(V, prefix, name); + if (isBlobLike(V)) return webidl.converters.Blob(V, prefix, name, { strict: false }); + if (ArrayBuffer.isView(V) || types$2.isArrayBuffer(V)) return webidl.converters.BufferSource(V, prefix, name); + if (util.isFormDataLike(V)) return webidl.converters.FormData(V, prefix, name, { strict: false }); + if (V instanceof URLSearchParams) return webidl.converters.URLSearchParams(V, prefix, name); + return webidl.converters.DOMString(V, prefix, name); + }; + webidl.converters.BodyInit = function(V, prefix, argument) { + if (V instanceof ReadableStream) return webidl.converters.ReadableStream(V, prefix, argument); + if (V?.[Symbol.asyncIterator]) return V; + return webidl.converters.XMLHttpRequestBodyInit(V, prefix, argument); + }; + webidl.converters.ResponseInit = webidl.dictionaryConverter([ + { + key: "status", + converter: webidl.converters["unsigned short"], + defaultValue: () => 200 + }, + { + key: "statusText", + converter: webidl.converters.ByteString, + defaultValue: () => "" + }, + { + key: "headers", + converter: webidl.converters.HeadersInit + } + ]); + module.exports = { + isNetworkError, + makeNetworkError, + makeResponse, + makeAppropriateNetworkError, + filterResponse, + Response, + cloneResponse, + fromInnerResponse + }; +})); +//#endregion +//#region node_modules/undici/lib/web/fetch/dispatcher-weakref.js +var require_dispatcher_weakref = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { kConnected, kSize } = require_symbols$4(); + var CompatWeakRef = class { + constructor(value) { + this.value = value; + } + deref() { + return this.value[kConnected] === 0 && this.value[kSize] === 0 ? void 0 : this.value; + } + }; + var CompatFinalizer = class { + constructor(finalizer) { + this.finalizer = finalizer; + } + register(dispatcher, key) { + if (dispatcher.on) dispatcher.on("disconnect", () => { + if (dispatcher[kConnected] === 0 && dispatcher[kSize] === 0) this.finalizer(key); + }); + } + unregister(key) {} + }; + module.exports = function() { + if (process.env.NODE_V8_COVERAGE && process.version.startsWith("v18")) { + process._rawDebug("Using compatibility WeakRef and FinalizationRegistry"); + return { + WeakRef: CompatWeakRef, + FinalizationRegistry: CompatFinalizer + }; + } + return { + WeakRef, + FinalizationRegistry + }; + }; +})); +//#endregion +//#region node_modules/undici/lib/web/fetch/request.js +var require_request = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { extractBody, mixinBody, cloneBody, bodyUnusable } = require_body(); + var { Headers, fill: fillHeaders, HeadersList, setHeadersGuard, getHeadersGuard, setHeadersList, getHeadersList } = require_headers(); + var { FinalizationRegistry } = require_dispatcher_weakref()(); + var util = require_util$7(); + var nodeUtil = __require("node:util"); + var { isValidHTTPToken, sameOrigin, environmentSettingsObject } = require_util$6(); + var { forbiddenMethodsSet, corsSafeListedMethodsSet, referrerPolicy, requestRedirect, requestMode, requestCredentials, requestCache, requestDuplex } = require_constants$2(); + var { kEnumerableProperty, normalizedMethodRecordsBase, normalizedMethodRecords } = util; + var { kHeaders, kSignal, kState, kDispatcher } = require_symbols$3(); + var { webidl } = require_webidl(); + var { URLSerializer } = require_data_url(); + var { kConstruct } = require_symbols$4(); + var assert$5 = __require("node:assert"); + var { getMaxListeners, setMaxListeners, getEventListeners, defaultMaxListeners } = __require("node:events"); + var kAbortController = Symbol("abortController"); + var requestFinalizer = new FinalizationRegistry(({ signal, abort }) => { + signal.removeEventListener("abort", abort); + }); + var dependentControllerMap = /* @__PURE__ */ new WeakMap(); + function buildAbort(acRef) { + return abort; + function abort() { + const ac = acRef.deref(); + if (ac !== void 0) { + requestFinalizer.unregister(abort); + this.removeEventListener("abort", abort); + ac.abort(this.reason); + const controllerList = dependentControllerMap.get(ac.signal); + if (controllerList !== void 0) { + if (controllerList.size !== 0) { + for (const ref of controllerList) { + const ctrl = ref.deref(); + if (ctrl !== void 0) ctrl.abort(this.reason); + } + controllerList.clear(); + } + dependentControllerMap.delete(ac.signal); + } + } + } + } + var patchMethodWarning = false; + var Request = class Request { + constructor(input, init = {}) { + webidl.util.markAsUncloneable(this); + if (input === kConstruct) return; + const prefix = "Request constructor"; + webidl.argumentLengthCheck(arguments, 1, prefix); + input = webidl.converters.RequestInfo(input, prefix, "input"); + init = webidl.converters.RequestInit(init, prefix, "init"); + let request = null; + let fallbackMode = null; + const baseUrl = environmentSettingsObject.settingsObject.baseUrl; + let signal = null; + if (typeof input === "string") { + this[kDispatcher] = init.dispatcher; + let parsedURL; + try { + parsedURL = new URL(input, baseUrl); + } catch (err) { + throw new TypeError("Failed to parse URL from " + input, { cause: err }); + } + if (parsedURL.username || parsedURL.password) throw new TypeError("Request cannot be constructed from a URL that includes credentials: " + input); + request = makeRequest({ urlList: [parsedURL] }); + fallbackMode = "cors"; + } else { + this[kDispatcher] = init.dispatcher || input[kDispatcher]; + assert$5(input instanceof Request); + request = input[kState]; + signal = input[kSignal]; + } + const origin = environmentSettingsObject.settingsObject.origin; + let window = "client"; + if (request.window?.constructor?.name === "EnvironmentSettingsObject" && sameOrigin(request.window, origin)) window = request.window; + if (init.window != null) throw new TypeError(`'window' option '${window}' must be null`); + if ("window" in init) window = "no-window"; + request = makeRequest({ + method: request.method, + headersList: request.headersList, + unsafeRequest: request.unsafeRequest, + client: environmentSettingsObject.settingsObject, + window, + priority: request.priority, + origin: request.origin, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + mode: request.mode, + credentials: request.credentials, + cache: request.cache, + redirect: request.redirect, + integrity: request.integrity, + keepalive: request.keepalive, + reloadNavigation: request.reloadNavigation, + historyNavigation: request.historyNavigation, + urlList: [...request.urlList] + }); + const initHasKey = Object.keys(init).length !== 0; + if (initHasKey) { + if (request.mode === "navigate") request.mode = "same-origin"; + request.reloadNavigation = false; + request.historyNavigation = false; + request.origin = "client"; + request.referrer = "client"; + request.referrerPolicy = ""; + request.url = request.urlList[request.urlList.length - 1]; + request.urlList = [request.url]; + } + if (init.referrer !== void 0) { + const referrer = init.referrer; + if (referrer === "") request.referrer = "no-referrer"; + else { + let parsedReferrer; + try { + parsedReferrer = new URL(referrer, baseUrl); + } catch (err) { + throw new TypeError(`Referrer "${referrer}" is not a valid URL.`, { cause: err }); + } + if (parsedReferrer.protocol === "about:" && parsedReferrer.hostname === "client" || origin && !sameOrigin(parsedReferrer, environmentSettingsObject.settingsObject.baseUrl)) request.referrer = "client"; + else request.referrer = parsedReferrer; + } + } + if (init.referrerPolicy !== void 0) request.referrerPolicy = init.referrerPolicy; + let mode; + if (init.mode !== void 0) mode = init.mode; + else mode = fallbackMode; + if (mode === "navigate") throw webidl.errors.exception({ + header: "Request constructor", + message: "invalid request mode navigate." + }); + if (mode != null) request.mode = mode; + if (init.credentials !== void 0) request.credentials = init.credentials; + if (init.cache !== void 0) request.cache = init.cache; + if (request.cache === "only-if-cached" && request.mode !== "same-origin") throw new TypeError("'only-if-cached' can be set only with 'same-origin' mode"); + if (init.redirect !== void 0) request.redirect = init.redirect; + if (init.integrity != null) request.integrity = String(init.integrity); + if (init.keepalive !== void 0) request.keepalive = Boolean(init.keepalive); + if (init.method !== void 0) { + let method = init.method; + const mayBeNormalized = normalizedMethodRecords[method]; + if (mayBeNormalized !== void 0) request.method = mayBeNormalized; + else { + if (!isValidHTTPToken(method)) throw new TypeError(`'${method}' is not a valid HTTP method.`); + const upperCase = method.toUpperCase(); + if (forbiddenMethodsSet.has(upperCase)) throw new TypeError(`'${method}' HTTP method is unsupported.`); + method = normalizedMethodRecordsBase[upperCase] ?? method; + request.method = method; + } + if (!patchMethodWarning && request.method === "patch") { + process.emitWarning("Using `patch` is highly likely to result in a `405 Method Not Allowed`. `PATCH` is much more likely to succeed.", { code: "UNDICI-FETCH-patch" }); + patchMethodWarning = true; + } + } + if (init.signal !== void 0) signal = init.signal; + this[kState] = request; + const ac = new AbortController(); + this[kSignal] = ac.signal; + if (signal != null) { + if (!signal || typeof signal.aborted !== "boolean" || typeof signal.addEventListener !== "function") throw new TypeError("Failed to construct 'Request': member signal is not of type AbortSignal."); + if (signal.aborted) ac.abort(signal.reason); + else { + this[kAbortController] = ac; + const abort = buildAbort(new WeakRef(ac)); + try { + if (typeof getMaxListeners === "function" && getMaxListeners(signal) === defaultMaxListeners) setMaxListeners(1500, signal); + else if (getEventListeners(signal, "abort").length >= defaultMaxListeners) setMaxListeners(1500, signal); + } catch {} + util.addAbortListener(signal, abort); + requestFinalizer.register(ac, { + signal, + abort + }, abort); + } + } + this[kHeaders] = new Headers(kConstruct); + setHeadersList(this[kHeaders], request.headersList); + setHeadersGuard(this[kHeaders], "request"); + if (mode === "no-cors") { + if (!corsSafeListedMethodsSet.has(request.method)) throw new TypeError(`'${request.method} is unsupported in no-cors mode.`); + setHeadersGuard(this[kHeaders], "request-no-cors"); + } + if (initHasKey) { + /** @type {HeadersList} */ + const headersList = getHeadersList(this[kHeaders]); + const headers = init.headers !== void 0 ? init.headers : new HeadersList(headersList); + headersList.clear(); + if (headers instanceof HeadersList) { + for (const { name, value } of headers.rawValues()) headersList.append(name, value, false); + headersList.cookies = headers.cookies; + } else fillHeaders(this[kHeaders], headers); + } + const inputBody = input instanceof Request ? input[kState].body : null; + if ((init.body != null || inputBody != null) && (request.method === "GET" || request.method === "HEAD")) throw new TypeError("Request with GET/HEAD method cannot have body."); + let initBody = null; + if (init.body != null) { + const [extractedBody, contentType] = extractBody(init.body, request.keepalive); + initBody = extractedBody; + if (contentType && !getHeadersList(this[kHeaders]).contains("content-type", true)) this[kHeaders].append("content-type", contentType); + } + const inputOrInitBody = initBody ?? inputBody; + if (inputOrInitBody != null && inputOrInitBody.source == null) { + if (initBody != null && init.duplex == null) throw new TypeError("RequestInit: duplex option is required when sending a body."); + if (request.mode !== "same-origin" && request.mode !== "cors") throw new TypeError("If request is made from ReadableStream, mode should be \"same-origin\" or \"cors\""); + request.useCORSPreflightFlag = true; + } + let finalBody = inputOrInitBody; + if (initBody == null && inputBody != null) { + if (bodyUnusable(input)) throw new TypeError("Cannot construct a Request with a Request object that has already been used."); + const identityTransform = new TransformStream(); + inputBody.stream.pipeThrough(identityTransform); + finalBody = { + source: inputBody.source, + length: inputBody.length, + stream: identityTransform.readable + }; + } + this[kState].body = finalBody; + } + get method() { + webidl.brandCheck(this, Request); + return this[kState].method; + } + get url() { + webidl.brandCheck(this, Request); + return URLSerializer(this[kState].url); + } + get headers() { + webidl.brandCheck(this, Request); + return this[kHeaders]; + } + get destination() { + webidl.brandCheck(this, Request); + return this[kState].destination; + } + get referrer() { + webidl.brandCheck(this, Request); + if (this[kState].referrer === "no-referrer") return ""; + if (this[kState].referrer === "client") return "about:client"; + return this[kState].referrer.toString(); + } + get referrerPolicy() { + webidl.brandCheck(this, Request); + return this[kState].referrerPolicy; + } + get mode() { + webidl.brandCheck(this, Request); + return this[kState].mode; + } + get credentials() { + return this[kState].credentials; + } + get cache() { + webidl.brandCheck(this, Request); + return this[kState].cache; + } + get redirect() { + webidl.brandCheck(this, Request); + return this[kState].redirect; + } + get integrity() { + webidl.brandCheck(this, Request); + return this[kState].integrity; + } + get keepalive() { + webidl.brandCheck(this, Request); + return this[kState].keepalive; + } + get isReloadNavigation() { + webidl.brandCheck(this, Request); + return this[kState].reloadNavigation; + } + get isHistoryNavigation() { + webidl.brandCheck(this, Request); + return this[kState].historyNavigation; + } + get signal() { + webidl.brandCheck(this, Request); + return this[kSignal]; + } + get body() { + webidl.brandCheck(this, Request); + return this[kState].body ? this[kState].body.stream : null; + } + get bodyUsed() { + webidl.brandCheck(this, Request); + return !!this[kState].body && util.isDisturbed(this[kState].body.stream); + } + get duplex() { + webidl.brandCheck(this, Request); + return "half"; + } + clone() { + webidl.brandCheck(this, Request); + if (bodyUnusable(this)) throw new TypeError("unusable"); + const clonedRequest = cloneRequest(this[kState]); + const ac = new AbortController(); + if (this.signal.aborted) ac.abort(this.signal.reason); + else { + let list = dependentControllerMap.get(this.signal); + if (list === void 0) { + list = /* @__PURE__ */ new Set(); + dependentControllerMap.set(this.signal, list); + } + const acRef = new WeakRef(ac); + list.add(acRef); + util.addAbortListener(ac.signal, buildAbort(acRef)); + } + return fromInnerRequest(clonedRequest, ac.signal, getHeadersGuard(this[kHeaders])); + } + [nodeUtil.inspect.custom](depth, options) { + if (options.depth === null) options.depth = 2; + options.colors ??= true; + const properties = { + method: this.method, + url: this.url, + headers: this.headers, + destination: this.destination, + referrer: this.referrer, + referrerPolicy: this.referrerPolicy, + mode: this.mode, + credentials: this.credentials, + cache: this.cache, + redirect: this.redirect, + integrity: this.integrity, + keepalive: this.keepalive, + isReloadNavigation: this.isReloadNavigation, + isHistoryNavigation: this.isHistoryNavigation, + signal: this.signal + }; + return `Request ${nodeUtil.formatWithOptions(options, properties)}`; + } + }; + mixinBody(Request); + function makeRequest(init) { + return { + method: init.method ?? "GET", + localURLsOnly: init.localURLsOnly ?? false, + unsafeRequest: init.unsafeRequest ?? false, + body: init.body ?? null, + client: init.client ?? null, + reservedClient: init.reservedClient ?? null, + replacesClientId: init.replacesClientId ?? "", + window: init.window ?? "client", + keepalive: init.keepalive ?? false, + serviceWorkers: init.serviceWorkers ?? "all", + initiator: init.initiator ?? "", + destination: init.destination ?? "", + priority: init.priority ?? null, + origin: init.origin ?? "client", + policyContainer: init.policyContainer ?? "client", + referrer: init.referrer ?? "client", + referrerPolicy: init.referrerPolicy ?? "", + mode: init.mode ?? "no-cors", + useCORSPreflightFlag: init.useCORSPreflightFlag ?? false, + credentials: init.credentials ?? "same-origin", + useCredentials: init.useCredentials ?? false, + cache: init.cache ?? "default", + redirect: init.redirect ?? "follow", + integrity: init.integrity ?? "", + cryptoGraphicsNonceMetadata: init.cryptoGraphicsNonceMetadata ?? "", + parserMetadata: init.parserMetadata ?? "", + reloadNavigation: init.reloadNavigation ?? false, + historyNavigation: init.historyNavigation ?? false, + userActivation: init.userActivation ?? false, + taintedOrigin: init.taintedOrigin ?? false, + redirectCount: init.redirectCount ?? 0, + responseTainting: init.responseTainting ?? "basic", + preventNoCacheCacheControlHeaderModification: init.preventNoCacheCacheControlHeaderModification ?? false, + done: init.done ?? false, + timingAllowFailed: init.timingAllowFailed ?? false, + urlList: init.urlList, + url: init.urlList[0], + headersList: init.headersList ? new HeadersList(init.headersList) : new HeadersList() + }; + } + function cloneRequest(request) { + const newRequest = makeRequest({ + ...request, + body: null + }); + if (request.body != null) newRequest.body = cloneBody(newRequest, request.body); + return newRequest; + } + /** + * @see https://fetch.spec.whatwg.org/#request-create + * @param {any} innerRequest + * @param {AbortSignal} signal + * @param {'request' | 'immutable' | 'request-no-cors' | 'response' | 'none'} guard + * @returns {Request} + */ + function fromInnerRequest(innerRequest, signal, guard) { + const request = new Request(kConstruct); + request[kState] = innerRequest; + request[kSignal] = signal; + request[kHeaders] = new Headers(kConstruct); + setHeadersList(request[kHeaders], innerRequest.headersList); + setHeadersGuard(request[kHeaders], guard); + return request; + } + Object.defineProperties(Request.prototype, { + method: kEnumerableProperty, + url: kEnumerableProperty, + headers: kEnumerableProperty, + redirect: kEnumerableProperty, + clone: kEnumerableProperty, + signal: kEnumerableProperty, + duplex: kEnumerableProperty, + destination: kEnumerableProperty, + body: kEnumerableProperty, + bodyUsed: kEnumerableProperty, + isHistoryNavigation: kEnumerableProperty, + isReloadNavigation: kEnumerableProperty, + keepalive: kEnumerableProperty, + integrity: kEnumerableProperty, + cache: kEnumerableProperty, + credentials: kEnumerableProperty, + attribute: kEnumerableProperty, + referrerPolicy: kEnumerableProperty, + referrer: kEnumerableProperty, + mode: kEnumerableProperty, + [Symbol.toStringTag]: { + value: "Request", + configurable: true + } + }); + webidl.converters.Request = webidl.interfaceConverter(Request); + webidl.converters.RequestInfo = function(V, prefix, argument) { + if (typeof V === "string") return webidl.converters.USVString(V, prefix, argument); + if (V instanceof Request) return webidl.converters.Request(V, prefix, argument); + return webidl.converters.USVString(V, prefix, argument); + }; + webidl.converters.AbortSignal = webidl.interfaceConverter(AbortSignal); + webidl.converters.RequestInit = webidl.dictionaryConverter([ + { + key: "method", + converter: webidl.converters.ByteString + }, + { + key: "headers", + converter: webidl.converters.HeadersInit + }, + { + key: "body", + converter: webidl.nullableConverter(webidl.converters.BodyInit) + }, + { + key: "referrer", + converter: webidl.converters.USVString + }, + { + key: "referrerPolicy", + converter: webidl.converters.DOMString, + allowedValues: referrerPolicy + }, + { + key: "mode", + converter: webidl.converters.DOMString, + allowedValues: requestMode + }, + { + key: "credentials", + converter: webidl.converters.DOMString, + allowedValues: requestCredentials + }, + { + key: "cache", + converter: webidl.converters.DOMString, + allowedValues: requestCache + }, + { + key: "redirect", + converter: webidl.converters.DOMString, + allowedValues: requestRedirect + }, + { + key: "integrity", + converter: webidl.converters.DOMString + }, + { + key: "keepalive", + converter: webidl.converters.boolean + }, + { + key: "signal", + converter: webidl.nullableConverter((signal) => webidl.converters.AbortSignal(signal, "RequestInit", "signal", { strict: false })) + }, + { + key: "window", + converter: webidl.converters.any + }, + { + key: "duplex", + converter: webidl.converters.DOMString, + allowedValues: requestDuplex + }, + { + key: "dispatcher", + converter: webidl.converters.any + } + ]); + module.exports = { + Request, + makeRequest, + fromInnerRequest, + cloneRequest + }; +})); +//#endregion +//#region node_modules/undici/lib/web/fetch/index.js +var require_fetch = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { makeNetworkError, makeAppropriateNetworkError, filterResponse, makeResponse, fromInnerResponse } = require_response(); + var { HeadersList } = require_headers(); + var { Request, cloneRequest } = require_request(); + var zlib = __require("node:zlib"); + var { bytesMatch, makePolicyContainer, clonePolicyContainer, requestBadPort, TAOCheck, appendRequestOriginHeader, responseLocationURL, requestCurrentURL, setRequestReferrerPolicyOnRedirect, tryUpgradeRequestToAPotentiallyTrustworthyURL, createOpaqueTimingInfo, appendFetchMetadata, corsCheck, crossOriginResourcePolicyCheck, determineRequestsReferrer, coarsenedSharedCurrentTime, createDeferredPromise, isBlobLike, sameOrigin, isCancelled, isAborted, isErrorLike, fullyReadBody, readableStreamClose, isomorphicEncode, urlIsLocal, urlIsHttpHttpsScheme, urlHasHttpsScheme, clampAndCoarsenConnectionTimingInfo, simpleRangeHeaderValue, buildContentRange, createInflate, extractMimeType } = require_util$6(); + var { kState, kDispatcher } = require_symbols$3(); + var assert$4 = __require("node:assert"); + var { safelyExtractBody, extractBody } = require_body(); + var { redirectStatusSet, nullBodyStatus, safeMethodsSet, requestBodyHeader, subresourceSet } = require_constants$2(); + var EE = __require("node:events"); + var { Readable, pipeline: pipeline$1, finished } = __require("node:stream"); + var { addAbortListener, isErrored, isReadable, bufferToLowerCasedHeaderName } = require_util$7(); + var { dataURLProcessor, serializeAMimeType, minimizeSupportedMimeType } = require_data_url(); + var { getGlobalDispatcher } = require_global(); + var { webidl } = require_webidl(); + var { STATUS_CODES } = __require("node:http"); + var GET_OR_HEAD = ["GET", "HEAD"]; + var defaultUserAgent = typeof __UNDICI_IS_NODE__ !== "undefined" || typeof esbuildDetection !== "undefined" ? "node" : "undici"; + /** @type {import('buffer').resolveObjectURL} */ + var resolveObjectURL; + var Fetch = class extends EE { + constructor(dispatcher) { + super(); + this.dispatcher = dispatcher; + this.connection = null; + this.dump = false; + this.state = "ongoing"; + } + terminate(reason) { + if (this.state !== "ongoing") return; + this.state = "terminated"; + this.connection?.destroy(reason); + this.emit("terminated", reason); + } + abort(error) { + if (this.state !== "ongoing") return; + this.state = "aborted"; + if (!error) error = new DOMException("The operation was aborted.", "AbortError"); + this.serializedAbortReason = error; + this.connection?.destroy(error); + this.emit("terminated", error); + } + }; + function handleFetchDone(response) { + finalizeAndReportTiming(response, "fetch"); + } + function fetch(input, init = void 0) { + webidl.argumentLengthCheck(arguments, 1, "globalThis.fetch"); + let p = createDeferredPromise(); + let requestObject; + try { + requestObject = new Request(input, init); + } catch (e) { + p.reject(e); + return p.promise; + } + const request = requestObject[kState]; + if (requestObject.signal.aborted) { + abortFetch(p, request, null, requestObject.signal.reason); + return p.promise; + } + if (request.client.globalObject?.constructor?.name === "ServiceWorkerGlobalScope") request.serviceWorkers = "none"; + let responseObject = null; + let locallyAborted = false; + let controller = null; + addAbortListener(requestObject.signal, () => { + locallyAborted = true; + assert$4(controller != null); + controller.abort(requestObject.signal.reason); + const realResponse = responseObject?.deref(); + abortFetch(p, request, realResponse, requestObject.signal.reason); + }); + const processResponse = (response) => { + if (locallyAborted) return; + if (response.aborted) { + abortFetch(p, request, responseObject, controller.serializedAbortReason); + return; + } + if (response.type === "error") { + p.reject(new TypeError("fetch failed", { cause: response.error })); + return; + } + responseObject = new WeakRef(fromInnerResponse(response, "immutable")); + p.resolve(responseObject.deref()); + p = null; + }; + controller = fetching({ + request, + processResponseEndOfBody: handleFetchDone, + processResponse, + dispatcher: requestObject[kDispatcher] + }); + return p.promise; + } + function finalizeAndReportTiming(response, initiatorType = "other") { + if (response.type === "error" && response.aborted) return; + if (!response.urlList?.length) return; + const originalURL = response.urlList[0]; + let timingInfo = response.timingInfo; + let cacheState = response.cacheState; + if (!urlIsHttpHttpsScheme(originalURL)) return; + if (timingInfo === null) return; + if (!response.timingAllowPassed) { + timingInfo = createOpaqueTimingInfo({ startTime: timingInfo.startTime }); + cacheState = ""; + } + timingInfo.endTime = coarsenedSharedCurrentTime(); + response.timingInfo = timingInfo; + markResourceTiming(timingInfo, originalURL.href, initiatorType, globalThis, cacheState); + } + var markResourceTiming = performance.markResourceTiming; + function abortFetch(p, request, responseObject, error) { + if (p) p.reject(error); + if (request.body != null && isReadable(request.body?.stream)) request.body.stream.cancel(error).catch((err) => { + if (err.code === "ERR_INVALID_STATE") return; + throw err; + }); + if (responseObject == null) return; + const response = responseObject[kState]; + if (response.body != null && isReadable(response.body?.stream)) response.body.stream.cancel(error).catch((err) => { + if (err.code === "ERR_INVALID_STATE") return; + throw err; + }); + } + function fetching({ request, processRequestBodyChunkLength, processRequestEndOfBody, processResponse, processResponseEndOfBody, processResponseConsumeBody, useParallelQueue = false, dispatcher = getGlobalDispatcher() }) { + assert$4(dispatcher); + let taskDestination = null; + let crossOriginIsolatedCapability = false; + if (request.client != null) { + taskDestination = request.client.globalObject; + crossOriginIsolatedCapability = request.client.crossOriginIsolatedCapability; + } + const timingInfo = createOpaqueTimingInfo({ startTime: coarsenedSharedCurrentTime(crossOriginIsolatedCapability) }); + const fetchParams = { + controller: new Fetch(dispatcher), + request, + timingInfo, + processRequestBodyChunkLength, + processRequestEndOfBody, + processResponse, + processResponseConsumeBody, + processResponseEndOfBody, + taskDestination, + crossOriginIsolatedCapability + }; + assert$4(!request.body || request.body.stream); + if (request.window === "client") request.window = request.client?.globalObject?.constructor?.name === "Window" ? request.client : "no-window"; + if (request.origin === "client") request.origin = request.client.origin; + if (request.policyContainer === "client") if (request.client != null) request.policyContainer = clonePolicyContainer(request.client.policyContainer); + else request.policyContainer = makePolicyContainer(); + if (!request.headersList.contains("accept", true)) request.headersList.append("accept", "*/*", true); + if (!request.headersList.contains("accept-language", true)) request.headersList.append("accept-language", "*", true); + if (request.priority === null) {} + if (subresourceSet.has(request.destination)) {} + mainFetch(fetchParams).catch((err) => { + fetchParams.controller.terminate(err); + }); + return fetchParams.controller; + } + async function mainFetch(fetchParams, recursive = false) { + const request = fetchParams.request; + let response = null; + if (request.localURLsOnly && !urlIsLocal(requestCurrentURL(request))) response = makeNetworkError("local URLs only"); + tryUpgradeRequestToAPotentiallyTrustworthyURL(request); + if (requestBadPort(request) === "blocked") response = makeNetworkError("bad port"); + if (request.referrerPolicy === "") request.referrerPolicy = request.policyContainer.referrerPolicy; + if (request.referrer !== "no-referrer") request.referrer = determineRequestsReferrer(request); + if (response === null) response = await (async () => { + const currentURL = requestCurrentURL(request); + if (sameOrigin(currentURL, request.url) && request.responseTainting === "basic" || currentURL.protocol === "data:" || request.mode === "navigate" || request.mode === "websocket") { + request.responseTainting = "basic"; + return await schemeFetch(fetchParams); + } + if (request.mode === "same-origin") return makeNetworkError("request mode cannot be \"same-origin\""); + if (request.mode === "no-cors") { + if (request.redirect !== "follow") return makeNetworkError("redirect mode cannot be \"follow\" for \"no-cors\" request"); + request.responseTainting = "opaque"; + return await schemeFetch(fetchParams); + } + if (!urlIsHttpHttpsScheme(requestCurrentURL(request))) return makeNetworkError("URL scheme must be a HTTP(S) scheme"); + request.responseTainting = "cors"; + return await httpFetch(fetchParams); + })(); + if (recursive) return response; + if (response.status !== 0 && !response.internalResponse) { + if (request.responseTainting === "cors") {} + if (request.responseTainting === "basic") response = filterResponse(response, "basic"); + else if (request.responseTainting === "cors") response = filterResponse(response, "cors"); + else if (request.responseTainting === "opaque") response = filterResponse(response, "opaque"); + else assert$4(false); + } + let internalResponse = response.status === 0 ? response : response.internalResponse; + if (internalResponse.urlList.length === 0) internalResponse.urlList.push(...request.urlList); + if (!request.timingAllowFailed) response.timingAllowPassed = true; + if (response.type === "opaque" && internalResponse.status === 206 && internalResponse.rangeRequested && !request.headers.contains("range", true)) response = internalResponse = makeNetworkError(); + if (response.status !== 0 && (request.method === "HEAD" || request.method === "CONNECT" || nullBodyStatus.includes(internalResponse.status))) { + internalResponse.body = null; + fetchParams.controller.dump = true; + } + if (request.integrity) { + const processBodyError = (reason) => fetchFinale(fetchParams, makeNetworkError(reason)); + if (request.responseTainting === "opaque" || response.body == null) { + processBodyError(response.error); + return; + } + const processBody = (bytes) => { + if (!bytesMatch(bytes, request.integrity)) { + processBodyError("integrity mismatch"); + return; + } + response.body = safelyExtractBody(bytes)[0]; + fetchFinale(fetchParams, response); + }; + await fullyReadBody(response.body, processBody, processBodyError); + } else fetchFinale(fetchParams, response); + } + function schemeFetch(fetchParams) { + if (isCancelled(fetchParams) && fetchParams.request.redirectCount === 0) return Promise.resolve(makeAppropriateNetworkError(fetchParams)); + const { request } = fetchParams; + const { protocol: scheme } = requestCurrentURL(request); + switch (scheme) { + case "about:": return Promise.resolve(makeNetworkError("about scheme is not supported")); + case "blob:": { + if (!resolveObjectURL) resolveObjectURL = __require("node:buffer").resolveObjectURL; + const blobURLEntry = requestCurrentURL(request); + if (blobURLEntry.search.length !== 0) return Promise.resolve(makeNetworkError("NetworkError when attempting to fetch resource.")); + const blob = resolveObjectURL(blobURLEntry.toString()); + if (request.method !== "GET" || !isBlobLike(blob)) return Promise.resolve(makeNetworkError("invalid method")); + const response = makeResponse(); + const fullLength = blob.size; + const serializedFullLength = isomorphicEncode(`${fullLength}`); + const type = blob.type; + if (!request.headersList.contains("range", true)) { + const bodyWithType = extractBody(blob); + response.statusText = "OK"; + response.body = bodyWithType[0]; + response.headersList.set("content-length", serializedFullLength, true); + response.headersList.set("content-type", type, true); + } else { + response.rangeRequested = true; + const rangeValue = simpleRangeHeaderValue(request.headersList.get("range", true), true); + if (rangeValue === "failure") return Promise.resolve(makeNetworkError("failed to fetch the data URL")); + let { rangeStartValue: rangeStart, rangeEndValue: rangeEnd } = rangeValue; + if (rangeStart === null) { + rangeStart = fullLength - rangeEnd; + rangeEnd = rangeStart + rangeEnd - 1; + } else { + if (rangeStart >= fullLength) return Promise.resolve(makeNetworkError("Range start is greater than the blob's size.")); + if (rangeEnd === null || rangeEnd >= fullLength) rangeEnd = fullLength - 1; + } + const slicedBlob = blob.slice(rangeStart, rangeEnd, type); + response.body = extractBody(slicedBlob)[0]; + const serializedSlicedLength = isomorphicEncode(`${slicedBlob.size}`); + const contentRange = buildContentRange(rangeStart, rangeEnd, fullLength); + response.status = 206; + response.statusText = "Partial Content"; + response.headersList.set("content-length", serializedSlicedLength, true); + response.headersList.set("content-type", type, true); + response.headersList.set("content-range", contentRange, true); + } + return Promise.resolve(response); + } + case "data:": { + const dataURLStruct = dataURLProcessor(requestCurrentURL(request)); + if (dataURLStruct === "failure") return Promise.resolve(makeNetworkError("failed to fetch the data URL")); + const mimeType = serializeAMimeType(dataURLStruct.mimeType); + return Promise.resolve(makeResponse({ + statusText: "OK", + headersList: [["content-type", { + name: "Content-Type", + value: mimeType + }]], + body: safelyExtractBody(dataURLStruct.body)[0] + })); + } + case "file:": return Promise.resolve(makeNetworkError("not implemented... yet...")); + case "http:": + case "https:": return httpFetch(fetchParams).catch((err) => makeNetworkError(err)); + default: return Promise.resolve(makeNetworkError("unknown scheme")); + } + } + function finalizeResponse(fetchParams, response) { + fetchParams.request.done = true; + if (fetchParams.processResponseDone != null) queueMicrotask(() => fetchParams.processResponseDone(response)); + } + function fetchFinale(fetchParams, response) { + let timingInfo = fetchParams.timingInfo; + const processResponseEndOfBody = () => { + const unsafeEndTime = Date.now(); + if (fetchParams.request.destination === "document") fetchParams.controller.fullTimingInfo = timingInfo; + fetchParams.controller.reportTimingSteps = () => { + if (fetchParams.request.url.protocol !== "https:") return; + timingInfo.endTime = unsafeEndTime; + let cacheState = response.cacheState; + const bodyInfo = response.bodyInfo; + if (!response.timingAllowPassed) { + timingInfo = createOpaqueTimingInfo(timingInfo); + cacheState = ""; + } + let responseStatus = 0; + if (fetchParams.request.mode !== "navigator" || !response.hasCrossOriginRedirects) { + responseStatus = response.status; + const mimeType = extractMimeType(response.headersList); + if (mimeType !== "failure") bodyInfo.contentType = minimizeSupportedMimeType(mimeType); + } + if (fetchParams.request.initiatorType != null) markResourceTiming(timingInfo, fetchParams.request.url.href, fetchParams.request.initiatorType, globalThis, cacheState, bodyInfo, responseStatus); + }; + const processResponseEndOfBodyTask = () => { + fetchParams.request.done = true; + if (fetchParams.processResponseEndOfBody != null) queueMicrotask(() => fetchParams.processResponseEndOfBody(response)); + if (fetchParams.request.initiatorType != null) fetchParams.controller.reportTimingSteps(); + }; + queueMicrotask(() => processResponseEndOfBodyTask()); + }; + if (fetchParams.processResponse != null) queueMicrotask(() => { + fetchParams.processResponse(response); + fetchParams.processResponse = null; + }); + const internalResponse = response.type === "error" ? response : response.internalResponse ?? response; + if (internalResponse.body == null) processResponseEndOfBody(); + else finished(internalResponse.body.stream, () => { + processResponseEndOfBody(); + }); + } + async function httpFetch(fetchParams) { + const request = fetchParams.request; + let response = null; + let actualResponse = null; + const timingInfo = fetchParams.timingInfo; + if (request.serviceWorkers === "all") {} + if (response === null) { + if (request.redirect === "follow") request.serviceWorkers = "none"; + actualResponse = response = await httpNetworkOrCacheFetch(fetchParams); + if (request.responseTainting === "cors" && corsCheck(request, response) === "failure") return makeNetworkError("cors failure"); + if (TAOCheck(request, response) === "failure") request.timingAllowFailed = true; + } + if ((request.responseTainting === "opaque" || response.type === "opaque") && crossOriginResourcePolicyCheck(request.origin, request.client, request.destination, actualResponse) === "blocked") return makeNetworkError("blocked"); + if (redirectStatusSet.has(actualResponse.status)) { + if (request.redirect !== "manual") fetchParams.controller.connection.destroy(void 0, false); + if (request.redirect === "error") response = makeNetworkError("unexpected redirect"); + else if (request.redirect === "manual") response = actualResponse; + else if (request.redirect === "follow") response = await httpRedirectFetch(fetchParams, response); + else assert$4(false); + } + response.timingInfo = timingInfo; + return response; + } + function httpRedirectFetch(fetchParams, response) { + const request = fetchParams.request; + const actualResponse = response.internalResponse ? response.internalResponse : response; + let locationURL; + try { + locationURL = responseLocationURL(actualResponse, requestCurrentURL(request).hash); + if (locationURL == null) return response; + } catch (err) { + return Promise.resolve(makeNetworkError(err)); + } + if (!urlIsHttpHttpsScheme(locationURL)) return Promise.resolve(makeNetworkError("URL scheme must be a HTTP(S) scheme")); + if (request.redirectCount === 20) return Promise.resolve(makeNetworkError("redirect count exceeded")); + request.redirectCount += 1; + if (request.mode === "cors" && (locationURL.username || locationURL.password) && !sameOrigin(request, locationURL)) return Promise.resolve(makeNetworkError("cross origin not allowed for request mode \"cors\"")); + if (request.responseTainting === "cors" && (locationURL.username || locationURL.password)) return Promise.resolve(makeNetworkError("URL cannot contain credentials for request mode \"cors\"")); + if (actualResponse.status !== 303 && request.body != null && request.body.source == null) return Promise.resolve(makeNetworkError()); + if ([301, 302].includes(actualResponse.status) && request.method === "POST" || actualResponse.status === 303 && !GET_OR_HEAD.includes(request.method)) { + request.method = "GET"; + request.body = null; + for (const headerName of requestBodyHeader) request.headersList.delete(headerName); + } + if (!sameOrigin(requestCurrentURL(request), locationURL)) { + request.headersList.delete("authorization", true); + request.headersList.delete("proxy-authorization", true); + request.headersList.delete("cookie", true); + request.headersList.delete("host", true); + } + if (request.body != null) { + assert$4(request.body.source != null); + request.body = safelyExtractBody(request.body.source)[0]; + } + const timingInfo = fetchParams.timingInfo; + timingInfo.redirectEndTime = timingInfo.postRedirectStartTime = coarsenedSharedCurrentTime(fetchParams.crossOriginIsolatedCapability); + if (timingInfo.redirectStartTime === 0) timingInfo.redirectStartTime = timingInfo.startTime; + request.urlList.push(locationURL); + setRequestReferrerPolicyOnRedirect(request, actualResponse); + return mainFetch(fetchParams, true); + } + async function httpNetworkOrCacheFetch(fetchParams, isAuthenticationFetch = false, isNewConnectionFetch = false) { + const request = fetchParams.request; + let httpFetchParams = null; + let httpRequest = null; + let response = null; + if (request.window === "no-window" && request.redirect === "error") { + httpFetchParams = fetchParams; + httpRequest = request; + } else { + httpRequest = cloneRequest(request); + httpFetchParams = { ...fetchParams }; + httpFetchParams.request = httpRequest; + } + const includeCredentials = request.credentials === "include" || request.credentials === "same-origin" && request.responseTainting === "basic"; + const contentLength = httpRequest.body ? httpRequest.body.length : null; + let contentLengthHeaderValue = null; + if (httpRequest.body == null && ["POST", "PUT"].includes(httpRequest.method)) contentLengthHeaderValue = "0"; + if (contentLength != null) contentLengthHeaderValue = isomorphicEncode(`${contentLength}`); + if (contentLengthHeaderValue != null) httpRequest.headersList.append("content-length", contentLengthHeaderValue, true); + if (contentLength != null && httpRequest.keepalive) {} + if (httpRequest.referrer instanceof URL) httpRequest.headersList.append("referer", isomorphicEncode(httpRequest.referrer.href), true); + appendRequestOriginHeader(httpRequest); + appendFetchMetadata(httpRequest); + if (!httpRequest.headersList.contains("user-agent", true)) httpRequest.headersList.append("user-agent", defaultUserAgent); + if (httpRequest.cache === "default" && (httpRequest.headersList.contains("if-modified-since", true) || httpRequest.headersList.contains("if-none-match", true) || httpRequest.headersList.contains("if-unmodified-since", true) || httpRequest.headersList.contains("if-match", true) || httpRequest.headersList.contains("if-range", true))) httpRequest.cache = "no-store"; + if (httpRequest.cache === "no-cache" && !httpRequest.preventNoCacheCacheControlHeaderModification && !httpRequest.headersList.contains("cache-control", true)) httpRequest.headersList.append("cache-control", "max-age=0", true); + if (httpRequest.cache === "no-store" || httpRequest.cache === "reload") { + if (!httpRequest.headersList.contains("pragma", true)) httpRequest.headersList.append("pragma", "no-cache", true); + if (!httpRequest.headersList.contains("cache-control", true)) httpRequest.headersList.append("cache-control", "no-cache", true); + } + if (httpRequest.headersList.contains("range", true)) httpRequest.headersList.append("accept-encoding", "identity", true); + if (!httpRequest.headersList.contains("accept-encoding", true)) if (urlHasHttpsScheme(requestCurrentURL(httpRequest))) httpRequest.headersList.append("accept-encoding", "br, gzip, deflate", true); + else httpRequest.headersList.append("accept-encoding", "gzip, deflate", true); + httpRequest.headersList.delete("host", true); + if (includeCredentials) {} + httpRequest.cache = "no-store"; + if (httpRequest.cache !== "no-store" && httpRequest.cache !== "reload") {} + if (response == null) { + if (httpRequest.cache === "only-if-cached") return makeNetworkError("only if cached"); + const forwardResponse = await httpNetworkFetch(httpFetchParams, includeCredentials, isNewConnectionFetch); + if (!safeMethodsSet.has(httpRequest.method) && forwardResponse.status >= 200 && forwardResponse.status <= 399) {} + if (response == null) response = forwardResponse; + } + response.urlList = [...httpRequest.urlList]; + if (httpRequest.headersList.contains("range", true)) response.rangeRequested = true; + response.requestIncludesCredentials = includeCredentials; + if (response.status === 407) { + if (request.window === "no-window") return makeNetworkError(); + if (isCancelled(fetchParams)) return makeAppropriateNetworkError(fetchParams); + return makeNetworkError("proxy authentication required"); + } + if (response.status === 421 && !isNewConnectionFetch && (request.body == null || request.body.source != null)) { + if (isCancelled(fetchParams)) return makeAppropriateNetworkError(fetchParams); + fetchParams.controller.connection.destroy(); + response = await httpNetworkOrCacheFetch(fetchParams, isAuthenticationFetch, true); + } + if (isAuthenticationFetch) {} + return response; + } + async function httpNetworkFetch(fetchParams, includeCredentials = false, forceNewConnection = false) { + assert$4(!fetchParams.controller.connection || fetchParams.controller.connection.destroyed); + fetchParams.controller.connection = { + abort: null, + destroyed: false, + destroy(err, abort = true) { + if (!this.destroyed) { + this.destroyed = true; + if (abort) this.abort?.(err ?? new DOMException("The operation was aborted.", "AbortError")); + } + } + }; + const request = fetchParams.request; + let response = null; + const timingInfo = fetchParams.timingInfo; + request.cache = "no-store"; + if (request.mode === "websocket") {} + let requestBody = null; + if (request.body == null && fetchParams.processRequestEndOfBody) queueMicrotask(() => fetchParams.processRequestEndOfBody()); + else if (request.body != null) { + const processBodyChunk = async function* (bytes) { + if (isCancelled(fetchParams)) return; + yield bytes; + fetchParams.processRequestBodyChunkLength?.(bytes.byteLength); + }; + const processEndOfBody = () => { + if (isCancelled(fetchParams)) return; + if (fetchParams.processRequestEndOfBody) fetchParams.processRequestEndOfBody(); + }; + const processBodyError = (e) => { + if (isCancelled(fetchParams)) return; + if (e.name === "AbortError") fetchParams.controller.abort(); + else fetchParams.controller.terminate(e); + }; + requestBody = (async function* () { + try { + for await (const bytes of request.body.stream) yield* processBodyChunk(bytes); + processEndOfBody(); + } catch (err) { + processBodyError(err); + } + })(); + } + try { + const { body, status, statusText, headersList, socket } = await dispatch({ body: requestBody }); + if (socket) response = makeResponse({ + status, + statusText, + headersList, + socket + }); + else { + const iterator = body[Symbol.asyncIterator](); + fetchParams.controller.next = () => iterator.next(); + response = makeResponse({ + status, + statusText, + headersList + }); + } + } catch (err) { + if (err.name === "AbortError") { + fetchParams.controller.connection.destroy(); + return makeAppropriateNetworkError(fetchParams, err); + } + return makeNetworkError(err); + } + const pullAlgorithm = async () => { + await fetchParams.controller.resume(); + }; + const cancelAlgorithm = (reason) => { + if (!isCancelled(fetchParams)) fetchParams.controller.abort(reason); + }; + const stream = new ReadableStream({ + async start(controller) { + fetchParams.controller.controller = controller; + }, + async pull(controller) { + await pullAlgorithm(controller); + }, + async cancel(reason) { + await cancelAlgorithm(reason); + }, + type: "bytes" + }); + response.body = { + stream, + source: null, + length: null + }; + fetchParams.controller.onAborted = onAborted; + fetchParams.controller.on("terminated", onAborted); + fetchParams.controller.resume = async () => { + while (true) { + let bytes; + let isFailure; + try { + const { done, value } = await fetchParams.controller.next(); + if (isAborted(fetchParams)) break; + bytes = done ? void 0 : value; + } catch (err) { + if (fetchParams.controller.ended && !timingInfo.encodedBodySize) bytes = void 0; + else { + bytes = err; + isFailure = true; + } + } + if (bytes === void 0) { + readableStreamClose(fetchParams.controller.controller); + finalizeResponse(fetchParams, response); + return; + } + timingInfo.decodedBodySize += bytes?.byteLength ?? 0; + if (isFailure) { + fetchParams.controller.terminate(bytes); + return; + } + const buffer = new Uint8Array(bytes); + if (buffer.byteLength) fetchParams.controller.controller.enqueue(buffer); + if (isErrored(stream)) { + fetchParams.controller.terminate(); + return; + } + if (fetchParams.controller.controller.desiredSize <= 0) return; + } + }; + function onAborted(reason) { + if (isAborted(fetchParams)) { + response.aborted = true; + if (isReadable(stream)) fetchParams.controller.controller.error(fetchParams.controller.serializedAbortReason); + } else if (isReadable(stream)) fetchParams.controller.controller.error(new TypeError("terminated", { cause: isErrorLike(reason) ? reason : void 0 })); + fetchParams.controller.connection.destroy(); + } + return response; + function dispatch({ body }) { + const url = requestCurrentURL(request); + /** @type {import('../..').Agent} */ + const agent = fetchParams.controller.dispatcher; + return new Promise((resolve, reject) => agent.dispatch({ + path: url.pathname + url.search, + origin: url.origin, + method: request.method, + body: agent.isMockActive ? request.body && (request.body.source || request.body.stream) : body, + headers: request.headersList.entries, + maxRedirections: 0, + upgrade: request.mode === "websocket" ? "websocket" : void 0 + }, { + body: null, + abort: null, + onConnect(abort) { + const { connection } = fetchParams.controller; + timingInfo.finalConnectionTimingInfo = clampAndCoarsenConnectionTimingInfo(void 0, timingInfo.postRedirectStartTime, fetchParams.crossOriginIsolatedCapability); + if (connection.destroyed) abort(new DOMException("The operation was aborted.", "AbortError")); + else { + fetchParams.controller.on("terminated", abort); + this.abort = connection.abort = abort; + } + timingInfo.finalNetworkRequestStartTime = coarsenedSharedCurrentTime(fetchParams.crossOriginIsolatedCapability); + }, + onResponseStarted() { + timingInfo.finalNetworkResponseStartTime = coarsenedSharedCurrentTime(fetchParams.crossOriginIsolatedCapability); + }, + onHeaders(status, rawHeaders, resume, statusText) { + if (status < 200) return; + let location = ""; + const headersList = new HeadersList(); + for (let i = 0; i < rawHeaders.length; i += 2) headersList.append(bufferToLowerCasedHeaderName(rawHeaders[i]), rawHeaders[i + 1].toString("latin1"), true); + location = headersList.get("location", true); + this.body = new Readable({ read: resume }); + const decoders = []; + const willFollow = location && request.redirect === "follow" && redirectStatusSet.has(status); + if (request.method !== "HEAD" && request.method !== "CONNECT" && !nullBodyStatus.includes(status) && !willFollow) { + const contentEncoding = headersList.get("content-encoding", true); + /** @type {string[]} */ + const codings = contentEncoding ? contentEncoding.toLowerCase().split(",") : []; + const maxContentEncodings = 5; + if (codings.length > maxContentEncodings) { + reject(/* @__PURE__ */ new Error(`too many content-encodings in response: ${codings.length}, maximum allowed is ${maxContentEncodings}`)); + return true; + } + for (let i = codings.length - 1; i >= 0; --i) { + const coding = codings[i].trim(); + if (coding === "x-gzip" || coding === "gzip") decoders.push(zlib.createGunzip({ + flush: zlib.constants.Z_SYNC_FLUSH, + finishFlush: zlib.constants.Z_SYNC_FLUSH + })); + else if (coding === "deflate") decoders.push(createInflate({ + flush: zlib.constants.Z_SYNC_FLUSH, + finishFlush: zlib.constants.Z_SYNC_FLUSH + })); + else if (coding === "br") decoders.push(zlib.createBrotliDecompress({ + flush: zlib.constants.BROTLI_OPERATION_FLUSH, + finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH + })); + else { + decoders.length = 0; + break; + } + } + } + const onError = this.onError.bind(this); + resolve({ + status, + statusText, + headersList, + body: decoders.length ? pipeline$1(this.body, ...decoders, (err) => { + if (err) this.onError(err); + }).on("error", onError) : this.body.on("error", onError) + }); + return true; + }, + onData(chunk) { + if (fetchParams.controller.dump) return; + const bytes = chunk; + timingInfo.encodedBodySize += bytes.byteLength; + return this.body.push(bytes); + }, + onComplete() { + if (this.abort) fetchParams.controller.off("terminated", this.abort); + if (fetchParams.controller.onAborted) fetchParams.controller.off("terminated", fetchParams.controller.onAborted); + fetchParams.controller.ended = true; + this.body.push(null); + }, + onError(error) { + if (this.abort) fetchParams.controller.off("terminated", this.abort); + this.body?.destroy(error); + fetchParams.controller.terminate(error); + reject(error); + }, + onUpgrade(status, rawHeaders, socket) { + if (status !== 101) return; + const headersList = new HeadersList(); + for (let i = 0; i < rawHeaders.length; i += 2) headersList.append(bufferToLowerCasedHeaderName(rawHeaders[i]), rawHeaders[i + 1].toString("latin1"), true); + resolve({ + status, + statusText: STATUS_CODES[status], + headersList, + socket + }); + return true; + } + })); + } + } + module.exports = { + fetch, + Fetch, + fetching, + finalizeAndReportTiming + }; +})); +//#endregion +//#region node_modules/undici/lib/web/fileapi/symbols.js +var require_symbols$2 = /* @__PURE__ */ __commonJSMin(((exports, module) => { + module.exports = { + kState: Symbol("FileReader state"), + kResult: Symbol("FileReader result"), + kError: Symbol("FileReader error"), + kLastProgressEventFired: Symbol("FileReader last progress event fired timestamp"), + kEvents: Symbol("FileReader events"), + kAborted: Symbol("FileReader aborted") + }; +})); +//#endregion +//#region node_modules/undici/lib/web/fileapi/progressevent.js +var require_progressevent = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { webidl } = require_webidl(); + var kState = Symbol("ProgressEvent state"); + /** + * @see https://xhr.spec.whatwg.org/#progressevent + */ + var ProgressEvent = class ProgressEvent extends Event { + constructor(type, eventInitDict = {}) { + type = webidl.converters.DOMString(type, "ProgressEvent constructor", "type"); + eventInitDict = webidl.converters.ProgressEventInit(eventInitDict ?? {}); + super(type, eventInitDict); + this[kState] = { + lengthComputable: eventInitDict.lengthComputable, + loaded: eventInitDict.loaded, + total: eventInitDict.total + }; + } + get lengthComputable() { + webidl.brandCheck(this, ProgressEvent); + return this[kState].lengthComputable; + } + get loaded() { + webidl.brandCheck(this, ProgressEvent); + return this[kState].loaded; + } + get total() { + webidl.brandCheck(this, ProgressEvent); + return this[kState].total; + } + }; + webidl.converters.ProgressEventInit = webidl.dictionaryConverter([ + { + key: "lengthComputable", + converter: webidl.converters.boolean, + defaultValue: () => false + }, + { + key: "loaded", + converter: webidl.converters["unsigned long long"], + defaultValue: () => 0 + }, + { + key: "total", + converter: webidl.converters["unsigned long long"], + defaultValue: () => 0 + }, + { + key: "bubbles", + converter: webidl.converters.boolean, + defaultValue: () => false + }, + { + key: "cancelable", + converter: webidl.converters.boolean, + defaultValue: () => false + }, + { + key: "composed", + converter: webidl.converters.boolean, + defaultValue: () => false + } + ]); + module.exports = { ProgressEvent }; +})); +//#endregion +//#region node_modules/undici/lib/web/fileapi/encoding.js +var require_encoding = /* @__PURE__ */ __commonJSMin(((exports, module) => { + /** + * @see https://encoding.spec.whatwg.org/#concept-encoding-get + * @param {string|undefined} label + */ + function getEncoding(label) { + if (!label) return "failure"; + switch (label.trim().toLowerCase()) { + case "unicode-1-1-utf-8": + case "unicode11utf8": + case "unicode20utf8": + case "utf-8": + case "utf8": + case "x-unicode20utf8": return "UTF-8"; + case "866": + case "cp866": + case "csibm866": + case "ibm866": return "IBM866"; + case "csisolatin2": + case "iso-8859-2": + case "iso-ir-101": + case "iso8859-2": + case "iso88592": + case "iso_8859-2": + case "iso_8859-2:1987": + case "l2": + case "latin2": return "ISO-8859-2"; + case "csisolatin3": + case "iso-8859-3": + case "iso-ir-109": + case "iso8859-3": + case "iso88593": + case "iso_8859-3": + case "iso_8859-3:1988": + case "l3": + case "latin3": return "ISO-8859-3"; + case "csisolatin4": + case "iso-8859-4": + case "iso-ir-110": + case "iso8859-4": + case "iso88594": + case "iso_8859-4": + case "iso_8859-4:1988": + case "l4": + case "latin4": return "ISO-8859-4"; + case "csisolatincyrillic": + case "cyrillic": + case "iso-8859-5": + case "iso-ir-144": + case "iso8859-5": + case "iso88595": + case "iso_8859-5": + case "iso_8859-5:1988": return "ISO-8859-5"; + case "arabic": + case "asmo-708": + case "csiso88596e": + case "csiso88596i": + case "csisolatinarabic": + case "ecma-114": + case "iso-8859-6": + case "iso-8859-6-e": + case "iso-8859-6-i": + case "iso-ir-127": + case "iso8859-6": + case "iso88596": + case "iso_8859-6": + case "iso_8859-6:1987": return "ISO-8859-6"; + case "csisolatingreek": + case "ecma-118": + case "elot_928": + case "greek": + case "greek8": + case "iso-8859-7": + case "iso-ir-126": + case "iso8859-7": + case "iso88597": + case "iso_8859-7": + case "iso_8859-7:1987": + case "sun_eu_greek": return "ISO-8859-7"; + case "csiso88598e": + case "csisolatinhebrew": + case "hebrew": + case "iso-8859-8": + case "iso-8859-8-e": + case "iso-ir-138": + case "iso8859-8": + case "iso88598": + case "iso_8859-8": + case "iso_8859-8:1988": + case "visual": return "ISO-8859-8"; + case "csiso88598i": + case "iso-8859-8-i": + case "logical": return "ISO-8859-8-I"; + case "csisolatin6": + case "iso-8859-10": + case "iso-ir-157": + case "iso8859-10": + case "iso885910": + case "l6": + case "latin6": return "ISO-8859-10"; + case "iso-8859-13": + case "iso8859-13": + case "iso885913": return "ISO-8859-13"; + case "iso-8859-14": + case "iso8859-14": + case "iso885914": return "ISO-8859-14"; + case "csisolatin9": + case "iso-8859-15": + case "iso8859-15": + case "iso885915": + case "iso_8859-15": + case "l9": return "ISO-8859-15"; + case "iso-8859-16": return "ISO-8859-16"; + case "cskoi8r": + case "koi": + case "koi8": + case "koi8-r": + case "koi8_r": return "KOI8-R"; + case "koi8-ru": + case "koi8-u": return "KOI8-U"; + case "csmacintosh": + case "mac": + case "macintosh": + case "x-mac-roman": return "macintosh"; + case "iso-8859-11": + case "iso8859-11": + case "iso885911": + case "tis-620": + case "windows-874": return "windows-874"; + case "cp1250": + case "windows-1250": + case "x-cp1250": return "windows-1250"; + case "cp1251": + case "windows-1251": + case "x-cp1251": return "windows-1251"; + case "ansi_x3.4-1968": + case "ascii": + case "cp1252": + case "cp819": + case "csisolatin1": + case "ibm819": + case "iso-8859-1": + case "iso-ir-100": + case "iso8859-1": + case "iso88591": + case "iso_8859-1": + case "iso_8859-1:1987": + case "l1": + case "latin1": + case "us-ascii": + case "windows-1252": + case "x-cp1252": return "windows-1252"; + case "cp1253": + case "windows-1253": + case "x-cp1253": return "windows-1253"; + case "cp1254": + case "csisolatin5": + case "iso-8859-9": + case "iso-ir-148": + case "iso8859-9": + case "iso88599": + case "iso_8859-9": + case "iso_8859-9:1989": + case "l5": + case "latin5": + case "windows-1254": + case "x-cp1254": return "windows-1254"; + case "cp1255": + case "windows-1255": + case "x-cp1255": return "windows-1255"; + case "cp1256": + case "windows-1256": + case "x-cp1256": return "windows-1256"; + case "cp1257": + case "windows-1257": + case "x-cp1257": return "windows-1257"; + case "cp1258": + case "windows-1258": + case "x-cp1258": return "windows-1258"; + case "x-mac-cyrillic": + case "x-mac-ukrainian": return "x-mac-cyrillic"; + case "chinese": + case "csgb2312": + case "csiso58gb231280": + case "gb2312": + case "gb_2312": + case "gb_2312-80": + case "gbk": + case "iso-ir-58": + case "x-gbk": return "GBK"; + case "gb18030": return "gb18030"; + case "big5": + case "big5-hkscs": + case "cn-big5": + case "csbig5": + case "x-x-big5": return "Big5"; + case "cseucpkdfmtjapanese": + case "euc-jp": + case "x-euc-jp": return "EUC-JP"; + case "csiso2022jp": + case "iso-2022-jp": return "ISO-2022-JP"; + case "csshiftjis": + case "ms932": + case "ms_kanji": + case "shift-jis": + case "shift_jis": + case "sjis": + case "windows-31j": + case "x-sjis": return "Shift_JIS"; + case "cseuckr": + case "csksc56011987": + case "euc-kr": + case "iso-ir-149": + case "korean": + case "ks_c_5601-1987": + case "ks_c_5601-1989": + case "ksc5601": + case "ksc_5601": + case "windows-949": return "EUC-KR"; + case "csiso2022kr": + case "hz-gb-2312": + case "iso-2022-cn": + case "iso-2022-cn-ext": + case "iso-2022-kr": + case "replacement": return "replacement"; + case "unicodefffe": + case "utf-16be": return "UTF-16BE"; + case "csunicode": + case "iso-10646-ucs-2": + case "ucs-2": + case "unicode": + case "unicodefeff": + case "utf-16": + case "utf-16le": return "UTF-16LE"; + case "x-user-defined": return "x-user-defined"; + default: return "failure"; + } + } + module.exports = { getEncoding }; +})); +//#endregion +//#region node_modules/undici/lib/web/fileapi/util.js +var require_util$4 = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { kState, kError, kResult, kAborted, kLastProgressEventFired } = require_symbols$2(); + var { ProgressEvent } = require_progressevent(); + var { getEncoding } = require_encoding(); + var { serializeAMimeType, parseMIMEType } = require_data_url(); + var { types: types$1 } = __require("node:util"); + var { StringDecoder: StringDecoder$1 } = __require("node:string_decoder"); + var { btoa: btoa$1 } = __require("node:buffer"); + /** @type {PropertyDescriptor} */ + var staticPropertyDescriptors = { + enumerable: true, + writable: false, + configurable: false + }; + /** + * @see https://w3c.github.io/FileAPI/#readOperation + * @param {import('./filereader').FileReader} fr + * @param {import('buffer').Blob} blob + * @param {string} type + * @param {string?} encodingName + */ + function readOperation(fr, blob, type, encodingName) { + if (fr[kState] === "loading") throw new DOMException("Invalid state", "InvalidStateError"); + fr[kState] = "loading"; + fr[kResult] = null; + fr[kError] = null; + const reader = blob.stream().getReader(); + /** @type {Uint8Array[]} */ + const bytes = []; + let chunkPromise = reader.read(); + let isFirstChunk = true; + (async () => { + while (!fr[kAborted]) try { + const { done, value } = await chunkPromise; + if (isFirstChunk && !fr[kAborted]) queueMicrotask(() => { + fireAProgressEvent("loadstart", fr); + }); + isFirstChunk = false; + if (!done && types$1.isUint8Array(value)) { + bytes.push(value); + if ((fr[kLastProgressEventFired] === void 0 || Date.now() - fr[kLastProgressEventFired] >= 50) && !fr[kAborted]) { + fr[kLastProgressEventFired] = Date.now(); + queueMicrotask(() => { + fireAProgressEvent("progress", fr); + }); + } + chunkPromise = reader.read(); + } else if (done) { + queueMicrotask(() => { + fr[kState] = "done"; + try { + const result = packageData(bytes, type, blob.type, encodingName); + if (fr[kAborted]) return; + fr[kResult] = result; + fireAProgressEvent("load", fr); + } catch (error) { + fr[kError] = error; + fireAProgressEvent("error", fr); + } + if (fr[kState] !== "loading") fireAProgressEvent("loadend", fr); + }); + break; + } + } catch (error) { + if (fr[kAborted]) return; + queueMicrotask(() => { + fr[kState] = "done"; + fr[kError] = error; + fireAProgressEvent("error", fr); + if (fr[kState] !== "loading") fireAProgressEvent("loadend", fr); + }); + break; + } + })(); + } + /** + * @see https://w3c.github.io/FileAPI/#fire-a-progress-event + * @see https://dom.spec.whatwg.org/#concept-event-fire + * @param {string} e The name of the event + * @param {import('./filereader').FileReader} reader + */ + function fireAProgressEvent(e, reader) { + const event = new ProgressEvent(e, { + bubbles: false, + cancelable: false + }); + reader.dispatchEvent(event); + } + /** + * @see https://w3c.github.io/FileAPI/#blob-package-data + * @param {Uint8Array[]} bytes + * @param {string} type + * @param {string?} mimeType + * @param {string?} encodingName + */ + function packageData(bytes, type, mimeType, encodingName) { + switch (type) { + case "DataURL": { + let dataURL = "data:"; + const parsed = parseMIMEType(mimeType || "application/octet-stream"); + if (parsed !== "failure") dataURL += serializeAMimeType(parsed); + dataURL += ";base64,"; + const decoder = new StringDecoder$1("latin1"); + for (const chunk of bytes) dataURL += btoa$1(decoder.write(chunk)); + dataURL += btoa$1(decoder.end()); + return dataURL; + } + case "Text": { + let encoding = "failure"; + if (encodingName) encoding = getEncoding(encodingName); + if (encoding === "failure" && mimeType) { + const type = parseMIMEType(mimeType); + if (type !== "failure") encoding = getEncoding(type.parameters.get("charset")); + } + if (encoding === "failure") encoding = "UTF-8"; + return decode(bytes, encoding); + } + case "ArrayBuffer": return combineByteSequences(bytes).buffer; + case "BinaryString": { + let binaryString = ""; + const decoder = new StringDecoder$1("latin1"); + for (const chunk of bytes) binaryString += decoder.write(chunk); + binaryString += decoder.end(); + return binaryString; + } + } + } + /** + * @see https://encoding.spec.whatwg.org/#decode + * @param {Uint8Array[]} ioQueue + * @param {string} encoding + */ + function decode(ioQueue, encoding) { + const bytes = combineByteSequences(ioQueue); + const BOMEncoding = BOMSniffing(bytes); + let slice = 0; + if (BOMEncoding !== null) { + encoding = BOMEncoding; + slice = BOMEncoding === "UTF-8" ? 3 : 2; + } + const sliced = bytes.slice(slice); + return new TextDecoder(encoding).decode(sliced); + } + /** + * @see https://encoding.spec.whatwg.org/#bom-sniff + * @param {Uint8Array} ioQueue + */ + function BOMSniffing(ioQueue) { + const [a, b, c] = ioQueue; + if (a === 239 && b === 187 && c === 191) return "UTF-8"; + else if (a === 254 && b === 255) return "UTF-16BE"; + else if (a === 255 && b === 254) return "UTF-16LE"; + return null; + } + /** + * @param {Uint8Array[]} sequences + */ + function combineByteSequences(sequences) { + const size = sequences.reduce((a, b) => { + return a + b.byteLength; + }, 0); + let offset = 0; + return sequences.reduce((a, b) => { + a.set(b, offset); + offset += b.byteLength; + return a; + }, new Uint8Array(size)); + } + module.exports = { + staticPropertyDescriptors, + readOperation, + fireAProgressEvent + }; +})); +//#endregion +//#region node_modules/undici/lib/web/fileapi/filereader.js +var require_filereader = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { staticPropertyDescriptors, readOperation, fireAProgressEvent } = require_util$4(); + var { kState, kError, kResult, kEvents, kAborted } = require_symbols$2(); + var { webidl } = require_webidl(); + var { kEnumerableProperty } = require_util$7(); + var FileReader = class FileReader extends EventTarget { + constructor() { + super(); + this[kState] = "empty"; + this[kResult] = null; + this[kError] = null; + this[kEvents] = { + loadend: null, + error: null, + abort: null, + load: null, + progress: null, + loadstart: null + }; + } + /** + * @see https://w3c.github.io/FileAPI/#dfn-readAsArrayBuffer + * @param {import('buffer').Blob} blob + */ + readAsArrayBuffer(blob) { + webidl.brandCheck(this, FileReader); + webidl.argumentLengthCheck(arguments, 1, "FileReader.readAsArrayBuffer"); + blob = webidl.converters.Blob(blob, { strict: false }); + readOperation(this, blob, "ArrayBuffer"); + } + /** + * @see https://w3c.github.io/FileAPI/#readAsBinaryString + * @param {import('buffer').Blob} blob + */ + readAsBinaryString(blob) { + webidl.brandCheck(this, FileReader); + webidl.argumentLengthCheck(arguments, 1, "FileReader.readAsBinaryString"); + blob = webidl.converters.Blob(blob, { strict: false }); + readOperation(this, blob, "BinaryString"); + } + /** + * @see https://w3c.github.io/FileAPI/#readAsDataText + * @param {import('buffer').Blob} blob + * @param {string?} encoding + */ + readAsText(blob, encoding = void 0) { + webidl.brandCheck(this, FileReader); + webidl.argumentLengthCheck(arguments, 1, "FileReader.readAsText"); + blob = webidl.converters.Blob(blob, { strict: false }); + if (encoding !== void 0) encoding = webidl.converters.DOMString(encoding, "FileReader.readAsText", "encoding"); + readOperation(this, blob, "Text", encoding); + } + /** + * @see https://w3c.github.io/FileAPI/#dfn-readAsDataURL + * @param {import('buffer').Blob} blob + */ + readAsDataURL(blob) { + webidl.brandCheck(this, FileReader); + webidl.argumentLengthCheck(arguments, 1, "FileReader.readAsDataURL"); + blob = webidl.converters.Blob(blob, { strict: false }); + readOperation(this, blob, "DataURL"); + } + /** + * @see https://w3c.github.io/FileAPI/#dfn-abort + */ + abort() { + if (this[kState] === "empty" || this[kState] === "done") { + this[kResult] = null; + return; + } + if (this[kState] === "loading") { + this[kState] = "done"; + this[kResult] = null; + } + this[kAborted] = true; + fireAProgressEvent("abort", this); + if (this[kState] !== "loading") fireAProgressEvent("loadend", this); + } + /** + * @see https://w3c.github.io/FileAPI/#dom-filereader-readystate + */ + get readyState() { + webidl.brandCheck(this, FileReader); + switch (this[kState]) { + case "empty": return this.EMPTY; + case "loading": return this.LOADING; + case "done": return this.DONE; + } + } + /** + * @see https://w3c.github.io/FileAPI/#dom-filereader-result + */ + get result() { + webidl.brandCheck(this, FileReader); + return this[kResult]; + } + /** + * @see https://w3c.github.io/FileAPI/#dom-filereader-error + */ + get error() { + webidl.brandCheck(this, FileReader); + return this[kError]; + } + get onloadend() { + webidl.brandCheck(this, FileReader); + return this[kEvents].loadend; + } + set onloadend(fn) { + webidl.brandCheck(this, FileReader); + if (this[kEvents].loadend) this.removeEventListener("loadend", this[kEvents].loadend); + if (typeof fn === "function") { + this[kEvents].loadend = fn; + this.addEventListener("loadend", fn); + } else this[kEvents].loadend = null; + } + get onerror() { + webidl.brandCheck(this, FileReader); + return this[kEvents].error; + } + set onerror(fn) { + webidl.brandCheck(this, FileReader); + if (this[kEvents].error) this.removeEventListener("error", this[kEvents].error); + if (typeof fn === "function") { + this[kEvents].error = fn; + this.addEventListener("error", fn); + } else this[kEvents].error = null; + } + get onloadstart() { + webidl.brandCheck(this, FileReader); + return this[kEvents].loadstart; + } + set onloadstart(fn) { + webidl.brandCheck(this, FileReader); + if (this[kEvents].loadstart) this.removeEventListener("loadstart", this[kEvents].loadstart); + if (typeof fn === "function") { + this[kEvents].loadstart = fn; + this.addEventListener("loadstart", fn); + } else this[kEvents].loadstart = null; + } + get onprogress() { + webidl.brandCheck(this, FileReader); + return this[kEvents].progress; + } + set onprogress(fn) { + webidl.brandCheck(this, FileReader); + if (this[kEvents].progress) this.removeEventListener("progress", this[kEvents].progress); + if (typeof fn === "function") { + this[kEvents].progress = fn; + this.addEventListener("progress", fn); + } else this[kEvents].progress = null; + } + get onload() { + webidl.brandCheck(this, FileReader); + return this[kEvents].load; + } + set onload(fn) { + webidl.brandCheck(this, FileReader); + if (this[kEvents].load) this.removeEventListener("load", this[kEvents].load); + if (typeof fn === "function") { + this[kEvents].load = fn; + this.addEventListener("load", fn); + } else this[kEvents].load = null; + } + get onabort() { + webidl.brandCheck(this, FileReader); + return this[kEvents].abort; + } + set onabort(fn) { + webidl.brandCheck(this, FileReader); + if (this[kEvents].abort) this.removeEventListener("abort", this[kEvents].abort); + if (typeof fn === "function") { + this[kEvents].abort = fn; + this.addEventListener("abort", fn); + } else this[kEvents].abort = null; + } + }; + FileReader.EMPTY = FileReader.prototype.EMPTY = 0; + FileReader.LOADING = FileReader.prototype.LOADING = 1; + FileReader.DONE = FileReader.prototype.DONE = 2; + Object.defineProperties(FileReader.prototype, { + EMPTY: staticPropertyDescriptors, + LOADING: staticPropertyDescriptors, + DONE: staticPropertyDescriptors, + readAsArrayBuffer: kEnumerableProperty, + readAsBinaryString: kEnumerableProperty, + readAsText: kEnumerableProperty, + readAsDataURL: kEnumerableProperty, + abort: kEnumerableProperty, + readyState: kEnumerableProperty, + result: kEnumerableProperty, + error: kEnumerableProperty, + onloadstart: kEnumerableProperty, + onprogress: kEnumerableProperty, + onload: kEnumerableProperty, + onabort: kEnumerableProperty, + onerror: kEnumerableProperty, + onloadend: kEnumerableProperty, + [Symbol.toStringTag]: { + value: "FileReader", + writable: false, + enumerable: false, + configurable: true + } + }); + Object.defineProperties(FileReader, { + EMPTY: staticPropertyDescriptors, + LOADING: staticPropertyDescriptors, + DONE: staticPropertyDescriptors + }); + module.exports = { FileReader }; +})); +//#endregion +//#region node_modules/undici/lib/web/cache/symbols.js +var require_symbols$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => { + module.exports = { kConstruct: require_symbols$4().kConstruct }; +})); +//#endregion +//#region node_modules/undici/lib/web/cache/util.js +var require_util$3 = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var assert$3 = __require("node:assert"); + var { URLSerializer } = require_data_url(); + var { isValidHeaderName } = require_util$6(); + /** + * @see https://url.spec.whatwg.org/#concept-url-equals + * @param {URL} A + * @param {URL} B + * @param {boolean | undefined} excludeFragment + * @returns {boolean} + */ + function urlEquals(A, B, excludeFragment = false) { + return URLSerializer(A, excludeFragment) === URLSerializer(B, excludeFragment); + } + /** + * @see https://github.com/chromium/chromium/blob/694d20d134cb553d8d89e5500b9148012b1ba299/content/browser/cache_storage/cache_storage_cache.cc#L260-L262 + * @param {string} header + */ + function getFieldValues(header) { + assert$3(header !== null); + const values = []; + for (let value of header.split(",")) { + value = value.trim(); + if (isValidHeaderName(value)) values.push(value); + } + return values; + } + module.exports = { + urlEquals, + getFieldValues + }; +})); +//#endregion +//#region node_modules/undici/lib/web/cache/cache.js +var require_cache = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { kConstruct } = require_symbols$1(); + var { urlEquals, getFieldValues } = require_util$3(); + var { kEnumerableProperty, isDisturbed } = require_util$7(); + var { webidl } = require_webidl(); + var { Response, cloneResponse, fromInnerResponse } = require_response(); + var { Request, fromInnerRequest } = require_request(); + var { kState } = require_symbols$3(); + var { fetching } = require_fetch(); + var { urlIsHttpHttpsScheme, createDeferredPromise, readAllBytes } = require_util$6(); + var assert$2 = __require("node:assert"); + /** + * @see https://w3c.github.io/ServiceWorker/#dfn-cache-batch-operation + * @typedef {Object} CacheBatchOperation + * @property {'delete' | 'put'} type + * @property {any} request + * @property {any} response + * @property {import('../../types/cache').CacheQueryOptions} options + */ + /** + * @see https://w3c.github.io/ServiceWorker/#dfn-request-response-list + * @typedef {[any, any][]} requestResponseList + */ + var Cache = class Cache { + /** + * @see https://w3c.github.io/ServiceWorker/#dfn-relevant-request-response-list + * @type {requestResponseList} + */ + #relevantRequestResponseList; + constructor() { + if (arguments[0] !== kConstruct) webidl.illegalConstructor(); + webidl.util.markAsUncloneable(this); + this.#relevantRequestResponseList = arguments[1]; + } + async match(request, options = {}) { + webidl.brandCheck(this, Cache); + const prefix = "Cache.match"; + webidl.argumentLengthCheck(arguments, 1, prefix); + request = webidl.converters.RequestInfo(request, prefix, "request"); + options = webidl.converters.CacheQueryOptions(options, prefix, "options"); + const p = this.#internalMatchAll(request, options, 1); + if (p.length === 0) return; + return p[0]; + } + async matchAll(request = void 0, options = {}) { + webidl.brandCheck(this, Cache); + const prefix = "Cache.matchAll"; + if (request !== void 0) request = webidl.converters.RequestInfo(request, prefix, "request"); + options = webidl.converters.CacheQueryOptions(options, prefix, "options"); + return this.#internalMatchAll(request, options); + } + async add(request) { + webidl.brandCheck(this, Cache); + const prefix = "Cache.add"; + webidl.argumentLengthCheck(arguments, 1, prefix); + request = webidl.converters.RequestInfo(request, prefix, "request"); + const requests = [request]; + return await this.addAll(requests); + } + async addAll(requests) { + webidl.brandCheck(this, Cache); + const prefix = "Cache.addAll"; + webidl.argumentLengthCheck(arguments, 1, prefix); + const responsePromises = []; + const requestList = []; + for (let request of requests) { + if (request === void 0) throw webidl.errors.conversionFailed({ + prefix, + argument: "Argument 1", + types: ["undefined is not allowed"] + }); + request = webidl.converters.RequestInfo(request); + if (typeof request === "string") continue; + const r = request[kState]; + if (!urlIsHttpHttpsScheme(r.url) || r.method !== "GET") throw webidl.errors.exception({ + header: prefix, + message: "Expected http/s scheme when method is not GET." + }); + } + /** @type {ReturnType[]} */ + const fetchControllers = []; + for (const request of requests) { + const r = new Request(request)[kState]; + if (!urlIsHttpHttpsScheme(r.url)) throw webidl.errors.exception({ + header: prefix, + message: "Expected http/s scheme." + }); + r.initiator = "fetch"; + r.destination = "subresource"; + requestList.push(r); + const responsePromise = createDeferredPromise(); + fetchControllers.push(fetching({ + request: r, + processResponse(response) { + if (response.type === "error" || response.status === 206 || response.status < 200 || response.status > 299) responsePromise.reject(webidl.errors.exception({ + header: "Cache.addAll", + message: "Received an invalid status code or the request failed." + })); + else if (response.headersList.contains("vary")) { + const fieldValues = getFieldValues(response.headersList.get("vary")); + for (const fieldValue of fieldValues) if (fieldValue === "*") { + responsePromise.reject(webidl.errors.exception({ + header: "Cache.addAll", + message: "invalid vary field value" + })); + for (const controller of fetchControllers) controller.abort(); + return; + } + } + }, + processResponseEndOfBody(response) { + if (response.aborted) { + responsePromise.reject(new DOMException("aborted", "AbortError")); + return; + } + responsePromise.resolve(response); + } + })); + responsePromises.push(responsePromise.promise); + } + const responses = await Promise.all(responsePromises); + const operations = []; + let index = 0; + for (const response of responses) { + /** @type {CacheBatchOperation} */ + const operation = { + type: "put", + request: requestList[index], + response + }; + operations.push(operation); + index++; + } + const cacheJobPromise = createDeferredPromise(); + let errorData = null; + try { + this.#batchCacheOperations(operations); + } catch (e) { + errorData = e; + } + queueMicrotask(() => { + if (errorData === null) cacheJobPromise.resolve(void 0); + else cacheJobPromise.reject(errorData); + }); + return cacheJobPromise.promise; + } + async put(request, response) { + webidl.brandCheck(this, Cache); + const prefix = "Cache.put"; + webidl.argumentLengthCheck(arguments, 2, prefix); + request = webidl.converters.RequestInfo(request, prefix, "request"); + response = webidl.converters.Response(response, prefix, "response"); + let innerRequest = null; + if (request instanceof Request) innerRequest = request[kState]; + else innerRequest = new Request(request)[kState]; + if (!urlIsHttpHttpsScheme(innerRequest.url) || innerRequest.method !== "GET") throw webidl.errors.exception({ + header: prefix, + message: "Expected an http/s scheme when method is not GET" + }); + const innerResponse = response[kState]; + if (innerResponse.status === 206) throw webidl.errors.exception({ + header: prefix, + message: "Got 206 status" + }); + if (innerResponse.headersList.contains("vary")) { + const fieldValues = getFieldValues(innerResponse.headersList.get("vary")); + for (const fieldValue of fieldValues) if (fieldValue === "*") throw webidl.errors.exception({ + header: prefix, + message: "Got * vary field value" + }); + } + if (innerResponse.body && (isDisturbed(innerResponse.body.stream) || innerResponse.body.stream.locked)) throw webidl.errors.exception({ + header: prefix, + message: "Response body is locked or disturbed" + }); + const clonedResponse = cloneResponse(innerResponse); + const bodyReadPromise = createDeferredPromise(); + if (innerResponse.body != null) readAllBytes(innerResponse.body.stream.getReader()).then(bodyReadPromise.resolve, bodyReadPromise.reject); + else bodyReadPromise.resolve(void 0); + /** @type {CacheBatchOperation[]} */ + const operations = []; + /** @type {CacheBatchOperation} */ + const operation = { + type: "put", + request: innerRequest, + response: clonedResponse + }; + operations.push(operation); + const bytes = await bodyReadPromise.promise; + if (clonedResponse.body != null) clonedResponse.body.source = bytes; + const cacheJobPromise = createDeferredPromise(); + let errorData = null; + try { + this.#batchCacheOperations(operations); + } catch (e) { + errorData = e; + } + queueMicrotask(() => { + if (errorData === null) cacheJobPromise.resolve(); + else cacheJobPromise.reject(errorData); + }); + return cacheJobPromise.promise; + } + async delete(request, options = {}) { + webidl.brandCheck(this, Cache); + const prefix = "Cache.delete"; + webidl.argumentLengthCheck(arguments, 1, prefix); + request = webidl.converters.RequestInfo(request, prefix, "request"); + options = webidl.converters.CacheQueryOptions(options, prefix, "options"); + /** + * @type {Request} + */ + let r = null; + if (request instanceof Request) { + r = request[kState]; + if (r.method !== "GET" && !options.ignoreMethod) return false; + } else { + assert$2(typeof request === "string"); + r = new Request(request)[kState]; + } + /** @type {CacheBatchOperation[]} */ + const operations = []; + /** @type {CacheBatchOperation} */ + const operation = { + type: "delete", + request: r, + options + }; + operations.push(operation); + const cacheJobPromise = createDeferredPromise(); + let errorData = null; + let requestResponses; + try { + requestResponses = this.#batchCacheOperations(operations); + } catch (e) { + errorData = e; + } + queueMicrotask(() => { + if (errorData === null) cacheJobPromise.resolve(!!requestResponses?.length); + else cacheJobPromise.reject(errorData); + }); + return cacheJobPromise.promise; + } + /** + * @see https://w3c.github.io/ServiceWorker/#dom-cache-keys + * @param {any} request + * @param {import('../../types/cache').CacheQueryOptions} options + * @returns {Promise} + */ + async keys(request = void 0, options = {}) { + webidl.brandCheck(this, Cache); + const prefix = "Cache.keys"; + if (request !== void 0) request = webidl.converters.RequestInfo(request, prefix, "request"); + options = webidl.converters.CacheQueryOptions(options, prefix, "options"); + let r = null; + if (request !== void 0) { + if (request instanceof Request) { + r = request[kState]; + if (r.method !== "GET" && !options.ignoreMethod) return []; + } else if (typeof request === "string") r = new Request(request)[kState]; + } + const promise = createDeferredPromise(); + const requests = []; + if (request === void 0) for (const requestResponse of this.#relevantRequestResponseList) requests.push(requestResponse[0]); + else { + const requestResponses = this.#queryCache(r, options); + for (const requestResponse of requestResponses) requests.push(requestResponse[0]); + } + queueMicrotask(() => { + const requestList = []; + for (const request of requests) { + const requestObject = fromInnerRequest(request, new AbortController().signal, "immutable"); + requestList.push(requestObject); + } + promise.resolve(Object.freeze(requestList)); + }); + return promise.promise; + } + /** + * @see https://w3c.github.io/ServiceWorker/#batch-cache-operations-algorithm + * @param {CacheBatchOperation[]} operations + * @returns {requestResponseList} + */ + #batchCacheOperations(operations) { + const cache = this.#relevantRequestResponseList; + const backupCache = [...cache]; + const addedItems = []; + const resultList = []; + try { + for (const operation of operations) { + if (operation.type !== "delete" && operation.type !== "put") throw webidl.errors.exception({ + header: "Cache.#batchCacheOperations", + message: "operation type does not match \"delete\" or \"put\"" + }); + if (operation.type === "delete" && operation.response != null) throw webidl.errors.exception({ + header: "Cache.#batchCacheOperations", + message: "delete operation should not have an associated response" + }); + if (this.#queryCache(operation.request, operation.options, addedItems).length) throw new DOMException("???", "InvalidStateError"); + let requestResponses; + if (operation.type === "delete") { + requestResponses = this.#queryCache(operation.request, operation.options); + if (requestResponses.length === 0) return []; + for (const requestResponse of requestResponses) { + const idx = cache.indexOf(requestResponse); + assert$2(idx !== -1); + cache.splice(idx, 1); + } + } else if (operation.type === "put") { + if (operation.response == null) throw webidl.errors.exception({ + header: "Cache.#batchCacheOperations", + message: "put operation should have an associated response" + }); + const r = operation.request; + if (!urlIsHttpHttpsScheme(r.url)) throw webidl.errors.exception({ + header: "Cache.#batchCacheOperations", + message: "expected http or https scheme" + }); + if (r.method !== "GET") throw webidl.errors.exception({ + header: "Cache.#batchCacheOperations", + message: "not get method" + }); + if (operation.options != null) throw webidl.errors.exception({ + header: "Cache.#batchCacheOperations", + message: "options must not be defined" + }); + requestResponses = this.#queryCache(operation.request); + for (const requestResponse of requestResponses) { + const idx = cache.indexOf(requestResponse); + assert$2(idx !== -1); + cache.splice(idx, 1); + } + cache.push([operation.request, operation.response]); + addedItems.push([operation.request, operation.response]); + } + resultList.push([operation.request, operation.response]); + } + return resultList; + } catch (e) { + this.#relevantRequestResponseList.length = 0; + this.#relevantRequestResponseList = backupCache; + throw e; + } + } + /** + * @see https://w3c.github.io/ServiceWorker/#query-cache + * @param {any} requestQuery + * @param {import('../../types/cache').CacheQueryOptions} options + * @param {requestResponseList} targetStorage + * @returns {requestResponseList} + */ + #queryCache(requestQuery, options, targetStorage) { + /** @type {requestResponseList} */ + const resultList = []; + const storage = targetStorage ?? this.#relevantRequestResponseList; + for (const requestResponse of storage) { + const [cachedRequest, cachedResponse] = requestResponse; + if (this.#requestMatchesCachedItem(requestQuery, cachedRequest, cachedResponse, options)) resultList.push(requestResponse); + } + return resultList; + } + /** + * @see https://w3c.github.io/ServiceWorker/#request-matches-cached-item-algorithm + * @param {any} requestQuery + * @param {any} request + * @param {any | null} response + * @param {import('../../types/cache').CacheQueryOptions | undefined} options + * @returns {boolean} + */ + #requestMatchesCachedItem(requestQuery, request, response = null, options) { + const queryURL = new URL(requestQuery.url); + const cachedURL = new URL(request.url); + if (options?.ignoreSearch) { + cachedURL.search = ""; + queryURL.search = ""; + } + if (!urlEquals(queryURL, cachedURL, true)) return false; + if (response == null || options?.ignoreVary || !response.headersList.contains("vary")) return true; + const fieldValues = getFieldValues(response.headersList.get("vary")); + for (const fieldValue of fieldValues) { + if (fieldValue === "*") return false; + if (request.headersList.get(fieldValue) !== requestQuery.headersList.get(fieldValue)) return false; + } + return true; + } + #internalMatchAll(request, options, maxResponses = Infinity) { + let r = null; + if (request !== void 0) { + if (request instanceof Request) { + r = request[kState]; + if (r.method !== "GET" && !options.ignoreMethod) return []; + } else if (typeof request === "string") r = new Request(request)[kState]; + } + const responses = []; + if (request === void 0) for (const requestResponse of this.#relevantRequestResponseList) responses.push(requestResponse[1]); + else { + const requestResponses = this.#queryCache(r, options); + for (const requestResponse of requestResponses) responses.push(requestResponse[1]); + } + const responseList = []; + for (const response of responses) { + const responseObject = fromInnerResponse(response, "immutable"); + responseList.push(responseObject.clone()); + if (responseList.length >= maxResponses) break; + } + return Object.freeze(responseList); + } + }; + Object.defineProperties(Cache.prototype, { + [Symbol.toStringTag]: { + value: "Cache", + configurable: true + }, + match: kEnumerableProperty, + matchAll: kEnumerableProperty, + add: kEnumerableProperty, + addAll: kEnumerableProperty, + put: kEnumerableProperty, + delete: kEnumerableProperty, + keys: kEnumerableProperty + }); + var cacheQueryOptionConverters = [ + { + key: "ignoreSearch", + converter: webidl.converters.boolean, + defaultValue: () => false + }, + { + key: "ignoreMethod", + converter: webidl.converters.boolean, + defaultValue: () => false + }, + { + key: "ignoreVary", + converter: webidl.converters.boolean, + defaultValue: () => false + } + ]; + webidl.converters.CacheQueryOptions = webidl.dictionaryConverter(cacheQueryOptionConverters); + webidl.converters.MultiCacheQueryOptions = webidl.dictionaryConverter([...cacheQueryOptionConverters, { + key: "cacheName", + converter: webidl.converters.DOMString + }]); + webidl.converters.Response = webidl.interfaceConverter(Response); + webidl.converters["sequence"] = webidl.sequenceConverter(webidl.converters.RequestInfo); + module.exports = { Cache }; +})); +//#endregion +//#region node_modules/undici/lib/web/cache/cachestorage.js +var require_cachestorage = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { kConstruct } = require_symbols$1(); + var { Cache } = require_cache(); + var { webidl } = require_webidl(); + var { kEnumerableProperty } = require_util$7(); + var CacheStorage = class CacheStorage { + /** + * @see https://w3c.github.io/ServiceWorker/#dfn-relevant-name-to-cache-map + * @type {Map} + */ + async has(cacheName) { + webidl.brandCheck(this, CacheStorage); + const prefix = "CacheStorage.has"; + webidl.argumentLengthCheck(arguments, 1, prefix); + cacheName = webidl.converters.DOMString(cacheName, prefix, "cacheName"); + return this.#caches.has(cacheName); + } + /** + * @see https://w3c.github.io/ServiceWorker/#dom-cachestorage-open + * @param {string} cacheName + * @returns {Promise} + */ + async open(cacheName) { + webidl.brandCheck(this, CacheStorage); + const prefix = "CacheStorage.open"; + webidl.argumentLengthCheck(arguments, 1, prefix); + cacheName = webidl.converters.DOMString(cacheName, prefix, "cacheName"); + if (this.#caches.has(cacheName)) return new Cache(kConstruct, this.#caches.get(cacheName)); + const cache = []; + this.#caches.set(cacheName, cache); + return new Cache(kConstruct, cache); + } + /** + * @see https://w3c.github.io/ServiceWorker/#cache-storage-delete + * @param {string} cacheName + * @returns {Promise} + */ + async delete(cacheName) { + webidl.brandCheck(this, CacheStorage); + const prefix = "CacheStorage.delete"; + webidl.argumentLengthCheck(arguments, 1, prefix); + cacheName = webidl.converters.DOMString(cacheName, prefix, "cacheName"); + return this.#caches.delete(cacheName); + } + /** + * @see https://w3c.github.io/ServiceWorker/#cache-storage-keys + * @returns {Promise} + */ + async keys() { + webidl.brandCheck(this, CacheStorage); + return [...this.#caches.keys()]; + } + }; + Object.defineProperties(CacheStorage.prototype, { + [Symbol.toStringTag]: { + value: "CacheStorage", + configurable: true + }, + match: kEnumerableProperty, + has: kEnumerableProperty, + open: kEnumerableProperty, + delete: kEnumerableProperty, + keys: kEnumerableProperty + }); + module.exports = { CacheStorage }; +})); +//#endregion +//#region node_modules/undici/lib/web/cookies/constants.js +var require_constants$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => { + module.exports = { + maxAttributeValueSize: 1024, + maxNameValuePairSize: 4096 + }; +})); +//#endregion +//#region node_modules/undici/lib/web/cookies/util.js +var require_util$2 = /* @__PURE__ */ __commonJSMin(((exports, module) => { + /** + * @param {string} value + * @returns {boolean} + */ + function isCTLExcludingHtab(value) { + for (let i = 0; i < value.length; ++i) { + const code = value.charCodeAt(i); + if (code >= 0 && code <= 8 || code >= 10 && code <= 31 || code === 127) return true; + } + return false; + } + /** + CHAR = + token = 1* + separators = "(" | ")" | "<" | ">" | "@" + | "," | ";" | ":" | "\" | <"> + | "/" | "[" | "]" | "?" | "=" + | "{" | "}" | SP | HT + * @param {string} name + */ + function validateCookieName(name) { + for (let i = 0; i < name.length; ++i) { + const code = name.charCodeAt(i); + if (code < 33 || code > 126 || code === 34 || code === 40 || code === 41 || code === 60 || code === 62 || code === 64 || code === 44 || code === 59 || code === 58 || code === 92 || code === 47 || code === 91 || code === 93 || code === 63 || code === 61 || code === 123 || code === 125) throw new Error("Invalid cookie name"); + } + } + /** + cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) + cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E + ; US-ASCII characters excluding CTLs, + ; whitespace DQUOTE, comma, semicolon, + ; and backslash + * @param {string} value + */ + function validateCookieValue(value) { + let len = value.length; + let i = 0; + if (value[0] === "\"") { + if (len === 1 || value[len - 1] !== "\"") throw new Error("Invalid cookie value"); + --len; + ++i; + } + while (i < len) { + const code = value.charCodeAt(i++); + if (code < 33 || code > 126 || code === 34 || code === 44 || code === 59 || code === 92) throw new Error("Invalid cookie value"); + } + } + /** + * path-value = + * @param {string} path + */ + function validateCookiePath(path) { + for (let i = 0; i < path.length; ++i) { + const code = path.charCodeAt(i); + if (code < 32 || code === 127 || code === 59) throw new Error("Invalid cookie path"); + } + } + /** + * I have no idea why these values aren't allowed to be honest, + * but Deno tests these. - Khafra + * @param {string} domain + */ + function validateCookieDomain(domain) { + if (domain.startsWith("-") || domain.endsWith(".") || domain.endsWith("-")) throw new Error("Invalid cookie domain"); + } + var IMFDays = [ + "Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat" + ]; + var IMFMonths = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec" + ]; + var IMFPaddedNumbers = Array(61).fill(0).map((_, i) => i.toString().padStart(2, "0")); + /** + * @see https://www.rfc-editor.org/rfc/rfc7231#section-7.1.1.1 + * @param {number|Date} date + IMF-fixdate = day-name "," SP date1 SP time-of-day SP GMT + ; fixed length/zone/capitalization subset of the format + ; see Section 3.3 of [RFC5322] + + day-name = %x4D.6F.6E ; "Mon", case-sensitive + / %x54.75.65 ; "Tue", case-sensitive + / %x57.65.64 ; "Wed", case-sensitive + / %x54.68.75 ; "Thu", case-sensitive + / %x46.72.69 ; "Fri", case-sensitive + / %x53.61.74 ; "Sat", case-sensitive + / %x53.75.6E ; "Sun", case-sensitive + date1 = day SP month SP year + ; e.g., 02 Jun 1982 + + day = 2DIGIT + month = %x4A.61.6E ; "Jan", case-sensitive + / %x46.65.62 ; "Feb", case-sensitive + / %x4D.61.72 ; "Mar", case-sensitive + / %x41.70.72 ; "Apr", case-sensitive + / %x4D.61.79 ; "May", case-sensitive + / %x4A.75.6E ; "Jun", case-sensitive + / %x4A.75.6C ; "Jul", case-sensitive + / %x41.75.67 ; "Aug", case-sensitive + / %x53.65.70 ; "Sep", case-sensitive + / %x4F.63.74 ; "Oct", case-sensitive + / %x4E.6F.76 ; "Nov", case-sensitive + / %x44.65.63 ; "Dec", case-sensitive + year = 4DIGIT + + GMT = %x47.4D.54 ; "GMT", case-sensitive + + time-of-day = hour ":" minute ":" second + ; 00:00:00 - 23:59:60 (leap second) + + hour = 2DIGIT + minute = 2DIGIT + second = 2DIGIT + */ + function toIMFDate(date) { + if (typeof date === "number") date = new Date(date); + return `${IMFDays[date.getUTCDay()]}, ${IMFPaddedNumbers[date.getUTCDate()]} ${IMFMonths[date.getUTCMonth()]} ${date.getUTCFullYear()} ${IMFPaddedNumbers[date.getUTCHours()]}:${IMFPaddedNumbers[date.getUTCMinutes()]}:${IMFPaddedNumbers[date.getUTCSeconds()]} GMT`; + } + /** + max-age-av = "Max-Age=" non-zero-digit *DIGIT + ; In practice, both expires-av and max-age-av + ; are limited to dates representable by the + ; user agent. + * @param {number} maxAge + */ + function validateCookieMaxAge(maxAge) { + if (maxAge < 0) throw new Error("Invalid cookie max-age"); + } + /** + * @see https://www.rfc-editor.org/rfc/rfc6265#section-4.1.1 + * @param {import('./index').Cookie} cookie + */ + function stringify(cookie) { + if (cookie.name.length === 0) return null; + validateCookieName(cookie.name); + validateCookieValue(cookie.value); + const out = [`${cookie.name}=${cookie.value}`]; + if (cookie.name.startsWith("__Secure-")) cookie.secure = true; + if (cookie.name.startsWith("__Host-")) { + cookie.secure = true; + cookie.domain = null; + cookie.path = "/"; + } + if (cookie.secure) out.push("Secure"); + if (cookie.httpOnly) out.push("HttpOnly"); + if (typeof cookie.maxAge === "number") { + validateCookieMaxAge(cookie.maxAge); + out.push(`Max-Age=${cookie.maxAge}`); + } + if (cookie.domain) { + validateCookieDomain(cookie.domain); + out.push(`Domain=${cookie.domain}`); + } + if (cookie.path) { + validateCookiePath(cookie.path); + out.push(`Path=${cookie.path}`); + } + if (cookie.expires && cookie.expires.toString() !== "Invalid Date") out.push(`Expires=${toIMFDate(cookie.expires)}`); + if (cookie.sameSite) out.push(`SameSite=${cookie.sameSite}`); + for (const part of cookie.unparsed) { + if (!part.includes("=")) throw new Error("Invalid unparsed"); + const [key, ...value] = part.split("="); + out.push(`${key.trim()}=${value.join("=")}`); + } + return out.join("; "); + } + module.exports = { + isCTLExcludingHtab, + validateCookieName, + validateCookiePath, + validateCookieValue, + toIMFDate, + stringify + }; +})); +//#endregion +//#region node_modules/undici/lib/web/cookies/parse.js +var require_parse = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { maxNameValuePairSize, maxAttributeValueSize } = require_constants$1(); + var { isCTLExcludingHtab } = require_util$2(); + var { collectASequenceOfCodePointsFast } = require_data_url(); + var assert$1 = __require("node:assert"); + /** + * @description Parses the field-value attributes of a set-cookie header string. + * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4 + * @param {string} header + * @returns if the header is invalid, null will be returned + */ + function parseSetCookie(header) { + if (isCTLExcludingHtab(header)) return null; + let nameValuePair = ""; + let unparsedAttributes = ""; + let name = ""; + let value = ""; + if (header.includes(";")) { + const position = { position: 0 }; + nameValuePair = collectASequenceOfCodePointsFast(";", header, position); + unparsedAttributes = header.slice(position.position); + } else nameValuePair = header; + if (!nameValuePair.includes("=")) value = nameValuePair; + else { + const position = { position: 0 }; + name = collectASequenceOfCodePointsFast("=", nameValuePair, position); + value = nameValuePair.slice(position.position + 1); + } + name = name.trim(); + value = value.trim(); + if (name.length + value.length > maxNameValuePairSize) return null; + return { + name, + value, + ...parseUnparsedAttributes(unparsedAttributes) + }; + } + /** + * Parses the remaining attributes of a set-cookie header + * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4 + * @param {string} unparsedAttributes + * @param {[Object.]={}} cookieAttributeList + */ + function parseUnparsedAttributes(unparsedAttributes, cookieAttributeList = {}) { + if (unparsedAttributes.length === 0) return cookieAttributeList; + assert$1(unparsedAttributes[0] === ";"); + unparsedAttributes = unparsedAttributes.slice(1); + let cookieAv = ""; + if (unparsedAttributes.includes(";")) { + cookieAv = collectASequenceOfCodePointsFast(";", unparsedAttributes, { position: 0 }); + unparsedAttributes = unparsedAttributes.slice(cookieAv.length); + } else { + cookieAv = unparsedAttributes; + unparsedAttributes = ""; + } + let attributeName = ""; + let attributeValue = ""; + if (cookieAv.includes("=")) { + const position = { position: 0 }; + attributeName = collectASequenceOfCodePointsFast("=", cookieAv, position); + attributeValue = cookieAv.slice(position.position + 1); + } else attributeName = cookieAv; + attributeName = attributeName.trim(); + attributeValue = attributeValue.trim(); + if (attributeValue.length > maxAttributeValueSize) return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList); + const attributeNameLowercase = attributeName.toLowerCase(); + if (attributeNameLowercase === "expires") cookieAttributeList.expires = new Date(attributeValue); + else if (attributeNameLowercase === "max-age") { + const charCode = attributeValue.charCodeAt(0); + if ((charCode < 48 || charCode > 57) && attributeValue[0] !== "-") return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList); + if (!/^\d+$/.test(attributeValue)) return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList); + cookieAttributeList.maxAge = Number(attributeValue); + } else if (attributeNameLowercase === "domain") { + let cookieDomain = attributeValue; + if (cookieDomain[0] === ".") cookieDomain = cookieDomain.slice(1); + cookieDomain = cookieDomain.toLowerCase(); + cookieAttributeList.domain = cookieDomain; + } else if (attributeNameLowercase === "path") { + let cookiePath = ""; + if (attributeValue.length === 0 || attributeValue[0] !== "/") cookiePath = "/"; + else cookiePath = attributeValue; + cookieAttributeList.path = cookiePath; + } else if (attributeNameLowercase === "secure") cookieAttributeList.secure = true; + else if (attributeNameLowercase === "httponly") cookieAttributeList.httpOnly = true; + else if (attributeNameLowercase === "samesite") { + const attributeValueLowercase = attributeValue.toLowerCase(); + if (attributeValueLowercase === "none") cookieAttributeList.sameSite = "None"; + else if (attributeValueLowercase === "strict") cookieAttributeList.sameSite = "Strict"; + else if (attributeValueLowercase === "lax") cookieAttributeList.sameSite = "Lax"; + } else { + cookieAttributeList.unparsed ??= []; + cookieAttributeList.unparsed.push(`${attributeName}=${attributeValue}`); + } + return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList); + } + module.exports = { + parseSetCookie, + parseUnparsedAttributes + }; +})); +//#endregion +//#region node_modules/undici/lib/web/cookies/index.js +var require_cookies = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { parseSetCookie } = require_parse(); + var { stringify } = require_util$2(); + var { webidl } = require_webidl(); + var { Headers } = require_headers(); + /** + * @typedef {Object} Cookie + * @property {string} name + * @property {string} value + * @property {Date|number|undefined} expires + * @property {number|undefined} maxAge + * @property {string|undefined} domain + * @property {string|undefined} path + * @property {boolean|undefined} secure + * @property {boolean|undefined} httpOnly + * @property {'Strict'|'Lax'|'None'} sameSite + * @property {string[]} unparsed + */ + /** + * @param {Headers} headers + * @returns {Record} + */ + function getCookies(headers) { + webidl.argumentLengthCheck(arguments, 1, "getCookies"); + webidl.brandCheck(headers, Headers, { strict: false }); + const cookie = headers.get("cookie"); + const out = {}; + if (!cookie) return out; + for (const piece of cookie.split(";")) { + const [name, ...value] = piece.split("="); + out[name.trim()] = value.join("="); + } + return out; + } + /** + * @param {Headers} headers + * @param {string} name + * @param {{ path?: string, domain?: string }|undefined} attributes + * @returns {void} + */ + function deleteCookie(headers, name, attributes) { + webidl.brandCheck(headers, Headers, { strict: false }); + const prefix = "deleteCookie"; + webidl.argumentLengthCheck(arguments, 2, prefix); + name = webidl.converters.DOMString(name, prefix, "name"); + attributes = webidl.converters.DeleteCookieAttributes(attributes); + setCookie(headers, { + name, + value: "", + expires: /* @__PURE__ */ new Date(0), + ...attributes + }); + } + /** + * @param {Headers} headers + * @returns {Cookie[]} + */ + function getSetCookies(headers) { + webidl.argumentLengthCheck(arguments, 1, "getSetCookies"); + webidl.brandCheck(headers, Headers, { strict: false }); + const cookies = headers.getSetCookie(); + if (!cookies) return []; + return cookies.map((pair) => parseSetCookie(pair)); + } + /** + * @param {Headers} headers + * @param {Cookie} cookie + * @returns {void} + */ + function setCookie(headers, cookie) { + webidl.argumentLengthCheck(arguments, 2, "setCookie"); + webidl.brandCheck(headers, Headers, { strict: false }); + cookie = webidl.converters.Cookie(cookie); + const str = stringify(cookie); + if (str) headers.append("Set-Cookie", str); + } + webidl.converters.DeleteCookieAttributes = webidl.dictionaryConverter([{ + converter: webidl.nullableConverter(webidl.converters.DOMString), + key: "path", + defaultValue: () => null + }, { + converter: webidl.nullableConverter(webidl.converters.DOMString), + key: "domain", + defaultValue: () => null + }]); + webidl.converters.Cookie = webidl.dictionaryConverter([ + { + converter: webidl.converters.DOMString, + key: "name" + }, + { + converter: webidl.converters.DOMString, + key: "value" + }, + { + converter: webidl.nullableConverter((value) => { + if (typeof value === "number") return webidl.converters["unsigned long long"](value); + return new Date(value); + }), + key: "expires", + defaultValue: () => null + }, + { + converter: webidl.nullableConverter(webidl.converters["long long"]), + key: "maxAge", + defaultValue: () => null + }, + { + converter: webidl.nullableConverter(webidl.converters.DOMString), + key: "domain", + defaultValue: () => null + }, + { + converter: webidl.nullableConverter(webidl.converters.DOMString), + key: "path", + defaultValue: () => null + }, + { + converter: webidl.nullableConverter(webidl.converters.boolean), + key: "secure", + defaultValue: () => null + }, + { + converter: webidl.nullableConverter(webidl.converters.boolean), + key: "httpOnly", + defaultValue: () => null + }, + { + converter: webidl.converters.USVString, + key: "sameSite", + allowedValues: [ + "Strict", + "Lax", + "None" + ] + }, + { + converter: webidl.sequenceConverter(webidl.converters.DOMString), + key: "unparsed", + defaultValue: () => new Array(0) + } + ]); + module.exports = { + getCookies, + deleteCookie, + getSetCookies, + setCookie + }; +})); +//#endregion +//#region node_modules/undici/lib/web/websocket/events.js +var require_events = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { webidl } = require_webidl(); + var { kEnumerableProperty } = require_util$7(); + var { kConstruct } = require_symbols$4(); + var { MessagePort } = __require("node:worker_threads"); + /** + * @see https://html.spec.whatwg.org/multipage/comms.html#messageevent + */ + var MessageEvent = class MessageEvent extends Event { + #eventInit; + constructor(type, eventInitDict = {}) { + if (type === kConstruct) { + super(arguments[1], arguments[2]); + webidl.util.markAsUncloneable(this); + return; + } + const prefix = "MessageEvent constructor"; + webidl.argumentLengthCheck(arguments, 1, prefix); + type = webidl.converters.DOMString(type, prefix, "type"); + eventInitDict = webidl.converters.MessageEventInit(eventInitDict, prefix, "eventInitDict"); + super(type, eventInitDict); + this.#eventInit = eventInitDict; + webidl.util.markAsUncloneable(this); + } + get data() { + webidl.brandCheck(this, MessageEvent); + return this.#eventInit.data; + } + get origin() { + webidl.brandCheck(this, MessageEvent); + return this.#eventInit.origin; + } + get lastEventId() { + webidl.brandCheck(this, MessageEvent); + return this.#eventInit.lastEventId; + } + get source() { + webidl.brandCheck(this, MessageEvent); + return this.#eventInit.source; + } + get ports() { + webidl.brandCheck(this, MessageEvent); + if (!Object.isFrozen(this.#eventInit.ports)) Object.freeze(this.#eventInit.ports); + return this.#eventInit.ports; + } + initMessageEvent(type, bubbles = false, cancelable = false, data = null, origin = "", lastEventId = "", source = null, ports = []) { + webidl.brandCheck(this, MessageEvent); + webidl.argumentLengthCheck(arguments, 1, "MessageEvent.initMessageEvent"); + return new MessageEvent(type, { + bubbles, + cancelable, + data, + origin, + lastEventId, + source, + ports + }); + } + static createFastMessageEvent(type, init) { + const messageEvent = new MessageEvent(kConstruct, type, init); + messageEvent.#eventInit = init; + messageEvent.#eventInit.data ??= null; + messageEvent.#eventInit.origin ??= ""; + messageEvent.#eventInit.lastEventId ??= ""; + messageEvent.#eventInit.source ??= null; + messageEvent.#eventInit.ports ??= []; + return messageEvent; + } + }; + var { createFastMessageEvent } = MessageEvent; + delete MessageEvent.createFastMessageEvent; + /** + * @see https://websockets.spec.whatwg.org/#the-closeevent-interface + */ + var CloseEvent = class CloseEvent extends Event { + #eventInit; + constructor(type, eventInitDict = {}) { + const prefix = "CloseEvent constructor"; + webidl.argumentLengthCheck(arguments, 1, prefix); + type = webidl.converters.DOMString(type, prefix, "type"); + eventInitDict = webidl.converters.CloseEventInit(eventInitDict); + super(type, eventInitDict); + this.#eventInit = eventInitDict; + webidl.util.markAsUncloneable(this); + } + get wasClean() { + webidl.brandCheck(this, CloseEvent); + return this.#eventInit.wasClean; + } + get code() { + webidl.brandCheck(this, CloseEvent); + return this.#eventInit.code; + } + get reason() { + webidl.brandCheck(this, CloseEvent); + return this.#eventInit.reason; + } + }; + var ErrorEvent = class ErrorEvent extends Event { + #eventInit; + constructor(type, eventInitDict) { + const prefix = "ErrorEvent constructor"; + webidl.argumentLengthCheck(arguments, 1, prefix); + super(type, eventInitDict); + webidl.util.markAsUncloneable(this); + type = webidl.converters.DOMString(type, prefix, "type"); + eventInitDict = webidl.converters.ErrorEventInit(eventInitDict ?? {}); + this.#eventInit = eventInitDict; + } + get message() { + webidl.brandCheck(this, ErrorEvent); + return this.#eventInit.message; + } + get filename() { + webidl.brandCheck(this, ErrorEvent); + return this.#eventInit.filename; + } + get lineno() { + webidl.brandCheck(this, ErrorEvent); + return this.#eventInit.lineno; + } + get colno() { + webidl.brandCheck(this, ErrorEvent); + return this.#eventInit.colno; + } + get error() { + webidl.brandCheck(this, ErrorEvent); + return this.#eventInit.error; + } + }; + Object.defineProperties(MessageEvent.prototype, { + [Symbol.toStringTag]: { + value: "MessageEvent", + configurable: true + }, + data: kEnumerableProperty, + origin: kEnumerableProperty, + lastEventId: kEnumerableProperty, + source: kEnumerableProperty, + ports: kEnumerableProperty, + initMessageEvent: kEnumerableProperty + }); + Object.defineProperties(CloseEvent.prototype, { + [Symbol.toStringTag]: { + value: "CloseEvent", + configurable: true + }, + reason: kEnumerableProperty, + code: kEnumerableProperty, + wasClean: kEnumerableProperty + }); + Object.defineProperties(ErrorEvent.prototype, { + [Symbol.toStringTag]: { + value: "ErrorEvent", + configurable: true + }, + message: kEnumerableProperty, + filename: kEnumerableProperty, + lineno: kEnumerableProperty, + colno: kEnumerableProperty, + error: kEnumerableProperty + }); + webidl.converters.MessagePort = webidl.interfaceConverter(MessagePort); + webidl.converters["sequence"] = webidl.sequenceConverter(webidl.converters.MessagePort); + var eventInit = [ + { + key: "bubbles", + converter: webidl.converters.boolean, + defaultValue: () => false + }, + { + key: "cancelable", + converter: webidl.converters.boolean, + defaultValue: () => false + }, + { + key: "composed", + converter: webidl.converters.boolean, + defaultValue: () => false + } + ]; + webidl.converters.MessageEventInit = webidl.dictionaryConverter([ + ...eventInit, + { + key: "data", + converter: webidl.converters.any, + defaultValue: () => null + }, + { + key: "origin", + converter: webidl.converters.USVString, + defaultValue: () => "" + }, + { + key: "lastEventId", + converter: webidl.converters.DOMString, + defaultValue: () => "" + }, + { + key: "source", + converter: webidl.nullableConverter(webidl.converters.MessagePort), + defaultValue: () => null + }, + { + key: "ports", + converter: webidl.converters["sequence"], + defaultValue: () => new Array(0) + } + ]); + webidl.converters.CloseEventInit = webidl.dictionaryConverter([ + ...eventInit, + { + key: "wasClean", + converter: webidl.converters.boolean, + defaultValue: () => false + }, + { + key: "code", + converter: webidl.converters["unsigned short"], + defaultValue: () => 0 + }, + { + key: "reason", + converter: webidl.converters.USVString, + defaultValue: () => "" + } + ]); + webidl.converters.ErrorEventInit = webidl.dictionaryConverter([ + ...eventInit, + { + key: "message", + converter: webidl.converters.DOMString, + defaultValue: () => "" + }, + { + key: "filename", + converter: webidl.converters.USVString, + defaultValue: () => "" + }, + { + key: "lineno", + converter: webidl.converters["unsigned long"], + defaultValue: () => 0 + }, + { + key: "colno", + converter: webidl.converters["unsigned long"], + defaultValue: () => 0 + }, + { + key: "error", + converter: webidl.converters.any + } + ]); + module.exports = { + MessageEvent, + CloseEvent, + ErrorEvent, + createFastMessageEvent + }; +})); +//#endregion +//#region node_modules/undici/lib/web/websocket/constants.js +var require_constants = /* @__PURE__ */ __commonJSMin(((exports, module) => { + module.exports = { + uid: "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", + sentCloseFrameState: { + NOT_SENT: 0, + PROCESSING: 1, + SENT: 2 + }, + staticPropertyDescriptors: { + enumerable: true, + writable: false, + configurable: false + }, + states: { + CONNECTING: 0, + OPEN: 1, + CLOSING: 2, + CLOSED: 3 + }, + opcodes: { + CONTINUATION: 0, + TEXT: 1, + BINARY: 2, + CLOSE: 8, + PING: 9, + PONG: 10 + }, + maxUnsigned16Bit: 2 ** 16 - 1, + parserStates: { + INFO: 0, + PAYLOADLENGTH_16: 2, + PAYLOADLENGTH_64: 3, + READ_DATA: 4 + }, + emptyBuffer: Buffer.allocUnsafe(0), + sendHints: { + string: 1, + typedArray: 2, + arrayBuffer: 3, + blob: 4 + } + }; +})); +//#endregion +//#region node_modules/undici/lib/web/websocket/symbols.js +var require_symbols = /* @__PURE__ */ __commonJSMin(((exports, module) => { + module.exports = { + kWebSocketURL: Symbol("url"), + kReadyState: Symbol("ready state"), + kController: Symbol("controller"), + kResponse: Symbol("response"), + kBinaryType: Symbol("binary type"), + kSentClose: Symbol("sent close"), + kReceivedClose: Symbol("received close"), + kByteParser: Symbol("byte parser") + }; +})); +//#endregion +//#region node_modules/undici/lib/web/websocket/util.js +var require_util$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { kReadyState, kController, kResponse, kBinaryType, kWebSocketURL } = require_symbols(); + var { states, opcodes } = require_constants(); + var { ErrorEvent, createFastMessageEvent } = require_events(); + var { isUtf8 } = __require("node:buffer"); + var { collectASequenceOfCodePointsFast, removeHTTPWhitespace } = require_data_url(); + /** + * @param {import('./websocket').WebSocket} ws + * @returns {boolean} + */ + function isConnecting(ws) { + return ws[kReadyState] === states.CONNECTING; + } + /** + * @param {import('./websocket').WebSocket} ws + * @returns {boolean} + */ + function isEstablished(ws) { + return ws[kReadyState] === states.OPEN; + } + /** + * @param {import('./websocket').WebSocket} ws + * @returns {boolean} + */ + function isClosing(ws) { + return ws[kReadyState] === states.CLOSING; + } + /** + * @param {import('./websocket').WebSocket} ws + * @returns {boolean} + */ + function isClosed(ws) { + return ws[kReadyState] === states.CLOSED; + } + /** + * @see https://dom.spec.whatwg.org/#concept-event-fire + * @param {string} e + * @param {EventTarget} target + * @param {(...args: ConstructorParameters) => Event} eventFactory + * @param {EventInit | undefined} eventInitDict + */ + function fireEvent(e, target, eventFactory = (type, init) => new Event(type, init), eventInitDict = {}) { + const event = eventFactory(e, eventInitDict); + target.dispatchEvent(event); + } + /** + * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol + * @param {import('./websocket').WebSocket} ws + * @param {number} type Opcode + * @param {Buffer} data application data + */ + function websocketMessageReceived(ws, type, data) { + if (ws[kReadyState] !== states.OPEN) return; + let dataForEvent; + if (type === opcodes.TEXT) try { + dataForEvent = utf8Decode(data); + } catch { + failWebsocketConnection(ws, "Received invalid UTF-8 in text frame."); + return; + } + else if (type === opcodes.BINARY) if (ws[kBinaryType] === "blob") dataForEvent = new Blob([data]); + else dataForEvent = toArrayBuffer(data); + fireEvent("message", ws, createFastMessageEvent, { + origin: ws[kWebSocketURL].origin, + data: dataForEvent + }); + } + function toArrayBuffer(buffer) { + if (buffer.byteLength === buffer.buffer.byteLength) return buffer.buffer; + return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength); + } + /** + * @see https://datatracker.ietf.org/doc/html/rfc6455 + * @see https://datatracker.ietf.org/doc/html/rfc2616 + * @see https://bugs.chromium.org/p/chromium/issues/detail?id=398407 + * @param {string} protocol + */ + function isValidSubprotocol(protocol) { + if (protocol.length === 0) return false; + for (let i = 0; i < protocol.length; ++i) { + const code = protocol.charCodeAt(i); + if (code < 33 || code > 126 || code === 34 || code === 40 || code === 41 || code === 44 || code === 47 || code === 58 || code === 59 || code === 60 || code === 61 || code === 62 || code === 63 || code === 64 || code === 91 || code === 92 || code === 93 || code === 123 || code === 125) return false; + } + return true; + } + /** + * @see https://datatracker.ietf.org/doc/html/rfc6455#section-7-4 + * @param {number} code + */ + function isValidStatusCode(code) { + if (code >= 1e3 && code < 1015) return code !== 1004 && code !== 1005 && code !== 1006; + return code >= 3e3 && code <= 4999; + } + /** + * @param {import('./websocket').WebSocket} ws + * @param {string|undefined} reason + */ + function failWebsocketConnection(ws, reason) { + const { [kController]: controller, [kResponse]: response } = ws; + controller.abort(); + if (response?.socket && !response.socket.destroyed) response.socket.destroy(); + if (reason) fireEvent("error", ws, (type, init) => new ErrorEvent(type, init), { + error: new Error(reason), + message: reason + }); + } + /** + * @see https://datatracker.ietf.org/doc/html/rfc6455#section-5.5 + * @param {number} opcode + */ + function isControlFrame(opcode) { + return opcode === opcodes.CLOSE || opcode === opcodes.PING || opcode === opcodes.PONG; + } + function isContinuationFrame(opcode) { + return opcode === opcodes.CONTINUATION; + } + function isTextBinaryFrame(opcode) { + return opcode === opcodes.TEXT || opcode === opcodes.BINARY; + } + function isValidOpcode(opcode) { + return isTextBinaryFrame(opcode) || isContinuationFrame(opcode) || isControlFrame(opcode); + } + /** + * Parses a Sec-WebSocket-Extensions header value. + * @param {string} extensions + * @returns {Map} + */ + function parseExtensions(extensions) { + const position = { position: 0 }; + const extensionList = /* @__PURE__ */ new Map(); + while (position.position < extensions.length) { + const [name, value = ""] = collectASequenceOfCodePointsFast(";", extensions, position).split("="); + extensionList.set(removeHTTPWhitespace(name, true, false), removeHTTPWhitespace(value, false, true)); + position.position++; + } + return extensionList; + } + /** + * @see https://www.rfc-editor.org/rfc/rfc7692#section-7.1.2.2 + * @description "client-max-window-bits = 1*DIGIT" + * @param {string} value + */ + function isValidClientWindowBits(value) { + if (value.length === 0) return false; + for (let i = 0; i < value.length; i++) { + const byte = value.charCodeAt(i); + if (byte < 48 || byte > 57) return false; + } + const num = Number.parseInt(value, 10); + return num >= 8 && num <= 15; + } + var hasIntl = typeof process.versions.icu === "string"; + var fatalDecoder = hasIntl ? new TextDecoder("utf-8", { fatal: true }) : void 0; + /** + * Converts a Buffer to utf-8, even on platforms without icu. + * @param {Buffer} buffer + */ + var utf8Decode = hasIntl ? fatalDecoder.decode.bind(fatalDecoder) : function(buffer) { + if (isUtf8(buffer)) return buffer.toString("utf-8"); + throw new TypeError("Invalid utf-8 received."); + }; + module.exports = { + isConnecting, + isEstablished, + isClosing, + isClosed, + fireEvent, + isValidSubprotocol, + isValidStatusCode, + failWebsocketConnection, + websocketMessageReceived, + utf8Decode, + isControlFrame, + isContinuationFrame, + isTextBinaryFrame, + isValidOpcode, + parseExtensions, + isValidClientWindowBits + }; +})); +//#endregion +//#region node_modules/undici/lib/web/websocket/frame.js +var require_frame = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { maxUnsigned16Bit } = require_constants(); + var BUFFER_SIZE = 16386; + /** @type {import('crypto')} */ + var crypto; + var buffer = null; + var bufIdx = BUFFER_SIZE; + try { + crypto = __require("node:crypto"); + } catch { + crypto = { randomFillSync: function randomFillSync(buffer, _offset, _size) { + for (let i = 0; i < buffer.length; ++i) buffer[i] = Math.random() * 255 | 0; + return buffer; + } }; + } + function generateMask() { + if (bufIdx === BUFFER_SIZE) { + bufIdx = 0; + crypto.randomFillSync(buffer ??= Buffer.allocUnsafe(BUFFER_SIZE), 0, BUFFER_SIZE); + } + return [ + buffer[bufIdx++], + buffer[bufIdx++], + buffer[bufIdx++], + buffer[bufIdx++] + ]; + } + var WebsocketFrameSend = class { + /** + * @param {Buffer|undefined} data + */ + constructor(data) { + this.frameData = data; + } + createFrame(opcode) { + const frameData = this.frameData; + const maskKey = generateMask(); + const bodyLength = frameData?.byteLength ?? 0; + /** @type {number} */ + let payloadLength = bodyLength; + let offset = 6; + if (bodyLength > maxUnsigned16Bit) { + offset += 8; + payloadLength = 127; + } else if (bodyLength > 125) { + offset += 2; + payloadLength = 126; + } + const buffer = Buffer.allocUnsafe(bodyLength + offset); + buffer[0] = buffer[1] = 0; + buffer[0] |= 128; + buffer[0] = (buffer[0] & 240) + opcode; + /*! ws. MIT License. Einar Otto Stangvik */ + buffer[offset - 4] = maskKey[0]; + buffer[offset - 3] = maskKey[1]; + buffer[offset - 2] = maskKey[2]; + buffer[offset - 1] = maskKey[3]; + buffer[1] = payloadLength; + if (payloadLength === 126) buffer.writeUInt16BE(bodyLength, 2); + else if (payloadLength === 127) { + buffer[2] = buffer[3] = 0; + buffer.writeUIntBE(bodyLength, 4, 6); + } + buffer[1] |= 128; + for (let i = 0; i < bodyLength; ++i) buffer[offset + i] = frameData[i] ^ maskKey[i & 3]; + return buffer; + } + }; + module.exports = { WebsocketFrameSend }; +})); +//#endregion +//#region node_modules/undici/lib/web/websocket/connection.js +var require_connection = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { uid, states, sentCloseFrameState, emptyBuffer, opcodes } = require_constants(); + var { kReadyState, kSentClose, kByteParser, kReceivedClose, kResponse } = require_symbols(); + var { fireEvent, failWebsocketConnection, isClosing, isClosed, isEstablished, parseExtensions } = require_util$1(); + var { channels } = require_diagnostics(); + var { CloseEvent } = require_events(); + var { makeRequest } = require_request(); + var { fetching } = require_fetch(); + var { Headers, getHeadersList } = require_headers(); + var { getDecodeSplit } = require_util$6(); + var { WebsocketFrameSend } = require_frame(); + /** @type {import('crypto')} */ + var crypto; + try { + crypto = __require("node:crypto"); + } catch {} + /** + * @see https://websockets.spec.whatwg.org/#concept-websocket-establish + * @param {URL} url + * @param {string|string[]} protocols + * @param {import('./websocket').WebSocket} ws + * @param {(response: any, extensions: string[] | undefined) => void} onEstablish + * @param {Partial} options + */ + function establishWebSocketConnection(url, protocols, client, ws, onEstablish, options) { + const requestURL = url; + requestURL.protocol = url.protocol === "ws:" ? "http:" : "https:"; + const request = makeRequest({ + urlList: [requestURL], + client, + serviceWorkers: "none", + referrer: "no-referrer", + mode: "websocket", + credentials: "include", + cache: "no-store", + redirect: "error" + }); + if (options.headers) request.headersList = getHeadersList(new Headers(options.headers)); + const keyValue = crypto.randomBytes(16).toString("base64"); + request.headersList.append("sec-websocket-key", keyValue); + request.headersList.append("sec-websocket-version", "13"); + for (const protocol of protocols) request.headersList.append("sec-websocket-protocol", protocol); + request.headersList.append("sec-websocket-extensions", "permessage-deflate; client_max_window_bits"); + return fetching({ + request, + useParallelQueue: true, + dispatcher: options.dispatcher, + processResponse(response) { + if (response.type === "error" || response.status !== 101) { + failWebsocketConnection(ws, "Received network error or non-101 status code."); + return; + } + if (protocols.length !== 0 && !response.headersList.get("Sec-WebSocket-Protocol")) { + failWebsocketConnection(ws, "Server did not respond with sent protocols."); + return; + } + if (response.headersList.get("Upgrade")?.toLowerCase() !== "websocket") { + failWebsocketConnection(ws, "Server did not set Upgrade header to \"websocket\"."); + return; + } + if (response.headersList.get("Connection")?.toLowerCase() !== "upgrade") { + failWebsocketConnection(ws, "Server did not set Connection header to \"upgrade\"."); + return; + } + if (response.headersList.get("Sec-WebSocket-Accept") !== crypto.createHash("sha1").update(keyValue + uid).digest("base64")) { + failWebsocketConnection(ws, "Incorrect hash received in Sec-WebSocket-Accept header."); + return; + } + const secExtension = response.headersList.get("Sec-WebSocket-Extensions"); + let extensions; + if (secExtension !== null) { + extensions = parseExtensions(secExtension); + if (!extensions.has("permessage-deflate")) { + failWebsocketConnection(ws, "Sec-WebSocket-Extensions header does not match."); + return; + } + } + const secProtocol = response.headersList.get("Sec-WebSocket-Protocol"); + if (secProtocol !== null) { + if (!getDecodeSplit("sec-websocket-protocol", request.headersList).includes(secProtocol)) { + failWebsocketConnection(ws, "Protocol was not set in the opening handshake."); + return; + } + } + response.socket.on("data", onSocketData); + response.socket.on("close", onSocketClose); + response.socket.on("error", onSocketError); + if (channels.open.hasSubscribers) channels.open.publish({ + address: response.socket.address(), + protocol: secProtocol, + extensions: secExtension + }); + onEstablish(response, extensions); + } + }); + } + function closeWebSocketConnection(ws, code, reason, reasonByteLength) { + if (isClosing(ws) || isClosed(ws)) {} else if (!isEstablished(ws)) { + failWebsocketConnection(ws, "Connection was closed before it was established."); + ws[kReadyState] = states.CLOSING; + } else if (ws[kSentClose] === sentCloseFrameState.NOT_SENT) { + ws[kSentClose] = sentCloseFrameState.PROCESSING; + const frame = new WebsocketFrameSend(); + if (code !== void 0 && reason === void 0) { + frame.frameData = Buffer.allocUnsafe(2); + frame.frameData.writeUInt16BE(code, 0); + } else if (code !== void 0 && reason !== void 0) { + frame.frameData = Buffer.allocUnsafe(2 + reasonByteLength); + frame.frameData.writeUInt16BE(code, 0); + frame.frameData.write(reason, 2, "utf-8"); + } else frame.frameData = emptyBuffer; + ws[kResponse].socket.write(frame.createFrame(opcodes.CLOSE)); + ws[kSentClose] = sentCloseFrameState.SENT; + ws[kReadyState] = states.CLOSING; + } else ws[kReadyState] = states.CLOSING; + } + /** + * @param {Buffer} chunk + */ + function onSocketData(chunk) { + if (!this.ws[kByteParser].write(chunk)) this.pause(); + } + /** + * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol + * @see https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.4 + */ + function onSocketClose() { + const { ws } = this; + const { [kResponse]: response } = ws; + response.socket.off("data", onSocketData); + response.socket.off("close", onSocketClose); + response.socket.off("error", onSocketError); + const wasClean = ws[kSentClose] === sentCloseFrameState.SENT && ws[kReceivedClose]; + let code = 1005; + let reason = ""; + const result = ws[kByteParser].closingInfo; + if (result && !result.error) { + code = result.code ?? 1005; + reason = result.reason; + } else if (!ws[kReceivedClose]) code = 1006; + ws[kReadyState] = states.CLOSED; + fireEvent("close", ws, (type, init) => new CloseEvent(type, init), { + wasClean, + code, + reason + }); + if (channels.close.hasSubscribers) channels.close.publish({ + websocket: ws, + code, + reason + }); + } + function onSocketError(error) { + const { ws } = this; + ws[kReadyState] = states.CLOSING; + if (channels.socketError.hasSubscribers) channels.socketError.publish(error); + this.destroy(); + } + module.exports = { + establishWebSocketConnection, + closeWebSocketConnection + }; +})); +//#endregion +//#region node_modules/undici/lib/web/websocket/permessage-deflate.js +var require_permessage_deflate = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { createInflateRaw, Z_DEFAULT_WINDOWBITS } = __require("node:zlib"); + var { isValidClientWindowBits } = require_util$1(); + var { MessageSizeExceededError } = require_errors(); + var tail = Buffer.from([ + 0, + 0, + 255, + 255 + ]); + var kBuffer = Symbol("kBuffer"); + var kLength = Symbol("kLength"); + var PerMessageDeflate = class { + /** @type {import('node:zlib').InflateRaw} */ + #inflate; + #options = {}; + #maxPayloadSize = 0; + /** + * @param {Map} extensions + */ + constructor(extensions, options) { + this.#options.serverNoContextTakeover = extensions.has("server_no_context_takeover"); + this.#options.serverMaxWindowBits = extensions.get("server_max_window_bits"); + this.#maxPayloadSize = options.maxPayloadSize; + } + /** + * Decompress a compressed payload. + * @param {Buffer} chunk Compressed data + * @param {boolean} fin Final fragment flag + * @param {Function} callback Callback function + */ + decompress(chunk, fin, callback) { + if (!this.#inflate) { + let windowBits = Z_DEFAULT_WINDOWBITS; + if (this.#options.serverMaxWindowBits) { + if (!isValidClientWindowBits(this.#options.serverMaxWindowBits)) { + callback(/* @__PURE__ */ new Error("Invalid server_max_window_bits")); + return; + } + windowBits = Number.parseInt(this.#options.serverMaxWindowBits); + } + try { + this.#inflate = createInflateRaw({ windowBits }); + } catch (err) { + callback(err); + return; + } + this.#inflate[kBuffer] = []; + this.#inflate[kLength] = 0; + this.#inflate.on("data", (data) => { + this.#inflate[kLength] += data.length; + if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) { + callback(new MessageSizeExceededError()); + this.#inflate.removeAllListeners(); + this.#inflate = null; + return; + } + this.#inflate[kBuffer].push(data); + }); + this.#inflate.on("error", (err) => { + this.#inflate = null; + callback(err); + }); + } + this.#inflate.write(chunk); + if (fin) this.#inflate.write(tail); + this.#inflate.flush(() => { + if (!this.#inflate) return; + const full = Buffer.concat(this.#inflate[kBuffer], this.#inflate[kLength]); + this.#inflate[kBuffer].length = 0; + this.#inflate[kLength] = 0; + callback(null, full); + }); + } + }; + module.exports = { PerMessageDeflate }; +})); +//#endregion +//#region node_modules/undici/lib/web/websocket/receiver.js +var require_receiver = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { Writable } = __require("node:stream"); + var assert = __require("node:assert"); + var { parserStates, opcodes, states, emptyBuffer, sentCloseFrameState } = require_constants(); + var { kReadyState, kSentClose, kResponse, kReceivedClose } = require_symbols(); + var { channels } = require_diagnostics(); + var { isValidStatusCode, isValidOpcode, failWebsocketConnection, websocketMessageReceived, utf8Decode, isControlFrame, isTextBinaryFrame, isContinuationFrame } = require_util$1(); + var { WebsocketFrameSend } = require_frame(); + var { closeWebSocketConnection } = require_connection(); + var { PerMessageDeflate } = require_permessage_deflate(); + var { MessageSizeExceededError } = require_errors(); + function failWebsocketConnectionWithCode(ws, code, reason) { + closeWebSocketConnection(ws, code, reason, Buffer.byteLength(reason)); + failWebsocketConnection(ws, reason); + } + var ByteParser = class extends Writable { + #buffers = []; + #fragmentsBytes = 0; + #byteOffset = 0; + #loop = false; + #state = parserStates.INFO; + #info = {}; + #fragments = []; + /** @type {Map} */ + #extensions; + /** @type {number} */ + #maxFragments; + /** @type {number} */ + #maxPayloadSize; + /** + * @param {import('./websocket').WebSocket} ws + * @param {Map|null} extensions + * @param {{ maxFragments?: number, maxPayloadSize?: number }} [options] + */ + constructor(ws, extensions, options = {}) { + super(); + this.ws = ws; + this.#extensions = extensions == null ? /* @__PURE__ */ new Map() : extensions; + this.#maxFragments = options.maxFragments ?? 0; + this.#maxPayloadSize = options.maxPayloadSize ?? 0; + if (this.#extensions.has("permessage-deflate")) this.#extensions.set("permessage-deflate", new PerMessageDeflate(extensions, options)); + } + /** + * @param {Buffer} chunk + * @param {() => void} callback + */ + _write(chunk, _, callback) { + this.#buffers.push(chunk); + this.#byteOffset += chunk.length; + this.#loop = true; + this.run(callback); + } + #validatePayloadLength() { + if (this.#maxPayloadSize > 0 && !isControlFrame(this.#info.opcode) && this.#info.payloadLength + this.#fragmentsBytes > this.#maxPayloadSize) { + failWebsocketConnectionWithCode(this.ws, 1009, "Payload size exceeds maximum allowed size"); + return false; + } + return true; + } + /** + * Runs whenever a new chunk is received. + * Callback is called whenever there are no more chunks buffering, + * or not enough bytes are buffered to parse. + */ + run(callback) { + while (this.#loop) if (this.#state === parserStates.INFO) { + if (this.#byteOffset < 2) return callback(); + const buffer = this.consume(2); + const fin = (buffer[0] & 128) !== 0; + const opcode = buffer[0] & 15; + const masked = (buffer[1] & 128) === 128; + const fragmented = !fin && opcode !== opcodes.CONTINUATION; + const payloadLength = buffer[1] & 127; + const rsv1 = buffer[0] & 64; + const rsv2 = buffer[0] & 32; + const rsv3 = buffer[0] & 16; + if (!isValidOpcode(opcode)) { + failWebsocketConnection(this.ws, "Invalid opcode received"); + return callback(); + } + if (masked) { + failWebsocketConnection(this.ws, "Frame cannot be masked"); + return callback(); + } + if (rsv1 !== 0 && !this.#extensions.has("permessage-deflate")) { + failWebsocketConnection(this.ws, "Expected RSV1 to be clear."); + return; + } + if (rsv2 !== 0 || rsv3 !== 0) { + failWebsocketConnection(this.ws, "RSV1, RSV2, RSV3 must be clear"); + return; + } + if (fragmented && !isTextBinaryFrame(opcode)) { + failWebsocketConnection(this.ws, "Invalid frame type was fragmented."); + return; + } + if (isTextBinaryFrame(opcode) && this.#fragments.length > 0) { + failWebsocketConnection(this.ws, "Expected continuation frame"); + return; + } + if (this.#info.fragmented && fragmented) { + failWebsocketConnection(this.ws, "Fragmented frame exceeded 125 bytes."); + return; + } + if ((payloadLength > 125 || fragmented) && isControlFrame(opcode)) { + failWebsocketConnection(this.ws, "Control frame either too large or fragmented"); + return; + } + if (isContinuationFrame(opcode) && this.#fragments.length === 0 && !this.#info.compressed) { + failWebsocketConnection(this.ws, "Unexpected continuation frame"); + return; + } + if (payloadLength <= 125) { + this.#info.payloadLength = payloadLength; + this.#state = parserStates.READ_DATA; + if (!this.#validatePayloadLength()) return; + } else if (payloadLength === 126) this.#state = parserStates.PAYLOADLENGTH_16; + else if (payloadLength === 127) this.#state = parserStates.PAYLOADLENGTH_64; + if (isTextBinaryFrame(opcode)) { + this.#info.binaryType = opcode; + this.#info.compressed = rsv1 !== 0; + } + this.#info.opcode = opcode; + this.#info.masked = masked; + this.#info.fin = fin; + this.#info.fragmented = fragmented; + } else if (this.#state === parserStates.PAYLOADLENGTH_16) { + if (this.#byteOffset < 2) return callback(); + const buffer = this.consume(2); + this.#info.payloadLength = buffer.readUInt16BE(0); + this.#state = parserStates.READ_DATA; + if (!this.#validatePayloadLength()) return; + } else if (this.#state === parserStates.PAYLOADLENGTH_64) { + if (this.#byteOffset < 8) return callback(); + const buffer = this.consume(8); + const upper = buffer.readUInt32BE(0); + const lower = buffer.readUInt32BE(4); + if (upper !== 0 || lower > 2 ** 31 - 1) { + failWebsocketConnection(this.ws, "Received payload length > 2^31 bytes."); + return; + } + this.#info.payloadLength = lower; + this.#state = parserStates.READ_DATA; + if (!this.#validatePayloadLength()) return; + } else if (this.#state === parserStates.READ_DATA) { + if (this.#byteOffset < this.#info.payloadLength) return callback(); + const body = this.consume(this.#info.payloadLength); + if (isControlFrame(this.#info.opcode)) { + this.#loop = this.parseControlFrame(body); + this.#state = parserStates.INFO; + } else if (!this.#info.compressed) { + if (!this.writeFragments(body)) return; + if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) { + failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message); + return; + } + if (!this.#info.fragmented && this.#info.fin) websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments()); + this.#state = parserStates.INFO; + } else { + this.#extensions.get("permessage-deflate").decompress(body, this.#info.fin, (error, data) => { + if (error) { + const code = error instanceof MessageSizeExceededError ? 1009 : 1007; + failWebsocketConnectionWithCode(this.ws, code, error.message); + return; + } + if (!this.writeFragments(data)) return; + if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) { + failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message); + return; + } + if (!this.#info.fin) { + this.#state = parserStates.INFO; + this.#loop = true; + this.run(callback); + return; + } + websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments()); + this.#loop = true; + this.#state = parserStates.INFO; + this.run(callback); + }); + this.#loop = false; + break; + } + } + } + /** + * Take n bytes from the buffered Buffers + * @param {number} n + * @returns {Buffer} + */ + consume(n) { + if (n > this.#byteOffset) throw new Error("Called consume() before buffers satiated."); + else if (n === 0) return emptyBuffer; + if (this.#buffers[0].length === n) { + this.#byteOffset -= this.#buffers[0].length; + return this.#buffers.shift(); + } + const buffer = Buffer.allocUnsafe(n); + let offset = 0; + while (offset !== n) { + const next = this.#buffers[0]; + const { length } = next; + if (length + offset === n) { + buffer.set(this.#buffers.shift(), offset); + break; + } else if (length + offset > n) { + buffer.set(next.subarray(0, n - offset), offset); + this.#buffers[0] = next.subarray(n - offset); + break; + } else { + buffer.set(this.#buffers.shift(), offset); + offset += next.length; + } + } + this.#byteOffset -= n; + return buffer; + } + writeFragments(fragment) { + if (this.#maxFragments > 0 && this.#fragments.length === this.#maxFragments) { + failWebsocketConnectionWithCode(this.ws, 1008, "Too many message fragments"); + return false; + } + this.#fragmentsBytes += fragment.length; + this.#fragments.push(fragment); + return true; + } + consumeFragments() { + const fragments = this.#fragments; + if (fragments.length === 1) { + this.#fragmentsBytes = 0; + return fragments.shift(); + } + const output = Buffer.concat(fragments, this.#fragmentsBytes); + this.#fragments = []; + this.#fragmentsBytes = 0; + return output; + } + parseCloseBody(data) { + assert(data.length !== 1); + /** @type {number|undefined} */ + let code; + if (data.length >= 2) code = data.readUInt16BE(0); + if (code !== void 0 && !isValidStatusCode(code)) return { + code: 1002, + reason: "Invalid status code", + error: true + }; + /** @type {Buffer} */ + let reason = data.subarray(2); + if (reason[0] === 239 && reason[1] === 187 && reason[2] === 191) reason = reason.subarray(3); + try { + reason = utf8Decode(reason); + } catch { + return { + code: 1007, + reason: "Invalid UTF-8", + error: true + }; + } + return { + code, + reason, + error: false + }; + } + /** + * Parses control frames. + * @param {Buffer} body + */ + parseControlFrame(body) { + const { opcode, payloadLength } = this.#info; + if (opcode === opcodes.CLOSE) { + if (payloadLength === 1) { + failWebsocketConnection(this.ws, "Received close frame with a 1-byte body."); + return false; + } + this.#info.closeInfo = this.parseCloseBody(body); + if (this.#info.closeInfo.error) { + const { code, reason } = this.#info.closeInfo; + closeWebSocketConnection(this.ws, code, reason, reason.length); + failWebsocketConnection(this.ws, reason); + return false; + } + if (this.ws[kSentClose] !== sentCloseFrameState.SENT) { + let body = emptyBuffer; + if (this.#info.closeInfo.code) { + body = Buffer.allocUnsafe(2); + body.writeUInt16BE(this.#info.closeInfo.code, 0); + } + const closeFrame = new WebsocketFrameSend(body); + this.ws[kResponse].socket.write(closeFrame.createFrame(opcodes.CLOSE), (err) => { + if (!err) this.ws[kSentClose] = sentCloseFrameState.SENT; + }); + } + this.ws[kReadyState] = states.CLOSING; + this.ws[kReceivedClose] = true; + return false; + } else if (opcode === opcodes.PING) { + if (!this.ws[kReceivedClose]) { + const frame = new WebsocketFrameSend(body); + this.ws[kResponse].socket.write(frame.createFrame(opcodes.PONG)); + if (channels.ping.hasSubscribers) channels.ping.publish({ payload: body }); + } + } else if (opcode === opcodes.PONG) { + if (channels.pong.hasSubscribers) channels.pong.publish({ payload: body }); + } + return true; + } + get closingInfo() { + return this.#info.closeInfo; + } + }; + module.exports = { ByteParser }; +})); +//#endregion +//#region node_modules/undici/lib/web/websocket/sender.js +var require_sender = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { WebsocketFrameSend } = require_frame(); + var { opcodes, sendHints } = require_constants(); + var FixedQueue = require_fixed_queue(); + /** @type {typeof Uint8Array} */ + var FastBuffer = Buffer[Symbol.species]; + /** + * @typedef {object} SendQueueNode + * @property {Promise | null} promise + * @property {((...args: any[]) => any)} callback + * @property {Buffer | null} frame + */ + var SendQueue = class { + /** + * @type {FixedQueue} + */ + #queue = new FixedQueue(); + /** + * @type {boolean} + */ + #running = false; + /** @type {import('node:net').Socket} */ + #socket; + constructor(socket) { + this.#socket = socket; + } + add(item, cb, hint) { + if (hint !== sendHints.blob) { + const frame = createFrame(item, hint); + if (!this.#running) this.#socket.write(frame, cb); + else { + /** @type {SendQueueNode} */ + const node = { + promise: null, + callback: cb, + frame + }; + this.#queue.push(node); + } + return; + } + /** @type {SendQueueNode} */ + const node = { + promise: item.arrayBuffer().then((ab) => { + node.promise = null; + node.frame = createFrame(ab, hint); + }), + callback: cb, + frame: null + }; + this.#queue.push(node); + if (!this.#running) this.#run(); + } + async #run() { + this.#running = true; + const queue = this.#queue; + while (!queue.isEmpty()) { + const node = queue.shift(); + if (node.promise !== null) await node.promise; + this.#socket.write(node.frame, node.callback); + node.callback = node.frame = null; + } + this.#running = false; + } + }; + function createFrame(data, hint) { + return new WebsocketFrameSend(toBuffer(data, hint)).createFrame(hint === sendHints.string ? opcodes.TEXT : opcodes.BINARY); + } + function toBuffer(data, hint) { + switch (hint) { + case sendHints.string: return Buffer.from(data); + case sendHints.arrayBuffer: + case sendHints.blob: return new FastBuffer(data); + case sendHints.typedArray: return new FastBuffer(data.buffer, data.byteOffset, data.byteLength); + } + } + module.exports = { SendQueue }; +})); +//#endregion +//#region node_modules/undici/lib/web/websocket/websocket.js +var require_websocket = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { webidl } = require_webidl(); + var { URLSerializer } = require_data_url(); + var { environmentSettingsObject } = require_util$6(); + var { staticPropertyDescriptors, states, sentCloseFrameState, sendHints } = require_constants(); + var { kWebSocketURL, kReadyState, kController, kBinaryType, kResponse, kSentClose, kByteParser } = require_symbols(); + var { isConnecting, isEstablished, isClosing, isValidSubprotocol, fireEvent } = require_util$1(); + var { establishWebSocketConnection, closeWebSocketConnection } = require_connection(); + var { ByteParser } = require_receiver(); + var { kEnumerableProperty, isBlobLike } = require_util$7(); + var { getGlobalDispatcher } = require_global(); + var { types } = __require("node:util"); + var { ErrorEvent, CloseEvent } = require_events(); + var { SendQueue } = require_sender(); + var WebSocket = class WebSocket extends EventTarget { + #events = { + open: null, + error: null, + close: null, + message: null + }; + #bufferedAmount = 0; + #protocol = ""; + #extensions = ""; + /** @type {SendQueue} */ + #sendQueue; + /** + * @param {string} url + * @param {string|string[]} protocols + */ + constructor(url, protocols = []) { + super(); + webidl.util.markAsUncloneable(this); + const prefix = "WebSocket constructor"; + webidl.argumentLengthCheck(arguments, 1, prefix); + const options = webidl.converters["DOMString or sequence or WebSocketInit"](protocols, prefix, "options"); + url = webidl.converters.USVString(url, prefix, "url"); + protocols = options.protocols; + const baseURL = environmentSettingsObject.settingsObject.baseUrl; + let urlRecord; + try { + urlRecord = new URL(url, baseURL); + } catch (e) { + throw new DOMException(e, "SyntaxError"); + } + if (urlRecord.protocol === "http:") urlRecord.protocol = "ws:"; + else if (urlRecord.protocol === "https:") urlRecord.protocol = "wss:"; + if (urlRecord.protocol !== "ws:" && urlRecord.protocol !== "wss:") throw new DOMException(`Expected a ws: or wss: protocol, got ${urlRecord.protocol}`, "SyntaxError"); + if (urlRecord.hash || urlRecord.href.endsWith("#")) throw new DOMException("Got fragment", "SyntaxError"); + if (typeof protocols === "string") protocols = [protocols]; + if (protocols.length !== new Set(protocols.map((p) => p.toLowerCase())).size) throw new DOMException("Invalid Sec-WebSocket-Protocol value", "SyntaxError"); + if (protocols.length > 0 && !protocols.every((p) => isValidSubprotocol(p))) throw new DOMException("Invalid Sec-WebSocket-Protocol value", "SyntaxError"); + this[kWebSocketURL] = new URL(urlRecord.href); + const client = environmentSettingsObject.settingsObject; + this[kController] = establishWebSocketConnection(urlRecord, protocols, client, this, (response, extensions) => this.#onConnectionEstablished(response, extensions), options); + this[kReadyState] = WebSocket.CONNECTING; + this[kSentClose] = sentCloseFrameState.NOT_SENT; + this[kBinaryType] = "blob"; + } + /** + * @see https://websockets.spec.whatwg.org/#dom-websocket-close + * @param {number|undefined} code + * @param {string|undefined} reason + */ + close(code = void 0, reason = void 0) { + webidl.brandCheck(this, WebSocket); + const prefix = "WebSocket.close"; + if (code !== void 0) code = webidl.converters["unsigned short"](code, prefix, "code", { clamp: true }); + if (reason !== void 0) reason = webidl.converters.USVString(reason, prefix, "reason"); + if (code !== void 0) { + if (code !== 1e3 && (code < 3e3 || code > 4999)) throw new DOMException("invalid code", "InvalidAccessError"); + } + let reasonByteLength = 0; + if (reason !== void 0) { + reasonByteLength = Buffer.byteLength(reason); + if (reasonByteLength > 123) throw new DOMException(`Reason must be less than 123 bytes; received ${reasonByteLength}`, "SyntaxError"); + } + closeWebSocketConnection(this, code, reason, reasonByteLength); + } + /** + * @see https://websockets.spec.whatwg.org/#dom-websocket-send + * @param {NodeJS.TypedArray|ArrayBuffer|Blob|string} data + */ + send(data) { + webidl.brandCheck(this, WebSocket); + const prefix = "WebSocket.send"; + webidl.argumentLengthCheck(arguments, 1, prefix); + data = webidl.converters.WebSocketSendData(data, prefix, "data"); + if (isConnecting(this)) throw new DOMException("Sent before connected.", "InvalidStateError"); + if (!isEstablished(this) || isClosing(this)) return; + if (typeof data === "string") { + const length = Buffer.byteLength(data); + this.#bufferedAmount += length; + this.#sendQueue.add(data, () => { + this.#bufferedAmount -= length; + }, sendHints.string); + } else if (types.isArrayBuffer(data)) { + this.#bufferedAmount += data.byteLength; + this.#sendQueue.add(data, () => { + this.#bufferedAmount -= data.byteLength; + }, sendHints.arrayBuffer); + } else if (ArrayBuffer.isView(data)) { + this.#bufferedAmount += data.byteLength; + this.#sendQueue.add(data, () => { + this.#bufferedAmount -= data.byteLength; + }, sendHints.typedArray); + } else if (isBlobLike(data)) { + this.#bufferedAmount += data.size; + this.#sendQueue.add(data, () => { + this.#bufferedAmount -= data.size; + }, sendHints.blob); + } + } + get readyState() { + webidl.brandCheck(this, WebSocket); + return this[kReadyState]; + } + get bufferedAmount() { + webidl.brandCheck(this, WebSocket); + return this.#bufferedAmount; + } + get url() { + webidl.brandCheck(this, WebSocket); + return URLSerializer(this[kWebSocketURL]); + } + get extensions() { + webidl.brandCheck(this, WebSocket); + return this.#extensions; + } + get protocol() { + webidl.brandCheck(this, WebSocket); + return this.#protocol; + } + get onopen() { + webidl.brandCheck(this, WebSocket); + return this.#events.open; + } + set onopen(fn) { + webidl.brandCheck(this, WebSocket); + if (this.#events.open) this.removeEventListener("open", this.#events.open); + if (typeof fn === "function") { + this.#events.open = fn; + this.addEventListener("open", fn); + } else this.#events.open = null; + } + get onerror() { + webidl.brandCheck(this, WebSocket); + return this.#events.error; + } + set onerror(fn) { + webidl.brandCheck(this, WebSocket); + if (this.#events.error) this.removeEventListener("error", this.#events.error); + if (typeof fn === "function") { + this.#events.error = fn; + this.addEventListener("error", fn); + } else this.#events.error = null; + } + get onclose() { + webidl.brandCheck(this, WebSocket); + return this.#events.close; + } + set onclose(fn) { + webidl.brandCheck(this, WebSocket); + if (this.#events.close) this.removeEventListener("close", this.#events.close); + if (typeof fn === "function") { + this.#events.close = fn; + this.addEventListener("close", fn); + } else this.#events.close = null; + } + get onmessage() { + webidl.brandCheck(this, WebSocket); + return this.#events.message; + } + set onmessage(fn) { + webidl.brandCheck(this, WebSocket); + if (this.#events.message) this.removeEventListener("message", this.#events.message); + if (typeof fn === "function") { + this.#events.message = fn; + this.addEventListener("message", fn); + } else this.#events.message = null; + } + get binaryType() { + webidl.brandCheck(this, WebSocket); + return this[kBinaryType]; + } + set binaryType(type) { + webidl.brandCheck(this, WebSocket); + if (type !== "blob" && type !== "arraybuffer") this[kBinaryType] = "blob"; + else this[kBinaryType] = type; + } + /** + * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol + */ + #onConnectionEstablished(response, parsedExtensions) { + this[kResponse] = response; + const webSocketOptions = this[kController]?.dispatcher?.webSocketOptions; + const maxFragments = webSocketOptions?.maxFragments; + const maxPayloadSize = webSocketOptions?.maxPayloadSize; + const parser = new ByteParser(this, parsedExtensions, { + maxFragments, + maxPayloadSize + }); + parser.on("drain", onParserDrain); + parser.on("error", onParserError.bind(this)); + response.socket.ws = this; + this[kByteParser] = parser; + this.#sendQueue = new SendQueue(response.socket); + this[kReadyState] = states.OPEN; + const extensions = response.headersList.get("sec-websocket-extensions"); + if (extensions !== null) this.#extensions = extensions; + const protocol = response.headersList.get("sec-websocket-protocol"); + if (protocol !== null) this.#protocol = protocol; + fireEvent("open", this); + } + }; + WebSocket.CONNECTING = WebSocket.prototype.CONNECTING = states.CONNECTING; + WebSocket.OPEN = WebSocket.prototype.OPEN = states.OPEN; + WebSocket.CLOSING = WebSocket.prototype.CLOSING = states.CLOSING; + WebSocket.CLOSED = WebSocket.prototype.CLOSED = states.CLOSED; + Object.defineProperties(WebSocket.prototype, { + CONNECTING: staticPropertyDescriptors, + OPEN: staticPropertyDescriptors, + CLOSING: staticPropertyDescriptors, + CLOSED: staticPropertyDescriptors, + url: kEnumerableProperty, + readyState: kEnumerableProperty, + bufferedAmount: kEnumerableProperty, + onopen: kEnumerableProperty, + onerror: kEnumerableProperty, + onclose: kEnumerableProperty, + close: kEnumerableProperty, + onmessage: kEnumerableProperty, + binaryType: kEnumerableProperty, + send: kEnumerableProperty, + extensions: kEnumerableProperty, + protocol: kEnumerableProperty, + [Symbol.toStringTag]: { + value: "WebSocket", + writable: false, + enumerable: false, + configurable: true + } + }); + Object.defineProperties(WebSocket, { + CONNECTING: staticPropertyDescriptors, + OPEN: staticPropertyDescriptors, + CLOSING: staticPropertyDescriptors, + CLOSED: staticPropertyDescriptors + }); + webidl.converters["sequence"] = webidl.sequenceConverter(webidl.converters.DOMString); + webidl.converters["DOMString or sequence"] = function(V, prefix, argument) { + if (webidl.util.Type(V) === "Object" && Symbol.iterator in V) return webidl.converters["sequence"](V); + return webidl.converters.DOMString(V, prefix, argument); + }; + webidl.converters.WebSocketInit = webidl.dictionaryConverter([ + { + key: "protocols", + converter: webidl.converters["DOMString or sequence"], + defaultValue: () => new Array(0) + }, + { + key: "dispatcher", + converter: webidl.converters.any, + defaultValue: () => getGlobalDispatcher() + }, + { + key: "headers", + converter: webidl.nullableConverter(webidl.converters.HeadersInit) + } + ]); + webidl.converters["DOMString or sequence or WebSocketInit"] = function(V) { + if (webidl.util.Type(V) === "Object" && !(Symbol.iterator in V)) return webidl.converters.WebSocketInit(V); + return { protocols: webidl.converters["DOMString or sequence"](V) }; + }; + webidl.converters.WebSocketSendData = function(V) { + if (webidl.util.Type(V) === "Object") { + if (isBlobLike(V)) return webidl.converters.Blob(V, { strict: false }); + if (ArrayBuffer.isView(V) || types.isArrayBuffer(V)) return webidl.converters.BufferSource(V); + } + return webidl.converters.USVString(V); + }; + function onParserDrain() { + this.ws[kResponse].socket.resume(); + } + function onParserError(err) { + let message; + let code; + if (err instanceof CloseEvent) { + message = err.reason; + code = err.code; + } else message = err.message; + fireEvent("error", this, () => new ErrorEvent("error", { + error: err, + message + })); + closeWebSocketConnection(this, code); + } + module.exports = { WebSocket }; +})); +//#endregion +//#region node_modules/undici/lib/web/eventsource/util.js +var require_util = /* @__PURE__ */ __commonJSMin(((exports, module) => { + /** + * Checks if the given value is a valid LastEventId. + * @param {string} value + * @returns {boolean} + */ + function isValidLastEventId(value) { + return value.indexOf("\0") === -1; + } + /** + * Checks if the given value is a base 10 digit. + * @param {string} value + * @returns {boolean} + */ + function isASCIINumber(value) { + if (value.length === 0) return false; + for (let i = 0; i < value.length; i++) if (value.charCodeAt(i) < 48 || value.charCodeAt(i) > 57) return false; + return true; + } + function delay(ms) { + return new Promise((resolve) => { + setTimeout(resolve, ms).unref(); + }); + } + module.exports = { + isValidLastEventId, + isASCIINumber, + delay + }; +})); +//#endregion +//#region node_modules/undici/lib/web/eventsource/eventsource-stream.js +var require_eventsource_stream = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { Transform } = __require("node:stream"); + var { isASCIINumber, isValidLastEventId } = require_util(); + /** + * @type {number[]} BOM + */ + var BOM = [ + 239, + 187, + 191 + ]; + /** + * @type {10} LF + */ + var LF = 10; + /** + * @type {13} CR + */ + var CR = 13; + /** + * @type {58} COLON + */ + var COLON = 58; + /** + * @type {32} SPACE + */ + var SPACE = 32; + /** + * @typedef {object} EventSourceStreamEvent + * @type {object} + * @property {string} [event] The event type. + * @property {string} [data] The data of the message. + * @property {string} [id] A unique ID for the event. + * @property {string} [retry] The reconnection time, in milliseconds. + */ + /** + * @typedef eventSourceSettings + * @type {object} + * @property {string} lastEventId The last event ID received from the server. + * @property {string} origin The origin of the event source. + * @property {number} reconnectionTime The reconnection time, in milliseconds. + */ + var EventSourceStream = class extends Transform { + /** + * @type {eventSourceSettings} + */ + state = null; + /** + * Leading byte-order-mark check. + * @type {boolean} + */ + checkBOM = true; + /** + * @type {boolean} + */ + crlfCheck = false; + /** + * @type {boolean} + */ + eventEndCheck = false; + /** + * @type {Buffer} + */ + buffer = null; + pos = 0; + event = { + data: void 0, + event: void 0, + id: void 0, + retry: void 0 + }; + /** + * @param {object} options + * @param {eventSourceSettings} options.eventSourceSettings + * @param {Function} [options.push] + */ + constructor(options = {}) { + options.readableObjectMode = true; + super(options); + this.state = options.eventSourceSettings || {}; + if (options.push) this.push = options.push; + } + /** + * @param {Buffer} chunk + * @param {string} _encoding + * @param {Function} callback + * @returns {void} + */ + _transform(chunk, _encoding, callback) { + if (chunk.length === 0) { + callback(); + return; + } + if (this.buffer) this.buffer = Buffer.concat([this.buffer, chunk]); + else this.buffer = chunk; + if (this.checkBOM) switch (this.buffer.length) { + case 1: + if (this.buffer[0] === BOM[0]) { + callback(); + return; + } + this.checkBOM = false; + callback(); + return; + case 2: + if (this.buffer[0] === BOM[0] && this.buffer[1] === BOM[1]) { + callback(); + return; + } + this.checkBOM = false; + break; + case 3: + if (this.buffer[0] === BOM[0] && this.buffer[1] === BOM[1] && this.buffer[2] === BOM[2]) { + this.buffer = Buffer.alloc(0); + this.checkBOM = false; + callback(); + return; + } + this.checkBOM = false; + break; + default: + if (this.buffer[0] === BOM[0] && this.buffer[1] === BOM[1] && this.buffer[2] === BOM[2]) this.buffer = this.buffer.subarray(3); + this.checkBOM = false; + break; + } + while (this.pos < this.buffer.length) { + if (this.eventEndCheck) { + if (this.crlfCheck) { + if (this.buffer[this.pos] === LF) { + this.buffer = this.buffer.subarray(this.pos + 1); + this.pos = 0; + this.crlfCheck = false; + continue; + } + this.crlfCheck = false; + } + if (this.buffer[this.pos] === LF || this.buffer[this.pos] === CR) { + if (this.buffer[this.pos] === CR) this.crlfCheck = true; + this.buffer = this.buffer.subarray(this.pos + 1); + this.pos = 0; + if (this.event.data !== void 0 || this.event.event || this.event.id || this.event.retry) this.processEvent(this.event); + this.clearEvent(); + continue; + } + this.eventEndCheck = false; + continue; + } + if (this.buffer[this.pos] === LF || this.buffer[this.pos] === CR) { + if (this.buffer[this.pos] === CR) this.crlfCheck = true; + this.parseLine(this.buffer.subarray(0, this.pos), this.event); + this.buffer = this.buffer.subarray(this.pos + 1); + this.pos = 0; + this.eventEndCheck = true; + continue; + } + this.pos++; + } + callback(); + } + /** + * @param {Buffer} line + * @param {EventStreamEvent} event + */ + parseLine(line, event) { + if (line.length === 0) return; + const colonPosition = line.indexOf(COLON); + if (colonPosition === 0) return; + let field = ""; + let value = ""; + if (colonPosition !== -1) { + field = line.subarray(0, colonPosition).toString("utf8"); + let valueStart = colonPosition + 1; + if (line[valueStart] === SPACE) ++valueStart; + value = line.subarray(valueStart).toString("utf8"); + } else { + field = line.toString("utf8"); + value = ""; + } + switch (field) { + case "data": + if (event[field] === void 0) event[field] = value; + else event[field] += `\n${value}`; + break; + case "retry": + if (isASCIINumber(value)) event[field] = value; + break; + case "id": + if (isValidLastEventId(value)) event[field] = value; + break; + case "event": + if (value.length > 0) event[field] = value; + break; + } + } + /** + * @param {EventSourceStreamEvent} event + */ + processEvent(event) { + if (event.retry && isASCIINumber(event.retry)) this.state.reconnectionTime = parseInt(event.retry, 10); + if (event.id && isValidLastEventId(event.id)) this.state.lastEventId = event.id; + if (event.data !== void 0) this.push({ + type: event.event || "message", + options: { + data: event.data, + lastEventId: this.state.lastEventId, + origin: this.state.origin + } + }); + } + clearEvent() { + this.event = { + data: void 0, + event: void 0, + id: void 0, + retry: void 0 + }; + } + }; + module.exports = { EventSourceStream }; +})); +//#endregion +//#region node_modules/undici/lib/web/eventsource/eventsource.js +var require_eventsource = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var { pipeline } = __require("node:stream"); + var { fetching } = require_fetch(); + var { makeRequest } = require_request(); + var { webidl } = require_webidl(); + var { EventSourceStream } = require_eventsource_stream(); + var { parseMIMEType } = require_data_url(); + var { createFastMessageEvent } = require_events(); + var { isNetworkError } = require_response(); + var { delay } = require_util(); + var { kEnumerableProperty } = require_util$7(); + var { environmentSettingsObject } = require_util$6(); + var experimentalWarned = false; + /** + * A reconnection time, in milliseconds. This must initially be an implementation-defined value, + * probably in the region of a few seconds. + * + * In Comparison: + * - Chrome uses 3000ms. + * - Deno uses 5000ms. + * + * @type {3000} + */ + var defaultReconnectionTime = 3e3; + /** + * The readyState attribute represents the state of the connection. + * @enum + * @readonly + * @see https://html.spec.whatwg.org/multipage/server-sent-events.html#dom-eventsource-readystate-dev + */ + /** + * The connection has not yet been established, or it was closed and the user + * agent is reconnecting. + * @type {0} + */ + var CONNECTING = 0; + /** + * The user agent has an open connection and is dispatching events as it + * receives them. + * @type {1} + */ + var OPEN = 1; + /** + * The connection is not open, and the user agent is not trying to reconnect. + * @type {2} + */ + var CLOSED = 2; + /** + * Requests for the element will have their mode set to "cors" and their credentials mode set to "same-origin". + * @type {'anonymous'} + */ + var ANONYMOUS = "anonymous"; + /** + * Requests for the element will have their mode set to "cors" and their credentials mode set to "include". + * @type {'use-credentials'} + */ + var USE_CREDENTIALS = "use-credentials"; + /** + * The EventSource interface is used to receive server-sent events. It + * connects to a server over HTTP and receives events in text/event-stream + * format without closing the connection. + * @extends {EventTarget} + * @see https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events + * @api public + */ + var EventSource = class EventSource extends EventTarget { + #events = { + open: null, + error: null, + message: null + }; + #url = null; + #withCredentials = false; + #readyState = CONNECTING; + #request = null; + #controller = null; + #dispatcher; + /** + * @type {import('./eventsource-stream').eventSourceSettings} + */ + #state; + /** + * Creates a new EventSource object. + * @param {string} url + * @param {EventSourceInit} [eventSourceInitDict] + * @see https://html.spec.whatwg.org/multipage/server-sent-events.html#the-eventsource-interface + */ + constructor(url, eventSourceInitDict = {}) { + super(); + webidl.util.markAsUncloneable(this); + const prefix = "EventSource constructor"; + webidl.argumentLengthCheck(arguments, 1, prefix); + if (!experimentalWarned) { + experimentalWarned = true; + process.emitWarning("EventSource is experimental, expect them to change at any time.", { code: "UNDICI-ES" }); + } + url = webidl.converters.USVString(url, prefix, "url"); + eventSourceInitDict = webidl.converters.EventSourceInitDict(eventSourceInitDict, prefix, "eventSourceInitDict"); + this.#dispatcher = eventSourceInitDict.dispatcher; + this.#state = { + lastEventId: "", + reconnectionTime: defaultReconnectionTime + }; + const settings = environmentSettingsObject; + let urlRecord; + try { + urlRecord = new URL(url, settings.settingsObject.baseUrl); + this.#state.origin = urlRecord.origin; + } catch (e) { + throw new DOMException(e, "SyntaxError"); + } + this.#url = urlRecord.href; + let corsAttributeState = ANONYMOUS; + if (eventSourceInitDict.withCredentials) { + corsAttributeState = USE_CREDENTIALS; + this.#withCredentials = true; + } + const initRequest = { + redirect: "follow", + keepalive: true, + mode: "cors", + credentials: corsAttributeState === "anonymous" ? "same-origin" : "omit", + referrer: "no-referrer" + }; + initRequest.client = environmentSettingsObject.settingsObject; + initRequest.headersList = [["accept", { + name: "accept", + value: "text/event-stream" + }]]; + initRequest.cache = "no-store"; + initRequest.initiator = "other"; + initRequest.urlList = [new URL(this.#url)]; + this.#request = makeRequest(initRequest); + this.#connect(); + } + /** + * Returns the state of this EventSource object's connection. It can have the + * values described below. + * @returns {0|1|2} + * @readonly + */ + get readyState() { + return this.#readyState; + } + /** + * Returns the URL providing the event stream. + * @readonly + * @returns {string} + */ + get url() { + return this.#url; + } + /** + * Returns a boolean indicating whether the EventSource object was + * instantiated with CORS credentials set (true), or not (false, the default). + */ + get withCredentials() { + return this.#withCredentials; + } + #connect() { + if (this.#readyState === CLOSED) return; + this.#readyState = CONNECTING; + const fetchParams = { + request: this.#request, + dispatcher: this.#dispatcher + }; + const processEventSourceEndOfBody = (response) => { + if (isNetworkError(response)) { + this.dispatchEvent(new Event("error")); + this.close(); + } + this.#reconnect(); + }; + fetchParams.processResponseEndOfBody = processEventSourceEndOfBody; + fetchParams.processResponse = (response) => { + if (isNetworkError(response)) if (response.aborted) { + this.close(); + this.dispatchEvent(new Event("error")); + return; + } else { + this.#reconnect(); + return; + } + const contentType = response.headersList.get("content-type", true); + const mimeType = contentType !== null ? parseMIMEType(contentType) : "failure"; + const contentTypeValid = mimeType !== "failure" && mimeType.essence === "text/event-stream"; + if (response.status !== 200 || contentTypeValid === false) { + this.close(); + this.dispatchEvent(new Event("error")); + return; + } + this.#readyState = OPEN; + this.dispatchEvent(new Event("open")); + this.#state.origin = response.urlList[response.urlList.length - 1].origin; + const eventSourceStream = new EventSourceStream({ + eventSourceSettings: this.#state, + push: (event) => { + this.dispatchEvent(createFastMessageEvent(event.type, event.options)); + } + }); + pipeline(response.body.stream, eventSourceStream, (error) => { + if (error?.aborted === false) { + this.close(); + this.dispatchEvent(new Event("error")); + } + }); + }; + this.#controller = fetching(fetchParams); + } + /** + * @see https://html.spec.whatwg.org/multipage/server-sent-events.html#sse-processing-model + * @returns {Promise} + */ + async #reconnect() { + if (this.#readyState === CLOSED) return; + this.#readyState = CONNECTING; + this.dispatchEvent(new Event("error")); + await delay(this.#state.reconnectionTime); + if (this.#readyState !== CONNECTING) return; + if (this.#state.lastEventId.length) this.#request.headersList.set("last-event-id", this.#state.lastEventId, true); + this.#connect(); + } + /** + * Closes the connection, if any, and sets the readyState attribute to + * CLOSED. + */ + close() { + webidl.brandCheck(this, EventSource); + if (this.#readyState === CLOSED) return; + this.#readyState = CLOSED; + this.#controller.abort(); + this.#request = null; + } + get onopen() { + return this.#events.open; + } + set onopen(fn) { + if (this.#events.open) this.removeEventListener("open", this.#events.open); + if (typeof fn === "function") { + this.#events.open = fn; + this.addEventListener("open", fn); + } else this.#events.open = null; + } + get onmessage() { + return this.#events.message; + } + set onmessage(fn) { + if (this.#events.message) this.removeEventListener("message", this.#events.message); + if (typeof fn === "function") { + this.#events.message = fn; + this.addEventListener("message", fn); + } else this.#events.message = null; + } + get onerror() { + return this.#events.error; + } + set onerror(fn) { + if (this.#events.error) this.removeEventListener("error", this.#events.error); + if (typeof fn === "function") { + this.#events.error = fn; + this.addEventListener("error", fn); + } else this.#events.error = null; + } + }; + var constantsPropertyDescriptors = { + CONNECTING: { + __proto__: null, + configurable: false, + enumerable: true, + value: CONNECTING, + writable: false + }, + OPEN: { + __proto__: null, + configurable: false, + enumerable: true, + value: OPEN, + writable: false + }, + CLOSED: { + __proto__: null, + configurable: false, + enumerable: true, + value: CLOSED, + writable: false + } + }; + Object.defineProperties(EventSource, constantsPropertyDescriptors); + Object.defineProperties(EventSource.prototype, constantsPropertyDescriptors); + Object.defineProperties(EventSource.prototype, { + close: kEnumerableProperty, + onerror: kEnumerableProperty, + onmessage: kEnumerableProperty, + onopen: kEnumerableProperty, + readyState: kEnumerableProperty, + url: kEnumerableProperty, + withCredentials: kEnumerableProperty + }); + webidl.converters.EventSourceInitDict = webidl.dictionaryConverter([{ + key: "withCredentials", + converter: webidl.converters.boolean, + defaultValue: () => false + }, { + key: "dispatcher", + converter: webidl.converters.any + }]); + module.exports = { + EventSource, + defaultReconnectionTime + }; +})); +//#endregion +//#region node_modules/undici/index.js +var require_undici = /* @__PURE__ */ __commonJSMin(((exports, module) => { + var Client = require_client(); + var Dispatcher = require_dispatcher(); + var Pool = require_pool(); + var BalancedPool = require_balanced_pool(); + var Agent = require_agent(); + var ProxyAgent = require_proxy_agent(); + var EnvHttpProxyAgent = require_env_http_proxy_agent(); + var RetryAgent = require_retry_agent(); + var errors = require_errors(); + var util = require_util$7(); + var { InvalidArgumentError } = errors; + var api = require_api(); + var buildConnector = require_connect(); + var MockClient = require_mock_client(); + var MockAgent = require_mock_agent(); + var MockPool = require_mock_pool(); + var mockErrors = require_mock_errors(); + var RetryHandler = require_retry_handler(); + var { getGlobalDispatcher, setGlobalDispatcher } = require_global(); + var DecoratorHandler = require_decorator_handler(); + var RedirectHandler = require_redirect_handler(); + var createRedirectInterceptor = require_redirect_interceptor(); + Object.assign(Dispatcher.prototype, api); + module.exports.Dispatcher = Dispatcher; + module.exports.Client = Client; + module.exports.Pool = Pool; + module.exports.BalancedPool = BalancedPool; + module.exports.Agent = Agent; + module.exports.ProxyAgent = ProxyAgent; + module.exports.EnvHttpProxyAgent = EnvHttpProxyAgent; + module.exports.RetryAgent = RetryAgent; + module.exports.RetryHandler = RetryHandler; + module.exports.DecoratorHandler = DecoratorHandler; + module.exports.RedirectHandler = RedirectHandler; + module.exports.createRedirectInterceptor = createRedirectInterceptor; + module.exports.interceptors = { + redirect: require_redirect(), + retry: require_retry(), + dump: require_dump(), + dns: require_dns() + }; + module.exports.buildConnector = buildConnector; + module.exports.errors = errors; + module.exports.util = { + parseHeaders: util.parseHeaders, + headerNameToString: util.headerNameToString + }; + function makeDispatcher(fn) { + return (url, opts, handler) => { + if (typeof opts === "function") { + handler = opts; + opts = null; + } + if (!url || typeof url !== "string" && typeof url !== "object" && !(url instanceof URL)) throw new InvalidArgumentError("invalid url"); + if (opts != null && typeof opts !== "object") throw new InvalidArgumentError("invalid opts"); + if (opts && opts.path != null) { + if (typeof opts.path !== "string") throw new InvalidArgumentError("invalid opts.path"); + let path = opts.path; + if (!opts.path.startsWith("/")) path = `/${path}`; + url = new URL(util.parseOrigin(url).origin + path); + } else { + if (!opts) opts = typeof url === "object" ? url : {}; + url = util.parseURL(url); + } + const { agent, dispatcher = getGlobalDispatcher() } = opts; + if (agent) throw new InvalidArgumentError("unsupported opts.agent. Did you mean opts.client?"); + return fn.call(dispatcher, { + ...opts, + origin: url.origin, + path: url.search ? `${url.pathname}${url.search}` : url.pathname, + method: opts.method || (opts.body ? "PUT" : "GET") + }, handler); + }; + } + module.exports.setGlobalDispatcher = setGlobalDispatcher; + module.exports.getGlobalDispatcher = getGlobalDispatcher; + var fetchImpl = require_fetch().fetch; + module.exports.fetch = async function fetch(init, options = void 0) { + try { + return await fetchImpl(init, options); + } catch (err) { + if (err && typeof err === "object") Error.captureStackTrace(err); + throw err; + } + }; + module.exports.Headers = require_headers().Headers; + module.exports.Response = require_response().Response; + module.exports.Request = require_request().Request; + module.exports.FormData = require_formdata().FormData; + module.exports.File = globalThis.File ?? __require("node:buffer").File; + module.exports.FileReader = require_filereader().FileReader; + var { setGlobalOrigin, getGlobalOrigin } = require_global$1(); + module.exports.setGlobalOrigin = setGlobalOrigin; + module.exports.getGlobalOrigin = getGlobalOrigin; + var { CacheStorage } = require_cachestorage(); + var { kConstruct } = require_symbols$1(); + module.exports.caches = new CacheStorage(kConstruct); + var { deleteCookie, getCookies, getSetCookies, setCookie } = require_cookies(); + module.exports.deleteCookie = deleteCookie; + module.exports.getCookies = getCookies; + module.exports.getSetCookies = getSetCookies; + module.exports.setCookie = setCookie; + var { parseMIMEType, serializeAMimeType } = require_data_url(); + module.exports.parseMIMEType = parseMIMEType; + module.exports.serializeAMimeType = serializeAMimeType; + var { CloseEvent, ErrorEvent, MessageEvent } = require_events(); + module.exports.WebSocket = require_websocket().WebSocket; + module.exports.CloseEvent = CloseEvent; + module.exports.ErrorEvent = ErrorEvent; + module.exports.MessageEvent = MessageEvent; + module.exports.request = makeDispatcher(api.request); + module.exports.stream = makeDispatcher(api.stream); + module.exports.pipeline = makeDispatcher(api.pipeline); + module.exports.connect = makeDispatcher(api.connect); + module.exports.upgrade = makeDispatcher(api.upgrade); + module.exports.MockClient = MockClient; + module.exports.MockPool = MockPool; + module.exports.MockAgent = MockAgent; + module.exports.mockErrors = mockErrors; + var { EventSource } = require_eventsource(); + module.exports.EventSource = EventSource; +})); +//#endregion +//#region node_modules/@actions/http-client/lib/index.js +var import_tunnel = /* @__PURE__ */ __toESM(require_tunnel(), 1); +var import_undici = require_undici(); +var __awaiter$10 = function(thisArg, _arguments, P, generator) { + function adopt(value) { + return value instanceof P ? value : new P(function(resolve) { + resolve(value); + }); + } + return new (P || (P = Promise))(function(resolve, reject) { + function fulfilled(value) { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + } + function rejected(value) { + try { + step(generator["throw"](value)); + } catch (e) { + reject(e); + } + } + function step(result) { + result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); + } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var HttpCodes; +(function(HttpCodes) { + HttpCodes[HttpCodes["OK"] = 200] = "OK"; + HttpCodes[HttpCodes["MultipleChoices"] = 300] = "MultipleChoices"; + HttpCodes[HttpCodes["MovedPermanently"] = 301] = "MovedPermanently"; + HttpCodes[HttpCodes["ResourceMoved"] = 302] = "ResourceMoved"; + HttpCodes[HttpCodes["SeeOther"] = 303] = "SeeOther"; + HttpCodes[HttpCodes["NotModified"] = 304] = "NotModified"; + HttpCodes[HttpCodes["UseProxy"] = 305] = "UseProxy"; + HttpCodes[HttpCodes["SwitchProxy"] = 306] = "SwitchProxy"; + HttpCodes[HttpCodes["TemporaryRedirect"] = 307] = "TemporaryRedirect"; + HttpCodes[HttpCodes["PermanentRedirect"] = 308] = "PermanentRedirect"; + HttpCodes[HttpCodes["BadRequest"] = 400] = "BadRequest"; + HttpCodes[HttpCodes["Unauthorized"] = 401] = "Unauthorized"; + HttpCodes[HttpCodes["PaymentRequired"] = 402] = "PaymentRequired"; + HttpCodes[HttpCodes["Forbidden"] = 403] = "Forbidden"; + HttpCodes[HttpCodes["NotFound"] = 404] = "NotFound"; + HttpCodes[HttpCodes["MethodNotAllowed"] = 405] = "MethodNotAllowed"; + HttpCodes[HttpCodes["NotAcceptable"] = 406] = "NotAcceptable"; + HttpCodes[HttpCodes["ProxyAuthenticationRequired"] = 407] = "ProxyAuthenticationRequired"; + HttpCodes[HttpCodes["RequestTimeout"] = 408] = "RequestTimeout"; + HttpCodes[HttpCodes["Conflict"] = 409] = "Conflict"; + HttpCodes[HttpCodes["Gone"] = 410] = "Gone"; + HttpCodes[HttpCodes["TooManyRequests"] = 429] = "TooManyRequests"; + HttpCodes[HttpCodes["InternalServerError"] = 500] = "InternalServerError"; + HttpCodes[HttpCodes["NotImplemented"] = 501] = "NotImplemented"; + HttpCodes[HttpCodes["BadGateway"] = 502] = "BadGateway"; + HttpCodes[HttpCodes["ServiceUnavailable"] = 503] = "ServiceUnavailable"; + HttpCodes[HttpCodes["GatewayTimeout"] = 504] = "GatewayTimeout"; +})(HttpCodes || (HttpCodes = {})); +var Headers; +(function(Headers) { + Headers["Accept"] = "accept"; + Headers["ContentType"] = "content-type"; +})(Headers || (Headers = {})); +var MediaTypes; +(function(MediaTypes) { + MediaTypes["ApplicationJson"] = "application/json"; +})(MediaTypes || (MediaTypes = {})); +var HttpRedirectCodes = [ + HttpCodes.MovedPermanently, + HttpCodes.ResourceMoved, + HttpCodes.SeeOther, + HttpCodes.TemporaryRedirect, + HttpCodes.PermanentRedirect +]; +var HttpResponseRetryCodes = [ + HttpCodes.BadGateway, + HttpCodes.ServiceUnavailable, + HttpCodes.GatewayTimeout +]; +var RetryableHttpVerbs = [ + "OPTIONS", + "GET", + "DELETE", + "HEAD" +]; +var ExponentialBackoffCeiling = 10; +var ExponentialBackoffTimeSlice = 5; +var HttpClientError = class HttpClientError extends Error { + constructor(message, statusCode) { + super(message); + this.name = "HttpClientError"; + this.statusCode = statusCode; + Object.setPrototypeOf(this, HttpClientError.prototype); + } +}; +var HttpClientResponse = class { + constructor(message) { + this.message = message; + } + readBody() { + return __awaiter$10(this, void 0, void 0, function* () { + return new Promise((resolve) => __awaiter$10(this, void 0, void 0, function* () { + let output = Buffer.alloc(0); + this.message.on("data", (chunk) => { + output = Buffer.concat([output, chunk]); + }); + this.message.on("end", () => { + resolve(output.toString()); + }); + })); + }); + } + readBodyBuffer() { + return __awaiter$10(this, void 0, void 0, function* () { + return new Promise((resolve) => __awaiter$10(this, void 0, void 0, function* () { + const chunks = []; + this.message.on("data", (chunk) => { + chunks.push(chunk); + }); + this.message.on("end", () => { + resolve(Buffer.concat(chunks)); + }); + })); + }); + } +}; +var HttpClient = class { + constructor(userAgent, handlers, requestOptions) { + this._ignoreSslError = false; + this._allowRedirects = true; + this._allowRedirectDowngrade = false; + this._maxRedirects = 50; + this._allowRetries = false; + this._maxRetries = 1; + this._keepAlive = false; + this._disposed = false; + this.userAgent = this._getUserAgentWithOrchestrationId(userAgent); + this.handlers = handlers || []; + this.requestOptions = requestOptions; + if (requestOptions) { + if (requestOptions.ignoreSslError != null) this._ignoreSslError = requestOptions.ignoreSslError; + this._socketTimeout = requestOptions.socketTimeout; + if (requestOptions.allowRedirects != null) this._allowRedirects = requestOptions.allowRedirects; + if (requestOptions.allowRedirectDowngrade != null) this._allowRedirectDowngrade = requestOptions.allowRedirectDowngrade; + if (requestOptions.maxRedirects != null) this._maxRedirects = Math.max(requestOptions.maxRedirects, 0); + if (requestOptions.keepAlive != null) this._keepAlive = requestOptions.keepAlive; + if (requestOptions.allowRetries != null) this._allowRetries = requestOptions.allowRetries; + if (requestOptions.maxRetries != null) this._maxRetries = requestOptions.maxRetries; + } + } + options(requestUrl, additionalHeaders) { + return __awaiter$10(this, void 0, void 0, function* () { + return this.request("OPTIONS", requestUrl, null, additionalHeaders || {}); + }); + } + get(requestUrl, additionalHeaders) { + return __awaiter$10(this, void 0, void 0, function* () { + return this.request("GET", requestUrl, null, additionalHeaders || {}); + }); + } + del(requestUrl, additionalHeaders) { + return __awaiter$10(this, void 0, void 0, function* () { + return this.request("DELETE", requestUrl, null, additionalHeaders || {}); + }); + } + post(requestUrl, data, additionalHeaders) { + return __awaiter$10(this, void 0, void 0, function* () { + return this.request("POST", requestUrl, data, additionalHeaders || {}); + }); + } + patch(requestUrl, data, additionalHeaders) { + return __awaiter$10(this, void 0, void 0, function* () { + return this.request("PATCH", requestUrl, data, additionalHeaders || {}); + }); + } + put(requestUrl, data, additionalHeaders) { + return __awaiter$10(this, void 0, void 0, function* () { + return this.request("PUT", requestUrl, data, additionalHeaders || {}); + }); + } + head(requestUrl, additionalHeaders) { + return __awaiter$10(this, void 0, void 0, function* () { + return this.request("HEAD", requestUrl, null, additionalHeaders || {}); + }); + } + sendStream(verb, requestUrl, stream, additionalHeaders) { + return __awaiter$10(this, void 0, void 0, function* () { + return this.request(verb, requestUrl, stream, additionalHeaders); + }); + } + /** + * Gets a typed object from an endpoint + * Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise + */ + getJson(requestUrl_1) { + return __awaiter$10(this, arguments, void 0, function* (requestUrl, additionalHeaders = {}) { + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + const res = yield this.get(requestUrl, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + postJson(requestUrl_1, obj_1) { + return __awaiter$10(this, arguments, void 0, function* (requestUrl, obj, additionalHeaders = {}) { + const data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultContentTypeHeader(additionalHeaders, MediaTypes.ApplicationJson); + const res = yield this.post(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + putJson(requestUrl_1, obj_1) { + return __awaiter$10(this, arguments, void 0, function* (requestUrl, obj, additionalHeaders = {}) { + const data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultContentTypeHeader(additionalHeaders, MediaTypes.ApplicationJson); + const res = yield this.put(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + patchJson(requestUrl_1, obj_1) { + return __awaiter$10(this, arguments, void 0, function* (requestUrl, obj, additionalHeaders = {}) { + const data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultContentTypeHeader(additionalHeaders, MediaTypes.ApplicationJson); + const res = yield this.patch(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + /** + * Makes a raw http request. + * All other methods such as get, post, patch, and request ultimately call this. + * Prefer get, del, post and patch + */ + request(verb, requestUrl, data, headers) { + return __awaiter$10(this, void 0, void 0, function* () { + if (this._disposed) throw new Error("Client has already been disposed."); + const parsedUrl = new URL(requestUrl); + let info = this._prepareRequest(verb, parsedUrl, headers); + const maxTries = this._allowRetries && RetryableHttpVerbs.includes(verb) ? this._maxRetries + 1 : 1; + let numTries = 0; + let response; + do { + response = yield this.requestRaw(info, data); + if (response && response.message && response.message.statusCode === HttpCodes.Unauthorized) { + let authenticationHandler; + for (const handler of this.handlers) if (handler.canHandleAuthentication(response)) { + authenticationHandler = handler; + break; + } + if (authenticationHandler) return authenticationHandler.handleAuthentication(this, info, data); + else return response; + } + let redirectsRemaining = this._maxRedirects; + while (response.message.statusCode && HttpRedirectCodes.includes(response.message.statusCode) && this._allowRedirects && redirectsRemaining > 0) { + const redirectUrl = response.message.headers["location"]; + if (!redirectUrl) break; + const parsedRedirectUrl = new URL(redirectUrl); + if (parsedUrl.protocol === "https:" && parsedUrl.protocol !== parsedRedirectUrl.protocol && !this._allowRedirectDowngrade) throw new Error("Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true."); + yield response.readBody(); + if (parsedRedirectUrl.hostname !== parsedUrl.hostname) { + for (const header in headers) if (header.toLowerCase() === "authorization") delete headers[header]; + } + info = this._prepareRequest(verb, parsedRedirectUrl, headers); + response = yield this.requestRaw(info, data); + redirectsRemaining--; + } + if (!response.message.statusCode || !HttpResponseRetryCodes.includes(response.message.statusCode)) return response; + numTries += 1; + if (numTries < maxTries) { + yield response.readBody(); + yield this._performExponentialBackoff(numTries); + } + } while (numTries < maxTries); + return response; + }); + } + /** + * Needs to be called if keepAlive is set to true in request options. + */ + dispose() { + if (this._agent) this._agent.destroy(); + this._disposed = true; + } + /** + * Raw request. + * @param info + * @param data + */ + requestRaw(info, data) { + return __awaiter$10(this, void 0, void 0, function* () { + return new Promise((resolve, reject) => { + function callbackForResult(err, res) { + if (err) reject(err); + else if (!res) reject(/* @__PURE__ */ new Error("Unknown error")); + else resolve(res); + } + this.requestRawWithCallback(info, data, callbackForResult); + }); + }); + } + /** + * Raw request with callback. + * @param info + * @param data + * @param onResult + */ + requestRawWithCallback(info, data, onResult) { + if (typeof data === "string") { + if (!info.options.headers) info.options.headers = {}; + info.options.headers["Content-Length"] = Buffer.byteLength(data, "utf8"); + } + let callbackCalled = false; + function handleResult(err, res) { + if (!callbackCalled) { + callbackCalled = true; + onResult(err, res); + } + } + const req = info.httpModule.request(info.options, (msg) => { + handleResult(void 0, new HttpClientResponse(msg)); + }); + let socket; + req.on("socket", (sock) => { + socket = sock; + }); + req.setTimeout(this._socketTimeout || 3 * 6e4, () => { + if (socket) socket.end(); + handleResult(/* @__PURE__ */ new Error(`Request timeout: ${info.options.path}`)); + }); + req.on("error", function(err) { + handleResult(err); + }); + if (data && typeof data === "string") req.write(data, "utf8"); + if (data && typeof data !== "string") { + data.on("close", function() { + req.end(); + }); + data.pipe(req); + } else req.end(); + } + /** + * Gets an http agent. This function is useful when you need an http agent that handles + * routing through a proxy server - depending upon the url and proxy environment variables. + * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com + */ + getAgent(serverUrl) { + const parsedUrl = new URL(serverUrl); + return this._getAgent(parsedUrl); + } + getAgentDispatcher(serverUrl) { + const parsedUrl = new URL(serverUrl); + const proxyUrl = getProxyUrl(parsedUrl); + if (!(proxyUrl && proxyUrl.hostname)) return; + return this._getProxyAgentDispatcher(parsedUrl, proxyUrl); + } + _prepareRequest(method, requestUrl, headers) { + const info = {}; + info.parsedUrl = requestUrl; + const usingSsl = info.parsedUrl.protocol === "https:"; + info.httpModule = usingSsl ? https$1 : http$1; + const defaultPort = usingSsl ? 443 : 80; + info.options = {}; + info.options.host = info.parsedUrl.hostname; + info.options.port = info.parsedUrl.port ? parseInt(info.parsedUrl.port) : defaultPort; + info.options.path = (info.parsedUrl.pathname || "") + (info.parsedUrl.search || ""); + info.options.method = method; + info.options.headers = this._mergeHeaders(headers); + if (this.userAgent != null) info.options.headers["user-agent"] = this.userAgent; + info.options.agent = this._getAgent(info.parsedUrl); + if (this.handlers) for (const handler of this.handlers) handler.prepareRequest(info.options); + return info; + } + _mergeHeaders(headers) { + if (this.requestOptions && this.requestOptions.headers) return Object.assign({}, lowercaseKeys$1(this.requestOptions.headers), lowercaseKeys$1(headers || {})); + return lowercaseKeys$1(headers || {}); + } + /** + * Gets an existing header value or returns a default. + * Handles converting number header values to strings since HTTP headers must be strings. + * Note: This returns string | string[] since some headers can have multiple values. + * For headers that must always be a single string (like Content-Type), use the + * specialized _getExistingOrDefaultContentTypeHeader method instead. + */ + _getExistingOrDefaultHeader(additionalHeaders, header, _default) { + let clientHeader; + if (this.requestOptions && this.requestOptions.headers) { + const headerValue = lowercaseKeys$1(this.requestOptions.headers)[header]; + if (headerValue) clientHeader = typeof headerValue === "number" ? headerValue.toString() : headerValue; + } + const additionalValue = additionalHeaders[header]; + if (additionalValue !== void 0) return typeof additionalValue === "number" ? additionalValue.toString() : additionalValue; + if (clientHeader !== void 0) return clientHeader; + return _default; + } + /** + * Specialized version of _getExistingOrDefaultHeader for Content-Type header. + * Always returns a single string (not an array) since Content-Type should be a single value. + * Converts arrays to comma-separated strings and numbers to strings to ensure type safety. + * This was split from _getExistingOrDefaultHeader to provide stricter typing for callers + * that assign the result to places expecting a string (e.g., additionalHeaders[Headers.ContentType]). + */ + _getExistingOrDefaultContentTypeHeader(additionalHeaders, _default) { + let clientHeader; + if (this.requestOptions && this.requestOptions.headers) { + const headerValue = lowercaseKeys$1(this.requestOptions.headers)[Headers.ContentType]; + if (headerValue) if (typeof headerValue === "number") clientHeader = String(headerValue); + else if (Array.isArray(headerValue)) clientHeader = headerValue.join(", "); + else clientHeader = headerValue; + } + const additionalValue = additionalHeaders[Headers.ContentType]; + if (additionalValue !== void 0) if (typeof additionalValue === "number") return String(additionalValue); + else if (Array.isArray(additionalValue)) return additionalValue.join(", "); + else return additionalValue; + if (clientHeader !== void 0) return clientHeader; + return _default; + } + _getAgent(parsedUrl) { + let agent; + const proxyUrl = getProxyUrl(parsedUrl); + const useProxy = proxyUrl && proxyUrl.hostname; + if (this._keepAlive && useProxy) agent = this._proxyAgent; + if (!useProxy) agent = this._agent; + if (agent) return agent; + const usingSsl = parsedUrl.protocol === "https:"; + let maxSockets = 100; + if (this.requestOptions) maxSockets = this.requestOptions.maxSockets || http$1.globalAgent.maxSockets; + if (proxyUrl && proxyUrl.hostname) { + const agentOptions = { + maxSockets, + keepAlive: this._keepAlive, + proxy: Object.assign(Object.assign({}, (proxyUrl.username || proxyUrl.password) && { proxyAuth: `${proxyUrl.username}:${proxyUrl.password}` }), { + host: proxyUrl.hostname, + port: proxyUrl.port + }) + }; + let tunnelAgent; + const overHttps = proxyUrl.protocol === "https:"; + if (usingSsl) tunnelAgent = overHttps ? import_tunnel.httpsOverHttps : import_tunnel.httpsOverHttp; + else tunnelAgent = overHttps ? import_tunnel.httpOverHttps : import_tunnel.httpOverHttp; + agent = tunnelAgent(agentOptions); + this._proxyAgent = agent; + } + if (!agent) { + const options = { + keepAlive: this._keepAlive, + maxSockets + }; + agent = usingSsl ? new https$1.Agent(options) : new http$1.Agent(options); + this._agent = agent; + } + if (usingSsl && this._ignoreSslError) agent.options = Object.assign(agent.options || {}, { rejectUnauthorized: false }); + return agent; + } + _getProxyAgentDispatcher(parsedUrl, proxyUrl) { + let proxyAgent; + if (this._keepAlive) proxyAgent = this._proxyAgentDispatcher; + if (proxyAgent) return proxyAgent; + const usingSsl = parsedUrl.protocol === "https:"; + proxyAgent = new import_undici.ProxyAgent(Object.assign({ + uri: proxyUrl.href, + pipelining: !this._keepAlive ? 0 : 1 + }, (proxyUrl.username || proxyUrl.password) && { token: `Basic ${Buffer.from(`${proxyUrl.username}:${proxyUrl.password}`).toString("base64")}` })); + this._proxyAgentDispatcher = proxyAgent; + if (usingSsl && this._ignoreSslError) proxyAgent.options = Object.assign(proxyAgent.options.requestTls || {}, { rejectUnauthorized: false }); + return proxyAgent; + } + _getUserAgentWithOrchestrationId(userAgent) { + const baseUserAgent = userAgent || "actions/http-client"; + const orchId = process.env["ACTIONS_ORCHESTRATION_ID"]; + if (orchId) return `${baseUserAgent} actions_orchestration_id/${orchId.replace(/[^a-z0-9_.-]/gi, "_")}`; + return baseUserAgent; + } + _performExponentialBackoff(retryNumber) { + return __awaiter$10(this, void 0, void 0, function* () { + retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber); + const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber); + return new Promise((resolve) => setTimeout(() => resolve(), ms)); + }); + } + _processResponse(res, options) { + return __awaiter$10(this, void 0, void 0, function* () { + return new Promise((resolve, reject) => __awaiter$10(this, void 0, void 0, function* () { + const statusCode = res.message.statusCode || 0; + const response = { + statusCode, + result: null, + headers: {} + }; + if (statusCode === HttpCodes.NotFound) resolve(response); + function dateTimeDeserializer(key, value) { + if (typeof value === "string") { + const a = new Date(value); + if (!isNaN(a.valueOf())) return a; + } + return value; + } + let obj; + let contents; + try { + contents = yield res.readBody(); + if (contents && contents.length > 0) { + if (options && options.deserializeDates) obj = JSON.parse(contents, dateTimeDeserializer); + else obj = JSON.parse(contents); + response.result = obj; + } + response.headers = res.message.headers; + } catch (err) {} + if (statusCode > 299) { + let msg; + if (obj && obj.message) msg = obj.message; + else if (contents && contents.length > 0) msg = contents; + else msg = `Failed request: (${statusCode})`; + const err = new HttpClientError(msg, statusCode); + err.result = response.result; + reject(err); + } else resolve(response); + })); + }); + } +}; +var lowercaseKeys$1 = (obj) => Object.keys(obj).reduce((c, k) => (c[k.toLowerCase()] = obj[k], c), {}); +//#endregion +//#region node_modules/@actions/http-client/lib/auth.js +var __awaiter$9 = function(thisArg, _arguments, P, generator) { + function adopt(value) { + return value instanceof P ? value : new P(function(resolve) { + resolve(value); + }); + } + return new (P || (P = Promise))(function(resolve, reject) { + function fulfilled(value) { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + } + function rejected(value) { + try { + step(generator["throw"](value)); + } catch (e) { + reject(e); + } + } + function step(result) { + result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); + } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var BearerCredentialHandler = class { + constructor(token) { + this.token = token; + } + prepareRequest(options) { + if (!options.headers) throw Error("The request has no headers"); + options.headers["Authorization"] = `Bearer ${this.token}`; + } + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter$9(this, void 0, void 0, function* () { + throw new Error("not implemented"); + }); + } +}; +//#endregion +//#region node_modules/@actions/core/lib/oidc-utils.js +var __awaiter$8 = function(thisArg, _arguments, P, generator) { + function adopt(value) { + return value instanceof P ? value : new P(function(resolve) { + resolve(value); + }); + } + return new (P || (P = Promise))(function(resolve, reject) { + function fulfilled(value) { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + } + function rejected(value) { + try { + step(generator["throw"](value)); + } catch (e) { + reject(e); + } + } + function step(result) { + result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); + } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var OidcClient = class OidcClient { + static createHttpClient(allowRetry = true, maxRetry = 10) { + const requestOptions = { + allowRetries: allowRetry, + maxRetries: maxRetry + }; + return new HttpClient("actions/oidc-client", [new BearerCredentialHandler(OidcClient.getRequestToken())], requestOptions); + } + static getRequestToken() { + const token = process.env["ACTIONS_ID_TOKEN_REQUEST_TOKEN"]; + if (!token) throw new Error("Unable to get ACTIONS_ID_TOKEN_REQUEST_TOKEN env variable"); + return token; + } + static getIDTokenUrl() { + const runtimeUrl = process.env["ACTIONS_ID_TOKEN_REQUEST_URL"]; + if (!runtimeUrl) throw new Error("Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable"); + return runtimeUrl; + } + static getCall(id_token_url) { + return __awaiter$8(this, void 0, void 0, function* () { + var _a; + const id_token = (_a = (yield OidcClient.createHttpClient().getJson(id_token_url).catch((error) => { + throw new Error(`Failed to get ID Token. \n + Error Code : ${error.statusCode}\n + Error Message: ${error.message}`); + })).result) === null || _a === void 0 ? void 0 : _a.value; + if (!id_token) throw new Error("Response json body do not have ID Token field"); + return id_token; + }); + } + static getIDToken(audience) { + return __awaiter$8(this, void 0, void 0, function* () { + try { + let id_token_url = OidcClient.getIDTokenUrl(); + if (audience) id_token_url = `${id_token_url}&audience=${encodeURIComponent(audience)}`; + debug(`ID token url is ${id_token_url}`); + const id_token = yield OidcClient.getCall(id_token_url); + setSecret(id_token); + return id_token; + } catch (error) { + throw new Error(`Error message: ${error.message}`); + } + }); + } +}; +//#endregion +//#region node_modules/@actions/core/lib/summary.js +var __awaiter$7 = function(thisArg, _arguments, P, generator) { + function adopt(value) { + return value instanceof P ? value : new P(function(resolve) { + resolve(value); + }); + } + return new (P || (P = Promise))(function(resolve, reject) { + function fulfilled(value) { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + } + function rejected(value) { + try { + step(generator["throw"](value)); + } catch (e) { + reject(e); + } + } + function step(result) { + result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); + } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var { access, appendFile, writeFile } = promises; +var SUMMARY_ENV_VAR = "GITHUB_STEP_SUMMARY"; +var Summary = class { + constructor() { + this._buffer = ""; + } + /** + * Finds the summary file path from the environment, rejects if env var is not found or file does not exist + * Also checks r/w permissions. + * + * @returns step summary file path + */ + filePath() { + return __awaiter$7(this, void 0, void 0, function* () { + if (this._filePath) return this._filePath; + const pathFromEnv = process.env[SUMMARY_ENV_VAR]; + if (!pathFromEnv) throw new Error(`Unable to find environment variable for $${SUMMARY_ENV_VAR}. Check if your runtime environment supports job summaries.`); + try { + yield access(pathFromEnv, constants.R_OK | constants.W_OK); + } catch (_a) { + throw new Error(`Unable to access summary file: '${pathFromEnv}'. Check if the file has correct read/write permissions.`); + } + this._filePath = pathFromEnv; + return this._filePath; + }); + } + /** + * Wraps content in an HTML tag, adding any HTML attributes + * + * @param {string} tag HTML tag to wrap + * @param {string | null} content content within the tag + * @param {[attribute: string]: string} attrs key-value list of HTML attributes to add + * + * @returns {string} content wrapped in HTML element + */ + wrap(tag, content, attrs = {}) { + const htmlAttrs = Object.entries(attrs).map(([key, value]) => ` ${key}="${value}"`).join(""); + if (!content) return `<${tag}${htmlAttrs}>`; + return `<${tag}${htmlAttrs}>${content}`; + } + /** + * Writes text in the buffer to the summary buffer file and empties buffer. Will append by default. + * + * @param {SummaryWriteOptions} [options] (optional) options for write operation + * + * @returns {Promise} summary instance + */ + write(options) { + return __awaiter$7(this, void 0, void 0, function* () { + const overwrite = !!(options === null || options === void 0 ? void 0 : options.overwrite); + const filePath = yield this.filePath(); + yield (overwrite ? writeFile : appendFile)(filePath, this._buffer, { encoding: "utf8" }); + return this.emptyBuffer(); + }); + } + /** + * Clears the summary buffer and wipes the summary file + * + * @returns {Summary} summary instance + */ + clear() { + return __awaiter$7(this, void 0, void 0, function* () { + return this.emptyBuffer().write({ overwrite: true }); + }); + } + /** + * Returns the current summary buffer as a string + * + * @returns {string} string of summary buffer + */ + stringify() { + return this._buffer; + } + /** + * If the summary buffer is empty + * + * @returns {boolen} true if the buffer is empty + */ + isEmptyBuffer() { + return this._buffer.length === 0; + } + /** + * Resets the summary buffer without writing to summary file + * + * @returns {Summary} summary instance + */ + emptyBuffer() { + this._buffer = ""; + return this; + } + /** + * Adds raw text to the summary buffer + * + * @param {string} text content to add + * @param {boolean} [addEOL=false] (optional) append an EOL to the raw text (default: false) + * + * @returns {Summary} summary instance + */ + addRaw(text, addEOL = false) { + this._buffer += text; + return addEOL ? this.addEOL() : this; + } + /** + * Adds the operating system-specific end-of-line marker to the buffer + * + * @returns {Summary} summary instance + */ + addEOL() { + return this.addRaw(EOL); + } + /** + * Adds an HTML codeblock to the summary buffer + * + * @param {string} code content to render within fenced code block + * @param {string} lang (optional) language to syntax highlight code + * + * @returns {Summary} summary instance + */ + addCodeBlock(code, lang) { + const attrs = Object.assign({}, lang && { lang }); + const element = this.wrap("pre", this.wrap("code", code), attrs); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML list to the summary buffer + * + * @param {string[]} items list of items to render + * @param {boolean} [ordered=false] (optional) if the rendered list should be ordered or not (default: false) + * + * @returns {Summary} summary instance + */ + addList(items, ordered = false) { + const tag = ordered ? "ol" : "ul"; + const listItems = items.map((item) => this.wrap("li", item)).join(""); + const element = this.wrap(tag, listItems); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML table to the summary buffer + * + * @param {SummaryTableCell[]} rows table rows + * + * @returns {Summary} summary instance + */ + addTable(rows) { + const tableBody = rows.map((row) => { + const cells = row.map((cell) => { + if (typeof cell === "string") return this.wrap("td", cell); + const { header, data, colspan, rowspan } = cell; + const tag = header ? "th" : "td"; + const attrs = Object.assign(Object.assign({}, colspan && { colspan }), rowspan && { rowspan }); + return this.wrap(tag, data, attrs); + }).join(""); + return this.wrap("tr", cells); + }).join(""); + const element = this.wrap("table", tableBody); + return this.addRaw(element).addEOL(); + } + /** + * Adds a collapsable HTML details element to the summary buffer + * + * @param {string} label text for the closed state + * @param {string} content collapsable content + * + * @returns {Summary} summary instance + */ + addDetails(label, content) { + const element = this.wrap("details", this.wrap("summary", label) + content); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML image tag to the summary buffer + * + * @param {string} src path to the image you to embed + * @param {string} alt text description of the image + * @param {SummaryImageOptions} options (optional) addition image attributes + * + * @returns {Summary} summary instance + */ + addImage(src, alt, options) { + const { width, height } = options || {}; + const attrs = Object.assign(Object.assign({}, width && { width }), height && { height }); + const element = this.wrap("img", null, Object.assign({ + src, + alt + }, attrs)); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML section heading element + * + * @param {string} text heading text + * @param {number | string} [level=1] (optional) the heading level, default: 1 + * + * @returns {Summary} summary instance + */ + addHeading(text, level) { + const tag = `h${level}`; + const allowedTag = [ + "h1", + "h2", + "h3", + "h4", + "h5", + "h6" + ].includes(tag) ? tag : "h1"; + const element = this.wrap(allowedTag, text); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML thematic break (
) to the summary buffer + * + * @returns {Summary} summary instance + */ + addSeparator() { + const element = this.wrap("hr", null); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML line break (
) to the summary buffer + * + * @returns {Summary} summary instance + */ + addBreak() { + const element = this.wrap("br", null); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML blockquote to the summary buffer + * + * @param {string} text quote text + * @param {string} cite (optional) citation url + * + * @returns {Summary} summary instance + */ + addQuote(text, cite) { + const attrs = Object.assign({}, cite && { cite }); + const element = this.wrap("blockquote", text, attrs); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML anchor tag to the summary buffer + * + * @param {string} text link text/content + * @param {string} href hyperlink + * + * @returns {Summary} summary instance + */ + addLink(text, href) { + const element = this.wrap("a", text, { href }); + return this.addRaw(element).addEOL(); + } +}; +var _summary = new Summary(); +/** +* @deprecated use `core.summary` +*/ +var markdownSummary = _summary; +var summary = _summary; +//#endregion +//#region node_modules/@actions/core/lib/path-utils.js +/** +* toPosixPath converts the given path to the posix form. On Windows, \\ will be +* replaced with /. +* +* @param pth. Path to transform. +* @return string Posix path. +*/ +function toPosixPath(pth) { + return pth.replace(/[\\]/g, "/"); +} +/** +* toWin32Path converts the given path to the win32 form. On Linux, / will be +* replaced with \\. +* +* @param pth. Path to transform. +* @return string Win32 path. +*/ +function toWin32Path(pth) { + return pth.replace(/[/]/g, "\\"); +} +/** +* toPlatformPath converts the given path to a platform-specific path. It does +* this by replacing instances of / and \ with the platform-specific path +* separator. +* +* @param pth The path to platformize. +* @return string The platform-specific path. +*/ +function toPlatformPath(pth) { + return pth.replace(/[/\\]/g, path$1.sep); +} +//#endregion +//#region node_modules/@actions/io/lib/io-util.js +var __awaiter$6 = function(thisArg, _arguments, P, generator) { + function adopt(value) { + return value instanceof P ? value : new P(function(resolve) { + resolve(value); + }); + } + return new (P || (P = Promise))(function(resolve, reject) { + function fulfilled(value) { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + } + function rejected(value) { + try { + step(generator["throw"](value)); + } catch (e) { + reject(e); + } + } + function step(result) { + result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); + } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var { chmod, copyFile, lstat, mkdir, open, readdir, rename, rm, rmdir, stat, symlink, unlink } = fs.promises; +var IS_WINDOWS$1 = process.platform === "win32"; +fs.constants.O_RDONLY; +function exists(fsPath) { + return __awaiter$6(this, void 0, void 0, function* () { + try { + yield stat(fsPath); + } catch (err) { + if (err.code === "ENOENT") return false; + throw err; + } + return true; + }); +} +/** +* On OSX/Linux, true if path starts with '/'. On Windows, true for paths like: +* \, \hello, \\hello\share, C:, and C:\hello (and corresponding alternate separator cases). +*/ +function isRooted(p) { + p = normalizeSeparators(p); + if (!p) throw new Error("isRooted() parameter \"p\" cannot be empty"); + if (IS_WINDOWS$1) return p.startsWith("\\") || /^[A-Z]:/i.test(p); + return p.startsWith("/"); +} +/** +* Best effort attempt to determine whether a file exists and is executable. +* @param filePath file path to check +* @param extensions additional file extensions to try +* @return if file exists and is executable, returns the file path. otherwise empty string. +*/ +function tryGetExecutablePath(filePath, extensions) { + return __awaiter$6(this, void 0, void 0, function* () { + let stats = void 0; + try { + stats = yield stat(filePath); + } catch (err) { + if (err.code !== "ENOENT") console.log(`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`); + } + if (stats && stats.isFile()) { + if (IS_WINDOWS$1) { + const upperExt = path$1.extname(filePath).toUpperCase(); + if (extensions.some((validExt) => validExt.toUpperCase() === upperExt)) return filePath; + } else if (isUnixExecutable(stats)) return filePath; + } + const originalFilePath = filePath; + for (const extension of extensions) { + filePath = originalFilePath + extension; + stats = void 0; + try { + stats = yield stat(filePath); + } catch (err) { + if (err.code !== "ENOENT") console.log(`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`); + } + if (stats && stats.isFile()) { + if (IS_WINDOWS$1) { + try { + const directory = path$1.dirname(filePath); + const upperName = path$1.basename(filePath).toUpperCase(); + for (const actualName of yield readdir(directory)) if (upperName === actualName.toUpperCase()) { + filePath = path$1.join(directory, actualName); + break; + } + } catch (err) { + console.log(`Unexpected error attempting to determine the actual case of the file '${filePath}': ${err}`); + } + return filePath; + } else if (isUnixExecutable(stats)) return filePath; + } + } + return ""; + }); +} +function normalizeSeparators(p) { + p = p || ""; + if (IS_WINDOWS$1) { + p = p.replace(/\//g, "\\"); + return p.replace(/\\\\+/g, "\\"); + } + return p.replace(/\/\/+/g, "/"); +} +function isUnixExecutable(stats) { + return (stats.mode & 1) > 0 || (stats.mode & 8) > 0 && process.getgid !== void 0 && stats.gid === process.getgid() || (stats.mode & 64) > 0 && process.getuid !== void 0 && stats.uid === process.getuid(); +} +//#endregion +//#region node_modules/@actions/io/lib/io.js +var __awaiter$5 = function(thisArg, _arguments, P, generator) { + function adopt(value) { + return value instanceof P ? value : new P(function(resolve) { + resolve(value); + }); + } + return new (P || (P = Promise))(function(resolve, reject) { + function fulfilled(value) { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + } + function rejected(value) { + try { + step(generator["throw"](value)); + } catch (e) { + reject(e); + } + } + function step(result) { + result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); + } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +/** +* Returns path of a tool had the tool actually been invoked. Resolves via paths. +* If you check and the tool does not exist, it will throw. +* +* @param tool name of the tool +* @param check whether to check if tool exists +* @returns Promise path to tool +*/ +function which(tool, check) { + return __awaiter$5(this, void 0, void 0, function* () { + if (!tool) throw new Error("parameter 'tool' is required"); + if (check) { + const result = yield which(tool, false); + if (!result) if (IS_WINDOWS$1) throw new Error(`Unable to locate executable file: ${tool}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also verify the file has a valid extension for an executable file.`); + else throw new Error(`Unable to locate executable file: ${tool}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.`); + return result; + } + const matches = yield findInPath(tool); + if (matches && matches.length > 0) return matches[0]; + return ""; + }); +} +/** +* Returns a list of all occurrences of the given tool on the system path. +* +* @returns Promise the paths of the tool +*/ +function findInPath(tool) { + return __awaiter$5(this, void 0, void 0, function* () { + if (!tool) throw new Error("parameter 'tool' is required"); + const extensions = []; + if (IS_WINDOWS$1 && process.env["PATHEXT"]) { + for (const extension of process.env["PATHEXT"].split(path$1.delimiter)) if (extension) extensions.push(extension); + } + if (isRooted(tool)) { + const filePath = yield tryGetExecutablePath(tool, extensions); + if (filePath) return [filePath]; + return []; + } + if (tool.includes(path$1.sep)) return []; + const directories = []; + if (process.env.PATH) { + for (const p of process.env.PATH.split(path$1.delimiter)) if (p) directories.push(p); + } + const matches = []; + for (const directory of directories) { + const filePath = yield tryGetExecutablePath(path$1.join(directory, tool), extensions); + if (filePath) matches.push(filePath); + } + return matches; + }); +} +//#endregion +//#region node_modules/@actions/exec/lib/toolrunner.js +var __awaiter$4 = function(thisArg, _arguments, P, generator) { + function adopt(value) { + return value instanceof P ? value : new P(function(resolve) { + resolve(value); + }); + } + return new (P || (P = Promise))(function(resolve, reject) { + function fulfilled(value) { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + } + function rejected(value) { + try { + step(generator["throw"](value)); + } catch (e) { + reject(e); + } + } + function step(result) { + result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); + } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var IS_WINDOWS = process.platform === "win32"; +var ToolRunner = class extends events.EventEmitter { + constructor(toolPath, args, options) { + super(); + if (!toolPath) throw new Error("Parameter 'toolPath' cannot be null or empty."); + this.toolPath = toolPath; + this.args = args || []; + this.options = options || {}; + } + _debug(message) { + if (this.options.listeners && this.options.listeners.debug) this.options.listeners.debug(message); + } + _getCommandString(options, noPrefix) { + const toolPath = this._getSpawnFileName(); + const args = this._getSpawnArgs(options); + let cmd = noPrefix ? "" : "[command]"; + if (IS_WINDOWS) if (this._isCmdFile()) { + cmd += toolPath; + for (const a of args) cmd += ` ${a}`; + } else if (options.windowsVerbatimArguments) { + cmd += `"${toolPath}"`; + for (const a of args) cmd += ` ${a}`; + } else { + cmd += this._windowsQuoteCmdArg(toolPath); + for (const a of args) cmd += ` ${this._windowsQuoteCmdArg(a)}`; + } + else { + cmd += toolPath; + for (const a of args) cmd += ` ${a}`; + } + return cmd; + } + _processLineBuffer(data, strBuffer, onLine) { + try { + let s = strBuffer + data.toString(); + let n = s.indexOf(os$1.EOL); + while (n > -1) { + onLine(s.substring(0, n)); + s = s.substring(n + os$1.EOL.length); + n = s.indexOf(os$1.EOL); + } + return s; + } catch (err) { + this._debug(`error processing line. Failed with error ${err}`); + return ""; + } + } + _getSpawnFileName() { + if (IS_WINDOWS) { + if (this._isCmdFile()) return process.env["COMSPEC"] || "cmd.exe"; + } + return this.toolPath; + } + _getSpawnArgs(options) { + if (IS_WINDOWS) { + if (this._isCmdFile()) { + let argline = `/D /S /C "${this._windowsQuoteCmdArg(this.toolPath)}`; + for (const a of this.args) { + argline += " "; + argline += options.windowsVerbatimArguments ? a : this._windowsQuoteCmdArg(a); + } + argline += "\""; + return [argline]; + } + } + return this.args; + } + _endsWith(str, end) { + return str.endsWith(end); + } + _isCmdFile() { + const upperToolPath = this.toolPath.toUpperCase(); + return this._endsWith(upperToolPath, ".CMD") || this._endsWith(upperToolPath, ".BAT"); + } + _windowsQuoteCmdArg(arg) { + if (!this._isCmdFile()) return this._uvQuoteCmdArg(arg); + if (!arg) return "\"\""; + const cmdSpecialChars = [ + " ", + " ", + "&", + "(", + ")", + "[", + "]", + "{", + "}", + "^", + "=", + ";", + "!", + "'", + "+", + ",", + "`", + "~", + "|", + "<", + ">", + "\"" + ]; + let needsQuotes = false; + for (const char of arg) if (cmdSpecialChars.some((x) => x === char)) { + needsQuotes = true; + break; + } + if (!needsQuotes) return arg; + let reverse = "\""; + let quoteHit = true; + for (let i = arg.length; i > 0; i--) { + reverse += arg[i - 1]; + if (quoteHit && arg[i - 1] === "\\") reverse += "\\"; + else if (arg[i - 1] === "\"") { + quoteHit = true; + reverse += "\""; + } else quoteHit = false; + } + reverse += "\""; + return reverse.split("").reverse().join(""); + } + _uvQuoteCmdArg(arg) { + if (!arg) return "\"\""; + if (!arg.includes(" ") && !arg.includes(" ") && !arg.includes("\"")) return arg; + if (!arg.includes("\"") && !arg.includes("\\")) return `"${arg}"`; + let reverse = "\""; + let quoteHit = true; + for (let i = arg.length; i > 0; i--) { + reverse += arg[i - 1]; + if (quoteHit && arg[i - 1] === "\\") reverse += "\\"; + else if (arg[i - 1] === "\"") { + quoteHit = true; + reverse += "\\"; + } else quoteHit = false; + } + reverse += "\""; + return reverse.split("").reverse().join(""); + } + _cloneExecOptions(options) { + options = options || {}; + const result = { + cwd: options.cwd || process.cwd(), + env: options.env || process.env, + silent: options.silent || false, + windowsVerbatimArguments: options.windowsVerbatimArguments || false, + failOnStdErr: options.failOnStdErr || false, + ignoreReturnCode: options.ignoreReturnCode || false, + delay: options.delay || 1e4 + }; + result.outStream = options.outStream || process.stdout; + result.errStream = options.errStream || process.stderr; + return result; + } + _getSpawnOptions(options, toolPath) { + options = options || {}; + const result = {}; + result.cwd = options.cwd; + result.env = options.env; + result["windowsVerbatimArguments"] = options.windowsVerbatimArguments || this._isCmdFile(); + if (options.windowsVerbatimArguments) result.argv0 = `"${toolPath}"`; + return result; + } + /** + * Exec a tool. + * Output will be streamed to the live console. + * Returns promise with return code + * + * @param tool path to tool to exec + * @param options optional exec options. See ExecOptions + * @returns number + */ + exec() { + return __awaiter$4(this, void 0, void 0, function* () { + if (!isRooted(this.toolPath) && (this.toolPath.includes("/") || IS_WINDOWS && this.toolPath.includes("\\"))) this.toolPath = path$1.resolve(process.cwd(), this.options.cwd || process.cwd(), this.toolPath); + this.toolPath = yield which(this.toolPath, true); + return new Promise((resolve, reject) => __awaiter$4(this, void 0, void 0, function* () { + this._debug(`exec tool: ${this.toolPath}`); + this._debug("arguments:"); + for (const arg of this.args) this._debug(` ${arg}`); + const optionsNonNull = this._cloneExecOptions(this.options); + if (!optionsNonNull.silent && optionsNonNull.outStream) optionsNonNull.outStream.write(this._getCommandString(optionsNonNull) + os$1.EOL); + const state = new ExecState(optionsNonNull, this.toolPath); + state.on("debug", (message) => { + this._debug(message); + }); + if (this.options.cwd && !(yield exists(this.options.cwd))) return reject(/* @__PURE__ */ new Error(`The cwd: ${this.options.cwd} does not exist!`)); + const fileName = this._getSpawnFileName(); + const cp = child.spawn(fileName, this._getSpawnArgs(optionsNonNull), this._getSpawnOptions(this.options, fileName)); + let stdbuffer = ""; + if (cp.stdout) cp.stdout.on("data", (data) => { + if (this.options.listeners && this.options.listeners.stdout) this.options.listeners.stdout(data); + if (!optionsNonNull.silent && optionsNonNull.outStream) optionsNonNull.outStream.write(data); + stdbuffer = this._processLineBuffer(data, stdbuffer, (line) => { + if (this.options.listeners && this.options.listeners.stdline) this.options.listeners.stdline(line); + }); + }); + let errbuffer = ""; + if (cp.stderr) cp.stderr.on("data", (data) => { + state.processStderr = true; + if (this.options.listeners && this.options.listeners.stderr) this.options.listeners.stderr(data); + if (!optionsNonNull.silent && optionsNonNull.errStream && optionsNonNull.outStream) (optionsNonNull.failOnStdErr ? optionsNonNull.errStream : optionsNonNull.outStream).write(data); + errbuffer = this._processLineBuffer(data, errbuffer, (line) => { + if (this.options.listeners && this.options.listeners.errline) this.options.listeners.errline(line); + }); + }); + cp.on("error", (err) => { + state.processError = err.message; + state.processExited = true; + state.processClosed = true; + state.CheckComplete(); + }); + cp.on("exit", (code) => { + state.processExitCode = code; + state.processExited = true; + this._debug(`Exit code ${code} received from tool '${this.toolPath}'`); + state.CheckComplete(); + }); + cp.on("close", (code) => { + state.processExitCode = code; + state.processExited = true; + state.processClosed = true; + this._debug(`STDIO streams have closed for tool '${this.toolPath}'`); + state.CheckComplete(); + }); + state.on("done", (error, exitCode) => { + if (stdbuffer.length > 0) this.emit("stdline", stdbuffer); + if (errbuffer.length > 0) this.emit("errline", errbuffer); + cp.removeAllListeners(); + if (error) reject(error); + else resolve(exitCode); + }); + if (this.options.input) { + if (!cp.stdin) throw new Error("child process missing stdin"); + cp.stdin.end(this.options.input); + } + })); + }); + } +}; +/** +* Convert an arg string to an array of args. Handles escaping +* +* @param argString string of arguments +* @returns string[] array of arguments +*/ +function argStringToArray(argString) { + const args = []; + let inQuotes = false; + let escaped = false; + let arg = ""; + function append(c) { + if (escaped && c !== "\"") arg += "\\"; + arg += c; + escaped = false; + } + for (let i = 0; i < argString.length; i++) { + const c = argString.charAt(i); + if (c === "\"") { + if (!escaped) inQuotes = !inQuotes; + else append(c); + continue; + } + if (c === "\\" && escaped) { + append(c); + continue; + } + if (c === "\\" && inQuotes) { + escaped = true; + continue; + } + if (c === " " && !inQuotes) { + if (arg.length > 0) { + args.push(arg); + arg = ""; + } + continue; + } + append(c); + } + if (arg.length > 0) args.push(arg.trim()); + return args; +} +var ExecState = class ExecState extends events.EventEmitter { + constructor(options, toolPath) { + super(); + this.processClosed = false; + this.processError = ""; + this.processExitCode = 0; + this.processExited = false; + this.processStderr = false; + this.delay = 1e4; + this.done = false; + this.timeout = null; + if (!toolPath) throw new Error("toolPath must not be empty"); + this.options = options; + this.toolPath = toolPath; + if (options.delay) this.delay = options.delay; + } + CheckComplete() { + if (this.done) return; + if (this.processClosed) this._setResult(); + else if (this.processExited) this.timeout = setTimeout$1(ExecState.HandleTimeout, this.delay, this); + } + _debug(message) { + this.emit("debug", message); + } + _setResult() { + let error; + if (this.processExited) { + if (this.processError) error = /* @__PURE__ */ new Error(`There was an error when attempting to execute the process '${this.toolPath}'. This may indicate the process failed to start. Error: ${this.processError}`); + else if (this.processExitCode !== 0 && !this.options.ignoreReturnCode) error = /* @__PURE__ */ new Error(`The process '${this.toolPath}' failed with exit code ${this.processExitCode}`); + else if (this.processStderr && this.options.failOnStdErr) error = /* @__PURE__ */ new Error(`The process '${this.toolPath}' failed because one or more lines were written to the STDERR stream`); + } + if (this.timeout) { + clearTimeout(this.timeout); + this.timeout = null; + } + this.done = true; + this.emit("done", error, this.processExitCode); + } + static HandleTimeout(state) { + if (state.done) return; + if (!state.processClosed && state.processExited) { + const message = `The STDIO streams did not close within ${state.delay / 1e3} seconds of the exit event from process '${state.toolPath}'. This may indicate a child process inherited the STDIO streams and has not yet exited.`; + state._debug(message); + } + state._setResult(); + } +}; +//#endregion +//#region node_modules/@actions/exec/lib/exec.js +var __awaiter$3 = function(thisArg, _arguments, P, generator) { + function adopt(value) { + return value instanceof P ? value : new P(function(resolve) { + resolve(value); + }); + } + return new (P || (P = Promise))(function(resolve, reject) { + function fulfilled(value) { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + } + function rejected(value) { + try { + step(generator["throw"](value)); + } catch (e) { + reject(e); + } + } + function step(result) { + result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); + } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +/** +* Exec a command. +* Output will be streamed to the live console. +* Returns promise with return code +* +* @param commandLine command to execute (can include additional args). Must be correctly escaped. +* @param args optional arguments for tool. Escaping is handled by the lib. +* @param options optional exec options. See ExecOptions +* @returns Promise exit code +*/ +function exec(commandLine, args, options) { + return __awaiter$3(this, void 0, void 0, function* () { + const commandArgs = argStringToArray(commandLine); + if (commandArgs.length === 0) throw new Error(`Parameter 'commandLine' cannot be null or empty.`); + const toolPath = commandArgs[0]; + args = commandArgs.slice(1).concat(args || []); + return new ToolRunner(toolPath, args, options).exec(); + }); +} +/** +* Exec a command and get the output. +* Output will be streamed to the live console. +* Returns promise with the exit code and collected stdout and stderr +* +* @param commandLine command to execute (can include additional args). Must be correctly escaped. +* @param args optional arguments for tool. Escaping is handled by the lib. +* @param options optional exec options. See ExecOptions +* @returns Promise exit code, stdout, and stderr +*/ +function getExecOutput(commandLine, args, options) { + return __awaiter$3(this, void 0, void 0, function* () { + var _a, _b; + let stdout = ""; + let stderr = ""; + const stdoutDecoder = new StringDecoder("utf8"); + const stderrDecoder = new StringDecoder("utf8"); + const originalStdoutListener = (_a = options === null || options === void 0 ? void 0 : options.listeners) === null || _a === void 0 ? void 0 : _a.stdout; + const originalStdErrListener = (_b = options === null || options === void 0 ? void 0 : options.listeners) === null || _b === void 0 ? void 0 : _b.stderr; + const stdErrListener = (data) => { + stderr += stderrDecoder.write(data); + if (originalStdErrListener) originalStdErrListener(data); + }; + const stdOutListener = (data) => { + stdout += stdoutDecoder.write(data); + if (originalStdoutListener) originalStdoutListener(data); + }; + const listeners = Object.assign(Object.assign({}, options === null || options === void 0 ? void 0 : options.listeners), { + stdout: stdOutListener, + stderr: stdErrListener + }); + const exitCode = yield exec(commandLine, args, Object.assign(Object.assign({}, options), { listeners })); + stdout += stdoutDecoder.end(); + stderr += stderrDecoder.end(); + return { + exitCode, + stdout, + stderr + }; + }); +} +//#endregion +//#region node_modules/@actions/core/lib/platform.js +var platform_exports = /* @__PURE__ */ __exportAll({ + arch: () => arch, + getDetails: () => getDetails, + isLinux: () => isLinux, + isMacOS: () => isMacOS, + isWindows: () => isWindows, + platform: () => platform +}); +var __awaiter$2 = function(thisArg, _arguments, P, generator) { + function adopt(value) { + return value instanceof P ? value : new P(function(resolve) { + resolve(value); + }); + } + return new (P || (P = Promise))(function(resolve, reject) { + function fulfilled(value) { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + } + function rejected(value) { + try { + step(generator["throw"](value)); + } catch (e) { + reject(e); + } + } + function step(result) { + result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); + } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var getWindowsInfo = () => __awaiter$2(void 0, void 0, void 0, function* () { + const { stdout: version } = yield getExecOutput("powershell -command \"(Get-CimInstance -ClassName Win32_OperatingSystem).Version\"", void 0, { silent: true }); + const { stdout: name } = yield getExecOutput("powershell -command \"(Get-CimInstance -ClassName Win32_OperatingSystem).Caption\"", void 0, { silent: true }); + return { + name: name.trim(), + version: version.trim() + }; +}); +var getMacOsInfo = () => __awaiter$2(void 0, void 0, void 0, function* () { + var _a, _b, _c, _d; + const { stdout } = yield getExecOutput("sw_vers", void 0, { silent: true }); + const version = (_b = (_a = stdout.match(/ProductVersion:\s*(.+)/)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : ""; + return { + name: (_d = (_c = stdout.match(/ProductName:\s*(.+)/)) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : "", + version + }; +}); +var getLinuxInfo = () => __awaiter$2(void 0, void 0, void 0, function* () { + const { stdout } = yield getExecOutput("lsb_release", [ + "-i", + "-r", + "-s" + ], { silent: true }); + const [name, version] = stdout.trim().split("\n"); + return { + name, + version + }; +}); +var platform = os.platform(); +var arch = os.arch(); +var isWindows = platform === "win32"; +var isMacOS = platform === "darwin"; +var isLinux = platform === "linux"; +function getDetails() { + return __awaiter$2(this, void 0, void 0, function* () { + return Object.assign(Object.assign({}, yield isWindows ? getWindowsInfo() : isMacOS ? getMacOsInfo() : getLinuxInfo()), { + platform, + arch, + isWindows, + isMacOS, + isLinux + }); + }); +} +//#endregion +//#region node_modules/@actions/core/lib/core.js +var core_exports = /* @__PURE__ */ __exportAll({ + ExitCode: () => ExitCode, + addPath: () => addPath, + debug: () => debug, + endGroup: () => endGroup, + error: () => error, + exportVariable: () => exportVariable, + getBooleanInput: () => getBooleanInput, + getIDToken: () => getIDToken, + getInput: () => getInput, + getMultilineInput: () => getMultilineInput, + getState: () => getState, + group: () => group, + info: () => info, + isDebug: () => isDebug, + markdownSummary: () => markdownSummary, + notice: () => notice, + platform: () => platform_exports, + saveState: () => saveState, + setCommandEcho: () => setCommandEcho, + setFailed: () => setFailed, + setOutput: () => setOutput, + setSecret: () => setSecret, + startGroup: () => startGroup, + summary: () => summary, + toPlatformPath: () => toPlatformPath, + toPosixPath: () => toPosixPath, + toWin32Path: () => toWin32Path, + warning: () => warning +}); +var __awaiter$1 = function(thisArg, _arguments, P, generator) { + function adopt(value) { + return value instanceof P ? value : new P(function(resolve) { + resolve(value); + }); + } + return new (P || (P = Promise))(function(resolve, reject) { + function fulfilled(value) { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + } + function rejected(value) { + try { + step(generator["throw"](value)); + } catch (e) { + reject(e); + } + } + function step(result) { + result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); + } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +/** +* The code to exit an action +*/ +var ExitCode; +(function(ExitCode) { + /** + * A code indicating that the action was successful + */ + ExitCode[ExitCode["Success"] = 0] = "Success"; + /** + * A code indicating that the action was a failure + */ + ExitCode[ExitCode["Failure"] = 1] = "Failure"; +})(ExitCode || (ExitCode = {})); +/** +* Sets env variable for this action and future actions in the job +* @param name the name of the variable to set +* @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify +*/ +function exportVariable(name, val) { + const convertedVal = toCommandValue(val); + process.env[name] = convertedVal; + if (process.env["GITHUB_ENV"] || "") return issueFileCommand("ENV", prepareKeyValueMessage(name, val)); + issueCommand("set-env", { name }, convertedVal); +} +/** +* Registers a secret which will get masked from logs +* +* @param secret - Value of the secret to be masked +* @remarks +* This function instructs the Actions runner to mask the specified value in any +* logs produced during the workflow run. Once registered, the secret value will +* be replaced with asterisks (***) whenever it appears in console output, logs, +* or error messages. +* +* This is useful for protecting sensitive information such as: +* - API keys +* - Access tokens +* - Authentication credentials +* - URL parameters containing signatures (SAS tokens) +* +* Note that masking only affects future logs; any previous appearances of the +* secret in logs before calling this function will remain unmasked. +* +* @example +* ```typescript +* // Register an API token as a secret +* const apiToken = "abc123xyz456"; +* setSecret(apiToken); +* +* // Now any logs containing this value will show *** instead +* console.log(`Using token: ${apiToken}`); // Outputs: "Using token: ***" +* ``` +*/ +function setSecret(secret) { + issueCommand("add-mask", {}, secret); +} +/** +* Prepends inputPath to the PATH (for this action and future actions) +* @param inputPath +*/ +function addPath(inputPath) { + if (process.env["GITHUB_PATH"] || "") issueFileCommand("PATH", inputPath); + else issueCommand("add-path", {}, inputPath); + process.env["PATH"] = `${inputPath}${path$1.delimiter}${process.env["PATH"]}`; +} +/** +* Gets the value of an input. +* Unless trimWhitespace is set to false in InputOptions, the value is also trimmed. +* Returns an empty string if the value is not defined. +* +* @param name name of the input to get +* @param options optional. See InputOptions. +* @returns string +*/ +function getInput(name, options) { + const val = process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] || ""; + if (options && options.required && !val) throw new Error(`Input required and not supplied: ${name}`); + if (options && options.trimWhitespace === false) return val; + return val.trim(); +} +/** +* Gets the values of an multiline input. Each value is also trimmed. +* +* @param name name of the input to get +* @param options optional. See InputOptions. +* @returns string[] +* +*/ +function getMultilineInput(name, options) { + const inputs = getInput(name, options).split("\n").filter((x) => x !== ""); + if (options && options.trimWhitespace === false) return inputs; + return inputs.map((input) => input.trim()); +} +/** +* Gets the input value of the boolean type in the YAML 1.2 "core schema" specification. +* Support boolean input list: `true | True | TRUE | false | False | FALSE` . +* The return value is also in boolean type. +* ref: https://yaml.org/spec/1.2/spec.html#id2804923 +* +* @param name name of the input to get +* @param options optional. See InputOptions. +* @returns boolean +*/ +function getBooleanInput(name, options) { + const trueValue = [ + "true", + "True", + "TRUE" + ]; + const falseValue = [ + "false", + "False", + "FALSE" + ]; + const val = getInput(name, options); + if (trueValue.includes(val)) return true; + if (falseValue.includes(val)) return false; + throw new TypeError(`Input does not meet YAML 1.2 "Core Schema" specification: ${name}\nSupport boolean input list: \`true | True | TRUE | false | False | FALSE\``); +} +/** +* Sets the value of an output. +* +* @param name name of the output to set +* @param value value to store. Non-string values will be converted to a string via JSON.stringify +*/ +function setOutput(name, value) { + if (process.env["GITHUB_OUTPUT"] || "") return issueFileCommand("OUTPUT", prepareKeyValueMessage(name, value)); + process.stdout.write(os$1.EOL); + issueCommand("set-output", { name }, toCommandValue(value)); +} +/** +* Enables or disables the echoing of commands into stdout for the rest of the step. +* Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set. +* +*/ +function setCommandEcho(enabled) { + issue$1("echo", enabled ? "on" : "off"); +} +/** +* Sets the action status to failed. +* When the action exits it will be with an exit code of 1 +* @param message add error issue message +*/ +function setFailed(message) { + process.exitCode = ExitCode.Failure; + error(message); +} +/** +* Gets whether Actions Step Debug is on or not +*/ +function isDebug() { + return process.env["RUNNER_DEBUG"] === "1"; +} +/** +* Writes debug message to user log +* @param message debug message +*/ +function debug(message) { + issueCommand("debug", {}, message); +} +/** +* Adds an error issue +* @param message error issue message. Errors will be converted to string via toString() +* @param properties optional properties to add to the annotation. +*/ +function error(message, properties = {}) { + issueCommand("error", toCommandProperties(properties), message instanceof Error ? message.toString() : message); +} +/** +* Adds a warning issue +* @param message warning issue message. Errors will be converted to string via toString() +* @param properties optional properties to add to the annotation. +*/ +function warning(message, properties = {}) { + issueCommand("warning", toCommandProperties(properties), message instanceof Error ? message.toString() : message); +} +/** +* Adds a notice issue +* @param message notice issue message. Errors will be converted to string via toString() +* @param properties optional properties to add to the annotation. +*/ +function notice(message, properties = {}) { + issueCommand("notice", toCommandProperties(properties), message instanceof Error ? message.toString() : message); +} +/** +* Writes info to log with console.log. +* @param message info message +*/ +function info(message) { + process.stdout.write(message + os$1.EOL); +} +/** +* Begin an output group. +* +* Output until the next `groupEnd` will be foldable in this group +* +* @param name The name of the output group +*/ +function startGroup(name) { + issue$1("group", name); +} +/** +* End an output group. +*/ +function endGroup() { + issue$1("endgroup"); +} +/** +* Wrap an asynchronous function call in a group. +* +* Returns the same type as the function itself. +* +* @param name The name of the group +* @param fn The function to wrap in the group +*/ +function group(name, fn) { + return __awaiter$1(this, void 0, void 0, function* () { + startGroup(name); + let result; + try { + result = yield fn(); + } finally { + endGroup(); + } + return result; + }); +} +/** +* Saves state for current action, the state can only be retrieved by this action's post job execution. +* +* @param name name of the state to store +* @param value value to store. Non-string values will be converted to a string via JSON.stringify +*/ +function saveState(name, value) { + if (process.env["GITHUB_STATE"] || "") return issueFileCommand("STATE", prepareKeyValueMessage(name, value)); + issueCommand("save-state", { name }, toCommandValue(value)); +} +/** +* Gets the value of an state set by this action's main execution. +* +* @param name name of the state to get +* @returns string +*/ +function getState(name) { + return process.env[`STATE_${name}`] || ""; +} +function getIDToken(aud) { + return __awaiter$1(this, void 0, void 0, function* () { + return yield OidcClient.getIDToken(aud); + }); +} +//#endregion +//#region node_modules/zod/v4/core/core.js +var _a$1; +function $constructor(name, initializer, params) { + function init(inst, def) { + if (!inst._zod) Object.defineProperty(inst, "_zod", { + value: { + def, + constr: _, + traits: /* @__PURE__ */ new Set() + }, + enumerable: false + }); + if (inst._zod.traits.has(name)) return; + inst._zod.traits.add(name); + initializer(inst, def); + const proto = _.prototype; + const keys = Object.keys(proto); + for (let i = 0; i < keys.length; i++) { + const k = keys[i]; + if (!(k in inst)) inst[k] = proto[k].bind(inst); + } + } + const Parent = params?.Parent ?? Object; + class Definition extends Parent {} + Object.defineProperty(Definition, "name", { value: name }); + function _(def) { + var _a; + const inst = params?.Parent ? new Definition() : this; + init(inst, def); + (_a = inst._zod).deferred ?? (_a.deferred = []); + for (const fn of inst._zod.deferred) fn(); + return inst; + } + Object.defineProperty(_, "init", { value: init }); + Object.defineProperty(_, Symbol.hasInstance, { value: (inst) => { + if (params?.Parent && inst instanceof params.Parent) return true; + return inst?._zod?.traits?.has(name); + } }); + Object.defineProperty(_, "name", { value: name }); + return _; +} +var $ZodAsyncError = class extends Error { + constructor() { + super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`); + } +}; +var $ZodEncodeError = class extends Error { + constructor(name) { + super(`Encountered unidirectional transform during encode: ${name}`); + this.name = "ZodEncodeError"; + } +}; +(_a$1 = globalThis).__zod_globalConfig ?? (_a$1.__zod_globalConfig = {}); +var globalConfig = globalThis.__zod_globalConfig; +function config(newConfig) { + if (newConfig) Object.assign(globalConfig, newConfig); + return globalConfig; +} +//#endregion +//#region node_modules/zod/v4/core/util.js +function getEnumValues(entries) { + const numericValues = Object.values(entries).filter((v) => typeof v === "number"); + return Object.entries(entries).filter(([k, _]) => numericValues.indexOf(+k) === -1).map(([_, v]) => v); +} +function jsonStringifyReplacer(_, value) { + if (typeof value === "bigint") return value.toString(); + return value; +} +function cached(getter) { + return { get value() { + { + const value = getter(); + Object.defineProperty(this, "value", { value }); + return value; + } + throw new Error("cached value already set"); + } }; +} +function nullish(input) { + return input === null || input === void 0; +} +function cleanRegex(source) { + const start = source.startsWith("^") ? 1 : 0; + const end = source.endsWith("$") ? source.length - 1 : source.length; + return source.slice(start, end); +} +function floatSafeRemainder(val, step) { + const ratio = val / step; + const roundedRatio = Math.round(ratio); + const tolerance = Number.EPSILON * Math.max(Math.abs(ratio), 1); + if (Math.abs(ratio - roundedRatio) < tolerance) return 0; + return ratio - roundedRatio; +} +var EVALUATING = /* @__PURE__*/ Symbol("evaluating"); +function defineLazy(object, key, getter) { + let value = void 0; + Object.defineProperty(object, key, { + get() { + if (value === EVALUATING) return; + if (value === void 0) { + value = EVALUATING; + value = getter(); + } + return value; + }, + set(v) { + Object.defineProperty(object, key, { value: v }); + }, + configurable: true + }); +} +function assignProp(target, prop, value) { + Object.defineProperty(target, prop, { + value, + writable: true, + enumerable: true, + configurable: true + }); +} +function mergeDefs(...defs) { + const mergedDescriptors = {}; + for (const def of defs) { + const descriptors = Object.getOwnPropertyDescriptors(def); + Object.assign(mergedDescriptors, descriptors); + } + return Object.defineProperties({}, mergedDescriptors); +} +function esc(str) { + return JSON.stringify(str); +} +function slugify(input) { + return input.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, ""); +} +var captureStackTrace = "captureStackTrace" in Error ? Error.captureStackTrace : (..._args) => {}; +function isObject$1(data) { + return typeof data === "object" && data !== null && !Array.isArray(data); +} +var allowsEval = /* @__PURE__*/ cached(() => { + if (globalConfig.jitless) return false; + if (typeof navigator !== "undefined" && navigator?.userAgent?.includes("Cloudflare")) return false; + try { + new Function(""); + return true; + } catch (_) { + return false; + } +}); +function isPlainObject$2(o) { + if (isObject$1(o) === false) return false; + const ctor = o.constructor; + if (ctor === void 0) return true; + if (typeof ctor !== "function") return true; + const prot = ctor.prototype; + if (isObject$1(prot) === false) return false; + if (Object.prototype.hasOwnProperty.call(prot, "isPrototypeOf") === false) return false; + return true; +} +function shallowClone(o) { + if (isPlainObject$2(o)) return { ...o }; + if (Array.isArray(o)) return [...o]; + if (o instanceof Map) return new Map(o); + if (o instanceof Set) return new Set(o); + return o; +} +var propertyKeyTypes = /* @__PURE__*/ new Set([ + "string", + "number", + "symbol" +]); +function escapeRegex(str) { + return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} +function clone(inst, def, params) { + const cl = new inst._zod.constr(def ?? inst._zod.def); + if (!def || params?.parent) cl._zod.parent = inst; + return cl; +} +function normalizeParams(_params) { + const params = _params; + if (!params) return {}; + if (typeof params === "string") return { error: () => params }; + if (params?.message !== void 0) { + if (params?.error !== void 0) throw new Error("Cannot specify both `message` and `error` params"); + params.error = params.message; + } + delete params.message; + if (typeof params.error === "string") return { + ...params, + error: () => params.error + }; + return params; +} +function optionalKeys(shape) { + return Object.keys(shape).filter((k) => { + return shape[k]._zod.optin === "optional" && shape[k]._zod.optout === "optional"; + }); +} +var NUMBER_FORMAT_RANGES = { + safeint: [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER], + int32: [-2147483648, 2147483647], + uint32: [0, 4294967295], + float32: [-34028234663852886e22, 34028234663852886e22], + float64: [-Number.MAX_VALUE, Number.MAX_VALUE] +}; +function pick(schema, mask) { + const currDef = schema._zod.def; + const checks = currDef.checks; + if (checks && checks.length > 0) throw new Error(".pick() cannot be used on object schemas containing refinements"); + return clone(schema, mergeDefs(schema._zod.def, { + get shape() { + const newShape = {}; + for (const key in mask) { + if (!(key in currDef.shape)) throw new Error(`Unrecognized key: "${key}"`); + if (!mask[key]) continue; + newShape[key] = currDef.shape[key]; + } + assignProp(this, "shape", newShape); + return newShape; + }, + checks: [] + })); +} +function omit$1(schema, mask) { + const currDef = schema._zod.def; + const checks = currDef.checks; + if (checks && checks.length > 0) throw new Error(".omit() cannot be used on object schemas containing refinements"); + return clone(schema, mergeDefs(schema._zod.def, { + get shape() { + const newShape = { ...schema._zod.def.shape }; + for (const key in mask) { + if (!(key in currDef.shape)) throw new Error(`Unrecognized key: "${key}"`); + if (!mask[key]) continue; + delete newShape[key]; + } + assignProp(this, "shape", newShape); + return newShape; + }, + checks: [] + })); +} +function extend(schema, shape) { + if (!isPlainObject$2(shape)) throw new Error("Invalid input to extend: expected a plain object"); + const checks = schema._zod.def.checks; + if (checks && checks.length > 0) { + const existingShape = schema._zod.def.shape; + for (const key in shape) if (Object.getOwnPropertyDescriptor(existingShape, key) !== void 0) throw new Error("Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead."); + } + return clone(schema, mergeDefs(schema._zod.def, { get shape() { + const _shape = { + ...schema._zod.def.shape, + ...shape + }; + assignProp(this, "shape", _shape); + return _shape; + } })); +} +function safeExtend(schema, shape) { + if (!isPlainObject$2(shape)) throw new Error("Invalid input to safeExtend: expected a plain object"); + return clone(schema, mergeDefs(schema._zod.def, { get shape() { + const _shape = { + ...schema._zod.def.shape, + ...shape + }; + assignProp(this, "shape", _shape); + return _shape; + } })); +} +function merge$2(a, b) { + if (a._zod.def.checks?.length) throw new Error(".merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead."); + return clone(a, mergeDefs(a._zod.def, { + get shape() { + const _shape = { + ...a._zod.def.shape, + ...b._zod.def.shape + }; + assignProp(this, "shape", _shape); + return _shape; + }, + get catchall() { + return b._zod.def.catchall; + }, + checks: b._zod.def.checks ?? [] + })); +} +function partial(Class, schema, mask) { + const checks = schema._zod.def.checks; + if (checks && checks.length > 0) throw new Error(".partial() cannot be used on object schemas containing refinements"); + return clone(schema, mergeDefs(schema._zod.def, { + get shape() { + const oldShape = schema._zod.def.shape; + const shape = { ...oldShape }; + if (mask) for (const key in mask) { + if (!(key in oldShape)) throw new Error(`Unrecognized key: "${key}"`); + if (!mask[key]) continue; + shape[key] = Class ? new Class({ + type: "optional", + innerType: oldShape[key] + }) : oldShape[key]; + } + else for (const key in oldShape) shape[key] = Class ? new Class({ + type: "optional", + innerType: oldShape[key] + }) : oldShape[key]; + assignProp(this, "shape", shape); + return shape; + }, + checks: [] + })); +} +function required(Class, schema, mask) { + return clone(schema, mergeDefs(schema._zod.def, { get shape() { + const oldShape = schema._zod.def.shape; + const shape = { ...oldShape }; + if (mask) for (const key in mask) { + if (!(key in shape)) throw new Error(`Unrecognized key: "${key}"`); + if (!mask[key]) continue; + shape[key] = new Class({ + type: "nonoptional", + innerType: oldShape[key] + }); + } + else for (const key in oldShape) shape[key] = new Class({ + type: "nonoptional", + innerType: oldShape[key] + }); + assignProp(this, "shape", shape); + return shape; + } })); +} +function aborted(x, startIndex = 0) { + if (x.aborted === true) return true; + for (let i = startIndex; i < x.issues.length; i++) if (x.issues[i]?.continue !== true) return true; + return false; +} +function explicitlyAborted(x, startIndex = 0) { + if (x.aborted === true) return true; + for (let i = startIndex; i < x.issues.length; i++) if (x.issues[i]?.continue === false) return true; + return false; +} +function prefixIssues(path, issues) { + return issues.map((iss) => { + var _a; + (_a = iss).path ?? (_a.path = []); + iss.path.unshift(path); + return iss; + }); +} +function unwrapMessage(message) { + return typeof message === "string" ? message : message?.message; +} +function finalizeIssue(iss, ctx, config) { + const message = iss.message ? iss.message : unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config.customError?.(iss)) ?? unwrapMessage(config.localeError?.(iss)) ?? "Invalid input"; + const { inst: _inst, continue: _continue, input: _input, ...rest } = iss; + rest.path ?? (rest.path = []); + rest.message = message; + if (ctx?.reportInput) rest.input = _input; + return rest; +} +function getLengthableOrigin(input) { + if (Array.isArray(input)) return "array"; + if (typeof input === "string") return "string"; + return "unknown"; +} +function issue(...args) { + const [iss, input, inst] = args; + if (typeof iss === "string") return { + message: iss, + code: "custom", + input, + inst + }; + return { ...iss }; +} +//#endregion +//#region node_modules/zod/v4/core/errors.js +var initializer$1 = (inst, def) => { + inst.name = "$ZodError"; + Object.defineProperty(inst, "_zod", { + value: inst._zod, + enumerable: false + }); + Object.defineProperty(inst, "issues", { + value: def, + enumerable: false + }); + inst.message = JSON.stringify(def, jsonStringifyReplacer, 2); + Object.defineProperty(inst, "toString", { + value: () => inst.message, + enumerable: false + }); +}; +var $ZodError = $constructor("$ZodError", initializer$1); +var $ZodRealError = $constructor("$ZodError", initializer$1, { Parent: Error }); +function flattenError(error, mapper = (issue) => issue.message) { + const fieldErrors = {}; + const formErrors = []; + for (const sub of error.issues) if (sub.path.length > 0) { + fieldErrors[sub.path[0]] = fieldErrors[sub.path[0]] || []; + fieldErrors[sub.path[0]].push(mapper(sub)); + } else formErrors.push(mapper(sub)); + return { + formErrors, + fieldErrors + }; +} +function formatError(error, mapper = (issue) => issue.message) { + const fieldErrors = { _errors: [] }; + const processError = (error, path = []) => { + for (const issue of error.issues) if (issue.code === "invalid_union" && issue.errors.length) issue.errors.map((issues) => processError({ issues }, [...path, ...issue.path])); + else if (issue.code === "invalid_key") processError({ issues: issue.issues }, [...path, ...issue.path]); + else if (issue.code === "invalid_element") processError({ issues: issue.issues }, [...path, ...issue.path]); + else { + const fullpath = [...path, ...issue.path]; + if (fullpath.length === 0) fieldErrors._errors.push(mapper(issue)); + else { + let curr = fieldErrors; + let i = 0; + while (i < fullpath.length) { + const el = fullpath[i]; + if (!(i === fullpath.length - 1)) curr[el] = curr[el] || { _errors: [] }; + else { + curr[el] = curr[el] || { _errors: [] }; + curr[el]._errors.push(mapper(issue)); + } + curr = curr[el]; + i++; + } + } + } + }; + processError(error); + return fieldErrors; +} +/** Format a ZodError as a human-readable string in the following form. +* +* From +* +* ```ts +* ZodError { +* issues: [ +* { +* expected: 'string', +* code: 'invalid_type', +* path: [ 'username' ], +* message: 'Invalid input: expected string' +* }, +* { +* expected: 'number', +* code: 'invalid_type', +* path: [ 'favoriteNumbers', 1 ], +* message: 'Invalid input: expected number' +* } +* ]; +* } +* ``` +* +* to +* +* ``` +* username +* ✖ Expected number, received string at "username +* favoriteNumbers[0] +* ✖ Invalid input: expected number +* ``` +*/ +function toDotPath(_path) { + const segs = []; + const path = _path.map((seg) => typeof seg === "object" ? seg.key : seg); + for (const seg of path) if (typeof seg === "number") segs.push(`[${seg}]`); + else if (typeof seg === "symbol") segs.push(`[${JSON.stringify(String(seg))}]`); + else if (/[^\w$]/.test(seg)) segs.push(`[${JSON.stringify(seg)}]`); + else { + if (segs.length) segs.push("."); + segs.push(seg); + } + return segs.join(""); +} +function prettifyError$1(error) { + const lines = []; + const issues = [...error.issues].sort((a, b) => (a.path ?? []).length - (b.path ?? []).length); + for (const issue of issues) { + lines.push(`✖ ${issue.message}`); + if (issue.path?.length) lines.push(` → at ${toDotPath(issue.path)}`); + } + return lines.join("\n"); +} +//#endregion +//#region node_modules/zod/v4/core/parse.js +var _parse = (_Err) => (schema, value, _ctx, _params) => { + const ctx = _ctx ? { + ..._ctx, + async: false + } : { async: false }; + const result = schema._zod.run({ + value, + issues: [] + }, ctx); + if (result instanceof Promise) throw new $ZodAsyncError(); + if (result.issues.length) { + const e = new ((_params?.Err) ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config()))); + captureStackTrace(e, _params?.callee); + throw e; + } + return result.value; +}; +var _parseAsync = (_Err) => async (schema, value, _ctx, params) => { + const ctx = _ctx ? { + ..._ctx, + async: true + } : { async: true }; + let result = schema._zod.run({ + value, + issues: [] + }, ctx); + if (result instanceof Promise) result = await result; + if (result.issues.length) { + const e = new ((params?.Err) ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config()))); + captureStackTrace(e, params?.callee); + throw e; + } + return result.value; +}; +var _safeParse = (_Err) => (schema, value, _ctx) => { + const ctx = _ctx ? { + ..._ctx, + async: false + } : { async: false }; + const result = schema._zod.run({ + value, + issues: [] + }, ctx); + if (result instanceof Promise) throw new $ZodAsyncError(); + return result.issues.length ? { + success: false, + error: new (_Err ?? $ZodError)(result.issues.map((iss) => finalizeIssue(iss, ctx, config()))) + } : { + success: true, + data: result.value + }; +}; +var safeParse$2 = /* @__PURE__*/ _safeParse($ZodRealError); +var _safeParseAsync = (_Err) => async (schema, value, _ctx) => { + const ctx = _ctx ? { + ..._ctx, + async: true + } : { async: true }; + let result = schema._zod.run({ + value, + issues: [] + }, ctx); + if (result instanceof Promise) result = await result; + return result.issues.length ? { + success: false, + error: new _Err(result.issues.map((iss) => finalizeIssue(iss, ctx, config()))) + } : { + success: true, + data: result.value + }; +}; +var safeParseAsync$1 = /* @__PURE__*/ _safeParseAsync($ZodRealError); +var _encode = (_Err) => (schema, value, _ctx) => { + const ctx = _ctx ? { + ..._ctx, + direction: "backward" + } : { direction: "backward" }; + return _parse(_Err)(schema, value, ctx); +}; +var _decode = (_Err) => (schema, value, _ctx) => { + return _parse(_Err)(schema, value, _ctx); +}; +var _encodeAsync = (_Err) => async (schema, value, _ctx) => { + const ctx = _ctx ? { + ..._ctx, + direction: "backward" + } : { direction: "backward" }; + return _parseAsync(_Err)(schema, value, ctx); +}; +var _decodeAsync = (_Err) => async (schema, value, _ctx) => { + return _parseAsync(_Err)(schema, value, _ctx); +}; +var _safeEncode = (_Err) => (schema, value, _ctx) => { + const ctx = _ctx ? { + ..._ctx, + direction: "backward" + } : { direction: "backward" }; + return _safeParse(_Err)(schema, value, ctx); +}; +var _safeDecode = (_Err) => (schema, value, _ctx) => { + return _safeParse(_Err)(schema, value, _ctx); +}; +var _safeEncodeAsync = (_Err) => async (schema, value, _ctx) => { + const ctx = _ctx ? { + ..._ctx, + direction: "backward" + } : { direction: "backward" }; + return _safeParseAsync(_Err)(schema, value, ctx); +}; +var _safeDecodeAsync = (_Err) => async (schema, value, _ctx) => { + return _safeParseAsync(_Err)(schema, value, _ctx); +}; +//#endregion +//#region node_modules/zod/v4/core/regexes.js +/** +* @deprecated CUID v1 is deprecated by its authors due to information leakage +* (timestamps embedded in the id). Use {@link cuid2} instead. +* See https://github.com/paralleldrive/cuid. +*/ +var cuid = /^[cC][0-9a-z]{6,}$/; +var cuid2 = /^[0-9a-z]+$/; +var ulid = /^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/; +var xid = /^[0-9a-vA-V]{20}$/; +var ksuid = /^[A-Za-z0-9]{27}$/; +var nanoid = /^[a-zA-Z0-9_-]{21}$/; +/** ISO 8601-1 duration regex. Does not support the 8601-2 extensions like negative durations or fractional/negative components. */ +var duration$1 = /^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/; +/** A regex for any UUID-like identifier: 8-4-4-4-12 hex pattern */ +var guid = /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/; +/** Returns a regex for validating an RFC 9562/4122 UUID. +* +* @param version Optionally specify a version 1-8. If no version is specified, all versions are supported. */ +var uuid = (version) => { + if (!version) return /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/; + return new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${version}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`); +}; +/** Practical email validation */ +var email = /^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/; +var _emoji$1 = `^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$`; +function emoji() { + return new RegExp(_emoji$1, "u"); +} +var ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/; +var ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/; +var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/; +var cidrv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/; +var base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/; +var base64url = /^[A-Za-z0-9_-]*$/; +var httpProtocol = /^https?$/; +var e164 = /^\+[1-9]\d{6,14}$/; +var dateSource = `(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))`; +var date$1 = /*@__PURE__*/ new RegExp(`^${dateSource}$`); +function timeSource(args) { + const hhmm = `(?:[01]\\d|2[0-3]):[0-5]\\d`; + return typeof args.precision === "number" ? args.precision === -1 ? `${hhmm}` : args.precision === 0 ? `${hhmm}:[0-5]\\d` : `${hhmm}:[0-5]\\d\\.\\d{${args.precision}}` : `${hhmm}(?::[0-5]\\d(?:\\.\\d+)?)?`; +} +function time$1(args) { + return new RegExp(`^${timeSource(args)}$`); +} +function datetime$1(args) { + const time = timeSource({ precision: args.precision }); + const opts = ["Z"]; + if (args.local) opts.push(""); + if (args.offset) opts.push(`([+-](?:[01]\\d|2[0-3]):[0-5]\\d)`); + const timeRegex = `${time}(?:${opts.join("|")})`; + return new RegExp(`^${dateSource}T(?:${timeRegex})$`); +} +var string$2 = (params) => { + const regex = params ? `[\\s\\S]{${params?.minimum ?? 0},${params?.maximum ?? ""}}` : `[\\s\\S]*`; + return new RegExp(`^${regex}$`); +}; +var integer = /^-?\d+$/; +var number$1 = /^-?\d+(?:\.\d+)?$/; +var boolean$1 = /^(?:true|false)$/i; +var _null$2 = /^null$/i; +var lowercase = /^[^A-Z]*$/; +var uppercase = /^[^a-z]*$/; +//#endregion +//#region node_modules/zod/v4/core/checks.js +var $ZodCheck = /*@__PURE__*/ $constructor("$ZodCheck", (inst, def) => { + var _a; + inst._zod ?? (inst._zod = {}); + inst._zod.def = def; + (_a = inst._zod).onattach ?? (_a.onattach = []); +}); +var numericOriginMap = { + number: "number", + bigint: "bigint", + object: "date" +}; +var $ZodCheckLessThan = /*@__PURE__*/ $constructor("$ZodCheckLessThan", (inst, def) => { + $ZodCheck.init(inst, def); + const origin = numericOriginMap[typeof def.value]; + inst._zod.onattach.push((inst) => { + const bag = inst._zod.bag; + const curr = (def.inclusive ? bag.maximum : bag.exclusiveMaximum) ?? Number.POSITIVE_INFINITY; + if (def.value < curr) if (def.inclusive) bag.maximum = def.value; + else bag.exclusiveMaximum = def.value; + }); + inst._zod.check = (payload) => { + if (def.inclusive ? payload.value <= def.value : payload.value < def.value) return; + payload.issues.push({ + origin, + code: "too_big", + maximum: typeof def.value === "object" ? def.value.getTime() : def.value, + input: payload.value, + inclusive: def.inclusive, + inst, + continue: !def.abort + }); + }; +}); +var $ZodCheckGreaterThan = /*@__PURE__*/ $constructor("$ZodCheckGreaterThan", (inst, def) => { + $ZodCheck.init(inst, def); + const origin = numericOriginMap[typeof def.value]; + inst._zod.onattach.push((inst) => { + const bag = inst._zod.bag; + const curr = (def.inclusive ? bag.minimum : bag.exclusiveMinimum) ?? Number.NEGATIVE_INFINITY; + if (def.value > curr) if (def.inclusive) bag.minimum = def.value; + else bag.exclusiveMinimum = def.value; + }); + inst._zod.check = (payload) => { + if (def.inclusive ? payload.value >= def.value : payload.value > def.value) return; + payload.issues.push({ + origin, + code: "too_small", + minimum: typeof def.value === "object" ? def.value.getTime() : def.value, + input: payload.value, + inclusive: def.inclusive, + inst, + continue: !def.abort + }); + }; +}); +var $ZodCheckMultipleOf = /*@__PURE__*/ $constructor("$ZodCheckMultipleOf", (inst, def) => { + $ZodCheck.init(inst, def); + inst._zod.onattach.push((inst) => { + var _a; + (_a = inst._zod.bag).multipleOf ?? (_a.multipleOf = def.value); + }); + inst._zod.check = (payload) => { + if (typeof payload.value !== typeof def.value) throw new Error("Cannot mix number and bigint in multiple_of check."); + if (typeof payload.value === "bigint" ? payload.value % def.value === BigInt(0) : floatSafeRemainder(payload.value, def.value) === 0) return; + payload.issues.push({ + origin: typeof payload.value, + code: "not_multiple_of", + divisor: def.value, + input: payload.value, + inst, + continue: !def.abort + }); + }; +}); +var $ZodCheckNumberFormat = /*@__PURE__*/ $constructor("$ZodCheckNumberFormat", (inst, def) => { + $ZodCheck.init(inst, def); + def.format = def.format || "float64"; + const isInt = def.format?.includes("int"); + const origin = isInt ? "int" : "number"; + const [minimum, maximum] = NUMBER_FORMAT_RANGES[def.format]; + inst._zod.onattach.push((inst) => { + const bag = inst._zod.bag; + bag.format = def.format; + bag.minimum = minimum; + bag.maximum = maximum; + if (isInt) bag.pattern = integer; + }); + inst._zod.check = (payload) => { + const input = payload.value; + if (isInt) { + if (!Number.isInteger(input)) { + payload.issues.push({ + expected: origin, + format: def.format, + code: "invalid_type", + continue: false, + input, + inst + }); + return; + } + if (!Number.isSafeInteger(input)) { + if (input > 0) payload.issues.push({ + input, + code: "too_big", + maximum: Number.MAX_SAFE_INTEGER, + note: "Integers must be within the safe integer range.", + inst, + origin, + inclusive: true, + continue: !def.abort + }); + else payload.issues.push({ + input, + code: "too_small", + minimum: Number.MIN_SAFE_INTEGER, + note: "Integers must be within the safe integer range.", + inst, + origin, + inclusive: true, + continue: !def.abort + }); + return; + } + } + if (input < minimum) payload.issues.push({ + origin: "number", + input, + code: "too_small", + minimum, + inclusive: true, + inst, + continue: !def.abort + }); + if (input > maximum) payload.issues.push({ + origin: "number", + input, + code: "too_big", + maximum, + inclusive: true, + inst, + continue: !def.abort + }); + }; +}); +var $ZodCheckMaxLength = /*@__PURE__*/ $constructor("$ZodCheckMaxLength", (inst, def) => { + var _a; + $ZodCheck.init(inst, def); + (_a = inst._zod.def).when ?? (_a.when = (payload) => { + const val = payload.value; + return !nullish(val) && val.length !== void 0; + }); + inst._zod.onattach.push((inst) => { + const curr = inst._zod.bag.maximum ?? Number.POSITIVE_INFINITY; + if (def.maximum < curr) inst._zod.bag.maximum = def.maximum; + }); + inst._zod.check = (payload) => { + const input = payload.value; + if (input.length <= def.maximum) return; + const origin = getLengthableOrigin(input); + payload.issues.push({ + origin, + code: "too_big", + maximum: def.maximum, + inclusive: true, + input, + inst, + continue: !def.abort + }); + }; +}); +var $ZodCheckMinLength = /*@__PURE__*/ $constructor("$ZodCheckMinLength", (inst, def) => { + var _a; + $ZodCheck.init(inst, def); + (_a = inst._zod.def).when ?? (_a.when = (payload) => { + const val = payload.value; + return !nullish(val) && val.length !== void 0; + }); + inst._zod.onattach.push((inst) => { + const curr = inst._zod.bag.minimum ?? Number.NEGATIVE_INFINITY; + if (def.minimum > curr) inst._zod.bag.minimum = def.minimum; + }); + inst._zod.check = (payload) => { + const input = payload.value; + if (input.length >= def.minimum) return; + const origin = getLengthableOrigin(input); + payload.issues.push({ + origin, + code: "too_small", + minimum: def.minimum, + inclusive: true, + input, + inst, + continue: !def.abort + }); + }; +}); +var $ZodCheckLengthEquals = /*@__PURE__*/ $constructor("$ZodCheckLengthEquals", (inst, def) => { + var _a; + $ZodCheck.init(inst, def); + (_a = inst._zod.def).when ?? (_a.when = (payload) => { + const val = payload.value; + return !nullish(val) && val.length !== void 0; + }); + inst._zod.onattach.push((inst) => { + const bag = inst._zod.bag; + bag.minimum = def.length; + bag.maximum = def.length; + bag.length = def.length; + }); + inst._zod.check = (payload) => { + const input = payload.value; + const length = input.length; + if (length === def.length) return; + const origin = getLengthableOrigin(input); + const tooBig = length > def.length; + payload.issues.push({ + origin, + ...tooBig ? { + code: "too_big", + maximum: def.length + } : { + code: "too_small", + minimum: def.length + }, + inclusive: true, + exact: true, + input: payload.value, + inst, + continue: !def.abort + }); + }; +}); +var $ZodCheckStringFormat = /*@__PURE__*/ $constructor("$ZodCheckStringFormat", (inst, def) => { + var _a, _b; + $ZodCheck.init(inst, def); + inst._zod.onattach.push((inst) => { + const bag = inst._zod.bag; + bag.format = def.format; + if (def.pattern) { + bag.patterns ?? (bag.patterns = /* @__PURE__ */ new Set()); + bag.patterns.add(def.pattern); + } + }); + if (def.pattern) (_a = inst._zod).check ?? (_a.check = (payload) => { + def.pattern.lastIndex = 0; + if (def.pattern.test(payload.value)) return; + payload.issues.push({ + origin: "string", + code: "invalid_format", + format: def.format, + input: payload.value, + ...def.pattern ? { pattern: def.pattern.toString() } : {}, + inst, + continue: !def.abort + }); + }); + else (_b = inst._zod).check ?? (_b.check = () => {}); +}); +var $ZodCheckRegex = /*@__PURE__*/ $constructor("$ZodCheckRegex", (inst, def) => { + $ZodCheckStringFormat.init(inst, def); + inst._zod.check = (payload) => { + def.pattern.lastIndex = 0; + if (def.pattern.test(payload.value)) return; + payload.issues.push({ + origin: "string", + code: "invalid_format", + format: "regex", + input: payload.value, + pattern: def.pattern.toString(), + inst, + continue: !def.abort + }); + }; +}); +var $ZodCheckLowerCase = /*@__PURE__*/ $constructor("$ZodCheckLowerCase", (inst, def) => { + def.pattern ?? (def.pattern = lowercase); + $ZodCheckStringFormat.init(inst, def); +}); +var $ZodCheckUpperCase = /*@__PURE__*/ $constructor("$ZodCheckUpperCase", (inst, def) => { + def.pattern ?? (def.pattern = uppercase); + $ZodCheckStringFormat.init(inst, def); +}); +var $ZodCheckIncludes = /*@__PURE__*/ $constructor("$ZodCheckIncludes", (inst, def) => { + $ZodCheck.init(inst, def); + const escapedRegex = escapeRegex(def.includes); + const pattern = new RegExp(typeof def.position === "number" ? `^.{${def.position}}${escapedRegex}` : escapedRegex); + def.pattern = pattern; + inst._zod.onattach.push((inst) => { + const bag = inst._zod.bag; + bag.patterns ?? (bag.patterns = /* @__PURE__ */ new Set()); + bag.patterns.add(pattern); + }); + inst._zod.check = (payload) => { + if (payload.value.includes(def.includes, def.position)) return; + payload.issues.push({ + origin: "string", + code: "invalid_format", + format: "includes", + includes: def.includes, + input: payload.value, + inst, + continue: !def.abort + }); + }; +}); +var $ZodCheckStartsWith = /*@__PURE__*/ $constructor("$ZodCheckStartsWith", (inst, def) => { + $ZodCheck.init(inst, def); + const pattern = new RegExp(`^${escapeRegex(def.prefix)}.*`); + def.pattern ?? (def.pattern = pattern); + inst._zod.onattach.push((inst) => { + const bag = inst._zod.bag; + bag.patterns ?? (bag.patterns = /* @__PURE__ */ new Set()); + bag.patterns.add(pattern); + }); + inst._zod.check = (payload) => { + if (payload.value.startsWith(def.prefix)) return; + payload.issues.push({ + origin: "string", + code: "invalid_format", + format: "starts_with", + prefix: def.prefix, + input: payload.value, + inst, + continue: !def.abort + }); + }; +}); +var $ZodCheckEndsWith = /*@__PURE__*/ $constructor("$ZodCheckEndsWith", (inst, def) => { + $ZodCheck.init(inst, def); + const pattern = new RegExp(`.*${escapeRegex(def.suffix)}$`); + def.pattern ?? (def.pattern = pattern); + inst._zod.onattach.push((inst) => { + const bag = inst._zod.bag; + bag.patterns ?? (bag.patterns = /* @__PURE__ */ new Set()); + bag.patterns.add(pattern); + }); + inst._zod.check = (payload) => { + if (payload.value.endsWith(def.suffix)) return; + payload.issues.push({ + origin: "string", + code: "invalid_format", + format: "ends_with", + suffix: def.suffix, + input: payload.value, + inst, + continue: !def.abort + }); + }; +}); +var $ZodCheckOverwrite = /*@__PURE__*/ $constructor("$ZodCheckOverwrite", (inst, def) => { + $ZodCheck.init(inst, def); + inst._zod.check = (payload) => { + payload.value = def.tx(payload.value); + }; +}); +//#endregion +//#region node_modules/zod/v4/core/doc.js +var Doc = class { + constructor(args = []) { + this.content = []; + this.indent = 0; + if (this) this.args = args; + } + indented(fn) { + this.indent += 1; + fn(this); + this.indent -= 1; + } + write(arg) { + if (typeof arg === "function") { + arg(this, { execution: "sync" }); + arg(this, { execution: "async" }); + return; + } + const lines = arg.split("\n").filter((x) => x); + const minIndent = Math.min(...lines.map((x) => x.length - x.trimStart().length)); + const dedented = lines.map((x) => x.slice(minIndent)).map((x) => " ".repeat(this.indent * 2) + x); + for (const line of dedented) this.content.push(line); + } + compile() { + const F = Function; + const args = this?.args; + const lines = [...(this?.content ?? [``]).map((x) => ` ${x}`)]; + return new F(...args, lines.join("\n")); + } +}; +//#endregion +//#region node_modules/zod/v4/core/versions.js +var version = { + major: 4, + minor: 4, + patch: 3 +}; +//#endregion +//#region node_modules/zod/v4/core/schemas.js +var $ZodType = /*@__PURE__*/ $constructor("$ZodType", (inst, def) => { + var _a; + inst ?? (inst = {}); + inst._zod.def = def; + inst._zod.bag = inst._zod.bag || {}; + inst._zod.version = version; + const checks = [...inst._zod.def.checks ?? []]; + if (inst._zod.traits.has("$ZodCheck")) checks.unshift(inst); + for (const ch of checks) for (const fn of ch._zod.onattach) fn(inst); + if (checks.length === 0) { + (_a = inst._zod).deferred ?? (_a.deferred = []); + inst._zod.deferred?.push(() => { + inst._zod.run = inst._zod.parse; + }); + } else { + const runChecks = (payload, checks, ctx) => { + let isAborted = aborted(payload); + let asyncResult; + for (const ch of checks) { + if (ch._zod.def.when) { + if (explicitlyAborted(payload)) continue; + if (!ch._zod.def.when(payload)) continue; + } else if (isAborted) continue; + const currLen = payload.issues.length; + const _ = ch._zod.check(payload); + if (_ instanceof Promise && ctx?.async === false) throw new $ZodAsyncError(); + if (asyncResult || _ instanceof Promise) asyncResult = (asyncResult ?? Promise.resolve()).then(async () => { + await _; + if (payload.issues.length === currLen) return; + if (!isAborted) isAborted = aborted(payload, currLen); + }); + else { + if (payload.issues.length === currLen) continue; + if (!isAborted) isAborted = aborted(payload, currLen); + } + } + if (asyncResult) return asyncResult.then(() => { + return payload; + }); + return payload; + }; + const handleCanaryResult = (canary, payload, ctx) => { + if (aborted(canary)) { + canary.aborted = true; + return canary; + } + const checkResult = runChecks(payload, checks, ctx); + if (checkResult instanceof Promise) { + if (ctx.async === false) throw new $ZodAsyncError(); + return checkResult.then((checkResult) => inst._zod.parse(checkResult, ctx)); + } + return inst._zod.parse(checkResult, ctx); + }; + inst._zod.run = (payload, ctx) => { + if (ctx.skipChecks) return inst._zod.parse(payload, ctx); + if (ctx.direction === "backward") { + const canary = inst._zod.parse({ + value: payload.value, + issues: [] + }, { + ...ctx, + skipChecks: true + }); + if (canary instanceof Promise) return canary.then((canary) => { + return handleCanaryResult(canary, payload, ctx); + }); + return handleCanaryResult(canary, payload, ctx); + } + const result = inst._zod.parse(payload, ctx); + if (result instanceof Promise) { + if (ctx.async === false) throw new $ZodAsyncError(); + return result.then((result) => runChecks(result, checks, ctx)); + } + return runChecks(result, checks, ctx); + }; + } + defineLazy(inst, "~standard", () => ({ + validate: (value) => { + try { + const r = safeParse$2(inst, value); + return r.success ? { value: r.data } : { issues: r.error?.issues }; + } catch (_) { + return safeParseAsync$1(inst, value).then((r) => r.success ? { value: r.data } : { issues: r.error?.issues }); + } + }, + vendor: "zod", + version: 1 + })); +}); +var $ZodString = /*@__PURE__*/ $constructor("$ZodString", (inst, def) => { + $ZodType.init(inst, def); + inst._zod.pattern = [...inst?._zod.bag?.patterns ?? []].pop() ?? string$2(inst._zod.bag); + inst._zod.parse = (payload, _) => { + if (def.coerce) try { + payload.value = String(payload.value); + } catch (_) {} + if (typeof payload.value === "string") return payload; + payload.issues.push({ + expected: "string", + code: "invalid_type", + input: payload.value, + inst + }); + return payload; + }; +}); +var $ZodStringFormat = /*@__PURE__*/ $constructor("$ZodStringFormat", (inst, def) => { + $ZodCheckStringFormat.init(inst, def); + $ZodString.init(inst, def); +}); +var $ZodGUID = /*@__PURE__*/ $constructor("$ZodGUID", (inst, def) => { + def.pattern ?? (def.pattern = guid); + $ZodStringFormat.init(inst, def); +}); +var $ZodUUID = /*@__PURE__*/ $constructor("$ZodUUID", (inst, def) => { + if (def.version) { + const v = { + v1: 1, + v2: 2, + v3: 3, + v4: 4, + v5: 5, + v6: 6, + v7: 7, + v8: 8 + }[def.version]; + if (v === void 0) throw new Error(`Invalid UUID version: "${def.version}"`); + def.pattern ?? (def.pattern = uuid(v)); + } else def.pattern ?? (def.pattern = uuid()); + $ZodStringFormat.init(inst, def); +}); +var $ZodEmail = /*@__PURE__*/ $constructor("$ZodEmail", (inst, def) => { + def.pattern ?? (def.pattern = email); + $ZodStringFormat.init(inst, def); +}); +var $ZodURL = /*@__PURE__*/ $constructor("$ZodURL", (inst, def) => { + $ZodStringFormat.init(inst, def); + inst._zod.check = (payload) => { + try { + const trimmed = payload.value.trim(); + if (!def.normalize && def.protocol?.source === httpProtocol.source) { + if (!/^https?:\/\//i.test(trimmed)) { + payload.issues.push({ + code: "invalid_format", + format: "url", + note: "Invalid URL format", + input: payload.value, + inst, + continue: !def.abort + }); + return; + } + } + const url = new URL(trimmed); + if (def.hostname) { + def.hostname.lastIndex = 0; + if (!def.hostname.test(url.hostname)) payload.issues.push({ + code: "invalid_format", + format: "url", + note: "Invalid hostname", + pattern: def.hostname.source, + input: payload.value, + inst, + continue: !def.abort + }); + } + if (def.protocol) { + def.protocol.lastIndex = 0; + if (!def.protocol.test(url.protocol.endsWith(":") ? url.protocol.slice(0, -1) : url.protocol)) payload.issues.push({ + code: "invalid_format", + format: "url", + note: "Invalid protocol", + pattern: def.protocol.source, + input: payload.value, + inst, + continue: !def.abort + }); + } + if (def.normalize) payload.value = url.href; + else payload.value = trimmed; + return; + } catch (_) { + payload.issues.push({ + code: "invalid_format", + format: "url", + input: payload.value, + inst, + continue: !def.abort + }); + } + }; +}); +var $ZodEmoji = /*@__PURE__*/ $constructor("$ZodEmoji", (inst, def) => { + def.pattern ?? (def.pattern = emoji()); + $ZodStringFormat.init(inst, def); +}); +var $ZodNanoID = /*@__PURE__*/ $constructor("$ZodNanoID", (inst, def) => { + def.pattern ?? (def.pattern = nanoid); + $ZodStringFormat.init(inst, def); +}); +/** +* @deprecated CUID v1 is deprecated by its authors due to information leakage +* (timestamps embedded in the id). Use {@link $ZodCUID2} instead. +* See https://github.com/paralleldrive/cuid. +*/ +var $ZodCUID = /*@__PURE__*/ $constructor("$ZodCUID", (inst, def) => { + def.pattern ?? (def.pattern = cuid); + $ZodStringFormat.init(inst, def); +}); +var $ZodCUID2 = /*@__PURE__*/ $constructor("$ZodCUID2", (inst, def) => { + def.pattern ?? (def.pattern = cuid2); + $ZodStringFormat.init(inst, def); +}); +var $ZodULID = /*@__PURE__*/ $constructor("$ZodULID", (inst, def) => { + def.pattern ?? (def.pattern = ulid); + $ZodStringFormat.init(inst, def); +}); +var $ZodXID = /*@__PURE__*/ $constructor("$ZodXID", (inst, def) => { + def.pattern ?? (def.pattern = xid); + $ZodStringFormat.init(inst, def); +}); +var $ZodKSUID = /*@__PURE__*/ $constructor("$ZodKSUID", (inst, def) => { + def.pattern ?? (def.pattern = ksuid); + $ZodStringFormat.init(inst, def); +}); +var $ZodISODateTime = /*@__PURE__*/ $constructor("$ZodISODateTime", (inst, def) => { + def.pattern ?? (def.pattern = datetime$1(def)); + $ZodStringFormat.init(inst, def); +}); +var $ZodISODate = /*@__PURE__*/ $constructor("$ZodISODate", (inst, def) => { + def.pattern ?? (def.pattern = date$1); + $ZodStringFormat.init(inst, def); +}); +var $ZodISOTime = /*@__PURE__*/ $constructor("$ZodISOTime", (inst, def) => { + def.pattern ?? (def.pattern = time$1(def)); + $ZodStringFormat.init(inst, def); +}); +var $ZodISODuration = /*@__PURE__*/ $constructor("$ZodISODuration", (inst, def) => { + def.pattern ?? (def.pattern = duration$1); + $ZodStringFormat.init(inst, def); +}); +var $ZodIPv4 = /*@__PURE__*/ $constructor("$ZodIPv4", (inst, def) => { + def.pattern ?? (def.pattern = ipv4); + $ZodStringFormat.init(inst, def); + inst._zod.bag.format = `ipv4`; +}); +var $ZodIPv6 = /*@__PURE__*/ $constructor("$ZodIPv6", (inst, def) => { + def.pattern ?? (def.pattern = ipv6); + $ZodStringFormat.init(inst, def); + inst._zod.bag.format = `ipv6`; + inst._zod.check = (payload) => { + try { + new URL(`http://[${payload.value}]`); + } catch { + payload.issues.push({ + code: "invalid_format", + format: "ipv6", + input: payload.value, + inst, + continue: !def.abort + }); + } + }; +}); +var $ZodCIDRv4 = /*@__PURE__*/ $constructor("$ZodCIDRv4", (inst, def) => { + def.pattern ?? (def.pattern = cidrv4); + $ZodStringFormat.init(inst, def); +}); +var $ZodCIDRv6 = /*@__PURE__*/ $constructor("$ZodCIDRv6", (inst, def) => { + def.pattern ?? (def.pattern = cidrv6); + $ZodStringFormat.init(inst, def); + inst._zod.check = (payload) => { + const parts = payload.value.split("/"); + try { + if (parts.length !== 2) throw new Error(); + const [address, prefix] = parts; + if (!prefix) throw new Error(); + const prefixNum = Number(prefix); + if (`${prefixNum}` !== prefix) throw new Error(); + if (prefixNum < 0 || prefixNum > 128) throw new Error(); + new URL(`http://[${address}]`); + } catch { + payload.issues.push({ + code: "invalid_format", + format: "cidrv6", + input: payload.value, + inst, + continue: !def.abort + }); + } + }; +}); +function isValidBase64(data) { + if (data === "") return true; + if (/\s/.test(data)) return false; + if (data.length % 4 !== 0) return false; + try { + atob(data); + return true; + } catch { + return false; + } +} +var $ZodBase64 = /*@__PURE__*/ $constructor("$ZodBase64", (inst, def) => { + def.pattern ?? (def.pattern = base64); + $ZodStringFormat.init(inst, def); + inst._zod.bag.contentEncoding = "base64"; + inst._zod.check = (payload) => { + if (isValidBase64(payload.value)) return; + payload.issues.push({ + code: "invalid_format", + format: "base64", + input: payload.value, + inst, + continue: !def.abort + }); + }; +}); +function isValidBase64URL(data) { + if (!base64url.test(data)) return false; + const base64 = data.replace(/[-_]/g, (c) => c === "-" ? "+" : "/"); + return isValidBase64(base64.padEnd(Math.ceil(base64.length / 4) * 4, "=")); +} +var $ZodBase64URL = /*@__PURE__*/ $constructor("$ZodBase64URL", (inst, def) => { + def.pattern ?? (def.pattern = base64url); + $ZodStringFormat.init(inst, def); + inst._zod.bag.contentEncoding = "base64url"; + inst._zod.check = (payload) => { + if (isValidBase64URL(payload.value)) return; + payload.issues.push({ + code: "invalid_format", + format: "base64url", + input: payload.value, + inst, + continue: !def.abort + }); + }; +}); +var $ZodE164 = /*@__PURE__*/ $constructor("$ZodE164", (inst, def) => { + def.pattern ?? (def.pattern = e164); + $ZodStringFormat.init(inst, def); +}); +function isValidJWT(token, algorithm = null) { + try { + const tokensParts = token.split("."); + if (tokensParts.length !== 3) return false; + const [header] = tokensParts; + if (!header) return false; + const parsedHeader = JSON.parse(atob(header)); + if ("typ" in parsedHeader && parsedHeader?.typ !== "JWT") return false; + if (!parsedHeader.alg) return false; + if (algorithm && (!("alg" in parsedHeader) || parsedHeader.alg !== algorithm)) return false; + return true; + } catch { + return false; + } +} +var $ZodJWT = /*@__PURE__*/ $constructor("$ZodJWT", (inst, def) => { + $ZodStringFormat.init(inst, def); + inst._zod.check = (payload) => { + if (isValidJWT(payload.value, def.alg)) return; + payload.issues.push({ + code: "invalid_format", + format: "jwt", + input: payload.value, + inst, + continue: !def.abort + }); + }; +}); +var $ZodNumber = /*@__PURE__*/ $constructor("$ZodNumber", (inst, def) => { + $ZodType.init(inst, def); + inst._zod.pattern = inst._zod.bag.pattern ?? number$1; + inst._zod.parse = (payload, _ctx) => { + if (def.coerce) try { + payload.value = Number(payload.value); + } catch (_) {} + const input = payload.value; + if (typeof input === "number" && !Number.isNaN(input) && Number.isFinite(input)) return payload; + const received = typeof input === "number" ? Number.isNaN(input) ? "NaN" : !Number.isFinite(input) ? "Infinity" : void 0 : void 0; + payload.issues.push({ + expected: "number", + code: "invalid_type", + input, + inst, + ...received ? { received } : {} + }); + return payload; + }; +}); +var $ZodNumberFormat = /*@__PURE__*/ $constructor("$ZodNumberFormat", (inst, def) => { + $ZodCheckNumberFormat.init(inst, def); + $ZodNumber.init(inst, def); +}); +var $ZodBoolean = /*@__PURE__*/ $constructor("$ZodBoolean", (inst, def) => { + $ZodType.init(inst, def); + inst._zod.pattern = boolean$1; + inst._zod.parse = (payload, _ctx) => { + if (def.coerce) try { + payload.value = Boolean(payload.value); + } catch (_) {} + const input = payload.value; + if (typeof input === "boolean") return payload; + payload.issues.push({ + expected: "boolean", + code: "invalid_type", + input, + inst + }); + return payload; + }; +}); +var $ZodNull = /*@__PURE__*/ $constructor("$ZodNull", (inst, def) => { + $ZodType.init(inst, def); + inst._zod.pattern = _null$2; + inst._zod.values = /* @__PURE__ */ new Set([null]); + inst._zod.parse = (payload, _ctx) => { + const input = payload.value; + if (input === null) return payload; + payload.issues.push({ + expected: "null", + code: "invalid_type", + input, + inst + }); + return payload; + }; +}); +var $ZodUnknown = /*@__PURE__*/ $constructor("$ZodUnknown", (inst, def) => { + $ZodType.init(inst, def); + inst._zod.parse = (payload) => payload; +}); +var $ZodNever = /*@__PURE__*/ $constructor("$ZodNever", (inst, def) => { + $ZodType.init(inst, def); + inst._zod.parse = (payload, _ctx) => { + payload.issues.push({ + expected: "never", + code: "invalid_type", + input: payload.value, + inst + }); + return payload; + }; +}); +function handleArrayResult(result, final, index) { + if (result.issues.length) final.issues.push(...prefixIssues(index, result.issues)); + final.value[index] = result.value; +} +var $ZodArray = /*@__PURE__*/ $constructor("$ZodArray", (inst, def) => { + $ZodType.init(inst, def); + inst._zod.parse = (payload, ctx) => { + const input = payload.value; + if (!Array.isArray(input)) { + payload.issues.push({ + expected: "array", + code: "invalid_type", + input, + inst + }); + return payload; + } + payload.value = Array(input.length); + const proms = []; + for (let i = 0; i < input.length; i++) { + const item = input[i]; + const result = def.element._zod.run({ + value: item, + issues: [] + }, ctx); + if (result instanceof Promise) proms.push(result.then((result) => handleArrayResult(result, payload, i))); + else handleArrayResult(result, payload, i); + } + if (proms.length) return Promise.all(proms).then(() => payload); + return payload; + }; +}); +function handlePropertyResult(result, final, key, input, isOptionalIn, isOptionalOut) { + const isPresent = key in input; + if (result.issues.length) { + if (isOptionalIn && isOptionalOut && !isPresent) return; + final.issues.push(...prefixIssues(key, result.issues)); + } + if (!isPresent && !isOptionalIn) { + if (!result.issues.length) final.issues.push({ + code: "invalid_type", + expected: "nonoptional", + input: void 0, + path: [key] + }); + return; + } + if (result.value === void 0) { + if (isPresent) final.value[key] = void 0; + } else final.value[key] = result.value; +} +function normalizeDef(def) { + const keys = Object.keys(def.shape); + for (const k of keys) if (!def.shape?.[k]?._zod?.traits?.has("$ZodType")) throw new Error(`Invalid element at key "${k}": expected a Zod schema`); + const okeys = optionalKeys(def.shape); + return { + ...def, + keys, + keySet: new Set(keys), + numKeys: keys.length, + optionalKeys: new Set(okeys) + }; +} +function handleCatchall(proms, input, payload, ctx, def, inst) { + const unrecognized = []; + const keySet = def.keySet; + const _catchall = def.catchall._zod; + const t = _catchall.def.type; + const isOptionalIn = _catchall.optin === "optional"; + const isOptionalOut = _catchall.optout === "optional"; + for (const key in input) { + if (key === "__proto__") continue; + if (keySet.has(key)) continue; + if (t === "never") { + unrecognized.push(key); + continue; + } + const r = _catchall.run({ + value: input[key], + issues: [] + }, ctx); + if (r instanceof Promise) proms.push(r.then((r) => handlePropertyResult(r, payload, key, input, isOptionalIn, isOptionalOut))); + else handlePropertyResult(r, payload, key, input, isOptionalIn, isOptionalOut); + } + if (unrecognized.length) payload.issues.push({ + code: "unrecognized_keys", + keys: unrecognized, + input, + inst + }); + if (!proms.length) return payload; + return Promise.all(proms).then(() => { + return payload; + }); +} +var $ZodObject = /*@__PURE__*/ $constructor("$ZodObject", (inst, def) => { + $ZodType.init(inst, def); + if (!Object.getOwnPropertyDescriptor(def, "shape")?.get) { + const sh = def.shape; + Object.defineProperty(def, "shape", { get: () => { + const newSh = { ...sh }; + Object.defineProperty(def, "shape", { value: newSh }); + return newSh; + } }); + } + const _normalized = cached(() => normalizeDef(def)); + defineLazy(inst._zod, "propValues", () => { + const shape = def.shape; + const propValues = {}; + for (const key in shape) { + const field = shape[key]._zod; + if (field.values) { + propValues[key] ?? (propValues[key] = /* @__PURE__ */ new Set()); + for (const v of field.values) propValues[key].add(v); + } + } + return propValues; + }); + const isObject = isObject$1; + const catchall = def.catchall; + let value; + inst._zod.parse = (payload, ctx) => { + value ?? (value = _normalized.value); + const input = payload.value; + if (!isObject(input)) { + payload.issues.push({ + expected: "object", + code: "invalid_type", + input, + inst + }); + return payload; + } + payload.value = {}; + const proms = []; + const shape = value.shape; + for (const key of value.keys) { + const el = shape[key]; + const isOptionalIn = el._zod.optin === "optional"; + const isOptionalOut = el._zod.optout === "optional"; + const r = el._zod.run({ + value: input[key], + issues: [] + }, ctx); + if (r instanceof Promise) proms.push(r.then((r) => handlePropertyResult(r, payload, key, input, isOptionalIn, isOptionalOut))); + else handlePropertyResult(r, payload, key, input, isOptionalIn, isOptionalOut); + } + if (!catchall) return proms.length ? Promise.all(proms).then(() => payload) : payload; + return handleCatchall(proms, input, payload, ctx, _normalized.value, inst); + }; +}); +var $ZodObjectJIT = /*@__PURE__*/ $constructor("$ZodObjectJIT", (inst, def) => { + $ZodObject.init(inst, def); + const superParse = inst._zod.parse; + const _normalized = cached(() => normalizeDef(def)); + const generateFastpass = (shape) => { + const doc = new Doc([ + "shape", + "payload", + "ctx" + ]); + const normalized = _normalized.value; + const parseStr = (key) => { + const k = esc(key); + return `shape[${k}]._zod.run({ value: input[${k}], issues: [] }, ctx)`; + }; + doc.write(`const input = payload.value;`); + const ids = Object.create(null); + let counter = 0; + for (const key of normalized.keys) ids[key] = `key_${counter++}`; + doc.write(`const newResult = {};`); + for (const key of normalized.keys) { + const id = ids[key]; + const k = esc(key); + const schema = shape[key]; + const isOptionalIn = schema?._zod?.optin === "optional"; + const isOptionalOut = schema?._zod?.optout === "optional"; + doc.write(`const ${id} = ${parseStr(key)};`); + if (isOptionalIn && isOptionalOut) doc.write(` + if (${id}.issues.length) { + if (${k} in input) { + payload.issues = payload.issues.concat(${id}.issues.map(iss => ({ + ...iss, + path: iss.path ? [${k}, ...iss.path] : [${k}] + }))); + } + } + + if (${id}.value === undefined) { + if (${k} in input) { + newResult[${k}] = undefined; + } + } else { + newResult[${k}] = ${id}.value; + } + + `); + else if (!isOptionalIn) doc.write(` + const ${id}_present = ${k} in input; + if (${id}.issues.length) { + payload.issues = payload.issues.concat(${id}.issues.map(iss => ({ + ...iss, + path: iss.path ? [${k}, ...iss.path] : [${k}] + }))); + } + if (!${id}_present && !${id}.issues.length) { + payload.issues.push({ + code: "invalid_type", + expected: "nonoptional", + input: undefined, + path: [${k}] + }); + } + + if (${id}_present) { + if (${id}.value === undefined) { + newResult[${k}] = undefined; + } else { + newResult[${k}] = ${id}.value; + } + } + + `); + else doc.write(` + if (${id}.issues.length) { + payload.issues = payload.issues.concat(${id}.issues.map(iss => ({ + ...iss, + path: iss.path ? [${k}, ...iss.path] : [${k}] + }))); + } + + if (${id}.value === undefined) { + if (${k} in input) { + newResult[${k}] = undefined; + } + } else { + newResult[${k}] = ${id}.value; + } + + `); + } + doc.write(`payload.value = newResult;`); + doc.write(`return payload;`); + const fn = doc.compile(); + return (payload, ctx) => fn(shape, payload, ctx); + }; + let fastpass; + const isObject = isObject$1; + const jit = !globalConfig.jitless; + const fastEnabled = jit && allowsEval.value; + const catchall = def.catchall; + let value; + inst._zod.parse = (payload, ctx) => { + value ?? (value = _normalized.value); + const input = payload.value; + if (!isObject(input)) { + payload.issues.push({ + expected: "object", + code: "invalid_type", + input, + inst + }); + return payload; + } + if (jit && fastEnabled && ctx?.async === false && ctx.jitless !== true) { + if (!fastpass) fastpass = generateFastpass(def.shape); + payload = fastpass(payload, ctx); + if (!catchall) return payload; + return handleCatchall([], input, payload, ctx, value, inst); + } + return superParse(payload, ctx); + }; +}); +function handleUnionResults(results, final, inst, ctx) { + for (const result of results) if (result.issues.length === 0) { + final.value = result.value; + return final; + } + const nonaborted = results.filter((r) => !aborted(r)); + if (nonaborted.length === 1) { + final.value = nonaborted[0].value; + return nonaborted[0]; + } + final.issues.push({ + code: "invalid_union", + input: final.value, + inst, + errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config()))) + }); + return final; +} +var $ZodUnion = /*@__PURE__*/ $constructor("$ZodUnion", (inst, def) => { + $ZodType.init(inst, def); + defineLazy(inst._zod, "optin", () => def.options.some((o) => o._zod.optin === "optional") ? "optional" : void 0); + defineLazy(inst._zod, "optout", () => def.options.some((o) => o._zod.optout === "optional") ? "optional" : void 0); + defineLazy(inst._zod, "values", () => { + if (def.options.every((o) => o._zod.values)) return new Set(def.options.flatMap((option) => Array.from(option._zod.values))); + }); + defineLazy(inst._zod, "pattern", () => { + if (def.options.every((o) => o._zod.pattern)) { + const patterns = def.options.map((o) => o._zod.pattern); + return new RegExp(`^(${patterns.map((p) => cleanRegex(p.source)).join("|")})$`); + } + }); + const first = def.options.length === 1 ? def.options[0]._zod.run : null; + inst._zod.parse = (payload, ctx) => { + if (first) return first(payload, ctx); + let async = false; + const results = []; + for (const option of def.options) { + const result = option._zod.run({ + value: payload.value, + issues: [] + }, ctx); + if (result instanceof Promise) { + results.push(result); + async = true; + } else { + if (result.issues.length === 0) return result; + results.push(result); + } + } + if (!async) return handleUnionResults(results, payload, inst, ctx); + return Promise.all(results).then((results) => { + return handleUnionResults(results, payload, inst, ctx); + }); + }; +}); +var $ZodIntersection = /*@__PURE__*/ $constructor("$ZodIntersection", (inst, def) => { + $ZodType.init(inst, def); + inst._zod.parse = (payload, ctx) => { + const input = payload.value; + const left = def.left._zod.run({ + value: input, + issues: [] + }, ctx); + const right = def.right._zod.run({ + value: input, + issues: [] + }, ctx); + if (left instanceof Promise || right instanceof Promise) return Promise.all([left, right]).then(([left, right]) => { + return handleIntersectionResults(payload, left, right); + }); + return handleIntersectionResults(payload, left, right); + }; +}); +function mergeValues(a, b) { + if (a === b) return { + valid: true, + data: a + }; + if (a instanceof Date && b instanceof Date && +a === +b) return { + valid: true, + data: a + }; + if (isPlainObject$2(a) && isPlainObject$2(b)) { + const bKeys = Object.keys(b); + const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1); + const newObj = { + ...a, + ...b + }; + for (const key of sharedKeys) { + const sharedValue = mergeValues(a[key], b[key]); + if (!sharedValue.valid) return { + valid: false, + mergeErrorPath: [key, ...sharedValue.mergeErrorPath] + }; + newObj[key] = sharedValue.data; + } + return { + valid: true, + data: newObj + }; + } + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) return { + valid: false, + mergeErrorPath: [] + }; + const newArray = []; + for (let index = 0; index < a.length; index++) { + const itemA = a[index]; + const itemB = b[index]; + const sharedValue = mergeValues(itemA, itemB); + if (!sharedValue.valid) return { + valid: false, + mergeErrorPath: [index, ...sharedValue.mergeErrorPath] + }; + newArray.push(sharedValue.data); + } + return { + valid: true, + data: newArray + }; + } + return { + valid: false, + mergeErrorPath: [] + }; +} +function handleIntersectionResults(result, left, right) { + const unrecKeys = /* @__PURE__ */ new Map(); + let unrecIssue; + for (const iss of left.issues) if (iss.code === "unrecognized_keys") { + unrecIssue ?? (unrecIssue = iss); + for (const k of iss.keys) { + if (!unrecKeys.has(k)) unrecKeys.set(k, {}); + unrecKeys.get(k).l = true; + } + } else result.issues.push(iss); + for (const iss of right.issues) if (iss.code === "unrecognized_keys") for (const k of iss.keys) { + if (!unrecKeys.has(k)) unrecKeys.set(k, {}); + unrecKeys.get(k).r = true; + } + else result.issues.push(iss); + const bothKeys = [...unrecKeys].filter(([, f]) => f.l && f.r).map(([k]) => k); + if (bothKeys.length && unrecIssue) result.issues.push({ + ...unrecIssue, + keys: bothKeys + }); + if (aborted(result)) return result; + const merged = mergeValues(left.value, right.value); + if (!merged.valid) throw new Error(`Unmergable intersection. Error path: ${JSON.stringify(merged.mergeErrorPath)}`); + result.value = merged.data; + return result; +} +var $ZodRecord = /*@__PURE__*/ $constructor("$ZodRecord", (inst, def) => { + $ZodType.init(inst, def); + inst._zod.parse = (payload, ctx) => { + const input = payload.value; + if (!isPlainObject$2(input)) { + payload.issues.push({ + expected: "record", + code: "invalid_type", + input, + inst + }); + return payload; + } + const proms = []; + const values = def.keyType._zod.values; + if (values) { + payload.value = {}; + const recordKeys = /* @__PURE__ */ new Set(); + for (const key of values) if (typeof key === "string" || typeof key === "number" || typeof key === "symbol") { + recordKeys.add(typeof key === "number" ? key.toString() : key); + const keyResult = def.keyType._zod.run({ + value: key, + issues: [] + }, ctx); + if (keyResult instanceof Promise) throw new Error("Async schemas not supported in object keys currently"); + if (keyResult.issues.length) { + payload.issues.push({ + code: "invalid_key", + origin: "record", + issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())), + input: key, + path: [key], + inst + }); + continue; + } + const outKey = keyResult.value; + const result = def.valueType._zod.run({ + value: input[key], + issues: [] + }, ctx); + if (result instanceof Promise) proms.push(result.then((result) => { + if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues)); + payload.value[outKey] = result.value; + })); + else { + if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues)); + payload.value[outKey] = result.value; + } + } + let unrecognized; + for (const key in input) if (!recordKeys.has(key)) { + unrecognized = unrecognized ?? []; + unrecognized.push(key); + } + if (unrecognized && unrecognized.length > 0) payload.issues.push({ + code: "unrecognized_keys", + input, + inst, + keys: unrecognized + }); + } else { + payload.value = {}; + for (const key of Reflect.ownKeys(input)) { + if (key === "__proto__") continue; + if (!Object.prototype.propertyIsEnumerable.call(input, key)) continue; + let keyResult = def.keyType._zod.run({ + value: key, + issues: [] + }, ctx); + if (keyResult instanceof Promise) throw new Error("Async schemas not supported in object keys currently"); + if (typeof key === "string" && number$1.test(key) && keyResult.issues.length) { + const retryResult = def.keyType._zod.run({ + value: Number(key), + issues: [] + }, ctx); + if (retryResult instanceof Promise) throw new Error("Async schemas not supported in object keys currently"); + if (retryResult.issues.length === 0) keyResult = retryResult; + } + if (keyResult.issues.length) { + if (def.mode === "loose") payload.value[key] = input[key]; + else payload.issues.push({ + code: "invalid_key", + origin: "record", + issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())), + input: key, + path: [key], + inst + }); + continue; + } + const result = def.valueType._zod.run({ + value: input[key], + issues: [] + }, ctx); + if (result instanceof Promise) proms.push(result.then((result) => { + if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues)); + payload.value[keyResult.value] = result.value; + })); + else { + if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues)); + payload.value[keyResult.value] = result.value; + } + } + } + if (proms.length) return Promise.all(proms).then(() => payload); + return payload; + }; +}); +var $ZodEnum = /*@__PURE__*/ $constructor("$ZodEnum", (inst, def) => { + $ZodType.init(inst, def); + const values = getEnumValues(def.entries); + const valuesSet = new Set(values); + inst._zod.values = valuesSet; + inst._zod.pattern = new RegExp(`^(${values.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === "string" ? escapeRegex(o) : o.toString()).join("|")})$`); + inst._zod.parse = (payload, _ctx) => { + const input = payload.value; + if (valuesSet.has(input)) return payload; + payload.issues.push({ + code: "invalid_value", + values, + input, + inst + }); + return payload; + }; +}); +var $ZodLiteral = /*@__PURE__*/ $constructor("$ZodLiteral", (inst, def) => { + $ZodType.init(inst, def); + if (def.values.length === 0) throw new Error("Cannot create literal schema with no valid values"); + const values = new Set(def.values); + inst._zod.values = values; + inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ? escapeRegex(o) : o ? escapeRegex(o.toString()) : String(o)).join("|")})$`); + inst._zod.parse = (payload, _ctx) => { + const input = payload.value; + if (values.has(input)) return payload; + payload.issues.push({ + code: "invalid_value", + values: def.values, + input, + inst + }); + return payload; + }; +}); +var $ZodTransform = /*@__PURE__*/ $constructor("$ZodTransform", (inst, def) => { + $ZodType.init(inst, def); + inst._zod.optin = "optional"; + inst._zod.parse = (payload, ctx) => { + if (ctx.direction === "backward") throw new $ZodEncodeError(inst.constructor.name); + const _out = def.transform(payload.value, payload); + if (ctx.async) return (_out instanceof Promise ? _out : Promise.resolve(_out)).then((output) => { + payload.value = output; + payload.fallback = true; + return payload; + }); + if (_out instanceof Promise) throw new $ZodAsyncError(); + payload.value = _out; + payload.fallback = true; + return payload; + }; +}); +function handleOptionalResult(result, input) { + if (input === void 0 && (result.issues.length || result.fallback)) return { + issues: [], + value: void 0 + }; + return result; +} +var $ZodOptional = /*@__PURE__*/ $constructor("$ZodOptional", (inst, def) => { + $ZodType.init(inst, def); + inst._zod.optin = "optional"; + inst._zod.optout = "optional"; + defineLazy(inst._zod, "values", () => { + return def.innerType._zod.values ? /* @__PURE__ */ new Set([...def.innerType._zod.values, void 0]) : void 0; + }); + defineLazy(inst._zod, "pattern", () => { + const pattern = def.innerType._zod.pattern; + return pattern ? new RegExp(`^(${cleanRegex(pattern.source)})?$`) : void 0; + }); + inst._zod.parse = (payload, ctx) => { + if (def.innerType._zod.optin === "optional") { + const input = payload.value; + const result = def.innerType._zod.run(payload, ctx); + if (result instanceof Promise) return result.then((r) => handleOptionalResult(r, input)); + return handleOptionalResult(result, input); + } + if (payload.value === void 0) return payload; + return def.innerType._zod.run(payload, ctx); + }; +}); +var $ZodExactOptional = /*@__PURE__*/ $constructor("$ZodExactOptional", (inst, def) => { + $ZodOptional.init(inst, def); + defineLazy(inst._zod, "values", () => def.innerType._zod.values); + defineLazy(inst._zod, "pattern", () => def.innerType._zod.pattern); + inst._zod.parse = (payload, ctx) => { + return def.innerType._zod.run(payload, ctx); + }; +}); +var $ZodNullable = /*@__PURE__*/ $constructor("$ZodNullable", (inst, def) => { + $ZodType.init(inst, def); + defineLazy(inst._zod, "optin", () => def.innerType._zod.optin); + defineLazy(inst._zod, "optout", () => def.innerType._zod.optout); + defineLazy(inst._zod, "pattern", () => { + const pattern = def.innerType._zod.pattern; + return pattern ? new RegExp(`^(${cleanRegex(pattern.source)}|null)$`) : void 0; + }); + defineLazy(inst._zod, "values", () => { + return def.innerType._zod.values ? /* @__PURE__ */ new Set([...def.innerType._zod.values, null]) : void 0; + }); + inst._zod.parse = (payload, ctx) => { + if (payload.value === null) return payload; + return def.innerType._zod.run(payload, ctx); + }; +}); +var $ZodDefault = /*@__PURE__*/ $constructor("$ZodDefault", (inst, def) => { + $ZodType.init(inst, def); + inst._zod.optin = "optional"; + defineLazy(inst._zod, "values", () => def.innerType._zod.values); + inst._zod.parse = (payload, ctx) => { + if (ctx.direction === "backward") return def.innerType._zod.run(payload, ctx); + if (payload.value === void 0) { + payload.value = def.defaultValue; + /** + * $ZodDefault returns the default value immediately in forward direction. + * It doesn't pass the default value into the validator ("prefault"). There's no reason to pass the default value through validation. The validity of the default is enforced by TypeScript statically. Otherwise, it's the responsibility of the user to ensure the default is valid. In the case of pipes with divergent in/out types, you can specify the default on the `in` schema of your ZodPipe to set a "prefault" for the pipe. */ + return payload; + } + const result = def.innerType._zod.run(payload, ctx); + if (result instanceof Promise) return result.then((result) => handleDefaultResult(result, def)); + return handleDefaultResult(result, def); + }; +}); +function handleDefaultResult(payload, def) { + if (payload.value === void 0) payload.value = def.defaultValue; + return payload; +} +var $ZodPrefault = /*@__PURE__*/ $constructor("$ZodPrefault", (inst, def) => { + $ZodType.init(inst, def); + inst._zod.optin = "optional"; + defineLazy(inst._zod, "values", () => def.innerType._zod.values); + inst._zod.parse = (payload, ctx) => { + if (ctx.direction === "backward") return def.innerType._zod.run(payload, ctx); + if (payload.value === void 0) payload.value = def.defaultValue; + return def.innerType._zod.run(payload, ctx); + }; +}); +var $ZodNonOptional = /*@__PURE__*/ $constructor("$ZodNonOptional", (inst, def) => { + $ZodType.init(inst, def); + defineLazy(inst._zod, "values", () => { + const v = def.innerType._zod.values; + return v ? new Set([...v].filter((x) => x !== void 0)) : void 0; + }); + inst._zod.parse = (payload, ctx) => { + const result = def.innerType._zod.run(payload, ctx); + if (result instanceof Promise) return result.then((result) => handleNonOptionalResult(result, inst)); + return handleNonOptionalResult(result, inst); + }; +}); +function handleNonOptionalResult(payload, inst) { + if (!payload.issues.length && payload.value === void 0) payload.issues.push({ + code: "invalid_type", + expected: "nonoptional", + input: payload.value, + inst + }); + return payload; +} +var $ZodCatch = /*@__PURE__*/ $constructor("$ZodCatch", (inst, def) => { + $ZodType.init(inst, def); + inst._zod.optin = "optional"; + defineLazy(inst._zod, "optout", () => def.innerType._zod.optout); + defineLazy(inst._zod, "values", () => def.innerType._zod.values); + inst._zod.parse = (payload, ctx) => { + if (ctx.direction === "backward") return def.innerType._zod.run(payload, ctx); + const result = def.innerType._zod.run(payload, ctx); + if (result instanceof Promise) return result.then((result) => { + payload.value = result.value; + if (result.issues.length) { + payload.value = def.catchValue({ + ...payload, + error: { issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config())) }, + input: payload.value + }); + payload.issues = []; + payload.fallback = true; + } + return payload; + }); + payload.value = result.value; + if (result.issues.length) { + payload.value = def.catchValue({ + ...payload, + error: { issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config())) }, + input: payload.value + }); + payload.issues = []; + payload.fallback = true; + } + return payload; + }; +}); +var $ZodPipe = /*@__PURE__*/ $constructor("$ZodPipe", (inst, def) => { + $ZodType.init(inst, def); + defineLazy(inst._zod, "values", () => def.in._zod.values); + defineLazy(inst._zod, "optin", () => def.in._zod.optin); + defineLazy(inst._zod, "optout", () => def.out._zod.optout); + defineLazy(inst._zod, "propValues", () => def.in._zod.propValues); + inst._zod.parse = (payload, ctx) => { + if (ctx.direction === "backward") { + const right = def.out._zod.run(payload, ctx); + if (right instanceof Promise) return right.then((right) => handlePipeResult(right, def.in, ctx)); + return handlePipeResult(right, def.in, ctx); + } + const left = def.in._zod.run(payload, ctx); + if (left instanceof Promise) return left.then((left) => handlePipeResult(left, def.out, ctx)); + return handlePipeResult(left, def.out, ctx); + }; +}); +function handlePipeResult(left, next, ctx) { + if (left.issues.length) { + left.aborted = true; + return left; + } + return next._zod.run({ + value: left.value, + issues: left.issues, + fallback: left.fallback + }, ctx); +} +var $ZodCodec = /*@__PURE__*/ $constructor("$ZodCodec", (inst, def) => { + $ZodType.init(inst, def); + defineLazy(inst._zod, "values", () => def.in._zod.values); + defineLazy(inst._zod, "optin", () => def.in._zod.optin); + defineLazy(inst._zod, "optout", () => def.out._zod.optout); + defineLazy(inst._zod, "propValues", () => def.in._zod.propValues); + inst._zod.parse = (payload, ctx) => { + if ((ctx.direction || "forward") === "forward") { + const left = def.in._zod.run(payload, ctx); + if (left instanceof Promise) return left.then((left) => handleCodecAResult(left, def, ctx)); + return handleCodecAResult(left, def, ctx); + } else { + const right = def.out._zod.run(payload, ctx); + if (right instanceof Promise) return right.then((right) => handleCodecAResult(right, def, ctx)); + return handleCodecAResult(right, def, ctx); + } + }; +}); +function handleCodecAResult(result, def, ctx) { + if (result.issues.length) { + result.aborted = true; + return result; + } + if ((ctx.direction || "forward") === "forward") { + const transformed = def.transform(result.value, result); + if (transformed instanceof Promise) return transformed.then((value) => handleCodecTxResult(result, value, def.out, ctx)); + return handleCodecTxResult(result, transformed, def.out, ctx); + } else { + const transformed = def.reverseTransform(result.value, result); + if (transformed instanceof Promise) return transformed.then((value) => handleCodecTxResult(result, value, def.in, ctx)); + return handleCodecTxResult(result, transformed, def.in, ctx); + } +} +function handleCodecTxResult(left, value, nextSchema, ctx) { + if (left.issues.length) { + left.aborted = true; + return left; + } + return nextSchema._zod.run({ + value, + issues: left.issues + }, ctx); +} +var $ZodReadonly = /*@__PURE__*/ $constructor("$ZodReadonly", (inst, def) => { + $ZodType.init(inst, def); + defineLazy(inst._zod, "propValues", () => def.innerType._zod.propValues); + defineLazy(inst._zod, "values", () => def.innerType._zod.values); + defineLazy(inst._zod, "optin", () => def.innerType?._zod?.optin); + defineLazy(inst._zod, "optout", () => def.innerType?._zod?.optout); + inst._zod.parse = (payload, ctx) => { + if (ctx.direction === "backward") return def.innerType._zod.run(payload, ctx); + const result = def.innerType._zod.run(payload, ctx); + if (result instanceof Promise) return result.then(handleReadonlyResult); + return handleReadonlyResult(result); + }; +}); +function handleReadonlyResult(payload) { + payload.value = Object.freeze(payload.value); + return payload; +} +var $ZodCustom = /*@__PURE__*/ $constructor("$ZodCustom", (inst, def) => { + $ZodCheck.init(inst, def); + $ZodType.init(inst, def); + inst._zod.parse = (payload, _) => { + return payload; + }; + inst._zod.check = (payload) => { + const input = payload.value; + const r = def.fn(input); + if (r instanceof Promise) return r.then((r) => handleRefineResult(r, payload, input, inst)); + handleRefineResult(r, payload, input, inst); + }; +}); +function handleRefineResult(result, payload, input, inst) { + if (!result) { + const _iss = { + code: "custom", + input, + inst, + path: [...inst._zod.def.path ?? []], + continue: !inst._zod.def.abort + }; + if (inst._zod.def.params) _iss.params = inst._zod.def.params; + payload.issues.push(issue(_iss)); + } +} +//#endregion +//#region node_modules/zod/v4/core/registries.js +var _a; +var $ZodRegistry = class { + constructor() { + this._map = /* @__PURE__ */ new WeakMap(); + this._idmap = /* @__PURE__ */ new Map(); + } + add(schema, ..._meta) { + const meta = _meta[0]; + this._map.set(schema, meta); + if (meta && typeof meta === "object" && "id" in meta) this._idmap.set(meta.id, schema); + return this; + } + clear() { + this._map = /* @__PURE__ */ new WeakMap(); + this._idmap = /* @__PURE__ */ new Map(); + return this; + } + remove(schema) { + const meta = this._map.get(schema); + if (meta && typeof meta === "object" && "id" in meta) this._idmap.delete(meta.id); + this._map.delete(schema); + return this; + } + get(schema) { + const p = schema._zod.parent; + if (p) { + const pm = { ...this.get(p) ?? {} }; + delete pm.id; + const f = { + ...pm, + ...this._map.get(schema) + }; + return Object.keys(f).length ? f : void 0; + } + return this._map.get(schema); + } + has(schema) { + return this._map.has(schema); + } +}; +function registry() { + return new $ZodRegistry(); +} +(_a = globalThis).__zod_globalRegistry ?? (_a.__zod_globalRegistry = registry()); +var globalRegistry = globalThis.__zod_globalRegistry; +//#endregion +//#region node_modules/zod/v4/core/api.js +// @__NO_SIDE_EFFECTS__ +function _string(Class, params) { + return new Class({ + type: "string", + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _email(Class, params) { + return new Class({ + type: "string", + format: "email", + check: "string_format", + abort: false, + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _guid(Class, params) { + return new Class({ + type: "string", + format: "guid", + check: "string_format", + abort: false, + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _uuid(Class, params) { + return new Class({ + type: "string", + format: "uuid", + check: "string_format", + abort: false, + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _uuidv4(Class, params) { + return new Class({ + type: "string", + format: "uuid", + check: "string_format", + abort: false, + version: "v4", + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _uuidv6(Class, params) { + return new Class({ + type: "string", + format: "uuid", + check: "string_format", + abort: false, + version: "v6", + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _uuidv7(Class, params) { + return new Class({ + type: "string", + format: "uuid", + check: "string_format", + abort: false, + version: "v7", + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _url(Class, params) { + return new Class({ + type: "string", + format: "url", + check: "string_format", + abort: false, + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _emoji(Class, params) { + return new Class({ + type: "string", + format: "emoji", + check: "string_format", + abort: false, + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _nanoid(Class, params) { + return new Class({ + type: "string", + format: "nanoid", + check: "string_format", + abort: false, + ...normalizeParams(params) + }); +} +/** +* @deprecated CUID v1 is deprecated by its authors due to information leakage +* (timestamps embedded in the id). Use {@link _cuid2} instead. +* See https://github.com/paralleldrive/cuid. +*/ +// @__NO_SIDE_EFFECTS__ +function _cuid(Class, params) { + return new Class({ + type: "string", + format: "cuid", + check: "string_format", + abort: false, + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _cuid2(Class, params) { + return new Class({ + type: "string", + format: "cuid2", + check: "string_format", + abort: false, + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _ulid(Class, params) { + return new Class({ + type: "string", + format: "ulid", + check: "string_format", + abort: false, + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _xid(Class, params) { + return new Class({ + type: "string", + format: "xid", + check: "string_format", + abort: false, + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _ksuid(Class, params) { + return new Class({ + type: "string", + format: "ksuid", + check: "string_format", + abort: false, + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _ipv4(Class, params) { + return new Class({ + type: "string", + format: "ipv4", + check: "string_format", + abort: false, + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _ipv6(Class, params) { + return new Class({ + type: "string", + format: "ipv6", + check: "string_format", + abort: false, + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _cidrv4(Class, params) { + return new Class({ + type: "string", + format: "cidrv4", + check: "string_format", + abort: false, + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _cidrv6(Class, params) { + return new Class({ + type: "string", + format: "cidrv6", + check: "string_format", + abort: false, + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _base64(Class, params) { + return new Class({ + type: "string", + format: "base64", + check: "string_format", + abort: false, + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _base64url(Class, params) { + return new Class({ + type: "string", + format: "base64url", + check: "string_format", + abort: false, + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _e164(Class, params) { + return new Class({ + type: "string", + format: "e164", + check: "string_format", + abort: false, + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _jwt(Class, params) { + return new Class({ + type: "string", + format: "jwt", + check: "string_format", + abort: false, + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _isoDateTime(Class, params) { + return new Class({ + type: "string", + format: "datetime", + check: "string_format", + offset: false, + local: false, + precision: null, + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _isoDate(Class, params) { + return new Class({ + type: "string", + format: "date", + check: "string_format", + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _isoTime(Class, params) { + return new Class({ + type: "string", + format: "time", + check: "string_format", + precision: null, + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _isoDuration(Class, params) { + return new Class({ + type: "string", + format: "duration", + check: "string_format", + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _number(Class, params) { + return new Class({ + type: "number", + checks: [], + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _int(Class, params) { + return new Class({ + type: "number", + check: "number_format", + abort: false, + format: "safeint", + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _boolean(Class, params) { + return new Class({ + type: "boolean", + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _null$1(Class, params) { + return new Class({ + type: "null", + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _unknown(Class) { + return new Class({ type: "unknown" }); +} +// @__NO_SIDE_EFFECTS__ +function _never(Class, params) { + return new Class({ + type: "never", + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _lt(value, params) { + return new $ZodCheckLessThan({ + check: "less_than", + ...normalizeParams(params), + value, + inclusive: false + }); +} +// @__NO_SIDE_EFFECTS__ +function _lte(value, params) { + return new $ZodCheckLessThan({ + check: "less_than", + ...normalizeParams(params), + value, + inclusive: true + }); +} +// @__NO_SIDE_EFFECTS__ +function _gt(value, params) { + return new $ZodCheckGreaterThan({ + check: "greater_than", + ...normalizeParams(params), + value, + inclusive: false + }); +} +// @__NO_SIDE_EFFECTS__ +function _gte(value, params) { + return new $ZodCheckGreaterThan({ + check: "greater_than", + ...normalizeParams(params), + value, + inclusive: true + }); +} +// @__NO_SIDE_EFFECTS__ +function _multipleOf(value, params) { + return new $ZodCheckMultipleOf({ + check: "multiple_of", + ...normalizeParams(params), + value + }); +} +// @__NO_SIDE_EFFECTS__ +function _maxLength(maximum, params) { + return new $ZodCheckMaxLength({ + check: "max_length", + ...normalizeParams(params), + maximum + }); +} +// @__NO_SIDE_EFFECTS__ +function _minLength(minimum, params) { + return new $ZodCheckMinLength({ + check: "min_length", + ...normalizeParams(params), + minimum + }); +} +// @__NO_SIDE_EFFECTS__ +function _length(length, params) { + return new $ZodCheckLengthEquals({ + check: "length_equals", + ...normalizeParams(params), + length + }); +} +// @__NO_SIDE_EFFECTS__ +function _regex(pattern, params) { + return new $ZodCheckRegex({ + check: "string_format", + format: "regex", + ...normalizeParams(params), + pattern + }); +} +// @__NO_SIDE_EFFECTS__ +function _lowercase(params) { + return new $ZodCheckLowerCase({ + check: "string_format", + format: "lowercase", + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _uppercase(params) { + return new $ZodCheckUpperCase({ + check: "string_format", + format: "uppercase", + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _includes(includes, params) { + return new $ZodCheckIncludes({ + check: "string_format", + format: "includes", + ...normalizeParams(params), + includes + }); +} +// @__NO_SIDE_EFFECTS__ +function _startsWith(prefix, params) { + return new $ZodCheckStartsWith({ + check: "string_format", + format: "starts_with", + ...normalizeParams(params), + prefix + }); +} +// @__NO_SIDE_EFFECTS__ +function _endsWith(suffix, params) { + return new $ZodCheckEndsWith({ + check: "string_format", + format: "ends_with", + ...normalizeParams(params), + suffix + }); +} +// @__NO_SIDE_EFFECTS__ +function _overwrite(tx) { + return new $ZodCheckOverwrite({ + check: "overwrite", + tx + }); +} +// @__NO_SIDE_EFFECTS__ +function _normalize(form) { + return /* @__PURE__ */ _overwrite((input) => input.normalize(form)); +} +// @__NO_SIDE_EFFECTS__ +function _trim() { + return /* @__PURE__ */ _overwrite((input) => input.trim()); +} +// @__NO_SIDE_EFFECTS__ +function _toLowerCase() { + return /* @__PURE__ */ _overwrite((input) => input.toLowerCase()); +} +// @__NO_SIDE_EFFECTS__ +function _toUpperCase() { + return /* @__PURE__ */ _overwrite((input) => input.toUpperCase()); +} +// @__NO_SIDE_EFFECTS__ +function _slugify() { + return /* @__PURE__ */ _overwrite((input) => slugify(input)); +} +// @__NO_SIDE_EFFECTS__ +function _array(Class, element, params) { + return new Class({ + type: "array", + element, + ...normalizeParams(params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _refine(Class, fn, _params) { + return new Class({ + type: "custom", + check: "custom", + fn, + ...normalizeParams(_params) + }); +} +// @__NO_SIDE_EFFECTS__ +function _superRefine(fn, params) { + const ch = /* @__PURE__ */ _check((payload) => { + payload.addIssue = (issue$3) => { + if (typeof issue$3 === "string") payload.issues.push(issue(issue$3, payload.value, ch._zod.def)); + else { + const _issue = issue$3; + if (_issue.fatal) _issue.continue = false; + _issue.code ?? (_issue.code = "custom"); + _issue.input ?? (_issue.input = payload.value); + _issue.inst ?? (_issue.inst = ch); + _issue.continue ?? (_issue.continue = !ch._zod.def.abort); + payload.issues.push(issue(_issue)); + } + }; + return fn(payload.value, payload); + }, params); + return ch; +} +// @__NO_SIDE_EFFECTS__ +function _check(fn, params) { + const ch = new $ZodCheck({ + check: "custom", + ...normalizeParams(params) + }); + ch._zod.check = fn; + return ch; +} +// @__NO_SIDE_EFFECTS__ +function _stringbool(Classes, _params) { + const params = normalizeParams(_params); + let truthyArray = params.truthy ?? [ + "true", + "1", + "yes", + "on", + "y", + "enabled" + ]; + let falsyArray = params.falsy ?? [ + "false", + "0", + "no", + "off", + "n", + "disabled" + ]; + if (params.case !== "sensitive") { + truthyArray = truthyArray.map((v) => typeof v === "string" ? v.toLowerCase() : v); + falsyArray = falsyArray.map((v) => typeof v === "string" ? v.toLowerCase() : v); + } + const truthySet = new Set(truthyArray); + const falsySet = new Set(falsyArray); + const _Codec = Classes.Codec ?? $ZodCodec; + const _Boolean = Classes.Boolean ?? $ZodBoolean; + const codec = new _Codec({ + type: "pipe", + in: new (Classes.String ?? $ZodString)({ + type: "string", + error: params.error + }), + out: new _Boolean({ + type: "boolean", + error: params.error + }), + transform: ((input, payload) => { + let data = input; + if (params.case !== "sensitive") data = data.toLowerCase(); + if (truthySet.has(data)) return true; + else if (falsySet.has(data)) return false; + else { + payload.issues.push({ + code: "invalid_value", + expected: "stringbool", + values: [...truthySet, ...falsySet], + input: payload.value, + inst: codec, + continue: false + }); + return {}; + } + }), + reverseTransform: ((input, _payload) => { + if (input === true) return truthyArray[0] || "true"; + else return falsyArray[0] || "false"; + }), + error: params.error + }); + return codec; +} +//#endregion +//#region node_modules/zod/v4/core/to-json-schema.js +function initializeContext(params) { + let target = params?.target ?? "draft-2020-12"; + if (target === "draft-4") target = "draft-04"; + if (target === "draft-7") target = "draft-07"; + return { + processors: params.processors ?? {}, + metadataRegistry: params?.metadata ?? globalRegistry, + target, + unrepresentable: params?.unrepresentable ?? "throw", + override: params?.override ?? (() => {}), + io: params?.io ?? "output", + counter: 0, + seen: /* @__PURE__ */ new Map(), + cycles: params?.cycles ?? "ref", + reused: params?.reused ?? "inline", + external: params?.external ?? void 0 + }; +} +function process$2(schema, ctx, _params = { + path: [], + schemaPath: [] +}) { + var _a; + const def = schema._zod.def; + const seen = ctx.seen.get(schema); + if (seen) { + seen.count++; + if (_params.schemaPath.includes(schema)) seen.cycle = _params.path; + return seen.schema; + } + const result = { + schema: {}, + count: 1, + cycle: void 0, + path: _params.path + }; + ctx.seen.set(schema, result); + const overrideSchema = schema._zod.toJSONSchema?.(); + if (overrideSchema) result.schema = overrideSchema; + else { + const params = { + ..._params, + schemaPath: [..._params.schemaPath, schema], + path: _params.path + }; + if (schema._zod.processJSONSchema) schema._zod.processJSONSchema(ctx, result.schema, params); + else { + const _json = result.schema; + const processor = ctx.processors[def.type]; + if (!processor) throw new Error(`[toJSONSchema]: Non-representable type encountered: ${def.type}`); + processor(schema, ctx, _json, params); + } + const parent = schema._zod.parent; + if (parent) { + if (!result.ref) result.ref = parent; + process$2(parent, ctx, params); + ctx.seen.get(parent).isParent = true; + } + } + const meta = ctx.metadataRegistry.get(schema); + if (meta) Object.assign(result.schema, meta); + if (ctx.io === "input" && isTransforming(schema)) { + delete result.schema.examples; + delete result.schema.default; + } + if (ctx.io === "input" && "_prefault" in result.schema) (_a = result.schema).default ?? (_a.default = result.schema._prefault); + delete result.schema._prefault; + return ctx.seen.get(schema).schema; +} +function extractDefs(ctx, schema) { + const root = ctx.seen.get(schema); + if (!root) throw new Error("Unprocessed schema. This is a bug in Zod."); + const idToSchema = /* @__PURE__ */ new Map(); + for (const entry of ctx.seen.entries()) { + const id = ctx.metadataRegistry.get(entry[0])?.id; + if (id) { + const existing = idToSchema.get(id); + if (existing && existing !== entry[0]) throw new Error(`Duplicate schema id "${id}" detected during JSON Schema conversion. Two different schemas cannot share the same id when converted together.`); + idToSchema.set(id, entry[0]); + } + } + const makeURI = (entry) => { + const defsSegment = ctx.target === "draft-2020-12" ? "$defs" : "definitions"; + if (ctx.external) { + const externalId = ctx.external.registry.get(entry[0])?.id; + const uriGenerator = ctx.external.uri ?? ((id) => id); + if (externalId) return { ref: uriGenerator(externalId) }; + const id = entry[1].defId ?? entry[1].schema.id ?? `schema${ctx.counter++}`; + entry[1].defId = id; + return { + defId: id, + ref: `${uriGenerator("__shared")}#/${defsSegment}/${id}` + }; + } + if (entry[1] === root) return { ref: "#" }; + const defUriPrefix = `#/${defsSegment}/`; + const defId = entry[1].schema.id ?? `__schema${ctx.counter++}`; + return { + defId, + ref: defUriPrefix + defId + }; + }; + const extractToDef = (entry) => { + if (entry[1].schema.$ref) return; + const seen = entry[1]; + const { ref, defId } = makeURI(entry); + seen.def = { ...seen.schema }; + if (defId) seen.defId = defId; + const schema = seen.schema; + for (const key in schema) delete schema[key]; + schema.$ref = ref; + }; + if (ctx.cycles === "throw") for (const entry of ctx.seen.entries()) { + const seen = entry[1]; + if (seen.cycle) throw new Error(`Cycle detected: #/${seen.cycle?.join("/")}/ + +Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.`); + } + for (const entry of ctx.seen.entries()) { + const seen = entry[1]; + if (schema === entry[0]) { + extractToDef(entry); + continue; + } + if (ctx.external) { + const ext = ctx.external.registry.get(entry[0])?.id; + if (schema !== entry[0] && ext) { + extractToDef(entry); + continue; + } + } + if (ctx.metadataRegistry.get(entry[0])?.id) { + extractToDef(entry); + continue; + } + if (seen.cycle) { + extractToDef(entry); + continue; + } + if (seen.count > 1) { + if (ctx.reused === "ref") { + extractToDef(entry); + continue; + } + } + } +} +function finalize(ctx, schema) { + const root = ctx.seen.get(schema); + if (!root) throw new Error("Unprocessed schema. This is a bug in Zod."); + const flattenRef = (zodSchema) => { + const seen = ctx.seen.get(zodSchema); + if (seen.ref === null) return; + const schema = seen.def ?? seen.schema; + const _cached = { ...schema }; + const ref = seen.ref; + seen.ref = null; + if (ref) { + flattenRef(ref); + const refSeen = ctx.seen.get(ref); + const refSchema = refSeen.schema; + if (refSchema.$ref && (ctx.target === "draft-07" || ctx.target === "draft-04" || ctx.target === "openapi-3.0")) { + schema.allOf = schema.allOf ?? []; + schema.allOf.push(refSchema); + } else Object.assign(schema, refSchema); + Object.assign(schema, _cached); + if (zodSchema._zod.parent === ref) for (const key in schema) { + if (key === "$ref" || key === "allOf") continue; + if (!(key in _cached)) delete schema[key]; + } + if (refSchema.$ref && refSeen.def) for (const key in schema) { + if (key === "$ref" || key === "allOf") continue; + if (key in refSeen.def && JSON.stringify(schema[key]) === JSON.stringify(refSeen.def[key])) delete schema[key]; + } + } + const parent = zodSchema._zod.parent; + if (parent && parent !== ref) { + flattenRef(parent); + const parentSeen = ctx.seen.get(parent); + if (parentSeen?.schema.$ref) { + schema.$ref = parentSeen.schema.$ref; + if (parentSeen.def) for (const key in schema) { + if (key === "$ref" || key === "allOf") continue; + if (key in parentSeen.def && JSON.stringify(schema[key]) === JSON.stringify(parentSeen.def[key])) delete schema[key]; + } + } + } + ctx.override({ + zodSchema, + jsonSchema: schema, + path: seen.path ?? [] + }); + }; + for (const entry of [...ctx.seen.entries()].reverse()) flattenRef(entry[0]); + const result = {}; + if (ctx.target === "draft-2020-12") result.$schema = "https://json-schema.org/draft/2020-12/schema"; + else if (ctx.target === "draft-07") result.$schema = "http://json-schema.org/draft-07/schema#"; + else if (ctx.target === "draft-04") result.$schema = "http://json-schema.org/draft-04/schema#"; + else if (ctx.target === "openapi-3.0") {} + if (ctx.external?.uri) { + const id = ctx.external.registry.get(schema)?.id; + if (!id) throw new Error("Schema is missing an `id` property"); + result.$id = ctx.external.uri(id); + } + Object.assign(result, root.def ?? root.schema); + const rootMetaId = ctx.metadataRegistry.get(schema)?.id; + if (rootMetaId !== void 0 && result.id === rootMetaId) delete result.id; + const defs = ctx.external?.defs ?? {}; + for (const entry of ctx.seen.entries()) { + const seen = entry[1]; + if (seen.def && seen.defId) { + if (seen.def.id === seen.defId) delete seen.def.id; + defs[seen.defId] = seen.def; + } + } + if (ctx.external) {} else if (Object.keys(defs).length > 0) if (ctx.target === "draft-2020-12") result.$defs = defs; + else result.definitions = defs; + try { + const finalized = JSON.parse(JSON.stringify(result)); + Object.defineProperty(finalized, "~standard", { + value: { + ...schema["~standard"], + jsonSchema: { + input: createStandardJSONSchemaMethod(schema, "input", ctx.processors), + output: createStandardJSONSchemaMethod(schema, "output", ctx.processors) + } + }, + enumerable: false, + writable: false + }); + return finalized; + } catch (_err) { + throw new Error("Error converting schema to JSON."); + } +} +function isTransforming(_schema, _ctx) { + const ctx = _ctx ?? { seen: /* @__PURE__ */ new Set() }; + if (ctx.seen.has(_schema)) return false; + ctx.seen.add(_schema); + const def = _schema._zod.def; + if (def.type === "transform") return true; + if (def.type === "array") return isTransforming(def.element, ctx); + if (def.type === "set") return isTransforming(def.valueType, ctx); + if (def.type === "lazy") return isTransforming(def.getter(), ctx); + if (def.type === "promise" || def.type === "optional" || def.type === "nonoptional" || def.type === "nullable" || def.type === "readonly" || def.type === "default" || def.type === "prefault") return isTransforming(def.innerType, ctx); + if (def.type === "intersection") return isTransforming(def.left, ctx) || isTransforming(def.right, ctx); + if (def.type === "record" || def.type === "map") return isTransforming(def.keyType, ctx) || isTransforming(def.valueType, ctx); + if (def.type === "pipe") { + if (_schema._zod.traits.has("$ZodCodec")) return true; + return isTransforming(def.in, ctx) || isTransforming(def.out, ctx); + } + if (def.type === "object") { + for (const key in def.shape) if (isTransforming(def.shape[key], ctx)) return true; + return false; + } + if (def.type === "union") { + for (const option of def.options) if (isTransforming(option, ctx)) return true; + return false; + } + if (def.type === "tuple") { + for (const item of def.items) if (isTransforming(item, ctx)) return true; + if (def.rest && isTransforming(def.rest, ctx)) return true; + return false; + } + return false; +} +/** +* Creates a toJSONSchema method for a schema instance. +* This encapsulates the logic of initializing context, processing, extracting defs, and finalizing. +*/ +var createToJSONSchemaMethod = (schema, processors = {}) => (params) => { + const ctx = initializeContext({ + ...params, + processors + }); + process$2(schema, ctx); + extractDefs(ctx, schema); + return finalize(ctx, schema); +}; +var createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) => { + const { libraryOptions, target } = params ?? {}; + const ctx = initializeContext({ + ...libraryOptions ?? {}, + target, + io, + processors + }); + process$2(schema, ctx); + extractDefs(ctx, schema); + return finalize(ctx, schema); +}; +//#endregion +//#region node_modules/zod/v4/core/json-schema-processors.js +var formatMap = { + guid: "uuid", + url: "uri", + datetime: "date-time", + json_string: "json-string", + regex: "" +}; +var stringProcessor = (schema, ctx, _json, _params) => { + const json = _json; + json.type = "string"; + const { minimum, maximum, format, patterns, contentEncoding } = schema._zod.bag; + if (typeof minimum === "number") json.minLength = minimum; + if (typeof maximum === "number") json.maxLength = maximum; + if (format) { + json.format = formatMap[format] ?? format; + if (json.format === "") delete json.format; + if (format === "time") delete json.format; + } + if (contentEncoding) json.contentEncoding = contentEncoding; + if (patterns && patterns.size > 0) { + const regexes = [...patterns]; + if (regexes.length === 1) json.pattern = regexes[0].source; + else if (regexes.length > 1) json.allOf = [...regexes.map((regex) => ({ + ...ctx.target === "draft-07" || ctx.target === "draft-04" || ctx.target === "openapi-3.0" ? { type: "string" } : {}, + pattern: regex.source + }))]; + } +}; +var numberProcessor = (schema, ctx, _json, _params) => { + const json = _json; + const { minimum, maximum, format, multipleOf, exclusiveMaximum, exclusiveMinimum } = schema._zod.bag; + if (typeof format === "string" && format.includes("int")) json.type = "integer"; + else json.type = "number"; + const exMin = typeof exclusiveMinimum === "number" && exclusiveMinimum >= (minimum ?? Number.NEGATIVE_INFINITY); + const exMax = typeof exclusiveMaximum === "number" && exclusiveMaximum <= (maximum ?? Number.POSITIVE_INFINITY); + const legacy = ctx.target === "draft-04" || ctx.target === "openapi-3.0"; + if (exMin) if (legacy) { + json.minimum = exclusiveMinimum; + json.exclusiveMinimum = true; + } else json.exclusiveMinimum = exclusiveMinimum; + else if (typeof minimum === "number") json.minimum = minimum; + if (exMax) if (legacy) { + json.maximum = exclusiveMaximum; + json.exclusiveMaximum = true; + } else json.exclusiveMaximum = exclusiveMaximum; + else if (typeof maximum === "number") json.maximum = maximum; + if (typeof multipleOf === "number") json.multipleOf = multipleOf; +}; +var booleanProcessor = (_schema, _ctx, json, _params) => { + json.type = "boolean"; +}; +var nullProcessor = (_schema, ctx, json, _params) => { + if (ctx.target === "openapi-3.0") { + json.type = "string"; + json.nullable = true; + json.enum = [null]; + } else json.type = "null"; +}; +var neverProcessor = (_schema, _ctx, json, _params) => { + json.not = {}; +}; +var enumProcessor = (schema, _ctx, json, _params) => { + const def = schema._zod.def; + const values = getEnumValues(def.entries); + if (values.every((v) => typeof v === "number")) json.type = "number"; + if (values.every((v) => typeof v === "string")) json.type = "string"; + json.enum = values; +}; +var literalProcessor = (schema, ctx, json, _params) => { + const def = schema._zod.def; + const vals = []; + for (const val of def.values) if (val === void 0) { + if (ctx.unrepresentable === "throw") throw new Error("Literal `undefined` cannot be represented in JSON Schema"); + } else if (typeof val === "bigint") if (ctx.unrepresentable === "throw") throw new Error("BigInt literals cannot be represented in JSON Schema"); + else vals.push(Number(val)); + else vals.push(val); + if (vals.length === 0) {} else if (vals.length === 1) { + const val = vals[0]; + json.type = val === null ? "null" : typeof val; + if (ctx.target === "draft-04" || ctx.target === "openapi-3.0") json.enum = [val]; + else json.const = val; + } else { + if (vals.every((v) => typeof v === "number")) json.type = "number"; + if (vals.every((v) => typeof v === "string")) json.type = "string"; + if (vals.every((v) => typeof v === "boolean")) json.type = "boolean"; + if (vals.every((v) => v === null)) json.type = "null"; + json.enum = vals; + } +}; +var customProcessor = (_schema, ctx, _json, _params) => { + if (ctx.unrepresentable === "throw") throw new Error("Custom types cannot be represented in JSON Schema"); +}; +var transformProcessor = (_schema, ctx, _json, _params) => { + if (ctx.unrepresentable === "throw") throw new Error("Transforms cannot be represented in JSON Schema"); +}; +var arrayProcessor = (schema, ctx, _json, params) => { + const json = _json; + const def = schema._zod.def; + const { minimum, maximum } = schema._zod.bag; + if (typeof minimum === "number") json.minItems = minimum; + if (typeof maximum === "number") json.maxItems = maximum; + json.type = "array"; + json.items = process$2(def.element, ctx, { + ...params, + path: [...params.path, "items"] + }); +}; +var objectProcessor = (schema, ctx, _json, params) => { + const json = _json; + const def = schema._zod.def; + json.type = "object"; + json.properties = {}; + const shape = def.shape; + for (const key in shape) json.properties[key] = process$2(shape[key], ctx, { + ...params, + path: [ + ...params.path, + "properties", + key + ] + }); + const allKeys = new Set(Object.keys(shape)); + const requiredKeys = new Set([...allKeys].filter((key) => { + const v = def.shape[key]._zod; + if (ctx.io === "input") return v.optin === void 0; + else return v.optout === void 0; + })); + if (requiredKeys.size > 0) json.required = Array.from(requiredKeys); + if (def.catchall?._zod.def.type === "never") json.additionalProperties = false; + else if (!def.catchall) { + if (ctx.io === "output") json.additionalProperties = false; + } else if (def.catchall) json.additionalProperties = process$2(def.catchall, ctx, { + ...params, + path: [...params.path, "additionalProperties"] + }); +}; +var unionProcessor = (schema, ctx, json, params) => { + const def = schema._zod.def; + const isExclusive = def.inclusive === false; + const options = def.options.map((x, i) => process$2(x, ctx, { + ...params, + path: [ + ...params.path, + isExclusive ? "oneOf" : "anyOf", + i + ] + })); + if (isExclusive) json.oneOf = options; + else json.anyOf = options; +}; +var intersectionProcessor = (schema, ctx, json, params) => { + const def = schema._zod.def; + const a = process$2(def.left, ctx, { + ...params, + path: [ + ...params.path, + "allOf", + 0 + ] + }); + const b = process$2(def.right, ctx, { + ...params, + path: [ + ...params.path, + "allOf", + 1 + ] + }); + const isSimpleIntersection = (val) => "allOf" in val && Object.keys(val).length === 1; + json.allOf = [...isSimpleIntersection(a) ? a.allOf : [a], ...isSimpleIntersection(b) ? b.allOf : [b]]; +}; +var recordProcessor = (schema, ctx, _json, params) => { + const json = _json; + const def = schema._zod.def; + json.type = "object"; + const keyType = def.keyType; + const patterns = keyType._zod.bag?.patterns; + if (def.mode === "loose" && patterns && patterns.size > 0) { + const valueSchema = process$2(def.valueType, ctx, { + ...params, + path: [ + ...params.path, + "patternProperties", + "*" + ] + }); + json.patternProperties = {}; + for (const pattern of patterns) json.patternProperties[pattern.source] = valueSchema; + } else { + if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") json.propertyNames = process$2(def.keyType, ctx, { + ...params, + path: [...params.path, "propertyNames"] + }); + json.additionalProperties = process$2(def.valueType, ctx, { + ...params, + path: [...params.path, "additionalProperties"] + }); + } + const keyValues = keyType._zod.values; + if (keyValues) { + const validKeyValues = [...keyValues].filter((v) => typeof v === "string" || typeof v === "number"); + if (validKeyValues.length > 0) json.required = validKeyValues; + } +}; +var nullableProcessor = (schema, ctx, json, params) => { + const def = schema._zod.def; + const inner = process$2(def.innerType, ctx, params); + const seen = ctx.seen.get(schema); + if (ctx.target === "openapi-3.0") { + seen.ref = def.innerType; + json.nullable = true; + } else json.anyOf = [inner, { type: "null" }]; +}; +var nonoptionalProcessor = (schema, ctx, _json, params) => { + const def = schema._zod.def; + process$2(def.innerType, ctx, params); + const seen = ctx.seen.get(schema); + seen.ref = def.innerType; +}; +var defaultProcessor = (schema, ctx, json, params) => { + const def = schema._zod.def; + process$2(def.innerType, ctx, params); + const seen = ctx.seen.get(schema); + seen.ref = def.innerType; + json.default = JSON.parse(JSON.stringify(def.defaultValue)); +}; +var prefaultProcessor = (schema, ctx, json, params) => { + const def = schema._zod.def; + process$2(def.innerType, ctx, params); + const seen = ctx.seen.get(schema); + seen.ref = def.innerType; + if (ctx.io === "input") json._prefault = JSON.parse(JSON.stringify(def.defaultValue)); +}; +var catchProcessor = (schema, ctx, json, params) => { + const def = schema._zod.def; + process$2(def.innerType, ctx, params); + const seen = ctx.seen.get(schema); + seen.ref = def.innerType; + let catchValue; + try { + catchValue = def.catchValue(void 0); + } catch { + throw new Error("Dynamic catch values are not supported in JSON Schema"); + } + json.default = catchValue; +}; +var pipeProcessor = (schema, ctx, _json, params) => { + const def = schema._zod.def; + const inIsTransform = def.in._zod.traits.has("$ZodTransform"); + const innerType = ctx.io === "input" ? inIsTransform ? def.out : def.in : def.out; + process$2(innerType, ctx, params); + const seen = ctx.seen.get(schema); + seen.ref = innerType; +}; +var readonlyProcessor = (schema, ctx, json, params) => { + const def = schema._zod.def; + process$2(def.innerType, ctx, params); + const seen = ctx.seen.get(schema); + seen.ref = def.innerType; + json.readOnly = true; +}; +var optionalProcessor = (schema, ctx, _json, params) => { + const def = schema._zod.def; + process$2(def.innerType, ctx, params); + const seen = ctx.seen.get(schema); + seen.ref = def.innerType; +}; +//#endregion +//#region node_modules/zod/v4/classic/iso.js +var ZodISODateTime = /*@__PURE__*/ $constructor("ZodISODateTime", (inst, def) => { + $ZodISODateTime.init(inst, def); + ZodStringFormat.init(inst, def); +}); +function datetime(params) { + return /* @__PURE__ */ _isoDateTime(ZodISODateTime, params); +} +var ZodISODate = /*@__PURE__*/ $constructor("ZodISODate", (inst, def) => { + $ZodISODate.init(inst, def); + ZodStringFormat.init(inst, def); +}); +function date(params) { + return /* @__PURE__ */ _isoDate(ZodISODate, params); +} +var ZodISOTime = /*@__PURE__*/ $constructor("ZodISOTime", (inst, def) => { + $ZodISOTime.init(inst, def); + ZodStringFormat.init(inst, def); +}); +function time(params) { + return /* @__PURE__ */ _isoTime(ZodISOTime, params); +} +var ZodISODuration = /*@__PURE__*/ $constructor("ZodISODuration", (inst, def) => { + $ZodISODuration.init(inst, def); + ZodStringFormat.init(inst, def); +}); +function duration(params) { + return /* @__PURE__ */ _isoDuration(ZodISODuration, params); +} +//#endregion +//#region node_modules/zod/v4/classic/errors.js +var initializer = (inst, issues) => { + $ZodError.init(inst, issues); + inst.name = "ZodError"; + Object.defineProperties(inst, { + format: { value: (mapper) => formatError(inst, mapper) }, + flatten: { value: (mapper) => flattenError(inst, mapper) }, + addIssue: { value: (issue) => { + inst.issues.push(issue); + inst.message = JSON.stringify(inst.issues, jsonStringifyReplacer, 2); + } }, + addIssues: { value: (issues) => { + inst.issues.push(...issues); + inst.message = JSON.stringify(inst.issues, jsonStringifyReplacer, 2); + } }, + isEmpty: { get() { + return inst.issues.length === 0; + } } + }); +}; +var ZodError = /*@__PURE__*/ $constructor("ZodError", initializer); +var ZodRealError = /*@__PURE__*/ $constructor("ZodError", initializer, { Parent: Error }); +//#endregion +//#region node_modules/zod/v4/classic/parse.js +var parse$2 = /* @__PURE__ */ _parse(ZodRealError); +var parseAsync = /* @__PURE__ */ _parseAsync(ZodRealError); +var safeParse$1 = /* @__PURE__ */ _safeParse(ZodRealError); +var safeParseAsync = /* @__PURE__ */ _safeParseAsync(ZodRealError); +var encode = /* @__PURE__ */ _encode(ZodRealError); +var decode = /* @__PURE__ */ _decode(ZodRealError); +var encodeAsync = /* @__PURE__ */ _encodeAsync(ZodRealError); +var decodeAsync = /* @__PURE__ */ _decodeAsync(ZodRealError); +var safeEncode = /* @__PURE__ */ _safeEncode(ZodRealError); +var safeDecode = /* @__PURE__ */ _safeDecode(ZodRealError); +var safeEncodeAsync = /* @__PURE__ */ _safeEncodeAsync(ZodRealError); +var safeDecodeAsync = /* @__PURE__ */ _safeDecodeAsync(ZodRealError); +//#endregion +//#region node_modules/zod/v4/classic/schemas.js +var _installedGroups = /* @__PURE__ */ new WeakMap(); +function _installLazyMethods(inst, group, methods) { + const proto = Object.getPrototypeOf(inst); + let installed = _installedGroups.get(proto); + if (!installed) { + installed = /* @__PURE__ */ new Set(); + _installedGroups.set(proto, installed); + } + if (installed.has(group)) return; + installed.add(group); + for (const key in methods) { + const fn = methods[key]; + Object.defineProperty(proto, key, { + configurable: true, + enumerable: false, + get() { + const bound = fn.bind(this); + Object.defineProperty(this, key, { + configurable: true, + writable: true, + enumerable: true, + value: bound + }); + return bound; + }, + set(v) { + Object.defineProperty(this, key, { + configurable: true, + writable: true, + enumerable: true, + value: v + }); + } + }); + } +} +var ZodType = /*@__PURE__*/ $constructor("ZodType", (inst, def) => { + $ZodType.init(inst, def); + Object.assign(inst["~standard"], { jsonSchema: { + input: createStandardJSONSchemaMethod(inst, "input"), + output: createStandardJSONSchemaMethod(inst, "output") + } }); + inst.toJSONSchema = createToJSONSchemaMethod(inst, {}); + inst.def = def; + inst.type = def.type; + Object.defineProperty(inst, "_def", { value: def }); + inst.parse = (data, params) => parse$2(inst, data, params, { callee: inst.parse }); + inst.safeParse = (data, params) => safeParse$1(inst, data, params); + inst.parseAsync = async (data, params) => parseAsync(inst, data, params, { callee: inst.parseAsync }); + inst.safeParseAsync = async (data, params) => safeParseAsync(inst, data, params); + inst.spa = inst.safeParseAsync; + inst.encode = (data, params) => encode(inst, data, params); + inst.decode = (data, params) => decode(inst, data, params); + inst.encodeAsync = async (data, params) => encodeAsync(inst, data, params); + inst.decodeAsync = async (data, params) => decodeAsync(inst, data, params); + inst.safeEncode = (data, params) => safeEncode(inst, data, params); + inst.safeDecode = (data, params) => safeDecode(inst, data, params); + inst.safeEncodeAsync = async (data, params) => safeEncodeAsync(inst, data, params); + inst.safeDecodeAsync = async (data, params) => safeDecodeAsync(inst, data, params); + _installLazyMethods(inst, "ZodType", { + check(...chks) { + const def = this.def; + return this.clone(mergeDefs(def, { checks: [...def.checks ?? [], ...chks.map((ch) => typeof ch === "function" ? { _zod: { + check: ch, + def: { check: "custom" }, + onattach: [] + } } : ch)] }), { parent: true }); + }, + with(...chks) { + return this.check(...chks); + }, + clone(def, params) { + return clone(this, def, params); + }, + brand() { + return this; + }, + register(reg, meta) { + reg.add(this, meta); + return this; + }, + refine(check, params) { + return this.check(refine(check, params)); + }, + superRefine(refinement, params) { + return this.check(superRefine(refinement, params)); + }, + overwrite(fn) { + return this.check(/* @__PURE__ */ _overwrite(fn)); + }, + optional() { + return optional(this); + }, + exactOptional() { + return exactOptional(this); + }, + nullable() { + return nullable(this); + }, + nullish() { + return optional(nullable(this)); + }, + nonoptional(params) { + return nonoptional(this, params); + }, + array() { + return array(this); + }, + or(arg) { + return union([this, arg]); + }, + and(arg) { + return intersection(this, arg); + }, + transform(tx) { + return pipe(this, transform(tx)); + }, + default(d) { + return _default(this, d); + }, + prefault(d) { + return prefault(this, d); + }, + catch(params) { + return _catch(this, params); + }, + pipe(target) { + return pipe(this, target); + }, + readonly() { + return readonly(this); + }, + describe(description) { + const cl = this.clone(); + globalRegistry.add(cl, { description }); + return cl; + }, + meta(...args) { + if (args.length === 0) return globalRegistry.get(this); + const cl = this.clone(); + globalRegistry.add(cl, args[0]); + return cl; + }, + isOptional() { + return this.safeParse(void 0).success; + }, + isNullable() { + return this.safeParse(null).success; + }, + apply(fn) { + return fn(this); + } + }); + Object.defineProperty(inst, "description", { + get() { + return globalRegistry.get(inst)?.description; + }, + configurable: true + }); + return inst; +}); +/** @internal */ +var _ZodString = /*@__PURE__*/ $constructor("_ZodString", (inst, def) => { + $ZodString.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => stringProcessor(inst, ctx, json, params); + const bag = inst._zod.bag; + inst.format = bag.format ?? null; + inst.minLength = bag.minimum ?? null; + inst.maxLength = bag.maximum ?? null; + _installLazyMethods(inst, "_ZodString", { + regex(...args) { + return this.check(/* @__PURE__ */ _regex(...args)); + }, + includes(...args) { + return this.check(/* @__PURE__ */ _includes(...args)); + }, + startsWith(...args) { + return this.check(/* @__PURE__ */ _startsWith(...args)); + }, + endsWith(...args) { + return this.check(/* @__PURE__ */ _endsWith(...args)); + }, + min(...args) { + return this.check(/* @__PURE__ */ _minLength(...args)); + }, + max(...args) { + return this.check(/* @__PURE__ */ _maxLength(...args)); + }, + length(...args) { + return this.check(/* @__PURE__ */ _length(...args)); + }, + nonempty(...args) { + return this.check(/* @__PURE__ */ _minLength(1, ...args)); + }, + lowercase(params) { + return this.check(/* @__PURE__ */ _lowercase(params)); + }, + uppercase(params) { + return this.check(/* @__PURE__ */ _uppercase(params)); + }, + trim() { + return this.check(/* @__PURE__ */ _trim()); + }, + normalize(...args) { + return this.check(/* @__PURE__ */ _normalize(...args)); + }, + toLowerCase() { + return this.check(/* @__PURE__ */ _toLowerCase()); + }, + toUpperCase() { + return this.check(/* @__PURE__ */ _toUpperCase()); + }, + slugify() { + return this.check(/* @__PURE__ */ _slugify()); + } + }); +}); +var ZodString = /*@__PURE__*/ $constructor("ZodString", (inst, def) => { + $ZodString.init(inst, def); + _ZodString.init(inst, def); + inst.email = (params) => inst.check(/* @__PURE__ */ _email(ZodEmail, params)); + inst.url = (params) => inst.check(/* @__PURE__ */ _url(ZodURL, params)); + inst.jwt = (params) => inst.check(/* @__PURE__ */ _jwt(ZodJWT, params)); + inst.emoji = (params) => inst.check(/* @__PURE__ */ _emoji(ZodEmoji, params)); + inst.guid = (params) => inst.check(/* @__PURE__ */ _guid(ZodGUID, params)); + inst.uuid = (params) => inst.check(/* @__PURE__ */ _uuid(ZodUUID, params)); + inst.uuidv4 = (params) => inst.check(/* @__PURE__ */ _uuidv4(ZodUUID, params)); + inst.uuidv6 = (params) => inst.check(/* @__PURE__ */ _uuidv6(ZodUUID, params)); + inst.uuidv7 = (params) => inst.check(/* @__PURE__ */ _uuidv7(ZodUUID, params)); + inst.nanoid = (params) => inst.check(/* @__PURE__ */ _nanoid(ZodNanoID, params)); + inst.guid = (params) => inst.check(/* @__PURE__ */ _guid(ZodGUID, params)); + inst.cuid = (params) => inst.check(/* @__PURE__ */ _cuid(ZodCUID, params)); + inst.cuid2 = (params) => inst.check(/* @__PURE__ */ _cuid2(ZodCUID2, params)); + inst.ulid = (params) => inst.check(/* @__PURE__ */ _ulid(ZodULID, params)); + inst.base64 = (params) => inst.check(/* @__PURE__ */ _base64(ZodBase64, params)); + inst.base64url = (params) => inst.check(/* @__PURE__ */ _base64url(ZodBase64URL, params)); + inst.xid = (params) => inst.check(/* @__PURE__ */ _xid(ZodXID, params)); + inst.ksuid = (params) => inst.check(/* @__PURE__ */ _ksuid(ZodKSUID, params)); + inst.ipv4 = (params) => inst.check(/* @__PURE__ */ _ipv4(ZodIPv4, params)); + inst.ipv6 = (params) => inst.check(/* @__PURE__ */ _ipv6(ZodIPv6, params)); + inst.cidrv4 = (params) => inst.check(/* @__PURE__ */ _cidrv4(ZodCIDRv4, params)); + inst.cidrv6 = (params) => inst.check(/* @__PURE__ */ _cidrv6(ZodCIDRv6, params)); + inst.e164 = (params) => inst.check(/* @__PURE__ */ _e164(ZodE164, params)); + inst.datetime = (params) => inst.check(datetime(params)); + inst.date = (params) => inst.check(date(params)); + inst.time = (params) => inst.check(time(params)); + inst.duration = (params) => inst.check(duration(params)); +}); +function string$1(params) { + return /* @__PURE__ */ _string(ZodString, params); +} +var ZodStringFormat = /*@__PURE__*/ $constructor("ZodStringFormat", (inst, def) => { + $ZodStringFormat.init(inst, def); + _ZodString.init(inst, def); +}); +var ZodEmail = /*@__PURE__*/ $constructor("ZodEmail", (inst, def) => { + $ZodEmail.init(inst, def); + ZodStringFormat.init(inst, def); +}); +var ZodGUID = /*@__PURE__*/ $constructor("ZodGUID", (inst, def) => { + $ZodGUID.init(inst, def); + ZodStringFormat.init(inst, def); +}); +var ZodUUID = /*@__PURE__*/ $constructor("ZodUUID", (inst, def) => { + $ZodUUID.init(inst, def); + ZodStringFormat.init(inst, def); +}); +var ZodURL = /*@__PURE__*/ $constructor("ZodURL", (inst, def) => { + $ZodURL.init(inst, def); + ZodStringFormat.init(inst, def); +}); +var ZodEmoji = /*@__PURE__*/ $constructor("ZodEmoji", (inst, def) => { + $ZodEmoji.init(inst, def); + ZodStringFormat.init(inst, def); +}); +var ZodNanoID = /*@__PURE__*/ $constructor("ZodNanoID", (inst, def) => { + $ZodNanoID.init(inst, def); + ZodStringFormat.init(inst, def); +}); +/** +* @deprecated CUID v1 is deprecated by its authors due to information leakage +* (timestamps embedded in the id). Use {@link ZodCUID2} instead. +* See https://github.com/paralleldrive/cuid. +*/ +var ZodCUID = /*@__PURE__*/ $constructor("ZodCUID", (inst, def) => { + $ZodCUID.init(inst, def); + ZodStringFormat.init(inst, def); +}); +var ZodCUID2 = /*@__PURE__*/ $constructor("ZodCUID2", (inst, def) => { + $ZodCUID2.init(inst, def); + ZodStringFormat.init(inst, def); +}); +var ZodULID = /*@__PURE__*/ $constructor("ZodULID", (inst, def) => { + $ZodULID.init(inst, def); + ZodStringFormat.init(inst, def); +}); +var ZodXID = /*@__PURE__*/ $constructor("ZodXID", (inst, def) => { + $ZodXID.init(inst, def); + ZodStringFormat.init(inst, def); +}); +var ZodKSUID = /*@__PURE__*/ $constructor("ZodKSUID", (inst, def) => { + $ZodKSUID.init(inst, def); + ZodStringFormat.init(inst, def); +}); +var ZodIPv4 = /*@__PURE__*/ $constructor("ZodIPv4", (inst, def) => { + $ZodIPv4.init(inst, def); + ZodStringFormat.init(inst, def); +}); +var ZodIPv6 = /*@__PURE__*/ $constructor("ZodIPv6", (inst, def) => { + $ZodIPv6.init(inst, def); + ZodStringFormat.init(inst, def); +}); +var ZodCIDRv4 = /*@__PURE__*/ $constructor("ZodCIDRv4", (inst, def) => { + $ZodCIDRv4.init(inst, def); + ZodStringFormat.init(inst, def); +}); +var ZodCIDRv6 = /*@__PURE__*/ $constructor("ZodCIDRv6", (inst, def) => { + $ZodCIDRv6.init(inst, def); + ZodStringFormat.init(inst, def); +}); +var ZodBase64 = /*@__PURE__*/ $constructor("ZodBase64", (inst, def) => { + $ZodBase64.init(inst, def); + ZodStringFormat.init(inst, def); +}); +var ZodBase64URL = /*@__PURE__*/ $constructor("ZodBase64URL", (inst, def) => { + $ZodBase64URL.init(inst, def); + ZodStringFormat.init(inst, def); +}); +var ZodE164 = /*@__PURE__*/ $constructor("ZodE164", (inst, def) => { + $ZodE164.init(inst, def); + ZodStringFormat.init(inst, def); +}); +var ZodJWT = /*@__PURE__*/ $constructor("ZodJWT", (inst, def) => { + $ZodJWT.init(inst, def); + ZodStringFormat.init(inst, def); +}); +var ZodNumber = /*@__PURE__*/ $constructor("ZodNumber", (inst, def) => { + $ZodNumber.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => numberProcessor(inst, ctx, json, params); + _installLazyMethods(inst, "ZodNumber", { + gt(value, params) { + return this.check(/* @__PURE__ */ _gt(value, params)); + }, + gte(value, params) { + return this.check(/* @__PURE__ */ _gte(value, params)); + }, + min(value, params) { + return this.check(/* @__PURE__ */ _gte(value, params)); + }, + lt(value, params) { + return this.check(/* @__PURE__ */ _lt(value, params)); + }, + lte(value, params) { + return this.check(/* @__PURE__ */ _lte(value, params)); + }, + max(value, params) { + return this.check(/* @__PURE__ */ _lte(value, params)); + }, + int(params) { + return this.check(int$2(params)); + }, + safe(params) { + return this.check(int$2(params)); + }, + positive(params) { + return this.check(/* @__PURE__ */ _gt(0, params)); + }, + nonnegative(params) { + return this.check(/* @__PURE__ */ _gte(0, params)); + }, + negative(params) { + return this.check(/* @__PURE__ */ _lt(0, params)); + }, + nonpositive(params) { + return this.check(/* @__PURE__ */ _lte(0, params)); + }, + multipleOf(value, params) { + return this.check(/* @__PURE__ */ _multipleOf(value, params)); + }, + step(value, params) { + return this.check(/* @__PURE__ */ _multipleOf(value, params)); + }, + finite() { + return this; + } + }); + const bag = inst._zod.bag; + inst.minValue = Math.max(bag.minimum ?? Number.NEGATIVE_INFINITY, bag.exclusiveMinimum ?? Number.NEGATIVE_INFINITY) ?? null; + inst.maxValue = Math.min(bag.maximum ?? Number.POSITIVE_INFINITY, bag.exclusiveMaximum ?? Number.POSITIVE_INFINITY) ?? null; + inst.isInt = (bag.format ?? "").includes("int") || Number.isSafeInteger(bag.multipleOf ?? .5); + inst.isFinite = true; + inst.format = bag.format ?? null; +}); +function number(params) { + return /* @__PURE__ */ _number(ZodNumber, params); +} +var ZodNumberFormat = /*@__PURE__*/ $constructor("ZodNumberFormat", (inst, def) => { + $ZodNumberFormat.init(inst, def); + ZodNumber.init(inst, def); +}); +function int$2(params) { + return /* @__PURE__ */ _int(ZodNumberFormat, params); +} +var ZodBoolean = /*@__PURE__*/ $constructor("ZodBoolean", (inst, def) => { + $ZodBoolean.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => booleanProcessor(inst, ctx, json, params); +}); +function boolean(params) { + return /* @__PURE__ */ _boolean(ZodBoolean, params); +} +var ZodNull = /*@__PURE__*/ $constructor("ZodNull", (inst, def) => { + $ZodNull.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => nullProcessor(inst, ctx, json, params); +}); +function _null(params) { + return /* @__PURE__ */ _null$1(ZodNull, params); +} +var ZodUnknown = /*@__PURE__*/ $constructor("ZodUnknown", (inst, def) => { + $ZodUnknown.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => void 0; +}); +function unknown() { + return /* @__PURE__ */ _unknown(ZodUnknown); +} +var ZodNever = /*@__PURE__*/ $constructor("ZodNever", (inst, def) => { + $ZodNever.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => neverProcessor(inst, ctx, json, params); +}); +function never(params) { + return /* @__PURE__ */ _never(ZodNever, params); +} +var ZodArray = /*@__PURE__*/ $constructor("ZodArray", (inst, def) => { + $ZodArray.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => arrayProcessor(inst, ctx, json, params); + inst.element = def.element; + _installLazyMethods(inst, "ZodArray", { + min(n, params) { + return this.check(/* @__PURE__ */ _minLength(n, params)); + }, + nonempty(params) { + return this.check(/* @__PURE__ */ _minLength(1, params)); + }, + max(n, params) { + return this.check(/* @__PURE__ */ _maxLength(n, params)); + }, + length(n, params) { + return this.check(/* @__PURE__ */ _length(n, params)); + }, + unwrap() { + return this.element; + } + }); +}); +function array(element, params) { + return /* @__PURE__ */ _array(ZodArray, element, params); +} +var ZodObject = /*@__PURE__*/ $constructor("ZodObject", (inst, def) => { + $ZodObjectJIT.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => objectProcessor(inst, ctx, json, params); + defineLazy(inst, "shape", () => { + return def.shape; + }); + _installLazyMethods(inst, "ZodObject", { + keyof() { + return _enum(Object.keys(this._zod.def.shape)); + }, + catchall(catchall) { + return this.clone({ + ...this._zod.def, + catchall + }); + }, + passthrough() { + return this.clone({ + ...this._zod.def, + catchall: unknown() + }); + }, + loose() { + return this.clone({ + ...this._zod.def, + catchall: unknown() + }); + }, + strict() { + return this.clone({ + ...this._zod.def, + catchall: never() + }); + }, + strip() { + return this.clone({ + ...this._zod.def, + catchall: void 0 + }); + }, + extend(incoming) { + return extend(this, incoming); + }, + safeExtend(incoming) { + return safeExtend(this, incoming); + }, + merge(other) { + return merge$2(this, other); + }, + pick(mask) { + return pick(this, mask); + }, + omit(mask) { + return omit$1(this, mask); + }, + partial(...args) { + return partial(ZodOptional, this, args[0]); + }, + required(...args) { + return required(ZodNonOptional, this, args[0]); + } + }); +}); +function object(shape, params) { + return new ZodObject({ + type: "object", + shape: shape ?? {}, + ...normalizeParams(params) + }); +} +function strictObject(shape, params) { + return new ZodObject({ + type: "object", + shape, + catchall: never(), + ...normalizeParams(params) + }); +} +function looseObject(shape, params) { + return new ZodObject({ + type: "object", + shape, + catchall: unknown(), + ...normalizeParams(params) + }); +} +var ZodUnion = /*@__PURE__*/ $constructor("ZodUnion", (inst, def) => { + $ZodUnion.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => unionProcessor(inst, ctx, json, params); + inst.options = def.options; +}); +function union(options, params) { + return new ZodUnion({ + type: "union", + options, + ...normalizeParams(params) + }); +} +var ZodIntersection = /*@__PURE__*/ $constructor("ZodIntersection", (inst, def) => { + $ZodIntersection.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => intersectionProcessor(inst, ctx, json, params); +}); +function intersection(left, right) { + return new ZodIntersection({ + type: "intersection", + left, + right + }); +} +var ZodRecord = /*@__PURE__*/ $constructor("ZodRecord", (inst, def) => { + $ZodRecord.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => recordProcessor(inst, ctx, json, params); + inst.keyType = def.keyType; + inst.valueType = def.valueType; +}); +function record(keyType, valueType, params) { + if (!valueType || !valueType._zod) return new ZodRecord({ + type: "record", + keyType: string$1(), + valueType: keyType, + ...normalizeParams(valueType) + }); + return new ZodRecord({ + type: "record", + keyType, + valueType, + ...normalizeParams(params) + }); +} +var ZodEnum = /*@__PURE__*/ $constructor("ZodEnum", (inst, def) => { + $ZodEnum.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => enumProcessor(inst, ctx, json, params); + inst.enum = def.entries; + inst.options = Object.values(def.entries); + const keys = new Set(Object.keys(def.entries)); + inst.extract = (values, params) => { + const newEntries = {}; + for (const value of values) if (keys.has(value)) newEntries[value] = def.entries[value]; + else throw new Error(`Key ${value} not found in enum`); + return new ZodEnum({ + ...def, + checks: [], + ...normalizeParams(params), + entries: newEntries + }); + }; + inst.exclude = (values, params) => { + const newEntries = { ...def.entries }; + for (const value of values) if (keys.has(value)) delete newEntries[value]; + else throw new Error(`Key ${value} not found in enum`); + return new ZodEnum({ + ...def, + checks: [], + ...normalizeParams(params), + entries: newEntries + }); + }; +}); +function _enum(values, params) { + return new ZodEnum({ + type: "enum", + entries: Array.isArray(values) ? Object.fromEntries(values.map((v) => [v, v])) : values, + ...normalizeParams(params) + }); +} +var ZodLiteral = /*@__PURE__*/ $constructor("ZodLiteral", (inst, def) => { + $ZodLiteral.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => literalProcessor(inst, ctx, json, params); + inst.values = new Set(def.values); + Object.defineProperty(inst, "value", { get() { + if (def.values.length > 1) throw new Error("This schema contains multiple valid literal values. Use `.values` instead."); + return def.values[0]; + } }); +}); +function literal(value, params) { + return new ZodLiteral({ + type: "literal", + values: Array.isArray(value) ? value : [value], + ...normalizeParams(params) + }); +} +var ZodTransform = /*@__PURE__*/ $constructor("ZodTransform", (inst, def) => { + $ZodTransform.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => transformProcessor(inst, ctx, json, params); + inst._zod.parse = (payload, _ctx) => { + if (_ctx.direction === "backward") throw new $ZodEncodeError(inst.constructor.name); + payload.addIssue = (issue$2) => { + if (typeof issue$2 === "string") payload.issues.push(issue(issue$2, payload.value, def)); + else { + const _issue = issue$2; + if (_issue.fatal) _issue.continue = false; + _issue.code ?? (_issue.code = "custom"); + _issue.input ?? (_issue.input = payload.value); + _issue.inst ?? (_issue.inst = inst); + payload.issues.push(issue(_issue)); + } + }; + const output = def.transform(payload.value, payload); + if (output instanceof Promise) return output.then((output) => { + payload.value = output; + payload.fallback = true; + return payload; + }); + payload.value = output; + payload.fallback = true; + return payload; + }; +}); +function transform(fn) { + return new ZodTransform({ + type: "transform", + transform: fn + }); +} +var ZodOptional = /*@__PURE__*/ $constructor("ZodOptional", (inst, def) => { + $ZodOptional.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => optionalProcessor(inst, ctx, json, params); + inst.unwrap = () => inst._zod.def.innerType; +}); +function optional(innerType) { + return new ZodOptional({ + type: "optional", + innerType + }); +} +var ZodExactOptional = /*@__PURE__*/ $constructor("ZodExactOptional", (inst, def) => { + $ZodExactOptional.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => optionalProcessor(inst, ctx, json, params); + inst.unwrap = () => inst._zod.def.innerType; +}); +function exactOptional(innerType) { + return new ZodExactOptional({ + type: "optional", + innerType + }); +} +var ZodNullable = /*@__PURE__*/ $constructor("ZodNullable", (inst, def) => { + $ZodNullable.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => nullableProcessor(inst, ctx, json, params); + inst.unwrap = () => inst._zod.def.innerType; +}); +function nullable(innerType) { + return new ZodNullable({ + type: "nullable", + innerType + }); +} +var ZodDefault = /*@__PURE__*/ $constructor("ZodDefault", (inst, def) => { + $ZodDefault.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => defaultProcessor(inst, ctx, json, params); + inst.unwrap = () => inst._zod.def.innerType; + inst.removeDefault = inst.unwrap; +}); +function _default(innerType, defaultValue) { + return new ZodDefault({ + type: "default", + innerType, + get defaultValue() { + return typeof defaultValue === "function" ? defaultValue() : shallowClone(defaultValue); + } + }); +} +var ZodPrefault = /*@__PURE__*/ $constructor("ZodPrefault", (inst, def) => { + $ZodPrefault.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => prefaultProcessor(inst, ctx, json, params); + inst.unwrap = () => inst._zod.def.innerType; +}); +function prefault(innerType, defaultValue) { + return new ZodPrefault({ + type: "prefault", + innerType, + get defaultValue() { + return typeof defaultValue === "function" ? defaultValue() : shallowClone(defaultValue); + } + }); +} +var ZodNonOptional = /*@__PURE__*/ $constructor("ZodNonOptional", (inst, def) => { + $ZodNonOptional.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => nonoptionalProcessor(inst, ctx, json, params); + inst.unwrap = () => inst._zod.def.innerType; +}); +function nonoptional(innerType, params) { + return new ZodNonOptional({ + type: "nonoptional", + innerType, + ...normalizeParams(params) + }); +} +var ZodCatch = /*@__PURE__*/ $constructor("ZodCatch", (inst, def) => { + $ZodCatch.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => catchProcessor(inst, ctx, json, params); + inst.unwrap = () => inst._zod.def.innerType; + inst.removeCatch = inst.unwrap; +}); +function _catch(innerType, catchValue) { + return new ZodCatch({ + type: "catch", + innerType, + catchValue: typeof catchValue === "function" ? catchValue : () => catchValue + }); +} +var ZodPipe = /*@__PURE__*/ $constructor("ZodPipe", (inst, def) => { + $ZodPipe.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => pipeProcessor(inst, ctx, json, params); + inst.in = def.in; + inst.out = def.out; +}); +function pipe(in_, out) { + return new ZodPipe({ + type: "pipe", + in: in_, + out + }); +} +var ZodCodec = /*@__PURE__*/ $constructor("ZodCodec", (inst, def) => { + ZodPipe.init(inst, def); + $ZodCodec.init(inst, def); +}); +var ZodReadonly = /*@__PURE__*/ $constructor("ZodReadonly", (inst, def) => { + $ZodReadonly.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => readonlyProcessor(inst, ctx, json, params); + inst.unwrap = () => inst._zod.def.innerType; +}); +function readonly(innerType) { + return new ZodReadonly({ + type: "readonly", + innerType + }); +} +var ZodCustom = /*@__PURE__*/ $constructor("ZodCustom", (inst, def) => { + $ZodCustom.init(inst, def); + ZodType.init(inst, def); + inst._zod.processJSONSchema = (ctx, json, params) => customProcessor(inst, ctx, json, params); +}); +function refine(fn, _params = {}) { + return /* @__PURE__ */ _refine(ZodCustom, fn, _params); +} +function superRefine(fn, params) { + return /* @__PURE__ */ _superRefine(fn, params); +} +var stringbool = (...args) => /* @__PURE__ */ _stringbool({ + Codec: ZodCodec, + Boolean: ZodBoolean, + String: ZodString +}, ...args); +//#endregion +//#region node_modules/yaml/browser/dist/nodes/identity.js +var ALIAS = Symbol.for("yaml.alias"); +var DOC = Symbol.for("yaml.document"); +var MAP = Symbol.for("yaml.map"); +var PAIR = Symbol.for("yaml.pair"); +var SCALAR$1 = Symbol.for("yaml.scalar"); +var SEQ = Symbol.for("yaml.seq"); +var NODE_TYPE = Symbol.for("yaml.node.type"); +var isAlias = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === ALIAS; +var isDocument = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === DOC; +var isMap = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === MAP; +var isPair = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === PAIR; +var isScalar = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SCALAR$1; +var isSeq = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SEQ; +function isCollection(node) { + if (node && typeof node === "object") switch (node[NODE_TYPE]) { + case MAP: + case SEQ: return true; + } + return false; +} +function isNode(node) { + if (node && typeof node === "object") switch (node[NODE_TYPE]) { + case ALIAS: + case MAP: + case SCALAR$1: + case SEQ: return true; + } + return false; +} +var hasAnchor = (node) => (isScalar(node) || isCollection(node)) && !!node.anchor; +//#endregion +//#region node_modules/yaml/browser/dist/visit.js +var BREAK$1 = Symbol("break visit"); +var SKIP$1 = Symbol("skip children"); +var REMOVE$1 = Symbol("remove node"); +/** +* Apply a visitor to an AST node or document. +* +* Walks through the tree (depth-first) starting from `node`, calling a +* `visitor` function with three arguments: +* - `key`: For sequence values and map `Pair`, the node's index in the +* collection. Within a `Pair`, `'key'` or `'value'`, correspondingly. +* `null` for the root node. +* - `node`: The current node. +* - `path`: The ancestry of the current node. +* +* The return value of the visitor may be used to control the traversal: +* - `undefined` (default): Do nothing and continue +* - `visit.SKIP`: Do not visit the children of this node, continue with next +* sibling +* - `visit.BREAK`: Terminate traversal completely +* - `visit.REMOVE`: Remove the current node, then continue with the next one +* - `Node`: Replace the current node, then continue by visiting it +* - `number`: While iterating the items of a sequence or map, set the index +* of the next step. This is useful especially if the index of the current +* node has changed. +* +* If `visitor` is a single function, it will be called with all values +* encountered in the tree, including e.g. `null` values. Alternatively, +* separate visitor functions may be defined for each `Map`, `Pair`, `Seq`, +* `Alias` and `Scalar` node. To define the same visitor function for more than +* one node type, use the `Collection` (map and seq), `Value` (map, seq & scalar) +* and `Node` (alias, map, seq & scalar) targets. Of all these, only the most +* specific defined one will be used for each node. +*/ +function visit$1(node, visitor) { + const visitor_ = initVisitor(visitor); + if (isDocument(node)) { + if (visit_(null, node.contents, visitor_, Object.freeze([node])) === REMOVE$1) node.contents = null; + } else visit_(null, node, visitor_, Object.freeze([])); +} +/** Terminate visit traversal completely */ +visit$1.BREAK = BREAK$1; +/** Do not visit the children of the current node */ +visit$1.SKIP = SKIP$1; +/** Remove the current node */ +visit$1.REMOVE = REMOVE$1; +function visit_(key, node, visitor, path) { + const ctrl = callVisitor(key, node, visitor, path); + if (isNode(ctrl) || isPair(ctrl)) { + replaceNode(key, path, ctrl); + return visit_(key, ctrl, visitor, path); + } + if (typeof ctrl !== "symbol") { + if (isCollection(node)) { + path = Object.freeze(path.concat(node)); + for (let i = 0; i < node.items.length; ++i) { + const ci = visit_(i, node.items[i], visitor, path); + if (typeof ci === "number") i = ci - 1; + else if (ci === BREAK$1) return BREAK$1; + else if (ci === REMOVE$1) { + node.items.splice(i, 1); + i -= 1; + } + } + } else if (isPair(node)) { + path = Object.freeze(path.concat(node)); + const ck = visit_("key", node.key, visitor, path); + if (ck === BREAK$1) return BREAK$1; + else if (ck === REMOVE$1) node.key = null; + const cv = visit_("value", node.value, visitor, path); + if (cv === BREAK$1) return BREAK$1; + else if (cv === REMOVE$1) node.value = null; + } + } + return ctrl; +} +/** +* Apply an async visitor to an AST node or document. +* +* Walks through the tree (depth-first) starting from `node`, calling a +* `visitor` function with three arguments: +* - `key`: For sequence values and map `Pair`, the node's index in the +* collection. Within a `Pair`, `'key'` or `'value'`, correspondingly. +* `null` for the root node. +* - `node`: The current node. +* - `path`: The ancestry of the current node. +* +* The return value of the visitor may be used to control the traversal: +* - `Promise`: Must resolve to one of the following values +* - `undefined` (default): Do nothing and continue +* - `visit.SKIP`: Do not visit the children of this node, continue with next +* sibling +* - `visit.BREAK`: Terminate traversal completely +* - `visit.REMOVE`: Remove the current node, then continue with the next one +* - `Node`: Replace the current node, then continue by visiting it +* - `number`: While iterating the items of a sequence or map, set the index +* of the next step. This is useful especially if the index of the current +* node has changed. +* +* If `visitor` is a single function, it will be called with all values +* encountered in the tree, including e.g. `null` values. Alternatively, +* separate visitor functions may be defined for each `Map`, `Pair`, `Seq`, +* `Alias` and `Scalar` node. To define the same visitor function for more than +* one node type, use the `Collection` (map and seq), `Value` (map, seq & scalar) +* and `Node` (alias, map, seq & scalar) targets. Of all these, only the most +* specific defined one will be used for each node. +*/ +async function visitAsync(node, visitor) { + const visitor_ = initVisitor(visitor); + if (isDocument(node)) { + if (await visitAsync_(null, node.contents, visitor_, Object.freeze([node])) === REMOVE$1) node.contents = null; + } else await visitAsync_(null, node, visitor_, Object.freeze([])); +} +/** Terminate visit traversal completely */ +visitAsync.BREAK = BREAK$1; +/** Do not visit the children of the current node */ +visitAsync.SKIP = SKIP$1; +/** Remove the current node */ +visitAsync.REMOVE = REMOVE$1; +async function visitAsync_(key, node, visitor, path) { + const ctrl = await callVisitor(key, node, visitor, path); + if (isNode(ctrl) || isPair(ctrl)) { + replaceNode(key, path, ctrl); + return visitAsync_(key, ctrl, visitor, path); + } + if (typeof ctrl !== "symbol") { + if (isCollection(node)) { + path = Object.freeze(path.concat(node)); + for (let i = 0; i < node.items.length; ++i) { + const ci = await visitAsync_(i, node.items[i], visitor, path); + if (typeof ci === "number") i = ci - 1; + else if (ci === BREAK$1) return BREAK$1; + else if (ci === REMOVE$1) { + node.items.splice(i, 1); + i -= 1; + } + } + } else if (isPair(node)) { + path = Object.freeze(path.concat(node)); + const ck = await visitAsync_("key", node.key, visitor, path); + if (ck === BREAK$1) return BREAK$1; + else if (ck === REMOVE$1) node.key = null; + const cv = await visitAsync_("value", node.value, visitor, path); + if (cv === BREAK$1) return BREAK$1; + else if (cv === REMOVE$1) node.value = null; + } + } + return ctrl; +} +function initVisitor(visitor) { + if (typeof visitor === "object" && (visitor.Collection || visitor.Node || visitor.Value)) return Object.assign({ + Alias: visitor.Node, + Map: visitor.Node, + Scalar: visitor.Node, + Seq: visitor.Node + }, visitor.Value && { + Map: visitor.Value, + Scalar: visitor.Value, + Seq: visitor.Value + }, visitor.Collection && { + Map: visitor.Collection, + Seq: visitor.Collection + }, visitor); + return visitor; +} +function callVisitor(key, node, visitor, path) { + if (typeof visitor === "function") return visitor(key, node, path); + if (isMap(node)) return visitor.Map?.(key, node, path); + if (isSeq(node)) return visitor.Seq?.(key, node, path); + if (isPair(node)) return visitor.Pair?.(key, node, path); + if (isScalar(node)) return visitor.Scalar?.(key, node, path); + if (isAlias(node)) return visitor.Alias?.(key, node, path); +} +function replaceNode(key, path, node) { + const parent = path[path.length - 1]; + if (isCollection(parent)) parent.items[key] = node; + else if (isPair(parent)) if (key === "key") parent.key = node; + else parent.value = node; + else if (isDocument(parent)) parent.contents = node; + else { + const pt = isAlias(parent) ? "alias" : "scalar"; + throw new Error(`Cannot replace node with ${pt} parent`); + } +} +//#endregion +//#region node_modules/yaml/browser/dist/doc/directives.js +var escapeChars = { + "!": "%21", + ",": "%2C", + "[": "%5B", + "]": "%5D", + "{": "%7B", + "}": "%7D" +}; +var escapeTagName = (tn) => tn.replace(/[!,[\]{}]/g, (ch) => escapeChars[ch]); +var Directives = class Directives { + constructor(yaml, tags) { + /** + * The directives-end/doc-start marker `---`. If `null`, a marker may still be + * included in the document's stringified representation. + */ + this.docStart = null; + /** The doc-end marker `...`. */ + this.docEnd = false; + this.yaml = Object.assign({}, Directives.defaultYaml, yaml); + this.tags = Object.assign({}, Directives.defaultTags, tags); + } + clone() { + const copy = new Directives(this.yaml, this.tags); + copy.docStart = this.docStart; + return copy; + } + /** + * During parsing, get a Directives instance for the current document and + * update the stream state according to the current version's spec. + */ + atDocument() { + const res = new Directives(this.yaml, this.tags); + switch (this.yaml.version) { + case "1.1": + this.atNextDocument = true; + break; + case "1.2": + this.atNextDocument = false; + this.yaml = { + explicit: Directives.defaultYaml.explicit, + version: "1.2" + }; + this.tags = Object.assign({}, Directives.defaultTags); + break; + } + return res; + } + /** + * @param onError - May be called even if the action was successful + * @returns `true` on success + */ + add(line, onError) { + if (this.atNextDocument) { + this.yaml = { + explicit: Directives.defaultYaml.explicit, + version: "1.1" + }; + this.tags = Object.assign({}, Directives.defaultTags); + this.atNextDocument = false; + } + const parts = line.trim().split(/[ \t]+/); + const name = parts.shift(); + switch (name) { + case "%TAG": { + if (parts.length !== 2) { + onError(0, "%TAG directive should contain exactly two parts"); + if (parts.length < 2) return false; + } + const [handle, prefix] = parts; + this.tags[handle] = prefix; + return true; + } + case "%YAML": { + this.yaml.explicit = true; + if (parts.length !== 1) { + onError(0, "%YAML directive should contain exactly one part"); + return false; + } + const [version] = parts; + if (version === "1.1" || version === "1.2") { + this.yaml.version = version; + return true; + } else { + const isValid = /^\d+\.\d+$/.test(version); + onError(6, `Unsupported YAML version ${version}`, isValid); + return false; + } + } + default: + onError(0, `Unknown directive ${name}`, true); + return false; + } + } + /** + * Resolves a tag, matching handles to those defined in %TAG directives. + * + * @returns Resolved tag, which may also be the non-specific tag `'!'` or a + * `'!local'` tag, or `null` if unresolvable. + */ + tagName(source, onError) { + if (source === "!") return "!"; + if (source[0] !== "!") { + onError(`Not a valid tag: ${source}`); + return null; + } + if (source[1] === "<") { + const verbatim = source.slice(2, -1); + if (verbatim === "!" || verbatim === "!!") { + onError(`Verbatim tags aren't resolved, so ${source} is invalid.`); + return null; + } + if (source[source.length - 1] !== ">") onError("Verbatim tags must end with a >"); + return verbatim; + } + const [, handle, suffix] = source.match(/^(.*!)([^!]*)$/s); + if (!suffix) onError(`The ${source} tag has no suffix`); + const prefix = this.tags[handle]; + if (prefix) try { + return prefix + decodeURIComponent(suffix); + } catch (error) { + onError(String(error)); + return null; + } + if (handle === "!") return source; + onError(`Could not resolve tag: ${source}`); + return null; + } + /** + * Given a fully resolved tag, returns its printable string form, + * taking into account current tag prefixes and defaults. + */ + tagString(tag) { + for (const [handle, prefix] of Object.entries(this.tags)) if (tag.startsWith(prefix)) return handle + escapeTagName(tag.substring(prefix.length)); + return tag[0] === "!" ? tag : `!<${tag}>`; + } + toString(doc) { + const lines = this.yaml.explicit ? [`%YAML ${this.yaml.version || "1.2"}`] : []; + const tagEntries = Object.entries(this.tags); + let tagNames; + if (doc && tagEntries.length > 0 && isNode(doc.contents)) { + const tags = {}; + visit$1(doc.contents, (_key, node) => { + if (isNode(node) && node.tag) tags[node.tag] = true; + }); + tagNames = Object.keys(tags); + } else tagNames = []; + for (const [handle, prefix] of tagEntries) { + if (handle === "!!" && prefix === "tag:yaml.org,2002:") continue; + if (!doc || tagNames.some((tn) => tn.startsWith(prefix))) lines.push(`%TAG ${handle} ${prefix}`); + } + return lines.join("\n"); + } +}; +Directives.defaultYaml = { + explicit: false, + version: "1.2" +}; +Directives.defaultTags = { "!!": "tag:yaml.org,2002:" }; +//#endregion +//#region node_modules/yaml/browser/dist/doc/anchors.js +/** +* Verify that the input string is a valid anchor. +* +* Will throw on errors. +*/ +function anchorIsValid(anchor) { + if (/[\x00-\x19\s,[\]{}]/.test(anchor)) { + const msg = `Anchor must not contain whitespace or control characters: ${JSON.stringify(anchor)}`; + throw new Error(msg); + } + return true; +} +function anchorNames(root) { + const anchors = /* @__PURE__ */ new Set(); + visit$1(root, { Value(_key, node) { + if (node.anchor) anchors.add(node.anchor); + } }); + return anchors; +} +/** Find a new anchor name with the given `prefix` and a one-indexed suffix. */ +function findNewAnchor(prefix, exclude) { + for (let i = 1;; ++i) { + const name = `${prefix}${i}`; + if (!exclude.has(name)) return name; + } +} +function createNodeAnchors(doc, prefix) { + const aliasObjects = []; + const sourceObjects = /* @__PURE__ */ new Map(); + let prevAnchors = null; + return { + onAnchor: (source) => { + aliasObjects.push(source); + prevAnchors ?? (prevAnchors = anchorNames(doc)); + const anchor = findNewAnchor(prefix, prevAnchors); + prevAnchors.add(anchor); + return anchor; + }, + /** + * With circular references, the source node is only resolved after all + * of its child nodes are. This is why anchors are set only after all of + * the nodes have been created. + */ + setAnchors: () => { + for (const source of aliasObjects) { + const ref = sourceObjects.get(source); + if (typeof ref === "object" && ref.anchor && (isScalar(ref.node) || isCollection(ref.node))) ref.node.anchor = ref.anchor; + else { + const error = /* @__PURE__ */ new Error("Failed to resolve repeated object (this should not happen)"); + error.source = source; + throw error; + } + } + }, + sourceObjects + }; +} +//#endregion +//#region node_modules/yaml/browser/dist/doc/applyReviver.js +/** +* Applies the JSON.parse reviver algorithm as defined in the ECMA-262 spec, +* in section 24.5.1.1 "Runtime Semantics: InternalizeJSONProperty" of the +* 2021 edition: https://tc39.es/ecma262/#sec-json.parse +* +* Includes extensions for handling Map and Set objects. +*/ +function applyReviver(reviver, obj, key, val) { + if (val && typeof val === "object") if (Array.isArray(val)) for (let i = 0, len = val.length; i < len; ++i) { + const v0 = val[i]; + const v1 = applyReviver(reviver, val, String(i), v0); + if (v1 === void 0) delete val[i]; + else if (v1 !== v0) val[i] = v1; + } + else if (val instanceof Map) for (const k of Array.from(val.keys())) { + const v0 = val.get(k); + const v1 = applyReviver(reviver, val, k, v0); + if (v1 === void 0) val.delete(k); + else if (v1 !== v0) val.set(k, v1); + } + else if (val instanceof Set) for (const v0 of Array.from(val)) { + const v1 = applyReviver(reviver, val, v0, v0); + if (v1 === void 0) val.delete(v0); + else if (v1 !== v0) { + val.delete(v0); + val.add(v1); + } + } + else for (const [k, v0] of Object.entries(val)) { + const v1 = applyReviver(reviver, val, k, v0); + if (v1 === void 0) delete val[k]; + else if (v1 !== v0) val[k] = v1; + } + return reviver.call(obj, key, val); +} +//#endregion +//#region node_modules/yaml/browser/dist/nodes/toJS.js +/** +* Recursively convert any node or its contents to native JavaScript +* +* @param value - The input value +* @param arg - If `value` defines a `toJSON()` method, use this +* as its first argument +* @param ctx - Conversion context, originally set in Document#toJS(). If +* `{ keep: true }` is not set, output should be suitable for JSON +* stringification. +*/ +function toJS(value, arg, ctx) { + if (Array.isArray(value)) return value.map((v, i) => toJS(v, String(i), ctx)); + if (value && typeof value.toJSON === "function") { + if (!ctx || !hasAnchor(value)) return value.toJSON(arg, ctx); + const data = { + aliasCount: 0, + count: 1, + res: void 0 + }; + ctx.anchors.set(value, data); + ctx.onCreate = (res) => { + data.res = res; + delete ctx.onCreate; + }; + const res = value.toJSON(arg, ctx); + if (ctx.onCreate) ctx.onCreate(res); + return res; + } + if (typeof value === "bigint" && !ctx?.keep) return Number(value); + return value; +} +//#endregion +//#region node_modules/yaml/browser/dist/nodes/Node.js +var NodeBase = class { + constructor(type) { + Object.defineProperty(this, NODE_TYPE, { value: type }); + } + /** Create a copy of this node. */ + clone() { + const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this)); + if (this.range) copy.range = this.range.slice(); + return copy; + } + /** A plain JavaScript representation of this node. */ + toJS(doc, { mapAsMap, maxAliasCount, onAnchor, reviver } = {}) { + if (!isDocument(doc)) throw new TypeError("A document argument is required"); + const ctx = { + anchors: /* @__PURE__ */ new Map(), + doc, + keep: true, + mapAsMap: mapAsMap === true, + mapKeyWarned: false, + maxAliasCount: typeof maxAliasCount === "number" ? maxAliasCount : 100 + }; + const res = toJS(this, "", ctx); + if (typeof onAnchor === "function") for (const { count, res } of ctx.anchors.values()) onAnchor(res, count); + return typeof reviver === "function" ? applyReviver(reviver, { "": res }, "", res) : res; + } +}; +//#endregion +//#region node_modules/yaml/browser/dist/nodes/Alias.js +var Alias = class extends NodeBase { + constructor(source) { + super(ALIAS); + this.source = source; + Object.defineProperty(this, "tag", { set() { + throw new Error("Alias nodes cannot have tags"); + } }); + } + /** + * Resolve the value of this alias within `doc`, finding the last + * instance of the `source` anchor before this node. + */ + resolve(doc, ctx) { + if (ctx?.maxAliasCount === 0) throw new ReferenceError("Alias resolution is disabled"); + let nodes; + if (ctx?.aliasResolveCache) nodes = ctx.aliasResolveCache; + else { + nodes = []; + visit$1(doc, { Node: (_key, node) => { + if (isAlias(node) || hasAnchor(node)) nodes.push(node); + } }); + if (ctx) ctx.aliasResolveCache = nodes; + } + let found = void 0; + for (const node of nodes) { + if (node === this) break; + if (node.anchor === this.source) found = node; + } + return found; + } + toJSON(_arg, ctx) { + if (!ctx) return { source: this.source }; + const { anchors, doc, maxAliasCount } = ctx; + const source = this.resolve(doc, ctx); + if (!source) { + const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`; + throw new ReferenceError(msg); + } + let data = anchors.get(source); + if (!data) { + toJS(source, null, ctx); + data = anchors.get(source); + } + /* istanbul ignore if */ + if (data?.res === void 0) throw new ReferenceError("This should not happen: Alias anchor was not resolved?"); + if (maxAliasCount >= 0) { + data.count += 1; + if (data.aliasCount === 0) data.aliasCount = getAliasCount(doc, source, anchors); + if (data.count * data.aliasCount > maxAliasCount) throw new ReferenceError("Excessive alias count indicates a resource exhaustion attack"); + } + return data.res; + } + toString(ctx, _onComment, _onChompKeep) { + const src = `*${this.source}`; + if (ctx) { + anchorIsValid(this.source); + if (ctx.options.verifyAliasOrder && !ctx.anchors.has(this.source)) { + const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`; + throw new Error(msg); + } + if (ctx.implicitKey) return `${src} `; + } + return src; + } +}; +function getAliasCount(doc, node, anchors) { + if (isAlias(node)) { + const source = node.resolve(doc); + const anchor = anchors && source && anchors.get(source); + return anchor ? anchor.count * anchor.aliasCount : 0; + } else if (isCollection(node)) { + let count = 0; + for (const item of node.items) { + const c = getAliasCount(doc, item, anchors); + if (c > count) count = c; + } + return count; + } else if (isPair(node)) { + const kc = getAliasCount(doc, node.key, anchors); + const vc = getAliasCount(doc, node.value, anchors); + return Math.max(kc, vc); + } + return 1; +} +//#endregion +//#region node_modules/yaml/browser/dist/nodes/Scalar.js +var isScalarValue = (value) => !value || typeof value !== "function" && typeof value !== "object"; +var Scalar = class extends NodeBase { + constructor(value) { + super(SCALAR$1); + this.value = value; + } + toJSON(arg, ctx) { + return ctx?.keep ? this.value : toJS(this.value, arg, ctx); + } + toString() { + return String(this.value); + } +}; +Scalar.BLOCK_FOLDED = "BLOCK_FOLDED"; +Scalar.BLOCK_LITERAL = "BLOCK_LITERAL"; +Scalar.PLAIN = "PLAIN"; +Scalar.QUOTE_DOUBLE = "QUOTE_DOUBLE"; +Scalar.QUOTE_SINGLE = "QUOTE_SINGLE"; +//#endregion +//#region node_modules/yaml/browser/dist/doc/createNode.js +var defaultTagPrefix = "tag:yaml.org,2002:"; +function findTagObject(value, tagName, tags) { + if (tagName) { + const match = tags.filter((t) => t.tag === tagName); + const tagObj = match.find((t) => !t.format) ?? match[0]; + if (!tagObj) throw new Error(`Tag ${tagName} not found`); + return tagObj; + } + return tags.find((t) => t.identify?.(value) && !t.format); +} +function createNode(value, tagName, ctx) { + if (isDocument(value)) value = value.contents; + if (isNode(value)) return value; + if (isPair(value)) { + const map = ctx.schema[MAP].createNode?.(ctx.schema, null, ctx); + map.items.push(value); + return map; + } + if (value instanceof String || value instanceof Number || value instanceof Boolean || typeof BigInt !== "undefined" && value instanceof BigInt) value = value.valueOf(); + const { aliasDuplicateObjects, onAnchor, onTagObj, schema, sourceObjects } = ctx; + let ref = void 0; + if (aliasDuplicateObjects && value && typeof value === "object") { + ref = sourceObjects.get(value); + if (ref) { + ref.anchor ?? (ref.anchor = onAnchor(value)); + return new Alias(ref.anchor); + } else { + ref = { + anchor: null, + node: null + }; + sourceObjects.set(value, ref); + } + } + if (tagName?.startsWith("!!")) tagName = defaultTagPrefix + tagName.slice(2); + let tagObj = findTagObject(value, tagName, schema.tags); + if (!tagObj) { + if (value && typeof value.toJSON === "function") value = value.toJSON(); + if (!value || typeof value !== "object") { + const node = new Scalar(value); + if (ref) ref.node = node; + return node; + } + tagObj = value instanceof Map ? schema[MAP] : Symbol.iterator in Object(value) ? schema[SEQ] : schema[MAP]; + } + if (onTagObj) { + onTagObj(tagObj); + delete ctx.onTagObj; + } + const node = tagObj?.createNode ? tagObj.createNode(ctx.schema, value, ctx) : typeof tagObj?.nodeClass?.from === "function" ? tagObj.nodeClass.from(ctx.schema, value, ctx) : new Scalar(value); + if (tagName) node.tag = tagName; + else if (!tagObj.default) node.tag = tagObj.tag; + if (ref) ref.node = node; + return node; +} +//#endregion +//#region node_modules/yaml/browser/dist/nodes/Collection.js +function collectionFromPath(schema, path, value) { + let v = value; + for (let i = path.length - 1; i >= 0; --i) { + const k = path[i]; + if (typeof k === "number" && Number.isInteger(k) && k >= 0) { + const a = []; + a[k] = v; + v = a; + } else v = /* @__PURE__ */ new Map([[k, v]]); + } + return createNode(v, void 0, { + aliasDuplicateObjects: false, + keepUndefined: false, + onAnchor: () => { + throw new Error("This should not happen, please report a bug."); + }, + schema, + sourceObjects: /* @__PURE__ */ new Map() + }); +} +var isEmptyPath = (path) => path == null || typeof path === "object" && !!path[Symbol.iterator]().next().done; +var Collection$1 = class extends NodeBase { + constructor(type, schema) { + super(type); + Object.defineProperty(this, "schema", { + value: schema, + configurable: true, + enumerable: false, + writable: true + }); + } + /** + * Create a copy of this collection. + * + * @param schema - If defined, overwrites the original's schema + */ + clone(schema) { + const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this)); + if (schema) copy.schema = schema; + copy.items = copy.items.map((it) => isNode(it) || isPair(it) ? it.clone(schema) : it); + if (this.range) copy.range = this.range.slice(); + return copy; + } + /** + * Adds a value to the collection. For `!!map` and `!!omap` the value must + * be a Pair instance or a `{ key, value }` object, which may not have a key + * that already exists in the map. + */ + addIn(path, value) { + if (isEmptyPath(path)) this.add(value); + else { + const [key, ...rest] = path; + const node = this.get(key, true); + if (isCollection(node)) node.addIn(rest, value); + else if (node === void 0 && this.schema) this.set(key, collectionFromPath(this.schema, rest, value)); + else throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); + } + } + /** + * Removes a value from the collection. + * @returns `true` if the item was found and removed. + */ + deleteIn(path) { + const [key, ...rest] = path; + if (rest.length === 0) return this.delete(key); + const node = this.get(key, true); + if (isCollection(node)) return node.deleteIn(rest); + else throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); + } + /** + * Returns item at `key`, or `undefined` if not found. By default unwraps + * scalar values from their surrounding node; to disable set `keepScalar` to + * `true` (collections are always returned intact). + */ + getIn(path, keepScalar) { + const [key, ...rest] = path; + const node = this.get(key, true); + if (rest.length === 0) return !keepScalar && isScalar(node) ? node.value : node; + else return isCollection(node) ? node.getIn(rest, keepScalar) : void 0; + } + hasAllNullValues(allowScalar) { + return this.items.every((node) => { + if (!isPair(node)) return false; + const n = node.value; + return n == null || allowScalar && isScalar(n) && n.value == null && !n.commentBefore && !n.comment && !n.tag; + }); + } + /** + * Checks if the collection includes a value with the key `key`. + */ + hasIn(path) { + const [key, ...rest] = path; + if (rest.length === 0) return this.has(key); + const node = this.get(key, true); + return isCollection(node) ? node.hasIn(rest) : false; + } + /** + * Sets a value in this collection. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + */ + setIn(path, value) { + const [key, ...rest] = path; + if (rest.length === 0) this.set(key, value); + else { + const node = this.get(key, true); + if (isCollection(node)) node.setIn(rest, value); + else if (node === void 0 && this.schema) this.set(key, collectionFromPath(this.schema, rest, value)); + else throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); + } + } +}; +//#endregion +//#region node_modules/yaml/browser/dist/stringify/stringifyComment.js +/** +* Stringifies a comment. +* +* Empty comment lines are left empty, +* lines consisting of a single space are replaced by `#`, +* and all other lines are prefixed with a `#`. +*/ +var stringifyComment = (str) => str.replace(/^(?!$)(?: $)?/gm, "#"); +function indentComment(comment, indent) { + if (/^\n+$/.test(comment)) return comment.substring(1); + return indent ? comment.replace(/^(?! *$)/gm, indent) : comment; +} +var lineComment = (str, indent, comment) => str.endsWith("\n") ? indentComment(comment, indent) : comment.includes("\n") ? "\n" + indentComment(comment, indent) : (str.endsWith(" ") ? "" : " ") + comment; +//#endregion +//#region node_modules/yaml/browser/dist/stringify/foldFlowLines.js +var FOLD_FLOW = "flow"; +var FOLD_BLOCK = "block"; +var FOLD_QUOTED = "quoted"; +/** +* Tries to keep input at up to `lineWidth` characters, splitting only on spaces +* not followed by newlines or spaces unless `mode` is `'quoted'`. Lines are +* terminated with `\n` and started with `indent`. +*/ +function foldFlowLines(text, indent, mode = "flow", { indentAtStart, lineWidth = 80, minContentWidth = 20, onFold, onOverflow } = {}) { + if (!lineWidth || lineWidth < 0) return text; + if (lineWidth < minContentWidth) minContentWidth = 0; + const endStep = Math.max(1 + minContentWidth, 1 + lineWidth - indent.length); + if (text.length <= endStep) return text; + const folds = []; + const escapedFolds = {}; + let end = lineWidth - indent.length; + if (typeof indentAtStart === "number") if (indentAtStart > lineWidth - Math.max(2, minContentWidth)) folds.push(0); + else end = lineWidth - indentAtStart; + let split = void 0; + let prev = void 0; + let overflow = false; + let i = -1; + let escStart = -1; + let escEnd = -1; + if (mode === "block") { + i = consumeMoreIndentedLines(text, i, indent.length); + if (i !== -1) end = i + endStep; + } + for (let ch; ch = text[i += 1];) { + if (mode === "quoted" && ch === "\\") { + escStart = i; + switch (text[i + 1]) { + case "x": + i += 3; + break; + case "u": + i += 5; + break; + case "U": + i += 9; + break; + default: i += 1; + } + escEnd = i; + } + if (ch === "\n") { + if (mode === "block") i = consumeMoreIndentedLines(text, i, indent.length); + end = i + indent.length + endStep; + split = void 0; + } else { + if (ch === " " && prev && prev !== " " && prev !== "\n" && prev !== " ") { + const next = text[i + 1]; + if (next && next !== " " && next !== "\n" && next !== " ") split = i; + } + if (i >= end) if (split) { + folds.push(split); + end = split + endStep; + split = void 0; + } else if (mode === "quoted") { + while (prev === " " || prev === " ") { + prev = ch; + ch = text[i += 1]; + overflow = true; + } + const j = i > escEnd + 1 ? i - 2 : escStart - 1; + if (escapedFolds[j]) return text; + folds.push(j); + escapedFolds[j] = true; + end = j + endStep; + split = void 0; + } else overflow = true; + } + prev = ch; + } + if (overflow && onOverflow) onOverflow(); + if (folds.length === 0) return text; + if (onFold) onFold(); + let res = text.slice(0, folds[0]); + for (let i = 0; i < folds.length; ++i) { + const fold = folds[i]; + const end = folds[i + 1] || text.length; + if (fold === 0) res = `\n${indent}${text.slice(0, end)}`; + else { + if (mode === "quoted" && escapedFolds[fold]) res += `${text[fold]}\\`; + res += `\n${indent}${text.slice(fold + 1, end)}`; + } + } + return res; +} +/** +* Presumes `i + 1` is at the start of a line +* @returns index of last newline in more-indented block +*/ +function consumeMoreIndentedLines(text, i, indent) { + let end = i; + let start = i + 1; + let ch = text[start]; + while (ch === " " || ch === " ") if (i < start + indent) ch = text[++i]; + else { + do + ch = text[++i]; + while (ch && ch !== "\n"); + end = i; + start = i + 1; + ch = text[start]; + } + return end; +} +//#endregion +//#region node_modules/yaml/browser/dist/stringify/stringifyString.js +var getFoldOptions = (ctx, isBlock) => ({ + indentAtStart: isBlock ? ctx.indent.length : ctx.indentAtStart, + lineWidth: ctx.options.lineWidth, + minContentWidth: ctx.options.minContentWidth +}); +var containsDocumentMarker = (str) => /^(%|---|\.\.\.)/m.test(str); +function lineLengthOverLimit(str, lineWidth, indentLength) { + if (!lineWidth || lineWidth < 0) return false; + const limit = lineWidth - indentLength; + const strLen = str.length; + if (strLen <= limit) return false; + for (let i = 0, start = 0; i < strLen; ++i) if (str[i] === "\n") { + if (i - start > limit) return true; + start = i + 1; + if (strLen - start <= limit) return false; + } + return true; +} +function doubleQuotedString(value, ctx) { + const json = JSON.stringify(value); + if (ctx.options.doubleQuotedAsJSON) return json; + const { implicitKey } = ctx; + const minMultiLineLength = ctx.options.doubleQuotedMinMultiLineLength; + const indent = ctx.indent || (containsDocumentMarker(value) ? " " : ""); + let str = ""; + let start = 0; + for (let i = 0, ch = json[i]; ch; ch = json[++i]) { + if (ch === " " && json[i + 1] === "\\" && json[i + 2] === "n") { + str += json.slice(start, i) + "\\ "; + i += 1; + start = i; + ch = "\\"; + } + if (ch === "\\") switch (json[i + 1]) { + case "u": + { + str += json.slice(start, i); + const code = json.substr(i + 2, 4); + switch (code) { + case "0000": + str += "\\0"; + break; + case "0007": + str += "\\a"; + break; + case "000b": + str += "\\v"; + break; + case "001b": + str += "\\e"; + break; + case "0085": + str += "\\N"; + break; + case "00a0": + str += "\\_"; + break; + case "2028": + str += "\\L"; + break; + case "2029": + str += "\\P"; + break; + default: if (code.substr(0, 2) === "00") str += "\\x" + code.substr(2); + else str += json.substr(i, 6); + } + i += 5; + start = i + 1; + } + break; + case "n": + if (implicitKey || json[i + 2] === "\"" || json.length < minMultiLineLength) i += 1; + else { + str += json.slice(start, i) + "\n\n"; + while (json[i + 2] === "\\" && json[i + 3] === "n" && json[i + 4] !== "\"") { + str += "\n"; + i += 2; + } + str += indent; + if (json[i + 2] === " ") str += "\\"; + i += 1; + start = i + 1; + } + break; + default: i += 1; + } + } + str = start ? str + json.slice(start) : json; + return implicitKey ? str : foldFlowLines(str, indent, FOLD_QUOTED, getFoldOptions(ctx, false)); +} +function singleQuotedString(value, ctx) { + if (ctx.options.singleQuote === false || ctx.implicitKey && value.includes("\n") || /[ \t]\n|\n[ \t]/.test(value)) return doubleQuotedString(value, ctx); + const indent = ctx.indent || (containsDocumentMarker(value) ? " " : ""); + const res = "'" + value.replace(/'/g, "''").replace(/\n+/g, `$&\n${indent}`) + "'"; + return ctx.implicitKey ? res : foldFlowLines(res, indent, FOLD_FLOW, getFoldOptions(ctx, false)); +} +function quotedString(value, ctx) { + const { singleQuote } = ctx.options; + let qs; + if (singleQuote === false) qs = doubleQuotedString; + else { + const hasDouble = value.includes("\""); + const hasSingle = value.includes("'"); + if (hasDouble && !hasSingle) qs = singleQuotedString; + else if (hasSingle && !hasDouble) qs = doubleQuotedString; + else qs = singleQuote ? singleQuotedString : doubleQuotedString; + } + return qs(value, ctx); +} +var blockEndNewlines; +try { + blockEndNewlines = /* @__PURE__ */ new RegExp("(^|(?\n"; + let chomp; + let endStart; + for (endStart = value.length; endStart > 0; --endStart) { + const ch = value[endStart - 1]; + if (ch !== "\n" && ch !== " " && ch !== " ") break; + } + let end = value.substring(endStart); + const endNlPos = end.indexOf("\n"); + if (endNlPos === -1) chomp = "-"; + else if (value === end || endNlPos !== end.length - 1) { + chomp = "+"; + if (onChompKeep) onChompKeep(); + } else chomp = ""; + if (end) { + value = value.slice(0, -end.length); + if (end[end.length - 1] === "\n") end = end.slice(0, -1); + end = end.replace(blockEndNewlines, `$&${indent}`); + } + let startWithSpace = false; + let startEnd; + let startNlPos = -1; + for (startEnd = 0; startEnd < value.length; ++startEnd) { + const ch = value[startEnd]; + if (ch === " ") startWithSpace = true; + else if (ch === "\n") startNlPos = startEnd; + else break; + } + let start = value.substring(0, startNlPos < startEnd ? startNlPos + 1 : startEnd); + if (start) { + value = value.substring(start.length); + start = start.replace(/\n+/g, `$&${indent}`); + } + let header = (startWithSpace ? indent ? "2" : "1" : "") + chomp; + if (comment) { + header += " " + commentString(comment.replace(/ ?[\r\n]+/g, " ")); + if (onComment) onComment(); + } + if (!literal) { + const foldedValue = value.replace(/\n+/g, "\n$&").replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g, "$1$2").replace(/\n+/g, `$&${indent}`); + let literalFallback = false; + const foldOptions = getFoldOptions(ctx, true); + if (blockQuote !== "folded" && type !== Scalar.BLOCK_FOLDED) foldOptions.onOverflow = () => { + literalFallback = true; + }; + const body = foldFlowLines(`${start}${foldedValue}${end}`, indent, FOLD_BLOCK, foldOptions); + if (!literalFallback) return `>${header}\n${indent}${body}`; + } + value = value.replace(/\n+/g, `$&${indent}`); + return `|${header}\n${indent}${start}${value}${end}`; +} +function plainString(item, ctx, onComment, onChompKeep) { + const { type, value } = item; + const { actualString, implicitKey, indent, indentStep, inFlow } = ctx; + if (implicitKey && value.includes("\n") || inFlow && /[[\]{},]/.test(value)) return quotedString(value, ctx); + if (/^[\n\t ,[\]{}#&*!|>'"%@`]|^[?-]$|^[?-][ \t]|[\n:][ \t]|[ \t]\n|[\n\t ]#|[\n\t :]$/.test(value)) return implicitKey || inFlow || !value.includes("\n") ? quotedString(value, ctx) : blockString(item, ctx, onComment, onChompKeep); + if (!implicitKey && !inFlow && type !== Scalar.PLAIN && value.includes("\n")) return blockString(item, ctx, onComment, onChompKeep); + if (containsDocumentMarker(value)) { + if (indent === "") { + ctx.forceBlockIndent = true; + return blockString(item, ctx, onComment, onChompKeep); + } else if (implicitKey && indent === indentStep) return quotedString(value, ctx); + } + const str = value.replace(/\n+/g, `$&\n${indent}`); + if (actualString) { + const test = (tag) => tag.default && tag.tag !== "tag:yaml.org,2002:str" && tag.test?.test(str); + const { compat, tags } = ctx.doc.schema; + if (tags.some(test) || compat?.some(test)) return quotedString(value, ctx); + } + return implicitKey ? str : foldFlowLines(str, indent, FOLD_FLOW, getFoldOptions(ctx, false)); +} +function stringifyString(item, ctx, onComment, onChompKeep) { + const { implicitKey, inFlow } = ctx; + const ss = typeof item.value === "string" ? item : Object.assign({}, item, { value: String(item.value) }); + let { type } = item; + if (type !== Scalar.QUOTE_DOUBLE) { + if (/[\x00-\x08\x0b-\x1f\x7f-\x9f\u{D800}-\u{DFFF}]/u.test(ss.value)) type = Scalar.QUOTE_DOUBLE; + } + const _stringify = (_type) => { + switch (_type) { + case Scalar.BLOCK_FOLDED: + case Scalar.BLOCK_LITERAL: return implicitKey || inFlow ? quotedString(ss.value, ctx) : blockString(ss, ctx, onComment, onChompKeep); + case Scalar.QUOTE_DOUBLE: return doubleQuotedString(ss.value, ctx); + case Scalar.QUOTE_SINGLE: return singleQuotedString(ss.value, ctx); + case Scalar.PLAIN: return plainString(ss, ctx, onComment, onChompKeep); + default: return null; + } + }; + let res = _stringify(type); + if (res === null) { + const { defaultKeyType, defaultStringType } = ctx.options; + const t = implicitKey && defaultKeyType || defaultStringType; + res = _stringify(t); + if (res === null) throw new Error(`Unsupported default string type ${t}`); + } + return res; +} +//#endregion +//#region node_modules/yaml/browser/dist/stringify/stringify.js +function createStringifyContext(doc, options) { + const opt = Object.assign({ + blockQuote: true, + commentString: stringifyComment, + defaultKeyType: null, + defaultStringType: "PLAIN", + directives: null, + doubleQuotedAsJSON: false, + doubleQuotedMinMultiLineLength: 40, + falseStr: "false", + flowCollectionPadding: true, + indentSeq: true, + lineWidth: 80, + minContentWidth: 20, + nullStr: "null", + simpleKeys: false, + singleQuote: null, + trailingComma: false, + trueStr: "true", + verifyAliasOrder: true + }, doc.schema.toStringOptions, options); + let inFlow; + switch (opt.collectionStyle) { + case "block": + inFlow = false; + break; + case "flow": + inFlow = true; + break; + default: inFlow = null; + } + return { + anchors: /* @__PURE__ */ new Set(), + doc, + flowCollectionPadding: opt.flowCollectionPadding ? " " : "", + indent: "", + indentStep: typeof opt.indent === "number" ? " ".repeat(opt.indent) : " ", + inFlow, + options: opt + }; +} +function getTagObject(tags, item) { + if (item.tag) { + const match = tags.filter((t) => t.tag === item.tag); + if (match.length > 0) return match.find((t) => t.format === item.format) ?? match[0]; + } + let tagObj = void 0; + let obj; + if (isScalar(item)) { + obj = item.value; + let match = tags.filter((t) => t.identify?.(obj)); + if (match.length > 1) { + const testMatch = match.filter((t) => t.test); + if (testMatch.length > 0) match = testMatch; + } + tagObj = match.find((t) => t.format === item.format) ?? match.find((t) => !t.format); + } else { + obj = item; + tagObj = tags.find((t) => t.nodeClass && obj instanceof t.nodeClass); + } + if (!tagObj) { + const name = obj?.constructor?.name ?? (obj === null ? "null" : typeof obj); + throw new Error(`Tag not resolved for ${name} value`); + } + return tagObj; +} +function stringifyProps(node, tagObj, { anchors, doc }) { + if (!doc.directives) return ""; + const props = []; + const anchor = (isScalar(node) || isCollection(node)) && node.anchor; + if (anchor && anchorIsValid(anchor)) { + anchors.add(anchor); + props.push(`&${anchor}`); + } + const tag = node.tag ?? (tagObj.default ? null : tagObj.tag); + if (tag) props.push(doc.directives.tagString(tag)); + return props.join(" "); +} +function stringify(item, ctx, onComment, onChompKeep) { + if (isPair(item)) return item.toString(ctx, onComment, onChompKeep); + if (isAlias(item)) { + if (ctx.doc.directives) return item.toString(ctx); + if (ctx.resolvedAliases?.has(item)) throw new TypeError(`Cannot stringify circular structure without alias nodes`); + else { + if (ctx.resolvedAliases) ctx.resolvedAliases.add(item); + else ctx.resolvedAliases = /* @__PURE__ */ new Set([item]); + item = item.resolve(ctx.doc); + } + } + let tagObj = void 0; + const node = isNode(item) ? item : ctx.doc.createNode(item, { onTagObj: (o) => tagObj = o }); + tagObj ?? (tagObj = getTagObject(ctx.doc.schema.tags, node)); + const props = stringifyProps(node, tagObj, ctx); + if (props.length > 0) ctx.indentAtStart = (ctx.indentAtStart ?? 0) + props.length + 1; + const str = typeof tagObj.stringify === "function" ? tagObj.stringify(node, ctx, onComment, onChompKeep) : isScalar(node) ? stringifyString(node, ctx, onComment, onChompKeep) : node.toString(ctx, onComment, onChompKeep); + if (!props) return str; + return isScalar(node) || str[0] === "{" || str[0] === "[" ? `${props} ${str}` : `${props}\n${ctx.indent}${str}`; +} +//#endregion +//#region node_modules/yaml/browser/dist/stringify/stringifyPair.js +function stringifyPair({ key, value }, ctx, onComment, onChompKeep) { + const { allNullValues, doc, indent, indentStep, options: { commentString, indentSeq, simpleKeys } } = ctx; + let keyComment = isNode(key) && key.comment || null; + if (simpleKeys) { + if (keyComment) throw new Error("With simple keys, key nodes cannot have comments"); + if (isCollection(key) || !isNode(key) && typeof key === "object") throw new Error("With simple keys, collection cannot be used as a key value"); + } + let explicitKey = !simpleKeys && (!key || keyComment && value == null && !ctx.inFlow || isCollection(key) || (isScalar(key) ? key.type === Scalar.BLOCK_FOLDED || key.type === Scalar.BLOCK_LITERAL : typeof key === "object")); + ctx = Object.assign({}, ctx, { + allNullValues: false, + implicitKey: !explicitKey && (simpleKeys || !allNullValues), + indent: indent + indentStep + }); + let keyCommentDone = false; + let chompKeep = false; + let str = stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true); + if (!explicitKey && !ctx.inFlow && str.length > 1024) { + if (simpleKeys) throw new Error("With simple keys, single line scalar must not span more than 1024 characters"); + explicitKey = true; + } + if (ctx.inFlow) { + if (allNullValues || value == null) { + if (keyCommentDone && onComment) onComment(); + return str === "" ? "?" : explicitKey ? `? ${str}` : str; + } + } else if (allNullValues && !simpleKeys || value == null && explicitKey) { + str = `? ${str}`; + if (keyComment && !keyCommentDone) str += lineComment(str, ctx.indent, commentString(keyComment)); + else if (chompKeep && onChompKeep) onChompKeep(); + return str; + } + if (keyCommentDone) keyComment = null; + if (explicitKey) { + if (keyComment) str += lineComment(str, ctx.indent, commentString(keyComment)); + str = `? ${str}\n${indent}:`; + } else { + str = `${str}:`; + if (keyComment) str += lineComment(str, ctx.indent, commentString(keyComment)); + } + let vsb, vcb, valueComment; + if (isNode(value)) { + vsb = !!value.spaceBefore; + vcb = value.commentBefore; + valueComment = value.comment; + } else { + vsb = false; + vcb = null; + valueComment = null; + if (value && typeof value === "object") value = doc.createNode(value); + } + ctx.implicitKey = false; + if (!explicitKey && !keyComment && isScalar(value)) ctx.indentAtStart = str.length + 1; + chompKeep = false; + if (!indentSeq && indentStep.length >= 2 && !ctx.inFlow && !explicitKey && isSeq(value) && !value.flow && !value.tag && !value.anchor) ctx.indent = ctx.indent.substring(2); + let valueCommentDone = false; + const valueStr = stringify(value, ctx, () => valueCommentDone = true, () => chompKeep = true); + let ws = " "; + if (keyComment || vsb || vcb) { + ws = vsb ? "\n" : ""; + if (vcb) { + const cs = commentString(vcb); + ws += `\n${indentComment(cs, ctx.indent)}`; + } + if (valueStr === "" && !ctx.inFlow) { + if (ws === "\n" && valueComment) ws = "\n\n"; + } else ws += `\n${ctx.indent}`; + } else if (!explicitKey && isCollection(value)) { + const vs0 = valueStr[0]; + const nl0 = valueStr.indexOf("\n"); + const hasNewline = nl0 !== -1; + const flow = ctx.inFlow ?? value.flow ?? value.items.length === 0; + if (hasNewline || !flow) { + let hasPropsLine = false; + if (hasNewline && (vs0 === "&" || vs0 === "!")) { + let sp0 = valueStr.indexOf(" "); + if (vs0 === "&" && sp0 !== -1 && sp0 < nl0 && valueStr[sp0 + 1] === "!") sp0 = valueStr.indexOf(" ", sp0 + 1); + if (sp0 === -1 || nl0 < sp0) hasPropsLine = true; + } + if (!hasPropsLine) ws = `\n${ctx.indent}`; + } + } else if (valueStr === "" || valueStr[0] === "\n") ws = ""; + str += ws + valueStr; + if (ctx.inFlow) { + if (valueCommentDone && onComment) onComment(); + } else if (valueComment && !valueCommentDone) str += lineComment(str, ctx.indent, commentString(valueComment)); + else if (chompKeep && onChompKeep) onChompKeep(); + return str; +} +//#endregion +//#region node_modules/yaml/browser/dist/log.js +function warn(logLevel, warning) { + if (logLevel === "debug" || logLevel === "warn") console.warn(warning); +} +//#endregion +//#region node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +var MERGE_KEY = "<<"; +var merge$1 = { + identify: (value) => value === MERGE_KEY || typeof value === "symbol" && value.description === MERGE_KEY, + default: "key", + tag: "tag:yaml.org,2002:merge", + test: /^<<$/, + resolve: () => Object.assign(new Scalar(Symbol(MERGE_KEY)), { addToJSMap: addMergeToJSMap }), + stringify: () => MERGE_KEY +}; +var isMergeKey = (ctx, key) => (merge$1.identify(key) || isScalar(key) && (!key.type || key.type === Scalar.PLAIN) && merge$1.identify(key.value)) && ctx?.doc.schema.tags.some((tag) => tag.tag === merge$1.tag && tag.default); +function addMergeToJSMap(ctx, map, value) { + const source = resolveAliasValue(ctx, value); + if (isSeq(source)) for (const it of source.items) mergeValue(ctx, map, it); + else if (Array.isArray(source)) for (const it of source) mergeValue(ctx, map, it); + else mergeValue(ctx, map, source); +} +function mergeValue(ctx, map, value) { + const source = resolveAliasValue(ctx, value); + if (!isMap(source)) throw new Error("Merge sources must be maps or map aliases"); + const srcMap = source.toJSON(null, ctx, Map); + for (const [key, value] of srcMap) if (map instanceof Map) { + if (!map.has(key)) map.set(key, value); + } else if (map instanceof Set) map.add(key); + else if (!Object.prototype.hasOwnProperty.call(map, key)) Object.defineProperty(map, key, { + value, + writable: true, + enumerable: true, + configurable: true + }); + return map; +} +function resolveAliasValue(ctx, value) { + return ctx && isAlias(value) ? value.resolve(ctx.doc, ctx) : value; +} +//#endregion +//#region node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +function addPairToJSMap(ctx, map, { key, value }) { + if (isNode(key) && key.addToJSMap) key.addToJSMap(ctx, map, value); + else if (isMergeKey(ctx, key)) addMergeToJSMap(ctx, map, value); + else { + const jsKey = toJS(key, "", ctx); + if (map instanceof Map) map.set(jsKey, toJS(value, jsKey, ctx)); + else if (map instanceof Set) map.add(jsKey); + else { + const stringKey = stringifyKey(key, jsKey, ctx); + const jsValue = toJS(value, stringKey, ctx); + if (stringKey in map) Object.defineProperty(map, stringKey, { + value: jsValue, + writable: true, + enumerable: true, + configurable: true + }); + else map[stringKey] = jsValue; + } + } + return map; +} +function stringifyKey(key, jsKey, ctx) { + if (jsKey === null) return ""; + if (typeof jsKey !== "object") return String(jsKey); + if (isNode(key) && ctx?.doc) { + const strCtx = createStringifyContext(ctx.doc, {}); + strCtx.anchors = /* @__PURE__ */ new Set(); + for (const node of ctx.anchors.keys()) strCtx.anchors.add(node.anchor); + strCtx.inFlow = true; + strCtx.inStringifyKey = true; + const strKey = key.toString(strCtx); + if (!ctx.mapKeyWarned) { + let jsonStr = JSON.stringify(strKey); + if (jsonStr.length > 40) jsonStr = jsonStr.substring(0, 36) + "...\""; + warn(ctx.doc.options.logLevel, `Keys with collection values will be stringified due to JS Object restrictions: ${jsonStr}. Set mapAsMap: true to use object keys.`); + ctx.mapKeyWarned = true; + } + return strKey; + } + return JSON.stringify(jsKey); +} +//#endregion +//#region node_modules/yaml/browser/dist/nodes/Pair.js +function createPair(key, value, ctx) { + return new Pair(createNode(key, void 0, ctx), createNode(value, void 0, ctx)); +} +var Pair = class Pair { + constructor(key, value = null) { + Object.defineProperty(this, NODE_TYPE, { value: PAIR }); + this.key = key; + this.value = value; + } + clone(schema) { + let { key, value } = this; + if (isNode(key)) key = key.clone(schema); + if (isNode(value)) value = value.clone(schema); + return new Pair(key, value); + } + toJSON(_, ctx) { + return addPairToJSMap(ctx, ctx?.mapAsMap ? /* @__PURE__ */ new Map() : {}, this); + } + toString(ctx, onComment, onChompKeep) { + return ctx?.doc ? stringifyPair(this, ctx, onComment, onChompKeep) : JSON.stringify(this); + } +}; +//#endregion +//#region node_modules/yaml/browser/dist/stringify/stringifyCollection.js +function stringifyCollection(collection, ctx, options) { + return (ctx.inFlow ?? collection.flow ? stringifyFlowCollection : stringifyBlockCollection)(collection, ctx, options); +} +function stringifyBlockCollection({ comment, items }, ctx, { blockItemPrefix, flowChars, itemIndent, onChompKeep, onComment }) { + const { indent, options: { commentString } } = ctx; + const itemCtx = Object.assign({}, ctx, { + indent: itemIndent, + type: null + }); + let chompKeep = false; + const lines = []; + for (let i = 0; i < items.length; ++i) { + const item = items[i]; + let comment = null; + if (isNode(item)) { + if (!chompKeep && item.spaceBefore) lines.push(""); + addCommentBefore(ctx, lines, item.commentBefore, chompKeep); + if (item.comment) comment = item.comment; + } else if (isPair(item)) { + const ik = isNode(item.key) ? item.key : null; + if (ik) { + if (!chompKeep && ik.spaceBefore) lines.push(""); + addCommentBefore(ctx, lines, ik.commentBefore, chompKeep); + } + } + chompKeep = false; + let str = stringify(item, itemCtx, () => comment = null, () => chompKeep = true); + if (comment) str += lineComment(str, itemIndent, commentString(comment)); + if (chompKeep && comment) chompKeep = false; + lines.push(blockItemPrefix + str); + } + let str; + if (lines.length === 0) str = flowChars.start + flowChars.end; + else { + str = lines[0]; + for (let i = 1; i < lines.length; ++i) { + const line = lines[i]; + str += line ? `\n${indent}${line}` : "\n"; + } + } + if (comment) { + str += "\n" + indentComment(commentString(comment), indent); + if (onComment) onComment(); + } else if (chompKeep && onChompKeep) onChompKeep(); + return str; +} +function stringifyFlowCollection({ items }, ctx, { flowChars, itemIndent }) { + const { indent, indentStep, flowCollectionPadding: fcPadding, options: { commentString } } = ctx; + itemIndent += indentStep; + const itemCtx = Object.assign({}, ctx, { + indent: itemIndent, + inFlow: true, + type: null + }); + let reqNewline = false; + let linesAtValue = 0; + const lines = []; + for (let i = 0; i < items.length; ++i) { + const item = items[i]; + let comment = null; + if (isNode(item)) { + if (item.spaceBefore) lines.push(""); + addCommentBefore(ctx, lines, item.commentBefore, false); + if (item.comment) comment = item.comment; + } else if (isPair(item)) { + const ik = isNode(item.key) ? item.key : null; + if (ik) { + if (ik.spaceBefore) lines.push(""); + addCommentBefore(ctx, lines, ik.commentBefore, false); + if (ik.comment) reqNewline = true; + } + const iv = isNode(item.value) ? item.value : null; + if (iv) { + if (iv.comment) comment = iv.comment; + if (iv.commentBefore) reqNewline = true; + } else if (item.value == null && ik?.comment) comment = ik.comment; + } + if (comment) reqNewline = true; + let str = stringify(item, itemCtx, () => comment = null); + reqNewline || (reqNewline = lines.length > linesAtValue || str.includes("\n")); + if (i < items.length - 1) str += ","; + else if (ctx.options.trailingComma) { + if (ctx.options.lineWidth > 0) reqNewline || (reqNewline = lines.reduce((sum, line) => sum + line.length + 2, 2) + (str.length + 2) > ctx.options.lineWidth); + if (reqNewline) str += ","; + } + if (comment) str += lineComment(str, itemIndent, commentString(comment)); + lines.push(str); + linesAtValue = lines.length; + } + const { start, end } = flowChars; + if (lines.length === 0) return start + end; + else { + if (!reqNewline) { + const len = lines.reduce((sum, line) => sum + line.length + 2, 2); + reqNewline = ctx.options.lineWidth > 0 && len > ctx.options.lineWidth; + } + if (reqNewline) { + let str = start; + for (const line of lines) str += line ? `\n${indentStep}${indent}${line}` : "\n"; + return `${str}\n${indent}${end}`; + } else return `${start}${fcPadding}${lines.join(" ")}${fcPadding}${end}`; + } +} +function addCommentBefore({ indent, options: { commentString } }, lines, comment, chompKeep) { + if (comment && chompKeep) comment = comment.replace(/^\n+/, ""); + if (comment) { + const ic = indentComment(commentString(comment), indent); + lines.push(ic.trimStart()); + } +} +//#endregion +//#region node_modules/yaml/browser/dist/nodes/YAMLMap.js +function findPair(items, key) { + const k = isScalar(key) ? key.value : key; + for (const it of items) if (isPair(it)) { + if (it.key === key || it.key === k) return it; + if (isScalar(it.key) && it.key.value === k) return it; + } +} +var YAMLMap = class extends Collection$1 { + static get tagName() { + return "tag:yaml.org,2002:map"; + } + constructor(schema) { + super(MAP, schema); + this.items = []; + } + /** + * A generic collection parsing method that can be extended + * to other node classes that inherit from YAMLMap + */ + static from(schema, obj, ctx) { + const { keepUndefined, replacer } = ctx; + const map = new this(schema); + const add = (key, value) => { + if (typeof replacer === "function") value = replacer.call(obj, key, value); + else if (Array.isArray(replacer) && !replacer.includes(key)) return; + if (value !== void 0 || keepUndefined) map.items.push(createPair(key, value, ctx)); + }; + if (obj instanceof Map) for (const [key, value] of obj) add(key, value); + else if (obj && typeof obj === "object") for (const key of Object.keys(obj)) add(key, obj[key]); + if (typeof schema.sortMapEntries === "function") map.items.sort(schema.sortMapEntries); + return map; + } + /** + * Adds a value to the collection. + * + * @param overwrite - If not set `true`, using a key that is already in the + * collection will throw. Otherwise, overwrites the previous value. + */ + add(pair, overwrite) { + let _pair; + if (isPair(pair)) _pair = pair; + else if (!pair || typeof pair !== "object" || !("key" in pair)) _pair = new Pair(pair, pair?.value); + else _pair = new Pair(pair.key, pair.value); + const prev = findPair(this.items, _pair.key); + const sortEntries = this.schema?.sortMapEntries; + if (prev) { + if (!overwrite) throw new Error(`Key ${_pair.key} already set`); + if (isScalar(prev.value) && isScalarValue(_pair.value)) prev.value.value = _pair.value; + else prev.value = _pair.value; + } else if (sortEntries) { + const i = this.items.findIndex((item) => sortEntries(_pair, item) < 0); + if (i === -1) this.items.push(_pair); + else this.items.splice(i, 0, _pair); + } else this.items.push(_pair); + } + delete(key) { + const it = findPair(this.items, key); + if (!it) return false; + return this.items.splice(this.items.indexOf(it), 1).length > 0; + } + get(key, keepScalar) { + const node = findPair(this.items, key)?.value; + return (!keepScalar && isScalar(node) ? node.value : node) ?? void 0; + } + has(key) { + return !!findPair(this.items, key); + } + set(key, value) { + this.add(new Pair(key, value), true); + } + /** + * @param ctx - Conversion context, originally set in Document#toJS() + * @param {Class} Type - If set, forces the returned collection type + * @returns Instance of Type, Map, or Object + */ + toJSON(_, ctx, Type) { + const map = Type ? new Type() : ctx?.mapAsMap ? /* @__PURE__ */ new Map() : {}; + if (ctx?.onCreate) ctx.onCreate(map); + for (const item of this.items) addPairToJSMap(ctx, map, item); + return map; + } + toString(ctx, onComment, onChompKeep) { + if (!ctx) return JSON.stringify(this); + for (const item of this.items) if (!isPair(item)) throw new Error(`Map items must all be pairs; found ${JSON.stringify(item)} instead`); + if (!ctx.allNullValues && this.hasAllNullValues(false)) ctx = Object.assign({}, ctx, { allNullValues: true }); + return stringifyCollection(this, ctx, { + blockItemPrefix: "", + flowChars: { + start: "{", + end: "}" + }, + itemIndent: ctx.indent || "", + onChompKeep, + onComment + }); + } +}; +//#endregion +//#region node_modules/yaml/browser/dist/schema/common/map.js +var map = { + collection: "map", + default: true, + nodeClass: YAMLMap, + tag: "tag:yaml.org,2002:map", + resolve(map, onError) { + if (!isMap(map)) onError("Expected a mapping for this tag"); + return map; + }, + createNode: (schema, obj, ctx) => YAMLMap.from(schema, obj, ctx) +}; +//#endregion +//#region node_modules/yaml/browser/dist/nodes/YAMLSeq.js +var YAMLSeq = class extends Collection$1 { + static get tagName() { + return "tag:yaml.org,2002:seq"; + } + constructor(schema) { + super(SEQ, schema); + this.items = []; + } + add(value) { + this.items.push(value); + } + /** + * Removes a value from the collection. + * + * `key` must contain a representation of an integer for this to succeed. + * It may be wrapped in a `Scalar`. + * + * @returns `true` if the item was found and removed. + */ + delete(key) { + const idx = asItemIndex(key); + if (typeof idx !== "number") return false; + return this.items.splice(idx, 1).length > 0; + } + get(key, keepScalar) { + const idx = asItemIndex(key); + if (typeof idx !== "number") return void 0; + const it = this.items[idx]; + return !keepScalar && isScalar(it) ? it.value : it; + } + /** + * Checks if the collection includes a value with the key `key`. + * + * `key` must contain a representation of an integer for this to succeed. + * It may be wrapped in a `Scalar`. + */ + has(key) { + const idx = asItemIndex(key); + return typeof idx === "number" && idx < this.items.length; + } + /** + * Sets a value in this collection. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + * + * If `key` does not contain a representation of an integer, this will throw. + * It may be wrapped in a `Scalar`. + */ + set(key, value) { + const idx = asItemIndex(key); + if (typeof idx !== "number") throw new Error(`Expected a valid index, not ${key}.`); + const prev = this.items[idx]; + if (isScalar(prev) && isScalarValue(value)) prev.value = value; + else this.items[idx] = value; + } + toJSON(_, ctx) { + const seq = []; + if (ctx?.onCreate) ctx.onCreate(seq); + let i = 0; + for (const item of this.items) seq.push(toJS(item, String(i++), ctx)); + return seq; + } + toString(ctx, onComment, onChompKeep) { + if (!ctx) return JSON.stringify(this); + return stringifyCollection(this, ctx, { + blockItemPrefix: "- ", + flowChars: { + start: "[", + end: "]" + }, + itemIndent: (ctx.indent || "") + " ", + onChompKeep, + onComment + }); + } + static from(schema, obj, ctx) { + const { replacer } = ctx; + const seq = new this(schema); + if (obj && Symbol.iterator in Object(obj)) { + let i = 0; + for (let it of obj) { + if (typeof replacer === "function") { + const key = obj instanceof Set ? it : String(i++); + it = replacer.call(obj, key, it); + } + seq.items.push(createNode(it, void 0, ctx)); + } + } + return seq; + } +}; +function asItemIndex(key) { + let idx = isScalar(key) ? key.value : key; + if (idx && typeof idx === "string") idx = Number(idx); + return typeof idx === "number" && Number.isInteger(idx) && idx >= 0 ? idx : null; +} +//#endregion +//#region node_modules/yaml/browser/dist/schema/common/seq.js +var seq = { + collection: "seq", + default: true, + nodeClass: YAMLSeq, + tag: "tag:yaml.org,2002:seq", + resolve(seq, onError) { + if (!isSeq(seq)) onError("Expected a sequence for this tag"); + return seq; + }, + createNode: (schema, obj, ctx) => YAMLSeq.from(schema, obj, ctx) +}; +//#endregion +//#region node_modules/yaml/browser/dist/schema/common/string.js +var string = { + identify: (value) => typeof value === "string", + default: true, + tag: "tag:yaml.org,2002:str", + resolve: (str) => str, + stringify(item, ctx, onComment, onChompKeep) { + ctx = Object.assign({ actualString: true }, ctx); + return stringifyString(item, ctx, onComment, onChompKeep); + } +}; +//#endregion +//#region node_modules/yaml/browser/dist/schema/common/null.js +var nullTag = { + identify: (value) => value == null, + createNode: () => new Scalar(null), + default: true, + tag: "tag:yaml.org,2002:null", + test: /^(?:~|[Nn]ull|NULL)?$/, + resolve: () => new Scalar(null), + stringify: ({ source }, ctx) => typeof source === "string" && nullTag.test.test(source) ? source : ctx.options.nullStr +}; +//#endregion +//#region node_modules/yaml/browser/dist/schema/core/bool.js +var boolTag = { + identify: (value) => typeof value === "boolean", + default: true, + tag: "tag:yaml.org,2002:bool", + test: /^(?:[Tt]rue|TRUE|[Ff]alse|FALSE)$/, + resolve: (str) => new Scalar(str[0] === "t" || str[0] === "T"), + stringify({ source, value }, ctx) { + if (source && boolTag.test.test(source)) { + if (value === (source[0] === "t" || source[0] === "T")) return source; + } + return value ? ctx.options.trueStr : ctx.options.falseStr; + } +}; +//#endregion +//#region node_modules/yaml/browser/dist/stringify/stringifyNumber.js +function stringifyNumber({ format, minFractionDigits, tag, value }) { + if (typeof value === "bigint") return String(value); + const num = typeof value === "number" ? value : Number(value); + if (!isFinite(num)) return isNaN(num) ? ".nan" : num < 0 ? "-.inf" : ".inf"; + let n = Object.is(value, -0) ? "-0" : JSON.stringify(value); + if (!format && minFractionDigits && (!tag || tag === "tag:yaml.org,2002:float") && /^-?\d/.test(n) && !n.includes("e")) { + let i = n.indexOf("."); + if (i < 0) { + i = n.length; + n += "."; + } + let d = minFractionDigits - (n.length - i - 1); + while (d-- > 0) n += "0"; + } + return n; +} +//#endregion +//#region node_modules/yaml/browser/dist/schema/core/float.js +var floatNaN$1 = { + identify: (value) => typeof value === "number", + default: true, + tag: "tag:yaml.org,2002:float", + test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/, + resolve: (str) => str.slice(-3).toLowerCase() === "nan" ? NaN : str[0] === "-" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY, + stringify: stringifyNumber +}; +var floatExp$1 = { + identify: (value) => typeof value === "number", + default: true, + tag: "tag:yaml.org,2002:float", + format: "EXP", + test: /^[-+]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)[eE][-+]?[0-9]+$/, + resolve: (str) => parseFloat(str), + stringify(node) { + const num = Number(node.value); + return isFinite(num) ? num.toExponential() : stringifyNumber(node); + } +}; +var float$1 = { + identify: (value) => typeof value === "number", + default: true, + tag: "tag:yaml.org,2002:float", + test: /^[-+]?(?:\.[0-9]+|[0-9]+\.[0-9]*)$/, + resolve(str) { + const node = new Scalar(parseFloat(str)); + const dot = str.indexOf("."); + if (dot !== -1 && str[str.length - 1] === "0") node.minFractionDigits = str.length - dot - 1; + return node; + }, + stringify: stringifyNumber +}; +//#endregion +//#region node_modules/yaml/browser/dist/schema/core/int.js +var intIdentify$2 = (value) => typeof value === "bigint" || Number.isInteger(value); +var intResolve$1 = (str, offset, radix, { intAsBigInt }) => intAsBigInt ? BigInt(str) : parseInt(str.substring(offset), radix); +function intStringify$1(node, radix, prefix) { + const { value } = node; + if (intIdentify$2(value) && value >= 0) return prefix + value.toString(radix); + return stringifyNumber(node); +} +var intOct$1 = { + identify: (value) => intIdentify$2(value) && value >= 0, + default: true, + tag: "tag:yaml.org,2002:int", + format: "OCT", + test: /^0o[0-7]+$/, + resolve: (str, _onError, opt) => intResolve$1(str, 2, 8, opt), + stringify: (node) => intStringify$1(node, 8, "0o") +}; +var int$1 = { + identify: intIdentify$2, + default: true, + tag: "tag:yaml.org,2002:int", + test: /^[-+]?[0-9]+$/, + resolve: (str, _onError, opt) => intResolve$1(str, 0, 10, opt), + stringify: stringifyNumber +}; +var intHex$1 = { + identify: (value) => intIdentify$2(value) && value >= 0, + default: true, + tag: "tag:yaml.org,2002:int", + format: "HEX", + test: /^0x[0-9a-fA-F]+$/, + resolve: (str, _onError, opt) => intResolve$1(str, 2, 16, opt), + stringify: (node) => intStringify$1(node, 16, "0x") +}; +//#endregion +//#region node_modules/yaml/browser/dist/schema/core/schema.js +var schema$2 = [ + map, + seq, + string, + nullTag, + boolTag, + intOct$1, + int$1, + intHex$1, + floatNaN$1, + floatExp$1, + float$1 +]; +//#endregion +//#region node_modules/yaml/browser/dist/schema/json/schema.js +function intIdentify$1(value) { + return typeof value === "bigint" || Number.isInteger(value); +} +var stringifyJSON = ({ value }) => JSON.stringify(value); +var jsonScalars = [ + { + identify: (value) => typeof value === "string", + default: true, + tag: "tag:yaml.org,2002:str", + resolve: (str) => str, + stringify: stringifyJSON + }, + { + identify: (value) => value == null, + createNode: () => new Scalar(null), + default: true, + tag: "tag:yaml.org,2002:null", + test: /^null$/, + resolve: () => null, + stringify: stringifyJSON + }, + { + identify: (value) => typeof value === "boolean", + default: true, + tag: "tag:yaml.org,2002:bool", + test: /^true$|^false$/, + resolve: (str) => str === "true", + stringify: stringifyJSON + }, + { + identify: intIdentify$1, + default: true, + tag: "tag:yaml.org,2002:int", + test: /^-?(?:0|[1-9][0-9]*)$/, + resolve: (str, _onError, { intAsBigInt }) => intAsBigInt ? BigInt(str) : parseInt(str, 10), + stringify: ({ value }) => intIdentify$1(value) ? value.toString() : JSON.stringify(value) + }, + { + identify: (value) => typeof value === "number", + default: true, + tag: "tag:yaml.org,2002:float", + test: /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][-+]?[0-9]+)?$/, + resolve: (str) => parseFloat(str), + stringify: stringifyJSON + } +]; +var schema$1 = [map, seq].concat(jsonScalars, { + default: true, + tag: "", + test: /^/, + resolve(str, onError) { + onError(`Unresolved plain scalar ${JSON.stringify(str)}`); + return str; + } +}); +//#endregion +//#region node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +var binary = { + identify: (value) => value instanceof Uint8Array, + default: false, + tag: "tag:yaml.org,2002:binary", + /** + * Returns a Buffer in node and an Uint8Array in browsers + * + * To use the resulting buffer as an image, you'll want to do something like: + * + * const blob = new Blob([buffer], { type: 'image/jpeg' }) + * document.querySelector('#photo').src = URL.createObjectURL(blob) + */ + resolve(src, onError) { + if (typeof atob === "function") { + const str = atob(src.replace(/[\n\r]/g, "")); + const buffer = new Uint8Array(str.length); + for (let i = 0; i < str.length; ++i) buffer[i] = str.charCodeAt(i); + return buffer; + } else { + onError("This environment does not support reading binary tags; either Buffer or atob is required"); + return src; + } + }, + stringify({ comment, type, value }, ctx, onComment, onChompKeep) { + if (!value) return ""; + const buf = value; + let str; + if (typeof btoa === "function") { + let s = ""; + for (let i = 0; i < buf.length; ++i) s += String.fromCharCode(buf[i]); + str = btoa(s); + } else throw new Error("This environment does not support writing binary tags; either Buffer or btoa is required"); + type ?? (type = Scalar.BLOCK_LITERAL); + if (type !== Scalar.QUOTE_DOUBLE) { + const lineWidth = Math.max(ctx.options.lineWidth - ctx.indent.length, ctx.options.minContentWidth); + const n = Math.ceil(str.length / lineWidth); + const lines = new Array(n); + for (let i = 0, o = 0; i < n; ++i, o += lineWidth) lines[i] = str.substr(o, lineWidth); + str = lines.join(type === Scalar.BLOCK_LITERAL ? "\n" : " "); + } + return stringifyString({ + comment, + type, + value: str + }, ctx, onComment, onChompKeep); + } +}; +//#endregion +//#region node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +function resolvePairs(seq, onError) { + if (isSeq(seq)) for (let i = 0; i < seq.items.length; ++i) { + let item = seq.items[i]; + if (isPair(item)) continue; + else if (isMap(item)) { + if (item.items.length > 1) onError("Each pair must have its own sequence indicator"); + const pair = item.items[0] || new Pair(new Scalar(null)); + if (item.commentBefore) pair.key.commentBefore = pair.key.commentBefore ? `${item.commentBefore}\n${pair.key.commentBefore}` : item.commentBefore; + if (item.comment) { + const cn = pair.value ?? pair.key; + cn.comment = cn.comment ? `${item.comment}\n${cn.comment}` : item.comment; + } + item = pair; + } + seq.items[i] = isPair(item) ? item : new Pair(item); + } + else onError("Expected a sequence for this tag"); + return seq; +} +function createPairs(schema, iterable, ctx) { + const { replacer } = ctx; + const pairs = new YAMLSeq(schema); + pairs.tag = "tag:yaml.org,2002:pairs"; + let i = 0; + if (iterable && Symbol.iterator in Object(iterable)) for (let it of iterable) { + if (typeof replacer === "function") it = replacer.call(iterable, String(i++), it); + let key, value; + if (Array.isArray(it)) if (it.length === 2) { + key = it[0]; + value = it[1]; + } else throw new TypeError(`Expected [key, value] tuple: ${it}`); + else if (it && it instanceof Object) { + const keys = Object.keys(it); + if (keys.length === 1) { + key = keys[0]; + value = it[key]; + } else throw new TypeError(`Expected tuple with one key, not ${keys.length} keys`); + } else key = it; + pairs.items.push(createPair(key, value, ctx)); + } + return pairs; +} +var pairs = { + collection: "seq", + default: false, + tag: "tag:yaml.org,2002:pairs", + resolve: resolvePairs, + createNode: createPairs +}; +//#endregion +//#region node_modules/yaml/browser/dist/schema/yaml-1.1/omap.js +var YAMLOMap = class YAMLOMap extends YAMLSeq { + constructor() { + super(); + this.add = YAMLMap.prototype.add.bind(this); + this.delete = YAMLMap.prototype.delete.bind(this); + this.get = YAMLMap.prototype.get.bind(this); + this.has = YAMLMap.prototype.has.bind(this); + this.set = YAMLMap.prototype.set.bind(this); + this.tag = YAMLOMap.tag; + } + /** + * If `ctx` is given, the return type is actually `Map`, + * but TypeScript won't allow widening the signature of a child method. + */ + toJSON(_, ctx) { + if (!ctx) return super.toJSON(_); + const map = /* @__PURE__ */ new Map(); + if (ctx?.onCreate) ctx.onCreate(map); + for (const pair of this.items) { + let key, value; + if (isPair(pair)) { + key = toJS(pair.key, "", ctx); + value = toJS(pair.value, key, ctx); + } else key = toJS(pair, "", ctx); + if (map.has(key)) throw new Error("Ordered maps must not include duplicate keys"); + map.set(key, value); + } + return map; + } + static from(schema, iterable, ctx) { + const pairs = createPairs(schema, iterable, ctx); + const omap = new this(); + omap.items = pairs.items; + return omap; + } +}; +YAMLOMap.tag = "tag:yaml.org,2002:omap"; +var omap = { + collection: "seq", + identify: (value) => value instanceof Map, + nodeClass: YAMLOMap, + default: false, + tag: "tag:yaml.org,2002:omap", + resolve(seq, onError) { + const pairs = resolvePairs(seq, onError); + const seenKeys = []; + for (const { key } of pairs.items) if (isScalar(key)) if (seenKeys.includes(key.value)) onError(`Ordered maps must not include duplicate keys: ${key.value}`); + else seenKeys.push(key.value); + return Object.assign(new YAMLOMap(), pairs); + }, + createNode: (schema, iterable, ctx) => YAMLOMap.from(schema, iterable, ctx) +}; +//#endregion +//#region node_modules/yaml/browser/dist/schema/yaml-1.1/bool.js +function boolStringify({ value, source }, ctx) { + if (source && (value ? trueTag : falseTag).test.test(source)) return source; + return value ? ctx.options.trueStr : ctx.options.falseStr; +} +var trueTag = { + identify: (value) => value === true, + default: true, + tag: "tag:yaml.org,2002:bool", + test: /^(?:Y|y|[Yy]es|YES|[Tt]rue|TRUE|[Oo]n|ON)$/, + resolve: () => new Scalar(true), + stringify: boolStringify +}; +var falseTag = { + identify: (value) => value === false, + default: true, + tag: "tag:yaml.org,2002:bool", + test: /^(?:N|n|[Nn]o|NO|[Ff]alse|FALSE|[Oo]ff|OFF)$/, + resolve: () => new Scalar(false), + stringify: boolStringify +}; +//#endregion +//#region node_modules/yaml/browser/dist/schema/yaml-1.1/float.js +var floatNaN = { + identify: (value) => typeof value === "number", + default: true, + tag: "tag:yaml.org,2002:float", + test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/, + resolve: (str) => str.slice(-3).toLowerCase() === "nan" ? NaN : str[0] === "-" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY, + stringify: stringifyNumber +}; +var floatExp = { + identify: (value) => typeof value === "number", + default: true, + tag: "tag:yaml.org,2002:float", + format: "EXP", + test: /^[-+]?(?:[0-9][0-9_]*)?(?:\.[0-9_]*)?[eE][-+]?[0-9]+$/, + resolve: (str) => parseFloat(str.replace(/_/g, "")), + stringify(node) { + const num = Number(node.value); + return isFinite(num) ? num.toExponential() : stringifyNumber(node); + } +}; +var float = { + identify: (value) => typeof value === "number", + default: true, + tag: "tag:yaml.org,2002:float", + test: /^[-+]?(?:[0-9][0-9_]*)?\.[0-9_]*$/, + resolve(str) { + const node = new Scalar(parseFloat(str.replace(/_/g, ""))); + const dot = str.indexOf("."); + if (dot !== -1) { + const f = str.substring(dot + 1).replace(/_/g, ""); + if (f[f.length - 1] === "0") node.minFractionDigits = f.length; + } + return node; + }, + stringify: stringifyNumber +}; +//#endregion +//#region node_modules/yaml/browser/dist/schema/yaml-1.1/int.js +var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value); +function intResolve(str, offset, radix, { intAsBigInt }) { + const sign = str[0]; + if (sign === "-" || sign === "+") offset += 1; + str = str.substring(offset).replace(/_/g, ""); + if (intAsBigInt) { + switch (radix) { + case 2: + str = `0b${str}`; + break; + case 8: + str = `0o${str}`; + break; + case 16: + str = `0x${str}`; + break; + } + const n = BigInt(str); + return sign === "-" ? BigInt(-1) * n : n; + } + const n = parseInt(str, radix); + return sign === "-" ? -1 * n : n; +} +function intStringify(node, radix, prefix) { + const { value } = node; + if (intIdentify(value)) { + const str = value.toString(radix); + return value < 0 ? "-" + prefix + str.substr(1) : prefix + str; + } + return stringifyNumber(node); +} +var intBin = { + identify: intIdentify, + default: true, + tag: "tag:yaml.org,2002:int", + format: "BIN", + test: /^[-+]?0b[0-1_]+$/, + resolve: (str, _onError, opt) => intResolve(str, 2, 2, opt), + stringify: (node) => intStringify(node, 2, "0b") +}; +var intOct = { + identify: intIdentify, + default: true, + tag: "tag:yaml.org,2002:int", + format: "OCT", + test: /^[-+]?0[0-7_]+$/, + resolve: (str, _onError, opt) => intResolve(str, 1, 8, opt), + stringify: (node) => intStringify(node, 8, "0") +}; +var int = { + identify: intIdentify, + default: true, + tag: "tag:yaml.org,2002:int", + test: /^[-+]?[0-9][0-9_]*$/, + resolve: (str, _onError, opt) => intResolve(str, 0, 10, opt), + stringify: stringifyNumber +}; +var intHex = { + identify: intIdentify, + default: true, + tag: "tag:yaml.org,2002:int", + format: "HEX", + test: /^[-+]?0x[0-9a-fA-F_]+$/, + resolve: (str, _onError, opt) => intResolve(str, 2, 16, opt), + stringify: (node) => intStringify(node, 16, "0x") +}; +//#endregion +//#region node_modules/yaml/browser/dist/schema/yaml-1.1/set.js +var YAMLSet = class YAMLSet extends YAMLMap { + constructor(schema) { + super(schema); + this.tag = YAMLSet.tag; + } + add(key) { + let pair; + if (isPair(key)) pair = key; + else if (key && typeof key === "object" && "key" in key && "value" in key && key.value === null) pair = new Pair(key.key, null); + else pair = new Pair(key, null); + if (!findPair(this.items, pair.key)) this.items.push(pair); + } + /** + * If `keepPair` is `true`, returns the Pair matching `key`. + * Otherwise, returns the value of that Pair's key. + */ + get(key, keepPair) { + const pair = findPair(this.items, key); + return !keepPair && isPair(pair) ? isScalar(pair.key) ? pair.key.value : pair.key : pair; + } + set(key, value) { + if (typeof value !== "boolean") throw new Error(`Expected boolean value for set(key, value) in a YAML set, not ${typeof value}`); + const prev = findPair(this.items, key); + if (prev && !value) this.items.splice(this.items.indexOf(prev), 1); + else if (!prev && value) this.items.push(new Pair(key)); + } + toJSON(_, ctx) { + return super.toJSON(_, ctx, Set); + } + toString(ctx, onComment, onChompKeep) { + if (!ctx) return JSON.stringify(this); + if (this.hasAllNullValues(true)) return super.toString(Object.assign({}, ctx, { allNullValues: true }), onComment, onChompKeep); + else throw new Error("Set items must all have null values"); + } + static from(schema, iterable, ctx) { + const { replacer } = ctx; + const set = new this(schema); + if (iterable && Symbol.iterator in Object(iterable)) for (let value of iterable) { + if (typeof replacer === "function") value = replacer.call(iterable, value, value); + set.items.push(createPair(value, null, ctx)); + } + return set; + } +}; +YAMLSet.tag = "tag:yaml.org,2002:set"; +var set$1 = { + collection: "map", + identify: (value) => value instanceof Set, + nodeClass: YAMLSet, + default: false, + tag: "tag:yaml.org,2002:set", + createNode: (schema, iterable, ctx) => YAMLSet.from(schema, iterable, ctx), + resolve(map, onError) { + if (isMap(map)) if (map.hasAllNullValues(true)) return Object.assign(new YAMLSet(), map); + else onError("Set items must all have null values"); + else onError("Expected a mapping for this tag"); + return map; + } +}; +//#endregion +//#region node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +/** Internal types handle bigint as number, because TS can't figure it out. */ +function parseSexagesimal(str, asBigInt) { + const sign = str[0]; + const parts = sign === "-" || sign === "+" ? str.substring(1) : str; + const num = (n) => asBigInt ? BigInt(n) : Number(n); + const res = parts.replace(/_/g, "").split(":").reduce((res, p) => res * num(60) + num(p), num(0)); + return sign === "-" ? num(-1) * res : res; +} +/** +* hhhh:mm:ss.sss +* +* Internal types handle bigint as number, because TS can't figure it out. +*/ +function stringifySexagesimal(node) { + let { value } = node; + let num = (n) => n; + if (typeof value === "bigint") num = (n) => BigInt(n); + else if (isNaN(value) || !isFinite(value)) return stringifyNumber(node); + let sign = ""; + if (value < 0) { + sign = "-"; + value *= num(-1); + } + const _60 = num(60); + const parts = [value % _60]; + if (value < 60) parts.unshift(0); + else { + value = (value - parts[0]) / _60; + parts.unshift(value % _60); + if (value >= 60) { + value = (value - parts[0]) / _60; + parts.unshift(value); + } + } + return sign + parts.map((n) => String(n).padStart(2, "0")).join(":").replace(/000000\d*$/, ""); +} +var intTime = { + identify: (value) => typeof value === "bigint" || Number.isInteger(value), + default: true, + tag: "tag:yaml.org,2002:int", + format: "TIME", + test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+$/, + resolve: (str, _onError, { intAsBigInt }) => parseSexagesimal(str, intAsBigInt), + stringify: stringifySexagesimal +}; +var floatTime = { + identify: (value) => typeof value === "number", + default: true, + tag: "tag:yaml.org,2002:float", + format: "TIME", + test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*$/, + resolve: (str) => parseSexagesimal(str, false), + stringify: stringifySexagesimal +}; +var timestamp = { + identify: (value) => value instanceof Date, + default: true, + tag: "tag:yaml.org,2002:timestamp", + test: RegExp("^([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})(?:(?:t|T|[ \\t]+)([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}(\\.[0-9]+)?)(?:[ \\t]*(Z|[-+][012]?[0-9](?::[0-9]{2})?))?)?$"), + resolve(str) { + const match = str.match(timestamp.test); + if (!match) throw new Error("!!timestamp expects a date, starting with yyyy-mm-dd"); + const [, year, month, day, hour, minute, second] = match.map(Number); + const millisec = match[7] ? Number((match[7] + "00").substr(1, 3)) : 0; + let date = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec); + const tz = match[8]; + if (tz && tz !== "Z") { + let d = parseSexagesimal(tz, false); + if (Math.abs(d) < 30) d *= 60; + date -= 6e4 * d; + } + return new Date(date); + }, + stringify: ({ value }) => value?.toISOString().replace(/(T00:00:00)?\.000Z$/, "") ?? "" +}; +//#endregion +//#region node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +var schema = [ + map, + seq, + string, + nullTag, + trueTag, + falseTag, + intBin, + intOct, + int, + intHex, + floatNaN, + floatExp, + float, + binary, + merge$1, + omap, + pairs, + set$1, + intTime, + floatTime, + timestamp +]; +//#endregion +//#region node_modules/yaml/browser/dist/schema/tags.js +var schemas = /* @__PURE__ */ new Map([ + ["core", schema$2], + ["failsafe", [ + map, + seq, + string + ]], + ["json", schema$1], + ["yaml11", schema], + ["yaml-1.1", schema] +]); +var tagsByName = { + binary, + bool: boolTag, + float: float$1, + floatExp: floatExp$1, + floatNaN: floatNaN$1, + floatTime, + int: int$1, + intHex: intHex$1, + intOct: intOct$1, + intTime, + map, + merge: merge$1, + null: nullTag, + omap, + pairs, + seq, + set: set$1, + timestamp +}; +var coreKnownTags = { + "tag:yaml.org,2002:binary": binary, + "tag:yaml.org,2002:merge": merge$1, + "tag:yaml.org,2002:omap": omap, + "tag:yaml.org,2002:pairs": pairs, + "tag:yaml.org,2002:set": set$1, + "tag:yaml.org,2002:timestamp": timestamp +}; +function getTags(customTags, schemaName, addMergeTag) { + const schemaTags = schemas.get(schemaName); + if (schemaTags && !customTags) return addMergeTag && !schemaTags.includes(merge$1) ? schemaTags.concat(merge$1) : schemaTags.slice(); + let tags = schemaTags; + if (!tags) if (Array.isArray(customTags)) tags = []; + else { + const keys = Array.from(schemas.keys()).filter((key) => key !== "yaml11").map((key) => JSON.stringify(key)).join(", "); + throw new Error(`Unknown schema "${schemaName}"; use one of ${keys} or define customTags array`); + } + if (Array.isArray(customTags)) for (const tag of customTags) tags = tags.concat(tag); + else if (typeof customTags === "function") tags = customTags(tags.slice()); + if (addMergeTag) tags = tags.concat(merge$1); + return tags.reduce((tags, tag) => { + const tagObj = typeof tag === "string" ? tagsByName[tag] : tag; + if (!tagObj) { + const tagName = JSON.stringify(tag); + const keys = Object.keys(tagsByName).map((key) => JSON.stringify(key)).join(", "); + throw new Error(`Unknown custom tag ${tagName}; use one of ${keys}`); + } + if (!tags.includes(tagObj)) tags.push(tagObj); + return tags; + }, []); +} +//#endregion +//#region node_modules/yaml/browser/dist/schema/Schema.js +var sortMapEntriesByKey = (a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0; +var Schema = class Schema { + constructor({ compat, customTags, merge, resolveKnownTags, schema, sortMapEntries, toStringDefaults }) { + this.compat = Array.isArray(compat) ? getTags(compat, "compat") : compat ? getTags(null, compat) : null; + this.name = typeof schema === "string" && schema || "core"; + this.knownTags = resolveKnownTags ? coreKnownTags : {}; + this.tags = getTags(customTags, this.name, merge); + this.toStringOptions = toStringDefaults ?? null; + Object.defineProperty(this, MAP, { value: map }); + Object.defineProperty(this, SCALAR$1, { value: string }); + Object.defineProperty(this, SEQ, { value: seq }); + this.sortMapEntries = typeof sortMapEntries === "function" ? sortMapEntries : sortMapEntries === true ? sortMapEntriesByKey : null; + } + clone() { + const copy = Object.create(Schema.prototype, Object.getOwnPropertyDescriptors(this)); + copy.tags = this.tags.slice(); + return copy; + } +}; +//#endregion +//#region node_modules/yaml/browser/dist/stringify/stringifyDocument.js +function stringifyDocument(doc, options) { + const lines = []; + let hasDirectives = options.directives === true; + if (options.directives !== false && doc.directives) { + const dir = doc.directives.toString(doc); + if (dir) { + lines.push(dir); + hasDirectives = true; + } else if (doc.directives.docStart) hasDirectives = true; + } + if (hasDirectives) lines.push("---"); + const ctx = createStringifyContext(doc, options); + const { commentString } = ctx.options; + if (doc.commentBefore) { + if (lines.length !== 1) lines.unshift(""); + const cs = commentString(doc.commentBefore); + lines.unshift(indentComment(cs, "")); + } + let chompKeep = false; + let contentComment = null; + if (doc.contents) { + if (isNode(doc.contents)) { + if (doc.contents.spaceBefore && hasDirectives) lines.push(""); + if (doc.contents.commentBefore) { + const cs = commentString(doc.contents.commentBefore); + lines.push(indentComment(cs, "")); + } + ctx.forceBlockIndent = !!doc.comment; + contentComment = doc.contents.comment; + } + const onChompKeep = contentComment ? void 0 : () => chompKeep = true; + let body = stringify(doc.contents, ctx, () => contentComment = null, onChompKeep); + if (contentComment) body += lineComment(body, "", commentString(contentComment)); + if ((body[0] === "|" || body[0] === ">") && lines[lines.length - 1] === "---") lines[lines.length - 1] = `--- ${body}`; + else lines.push(body); + } else lines.push(stringify(doc.contents, ctx)); + if (doc.directives?.docEnd) if (doc.comment) { + const cs = commentString(doc.comment); + if (cs.includes("\n")) { + lines.push("..."); + lines.push(indentComment(cs, "")); + } else lines.push(`... ${cs}`); + } else lines.push("..."); + else { + let dc = doc.comment; + if (dc && chompKeep) dc = dc.replace(/^\n+/, ""); + if (dc) { + if ((!chompKeep || contentComment) && lines[lines.length - 1] !== "") lines.push(""); + lines.push(indentComment(commentString(dc), "")); + } + } + return lines.join("\n") + "\n"; +} +//#endregion +//#region node_modules/yaml/browser/dist/doc/Document.js +var Document = class Document { + constructor(value, replacer, options) { + /** A comment before this Document */ + this.commentBefore = null; + /** A comment immediately after this Document */ + this.comment = null; + /** Errors encountered during parsing. */ + this.errors = []; + /** Warnings encountered during parsing. */ + this.warnings = []; + Object.defineProperty(this, NODE_TYPE, { value: DOC }); + let _replacer = null; + if (typeof replacer === "function" || Array.isArray(replacer)) _replacer = replacer; + else if (options === void 0 && replacer) { + options = replacer; + replacer = void 0; + } + const opt = Object.assign({ + intAsBigInt: false, + keepSourceTokens: false, + logLevel: "warn", + prettyErrors: true, + strict: true, + stringKeys: false, + uniqueKeys: true, + version: "1.2" + }, options); + this.options = opt; + let { version } = opt; + if (options?._directives) { + this.directives = options._directives.atDocument(); + if (this.directives.yaml.explicit) version = this.directives.yaml.version; + } else this.directives = new Directives({ version }); + this.setSchema(version, options); + this.contents = value === void 0 ? null : this.createNode(value, _replacer, options); + } + /** + * Create a deep copy of this Document and its contents. + * + * Custom Node values that inherit from `Object` still refer to their original instances. + */ + clone() { + const copy = Object.create(Document.prototype, { [NODE_TYPE]: { value: DOC } }); + copy.commentBefore = this.commentBefore; + copy.comment = this.comment; + copy.errors = this.errors.slice(); + copy.warnings = this.warnings.slice(); + copy.options = Object.assign({}, this.options); + if (this.directives) copy.directives = this.directives.clone(); + copy.schema = this.schema.clone(); + copy.contents = isNode(this.contents) ? this.contents.clone(copy.schema) : this.contents; + if (this.range) copy.range = this.range.slice(); + return copy; + } + /** Adds a value to the document. */ + add(value) { + if (assertCollection(this.contents)) this.contents.add(value); + } + /** Adds a value to the document. */ + addIn(path, value) { + if (assertCollection(this.contents)) this.contents.addIn(path, value); + } + /** + * Create a new `Alias` node, ensuring that the target `node` has the required anchor. + * + * If `node` already has an anchor, `name` is ignored. + * Otherwise, the `node.anchor` value will be set to `name`, + * or if an anchor with that name is already present in the document, + * `name` will be used as a prefix for a new unique anchor. + * If `name` is undefined, the generated anchor will use 'a' as a prefix. + */ + createAlias(node, name) { + if (!node.anchor) { + const prev = anchorNames(this); + node.anchor = !name || prev.has(name) ? findNewAnchor(name || "a", prev) : name; + } + return new Alias(node.anchor); + } + createNode(value, replacer, options) { + let _replacer = void 0; + if (typeof replacer === "function") { + value = replacer.call({ "": value }, "", value); + _replacer = replacer; + } else if (Array.isArray(replacer)) { + const keyToStr = (v) => typeof v === "number" || v instanceof String || v instanceof Number; + const asStr = replacer.filter(keyToStr).map(String); + if (asStr.length > 0) replacer = replacer.concat(asStr); + _replacer = replacer; + } else if (options === void 0 && replacer) { + options = replacer; + replacer = void 0; + } + const { aliasDuplicateObjects, anchorPrefix, flow, keepUndefined, onTagObj, tag } = options ?? {}; + const { onAnchor, setAnchors, sourceObjects } = createNodeAnchors(this, anchorPrefix || "a"); + const ctx = { + aliasDuplicateObjects: aliasDuplicateObjects ?? true, + keepUndefined: keepUndefined ?? false, + onAnchor, + onTagObj, + replacer: _replacer, + schema: this.schema, + sourceObjects + }; + const node = createNode(value, tag, ctx); + if (flow && isCollection(node)) node.flow = true; + setAnchors(); + return node; + } + /** + * Convert a key and a value into a `Pair` using the current schema, + * recursively wrapping all values as `Scalar` or `Collection` nodes. + */ + createPair(key, value, options = {}) { + return new Pair(this.createNode(key, null, options), this.createNode(value, null, options)); + } + /** + * Removes a value from the document. + * @returns `true` if the item was found and removed. + */ + delete(key) { + return assertCollection(this.contents) ? this.contents.delete(key) : false; + } + /** + * Removes a value from the document. + * @returns `true` if the item was found and removed. + */ + deleteIn(path) { + if (isEmptyPath(path)) { + if (this.contents == null) return false; + this.contents = null; + return true; + } + return assertCollection(this.contents) ? this.contents.deleteIn(path) : false; + } + /** + * Returns item at `key`, or `undefined` if not found. By default unwraps + * scalar values from their surrounding node; to disable set `keepScalar` to + * `true` (collections are always returned intact). + */ + get(key, keepScalar) { + return isCollection(this.contents) ? this.contents.get(key, keepScalar) : void 0; + } + /** + * Returns item at `path`, or `undefined` if not found. By default unwraps + * scalar values from their surrounding node; to disable set `keepScalar` to + * `true` (collections are always returned intact). + */ + getIn(path, keepScalar) { + if (isEmptyPath(path)) return !keepScalar && isScalar(this.contents) ? this.contents.value : this.contents; + return isCollection(this.contents) ? this.contents.getIn(path, keepScalar) : void 0; + } + /** + * Checks if the document includes a value with the key `key`. + */ + has(key) { + return isCollection(this.contents) ? this.contents.has(key) : false; + } + /** + * Checks if the document includes a value at `path`. + */ + hasIn(path) { + if (isEmptyPath(path)) return this.contents !== void 0; + return isCollection(this.contents) ? this.contents.hasIn(path) : false; + } + /** + * Sets a value in this document. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + */ + set(key, value) { + if (this.contents == null) this.contents = collectionFromPath(this.schema, [key], value); + else if (assertCollection(this.contents)) this.contents.set(key, value); + } + /** + * Sets a value in this document. For `!!set`, `value` needs to be a + * boolean to add/remove the item from the set. + */ + setIn(path, value) { + if (isEmptyPath(path)) this.contents = value; + else if (this.contents == null) this.contents = collectionFromPath(this.schema, Array.from(path), value); + else if (assertCollection(this.contents)) this.contents.setIn(path, value); + } + /** + * Change the YAML version and schema used by the document. + * A `null` version disables support for directives, explicit tags, anchors, and aliases. + * It also requires the `schema` option to be given as a `Schema` instance value. + * + * Overrides all previously set schema options. + */ + setSchema(version, options = {}) { + if (typeof version === "number") version = String(version); + let opt; + switch (version) { + case "1.1": + if (this.directives) this.directives.yaml.version = "1.1"; + else this.directives = new Directives({ version: "1.1" }); + opt = { + resolveKnownTags: false, + schema: "yaml-1.1" + }; + break; + case "1.2": + case "next": + if (this.directives) this.directives.yaml.version = version; + else this.directives = new Directives({ version }); + opt = { + resolveKnownTags: true, + schema: "core" + }; + break; + case null: + if (this.directives) delete this.directives; + opt = null; + break; + default: { + const sv = JSON.stringify(version); + throw new Error(`Expected '1.1', '1.2' or null as first argument, but found: ${sv}`); + } + } + if (options.schema instanceof Object) this.schema = options.schema; + else if (opt) this.schema = new Schema(Object.assign(opt, options)); + else throw new Error(`With a null YAML version, the { schema: Schema } option is required`); + } + toJS({ json, jsonArg, mapAsMap, maxAliasCount, onAnchor, reviver } = {}) { + const ctx = { + anchors: /* @__PURE__ */ new Map(), + doc: this, + keep: !json, + mapAsMap: mapAsMap === true, + mapKeyWarned: false, + maxAliasCount: typeof maxAliasCount === "number" ? maxAliasCount : 100 + }; + const res = toJS(this.contents, jsonArg ?? "", ctx); + if (typeof onAnchor === "function") for (const { count, res } of ctx.anchors.values()) onAnchor(res, count); + return typeof reviver === "function" ? applyReviver(reviver, { "": res }, "", res) : res; + } + /** + * A JSON representation of the document `contents`. + * + * @param jsonArg Used by `JSON.stringify` to indicate the array index or + * property name. + */ + toJSON(jsonArg, onAnchor) { + return this.toJS({ + json: true, + jsonArg, + mapAsMap: false, + onAnchor + }); + } + /** A YAML representation of the document. */ + toString(options = {}) { + if (this.errors.length > 0) throw new Error("Document with errors cannot be stringified"); + if ("indent" in options && (!Number.isInteger(options.indent) || Number(options.indent) <= 0)) { + const s = JSON.stringify(options.indent); + throw new Error(`"indent" option must be a positive integer, not ${s}`); + } + return stringifyDocument(this, options); + } +}; +function assertCollection(contents) { + if (isCollection(contents)) return true; + throw new Error("Expected a YAML collection as document contents"); +} +//#endregion +//#region node_modules/yaml/browser/dist/errors.js +var YAMLError = class extends Error { + constructor(name, pos, code, message) { + super(); + this.name = name; + this.code = code; + this.message = message; + this.pos = pos; + } +}; +var YAMLParseError = class extends YAMLError { + constructor(pos, code, message) { + super("YAMLParseError", pos, code, message); + } +}; +var YAMLWarning = class extends YAMLError { + constructor(pos, code, message) { + super("YAMLWarning", pos, code, message); + } +}; +var prettifyError = (src, lc) => (error) => { + if (error.pos[0] === -1) return; + error.linePos = error.pos.map((pos) => lc.linePos(pos)); + const { line, col } = error.linePos[0]; + error.message += ` at line ${line}, column ${col}`; + let ci = col - 1; + let lineStr = src.substring(lc.lineStarts[line - 1], lc.lineStarts[line]).replace(/[\n\r]+$/, ""); + if (ci >= 60 && lineStr.length > 80) { + const trimStart = Math.min(ci - 39, lineStr.length - 79); + lineStr = "…" + lineStr.substring(trimStart); + ci -= trimStart - 1; + } + if (lineStr.length > 80) lineStr = lineStr.substring(0, 79) + "…"; + if (line > 1 && /^ *$/.test(lineStr.substring(0, ci))) { + let prev = src.substring(lc.lineStarts[line - 2], lc.lineStarts[line - 1]); + if (prev.length > 80) prev = prev.substring(0, 79) + "…\n"; + lineStr = prev + lineStr; + } + if (/[^ ]/.test(lineStr)) { + let count = 1; + const end = error.linePos[1]; + if (end?.line === line && end.col > col) count = Math.max(1, Math.min(end.col - col, 80 - ci)); + const pointer = " ".repeat(ci) + "^".repeat(count); + error.message += `:\n\n${lineStr}\n${pointer}\n`; + } +}; +//#endregion +//#region node_modules/yaml/browser/dist/compose/resolve-props.js +function resolveProps(tokens, { flow, indicator, next, offset, onError, parentIndent, startOnNewline }) { + let spaceBefore = false; + let atNewline = startOnNewline; + let hasSpace = startOnNewline; + let comment = ""; + let commentSep = ""; + let hasNewline = false; + let reqSpace = false; + let tab = null; + let anchor = null; + let tag = null; + let newlineAfterProp = null; + let comma = null; + let found = null; + let start = null; + for (const token of tokens) { + if (reqSpace) { + if (token.type !== "space" && token.type !== "newline" && token.type !== "comma") onError(token.offset, "MISSING_CHAR", "Tags and anchors must be separated from the next token by white space"); + reqSpace = false; + } + if (tab) { + if (atNewline && token.type !== "comment" && token.type !== "newline") onError(tab, "TAB_AS_INDENT", "Tabs are not allowed as indentation"); + tab = null; + } + switch (token.type) { + case "space": + if (!flow && (indicator !== "doc-start" || next?.type !== "flow-collection") && token.source.includes(" ")) tab = token; + hasSpace = true; + break; + case "comment": { + if (!hasSpace) onError(token, "MISSING_CHAR", "Comments must be separated from other tokens by white space characters"); + const cb = token.source.substring(1) || " "; + if (!comment) comment = cb; + else comment += commentSep + cb; + commentSep = ""; + atNewline = false; + break; + } + case "newline": + if (atNewline) { + if (comment) comment += token.source; + else if (!found || indicator !== "seq-item-ind") spaceBefore = true; + } else commentSep += token.source; + atNewline = true; + hasNewline = true; + if (anchor || tag) newlineAfterProp = token; + hasSpace = true; + break; + case "anchor": + if (anchor) onError(token, "MULTIPLE_ANCHORS", "A node can have at most one anchor"); + if (token.source.endsWith(":")) onError(token.offset + token.source.length - 1, "BAD_ALIAS", "Anchor ending in : is ambiguous", true); + anchor = token; + start ?? (start = token.offset); + atNewline = false; + hasSpace = false; + reqSpace = true; + break; + case "tag": + if (tag) onError(token, "MULTIPLE_TAGS", "A node can have at most one tag"); + tag = token; + start ?? (start = token.offset); + atNewline = false; + hasSpace = false; + reqSpace = true; + break; + case indicator: + if (anchor || tag) onError(token, "BAD_PROP_ORDER", `Anchors and tags must be after the ${token.source} indicator`); + if (found) onError(token, "UNEXPECTED_TOKEN", `Unexpected ${token.source} in ${flow ?? "collection"}`); + found = token; + atNewline = indicator === "seq-item-ind" || indicator === "explicit-key-ind"; + hasSpace = false; + break; + case "comma": if (flow) { + if (comma) onError(token, "UNEXPECTED_TOKEN", `Unexpected , in ${flow}`); + comma = token; + atNewline = false; + hasSpace = false; + break; + } + default: + onError(token, "UNEXPECTED_TOKEN", `Unexpected ${token.type} token`); + atNewline = false; + hasSpace = false; + } + } + const last = tokens[tokens.length - 1]; + const end = last ? last.offset + last.source.length : offset; + if (reqSpace && next && next.type !== "space" && next.type !== "newline" && next.type !== "comma" && (next.type !== "scalar" || next.source !== "")) onError(next.offset, "MISSING_CHAR", "Tags and anchors must be separated from the next token by white space"); + if (tab && (atNewline && tab.indent <= parentIndent || next?.type === "block-map" || next?.type === "block-seq")) onError(tab, "TAB_AS_INDENT", "Tabs are not allowed as indentation"); + return { + comma, + found, + spaceBefore, + comment, + hasNewline, + anchor, + tag, + newlineAfterProp, + end, + start: start ?? end + }; +} +//#endregion +//#region node_modules/yaml/browser/dist/compose/util-contains-newline.js +function containsNewline(key) { + if (!key) return null; + switch (key.type) { + case "alias": + case "scalar": + case "double-quoted-scalar": + case "single-quoted-scalar": + if (key.source.includes("\n")) return true; + if (key.end) { + for (const st of key.end) if (st.type === "newline") return true; + } + return false; + case "flow-collection": + for (const it of key.items) { + for (const st of it.start) if (st.type === "newline") return true; + if (it.sep) { + for (const st of it.sep) if (st.type === "newline") return true; + } + if (containsNewline(it.key) || containsNewline(it.value)) return true; + } + return false; + default: return true; + } +} +//#endregion +//#region node_modules/yaml/browser/dist/compose/util-flow-indent-check.js +function flowIndentCheck(indent, fc, onError) { + if (fc?.type === "flow-collection") { + const end = fc.end[0]; + if (end.indent === indent && (end.source === "]" || end.source === "}") && containsNewline(fc)) onError(end, "BAD_INDENT", "Flow end indicator should be more indented than parent", true); + } +} +//#endregion +//#region node_modules/yaml/browser/dist/compose/util-map-includes.js +function mapIncludes(ctx, items, search) { + const { uniqueKeys } = ctx.options; + if (uniqueKeys === false) return false; + const isEqual = typeof uniqueKeys === "function" ? uniqueKeys : (a, b) => a === b || isScalar(a) && isScalar(b) && a.value === b.value; + return items.some((pair) => isEqual(pair.key, search)); +} +//#endregion +//#region node_modules/yaml/browser/dist/compose/resolve-block-map.js +var startColMsg = "All mapping items must start at the same column"; +function resolveBlockMap({ composeNode, composeEmptyNode }, ctx, bm, onError, tag) { + const map = new ((tag?.nodeClass) ?? YAMLMap)(ctx.schema); + if (ctx.atRoot) ctx.atRoot = false; + let offset = bm.offset; + let commentEnd = null; + for (const collItem of bm.items) { + const { start, key, sep, value } = collItem; + const keyProps = resolveProps(start, { + indicator: "explicit-key-ind", + next: key ?? sep?.[0], + offset, + onError, + parentIndent: bm.indent, + startOnNewline: true + }); + const implicitKey = !keyProps.found; + if (implicitKey) { + if (key) { + if (key.type === "block-seq") onError(offset, "BLOCK_AS_IMPLICIT_KEY", "A block sequence may not be used as an implicit map key"); + else if ("indent" in key && key.indent !== bm.indent) onError(offset, "BAD_INDENT", startColMsg); + } + if (!keyProps.anchor && !keyProps.tag && !sep) { + commentEnd = keyProps.end; + if (keyProps.comment) if (map.comment) map.comment += "\n" + keyProps.comment; + else map.comment = keyProps.comment; + continue; + } + if (keyProps.newlineAfterProp || containsNewline(key)) onError(key ?? start[start.length - 1], "MULTILINE_IMPLICIT_KEY", "Implicit keys need to be on a single line"); + } else if (keyProps.found?.indent !== bm.indent) onError(offset, "BAD_INDENT", startColMsg); + ctx.atKey = true; + const keyStart = keyProps.end; + const keyNode = key ? composeNode(ctx, key, keyProps, onError) : composeEmptyNode(ctx, keyStart, start, null, keyProps, onError); + if (ctx.schema.compat) flowIndentCheck(bm.indent, key, onError); + ctx.atKey = false; + if (mapIncludes(ctx, map.items, keyNode)) onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique"); + const valueProps = resolveProps(sep ?? [], { + indicator: "map-value-ind", + next: value, + offset: keyNode.range[2], + onError, + parentIndent: bm.indent, + startOnNewline: !key || key.type === "block-scalar" + }); + offset = valueProps.end; + if (valueProps.found) { + if (implicitKey) { + if (value?.type === "block-map" && !valueProps.hasNewline) onError(offset, "BLOCK_AS_IMPLICIT_KEY", "Nested mappings are not allowed in compact mappings"); + if (ctx.options.strict && keyProps.start < valueProps.found.offset - 1024) onError(keyNode.range, "KEY_OVER_1024_CHARS", "The : indicator must be at most 1024 chars after the start of an implicit block mapping key"); + } + const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep, null, valueProps, onError); + if (ctx.schema.compat) flowIndentCheck(bm.indent, value, onError); + offset = valueNode.range[2]; + const pair = new Pair(keyNode, valueNode); + if (ctx.options.keepSourceTokens) pair.srcToken = collItem; + map.items.push(pair); + } else { + if (implicitKey) onError(keyNode.range, "MISSING_CHAR", "Implicit map keys need to be followed by map values"); + if (valueProps.comment) if (keyNode.comment) keyNode.comment += "\n" + valueProps.comment; + else keyNode.comment = valueProps.comment; + const pair = new Pair(keyNode); + if (ctx.options.keepSourceTokens) pair.srcToken = collItem; + map.items.push(pair); + } + } + if (commentEnd && commentEnd < offset) onError(commentEnd, "IMPOSSIBLE", "Map comment with trailing content"); + map.range = [ + bm.offset, + offset, + commentEnd ?? offset + ]; + return map; +} +//#endregion +//#region node_modules/yaml/browser/dist/compose/resolve-block-seq.js +function resolveBlockSeq({ composeNode, composeEmptyNode }, ctx, bs, onError, tag) { + const seq = new ((tag?.nodeClass) ?? YAMLSeq)(ctx.schema); + if (ctx.atRoot) ctx.atRoot = false; + if (ctx.atKey) ctx.atKey = false; + let offset = bs.offset; + let commentEnd = null; + for (const { start, value } of bs.items) { + const props = resolveProps(start, { + indicator: "seq-item-ind", + next: value, + offset, + onError, + parentIndent: bs.indent, + startOnNewline: true + }); + if (!props.found) if (props.anchor || props.tag || value) if (value?.type === "block-seq") onError(props.end, "BAD_INDENT", "All sequence items must start at the same column"); + else onError(offset, "MISSING_CHAR", "Sequence item without - indicator"); + else { + commentEnd = props.end; + if (props.comment) seq.comment = props.comment; + continue; + } + const node = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, start, null, props, onError); + if (ctx.schema.compat) flowIndentCheck(bs.indent, value, onError); + offset = node.range[2]; + seq.items.push(node); + } + seq.range = [ + bs.offset, + offset, + commentEnd ?? offset + ]; + return seq; +} +//#endregion +//#region node_modules/yaml/browser/dist/compose/resolve-end.js +function resolveEnd(end, offset, reqSpace, onError) { + let comment = ""; + if (end) { + let hasSpace = false; + let sep = ""; + for (const token of end) { + const { source, type } = token; + switch (type) { + case "space": + hasSpace = true; + break; + case "comment": { + if (reqSpace && !hasSpace) onError(token, "MISSING_CHAR", "Comments must be separated from other tokens by white space characters"); + const cb = source.substring(1) || " "; + if (!comment) comment = cb; + else comment += sep + cb; + sep = ""; + break; + } + case "newline": + if (comment) sep += source; + hasSpace = true; + break; + default: onError(token, "UNEXPECTED_TOKEN", `Unexpected ${type} at node end`); + } + offset += source.length; + } + } + return { + comment, + offset + }; +} +//#endregion +//#region node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +var blockMsg = "Block collections are not allowed within flow collections"; +var isBlock = (token) => token && (token.type === "block-map" || token.type === "block-seq"); +function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onError, tag) { + const isMap = fc.start.source === "{"; + const fcName = isMap ? "flow map" : "flow sequence"; + const coll = new ((tag?.nodeClass) ?? (isMap ? YAMLMap : YAMLSeq))(ctx.schema); + coll.flow = true; + const atRoot = ctx.atRoot; + if (atRoot) ctx.atRoot = false; + if (ctx.atKey) ctx.atKey = false; + let offset = fc.offset + fc.start.source.length; + for (let i = 0; i < fc.items.length; ++i) { + const collItem = fc.items[i]; + const { start, key, sep, value } = collItem; + const props = resolveProps(start, { + flow: fcName, + indicator: "explicit-key-ind", + next: key ?? sep?.[0], + offset, + onError, + parentIndent: fc.indent, + startOnNewline: false + }); + if (!props.found) { + if (!props.anchor && !props.tag && !sep && !value) { + if (i === 0 && props.comma) onError(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`); + else if (i < fc.items.length - 1) onError(props.start, "UNEXPECTED_TOKEN", `Unexpected empty item in ${fcName}`); + if (props.comment) if (coll.comment) coll.comment += "\n" + props.comment; + else coll.comment = props.comment; + offset = props.end; + continue; + } + if (!isMap && ctx.options.strict && containsNewline(key)) onError(key, "MULTILINE_IMPLICIT_KEY", "Implicit keys of flow sequence pairs need to be on a single line"); + } + if (i === 0) { + if (props.comma) onError(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`); + } else { + if (!props.comma) onError(props.start, "MISSING_CHAR", `Missing , between ${fcName} items`); + if (props.comment) { + let prevItemComment = ""; + loop: for (const st of start) switch (st.type) { + case "comma": + case "space": break; + case "comment": + prevItemComment = st.source.substring(1); + break loop; + default: break loop; + } + if (prevItemComment) { + let prev = coll.items[coll.items.length - 1]; + if (isPair(prev)) prev = prev.value ?? prev.key; + if (prev.comment) prev.comment += "\n" + prevItemComment; + else prev.comment = prevItemComment; + props.comment = props.comment.substring(prevItemComment.length + 1); + } + } + } + if (!isMap && !sep && !props.found) { + const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep, null, props, onError); + coll.items.push(valueNode); + offset = valueNode.range[2]; + if (isBlock(value)) onError(valueNode.range, "BLOCK_IN_FLOW", blockMsg); + } else { + ctx.atKey = true; + const keyStart = props.end; + const keyNode = key ? composeNode(ctx, key, props, onError) : composeEmptyNode(ctx, keyStart, start, null, props, onError); + if (isBlock(key)) onError(keyNode.range, "BLOCK_IN_FLOW", blockMsg); + ctx.atKey = false; + const valueProps = resolveProps(sep ?? [], { + flow: fcName, + indicator: "map-value-ind", + next: value, + offset: keyNode.range[2], + onError, + parentIndent: fc.indent, + startOnNewline: false + }); + if (valueProps.found) { + if (!isMap && !props.found && ctx.options.strict) { + if (sep) for (const st of sep) { + if (st === valueProps.found) break; + if (st.type === "newline") { + onError(st, "MULTILINE_IMPLICIT_KEY", "Implicit keys of flow sequence pairs need to be on a single line"); + break; + } + } + if (props.start < valueProps.found.offset - 1024) onError(valueProps.found, "KEY_OVER_1024_CHARS", "The : indicator must be at most 1024 chars after the start of an implicit flow sequence key"); + } + } else if (value) if ("source" in value && value.source?.[0] === ":") onError(value, "MISSING_CHAR", `Missing space after : in ${fcName}`); + else onError(valueProps.start, "MISSING_CHAR", `Missing , or : between ${fcName} items`); + const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep, null, valueProps, onError) : null; + if (valueNode) { + if (isBlock(value)) onError(valueNode.range, "BLOCK_IN_FLOW", blockMsg); + } else if (valueProps.comment) if (keyNode.comment) keyNode.comment += "\n" + valueProps.comment; + else keyNode.comment = valueProps.comment; + const pair = new Pair(keyNode, valueNode); + if (ctx.options.keepSourceTokens) pair.srcToken = collItem; + if (isMap) { + const map = coll; + if (mapIncludes(ctx, map.items, keyNode)) onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique"); + map.items.push(pair); + } else { + const map = new YAMLMap(ctx.schema); + map.flow = true; + map.items.push(pair); + const endRange = (valueNode ?? keyNode).range; + map.range = [ + keyNode.range[0], + endRange[1], + endRange[2] + ]; + coll.items.push(map); + } + offset = valueNode ? valueNode.range[2] : valueProps.end; + } + } + const expectedEnd = isMap ? "}" : "]"; + const [ce, ...ee] = fc.end; + let cePos = offset; + if (ce?.source === expectedEnd) cePos = ce.offset + ce.source.length; + else { + const name = fcName[0].toUpperCase() + fcName.substring(1); + const msg = atRoot ? `${name} must end with a ${expectedEnd}` : `${name} in block collection must be sufficiently indented and end with a ${expectedEnd}`; + onError(offset, atRoot ? "MISSING_CHAR" : "BAD_INDENT", msg); + if (ce && ce.source.length !== 1) ee.unshift(ce); + } + if (ee.length > 0) { + const end = resolveEnd(ee, cePos, ctx.options.strict, onError); + if (end.comment) if (coll.comment) coll.comment += "\n" + end.comment; + else coll.comment = end.comment; + coll.range = [ + fc.offset, + cePos, + end.offset + ]; + } else coll.range = [ + fc.offset, + cePos, + cePos + ]; + return coll; +} +//#endregion +//#region node_modules/yaml/browser/dist/compose/compose-collection.js +function resolveCollection(CN, ctx, token, onError, tagName, tag) { + const coll = token.type === "block-map" ? resolveBlockMap(CN, ctx, token, onError, tag) : token.type === "block-seq" ? resolveBlockSeq(CN, ctx, token, onError, tag) : resolveFlowCollection(CN, ctx, token, onError, tag); + const Coll = coll.constructor; + if (tagName === "!" || tagName === Coll.tagName) { + coll.tag = Coll.tagName; + return coll; + } + if (tagName) coll.tag = tagName; + return coll; +} +function composeCollection(CN, ctx, token, props, onError) { + const tagToken = props.tag; + const tagName = !tagToken ? null : ctx.directives.tagName(tagToken.source, (msg) => onError(tagToken, "TAG_RESOLVE_FAILED", msg)); + if (token.type === "block-seq") { + const { anchor, newlineAfterProp: nl } = props; + const lastProp = anchor && tagToken ? anchor.offset > tagToken.offset ? anchor : tagToken : anchor ?? tagToken; + if (lastProp && (!nl || nl.offset < lastProp.offset)) onError(lastProp, "MISSING_CHAR", "Missing newline after block sequence props"); + } + const expType = token.type === "block-map" ? "map" : token.type === "block-seq" ? "seq" : token.start.source === "{" ? "map" : "seq"; + if (!tagToken || !tagName || tagName === "!" || tagName === YAMLMap.tagName && expType === "map" || tagName === YAMLSeq.tagName && expType === "seq") return resolveCollection(CN, ctx, token, onError, tagName); + let tag = ctx.schema.tags.find((t) => t.tag === tagName && t.collection === expType); + if (!tag) { + const kt = ctx.schema.knownTags[tagName]; + if (kt?.collection === expType) { + ctx.schema.tags.push(Object.assign({}, kt, { default: false })); + tag = kt; + } else { + if (kt) onError(tagToken, "BAD_COLLECTION_TYPE", `${kt.tag} used for ${expType} collection, but expects ${kt.collection ?? "scalar"}`, true); + else onError(tagToken, "TAG_RESOLVE_FAILED", `Unresolved tag: ${tagName}`, true); + return resolveCollection(CN, ctx, token, onError, tagName); + } + } + const coll = resolveCollection(CN, ctx, token, onError, tagName, tag); + const res = tag.resolve?.(coll, (msg) => onError(tagToken, "TAG_RESOLVE_FAILED", msg), ctx.options) ?? coll; + const node = isNode(res) ? res : new Scalar(res); + node.range = coll.range; + node.tag = tagName; + if (tag?.format) node.format = tag.format; + return node; +} +//#endregion +//#region node_modules/yaml/browser/dist/compose/resolve-block-scalar.js +function resolveBlockScalar(ctx, scalar, onError) { + const start = scalar.offset; + const header = parseBlockScalarHeader(scalar, ctx.options.strict, onError); + if (!header) return { + value: "", + type: null, + comment: "", + range: [ + start, + start, + start + ] + }; + const type = header.mode === ">" ? Scalar.BLOCK_FOLDED : Scalar.BLOCK_LITERAL; + const lines = scalar.source ? splitLines(scalar.source) : []; + let chompStart = lines.length; + for (let i = lines.length - 1; i >= 0; --i) { + const content = lines[i][1]; + if (content === "" || content === "\r") chompStart = i; + else break; + } + if (chompStart === 0) { + const value = header.chomp === "+" && lines.length > 0 ? "\n".repeat(Math.max(1, lines.length - 1)) : ""; + let end = start + header.length; + if (scalar.source) end += scalar.source.length; + return { + value, + type, + comment: header.comment, + range: [ + start, + end, + end + ] + }; + } + let trimIndent = scalar.indent + header.indent; + let offset = scalar.offset + header.length; + let contentStart = 0; + for (let i = 0; i < chompStart; ++i) { + const [indent, content] = lines[i]; + if (content === "" || content === "\r") { + if (header.indent === 0 && indent.length > trimIndent) trimIndent = indent.length; + } else { + if (indent.length < trimIndent) onError(offset + indent.length, "MISSING_CHAR", "Block scalars with more-indented leading empty lines must use an explicit indentation indicator"); + if (header.indent === 0) trimIndent = indent.length; + contentStart = i; + if (trimIndent === 0 && !ctx.atRoot) onError(offset, "BAD_INDENT", "Block scalar values in collections must be indented"); + break; + } + offset += indent.length + content.length + 1; + } + for (let i = lines.length - 1; i >= chompStart; --i) if (lines[i][0].length > trimIndent) chompStart = i + 1; + let value = ""; + let sep = ""; + let prevMoreIndented = false; + for (let i = 0; i < contentStart; ++i) value += lines[i][0].slice(trimIndent) + "\n"; + for (let i = contentStart; i < chompStart; ++i) { + let [indent, content] = lines[i]; + offset += indent.length + content.length + 1; + const crlf = content[content.length - 1] === "\r"; + if (crlf) content = content.slice(0, -1); + /* istanbul ignore if already caught in lexer */ + if (content && indent.length < trimIndent) { + const message = `Block scalar lines must not be less indented than their ${header.indent ? "explicit indentation indicator" : "first line"}`; + onError(offset - content.length - (crlf ? 2 : 1), "BAD_INDENT", message); + indent = ""; + } + if (type === Scalar.BLOCK_LITERAL) { + value += sep + indent.slice(trimIndent) + content; + sep = "\n"; + } else if (indent.length > trimIndent || content[0] === " ") { + if (sep === " ") sep = "\n"; + else if (!prevMoreIndented && sep === "\n") sep = "\n\n"; + value += sep + indent.slice(trimIndent) + content; + sep = "\n"; + prevMoreIndented = true; + } else if (content === "") if (sep === "\n") value += "\n"; + else sep = "\n"; + else { + value += sep + content; + sep = " "; + prevMoreIndented = false; + } + } + switch (header.chomp) { + case "-": break; + case "+": + for (let i = chompStart; i < lines.length; ++i) value += "\n" + lines[i][0].slice(trimIndent); + if (value[value.length - 1] !== "\n") value += "\n"; + break; + default: value += "\n"; + } + const end = start + header.length + scalar.source.length; + return { + value, + type, + comment: header.comment, + range: [ + start, + end, + end + ] + }; +} +function parseBlockScalarHeader({ offset, props }, strict, onError) { + /* istanbul ignore if should not happen */ + if (props[0].type !== "block-scalar-header") { + onError(props[0], "IMPOSSIBLE", "Block scalar header not found"); + return null; + } + const { source } = props[0]; + const mode = source[0]; + let indent = 0; + let chomp = ""; + let error = -1; + for (let i = 1; i < source.length; ++i) { + const ch = source[i]; + if (!chomp && (ch === "-" || ch === "+")) chomp = ch; + else { + const n = Number(ch); + if (!indent && n) indent = n; + else if (error === -1) error = offset + i; + } + } + if (error !== -1) onError(error, "UNEXPECTED_TOKEN", `Block scalar header includes extra characters: ${source}`); + let hasSpace = false; + let comment = ""; + let length = source.length; + for (let i = 1; i < props.length; ++i) { + const token = props[i]; + switch (token.type) { + case "space": hasSpace = true; + case "newline": + length += token.source.length; + break; + case "comment": + if (strict && !hasSpace) onError(token, "MISSING_CHAR", "Comments must be separated from other tokens by white space characters"); + length += token.source.length; + comment = token.source.substring(1); + break; + case "error": + onError(token, "UNEXPECTED_TOKEN", token.message); + length += token.source.length; + break; + /* istanbul ignore next should not happen */ + default: { + onError(token, "UNEXPECTED_TOKEN", `Unexpected token in block scalar header: ${token.type}`); + const ts = token.source; + if (ts && typeof ts === "string") length += ts.length; + } + } + } + return { + mode, + indent, + chomp, + comment, + length + }; +} +/** @returns Array of lines split up as `[indent, content]` */ +function splitLines(source) { + const split = source.split(/\n( *)/); + const first = split[0]; + const m = first.match(/^( *)/); + const lines = [m?.[1] ? [m[1], first.slice(m[1].length)] : ["", first]]; + for (let i = 1; i < split.length; i += 2) lines.push([split[i], split[i + 1]]); + return lines; +} +//#endregion +//#region node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +function resolveFlowScalar(scalar, strict, onError) { + const { offset, type, source, end } = scalar; + let _type; + let value; + const _onError = (rel, code, msg) => onError(offset + rel, code, msg); + switch (type) { + case "scalar": + _type = Scalar.PLAIN; + value = plainValue(source, _onError); + break; + case "single-quoted-scalar": + _type = Scalar.QUOTE_SINGLE; + value = singleQuotedValue(source, _onError); + break; + case "double-quoted-scalar": + _type = Scalar.QUOTE_DOUBLE; + value = doubleQuotedValue(source, _onError); + break; + /* istanbul ignore next should not happen */ + default: + onError(scalar, "UNEXPECTED_TOKEN", `Expected a flow scalar value, but found: ${type}`); + return { + value: "", + type: null, + comment: "", + range: [ + offset, + offset + source.length, + offset + source.length + ] + }; + } + const valueEnd = offset + source.length; + const re = resolveEnd(end, valueEnd, strict, onError); + return { + value, + type: _type, + comment: re.comment, + range: [ + offset, + valueEnd, + re.offset + ] + }; +} +function plainValue(source, onError) { + let badChar = ""; + switch (source[0]) { + /* istanbul ignore next should not happen */ + case " ": + badChar = "a tab character"; + break; + case ",": + badChar = "flow indicator character ,"; + break; + case "%": + badChar = "directive indicator character %"; + break; + case "|": + case ">": + badChar = `block scalar indicator ${source[0]}`; + break; + case "@": + case "`": + badChar = `reserved character ${source[0]}`; + break; + } + if (badChar) onError(0, "BAD_SCALAR_START", `Plain value cannot start with ${badChar}`); + return foldLines(source); +} +function singleQuotedValue(source, onError) { + if (source[source.length - 1] !== "'" || source.length === 1) onError(source.length, "MISSING_CHAR", "Missing closing 'quote"); + return foldLines(source.slice(1, -1)).replace(/''/g, "'"); +} +function foldLines(source) { + /** + * The negative lookbehind here and in the `re` RegExp is to + * prevent causing a polynomial search time in certain cases. + * + * The try-catch is for Safari, which doesn't support this yet: + * https://caniuse.com/js-regexp-lookbehind + */ + let first, line; + try { + first = /* @__PURE__ */ new RegExp("(.*?)(? wsStart ? source.slice(wsStart, i + 1) : ch; + } else res += ch; + } + if (source[source.length - 1] !== "\"" || source.length === 1) onError(source.length, "MISSING_CHAR", "Missing closing \"quote"); + return res; +} +/** +* Fold a single newline into a space, multiple newlines to N - 1 newlines. +* Presumes `source[offset] === '\n'` +*/ +function foldNewline(source, offset) { + let fold = ""; + let ch = source[offset + 1]; + while (ch === " " || ch === " " || ch === "\n" || ch === "\r") { + if (ch === "\r" && source[offset + 2] !== "\n") break; + if (ch === "\n") fold += "\n"; + offset += 1; + ch = source[offset + 1]; + } + if (!fold) fold = " "; + return { + fold, + offset + }; +} +var escapeCodes = { + "0": "\0", + a: "\x07", + b: "\b", + e: "\x1B", + f: "\f", + n: "\n", + r: "\r", + t: " ", + v: "\v", + N: "…", + _: "\xA0", + L: "\u2028", + P: "\u2029", + " ": " ", + "\"": "\"", + "/": "/", + "\\": "\\", + " ": " " +}; +function parseCharCode(source, offset, length, onError) { + const cc = source.substr(offset, length); + const code = cc.length === length && /^[0-9a-fA-F]+$/.test(cc) ? parseInt(cc, 16) : NaN; + try { + return String.fromCodePoint(code); + } catch { + const raw = source.substr(offset - 2, length + 2); + onError(offset - 2, "BAD_DQ_ESCAPE", `Invalid escape sequence ${raw}`); + return raw; + } +} +//#endregion +//#region node_modules/yaml/browser/dist/compose/compose-scalar.js +function composeScalar(ctx, token, tagToken, onError) { + const { value, type, comment, range } = token.type === "block-scalar" ? resolveBlockScalar(ctx, token, onError) : resolveFlowScalar(token, ctx.options.strict, onError); + const tagName = tagToken ? ctx.directives.tagName(tagToken.source, (msg) => onError(tagToken, "TAG_RESOLVE_FAILED", msg)) : null; + let tag; + if (ctx.options.stringKeys && ctx.atKey) tag = ctx.schema[SCALAR$1]; + else if (tagName) tag = findScalarTagByName(ctx.schema, value, tagName, tagToken, onError); + else if (token.type === "scalar") tag = findScalarTagByTest(ctx, value, token, onError); + else tag = ctx.schema[SCALAR$1]; + let scalar; + try { + const res = tag.resolve(value, (msg) => onError(tagToken ?? token, "TAG_RESOLVE_FAILED", msg), ctx.options); + scalar = isScalar(res) ? res : new Scalar(res); + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + onError(tagToken ?? token, "TAG_RESOLVE_FAILED", msg); + scalar = new Scalar(value); + } + scalar.range = range; + scalar.source = value; + if (type) scalar.type = type; + if (tagName) scalar.tag = tagName; + if (tag.format) scalar.format = tag.format; + if (comment) scalar.comment = comment; + return scalar; +} +function findScalarTagByName(schema, value, tagName, tagToken, onError) { + if (tagName === "!") return schema[SCALAR$1]; + const matchWithTest = []; + for (const tag of schema.tags) if (!tag.collection && tag.tag === tagName) if (tag.default && tag.test) matchWithTest.push(tag); + else return tag; + for (const tag of matchWithTest) if (tag.test?.test(value)) return tag; + const kt = schema.knownTags[tagName]; + if (kt && !kt.collection) { + schema.tags.push(Object.assign({}, kt, { + default: false, + test: void 0 + })); + return kt; + } + onError(tagToken, "TAG_RESOLVE_FAILED", `Unresolved tag: ${tagName}`, tagName !== "tag:yaml.org,2002:str"); + return schema[SCALAR$1]; +} +function findScalarTagByTest({ atKey, directives, schema }, value, token, onError) { + const tag = schema.tags.find((tag) => (tag.default === true || atKey && tag.default === "key") && tag.test?.test(value)) || schema[SCALAR$1]; + if (schema.compat) { + const compat = schema.compat.find((tag) => tag.default && tag.test?.test(value)) ?? schema[SCALAR$1]; + if (tag.tag !== compat.tag) onError(token, "TAG_RESOLVE_FAILED", `Value may be parsed as either ${directives.tagString(tag.tag)} or ${directives.tagString(compat.tag)}`, true); + } + return tag; +} +//#endregion +//#region node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +function emptyScalarPosition(offset, before, pos) { + if (before) { + pos ?? (pos = before.length); + for (let i = pos - 1; i >= 0; --i) { + let st = before[i]; + switch (st.type) { + case "space": + case "comment": + case "newline": + offset -= st.source.length; + continue; + } + st = before[++i]; + while (st?.type === "space") { + offset += st.source.length; + st = before[++i]; + } + break; + } + } + return offset; +} +//#endregion +//#region node_modules/yaml/browser/dist/compose/compose-node.js +var CN = { + composeNode, + composeEmptyNode +}; +function composeNode(ctx, token, props, onError) { + const atKey = ctx.atKey; + const { spaceBefore, comment, anchor, tag } = props; + let node; + let isSrcToken = true; + switch (token.type) { + case "alias": + node = composeAlias(ctx, token, onError); + if (anchor || tag) onError(token, "ALIAS_PROPS", "An alias node must not specify any properties"); + break; + case "scalar": + case "single-quoted-scalar": + case "double-quoted-scalar": + case "block-scalar": + node = composeScalar(ctx, token, tag, onError); + if (anchor) node.anchor = anchor.source.substring(1); + break; + case "block-map": + case "block-seq": + case "flow-collection": + try { + node = composeCollection(CN, ctx, token, props, onError); + if (anchor) node.anchor = anchor.source.substring(1); + } catch (error) { + onError(token, "RESOURCE_EXHAUSTION", error instanceof Error ? error.message : String(error)); + } + break; + default: + onError(token, "UNEXPECTED_TOKEN", token.type === "error" ? token.message : `Unsupported token (type: ${token.type})`); + isSrcToken = false; + } + node ?? (node = composeEmptyNode(ctx, token.offset, void 0, null, props, onError)); + if (anchor && node.anchor === "") onError(anchor, "BAD_ALIAS", "Anchor cannot be an empty string"); + if (atKey && ctx.options.stringKeys && (!isScalar(node) || typeof node.value !== "string" || node.tag && node.tag !== "tag:yaml.org,2002:str")) onError(tag ?? token, "NON_STRING_KEY", "With stringKeys, all keys must be strings"); + if (spaceBefore) node.spaceBefore = true; + if (comment) if (token.type === "scalar" && token.source === "") node.comment = comment; + else node.commentBefore = comment; + if (ctx.options.keepSourceTokens && isSrcToken) node.srcToken = token; + return node; +} +function composeEmptyNode(ctx, offset, before, pos, { spaceBefore, comment, anchor, tag, end }, onError) { + const node = composeScalar(ctx, { + type: "scalar", + offset: emptyScalarPosition(offset, before, pos), + indent: -1, + source: "" + }, tag, onError); + if (anchor) { + node.anchor = anchor.source.substring(1); + if (node.anchor === "") onError(anchor, "BAD_ALIAS", "Anchor cannot be an empty string"); + } + if (spaceBefore) node.spaceBefore = true; + if (comment) { + node.comment = comment; + node.range[2] = end; + } + return node; +} +function composeAlias({ options }, { offset, source, end }, onError) { + const alias = new Alias(source.substring(1)); + if (alias.source === "") onError(offset, "BAD_ALIAS", "Alias cannot be an empty string"); + if (alias.source.endsWith(":")) onError(offset + source.length - 1, "BAD_ALIAS", "Alias ending in : is ambiguous", true); + const valueEnd = offset + source.length; + const re = resolveEnd(end, valueEnd, options.strict, onError); + alias.range = [ + offset, + valueEnd, + re.offset + ]; + if (re.comment) alias.comment = re.comment; + return alias; +} +//#endregion +//#region node_modules/yaml/browser/dist/compose/compose-doc.js +function composeDoc(options, directives, { offset, start, value, end }, onError) { + const doc = new Document(void 0, Object.assign({ _directives: directives }, options)); + const ctx = { + atKey: false, + atRoot: true, + directives: doc.directives, + options: doc.options, + schema: doc.schema + }; + const props = resolveProps(start, { + indicator: "doc-start", + next: value ?? end?.[0], + offset, + onError, + parentIndent: 0, + startOnNewline: true + }); + if (props.found) { + doc.directives.docStart = true; + if (value && (value.type === "block-map" || value.type === "block-seq") && !props.hasNewline) onError(props.end, "MISSING_CHAR", "Block collection cannot start on same line with directives-end marker"); + } + doc.contents = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, start, null, props, onError); + const contentEnd = doc.contents.range[2]; + const re = resolveEnd(end, contentEnd, false, onError); + if (re.comment) doc.comment = re.comment; + doc.range = [ + offset, + contentEnd, + re.offset + ]; + return doc; +} +//#endregion +//#region node_modules/yaml/browser/dist/compose/composer.js +function getErrorPos(src) { + if (typeof src === "number") return [src, src + 1]; + if (Array.isArray(src)) return src.length === 2 ? src : [src[0], src[1]]; + const { offset, source } = src; + return [offset, offset + (typeof source === "string" ? source.length : 1)]; +} +function parsePrelude(prelude) { + let comment = ""; + let atComment = false; + let afterEmptyLine = false; + for (let i = 0; i < prelude.length; ++i) { + const source = prelude[i]; + switch (source[0]) { + case "#": + comment += (comment === "" ? "" : afterEmptyLine ? "\n\n" : "\n") + (source.substring(1) || " "); + atComment = true; + afterEmptyLine = false; + break; + case "%": + if (prelude[i + 1]?.[0] !== "#") i += 1; + atComment = false; + break; + default: + if (!atComment) afterEmptyLine = true; + atComment = false; + } + } + return { + comment, + afterEmptyLine + }; +} +/** +* Compose a stream of CST nodes into a stream of YAML Documents. +* +* ```ts +* import { Composer, Parser } from 'yaml' +* +* const src: string = ... +* const tokens = new Parser().parse(src) +* const docs = new Composer().compose(tokens) +* ``` +*/ +var Composer = class { + constructor(options = {}) { + this.doc = null; + this.atDirectives = false; + this.prelude = []; + this.errors = []; + this.warnings = []; + this.onError = (source, code, message, warning) => { + const pos = getErrorPos(source); + if (warning) this.warnings.push(new YAMLWarning(pos, code, message)); + else this.errors.push(new YAMLParseError(pos, code, message)); + }; + this.directives = new Directives({ version: options.version || "1.2" }); + this.options = options; + } + decorate(doc, afterDoc) { + const { comment, afterEmptyLine } = parsePrelude(this.prelude); + if (comment) { + const dc = doc.contents; + if (afterDoc) doc.comment = doc.comment ? `${doc.comment}\n${comment}` : comment; + else if (afterEmptyLine || doc.directives.docStart || !dc) doc.commentBefore = comment; + else if (isCollection(dc) && !dc.flow && dc.items.length > 0) { + let it = dc.items[0]; + if (isPair(it)) it = it.key; + const cb = it.commentBefore; + it.commentBefore = cb ? `${comment}\n${cb}` : comment; + } else { + const cb = dc.commentBefore; + dc.commentBefore = cb ? `${comment}\n${cb}` : comment; + } + } + if (afterDoc) { + for (let i = 0; i < this.errors.length; ++i) doc.errors.push(this.errors[i]); + for (let i = 0; i < this.warnings.length; ++i) doc.warnings.push(this.warnings[i]); + } else { + doc.errors = this.errors; + doc.warnings = this.warnings; + } + this.prelude = []; + this.errors = []; + this.warnings = []; + } + /** + * Current stream status information. + * + * Mostly useful at the end of input for an empty stream. + */ + streamInfo() { + return { + comment: parsePrelude(this.prelude).comment, + directives: this.directives, + errors: this.errors, + warnings: this.warnings + }; + } + /** + * Compose tokens into documents. + * + * @param forceDoc - If the stream contains no document, still emit a final document including any comments and directives that would be applied to a subsequent document. + * @param endOffset - Should be set if `forceDoc` is also set, to set the document range end and to indicate errors correctly. + */ + *compose(tokens, forceDoc = false, endOffset = -1) { + for (const token of tokens) yield* this.next(token); + yield* this.end(forceDoc, endOffset); + } + /** Advance the composer by one CST token. */ + *next(token) { + switch (token.type) { + case "directive": + this.directives.add(token.source, (offset, message, warning) => { + const pos = getErrorPos(token); + pos[0] += offset; + this.onError(pos, "BAD_DIRECTIVE", message, warning); + }); + this.prelude.push(token.source); + this.atDirectives = true; + break; + case "document": { + const doc = composeDoc(this.options, this.directives, token, this.onError); + if (this.atDirectives && !doc.directives.docStart) this.onError(token, "MISSING_CHAR", "Missing directives-end/doc-start indicator line"); + this.decorate(doc, false); + if (this.doc) yield this.doc; + this.doc = doc; + this.atDirectives = false; + break; + } + case "byte-order-mark": + case "space": break; + case "comment": + case "newline": + this.prelude.push(token.source); + break; + case "error": { + const msg = token.source ? `${token.message}: ${JSON.stringify(token.source)}` : token.message; + const error = new YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg); + if (this.atDirectives || !this.doc) this.errors.push(error); + else this.doc.errors.push(error); + break; + } + case "doc-end": { + if (!this.doc) { + this.errors.push(new YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", "Unexpected doc-end without preceding document")); + break; + } + this.doc.directives.docEnd = true; + const end = resolveEnd(token.end, token.offset + token.source.length, this.doc.options.strict, this.onError); + this.decorate(this.doc, true); + if (end.comment) { + const dc = this.doc.comment; + this.doc.comment = dc ? `${dc}\n${end.comment}` : end.comment; + } + this.doc.range[2] = end.offset; + break; + } + default: this.errors.push(new YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", `Unsupported token ${token.type}`)); + } + } + /** + * Call at end of input to yield any remaining document. + * + * @param forceDoc - If the stream contains no document, still emit a final document including any comments and directives that would be applied to a subsequent document. + * @param endOffset - Should be set if `forceDoc` is also set, to set the document range end and to indicate errors correctly. + */ + *end(forceDoc = false, endOffset = -1) { + if (this.doc) { + this.decorate(this.doc, true); + yield this.doc; + this.doc = null; + } else if (forceDoc) { + const doc = new Document(void 0, Object.assign({ _directives: this.directives }, this.options)); + if (this.atDirectives) this.onError(endOffset, "MISSING_CHAR", "Missing directives-end indicator line"); + doc.range = [ + 0, + endOffset, + endOffset + ]; + this.decorate(doc, false); + yield doc; + } + } +}; +//#endregion +//#region node_modules/yaml/browser/dist/parse/cst-visit.js +var BREAK = Symbol("break visit"); +var SKIP = Symbol("skip children"); +var REMOVE = Symbol("remove item"); +/** +* Apply a visitor to a CST document or item. +* +* Walks through the tree (depth-first) starting from the root, calling a +* `visitor` function with two arguments when entering each item: +* - `item`: The current item, which included the following members: +* - `start: SourceToken[]` – Source tokens before the key or value, +* possibly including its anchor or tag. +* - `key?: Token | null` – Set for pair values. May then be `null`, if +* the key before the `:` separator is empty. +* - `sep?: SourceToken[]` – Source tokens between the key and the value, +* which should include the `:` map value indicator if `value` is set. +* - `value?: Token` – The value of a sequence item, or of a map pair. +* - `path`: The steps from the root to the current node, as an array of +* `['key' | 'value', number]` tuples. +* +* The return value of the visitor may be used to control the traversal: +* - `undefined` (default): Do nothing and continue +* - `visit.SKIP`: Do not visit the children of this token, continue with +* next sibling +* - `visit.BREAK`: Terminate traversal completely +* - `visit.REMOVE`: Remove the current item, then continue with the next one +* - `number`: Set the index of the next step. This is useful especially if +* the index of the current token has changed. +* - `function`: Define the next visitor for this item. After the original +* visitor is called on item entry, next visitors are called after handling +* a non-empty `key` and when exiting the item. +*/ +function visit(cst, visitor) { + if ("type" in cst && cst.type === "document") cst = { + start: cst.start, + value: cst.value + }; + _visit(Object.freeze([]), cst, visitor); +} +/** Terminate visit traversal completely */ +visit.BREAK = BREAK; +/** Do not visit the children of the current item */ +visit.SKIP = SKIP; +/** Remove the current item */ +visit.REMOVE = REMOVE; +/** Find the item at `path` from `cst` as the root */ +visit.itemAtPath = (cst, path) => { + let item = cst; + for (const [field, index] of path) { + const tok = item?.[field]; + if (tok && "items" in tok) item = tok.items[index]; + else return void 0; + } + return item; +}; +/** +* Get the immediate parent collection of the item at `path` from `cst` as the root. +* +* Throws an error if the collection is not found, which should never happen if the item itself exists. +*/ +visit.parentCollection = (cst, path) => { + const parent = visit.itemAtPath(cst, path.slice(0, -1)); + const field = path[path.length - 1][0]; + const coll = parent?.[field]; + if (coll && "items" in coll) return coll; + throw new Error("Parent collection not found"); +}; +function _visit(path, item, visitor) { + let ctrl = visitor(item, path); + if (typeof ctrl === "symbol") return ctrl; + for (const field of ["key", "value"]) { + const token = item[field]; + if (token && "items" in token) { + for (let i = 0; i < token.items.length; ++i) { + const ci = _visit(Object.freeze(path.concat([[field, i]])), token.items[i], visitor); + if (typeof ci === "number") i = ci - 1; + else if (ci === BREAK) return BREAK; + else if (ci === REMOVE) { + token.items.splice(i, 1); + i -= 1; + } + } + if (typeof ctrl === "function" && field === "key") ctrl = ctrl(item, path); + } + } + return typeof ctrl === "function" ? ctrl(item, path) : ctrl; +} +/** Identify the type of a lexer token. May return `null` for unknown tokens. */ +function tokenType(source) { + switch (source) { + case "": return "byte-order-mark"; + case "": return "doc-mode"; + case "": return "flow-error-end"; + case "": return "scalar"; + case "---": return "doc-start"; + case "...": return "doc-end"; + case "": + case "\n": + case "\r\n": return "newline"; + case "-": return "seq-item-ind"; + case "?": return "explicit-key-ind"; + case ":": return "map-value-ind"; + case "{": return "flow-map-start"; + case "}": return "flow-map-end"; + case "[": return "flow-seq-start"; + case "]": return "flow-seq-end"; + case ",": return "comma"; + } + switch (source[0]) { + case " ": + case " ": return "space"; + case "#": return "comment"; + case "%": return "directive-line"; + case "*": return "alias"; + case "&": return "anchor"; + case "!": return "tag"; + case "'": return "single-quoted-scalar"; + case "\"": return "double-quoted-scalar"; + case "|": + case ">": return "block-scalar-header"; + } + return null; +} +//#endregion +//#region node_modules/yaml/browser/dist/parse/lexer.js +function isEmpty(ch) { + switch (ch) { + case void 0: + case " ": + case "\n": + case "\r": + case " ": return true; + default: return false; + } +} +var hexDigits = /* @__PURE__ */ new Set("0123456789ABCDEFabcdef"); +var tagChars = /* @__PURE__ */ new Set("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-#;/?:@&=+$_.!~*'()"); +var flowIndicatorChars = /* @__PURE__ */ new Set(",[]{}"); +var invalidAnchorChars = /* @__PURE__ */ new Set(" ,[]{}\n\r "); +var isNotAnchorChar = (ch) => !ch || invalidAnchorChars.has(ch); +/** +* Splits an input string into lexical tokens, i.e. smaller strings that are +* easily identifiable by `tokens.tokenType()`. +* +* Lexing starts always in a "stream" context. Incomplete input may be buffered +* until a complete token can be emitted. +* +* In addition to slices of the original input, the following control characters +* may also be emitted: +* +* - `\x02` (Start of Text): A document starts with the next token +* - `\x18` (Cancel): Unexpected end of flow-mode (indicates an error) +* - `\x1f` (Unit Separator): Next token is a scalar value +* - `\u{FEFF}` (Byte order mark): Emitted separately outside documents +*/ +var Lexer = class { + constructor() { + /** + * Flag indicating whether the end of the current buffer marks the end of + * all input + */ + this.atEnd = false; + /** + * Explicit indent set in block scalar header, as an offset from the current + * minimum indent, so e.g. set to 1 from a header `|2+`. Set to -1 if not + * explicitly set. + */ + this.blockScalarIndent = -1; + /** + * Block scalars that include a + (keep) chomping indicator in their header + * include trailing empty lines, which are otherwise excluded from the + * scalar's contents. + */ + this.blockScalarKeep = false; + /** Current input */ + this.buffer = ""; + /** + * Flag noting whether the map value indicator : can immediately follow this + * node within a flow context. + */ + this.flowKey = false; + /** Count of surrounding flow collection levels. */ + this.flowLevel = 0; + /** + * Minimum level of indentation required for next lines to be parsed as a + * part of the current scalar value. + */ + this.indentNext = 0; + /** Indentation level of the current line. */ + this.indentValue = 0; + /** Position of the next \n character. */ + this.lineEndPos = null; + /** Stores the state of the lexer if reaching the end of incpomplete input */ + this.next = null; + /** A pointer to `buffer`; the current position of the lexer. */ + this.pos = 0; + } + /** + * Generate YAML tokens from the `source` string. If `incomplete`, + * a part of the last line may be left as a buffer for the next call. + * + * @returns A generator of lexical tokens + */ + *lex(source, incomplete = false) { + if (source) { + if (typeof source !== "string") throw TypeError("source is not a string"); + this.buffer = this.buffer ? this.buffer + source : source; + this.lineEndPos = null; + } + this.atEnd = !incomplete; + let next = this.next ?? "stream"; + while (next && (incomplete || this.hasChars(1))) next = yield* this.parseNext(next); + } + atLineEnd() { + let i = this.pos; + let ch = this.buffer[i]; + while (ch === " " || ch === " ") ch = this.buffer[++i]; + if (!ch || ch === "#" || ch === "\n") return true; + if (ch === "\r") return this.buffer[i + 1] === "\n"; + return false; + } + charAt(n) { + return this.buffer[this.pos + n]; + } + continueScalar(offset) { + let ch = this.buffer[offset]; + if (this.indentNext > 0) { + let indent = 0; + while (ch === " ") ch = this.buffer[++indent + offset]; + if (ch === "\r") { + const next = this.buffer[indent + offset + 1]; + if (next === "\n" || !next && !this.atEnd) return offset + indent + 1; + } + return ch === "\n" || indent >= this.indentNext || !ch && !this.atEnd ? offset + indent : -1; + } + if (ch === "-" || ch === ".") { + const dt = this.buffer.substr(offset, 3); + if ((dt === "---" || dt === "...") && isEmpty(this.buffer[offset + 3])) return -1; + } + return offset; + } + getLine() { + let end = this.lineEndPos; + if (typeof end !== "number" || end !== -1 && end < this.pos) { + end = this.buffer.indexOf("\n", this.pos); + this.lineEndPos = end; + } + if (end === -1) return this.atEnd ? this.buffer.substring(this.pos) : null; + if (this.buffer[end - 1] === "\r") end -= 1; + return this.buffer.substring(this.pos, end); + } + hasChars(n) { + return this.pos + n <= this.buffer.length; + } + setNext(state) { + this.buffer = this.buffer.substring(this.pos); + this.pos = 0; + this.lineEndPos = null; + this.next = state; + return null; + } + peek(n) { + return this.buffer.substr(this.pos, n); + } + *parseNext(next) { + switch (next) { + case "stream": return yield* this.parseStream(); + case "line-start": return yield* this.parseLineStart(); + case "block-start": return yield* this.parseBlockStart(); + case "doc": return yield* this.parseDocument(); + case "flow": return yield* this.parseFlowCollection(); + case "quoted-scalar": return yield* this.parseQuotedScalar(); + case "block-scalar": return yield* this.parseBlockScalar(); + case "plain-scalar": return yield* this.parsePlainScalar(); + } + } + *parseStream() { + let line = this.getLine(); + if (line === null) return this.setNext("stream"); + if (line[0] === "") { + yield* this.pushCount(1); + line = line.substring(1); + } + if (line[0] === "%") { + let dirEnd = line.length; + let cs = line.indexOf("#"); + while (cs !== -1) { + const ch = line[cs - 1]; + if (ch === " " || ch === " ") { + dirEnd = cs - 1; + break; + } else cs = line.indexOf("#", cs + 1); + } + while (true) { + const ch = line[dirEnd - 1]; + if (ch === " " || ch === " ") dirEnd -= 1; + else break; + } + const n = (yield* this.pushCount(dirEnd)) + (yield* this.pushSpaces(true)); + yield* this.pushCount(line.length - n); + this.pushNewline(); + return "stream"; + } + if (this.atLineEnd()) { + const sp = yield* this.pushSpaces(true); + yield* this.pushCount(line.length - sp); + yield* this.pushNewline(); + return "stream"; + } + yield ""; + return yield* this.parseLineStart(); + } + *parseLineStart() { + const ch = this.charAt(0); + if (!ch && !this.atEnd) return this.setNext("line-start"); + if (ch === "-" || ch === ".") { + if (!this.atEnd && !this.hasChars(4)) return this.setNext("line-start"); + const s = this.peek(3); + if ((s === "---" || s === "...") && isEmpty(this.charAt(3))) { + yield* this.pushCount(3); + this.indentValue = 0; + this.indentNext = 0; + return s === "---" ? "doc" : "stream"; + } + } + this.indentValue = yield* this.pushSpaces(false); + if (this.indentNext > this.indentValue && !isEmpty(this.charAt(1))) this.indentNext = this.indentValue; + return yield* this.parseBlockStart(); + } + *parseBlockStart() { + const [ch0, ch1] = this.peek(2); + if (!ch1 && !this.atEnd) return this.setNext("block-start"); + if ((ch0 === "-" || ch0 === "?" || ch0 === ":") && isEmpty(ch1)) { + const n = (yield* this.pushCount(1)) + (yield* this.pushSpaces(true)); + this.indentNext = this.indentValue + 1; + this.indentValue += n; + return "block-start"; + } + return "doc"; + } + *parseDocument() { + yield* this.pushSpaces(true); + const line = this.getLine(); + if (line === null) return this.setNext("doc"); + let n = yield* this.pushIndicators(); + switch (line[n]) { + case "#": yield* this.pushCount(line.length - n); + case void 0: + yield* this.pushNewline(); + return yield* this.parseLineStart(); + case "{": + case "[": + yield* this.pushCount(1); + this.flowKey = false; + this.flowLevel = 1; + return "flow"; + case "}": + case "]": + yield* this.pushCount(1); + return "doc"; + case "*": + yield* this.pushUntil(isNotAnchorChar); + return "doc"; + case "\"": + case "'": return yield* this.parseQuotedScalar(); + case "|": + case ">": + n += yield* this.parseBlockScalarHeader(); + n += yield* this.pushSpaces(true); + yield* this.pushCount(line.length - n); + yield* this.pushNewline(); + return yield* this.parseBlockScalar(); + default: return yield* this.parsePlainScalar(); + } + } + *parseFlowCollection() { + let nl, sp; + let indent = -1; + do { + nl = yield* this.pushNewline(); + if (nl > 0) { + sp = yield* this.pushSpaces(false); + this.indentValue = indent = sp; + } else sp = 0; + sp += yield* this.pushSpaces(true); + } while (nl + sp > 0); + const line = this.getLine(); + if (line === null) return this.setNext("flow"); + if (indent !== -1 && indent < this.indentNext && line[0] !== "#" || indent === 0 && (line.startsWith("---") || line.startsWith("...")) && isEmpty(line[3])) { + if (!(indent === this.indentNext - 1 && this.flowLevel === 1 && (line[0] === "]" || line[0] === "}"))) { + this.flowLevel = 0; + yield ""; + return yield* this.parseLineStart(); + } + } + let n = 0; + while (line[n] === ",") { + n += yield* this.pushCount(1); + n += yield* this.pushSpaces(true); + this.flowKey = false; + } + n += yield* this.pushIndicators(); + switch (line[n]) { + case void 0: return "flow"; + case "#": + yield* this.pushCount(line.length - n); + return "flow"; + case "{": + case "[": + yield* this.pushCount(1); + this.flowKey = false; + this.flowLevel += 1; + return "flow"; + case "}": + case "]": + yield* this.pushCount(1); + this.flowKey = true; + this.flowLevel -= 1; + return this.flowLevel ? "flow" : "doc"; + case "*": + yield* this.pushUntil(isNotAnchorChar); + return "flow"; + case "\"": + case "'": + this.flowKey = true; + return yield* this.parseQuotedScalar(); + case ":": { + const next = this.charAt(1); + if (this.flowKey || isEmpty(next) || next === ",") { + this.flowKey = false; + yield* this.pushCount(1); + yield* this.pushSpaces(true); + return "flow"; + } + } + default: + this.flowKey = false; + return yield* this.parsePlainScalar(); + } + } + *parseQuotedScalar() { + const quote = this.charAt(0); + let end = this.buffer.indexOf(quote, this.pos + 1); + if (quote === "'") while (end !== -1 && this.buffer[end + 1] === "'") end = this.buffer.indexOf("'", end + 2); + else while (end !== -1) { + let n = 0; + while (this.buffer[end - 1 - n] === "\\") n += 1; + if (n % 2 === 0) break; + end = this.buffer.indexOf("\"", end + 1); + } + const qb = this.buffer.substring(0, end); + let nl = qb.indexOf("\n", this.pos); + if (nl !== -1) { + while (nl !== -1) { + const cs = this.continueScalar(nl + 1); + if (cs === -1) break; + nl = qb.indexOf("\n", cs); + } + if (nl !== -1) end = nl - (qb[nl - 1] === "\r" ? 2 : 1); + } + if (end === -1) { + if (!this.atEnd) return this.setNext("quoted-scalar"); + end = this.buffer.length; + } + yield* this.pushToIndex(end + 1, false); + return this.flowLevel ? "flow" : "doc"; + } + *parseBlockScalarHeader() { + this.blockScalarIndent = -1; + this.blockScalarKeep = false; + let i = this.pos; + while (true) { + const ch = this.buffer[++i]; + if (ch === "+") this.blockScalarKeep = true; + else if (ch > "0" && ch <= "9") this.blockScalarIndent = Number(ch) - 1; + else if (ch !== "-") break; + } + return yield* this.pushUntil((ch) => isEmpty(ch) || ch === "#"); + } + *parseBlockScalar() { + let nl = this.pos - 1; + let indent = 0; + let ch; + loop: for (let i = this.pos; ch = this.buffer[i]; ++i) switch (ch) { + case " ": + indent += 1; + break; + case "\n": + nl = i; + indent = 0; + break; + case "\r": { + const next = this.buffer[i + 1]; + if (!next && !this.atEnd) return this.setNext("block-scalar"); + if (next === "\n") break; + } + default: break loop; + } + if (!ch && !this.atEnd) return this.setNext("block-scalar"); + if (indent >= this.indentNext) { + if (this.blockScalarIndent === -1) this.indentNext = indent; + else this.indentNext = this.blockScalarIndent + (this.indentNext === 0 ? 1 : this.indentNext); + do { + const cs = this.continueScalar(nl + 1); + if (cs === -1) break; + nl = this.buffer.indexOf("\n", cs); + } while (nl !== -1); + if (nl === -1) { + if (!this.atEnd) return this.setNext("block-scalar"); + nl = this.buffer.length; + } + } + let i = nl + 1; + ch = this.buffer[i]; + while (ch === " ") ch = this.buffer[++i]; + if (ch === " ") { + while (ch === " " || ch === " " || ch === "\r" || ch === "\n") ch = this.buffer[++i]; + nl = i - 1; + } else if (!this.blockScalarKeep) do { + let i = nl - 1; + let ch = this.buffer[i]; + if (ch === "\r") ch = this.buffer[--i]; + const lastChar = i; + while (ch === " ") ch = this.buffer[--i]; + if (ch === "\n" && i >= this.pos && i + 1 + indent > lastChar) nl = i; + else break; + } while (true); + yield ""; + yield* this.pushToIndex(nl + 1, true); + return yield* this.parseLineStart(); + } + *parsePlainScalar() { + const inFlow = this.flowLevel > 0; + let end = this.pos - 1; + let i = this.pos - 1; + let ch; + while (ch = this.buffer[++i]) if (ch === ":") { + const next = this.buffer[i + 1]; + if (isEmpty(next) || inFlow && flowIndicatorChars.has(next)) break; + end = i; + } else if (isEmpty(ch)) { + let next = this.buffer[i + 1]; + if (ch === "\r") if (next === "\n") { + i += 1; + ch = "\n"; + next = this.buffer[i + 1]; + } else end = i; + if (next === "#" || inFlow && flowIndicatorChars.has(next)) break; + if (ch === "\n") { + const cs = this.continueScalar(i + 1); + if (cs === -1) break; + i = Math.max(i, cs - 2); + } + } else { + if (inFlow && flowIndicatorChars.has(ch)) break; + end = i; + } + if (!ch && !this.atEnd) return this.setNext("plain-scalar"); + yield ""; + yield* this.pushToIndex(end + 1, true); + return inFlow ? "flow" : "doc"; + } + *pushCount(n) { + if (n > 0) { + yield this.buffer.substr(this.pos, n); + this.pos += n; + return n; + } + return 0; + } + *pushToIndex(i, allowEmpty) { + const s = this.buffer.slice(this.pos, i); + if (s) { + yield s; + this.pos += s.length; + return s.length; + } else if (allowEmpty) yield ""; + return 0; + } + *pushIndicators() { + let n = 0; + loop: while (true) { + switch (this.charAt(0)) { + case "!": + n += yield* this.pushTag(); + n += yield* this.pushSpaces(true); + continue loop; + case "&": + n += yield* this.pushUntil(isNotAnchorChar); + n += yield* this.pushSpaces(true); + continue loop; + case "-": + case "?": + case ":": { + const inFlow = this.flowLevel > 0; + const ch1 = this.charAt(1); + if (isEmpty(ch1) || inFlow && flowIndicatorChars.has(ch1)) { + if (!inFlow) this.indentNext = this.indentValue + 1; + else if (this.flowKey) this.flowKey = false; + n += yield* this.pushCount(1); + n += yield* this.pushSpaces(true); + continue loop; + } + } + } + break loop; + } + return n; + } + *pushTag() { + if (this.charAt(1) === "<") { + let i = this.pos + 2; + let ch = this.buffer[i]; + while (!isEmpty(ch) && ch !== ">") ch = this.buffer[++i]; + return yield* this.pushToIndex(ch === ">" ? i + 1 : i, false); + } else { + let i = this.pos + 1; + let ch = this.buffer[i]; + while (ch) if (tagChars.has(ch)) ch = this.buffer[++i]; + else if (ch === "%" && hexDigits.has(this.buffer[i + 1]) && hexDigits.has(this.buffer[i + 2])) ch = this.buffer[i += 3]; + else break; + return yield* this.pushToIndex(i, false); + } + } + *pushNewline() { + const ch = this.buffer[this.pos]; + if (ch === "\n") return yield* this.pushCount(1); + else if (ch === "\r" && this.charAt(1) === "\n") return yield* this.pushCount(2); + else return 0; + } + *pushSpaces(allowTabs) { + let i = this.pos - 1; + let ch; + do + ch = this.buffer[++i]; + while (ch === " " || allowTabs && ch === " "); + const n = i - this.pos; + if (n > 0) { + yield this.buffer.substr(this.pos, n); + this.pos = i; + } + return n; + } + *pushUntil(test) { + let i = this.pos; + let ch = this.buffer[i]; + while (!test(ch)) ch = this.buffer[++i]; + return yield* this.pushToIndex(i, false); + } +}; +//#endregion +//#region node_modules/yaml/browser/dist/parse/line-counter.js +/** +* Tracks newlines during parsing in order to provide an efficient API for +* determining the one-indexed `{ line, col }` position for any offset +* within the input. +*/ +var LineCounter = class { + constructor() { + this.lineStarts = []; + /** + * Should be called in ascending order. Otherwise, call + * `lineCounter.lineStarts.sort()` before calling `linePos()`. + */ + this.addNewLine = (offset) => this.lineStarts.push(offset); + /** + * Performs a binary search and returns the 1-indexed { line, col } + * position of `offset`. If `line === 0`, `addNewLine` has never been + * called or `offset` is before the first known newline. + */ + this.linePos = (offset) => { + let low = 0; + let high = this.lineStarts.length; + while (low < high) { + const mid = low + high >> 1; + if (this.lineStarts[mid] < offset) low = mid + 1; + else high = mid; + } + if (this.lineStarts[low] === offset) return { + line: low + 1, + col: 1 + }; + if (low === 0) return { + line: 0, + col: offset + }; + const start = this.lineStarts[low - 1]; + return { + line: low, + col: offset - start + 1 + }; + }; + } +}; +//#endregion +//#region node_modules/yaml/browser/dist/parse/parser.js +function includesToken(list, type) { + for (let i = 0; i < list.length; ++i) if (list[i].type === type) return true; + return false; +} +function findNonEmptyIndex(list) { + for (let i = 0; i < list.length; ++i) switch (list[i].type) { + case "space": + case "comment": + case "newline": break; + default: return i; + } + return -1; +} +function isFlowToken(token) { + switch (token?.type) { + case "alias": + case "scalar": + case "single-quoted-scalar": + case "double-quoted-scalar": + case "flow-collection": return true; + default: return false; + } +} +function getPrevProps(parent) { + switch (parent.type) { + case "document": return parent.start; + case "block-map": { + const it = parent.items[parent.items.length - 1]; + return it.sep ?? it.start; + } + case "block-seq": return parent.items[parent.items.length - 1].start; + /* istanbul ignore next should not happen */ + default: return []; + } +} +/** Note: May modify input array */ +function getFirstKeyStartProps(prev) { + if (prev.length === 0) return []; + let i = prev.length; + loop: while (--i >= 0) switch (prev[i].type) { + case "doc-start": + case "explicit-key-ind": + case "map-value-ind": + case "seq-item-ind": + case "newline": break loop; + } + while (prev[++i]?.type === "space"); + return prev.splice(i, prev.length); +} +function arrayPushArray(target, source) { + if (source.length < 1e5) Array.prototype.push.apply(target, source); + else for (let i = 0; i < source.length; ++i) target.push(source[i]); +} +function fixFlowSeqItems(fc) { + if (fc.start.type === "flow-seq-start") { + for (const it of fc.items) if (it.sep && !it.value && !includesToken(it.start, "explicit-key-ind") && !includesToken(it.sep, "map-value-ind")) { + if (it.key) it.value = it.key; + delete it.key; + if (isFlowToken(it.value)) if (it.value.end) arrayPushArray(it.value.end, it.sep); + else it.value.end = it.sep; + else arrayPushArray(it.start, it.sep); + delete it.sep; + } + } +} +/** +* A YAML concrete syntax tree (CST) parser +* +* ```ts +* const src: string = ... +* for (const token of new Parser().parse(src)) { +* // token: Token +* } +* ``` +* +* To use the parser with a user-provided lexer: +* +* ```ts +* function* parse(source: string, lexer: Lexer) { +* const parser = new Parser() +* for (const lexeme of lexer.lex(source)) +* yield* parser.next(lexeme) +* yield* parser.end() +* } +* +* const src: string = ... +* const lexer = new Lexer() +* for (const token of parse(src, lexer)) { +* // token: Token +* } +* ``` +*/ +var Parser = class { + /** + * @param onNewLine - If defined, called separately with the start position of + * each new line (in `parse()`, including the start of input). + */ + constructor(onNewLine) { + /** If true, space and sequence indicators count as indentation */ + this.atNewLine = true; + /** If true, next token is a scalar value */ + this.atScalar = false; + /** Current indentation level */ + this.indent = 0; + /** Current offset since the start of parsing */ + this.offset = 0; + /** On the same line with a block map key */ + this.onKeyLine = false; + /** Top indicates the node that's currently being built */ + this.stack = []; + /** The source of the current token, set in parse() */ + this.source = ""; + /** The type of the current token, set in parse() */ + this.type = ""; + this.lexer = new Lexer(); + this.onNewLine = onNewLine; + } + /** + * Parse `source` as a YAML stream. + * If `incomplete`, a part of the last line may be left as a buffer for the next call. + * + * Errors are not thrown, but yielded as `{ type: 'error', message }` tokens. + * + * @returns A generator of tokens representing each directive, document, and other structure. + */ + *parse(source, incomplete = false) { + if (this.onNewLine && this.offset === 0) this.onNewLine(0); + for (const lexeme of this.lexer.lex(source, incomplete)) yield* this.next(lexeme); + if (!incomplete) yield* this.end(); + } + /** + * Advance the parser by the `source` of one lexical token. + */ + *next(source) { + this.source = source; + if (this.atScalar) { + this.atScalar = false; + yield* this.step(); + this.offset += source.length; + return; + } + const type = tokenType(source); + if (!type) { + const message = `Not a YAML token: ${source}`; + yield* this.pop({ + type: "error", + offset: this.offset, + message, + source + }); + this.offset += source.length; + } else if (type === "scalar") { + this.atNewLine = false; + this.atScalar = true; + this.type = "scalar"; + } else { + this.type = type; + yield* this.step(); + switch (type) { + case "newline": + this.atNewLine = true; + this.indent = 0; + if (this.onNewLine) this.onNewLine(this.offset + source.length); + break; + case "space": + if (this.atNewLine && source[0] === " ") this.indent += source.length; + break; + case "explicit-key-ind": + case "map-value-ind": + case "seq-item-ind": + if (this.atNewLine) this.indent += source.length; + break; + case "doc-mode": + case "flow-error-end": return; + default: this.atNewLine = false; + } + this.offset += source.length; + } + } + /** Call at end of input to push out any remaining constructions */ + *end() { + while (this.stack.length > 0) yield* this.pop(); + } + get sourceToken() { + return { + type: this.type, + offset: this.offset, + indent: this.indent, + source: this.source + }; + } + *step() { + const top = this.peek(1); + if (this.type === "doc-end" && top?.type !== "doc-end") { + while (this.stack.length > 0) yield* this.pop(); + this.stack.push({ + type: "doc-end", + offset: this.offset, + source: this.source + }); + return; + } + if (!top) return yield* this.stream(); + switch (top.type) { + case "document": return yield* this.document(top); + case "alias": + case "scalar": + case "single-quoted-scalar": + case "double-quoted-scalar": return yield* this.scalar(top); + case "block-scalar": return yield* this.blockScalar(top); + case "block-map": return yield* this.blockMap(top); + case "block-seq": return yield* this.blockSequence(top); + case "flow-collection": return yield* this.flowCollection(top); + case "doc-end": return yield* this.documentEnd(top); + } + /* istanbul ignore next should not happen */ + yield* this.pop(); + } + peek(n) { + return this.stack[this.stack.length - n]; + } + *pop(error) { + const token = error ?? this.stack.pop(); + /* istanbul ignore if should not happen */ + if (!token) yield { + type: "error", + offset: this.offset, + source: "", + message: "Tried to pop an empty stack" + }; + else if (this.stack.length === 0) yield token; + else { + const top = this.peek(1); + if (token.type === "block-scalar") token.indent = "indent" in top ? top.indent : 0; + else if (token.type === "flow-collection" && top.type === "document") token.indent = 0; + if (token.type === "flow-collection") fixFlowSeqItems(token); + switch (top.type) { + case "document": + top.value = token; + break; + case "block-scalar": + top.props.push(token); + break; + case "block-map": { + const it = top.items[top.items.length - 1]; + if (it.value) { + top.items.push({ + start: [], + key: token, + sep: [] + }); + this.onKeyLine = true; + return; + } else if (it.sep) it.value = token; + else { + Object.assign(it, { + key: token, + sep: [] + }); + this.onKeyLine = !it.explicitKey; + return; + } + break; + } + case "block-seq": { + const it = top.items[top.items.length - 1]; + if (it.value) top.items.push({ + start: [], + value: token + }); + else it.value = token; + break; + } + case "flow-collection": { + const it = top.items[top.items.length - 1]; + if (!it || it.value) top.items.push({ + start: [], + key: token, + sep: [] + }); + else if (it.sep) it.value = token; + else Object.assign(it, { + key: token, + sep: [] + }); + return; + } + /* istanbul ignore next should not happen */ + default: + yield* this.pop(); + yield* this.pop(token); + } + if ((top.type === "document" || top.type === "block-map" || top.type === "block-seq") && (token.type === "block-map" || token.type === "block-seq")) { + const last = token.items[token.items.length - 1]; + if (last && !last.sep && !last.value && last.start.length > 0 && findNonEmptyIndex(last.start) === -1 && (token.indent === 0 || last.start.every((st) => st.type !== "comment" || st.indent < token.indent))) { + if (top.type === "document") top.end = last.start; + else top.items.push({ start: last.start }); + token.items.splice(-1, 1); + } + } + } + } + *stream() { + switch (this.type) { + case "directive-line": + yield { + type: "directive", + offset: this.offset, + source: this.source + }; + return; + case "byte-order-mark": + case "space": + case "comment": + case "newline": + yield this.sourceToken; + return; + case "doc-mode": + case "doc-start": { + const doc = { + type: "document", + offset: this.offset, + start: [] + }; + if (this.type === "doc-start") doc.start.push(this.sourceToken); + this.stack.push(doc); + return; + } + } + yield { + type: "error", + offset: this.offset, + message: `Unexpected ${this.type} token in YAML stream`, + source: this.source + }; + } + *document(doc) { + if (doc.value) return yield* this.lineEnd(doc); + switch (this.type) { + case "doc-start": + if (findNonEmptyIndex(doc.start) !== -1) { + yield* this.pop(); + yield* this.step(); + } else doc.start.push(this.sourceToken); + return; + case "anchor": + case "tag": + case "space": + case "comment": + case "newline": + doc.start.push(this.sourceToken); + return; + } + const bv = this.startBlockValue(doc); + if (bv) this.stack.push(bv); + else yield { + type: "error", + offset: this.offset, + message: `Unexpected ${this.type} token in YAML document`, + source: this.source + }; + } + *scalar(scalar) { + if (this.type === "map-value-ind") { + const start = getFirstKeyStartProps(getPrevProps(this.peek(2))); + let sep; + if (scalar.end) { + sep = scalar.end; + sep.push(this.sourceToken); + delete scalar.end; + } else sep = [this.sourceToken]; + const map = { + type: "block-map", + offset: scalar.offset, + indent: scalar.indent, + items: [{ + start, + key: scalar, + sep + }] + }; + this.onKeyLine = true; + this.stack[this.stack.length - 1] = map; + } else yield* this.lineEnd(scalar); + } + *blockScalar(scalar) { + switch (this.type) { + case "space": + case "comment": + case "newline": + scalar.props.push(this.sourceToken); + return; + case "scalar": + scalar.source = this.source; + this.atNewLine = true; + this.indent = 0; + if (this.onNewLine) { + let nl = this.source.indexOf("\n") + 1; + while (nl !== 0) { + this.onNewLine(this.offset + nl); + nl = this.source.indexOf("\n", nl) + 1; + } + } + yield* this.pop(); + break; + /* istanbul ignore next should not happen */ + default: + yield* this.pop(); + yield* this.step(); + } + } + *blockMap(map) { + const it = map.items[map.items.length - 1]; + switch (this.type) { + case "newline": + this.onKeyLine = false; + if (it.value) { + const end = "end" in it.value ? it.value.end : void 0; + if ((Array.isArray(end) ? end[end.length - 1] : void 0)?.type === "comment") end?.push(this.sourceToken); + else map.items.push({ start: [this.sourceToken] }); + } else if (it.sep) it.sep.push(this.sourceToken); + else it.start.push(this.sourceToken); + return; + case "space": + case "comment": + if (it.value) map.items.push({ start: [this.sourceToken] }); + else if (it.sep) it.sep.push(this.sourceToken); + else { + if (this.atIndentedComment(it.start, map.indent)) { + const end = map.items[map.items.length - 2]?.value?.end; + if (Array.isArray(end)) { + arrayPushArray(end, it.start); + end.push(this.sourceToken); + map.items.pop(); + return; + } + } + it.start.push(this.sourceToken); + } + return; + } + if (this.indent >= map.indent) { + const atMapIndent = !this.onKeyLine && this.indent === map.indent; + const atNextItem = atMapIndent && (it.sep || it.explicitKey) && this.type !== "seq-item-ind"; + let start = []; + if (atNextItem && it.sep && !it.value) { + const nl = []; + for (let i = 0; i < it.sep.length; ++i) { + const st = it.sep[i]; + switch (st.type) { + case "newline": + nl.push(i); + break; + case "space": break; + case "comment": + if (st.indent > map.indent) nl.length = 0; + break; + default: nl.length = 0; + } + } + if (nl.length >= 2) start = it.sep.splice(nl[1]); + } + switch (this.type) { + case "anchor": + case "tag": + if (atNextItem || it.value) { + start.push(this.sourceToken); + map.items.push({ start }); + this.onKeyLine = true; + } else if (it.sep) it.sep.push(this.sourceToken); + else it.start.push(this.sourceToken); + return; + case "explicit-key-ind": + if (!it.sep && !it.explicitKey) { + it.start.push(this.sourceToken); + it.explicitKey = true; + } else if (atNextItem || it.value) { + start.push(this.sourceToken); + map.items.push({ + start, + explicitKey: true + }); + } else this.stack.push({ + type: "block-map", + offset: this.offset, + indent: this.indent, + items: [{ + start: [this.sourceToken], + explicitKey: true + }] + }); + this.onKeyLine = true; + return; + case "map-value-ind": + if (it.explicitKey) if (!it.sep) if (includesToken(it.start, "newline")) Object.assign(it, { + key: null, + sep: [this.sourceToken] + }); + else { + const start = getFirstKeyStartProps(it.start); + this.stack.push({ + type: "block-map", + offset: this.offset, + indent: this.indent, + items: [{ + start, + key: null, + sep: [this.sourceToken] + }] + }); + } + else if (it.value) map.items.push({ + start: [], + key: null, + sep: [this.sourceToken] + }); + else if (includesToken(it.sep, "map-value-ind")) this.stack.push({ + type: "block-map", + offset: this.offset, + indent: this.indent, + items: [{ + start, + key: null, + sep: [this.sourceToken] + }] + }); + else if (isFlowToken(it.key) && !includesToken(it.sep, "newline")) { + const start = getFirstKeyStartProps(it.start); + const key = it.key; + const sep = it.sep; + sep.push(this.sourceToken); + delete it.key; + delete it.sep; + this.stack.push({ + type: "block-map", + offset: this.offset, + indent: this.indent, + items: [{ + start, + key, + sep + }] + }); + } else if (start.length > 0) it.sep = it.sep.concat(start, this.sourceToken); + else it.sep.push(this.sourceToken); + else if (!it.sep) Object.assign(it, { + key: null, + sep: [this.sourceToken] + }); + else if (it.value || atNextItem) map.items.push({ + start, + key: null, + sep: [this.sourceToken] + }); + else if (includesToken(it.sep, "map-value-ind")) this.stack.push({ + type: "block-map", + offset: this.offset, + indent: this.indent, + items: [{ + start: [], + key: null, + sep: [this.sourceToken] + }] + }); + else it.sep.push(this.sourceToken); + this.onKeyLine = true; + return; + case "alias": + case "scalar": + case "single-quoted-scalar": + case "double-quoted-scalar": { + const fs = this.flowScalar(this.type); + if (atNextItem || it.value) { + map.items.push({ + start, + key: fs, + sep: [] + }); + this.onKeyLine = true; + } else if (it.sep) this.stack.push(fs); + else { + Object.assign(it, { + key: fs, + sep: [] + }); + this.onKeyLine = true; + } + return; + } + default: { + const bv = this.startBlockValue(map); + if (bv) { + if (bv.type === "block-seq") { + if (!it.explicitKey && it.sep && !includesToken(it.sep, "newline")) { + yield* this.pop({ + type: "error", + offset: this.offset, + message: "Unexpected block-seq-ind on same line with key", + source: this.source + }); + return; + } + } else if (atMapIndent) map.items.push({ start }); + this.stack.push(bv); + return; + } + } + } + } + yield* this.pop(); + yield* this.step(); + } + *blockSequence(seq) { + const it = seq.items[seq.items.length - 1]; + switch (this.type) { + case "newline": + if (it.value) { + const end = "end" in it.value ? it.value.end : void 0; + if ((Array.isArray(end) ? end[end.length - 1] : void 0)?.type === "comment") end?.push(this.sourceToken); + else seq.items.push({ start: [this.sourceToken] }); + } else it.start.push(this.sourceToken); + return; + case "space": + case "comment": + if (it.value) seq.items.push({ start: [this.sourceToken] }); + else { + if (this.atIndentedComment(it.start, seq.indent)) { + const end = seq.items[seq.items.length - 2]?.value?.end; + if (Array.isArray(end)) { + arrayPushArray(end, it.start); + end.push(this.sourceToken); + seq.items.pop(); + return; + } + } + it.start.push(this.sourceToken); + } + return; + case "anchor": + case "tag": + if (it.value || this.indent <= seq.indent) break; + it.start.push(this.sourceToken); + return; + case "seq-item-ind": + if (this.indent !== seq.indent) break; + if (it.value || includesToken(it.start, "seq-item-ind")) seq.items.push({ start: [this.sourceToken] }); + else it.start.push(this.sourceToken); + return; + } + if (this.indent > seq.indent) { + const bv = this.startBlockValue(seq); + if (bv) { + this.stack.push(bv); + return; + } + } + yield* this.pop(); + yield* this.step(); + } + *flowCollection(fc) { + const it = fc.items[fc.items.length - 1]; + if (this.type === "flow-error-end") { + let top; + do { + yield* this.pop(); + top = this.peek(1); + } while (top?.type === "flow-collection"); + } else if (fc.end.length === 0) { + switch (this.type) { + case "comma": + case "explicit-key-ind": + if (!it || it.sep) fc.items.push({ start: [this.sourceToken] }); + else it.start.push(this.sourceToken); + return; + case "map-value-ind": + if (!it || it.value) fc.items.push({ + start: [], + key: null, + sep: [this.sourceToken] + }); + else if (it.sep) it.sep.push(this.sourceToken); + else Object.assign(it, { + key: null, + sep: [this.sourceToken] + }); + return; + case "space": + case "comment": + case "newline": + case "anchor": + case "tag": + if (!it || it.value) fc.items.push({ start: [this.sourceToken] }); + else if (it.sep) it.sep.push(this.sourceToken); + else it.start.push(this.sourceToken); + return; + case "alias": + case "scalar": + case "single-quoted-scalar": + case "double-quoted-scalar": { + const fs = this.flowScalar(this.type); + if (!it || it.value) fc.items.push({ + start: [], + key: fs, + sep: [] + }); + else if (it.sep) this.stack.push(fs); + else Object.assign(it, { + key: fs, + sep: [] + }); + return; + } + case "flow-map-end": + case "flow-seq-end": + fc.end.push(this.sourceToken); + return; + } + const bv = this.startBlockValue(fc); + /* istanbul ignore else should not happen */ + if (bv) this.stack.push(bv); + else { + yield* this.pop(); + yield* this.step(); + } + } else { + const parent = this.peek(2); + if (parent.type === "block-map" && (this.type === "map-value-ind" && parent.indent === fc.indent || this.type === "newline" && !parent.items[parent.items.length - 1].sep)) { + yield* this.pop(); + yield* this.step(); + } else if (this.type === "map-value-ind" && parent.type !== "flow-collection") { + const start = getFirstKeyStartProps(getPrevProps(parent)); + fixFlowSeqItems(fc); + const sep = fc.end.splice(1, fc.end.length); + sep.push(this.sourceToken); + const map = { + type: "block-map", + offset: fc.offset, + indent: fc.indent, + items: [{ + start, + key: fc, + sep + }] + }; + this.onKeyLine = true; + this.stack[this.stack.length - 1] = map; + } else yield* this.lineEnd(fc); + } + } + flowScalar(type) { + if (this.onNewLine) { + let nl = this.source.indexOf("\n") + 1; + while (nl !== 0) { + this.onNewLine(this.offset + nl); + nl = this.source.indexOf("\n", nl) + 1; + } + } + return { + type, + offset: this.offset, + indent: this.indent, + source: this.source + }; + } + startBlockValue(parent) { + switch (this.type) { + case "alias": + case "scalar": + case "single-quoted-scalar": + case "double-quoted-scalar": return this.flowScalar(this.type); + case "block-scalar-header": return { + type: "block-scalar", + offset: this.offset, + indent: this.indent, + props: [this.sourceToken], + source: "" + }; + case "flow-map-start": + case "flow-seq-start": return { + type: "flow-collection", + offset: this.offset, + indent: this.indent, + start: this.sourceToken, + items: [], + end: [] + }; + case "seq-item-ind": return { + type: "block-seq", + offset: this.offset, + indent: this.indent, + items: [{ start: [this.sourceToken] }] + }; + case "explicit-key-ind": { + this.onKeyLine = true; + const start = getFirstKeyStartProps(getPrevProps(parent)); + start.push(this.sourceToken); + return { + type: "block-map", + offset: this.offset, + indent: this.indent, + items: [{ + start, + explicitKey: true + }] + }; + } + case "map-value-ind": { + this.onKeyLine = true; + const start = getFirstKeyStartProps(getPrevProps(parent)); + return { + type: "block-map", + offset: this.offset, + indent: this.indent, + items: [{ + start, + key: null, + sep: [this.sourceToken] + }] + }; + } + } + return null; + } + atIndentedComment(start, indent) { + if (this.type !== "comment") return false; + if (this.indent <= indent) return false; + return start.every((st) => st.type === "newline" || st.type === "space"); + } + *documentEnd(docEnd) { + if (this.type !== "doc-mode") { + if (docEnd.end) docEnd.end.push(this.sourceToken); + else docEnd.end = [this.sourceToken]; + if (this.type === "newline") yield* this.pop(); + } + } + *lineEnd(token) { + switch (this.type) { + case "comma": + case "doc-start": + case "doc-end": + case "flow-seq-end": + case "flow-map-end": + case "map-value-ind": + yield* this.pop(); + yield* this.step(); + break; + case "newline": this.onKeyLine = false; + default: + if (token.end) token.end.push(this.sourceToken); + else token.end = [this.sourceToken]; + if (this.type === "newline") yield* this.pop(); + } + } +}; +//#endregion +//#region node_modules/yaml/browser/dist/public-api.js +function parseOptions(options) { + const prettyErrors = options.prettyErrors !== false; + return { + lineCounter: options.lineCounter || prettyErrors && new LineCounter() || null, + prettyErrors + }; +} +/** Parse an input string into a single YAML.Document */ +function parseDocument(source, options = {}) { + const { lineCounter, prettyErrors } = parseOptions(options); + const parser = new Parser(lineCounter?.addNewLine); + const composer = new Composer(options); + let doc = null; + for (const _doc of composer.compose(parser.parse(source), true, source.length)) if (!doc) doc = _doc; + else if (doc.options.logLevel !== "silent") { + doc.errors.push(new YAMLParseError(_doc.range.slice(0, 2), "MULTIPLE_DOCS", "Source contains multiple documents; please use YAML.parseAllDocuments()")); + break; + } + if (prettyErrors && lineCounter) { + doc.errors.forEach(prettifyError(source, lineCounter)); + doc.warnings.forEach(prettifyError(source, lineCounter)); + } + return doc; +} +function parse$1(src, reviver, options) { + let _reviver = void 0; + if (typeof reviver === "function") _reviver = reviver; + else if (options === void 0 && reviver && typeof reviver === "object") options = reviver; + const doc = parseDocument(src, options); + if (!doc) return null; + doc.warnings.forEach((warning) => warn(doc.options.logLevel, warning)); + if (doc.errors.length > 0) if (doc.options.logLevel !== "silent") throw doc.errors[0]; + else doc.errors = []; + return doc.toJS(Object.assign({ reviver: _reviver }, options)); +} +var mergeStrategySchema = _enum([ + "override", + "append", + "prepend" +]); +var mergeStrategiesSchema = record(string$1(), mergeStrategySchema); +/** +* Parses the common envelope of a raw config file while retaining all +* action-specific keys for composition and later validation. +*/ +var configFileSchema = looseObject({ _extends: union([ + string$1(), + _null(), + strictObject({ + from: string$1().regex(/\S/, "'from' must not be blank"), + strategy: mergeStrategiesSchema.nullish() + }) +]).optional().transform((value) => { + if (value == null || typeof value === "string" && value.trim() === "") return; + if (typeof value === "string") return { + from: value.trim(), + strategy: {} + }; + return { + from: value.from.trim(), + strategy: value.strategy ?? {} + }; +}) }); +//#endregion +//#region src/common/config/get-config-file-from-fs.ts +var getConfigFileFromFs = (normalizedFilepath) => { + if (isAbsolute(normalizedFilepath)) throw new Error(`Absolute paths are not supported for config file path: ${normalizedFilepath}`); + if (!process$1.env.GITHUB_WORKSPACE) throw new Error(`env GITHUB_WORKSPACE is not set. Cannot resolve local repo path.`); + const repoRoot = process$1.env.GITHUB_WORKSPACE; + const configPath = path.join(repoRoot, normalizedFilepath); + info(`Looking for config locally at ${configPath}...`); + if (!existsSync$1(repoRoot)) throw new Error(`Root repo path does not exist: ${repoRoot}`); + if (!existsSync$1(configPath)) throw new Error(`Config file not found: ${configPath}. Did you clone your sources ? (ex: using @actions/checkout)`); + info(`Loading from file: ${configPath}`); + return readFileSync$1(configPath, "utf8"); +}; +//#endregion +//#region node_modules/@actions/github/lib/context.js +var Context = class { + /** + * Hydrate the context from the environment + */ + constructor() { + var _a, _b, _c; + this.payload = {}; + if (process.env.GITHUB_EVENT_PATH) if (existsSync(process.env.GITHUB_EVENT_PATH)) this.payload = JSON.parse(readFileSync(process.env.GITHUB_EVENT_PATH, { encoding: "utf8" })); + else { + const path = process.env.GITHUB_EVENT_PATH; + process.stdout.write(`GITHUB_EVENT_PATH ${path} does not exist${EOL}`); + } + this.eventName = process.env.GITHUB_EVENT_NAME; + this.sha = process.env.GITHUB_SHA; + this.ref = process.env.GITHUB_REF; + this.workflow = process.env.GITHUB_WORKFLOW; + this.action = process.env.GITHUB_ACTION; + this.actor = process.env.GITHUB_ACTOR; + this.job = process.env.GITHUB_JOB; + this.runAttempt = parseInt(process.env.GITHUB_RUN_ATTEMPT, 10); + this.runNumber = parseInt(process.env.GITHUB_RUN_NUMBER, 10); + this.runId = parseInt(process.env.GITHUB_RUN_ID, 10); + this.apiUrl = (_a = process.env.GITHUB_API_URL) !== null && _a !== void 0 ? _a : `https://api.github.com`; + this.serverUrl = (_b = process.env.GITHUB_SERVER_URL) !== null && _b !== void 0 ? _b : `https://github.com`; + this.graphqlUrl = (_c = process.env.GITHUB_GRAPHQL_URL) !== null && _c !== void 0 ? _c : `https://api.github.com/graphql`; + } + get issue() { + const payload = this.payload; + return Object.assign(Object.assign({}, this.repo), { number: (payload.issue || payload.pull_request || payload).number }); + } + get repo() { + if (process.env.GITHUB_REPOSITORY) { + const [owner, repo] = process.env.GITHUB_REPOSITORY.split("/"); + return { + owner, + repo + }; + } + if (this.payload.repository) return { + owner: this.payload.repository.owner.login, + repo: this.payload.repository.name + }; + throw new Error("context.repo requires a GITHUB_REPOSITORY environment variable like 'owner/repo'"); + } +}; +//#endregion +//#region node_modules/@actions/github/node_modules/@actions/http-client/lib/proxy.js +var require_proxy = /* @__PURE__ */ __commonJSMin(((exports) => { + Object.defineProperty(exports, "__esModule", { value: true }); + exports.getProxyUrl = getProxyUrl; + exports.checkBypass = checkBypass; + function getProxyUrl(reqUrl) { + const usingSsl = reqUrl.protocol === "https:"; + if (checkBypass(reqUrl)) return; + const proxyVar = (() => { + if (usingSsl) return process.env["https_proxy"] || process.env["HTTPS_PROXY"]; + else return process.env["http_proxy"] || process.env["HTTP_PROXY"]; + })(); + if (proxyVar) try { + return new DecodedURL(proxyVar); + } catch (_a) { + if (!proxyVar.startsWith("http://") && !proxyVar.startsWith("https://")) return new DecodedURL(`http://${proxyVar}`); + } + else return; + } + function checkBypass(reqUrl) { + if (!reqUrl.hostname) return false; + const reqHost = reqUrl.hostname; + if (isLoopbackAddress(reqHost)) return true; + const noProxy = process.env["no_proxy"] || process.env["NO_PROXY"] || ""; + if (!noProxy) return false; + let reqPort; + if (reqUrl.port) reqPort = Number(reqUrl.port); + else if (reqUrl.protocol === "http:") reqPort = 80; + else if (reqUrl.protocol === "https:") reqPort = 443; + const upperReqHosts = [reqUrl.hostname.toUpperCase()]; + if (typeof reqPort === "number") upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`); + for (const upperNoProxyItem of noProxy.split(",").map((x) => x.trim().toUpperCase()).filter((x) => x)) if (upperNoProxyItem === "*" || upperReqHosts.some((x) => x === upperNoProxyItem || x.endsWith(`.${upperNoProxyItem}`) || upperNoProxyItem.startsWith(".") && x.endsWith(`${upperNoProxyItem}`))) return true; + return false; + } + function isLoopbackAddress(host) { + const hostLower = host.toLowerCase(); + return hostLower === "localhost" || hostLower.startsWith("127.") || hostLower.startsWith("[::1]") || hostLower.startsWith("[0:0:0:0:0:0:0:1]"); + } + var DecodedURL = class extends URL { + constructor(url, base) { + super(url, base); + this._decodedUsername = decodeURIComponent(super.username); + this._decodedPassword = decodeURIComponent(super.password); + } + get username() { + return this._decodedUsername; + } + get password() { + return this._decodedPassword; + } + }; +})); +//#endregion +//#region node_modules/@actions/github/lib/internal/utils.js +var import_lib$1 = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports) => { + var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) { + if (k2 === void 0) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) desc = { + enumerable: true, + get: function() { + return m[k]; + } + }; + Object.defineProperty(o, k2, desc); + }) : (function(o, m, k, k2) { + if (k2 === void 0) k2 = k; + o[k2] = m[k]; + })); + var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { + enumerable: true, + value: v + }); + }) : function(o, v) { + o["default"] = v; + }); + var __importStar = exports && exports.__importStar || (function() { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function(o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function(mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) { + for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + } + __setModuleDefault(result, mod); + return result; + }; + })(); + var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) { + function adopt(value) { + return value instanceof P ? value : new P(function(resolve) { + resolve(value); + }); + } + return new (P || (P = Promise))(function(resolve, reject) { + function fulfilled(value) { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + } + function rejected(value) { + try { + step(generator["throw"](value)); + } catch (e) { + reject(e); + } + } + function step(result) { + result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); + } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); + }; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.HttpClient = exports.HttpClientResponse = exports.HttpClientError = exports.MediaTypes = exports.Headers = exports.HttpCodes = void 0; + exports.getProxyUrl = getProxyUrl; + exports.isHttps = isHttps; + var http = __importStar(__require("node:http")); + var https = __importStar(__require("node:https")); + var pm = __importStar(require_proxy()); + var tunnel = __importStar(require_tunnel()); + var undici_1 = require_undici(); + var HttpCodes; + (function(HttpCodes) { + HttpCodes[HttpCodes["OK"] = 200] = "OK"; + HttpCodes[HttpCodes["MultipleChoices"] = 300] = "MultipleChoices"; + HttpCodes[HttpCodes["MovedPermanently"] = 301] = "MovedPermanently"; + HttpCodes[HttpCodes["ResourceMoved"] = 302] = "ResourceMoved"; + HttpCodes[HttpCodes["SeeOther"] = 303] = "SeeOther"; + HttpCodes[HttpCodes["NotModified"] = 304] = "NotModified"; + HttpCodes[HttpCodes["UseProxy"] = 305] = "UseProxy"; + HttpCodes[HttpCodes["SwitchProxy"] = 306] = "SwitchProxy"; + HttpCodes[HttpCodes["TemporaryRedirect"] = 307] = "TemporaryRedirect"; + HttpCodes[HttpCodes["PermanentRedirect"] = 308] = "PermanentRedirect"; + HttpCodes[HttpCodes["BadRequest"] = 400] = "BadRequest"; + HttpCodes[HttpCodes["Unauthorized"] = 401] = "Unauthorized"; + HttpCodes[HttpCodes["PaymentRequired"] = 402] = "PaymentRequired"; + HttpCodes[HttpCodes["Forbidden"] = 403] = "Forbidden"; + HttpCodes[HttpCodes["NotFound"] = 404] = "NotFound"; + HttpCodes[HttpCodes["MethodNotAllowed"] = 405] = "MethodNotAllowed"; + HttpCodes[HttpCodes["NotAcceptable"] = 406] = "NotAcceptable"; + HttpCodes[HttpCodes["ProxyAuthenticationRequired"] = 407] = "ProxyAuthenticationRequired"; + HttpCodes[HttpCodes["RequestTimeout"] = 408] = "RequestTimeout"; + HttpCodes[HttpCodes["Conflict"] = 409] = "Conflict"; + HttpCodes[HttpCodes["Gone"] = 410] = "Gone"; + HttpCodes[HttpCodes["TooManyRequests"] = 429] = "TooManyRequests"; + HttpCodes[HttpCodes["InternalServerError"] = 500] = "InternalServerError"; + HttpCodes[HttpCodes["NotImplemented"] = 501] = "NotImplemented"; + HttpCodes[HttpCodes["BadGateway"] = 502] = "BadGateway"; + HttpCodes[HttpCodes["ServiceUnavailable"] = 503] = "ServiceUnavailable"; + HttpCodes[HttpCodes["GatewayTimeout"] = 504] = "GatewayTimeout"; + })(HttpCodes || (exports.HttpCodes = HttpCodes = {})); + var Headers; + (function(Headers) { + Headers["Accept"] = "accept"; + Headers["ContentType"] = "content-type"; + })(Headers || (exports.Headers = Headers = {})); + var MediaTypes; + (function(MediaTypes) { + MediaTypes["ApplicationJson"] = "application/json"; + })(MediaTypes || (exports.MediaTypes = MediaTypes = {})); + /** + * Returns the proxy URL, depending upon the supplied url and proxy environment variables. + * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com + */ + function getProxyUrl(serverUrl) { + const proxyUrl = pm.getProxyUrl(new URL(serverUrl)); + return proxyUrl ? proxyUrl.href : ""; + } + var HttpRedirectCodes = [ + HttpCodes.MovedPermanently, + HttpCodes.ResourceMoved, + HttpCodes.SeeOther, + HttpCodes.TemporaryRedirect, + HttpCodes.PermanentRedirect + ]; + var HttpResponseRetryCodes = [ + HttpCodes.BadGateway, + HttpCodes.ServiceUnavailable, + HttpCodes.GatewayTimeout + ]; + var RetryableHttpVerbs = [ + "OPTIONS", + "GET", + "DELETE", + "HEAD" + ]; + var ExponentialBackoffCeiling = 10; + var ExponentialBackoffTimeSlice = 5; + var HttpClientError = class HttpClientError extends Error { + constructor(message, statusCode) { + super(message); + this.name = "HttpClientError"; + this.statusCode = statusCode; + Object.setPrototypeOf(this, HttpClientError.prototype); + } + }; + exports.HttpClientError = HttpClientError; + var HttpClientResponse = class { + constructor(message) { + this.message = message; + } + readBody() { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { + let output = Buffer.alloc(0); + this.message.on("data", (chunk) => { + output = Buffer.concat([output, chunk]); + }); + this.message.on("end", () => { + resolve(output.toString()); + }); + })); + }); + } + readBodyBuffer() { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { + const chunks = []; + this.message.on("data", (chunk) => { + chunks.push(chunk); + }); + this.message.on("end", () => { + resolve(Buffer.concat(chunks)); + }); + })); + }); + } + }; + exports.HttpClientResponse = HttpClientResponse; + function isHttps(requestUrl) { + return new URL(requestUrl).protocol === "https:"; + } + var HttpClient = class { + constructor(userAgent, handlers, requestOptions) { + this._ignoreSslError = false; + this._allowRedirects = true; + this._allowRedirectDowngrade = false; + this._maxRedirects = 50; + this._allowRetries = false; + this._maxRetries = 1; + this._keepAlive = false; + this._disposed = false; + this.userAgent = this._getUserAgentWithOrchestrationId(userAgent); + this.handlers = handlers || []; + this.requestOptions = requestOptions; + if (requestOptions) { + if (requestOptions.ignoreSslError != null) this._ignoreSslError = requestOptions.ignoreSslError; + this._socketTimeout = requestOptions.socketTimeout; + if (requestOptions.allowRedirects != null) this._allowRedirects = requestOptions.allowRedirects; + if (requestOptions.allowRedirectDowngrade != null) this._allowRedirectDowngrade = requestOptions.allowRedirectDowngrade; + if (requestOptions.maxRedirects != null) this._maxRedirects = Math.max(requestOptions.maxRedirects, 0); + if (requestOptions.keepAlive != null) this._keepAlive = requestOptions.keepAlive; + if (requestOptions.allowRetries != null) this._allowRetries = requestOptions.allowRetries; + if (requestOptions.maxRetries != null) this._maxRetries = requestOptions.maxRetries; + } + } + options(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request("OPTIONS", requestUrl, null, additionalHeaders || {}); + }); + } + get(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request("GET", requestUrl, null, additionalHeaders || {}); + }); + } + del(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request("DELETE", requestUrl, null, additionalHeaders || {}); + }); + } + post(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request("POST", requestUrl, data, additionalHeaders || {}); + }); + } + patch(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request("PATCH", requestUrl, data, additionalHeaders || {}); + }); + } + put(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request("PUT", requestUrl, data, additionalHeaders || {}); + }); + } + head(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request("HEAD", requestUrl, null, additionalHeaders || {}); + }); + } + sendStream(verb, requestUrl, stream, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request(verb, requestUrl, stream, additionalHeaders); + }); + } + /** + * Gets a typed object from an endpoint + * Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise + */ + getJson(requestUrl_1) { + return __awaiter(this, arguments, void 0, function* (requestUrl, additionalHeaders = {}) { + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + const res = yield this.get(requestUrl, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + postJson(requestUrl_1, obj_1) { + return __awaiter(this, arguments, void 0, function* (requestUrl, obj, additionalHeaders = {}) { + const data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultContentTypeHeader(additionalHeaders, MediaTypes.ApplicationJson); + const res = yield this.post(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + putJson(requestUrl_1, obj_1) { + return __awaiter(this, arguments, void 0, function* (requestUrl, obj, additionalHeaders = {}) { + const data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultContentTypeHeader(additionalHeaders, MediaTypes.ApplicationJson); + const res = yield this.put(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + patchJson(requestUrl_1, obj_1) { + return __awaiter(this, arguments, void 0, function* (requestUrl, obj, additionalHeaders = {}) { + const data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultContentTypeHeader(additionalHeaders, MediaTypes.ApplicationJson); + const res = yield this.patch(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + /** + * Makes a raw http request. + * All other methods such as get, post, patch, and request ultimately call this. + * Prefer get, del, post and patch + */ + request(verb, requestUrl, data, headers) { + return __awaiter(this, void 0, void 0, function* () { + if (this._disposed) throw new Error("Client has already been disposed."); + const parsedUrl = new URL(requestUrl); + let info = this._prepareRequest(verb, parsedUrl, headers); + const maxTries = this._allowRetries && RetryableHttpVerbs.includes(verb) ? this._maxRetries + 1 : 1; + let numTries = 0; + let response; + do { + response = yield this.requestRaw(info, data); + if (response && response.message && response.message.statusCode === HttpCodes.Unauthorized) { + let authenticationHandler; + for (const handler of this.handlers) if (handler.canHandleAuthentication(response)) { + authenticationHandler = handler; + break; + } + if (authenticationHandler) return authenticationHandler.handleAuthentication(this, info, data); + else return response; + } + let redirectsRemaining = this._maxRedirects; + while (response.message.statusCode && HttpRedirectCodes.includes(response.message.statusCode) && this._allowRedirects && redirectsRemaining > 0) { + const redirectUrl = response.message.headers["location"]; + if (!redirectUrl) break; + const parsedRedirectUrl = new URL(redirectUrl); + if (parsedUrl.protocol === "https:" && parsedUrl.protocol !== parsedRedirectUrl.protocol && !this._allowRedirectDowngrade) throw new Error("Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true."); + yield response.readBody(); + if (parsedRedirectUrl.hostname !== parsedUrl.hostname) { + for (const header in headers) if (header.toLowerCase() === "authorization") delete headers[header]; + } + info = this._prepareRequest(verb, parsedRedirectUrl, headers); + response = yield this.requestRaw(info, data); + redirectsRemaining--; + } + if (!response.message.statusCode || !HttpResponseRetryCodes.includes(response.message.statusCode)) return response; + numTries += 1; + if (numTries < maxTries) { + yield response.readBody(); + yield this._performExponentialBackoff(numTries); + } + } while (numTries < maxTries); + return response; + }); + } + /** + * Needs to be called if keepAlive is set to true in request options. + */ + dispose() { + if (this._agent) this._agent.destroy(); + this._disposed = true; + } + /** + * Raw request. + * @param info + * @param data + */ + requestRaw(info, data) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve, reject) => { + function callbackForResult(err, res) { + if (err) reject(err); + else if (!res) reject(/* @__PURE__ */ new Error("Unknown error")); + else resolve(res); + } + this.requestRawWithCallback(info, data, callbackForResult); + }); + }); + } + /** + * Raw request with callback. + * @param info + * @param data + * @param onResult + */ + requestRawWithCallback(info, data, onResult) { + if (typeof data === "string") { + if (!info.options.headers) info.options.headers = {}; + info.options.headers["Content-Length"] = Buffer.byteLength(data, "utf8"); + } + let callbackCalled = false; + function handleResult(err, res) { + if (!callbackCalled) { + callbackCalled = true; + onResult(err, res); + } + } + const req = info.httpModule.request(info.options, (msg) => { + handleResult(void 0, new HttpClientResponse(msg)); + }); + let socket; + req.on("socket", (sock) => { + socket = sock; + }); + req.setTimeout(this._socketTimeout || 3 * 6e4, () => { + if (socket) socket.end(); + handleResult(/* @__PURE__ */ new Error(`Request timeout: ${info.options.path}`)); + }); + req.on("error", function(err) { + handleResult(err); + }); + if (data && typeof data === "string") req.write(data, "utf8"); + if (data && typeof data !== "string") { + data.on("close", function() { + req.end(); + }); + data.pipe(req); + } else req.end(); + } + /** + * Gets an http agent. This function is useful when you need an http agent that handles + * routing through a proxy server - depending upon the url and proxy environment variables. + * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com + */ + getAgent(serverUrl) { + const parsedUrl = new URL(serverUrl); + return this._getAgent(parsedUrl); + } + getAgentDispatcher(serverUrl) { + const parsedUrl = new URL(serverUrl); + const proxyUrl = pm.getProxyUrl(parsedUrl); + if (!(proxyUrl && proxyUrl.hostname)) return; + return this._getProxyAgentDispatcher(parsedUrl, proxyUrl); + } + _prepareRequest(method, requestUrl, headers) { + const info = {}; + info.parsedUrl = requestUrl; + const usingSsl = info.parsedUrl.protocol === "https:"; + info.httpModule = usingSsl ? https : http; + const defaultPort = usingSsl ? 443 : 80; + info.options = {}; + info.options.host = info.parsedUrl.hostname; + info.options.port = info.parsedUrl.port ? parseInt(info.parsedUrl.port) : defaultPort; + info.options.path = (info.parsedUrl.pathname || "") + (info.parsedUrl.search || ""); + info.options.method = method; + info.options.headers = this._mergeHeaders(headers); + if (this.userAgent != null) info.options.headers["user-agent"] = this.userAgent; + info.options.agent = this._getAgent(info.parsedUrl); + if (this.handlers) for (const handler of this.handlers) handler.prepareRequest(info.options); + return info; + } + _mergeHeaders(headers) { + if (this.requestOptions && this.requestOptions.headers) return Object.assign({}, lowercaseKeys(this.requestOptions.headers), lowercaseKeys(headers || {})); + return lowercaseKeys(headers || {}); + } + /** + * Gets an existing header value or returns a default. + * Handles converting number header values to strings since HTTP headers must be strings. + * Note: This returns string | string[] since some headers can have multiple values. + * For headers that must always be a single string (like Content-Type), use the + * specialized _getExistingOrDefaultContentTypeHeader method instead. + */ + _getExistingOrDefaultHeader(additionalHeaders, header, _default) { + let clientHeader; + if (this.requestOptions && this.requestOptions.headers) { + const headerValue = lowercaseKeys(this.requestOptions.headers)[header]; + if (headerValue) clientHeader = typeof headerValue === "number" ? headerValue.toString() : headerValue; + } + const additionalValue = additionalHeaders[header]; + if (additionalValue !== void 0) return typeof additionalValue === "number" ? additionalValue.toString() : additionalValue; + if (clientHeader !== void 0) return clientHeader; + return _default; + } + /** + * Specialized version of _getExistingOrDefaultHeader for Content-Type header. + * Always returns a single string (not an array) since Content-Type should be a single value. + * Converts arrays to comma-separated strings and numbers to strings to ensure type safety. + * This was split from _getExistingOrDefaultHeader to provide stricter typing for callers + * that assign the result to places expecting a string (e.g., additionalHeaders[Headers.ContentType]). + */ + _getExistingOrDefaultContentTypeHeader(additionalHeaders, _default) { + let clientHeader; + if (this.requestOptions && this.requestOptions.headers) { + const headerValue = lowercaseKeys(this.requestOptions.headers)[Headers.ContentType]; + if (headerValue) if (typeof headerValue === "number") clientHeader = String(headerValue); + else if (Array.isArray(headerValue)) clientHeader = headerValue.join(", "); + else clientHeader = headerValue; + } + const additionalValue = additionalHeaders[Headers.ContentType]; + if (additionalValue !== void 0) if (typeof additionalValue === "number") return String(additionalValue); + else if (Array.isArray(additionalValue)) return additionalValue.join(", "); + else return additionalValue; + if (clientHeader !== void 0) return clientHeader; + return _default; + } + _getAgent(parsedUrl) { + let agent; + const proxyUrl = pm.getProxyUrl(parsedUrl); + const useProxy = proxyUrl && proxyUrl.hostname; + if (this._keepAlive && useProxy) agent = this._proxyAgent; + if (!useProxy) agent = this._agent; + if (agent) return agent; + const usingSsl = parsedUrl.protocol === "https:"; + let maxSockets = 100; + if (this.requestOptions) maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets; + if (proxyUrl && proxyUrl.hostname) { + const agentOptions = { + maxSockets, + keepAlive: this._keepAlive, + proxy: Object.assign(Object.assign({}, (proxyUrl.username || proxyUrl.password) && { proxyAuth: `${proxyUrl.username}:${proxyUrl.password}` }), { + host: proxyUrl.hostname, + port: proxyUrl.port + }) + }; + let tunnelAgent; + const overHttps = proxyUrl.protocol === "https:"; + if (usingSsl) tunnelAgent = overHttps ? tunnel.httpsOverHttps : tunnel.httpsOverHttp; + else tunnelAgent = overHttps ? tunnel.httpOverHttps : tunnel.httpOverHttp; + agent = tunnelAgent(agentOptions); + this._proxyAgent = agent; + } + if (!agent) { + const options = { + keepAlive: this._keepAlive, + maxSockets + }; + agent = usingSsl ? new https.Agent(options) : new http.Agent(options); + this._agent = agent; + } + if (usingSsl && this._ignoreSslError) agent.options = Object.assign(agent.options || {}, { rejectUnauthorized: false }); + return agent; + } + _getProxyAgentDispatcher(parsedUrl, proxyUrl) { + let proxyAgent; + if (this._keepAlive) proxyAgent = this._proxyAgentDispatcher; + if (proxyAgent) return proxyAgent; + const usingSsl = parsedUrl.protocol === "https:"; + proxyAgent = new undici_1.ProxyAgent(Object.assign({ + uri: proxyUrl.href, + pipelining: !this._keepAlive ? 0 : 1 + }, (proxyUrl.username || proxyUrl.password) && { token: `Basic ${Buffer.from(`${proxyUrl.username}:${proxyUrl.password}`).toString("base64")}` })); + this._proxyAgentDispatcher = proxyAgent; + if (usingSsl && this._ignoreSslError) proxyAgent.options = Object.assign(proxyAgent.options.requestTls || {}, { rejectUnauthorized: false }); + return proxyAgent; + } + _getUserAgentWithOrchestrationId(userAgent) { + const baseUserAgent = userAgent || "actions/http-client"; + const orchId = process.env["ACTIONS_ORCHESTRATION_ID"]; + if (orchId) return `${baseUserAgent} actions_orchestration_id/${orchId.replace(/[^a-z0-9_.-]/gi, "_")}`; + return baseUserAgent; + } + _performExponentialBackoff(retryNumber) { + return __awaiter(this, void 0, void 0, function* () { + retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber); + const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber); + return new Promise((resolve) => setTimeout(() => resolve(), ms)); + }); + } + _processResponse(res, options) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { + const statusCode = res.message.statusCode || 0; + const response = { + statusCode, + result: null, + headers: {} + }; + if (statusCode === HttpCodes.NotFound) resolve(response); + function dateTimeDeserializer(key, value) { + if (typeof value === "string") { + const a = new Date(value); + if (!isNaN(a.valueOf())) return a; + } + return value; + } + let obj; + let contents; + try { + contents = yield res.readBody(); + if (contents && contents.length > 0) { + if (options && options.deserializeDates) obj = JSON.parse(contents, dateTimeDeserializer); + else obj = JSON.parse(contents); + response.result = obj; + } + response.headers = res.message.headers; + } catch (err) {} + if (statusCode > 299) { + let msg; + if (obj && obj.message) msg = obj.message; + else if (contents && contents.length > 0) msg = contents; + else msg = `Failed request: (${statusCode})`; + const err = new HttpClientError(msg, statusCode); + err.result = response.result; + reject(err); + } else resolve(response); + })); + }); + } + }; + exports.HttpClient = HttpClient; + var lowercaseKeys = (obj) => Object.keys(obj).reduce((c, k) => (c[k.toLowerCase()] = obj[k], c), {}); +})))(), 1); +var __awaiter = function(thisArg, _arguments, P, generator) { + function adopt(value) { + return value instanceof P ? value : new P(function(resolve) { + resolve(value); + }); + } + return new (P || (P = Promise))(function(resolve, reject) { + function fulfilled(value) { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + } + function rejected(value) { + try { + step(generator["throw"](value)); + } catch (e) { + reject(e); + } + } + function step(result) { + result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); + } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +function getAuthString(token, options) { + if (!token && !options.auth) throw new Error("Parameter token or opts.auth is required"); + else if (token && options.auth) throw new Error("Parameters token and opts.auth may not both be specified"); + return typeof options.auth === "string" ? options.auth : `token ${token}`; +} +function getProxyAgent(destinationUrl) { + return new import_lib$1.HttpClient().getAgent(destinationUrl); +} +function getProxyAgentDispatcher(destinationUrl) { + return new import_lib$1.HttpClient().getAgentDispatcher(destinationUrl); +} +function getProxyFetch(destinationUrl) { + const httpDispatcher = getProxyAgentDispatcher(destinationUrl); + const proxyFetch = (url, opts) => __awaiter(this, void 0, void 0, function* () { + return (0, import_undici.fetch)(url, Object.assign(Object.assign({}, opts), { dispatcher: httpDispatcher })); + }); + return proxyFetch; +} +function getApiBaseUrl() { + return process.env["GITHUB_API_URL"] || "https://api.github.com"; +} +function getUserAgentWithOrchestrationId(baseUserAgent) { + var _a; + const orchId = (_a = process.env["ACTIONS_ORCHESTRATION_ID"]) === null || _a === void 0 ? void 0 : _a.trim(); + if (orchId) { + const tag = `actions_orchestration_id/${orchId.replace(/[^a-z0-9_.-]/gi, "_")}`; + if (baseUserAgent === null || baseUserAgent === void 0 ? void 0 : baseUserAgent.includes(tag)) return baseUserAgent; + return `${baseUserAgent ? `${baseUserAgent} ` : ""}${tag}`; + } + return baseUserAgent; +} +//#endregion +//#region node_modules/universal-user-agent/index.js +function getUserAgent() { + if (typeof navigator === "object" && "userAgent" in navigator) return navigator.userAgent; + if (typeof process === "object" && process.version !== void 0) return `Node.js/${process.version.substr(1)} (${process.platform}; ${process.arch})`; + return ""; +} +//#endregion +//#region node_modules/before-after-hook/lib/register.js +function register(state, name, method, options) { + if (typeof method !== "function") throw new Error("method for before hook must be a function"); + if (!options) options = {}; + if (Array.isArray(name)) return name.reverse().reduce((callback, name) => { + return register.bind(null, state, name, callback, options); + }, method)(); + return Promise.resolve().then(() => { + if (!state.registry[name]) return method(options); + return state.registry[name].reduce((method, registered) => { + return registered.hook.bind(null, method, options); + }, method)(); + }); +} +//#endregion +//#region node_modules/before-after-hook/lib/add.js +function addHook(state, kind, name, hook) { + const orig = hook; + if (!state.registry[name]) state.registry[name] = []; + if (kind === "before") hook = (method, options) => { + return Promise.resolve().then(orig.bind(null, options)).then(method.bind(null, options)); + }; + if (kind === "after") hook = (method, options) => { + let result; + return Promise.resolve().then(method.bind(null, options)).then((result_) => { + result = result_; + return orig(result, options); + }).then(() => { + return result; + }); + }; + if (kind === "error") hook = (method, options) => { + return Promise.resolve().then(method.bind(null, options)).catch((error) => { + return orig(error, options); + }); + }; + state.registry[name].push({ + hook, + orig + }); +} +//#endregion +//#region node_modules/before-after-hook/lib/remove.js +function removeHook(state, name, method) { + if (!state.registry[name]) return; + const index = state.registry[name].map((registered) => { + return registered.orig; + }).indexOf(method); + if (index === -1) return; + state.registry[name].splice(index, 1); +} +//#endregion +//#region node_modules/before-after-hook/index.js +var bind = Function.bind; +var bindable = bind.bind(bind); +function bindApi(hook, state, name) { + const removeHookRef = bindable(removeHook, null).apply(null, name ? [state, name] : [state]); + hook.api = { remove: removeHookRef }; + hook.remove = removeHookRef; + [ + "before", + "error", + "after", + "wrap" + ].forEach((kind) => { + const args = name ? [ + state, + kind, + name + ] : [state, kind]; + hook[kind] = hook.api[kind] = bindable(addHook, null).apply(null, args); + }); +} +function Singular() { + const singularHookName = Symbol("Singular"); + const singularHookState = { registry: {} }; + const singularHook = register.bind(null, singularHookState, singularHookName); + bindApi(singularHook, singularHookState, singularHookName); + return singularHook; +} +function Collection() { + const state = { registry: {} }; + const hook = register.bind(null, state); + bindApi(hook, state); + return hook; +} +var before_after_hook_default = { + Singular, + Collection +}; +//#endregion +//#region node_modules/@octokit/endpoint/dist-bundle/index.js +var DEFAULTS = { + method: "GET", + baseUrl: "https://api.github.com", + headers: { + accept: "application/vnd.github.v3+json", + "user-agent": `octokit-endpoint.js/0.0.0-development ${getUserAgent()}` + }, + mediaType: { format: "" } +}; +function lowercaseKeys(object) { + if (!object) return {}; + return Object.keys(object).reduce((newObj, key) => { + newObj[key.toLowerCase()] = object[key]; + return newObj; + }, {}); +} +function isPlainObject$1(value) { + if (typeof value !== "object" || value === null) return false; + if (Object.prototype.toString.call(value) !== "[object Object]") return false; + const proto = Object.getPrototypeOf(value); + if (proto === null) return true; + const Ctor = Object.prototype.hasOwnProperty.call(proto, "constructor") && proto.constructor; + return typeof Ctor === "function" && Ctor instanceof Ctor && Function.prototype.call(Ctor) === Function.prototype.call(value); +} +function mergeDeep(defaults, options) { + const result = Object.assign({}, defaults); + Object.keys(options).forEach((key) => { + if (isPlainObject$1(options[key])) if (!(key in defaults)) Object.assign(result, { [key]: options[key] }); + else result[key] = mergeDeep(defaults[key], options[key]); + else Object.assign(result, { [key]: options[key] }); + }); + return result; +} +function removeUndefinedProperties(obj) { + for (const key in obj) if (obj[key] === void 0) delete obj[key]; + return obj; +} +function merge(defaults, route, options) { + if (typeof route === "string") { + let [method, url] = route.split(" "); + options = Object.assign(url ? { + method, + url + } : { url: method }, options); + } else options = Object.assign({}, route); + options.headers = lowercaseKeys(options.headers); + removeUndefinedProperties(options); + removeUndefinedProperties(options.headers); + const mergedOptions = mergeDeep(defaults || {}, options); + if (options.url === "/graphql") { + if (defaults && defaults.mediaType.previews?.length) mergedOptions.mediaType.previews = defaults.mediaType.previews.filter((preview) => !mergedOptions.mediaType.previews.includes(preview)).concat(mergedOptions.mediaType.previews); + mergedOptions.mediaType.previews = (mergedOptions.mediaType.previews || []).map((preview) => preview.replace(/-preview/, "")); + } + return mergedOptions; +} +function addQueryParameters(url, parameters) { + const separator = /\?/.test(url) ? "&" : "?"; + const names = Object.keys(parameters); + if (names.length === 0) return url; + return url + separator + names.map((name) => { + if (name === "q") return "q=" + parameters.q.split("+").map(encodeURIComponent).join("+"); + return `${name}=${encodeURIComponent(parameters[name])}`; + }).join("&"); +} +var urlVariableRegex = /\{[^{}}]+\}/g; +function removeNonChars(variableName) { + return variableName.replace(/(?:^\W+)|(?:(? a.concat(b), []); +} +function omit(object, keysToOmit) { + const result = { __proto__: null }; + for (const key of Object.keys(object)) if (keysToOmit.indexOf(key) === -1) result[key] = object[key]; + return result; +} +function encodeReserved(str) { + return str.split(/(%[0-9A-Fa-f]{2})/g).map(function(part) { + if (!/%[0-9A-Fa-f]/.test(part)) part = encodeURI(part).replace(/%5B/g, "[").replace(/%5D/g, "]"); + return part; + }).join(""); +} +function encodeUnreserved(str) { + return encodeURIComponent(str).replace(/[!'()*]/g, function(c) { + return "%" + c.charCodeAt(0).toString(16).toUpperCase(); + }); +} +function encodeValue(operator, value, key) { + value = operator === "+" || operator === "#" ? encodeReserved(value) : encodeUnreserved(value); + if (key) return encodeUnreserved(key) + "=" + value; + else return value; +} +function isDefined(value) { + return value !== void 0 && value !== null; +} +function isKeyOperator(operator) { + return operator === ";" || operator === "&" || operator === "?"; +} +function getValues(context, operator, key, modifier) { + var value = context[key], result = []; + if (isDefined(value) && value !== "") if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + value = value.toString(); + if (modifier && modifier !== "*") value = value.substring(0, parseInt(modifier, 10)); + result.push(encodeValue(operator, value, isKeyOperator(operator) ? key : "")); + } else if (modifier === "*") if (Array.isArray(value)) value.filter(isDefined).forEach(function(value2) { + result.push(encodeValue(operator, value2, isKeyOperator(operator) ? key : "")); + }); + else Object.keys(value).forEach(function(k) { + if (isDefined(value[k])) result.push(encodeValue(operator, value[k], k)); + }); + else { + const tmp = []; + if (Array.isArray(value)) value.filter(isDefined).forEach(function(value2) { + tmp.push(encodeValue(operator, value2)); + }); + else Object.keys(value).forEach(function(k) { + if (isDefined(value[k])) { + tmp.push(encodeUnreserved(k)); + tmp.push(encodeValue(operator, value[k].toString())); + } + }); + if (isKeyOperator(operator)) result.push(encodeUnreserved(key) + "=" + tmp.join(",")); + else if (tmp.length !== 0) result.push(tmp.join(",")); + } + else if (operator === ";") { + if (isDefined(value)) result.push(encodeUnreserved(key)); + } else if (value === "" && (operator === "&" || operator === "?")) result.push(encodeUnreserved(key) + "="); + else if (value === "") result.push(""); + return result; +} +function parseUrl(template) { + return { expand: expand.bind(null, template) }; +} +function expand(template, context) { + var operators = [ + "+", + "#", + ".", + "/", + ";", + "?", + "&" + ]; + template = template.replace(/\{([^\{\}]+)\}|([^\{\}]+)/g, function(_, expression, literal) { + if (expression) { + let operator = ""; + const values = []; + if (operators.indexOf(expression.charAt(0)) !== -1) { + operator = expression.charAt(0); + expression = expression.substr(1); + } + expression.split(/,/g).forEach(function(variable) { + var tmp = /([^:\*]*)(?::(\d+)|(\*))?/.exec(variable); + values.push(getValues(context, operator, tmp[1], tmp[2] || tmp[3])); + }); + if (operator && operator !== "+") { + var separator = ","; + if (operator === "?") separator = "&"; + else if (operator !== "#") separator = operator; + return (values.length !== 0 ? operator : "") + values.join(separator); + } else return values.join(","); + } else return encodeReserved(literal); + }); + if (template === "/") return template; + else return template.replace(/\/$/, ""); +} +function parse(options) { + let method = options.method.toUpperCase(); + let url = (options.url || "/").replace(/:([a-z]\w+)/g, "{$1}"); + let headers = Object.assign({}, options.headers); + let body; + let parameters = omit(options, [ + "method", + "baseUrl", + "url", + "headers", + "request", + "mediaType" + ]); + const urlVariableNames = extractUrlVariableNames(url); + url = parseUrl(url).expand(parameters); + if (!/^http/.test(url)) url = options.baseUrl + url; + const remainingParameters = omit(parameters, Object.keys(options).filter((option) => urlVariableNames.includes(option)).concat("baseUrl")); + if (!/application\/octet-stream/i.test(headers.accept)) { + if (options.mediaType.format) headers.accept = headers.accept.split(/,/).map((format) => format.replace(/application\/vnd(\.\w+)(\.v3)?(\.\w+)?(\+json)?$/, `application/vnd$1$2.${options.mediaType.format}`)).join(","); + if (url.endsWith("/graphql")) { + if (options.mediaType.previews?.length) headers.accept = (headers.accept.match(/(? { + return `application/vnd.github.${preview}-preview${options.mediaType.format ? `.${options.mediaType.format}` : "+json"}`; + }).join(","); + } + } + if (["GET", "HEAD"].includes(method)) url = addQueryParameters(url, remainingParameters); + else if ("data" in remainingParameters) body = remainingParameters.data; + else if (Object.keys(remainingParameters).length) body = remainingParameters; + if (!headers["content-type"] && typeof body !== "undefined") headers["content-type"] = "application/json; charset=utf-8"; + if (["PATCH", "PUT"].includes(method) && typeof body === "undefined") body = ""; + return Object.assign({ + method, + url, + headers + }, typeof body !== "undefined" ? { body } : null, options.request ? { request: options.request } : null); +} +function endpointWithDefaults(defaults, route, options) { + return parse(merge(defaults, route, options)); +} +function withDefaults$2(oldDefaults, newDefaults) { + const DEFAULTS2 = merge(oldDefaults, newDefaults); + const endpoint2 = endpointWithDefaults.bind(null, DEFAULTS2); + return Object.assign(endpoint2, { + DEFAULTS: DEFAULTS2, + defaults: withDefaults$2.bind(null, DEFAULTS2), + merge: merge.bind(null, DEFAULTS2), + parse + }); +} +var endpoint = withDefaults$2(null, DEFAULTS); +//#endregion +//#region node_modules/@octokit/request-error/dist-src/index.js +var import_fast_content_type_parse = (/* @__PURE__ */ __commonJSMin(((exports, module) => { + var NullObject = function NullObject() {}; + NullObject.prototype = Object.create(null); + /** + * RegExp to match *( ";" parameter ) in RFC 7231 sec 3.1.1.1 + * + * parameter = token "=" ( token / quoted-string ) + * token = 1*tchar + * tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" + * / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" + * / DIGIT / ALPHA + * ; any VCHAR, except delimiters + * quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE + * qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text + * obs-text = %x80-FF + * quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) + */ + var paramRE = /; *([!#$%&'*+.^\w`|~-]+)=("(?:[\v\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\v\u0020-\u00ff])*"|[!#$%&'*+.^\w`|~-]+) */gu; + /** + * RegExp to match quoted-pair in RFC 7230 sec 3.2.6 + * + * quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) + * obs-text = %x80-FF + */ + var quotedPairRE = /\\([\v\u0020-\u00ff])/gu; + /** + * RegExp to match type in RFC 7231 sec 3.1.1.1 + * + * media-type = type "/" subtype + * type = token + * subtype = token + */ + var mediaTypeRE = /^[!#$%&'*+.^\w|~-]+\/[!#$%&'*+.^\w|~-]+$/u; + var defaultContentType = { + type: "", + parameters: new NullObject() + }; + Object.freeze(defaultContentType.parameters); + Object.freeze(defaultContentType); + /** + * Parse media type to object. + * + * @param {string|object} header + * @return {Object} + * @public + */ + function parse(header) { + if (typeof header !== "string") throw new TypeError("argument header is required and must be a string"); + let index = header.indexOf(";"); + const type = index !== -1 ? header.slice(0, index).trim() : header.trim(); + if (mediaTypeRE.test(type) === false) throw new TypeError("invalid media type"); + const result = { + type: type.toLowerCase(), + parameters: new NullObject() + }; + if (index === -1) return result; + let key; + let match; + let value; + paramRE.lastIndex = index; + while (match = paramRE.exec(header)) { + if (match.index !== index) throw new TypeError("invalid parameter format"); + index += match[0].length; + key = match[1].toLowerCase(); + value = match[2]; + if (value[0] === "\"") { + value = value.slice(1, value.length - 1); + quotedPairRE.test(value) && (value = value.replace(quotedPairRE, "$1")); + } + result.parameters[key] = value; + } + if (index !== header.length) throw new TypeError("invalid parameter format"); + return result; + } + function safeParse(header) { + if (typeof header !== "string") return defaultContentType; + let index = header.indexOf(";"); + const type = index !== -1 ? header.slice(0, index).trim() : header.trim(); + if (mediaTypeRE.test(type) === false) return defaultContentType; + const result = { + type: type.toLowerCase(), + parameters: new NullObject() + }; + if (index === -1) return result; + let key; + let match; + let value; + paramRE.lastIndex = index; + while (match = paramRE.exec(header)) { + if (match.index !== index) return defaultContentType; + index += match[0].length; + key = match[1].toLowerCase(); + value = match[2]; + if (value[0] === "\"") { + value = value.slice(1, value.length - 1); + quotedPairRE.test(value) && (value = value.replace(quotedPairRE, "$1")); + } + result.parameters[key] = value; + } + if (index !== header.length) return defaultContentType; + return result; + } + module.exports.default = { + parse, + safeParse + }; + module.exports.parse = parse; + module.exports.safeParse = safeParse; + module.exports.defaultContentType = defaultContentType; +})))(); +var RequestError = class extends Error { + name; + /** + * http status code + */ + status; + /** + * Request options that lead to the error. + */ + request; + /** + * Response object if a response was received + */ + response; + constructor(message, statusCode, options) { + super(message, { cause: options.cause }); + this.name = "HttpError"; + this.status = Number.parseInt(statusCode); + if (Number.isNaN(this.status)) this.status = 0; + /* v8 ignore else -- @preserve -- Bug with vitest coverage where it sees an else branch that doesn't exist */ + if ("response" in options) this.response = options.response; + const requestCopy = Object.assign({}, options.request); + if (options.request.headers.authorization) requestCopy.headers = Object.assign({}, options.request.headers, { authorization: options.request.headers.authorization.replace(/(? ""; +async function fetchWrapper(requestOptions) { + const fetch = requestOptions.request?.fetch || globalThis.fetch; + if (!fetch) throw new Error("fetch is not set. Please pass a fetch implementation as new Octokit({ request: { fetch }}). Learn more at https://github.com/octokit/octokit.js/#fetch-missing"); + const log = requestOptions.request?.log || console; + const parseSuccessResponseBody = requestOptions.request?.parseSuccessResponseBody !== false; + const body = isPlainObject(requestOptions.body) || Array.isArray(requestOptions.body) ? JSON.stringify(requestOptions.body) : requestOptions.body; + const requestHeaders = Object.fromEntries(Object.entries(requestOptions.headers).map(([name, value]) => [name, String(value)])); + let fetchResponse; + try { + fetchResponse = await fetch(requestOptions.url, { + method: requestOptions.method, + body, + redirect: requestOptions.request?.redirect, + headers: requestHeaders, + signal: requestOptions.request?.signal, + ...requestOptions.body && { duplex: "half" } + }); + } catch (error) { + let message = "Unknown Error"; + if (error instanceof Error) { + if (error.name === "AbortError") { + error.status = 500; + throw error; + } + message = error.message; + if (error.name === "TypeError" && "cause" in error) { + if (error.cause instanceof Error) message = error.cause.message; + else if (typeof error.cause === "string") message = error.cause; + } + } + const requestError = new RequestError(message, 500, { request: requestOptions }); + requestError.cause = error; + throw requestError; + } + const status = fetchResponse.status; + const url = fetchResponse.url; + const responseHeaders = {}; + for (const [key, value] of fetchResponse.headers) responseHeaders[key] = value; + const octokitResponse = { + url, + status, + headers: responseHeaders, + data: "" + }; + if ("deprecation" in responseHeaders) { + const matches = responseHeaders.link && responseHeaders.link.match(/<([^<>]+)>; rel="deprecation"/); + const deprecationLink = matches && matches.pop(); + log.warn(`[@octokit/request] "${requestOptions.method} ${requestOptions.url}" is deprecated. It is scheduled to be removed on ${responseHeaders.sunset}${deprecationLink ? `. See ${deprecationLink}` : ""}`); + } + if (status === 204 || status === 205) return octokitResponse; + if (requestOptions.method === "HEAD") { + if (status < 400) return octokitResponse; + throw new RequestError(fetchResponse.statusText, status, { + response: octokitResponse, + request: requestOptions + }); + } + if (status === 304) { + octokitResponse.data = await getResponseData(fetchResponse); + throw new RequestError("Not modified", status, { + response: octokitResponse, + request: requestOptions + }); + } + if (status >= 400) { + octokitResponse.data = await getResponseData(fetchResponse); + throw new RequestError(toErrorMessage(octokitResponse.data), status, { + response: octokitResponse, + request: requestOptions + }); + } + octokitResponse.data = parseSuccessResponseBody ? await getResponseData(fetchResponse) : fetchResponse.body; + return octokitResponse; +} +async function getResponseData(response) { + const contentType = response.headers.get("content-type"); + if (!contentType) return response.text().catch(noop$1); + const mimetype = (0, import_fast_content_type_parse.safeParse)(contentType); + if (isJSONResponse(mimetype)) { + let text = ""; + try { + text = await response.text(); + return JSON.parse(text); + } catch (err) { + return text; + } + } else if (mimetype.type.startsWith("text/") || mimetype.parameters.charset?.toLowerCase() === "utf-8") return response.text().catch(noop$1); + else return response.arrayBuffer().catch( + /* v8 ignore next -- @preserve */ + () => /* @__PURE__ */ new ArrayBuffer(0) + ); +} +function isJSONResponse(mimetype) { + return mimetype.type === "application/json" || mimetype.type === "application/scim+json"; +} +function toErrorMessage(data) { + if (typeof data === "string") return data; + if (data instanceof ArrayBuffer) return "Unknown error"; + if ("message" in data) { + const suffix = "documentation_url" in data ? ` - ${data.documentation_url}` : ""; + return Array.isArray(data.errors) ? `${data.message}: ${data.errors.map((v) => JSON.stringify(v)).join(", ")}${suffix}` : `${data.message}${suffix}`; + } + return `Unknown error: ${JSON.stringify(data)}`; +} +function withDefaults$1(oldEndpoint, newDefaults) { + const endpoint2 = oldEndpoint.defaults(newDefaults); + const newApi = function(route, parameters) { + const endpointOptions = endpoint2.merge(route, parameters); + if (!endpointOptions.request || !endpointOptions.request.hook) return fetchWrapper(endpoint2.parse(endpointOptions)); + const request2 = (route2, parameters2) => { + return fetchWrapper(endpoint2.parse(endpoint2.merge(route2, parameters2))); + }; + Object.assign(request2, { + endpoint: endpoint2, + defaults: withDefaults$1.bind(null, endpoint2) + }); + return endpointOptions.request.hook(request2, endpointOptions); + }; + return Object.assign(newApi, { + endpoint: endpoint2, + defaults: withDefaults$1.bind(null, endpoint2) + }); +} +var request = withDefaults$1(endpoint, defaults_default); +/* v8 ignore next -- @preserve */ +/* v8 ignore else -- @preserve */ +//#endregion +//#region node_modules/@octokit/graphql/dist-bundle/index.js +var VERSION$4 = "0.0.0-development"; +function _buildMessageForResponseErrors(data) { + return `Request failed due to following response errors: +` + data.errors.map((e) => ` - ${e.message}`).join("\n"); +} +var GraphqlResponseError = class extends Error { + constructor(request2, headers, response) { + super(_buildMessageForResponseErrors(response)); + this.request = request2; + this.headers = headers; + this.response = response; + this.errors = response.errors; + this.data = response.data; + if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor); + } + name = "GraphqlResponseError"; + errors; + data; +}; +var NON_VARIABLE_OPTIONS = [ + "method", + "baseUrl", + "url", + "headers", + "request", + "query", + "mediaType", + "operationName" +]; +var FORBIDDEN_VARIABLE_OPTIONS = [ + "query", + "method", + "url" +]; +var GHES_V3_SUFFIX_REGEX = /\/api\/v3\/?$/; +function graphql(request2, query, options) { + if (options) { + if (typeof query === "string" && "query" in options) return Promise.reject(/* @__PURE__ */ new Error(`[@octokit/graphql] "query" cannot be used as variable name`)); + for (const key in options) { + if (!FORBIDDEN_VARIABLE_OPTIONS.includes(key)) continue; + return Promise.reject(/* @__PURE__ */ new Error(`[@octokit/graphql] "${key}" cannot be used as variable name`)); + } + } + const parsedOptions = typeof query === "string" ? Object.assign({ query }, options) : query; + const requestOptions = Object.keys(parsedOptions).reduce((result, key) => { + if (NON_VARIABLE_OPTIONS.includes(key)) { + result[key] = parsedOptions[key]; + return result; + } + if (!result.variables) result.variables = {}; + result.variables[key] = parsedOptions[key]; + return result; + }, {}); + const baseUrl = parsedOptions.baseUrl || request2.endpoint.DEFAULTS.baseUrl; + if (GHES_V3_SUFFIX_REGEX.test(baseUrl)) requestOptions.url = baseUrl.replace(GHES_V3_SUFFIX_REGEX, "/api/graphql"); + return request2(requestOptions).then((response) => { + if (response.data.errors) { + const headers = {}; + for (const key of Object.keys(response.headers)) headers[key] = response.headers[key]; + throw new GraphqlResponseError(requestOptions, headers, response.data); + } + return response.data.data; + }); +} +function withDefaults(request2, newDefaults) { + const newRequest = request2.defaults(newDefaults); + const newApi = (query, options) => { + return graphql(newRequest, query, options); + }; + return Object.assign(newApi, { + defaults: withDefaults.bind(null, newRequest), + endpoint: newRequest.endpoint + }); +} +withDefaults(request, { + headers: { "user-agent": `octokit-graphql.js/${VERSION$4} ${getUserAgent()}` }, + method: "POST", + url: "/graphql" +}); +function withCustomRequest(customRequest) { + return withDefaults(customRequest, { + method: "POST", + url: "/graphql" + }); +} +//#endregion +//#region node_modules/@octokit/auth-token/dist-bundle/index.js +var b64url = "(?:[a-zA-Z0-9_-]+)"; +var sep = "\\."; +var jwtRE = new RegExp(`^${b64url}${sep}${b64url}${sep}${b64url}$`); +var isJWT = jwtRE.test.bind(jwtRE); +async function auth(token) { + const isApp = isJWT(token); + const isInstallation = token.startsWith("v1.") || token.startsWith("ghs_"); + const isUserToServer = token.startsWith("ghu_"); + return { + type: "token", + token, + tokenType: isApp ? "app" : isInstallation ? "installation" : isUserToServer ? "user-to-server" : "oauth" + }; +} +function withAuthorizationPrefix(token) { + if (token.split(/\./).length === 3) return `bearer ${token}`; + return `token ${token}`; +} +async function hook(token, request, route, parameters) { + const endpoint = request.endpoint.merge(route, parameters); + endpoint.headers.authorization = withAuthorizationPrefix(token); + return request(endpoint); +} +var createTokenAuth = function createTokenAuth2(token) { + if (!token) throw new Error("[@octokit/auth-token] No token passed to createTokenAuth"); + if (typeof token !== "string") throw new Error("[@octokit/auth-token] Token passed to createTokenAuth is not a string"); + token = token.replace(/^(token|bearer) +/i, ""); + return Object.assign(auth.bind(null, token), { hook: hook.bind(null, token) }); +}; +//#endregion +//#region node_modules/@octokit/core/dist-src/version.js +var VERSION$3 = "7.0.6"; +//#endregion +//#region node_modules/@octokit/core/dist-src/index.js +var noop = () => {}; +var consoleWarn = console.warn.bind(console); +var consoleError = console.error.bind(console); +function createLogger(logger = {}) { + if (typeof logger.debug !== "function") logger.debug = noop; + if (typeof logger.info !== "function") logger.info = noop; + if (typeof logger.warn !== "function") logger.warn = consoleWarn; + if (typeof logger.error !== "function") logger.error = consoleError; + return logger; +} +var userAgentTrail = `octokit-core.js/${VERSION$3} ${getUserAgent()}`; +var Octokit = class { + static VERSION = VERSION$3; + static defaults(defaults) { + const OctokitWithDefaults = class extends this { + constructor(...args) { + const options = args[0] || {}; + if (typeof defaults === "function") { + super(defaults(options)); + return; + } + super(Object.assign({}, defaults, options, options.userAgent && defaults.userAgent ? { userAgent: `${options.userAgent} ${defaults.userAgent}` } : null)); + } + }; + return OctokitWithDefaults; + } + static plugins = []; + /** + * Attach a plugin (or many) to your Octokit instance. + * + * @example + * const API = Octokit.plugin(plugin1, plugin2, plugin3, ...) + */ + static plugin(...newPlugins) { + const currentPlugins = this.plugins; + const NewOctokit = class extends this { + static plugins = currentPlugins.concat(newPlugins.filter((plugin) => !currentPlugins.includes(plugin))); + }; + return NewOctokit; + } + constructor(options = {}) { + const hook = new before_after_hook_default.Collection(); + const requestDefaults = { + baseUrl: request.endpoint.DEFAULTS.baseUrl, + headers: {}, + request: Object.assign({}, options.request, { hook: hook.bind(null, "request") }), + mediaType: { + previews: [], + format: "" + } + }; + requestDefaults.headers["user-agent"] = options.userAgent ? `${options.userAgent} ${userAgentTrail}` : userAgentTrail; + if (options.baseUrl) requestDefaults.baseUrl = options.baseUrl; + if (options.previews) requestDefaults.mediaType.previews = options.previews; + if (options.timeZone) requestDefaults.headers["time-zone"] = options.timeZone; + this.request = request.defaults(requestDefaults); + this.graphql = withCustomRequest(this.request).defaults(requestDefaults); + this.log = createLogger(options.log); + this.hook = hook; + if (!options.authStrategy) if (!options.auth) this.auth = async () => ({ type: "unauthenticated" }); + else { + const auth = createTokenAuth(options.auth); + hook.wrap("request", auth.hook); + this.auth = auth; + } + else { + const { authStrategy, ...otherOptions } = options; + const auth = authStrategy(Object.assign({ + request: this.request, + log: this.log, + octokit: this, + octokitOptions: otherOptions + }, options.auth)); + hook.wrap("request", auth.hook); + this.auth = auth; + } + const classConstructor = this.constructor; + for (let i = 0; i < classConstructor.plugins.length; ++i) Object.assign(this, classConstructor.plugins[i](this, options)); + } + request; + graphql; + log; + hook; + auth; +}; +//#endregion +//#region node_modules/@octokit/plugin-rest-endpoint-methods/dist-src/version.js +var VERSION$2 = "17.0.0"; +//#endregion +//#region node_modules/@octokit/plugin-rest-endpoint-methods/dist-src/generated/endpoints.js +var endpoints_default = { + actions: { + addCustomLabelsToSelfHostedRunnerForOrg: ["POST /orgs/{org}/actions/runners/{runner_id}/labels"], + addCustomLabelsToSelfHostedRunnerForRepo: ["POST /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"], + addRepoAccessToSelfHostedRunnerGroupInOrg: ["PUT /orgs/{org}/actions/runner-groups/{runner_group_id}/repositories/{repository_id}"], + addSelectedRepoToOrgSecret: ["PUT /orgs/{org}/actions/secrets/{secret_name}/repositories/{repository_id}"], + addSelectedRepoToOrgVariable: ["PUT /orgs/{org}/actions/variables/{name}/repositories/{repository_id}"], + approveWorkflowRun: ["POST /repos/{owner}/{repo}/actions/runs/{run_id}/approve"], + cancelWorkflowRun: ["POST /repos/{owner}/{repo}/actions/runs/{run_id}/cancel"], + createEnvironmentVariable: ["POST /repos/{owner}/{repo}/environments/{environment_name}/variables"], + createHostedRunnerForOrg: ["POST /orgs/{org}/actions/hosted-runners"], + createOrUpdateEnvironmentSecret: ["PUT /repos/{owner}/{repo}/environments/{environment_name}/secrets/{secret_name}"], + createOrUpdateOrgSecret: ["PUT /orgs/{org}/actions/secrets/{secret_name}"], + createOrUpdateRepoSecret: ["PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}"], + createOrgVariable: ["POST /orgs/{org}/actions/variables"], + createRegistrationTokenForOrg: ["POST /orgs/{org}/actions/runners/registration-token"], + createRegistrationTokenForRepo: ["POST /repos/{owner}/{repo}/actions/runners/registration-token"], + createRemoveTokenForOrg: ["POST /orgs/{org}/actions/runners/remove-token"], + createRemoveTokenForRepo: ["POST /repos/{owner}/{repo}/actions/runners/remove-token"], + createRepoVariable: ["POST /repos/{owner}/{repo}/actions/variables"], + createWorkflowDispatch: ["POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches"], + deleteActionsCacheById: ["DELETE /repos/{owner}/{repo}/actions/caches/{cache_id}"], + deleteActionsCacheByKey: ["DELETE /repos/{owner}/{repo}/actions/caches{?key,ref}"], + deleteArtifact: ["DELETE /repos/{owner}/{repo}/actions/artifacts/{artifact_id}"], + deleteCustomImageFromOrg: ["DELETE /orgs/{org}/actions/hosted-runners/images/custom/{image_definition_id}"], + deleteCustomImageVersionFromOrg: ["DELETE /orgs/{org}/actions/hosted-runners/images/custom/{image_definition_id}/versions/{version}"], + deleteEnvironmentSecret: ["DELETE /repos/{owner}/{repo}/environments/{environment_name}/secrets/{secret_name}"], + deleteEnvironmentVariable: ["DELETE /repos/{owner}/{repo}/environments/{environment_name}/variables/{name}"], + deleteHostedRunnerForOrg: ["DELETE /orgs/{org}/actions/hosted-runners/{hosted_runner_id}"], + deleteOrgSecret: ["DELETE /orgs/{org}/actions/secrets/{secret_name}"], + deleteOrgVariable: ["DELETE /orgs/{org}/actions/variables/{name}"], + deleteRepoSecret: ["DELETE /repos/{owner}/{repo}/actions/secrets/{secret_name}"], + deleteRepoVariable: ["DELETE /repos/{owner}/{repo}/actions/variables/{name}"], + deleteSelfHostedRunnerFromOrg: ["DELETE /orgs/{org}/actions/runners/{runner_id}"], + deleteSelfHostedRunnerFromRepo: ["DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}"], + deleteWorkflowRun: ["DELETE /repos/{owner}/{repo}/actions/runs/{run_id}"], + deleteWorkflowRunLogs: ["DELETE /repos/{owner}/{repo}/actions/runs/{run_id}/logs"], + disableSelectedRepositoryGithubActionsOrganization: ["DELETE /orgs/{org}/actions/permissions/repositories/{repository_id}"], + disableWorkflow: ["PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/disable"], + downloadArtifact: ["GET /repos/{owner}/{repo}/actions/artifacts/{artifact_id}/{archive_format}"], + downloadJobLogsForWorkflowRun: ["GET /repos/{owner}/{repo}/actions/jobs/{job_id}/logs"], + downloadWorkflowRunAttemptLogs: ["GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/logs"], + downloadWorkflowRunLogs: ["GET /repos/{owner}/{repo}/actions/runs/{run_id}/logs"], + enableSelectedRepositoryGithubActionsOrganization: ["PUT /orgs/{org}/actions/permissions/repositories/{repository_id}"], + enableWorkflow: ["PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/enable"], + forceCancelWorkflowRun: ["POST /repos/{owner}/{repo}/actions/runs/{run_id}/force-cancel"], + generateRunnerJitconfigForOrg: ["POST /orgs/{org}/actions/runners/generate-jitconfig"], + generateRunnerJitconfigForRepo: ["POST /repos/{owner}/{repo}/actions/runners/generate-jitconfig"], + getActionsCacheList: ["GET /repos/{owner}/{repo}/actions/caches"], + getActionsCacheUsage: ["GET /repos/{owner}/{repo}/actions/cache/usage"], + getActionsCacheUsageByRepoForOrg: ["GET /orgs/{org}/actions/cache/usage-by-repository"], + getActionsCacheUsageForOrg: ["GET /orgs/{org}/actions/cache/usage"], + getAllowedActionsOrganization: ["GET /orgs/{org}/actions/permissions/selected-actions"], + getAllowedActionsRepository: ["GET /repos/{owner}/{repo}/actions/permissions/selected-actions"], + getArtifact: ["GET /repos/{owner}/{repo}/actions/artifacts/{artifact_id}"], + getCustomImageForOrg: ["GET /orgs/{org}/actions/hosted-runners/images/custom/{image_definition_id}"], + getCustomImageVersionForOrg: ["GET /orgs/{org}/actions/hosted-runners/images/custom/{image_definition_id}/versions/{version}"], + getCustomOidcSubClaimForRepo: ["GET /repos/{owner}/{repo}/actions/oidc/customization/sub"], + getEnvironmentPublicKey: ["GET /repos/{owner}/{repo}/environments/{environment_name}/secrets/public-key"], + getEnvironmentSecret: ["GET /repos/{owner}/{repo}/environments/{environment_name}/secrets/{secret_name}"], + getEnvironmentVariable: ["GET /repos/{owner}/{repo}/environments/{environment_name}/variables/{name}"], + getGithubActionsDefaultWorkflowPermissionsOrganization: ["GET /orgs/{org}/actions/permissions/workflow"], + getGithubActionsDefaultWorkflowPermissionsRepository: ["GET /repos/{owner}/{repo}/actions/permissions/workflow"], + getGithubActionsPermissionsOrganization: ["GET /orgs/{org}/actions/permissions"], + getGithubActionsPermissionsRepository: ["GET /repos/{owner}/{repo}/actions/permissions"], + getHostedRunnerForOrg: ["GET /orgs/{org}/actions/hosted-runners/{hosted_runner_id}"], + getHostedRunnersGithubOwnedImagesForOrg: ["GET /orgs/{org}/actions/hosted-runners/images/github-owned"], + getHostedRunnersLimitsForOrg: ["GET /orgs/{org}/actions/hosted-runners/limits"], + getHostedRunnersMachineSpecsForOrg: ["GET /orgs/{org}/actions/hosted-runners/machine-sizes"], + getHostedRunnersPartnerImagesForOrg: ["GET /orgs/{org}/actions/hosted-runners/images/partner"], + getHostedRunnersPlatformsForOrg: ["GET /orgs/{org}/actions/hosted-runners/platforms"], + getJobForWorkflowRun: ["GET /repos/{owner}/{repo}/actions/jobs/{job_id}"], + getOrgPublicKey: ["GET /orgs/{org}/actions/secrets/public-key"], + getOrgSecret: ["GET /orgs/{org}/actions/secrets/{secret_name}"], + getOrgVariable: ["GET /orgs/{org}/actions/variables/{name}"], + getPendingDeploymentsForRun: ["GET /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments"], + getRepoPermissions: [ + "GET /repos/{owner}/{repo}/actions/permissions", + {}, + { renamed: ["actions", "getGithubActionsPermissionsRepository"] } + ], + getRepoPublicKey: ["GET /repos/{owner}/{repo}/actions/secrets/public-key"], + getRepoSecret: ["GET /repos/{owner}/{repo}/actions/secrets/{secret_name}"], + getRepoVariable: ["GET /repos/{owner}/{repo}/actions/variables/{name}"], + getReviewsForRun: ["GET /repos/{owner}/{repo}/actions/runs/{run_id}/approvals"], + getSelfHostedRunnerForOrg: ["GET /orgs/{org}/actions/runners/{runner_id}"], + getSelfHostedRunnerForRepo: ["GET /repos/{owner}/{repo}/actions/runners/{runner_id}"], + getWorkflow: ["GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}"], + getWorkflowAccessToRepository: ["GET /repos/{owner}/{repo}/actions/permissions/access"], + getWorkflowRun: ["GET /repos/{owner}/{repo}/actions/runs/{run_id}"], + getWorkflowRunAttempt: ["GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}"], + getWorkflowRunUsage: ["GET /repos/{owner}/{repo}/actions/runs/{run_id}/timing"], + getWorkflowUsage: ["GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/timing"], + listArtifactsForRepo: ["GET /repos/{owner}/{repo}/actions/artifacts"], + listCustomImageVersionsForOrg: ["GET /orgs/{org}/actions/hosted-runners/images/custom/{image_definition_id}/versions"], + listCustomImagesForOrg: ["GET /orgs/{org}/actions/hosted-runners/images/custom"], + listEnvironmentSecrets: ["GET /repos/{owner}/{repo}/environments/{environment_name}/secrets"], + listEnvironmentVariables: ["GET /repos/{owner}/{repo}/environments/{environment_name}/variables"], + listGithubHostedRunnersInGroupForOrg: ["GET /orgs/{org}/actions/runner-groups/{runner_group_id}/hosted-runners"], + listHostedRunnersForOrg: ["GET /orgs/{org}/actions/hosted-runners"], + listJobsForWorkflowRun: ["GET /repos/{owner}/{repo}/actions/runs/{run_id}/jobs"], + listJobsForWorkflowRunAttempt: ["GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/jobs"], + listLabelsForSelfHostedRunnerForOrg: ["GET /orgs/{org}/actions/runners/{runner_id}/labels"], + listLabelsForSelfHostedRunnerForRepo: ["GET /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"], + listOrgSecrets: ["GET /orgs/{org}/actions/secrets"], + listOrgVariables: ["GET /orgs/{org}/actions/variables"], + listRepoOrganizationSecrets: ["GET /repos/{owner}/{repo}/actions/organization-secrets"], + listRepoOrganizationVariables: ["GET /repos/{owner}/{repo}/actions/organization-variables"], + listRepoSecrets: ["GET /repos/{owner}/{repo}/actions/secrets"], + listRepoVariables: ["GET /repos/{owner}/{repo}/actions/variables"], + listRepoWorkflows: ["GET /repos/{owner}/{repo}/actions/workflows"], + listRunnerApplicationsForOrg: ["GET /orgs/{org}/actions/runners/downloads"], + listRunnerApplicationsForRepo: ["GET /repos/{owner}/{repo}/actions/runners/downloads"], + listSelectedReposForOrgSecret: ["GET /orgs/{org}/actions/secrets/{secret_name}/repositories"], + listSelectedReposForOrgVariable: ["GET /orgs/{org}/actions/variables/{name}/repositories"], + listSelectedRepositoriesEnabledGithubActionsOrganization: ["GET /orgs/{org}/actions/permissions/repositories"], + listSelfHostedRunnersForOrg: ["GET /orgs/{org}/actions/runners"], + listSelfHostedRunnersForRepo: ["GET /repos/{owner}/{repo}/actions/runners"], + listWorkflowRunArtifacts: ["GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts"], + listWorkflowRuns: ["GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs"], + listWorkflowRunsForRepo: ["GET /repos/{owner}/{repo}/actions/runs"], + reRunJobForWorkflowRun: ["POST /repos/{owner}/{repo}/actions/jobs/{job_id}/rerun"], + reRunWorkflow: ["POST /repos/{owner}/{repo}/actions/runs/{run_id}/rerun"], + reRunWorkflowFailedJobs: ["POST /repos/{owner}/{repo}/actions/runs/{run_id}/rerun-failed-jobs"], + removeAllCustomLabelsFromSelfHostedRunnerForOrg: ["DELETE /orgs/{org}/actions/runners/{runner_id}/labels"], + removeAllCustomLabelsFromSelfHostedRunnerForRepo: ["DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"], + removeCustomLabelFromSelfHostedRunnerForOrg: ["DELETE /orgs/{org}/actions/runners/{runner_id}/labels/{name}"], + removeCustomLabelFromSelfHostedRunnerForRepo: ["DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}/labels/{name}"], + removeSelectedRepoFromOrgSecret: ["DELETE /orgs/{org}/actions/secrets/{secret_name}/repositories/{repository_id}"], + removeSelectedRepoFromOrgVariable: ["DELETE /orgs/{org}/actions/variables/{name}/repositories/{repository_id}"], + reviewCustomGatesForRun: ["POST /repos/{owner}/{repo}/actions/runs/{run_id}/deployment_protection_rule"], + reviewPendingDeploymentsForRun: ["POST /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments"], + setAllowedActionsOrganization: ["PUT /orgs/{org}/actions/permissions/selected-actions"], + setAllowedActionsRepository: ["PUT /repos/{owner}/{repo}/actions/permissions/selected-actions"], + setCustomLabelsForSelfHostedRunnerForOrg: ["PUT /orgs/{org}/actions/runners/{runner_id}/labels"], + setCustomLabelsForSelfHostedRunnerForRepo: ["PUT /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"], + setCustomOidcSubClaimForRepo: ["PUT /repos/{owner}/{repo}/actions/oidc/customization/sub"], + setGithubActionsDefaultWorkflowPermissionsOrganization: ["PUT /orgs/{org}/actions/permissions/workflow"], + setGithubActionsDefaultWorkflowPermissionsRepository: ["PUT /repos/{owner}/{repo}/actions/permissions/workflow"], + setGithubActionsPermissionsOrganization: ["PUT /orgs/{org}/actions/permissions"], + setGithubActionsPermissionsRepository: ["PUT /repos/{owner}/{repo}/actions/permissions"], + setSelectedReposForOrgSecret: ["PUT /orgs/{org}/actions/secrets/{secret_name}/repositories"], + setSelectedReposForOrgVariable: ["PUT /orgs/{org}/actions/variables/{name}/repositories"], + setSelectedRepositoriesEnabledGithubActionsOrganization: ["PUT /orgs/{org}/actions/permissions/repositories"], + setWorkflowAccessToRepository: ["PUT /repos/{owner}/{repo}/actions/permissions/access"], + updateEnvironmentVariable: ["PATCH /repos/{owner}/{repo}/environments/{environment_name}/variables/{name}"], + updateHostedRunnerForOrg: ["PATCH /orgs/{org}/actions/hosted-runners/{hosted_runner_id}"], + updateOrgVariable: ["PATCH /orgs/{org}/actions/variables/{name}"], + updateRepoVariable: ["PATCH /repos/{owner}/{repo}/actions/variables/{name}"] + }, + activity: { + checkRepoIsStarredByAuthenticatedUser: ["GET /user/starred/{owner}/{repo}"], + deleteRepoSubscription: ["DELETE /repos/{owner}/{repo}/subscription"], + deleteThreadSubscription: ["DELETE /notifications/threads/{thread_id}/subscription"], + getFeeds: ["GET /feeds"], + getRepoSubscription: ["GET /repos/{owner}/{repo}/subscription"], + getThread: ["GET /notifications/threads/{thread_id}"], + getThreadSubscriptionForAuthenticatedUser: ["GET /notifications/threads/{thread_id}/subscription"], + listEventsForAuthenticatedUser: ["GET /users/{username}/events"], + listNotificationsForAuthenticatedUser: ["GET /notifications"], + listOrgEventsForAuthenticatedUser: ["GET /users/{username}/events/orgs/{org}"], + listPublicEvents: ["GET /events"], + listPublicEventsForRepoNetwork: ["GET /networks/{owner}/{repo}/events"], + listPublicEventsForUser: ["GET /users/{username}/events/public"], + listPublicOrgEvents: ["GET /orgs/{org}/events"], + listReceivedEventsForUser: ["GET /users/{username}/received_events"], + listReceivedPublicEventsForUser: ["GET /users/{username}/received_events/public"], + listRepoEvents: ["GET /repos/{owner}/{repo}/events"], + listRepoNotificationsForAuthenticatedUser: ["GET /repos/{owner}/{repo}/notifications"], + listReposStarredByAuthenticatedUser: ["GET /user/starred"], + listReposStarredByUser: ["GET /users/{username}/starred"], + listReposWatchedByUser: ["GET /users/{username}/subscriptions"], + listStargazersForRepo: ["GET /repos/{owner}/{repo}/stargazers"], + listWatchedReposForAuthenticatedUser: ["GET /user/subscriptions"], + listWatchersForRepo: ["GET /repos/{owner}/{repo}/subscribers"], + markNotificationsAsRead: ["PUT /notifications"], + markRepoNotificationsAsRead: ["PUT /repos/{owner}/{repo}/notifications"], + markThreadAsDone: ["DELETE /notifications/threads/{thread_id}"], + markThreadAsRead: ["PATCH /notifications/threads/{thread_id}"], + setRepoSubscription: ["PUT /repos/{owner}/{repo}/subscription"], + setThreadSubscription: ["PUT /notifications/threads/{thread_id}/subscription"], + starRepoForAuthenticatedUser: ["PUT /user/starred/{owner}/{repo}"], + unstarRepoForAuthenticatedUser: ["DELETE /user/starred/{owner}/{repo}"] + }, + apps: { + addRepoToInstallation: [ + "PUT /user/installations/{installation_id}/repositories/{repository_id}", + {}, + { renamed: ["apps", "addRepoToInstallationForAuthenticatedUser"] } + ], + addRepoToInstallationForAuthenticatedUser: ["PUT /user/installations/{installation_id}/repositories/{repository_id}"], + checkToken: ["POST /applications/{client_id}/token"], + createFromManifest: ["POST /app-manifests/{code}/conversions"], + createInstallationAccessToken: ["POST /app/installations/{installation_id}/access_tokens"], + deleteAuthorization: ["DELETE /applications/{client_id}/grant"], + deleteInstallation: ["DELETE /app/installations/{installation_id}"], + deleteToken: ["DELETE /applications/{client_id}/token"], + getAuthenticated: ["GET /app"], + getBySlug: ["GET /apps/{app_slug}"], + getInstallation: ["GET /app/installations/{installation_id}"], + getOrgInstallation: ["GET /orgs/{org}/installation"], + getRepoInstallation: ["GET /repos/{owner}/{repo}/installation"], + getSubscriptionPlanForAccount: ["GET /marketplace_listing/accounts/{account_id}"], + getSubscriptionPlanForAccountStubbed: ["GET /marketplace_listing/stubbed/accounts/{account_id}"], + getUserInstallation: ["GET /users/{username}/installation"], + getWebhookConfigForApp: ["GET /app/hook/config"], + getWebhookDelivery: ["GET /app/hook/deliveries/{delivery_id}"], + listAccountsForPlan: ["GET /marketplace_listing/plans/{plan_id}/accounts"], + listAccountsForPlanStubbed: ["GET /marketplace_listing/stubbed/plans/{plan_id}/accounts"], + listInstallationReposForAuthenticatedUser: ["GET /user/installations/{installation_id}/repositories"], + listInstallationRequestsForAuthenticatedApp: ["GET /app/installation-requests"], + listInstallations: ["GET /app/installations"], + listInstallationsForAuthenticatedUser: ["GET /user/installations"], + listPlans: ["GET /marketplace_listing/plans"], + listPlansStubbed: ["GET /marketplace_listing/stubbed/plans"], + listReposAccessibleToInstallation: ["GET /installation/repositories"], + listSubscriptionsForAuthenticatedUser: ["GET /user/marketplace_purchases"], + listSubscriptionsForAuthenticatedUserStubbed: ["GET /user/marketplace_purchases/stubbed"], + listWebhookDeliveries: ["GET /app/hook/deliveries"], + redeliverWebhookDelivery: ["POST /app/hook/deliveries/{delivery_id}/attempts"], + removeRepoFromInstallation: [ + "DELETE /user/installations/{installation_id}/repositories/{repository_id}", + {}, + { renamed: ["apps", "removeRepoFromInstallationForAuthenticatedUser"] } + ], + removeRepoFromInstallationForAuthenticatedUser: ["DELETE /user/installations/{installation_id}/repositories/{repository_id}"], + resetToken: ["PATCH /applications/{client_id}/token"], + revokeInstallationAccessToken: ["DELETE /installation/token"], + scopeToken: ["POST /applications/{client_id}/token/scoped"], + suspendInstallation: ["PUT /app/installations/{installation_id}/suspended"], + unsuspendInstallation: ["DELETE /app/installations/{installation_id}/suspended"], + updateWebhookConfigForApp: ["PATCH /app/hook/config"] + }, + billing: { + getGithubActionsBillingOrg: ["GET /orgs/{org}/settings/billing/actions"], + getGithubActionsBillingUser: ["GET /users/{username}/settings/billing/actions"], + getGithubBillingPremiumRequestUsageReportOrg: ["GET /organizations/{org}/settings/billing/premium_request/usage"], + getGithubBillingPremiumRequestUsageReportUser: ["GET /users/{username}/settings/billing/premium_request/usage"], + getGithubBillingUsageReportOrg: ["GET /organizations/{org}/settings/billing/usage"], + getGithubBillingUsageReportUser: ["GET /users/{username}/settings/billing/usage"], + getGithubPackagesBillingOrg: ["GET /orgs/{org}/settings/billing/packages"], + getGithubPackagesBillingUser: ["GET /users/{username}/settings/billing/packages"], + getSharedStorageBillingOrg: ["GET /orgs/{org}/settings/billing/shared-storage"], + getSharedStorageBillingUser: ["GET /users/{username}/settings/billing/shared-storage"] + }, + campaigns: { + createCampaign: ["POST /orgs/{org}/campaigns"], + deleteCampaign: ["DELETE /orgs/{org}/campaigns/{campaign_number}"], + getCampaignSummary: ["GET /orgs/{org}/campaigns/{campaign_number}"], + listOrgCampaigns: ["GET /orgs/{org}/campaigns"], + updateCampaign: ["PATCH /orgs/{org}/campaigns/{campaign_number}"] + }, + checks: { + create: ["POST /repos/{owner}/{repo}/check-runs"], + createSuite: ["POST /repos/{owner}/{repo}/check-suites"], + get: ["GET /repos/{owner}/{repo}/check-runs/{check_run_id}"], + getSuite: ["GET /repos/{owner}/{repo}/check-suites/{check_suite_id}"], + listAnnotations: ["GET /repos/{owner}/{repo}/check-runs/{check_run_id}/annotations"], + listForRef: ["GET /repos/{owner}/{repo}/commits/{ref}/check-runs"], + listForSuite: ["GET /repos/{owner}/{repo}/check-suites/{check_suite_id}/check-runs"], + listSuitesForRef: ["GET /repos/{owner}/{repo}/commits/{ref}/check-suites"], + rerequestRun: ["POST /repos/{owner}/{repo}/check-runs/{check_run_id}/rerequest"], + rerequestSuite: ["POST /repos/{owner}/{repo}/check-suites/{check_suite_id}/rerequest"], + setSuitesPreferences: ["PATCH /repos/{owner}/{repo}/check-suites/preferences"], + update: ["PATCH /repos/{owner}/{repo}/check-runs/{check_run_id}"] + }, + codeScanning: { + commitAutofix: ["POST /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/autofix/commits"], + createAutofix: ["POST /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/autofix"], + createVariantAnalysis: ["POST /repos/{owner}/{repo}/code-scanning/codeql/variant-analyses"], + deleteAnalysis: ["DELETE /repos/{owner}/{repo}/code-scanning/analyses/{analysis_id}{?confirm_delete}"], + deleteCodeqlDatabase: ["DELETE /repos/{owner}/{repo}/code-scanning/codeql/databases/{language}"], + getAlert: [ + "GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}", + {}, + { renamedParameters: { alert_id: "alert_number" } } + ], + getAnalysis: ["GET /repos/{owner}/{repo}/code-scanning/analyses/{analysis_id}"], + getAutofix: ["GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/autofix"], + getCodeqlDatabase: ["GET /repos/{owner}/{repo}/code-scanning/codeql/databases/{language}"], + getDefaultSetup: ["GET /repos/{owner}/{repo}/code-scanning/default-setup"], + getSarif: ["GET /repos/{owner}/{repo}/code-scanning/sarifs/{sarif_id}"], + getVariantAnalysis: ["GET /repos/{owner}/{repo}/code-scanning/codeql/variant-analyses/{codeql_variant_analysis_id}"], + getVariantAnalysisRepoTask: ["GET /repos/{owner}/{repo}/code-scanning/codeql/variant-analyses/{codeql_variant_analysis_id}/repos/{repo_owner}/{repo_name}"], + listAlertInstances: ["GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/instances"], + listAlertsForOrg: ["GET /orgs/{org}/code-scanning/alerts"], + listAlertsForRepo: ["GET /repos/{owner}/{repo}/code-scanning/alerts"], + listAlertsInstances: [ + "GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/instances", + {}, + { renamed: ["codeScanning", "listAlertInstances"] } + ], + listCodeqlDatabases: ["GET /repos/{owner}/{repo}/code-scanning/codeql/databases"], + listRecentAnalyses: ["GET /repos/{owner}/{repo}/code-scanning/analyses"], + updateAlert: ["PATCH /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}"], + updateDefaultSetup: ["PATCH /repos/{owner}/{repo}/code-scanning/default-setup"], + uploadSarif: ["POST /repos/{owner}/{repo}/code-scanning/sarifs"] + }, + codeSecurity: { + attachConfiguration: ["POST /orgs/{org}/code-security/configurations/{configuration_id}/attach"], + attachEnterpriseConfiguration: ["POST /enterprises/{enterprise}/code-security/configurations/{configuration_id}/attach"], + createConfiguration: ["POST /orgs/{org}/code-security/configurations"], + createConfigurationForEnterprise: ["POST /enterprises/{enterprise}/code-security/configurations"], + deleteConfiguration: ["DELETE /orgs/{org}/code-security/configurations/{configuration_id}"], + deleteConfigurationForEnterprise: ["DELETE /enterprises/{enterprise}/code-security/configurations/{configuration_id}"], + detachConfiguration: ["DELETE /orgs/{org}/code-security/configurations/detach"], + getConfiguration: ["GET /orgs/{org}/code-security/configurations/{configuration_id}"], + getConfigurationForRepository: ["GET /repos/{owner}/{repo}/code-security-configuration"], + getConfigurationsForEnterprise: ["GET /enterprises/{enterprise}/code-security/configurations"], + getConfigurationsForOrg: ["GET /orgs/{org}/code-security/configurations"], + getDefaultConfigurations: ["GET /orgs/{org}/code-security/configurations/defaults"], + getDefaultConfigurationsForEnterprise: ["GET /enterprises/{enterprise}/code-security/configurations/defaults"], + getRepositoriesForConfiguration: ["GET /orgs/{org}/code-security/configurations/{configuration_id}/repositories"], + getRepositoriesForEnterpriseConfiguration: ["GET /enterprises/{enterprise}/code-security/configurations/{configuration_id}/repositories"], + getSingleConfigurationForEnterprise: ["GET /enterprises/{enterprise}/code-security/configurations/{configuration_id}"], + setConfigurationAsDefault: ["PUT /orgs/{org}/code-security/configurations/{configuration_id}/defaults"], + setConfigurationAsDefaultForEnterprise: ["PUT /enterprises/{enterprise}/code-security/configurations/{configuration_id}/defaults"], + updateConfiguration: ["PATCH /orgs/{org}/code-security/configurations/{configuration_id}"], + updateEnterpriseConfiguration: ["PATCH /enterprises/{enterprise}/code-security/configurations/{configuration_id}"] + }, + codesOfConduct: { + getAllCodesOfConduct: ["GET /codes_of_conduct"], + getConductCode: ["GET /codes_of_conduct/{key}"] + }, + codespaces: { + addRepositoryForSecretForAuthenticatedUser: ["PUT /user/codespaces/secrets/{secret_name}/repositories/{repository_id}"], + addSelectedRepoToOrgSecret: ["PUT /orgs/{org}/codespaces/secrets/{secret_name}/repositories/{repository_id}"], + checkPermissionsForDevcontainer: ["GET /repos/{owner}/{repo}/codespaces/permissions_check"], + codespaceMachinesForAuthenticatedUser: ["GET /user/codespaces/{codespace_name}/machines"], + createForAuthenticatedUser: ["POST /user/codespaces"], + createOrUpdateOrgSecret: ["PUT /orgs/{org}/codespaces/secrets/{secret_name}"], + createOrUpdateRepoSecret: ["PUT /repos/{owner}/{repo}/codespaces/secrets/{secret_name}"], + createOrUpdateSecretForAuthenticatedUser: ["PUT /user/codespaces/secrets/{secret_name}"], + createWithPrForAuthenticatedUser: ["POST /repos/{owner}/{repo}/pulls/{pull_number}/codespaces"], + createWithRepoForAuthenticatedUser: ["POST /repos/{owner}/{repo}/codespaces"], + deleteForAuthenticatedUser: ["DELETE /user/codespaces/{codespace_name}"], + deleteFromOrganization: ["DELETE /orgs/{org}/members/{username}/codespaces/{codespace_name}"], + deleteOrgSecret: ["DELETE /orgs/{org}/codespaces/secrets/{secret_name}"], + deleteRepoSecret: ["DELETE /repos/{owner}/{repo}/codespaces/secrets/{secret_name}"], + deleteSecretForAuthenticatedUser: ["DELETE /user/codespaces/secrets/{secret_name}"], + exportForAuthenticatedUser: ["POST /user/codespaces/{codespace_name}/exports"], + getCodespacesForUserInOrg: ["GET /orgs/{org}/members/{username}/codespaces"], + getExportDetailsForAuthenticatedUser: ["GET /user/codespaces/{codespace_name}/exports/{export_id}"], + getForAuthenticatedUser: ["GET /user/codespaces/{codespace_name}"], + getOrgPublicKey: ["GET /orgs/{org}/codespaces/secrets/public-key"], + getOrgSecret: ["GET /orgs/{org}/codespaces/secrets/{secret_name}"], + getPublicKeyForAuthenticatedUser: ["GET /user/codespaces/secrets/public-key"], + getRepoPublicKey: ["GET /repos/{owner}/{repo}/codespaces/secrets/public-key"], + getRepoSecret: ["GET /repos/{owner}/{repo}/codespaces/secrets/{secret_name}"], + getSecretForAuthenticatedUser: ["GET /user/codespaces/secrets/{secret_name}"], + listDevcontainersInRepositoryForAuthenticatedUser: ["GET /repos/{owner}/{repo}/codespaces/devcontainers"], + listForAuthenticatedUser: ["GET /user/codespaces"], + listInOrganization: [ + "GET /orgs/{org}/codespaces", + {}, + { renamedParameters: { org_id: "org" } } + ], + listInRepositoryForAuthenticatedUser: ["GET /repos/{owner}/{repo}/codespaces"], + listOrgSecrets: ["GET /orgs/{org}/codespaces/secrets"], + listRepoSecrets: ["GET /repos/{owner}/{repo}/codespaces/secrets"], + listRepositoriesForSecretForAuthenticatedUser: ["GET /user/codespaces/secrets/{secret_name}/repositories"], + listSecretsForAuthenticatedUser: ["GET /user/codespaces/secrets"], + listSelectedReposForOrgSecret: ["GET /orgs/{org}/codespaces/secrets/{secret_name}/repositories"], + preFlightWithRepoForAuthenticatedUser: ["GET /repos/{owner}/{repo}/codespaces/new"], + publishForAuthenticatedUser: ["POST /user/codespaces/{codespace_name}/publish"], + removeRepositoryForSecretForAuthenticatedUser: ["DELETE /user/codespaces/secrets/{secret_name}/repositories/{repository_id}"], + removeSelectedRepoFromOrgSecret: ["DELETE /orgs/{org}/codespaces/secrets/{secret_name}/repositories/{repository_id}"], + repoMachinesForAuthenticatedUser: ["GET /repos/{owner}/{repo}/codespaces/machines"], + setRepositoriesForSecretForAuthenticatedUser: ["PUT /user/codespaces/secrets/{secret_name}/repositories"], + setSelectedReposForOrgSecret: ["PUT /orgs/{org}/codespaces/secrets/{secret_name}/repositories"], + startForAuthenticatedUser: ["POST /user/codespaces/{codespace_name}/start"], + stopForAuthenticatedUser: ["POST /user/codespaces/{codespace_name}/stop"], + stopInOrganization: ["POST /orgs/{org}/members/{username}/codespaces/{codespace_name}/stop"], + updateForAuthenticatedUser: ["PATCH /user/codespaces/{codespace_name}"] + }, + copilot: { + addCopilotSeatsForTeams: ["POST /orgs/{org}/copilot/billing/selected_teams"], + addCopilotSeatsForUsers: ["POST /orgs/{org}/copilot/billing/selected_users"], + cancelCopilotSeatAssignmentForTeams: ["DELETE /orgs/{org}/copilot/billing/selected_teams"], + cancelCopilotSeatAssignmentForUsers: ["DELETE /orgs/{org}/copilot/billing/selected_users"], + copilotMetricsForOrganization: ["GET /orgs/{org}/copilot/metrics"], + copilotMetricsForTeam: ["GET /orgs/{org}/team/{team_slug}/copilot/metrics"], + getCopilotOrganizationDetails: ["GET /orgs/{org}/copilot/billing"], + getCopilotSeatDetailsForUser: ["GET /orgs/{org}/members/{username}/copilot"], + listCopilotSeats: ["GET /orgs/{org}/copilot/billing/seats"] + }, + credentials: { revoke: ["POST /credentials/revoke"] }, + dependabot: { + addSelectedRepoToOrgSecret: ["PUT /orgs/{org}/dependabot/secrets/{secret_name}/repositories/{repository_id}"], + createOrUpdateOrgSecret: ["PUT /orgs/{org}/dependabot/secrets/{secret_name}"], + createOrUpdateRepoSecret: ["PUT /repos/{owner}/{repo}/dependabot/secrets/{secret_name}"], + deleteOrgSecret: ["DELETE /orgs/{org}/dependabot/secrets/{secret_name}"], + deleteRepoSecret: ["DELETE /repos/{owner}/{repo}/dependabot/secrets/{secret_name}"], + getAlert: ["GET /repos/{owner}/{repo}/dependabot/alerts/{alert_number}"], + getOrgPublicKey: ["GET /orgs/{org}/dependabot/secrets/public-key"], + getOrgSecret: ["GET /orgs/{org}/dependabot/secrets/{secret_name}"], + getRepoPublicKey: ["GET /repos/{owner}/{repo}/dependabot/secrets/public-key"], + getRepoSecret: ["GET /repos/{owner}/{repo}/dependabot/secrets/{secret_name}"], + listAlertsForEnterprise: ["GET /enterprises/{enterprise}/dependabot/alerts"], + listAlertsForOrg: ["GET /orgs/{org}/dependabot/alerts"], + listAlertsForRepo: ["GET /repos/{owner}/{repo}/dependabot/alerts"], + listOrgSecrets: ["GET /orgs/{org}/dependabot/secrets"], + listRepoSecrets: ["GET /repos/{owner}/{repo}/dependabot/secrets"], + listSelectedReposForOrgSecret: ["GET /orgs/{org}/dependabot/secrets/{secret_name}/repositories"], + removeSelectedRepoFromOrgSecret: ["DELETE /orgs/{org}/dependabot/secrets/{secret_name}/repositories/{repository_id}"], + repositoryAccessForOrg: ["GET /organizations/{org}/dependabot/repository-access"], + setRepositoryAccessDefaultLevel: ["PUT /organizations/{org}/dependabot/repository-access/default-level"], + setSelectedReposForOrgSecret: ["PUT /orgs/{org}/dependabot/secrets/{secret_name}/repositories"], + updateAlert: ["PATCH /repos/{owner}/{repo}/dependabot/alerts/{alert_number}"], + updateRepositoryAccessForOrg: ["PATCH /organizations/{org}/dependabot/repository-access"] + }, + dependencyGraph: { + createRepositorySnapshot: ["POST /repos/{owner}/{repo}/dependency-graph/snapshots"], + diffRange: ["GET /repos/{owner}/{repo}/dependency-graph/compare/{basehead}"], + exportSbom: ["GET /repos/{owner}/{repo}/dependency-graph/sbom"] + }, + emojis: { get: ["GET /emojis"] }, + enterpriseTeamMemberships: { + add: ["PUT /enterprises/{enterprise}/teams/{enterprise-team}/memberships/{username}"], + bulkAdd: ["POST /enterprises/{enterprise}/teams/{enterprise-team}/memberships/add"], + bulkRemove: ["POST /enterprises/{enterprise}/teams/{enterprise-team}/memberships/remove"], + get: ["GET /enterprises/{enterprise}/teams/{enterprise-team}/memberships/{username}"], + list: ["GET /enterprises/{enterprise}/teams/{enterprise-team}/memberships"], + remove: ["DELETE /enterprises/{enterprise}/teams/{enterprise-team}/memberships/{username}"] + }, + enterpriseTeamOrganizations: { + add: ["PUT /enterprises/{enterprise}/teams/{enterprise-team}/organizations/{org}"], + bulkAdd: ["POST /enterprises/{enterprise}/teams/{enterprise-team}/organizations/add"], + bulkRemove: ["POST /enterprises/{enterprise}/teams/{enterprise-team}/organizations/remove"], + delete: ["DELETE /enterprises/{enterprise}/teams/{enterprise-team}/organizations/{org}"], + getAssignment: ["GET /enterprises/{enterprise}/teams/{enterprise-team}/organizations/{org}"], + getAssignments: ["GET /enterprises/{enterprise}/teams/{enterprise-team}/organizations"] + }, + enterpriseTeams: { + create: ["POST /enterprises/{enterprise}/teams"], + delete: ["DELETE /enterprises/{enterprise}/teams/{team_slug}"], + get: ["GET /enterprises/{enterprise}/teams/{team_slug}"], + list: ["GET /enterprises/{enterprise}/teams"], + update: ["PATCH /enterprises/{enterprise}/teams/{team_slug}"] + }, + gists: { + checkIsStarred: ["GET /gists/{gist_id}/star"], + create: ["POST /gists"], + createComment: ["POST /gists/{gist_id}/comments"], + delete: ["DELETE /gists/{gist_id}"], + deleteComment: ["DELETE /gists/{gist_id}/comments/{comment_id}"], + fork: ["POST /gists/{gist_id}/forks"], + get: ["GET /gists/{gist_id}"], + getComment: ["GET /gists/{gist_id}/comments/{comment_id}"], + getRevision: ["GET /gists/{gist_id}/{sha}"], + list: ["GET /gists"], + listComments: ["GET /gists/{gist_id}/comments"], + listCommits: ["GET /gists/{gist_id}/commits"], + listForUser: ["GET /users/{username}/gists"], + listForks: ["GET /gists/{gist_id}/forks"], + listPublic: ["GET /gists/public"], + listStarred: ["GET /gists/starred"], + star: ["PUT /gists/{gist_id}/star"], + unstar: ["DELETE /gists/{gist_id}/star"], + update: ["PATCH /gists/{gist_id}"], + updateComment: ["PATCH /gists/{gist_id}/comments/{comment_id}"] + }, + git: { + createBlob: ["POST /repos/{owner}/{repo}/git/blobs"], + createCommit: ["POST /repos/{owner}/{repo}/git/commits"], + createRef: ["POST /repos/{owner}/{repo}/git/refs"], + createTag: ["POST /repos/{owner}/{repo}/git/tags"], + createTree: ["POST /repos/{owner}/{repo}/git/trees"], + deleteRef: ["DELETE /repos/{owner}/{repo}/git/refs/{ref}"], + getBlob: ["GET /repos/{owner}/{repo}/git/blobs/{file_sha}"], + getCommit: ["GET /repos/{owner}/{repo}/git/commits/{commit_sha}"], + getRef: ["GET /repos/{owner}/{repo}/git/ref/{ref}"], + getTag: ["GET /repos/{owner}/{repo}/git/tags/{tag_sha}"], + getTree: ["GET /repos/{owner}/{repo}/git/trees/{tree_sha}"], + listMatchingRefs: ["GET /repos/{owner}/{repo}/git/matching-refs/{ref}"], + updateRef: ["PATCH /repos/{owner}/{repo}/git/refs/{ref}"] + }, + gitignore: { + getAllTemplates: ["GET /gitignore/templates"], + getTemplate: ["GET /gitignore/templates/{name}"] + }, + hostedCompute: { + createNetworkConfigurationForOrg: ["POST /orgs/{org}/settings/network-configurations"], + deleteNetworkConfigurationFromOrg: ["DELETE /orgs/{org}/settings/network-configurations/{network_configuration_id}"], + getNetworkConfigurationForOrg: ["GET /orgs/{org}/settings/network-configurations/{network_configuration_id}"], + getNetworkSettingsForOrg: ["GET /orgs/{org}/settings/network-settings/{network_settings_id}"], + listNetworkConfigurationsForOrg: ["GET /orgs/{org}/settings/network-configurations"], + updateNetworkConfigurationForOrg: ["PATCH /orgs/{org}/settings/network-configurations/{network_configuration_id}"] + }, + interactions: { + getRestrictionsForAuthenticatedUser: ["GET /user/interaction-limits"], + getRestrictionsForOrg: ["GET /orgs/{org}/interaction-limits"], + getRestrictionsForRepo: ["GET /repos/{owner}/{repo}/interaction-limits"], + getRestrictionsForYourPublicRepos: [ + "GET /user/interaction-limits", + {}, + { renamed: ["interactions", "getRestrictionsForAuthenticatedUser"] } + ], + removeRestrictionsForAuthenticatedUser: ["DELETE /user/interaction-limits"], + removeRestrictionsForOrg: ["DELETE /orgs/{org}/interaction-limits"], + removeRestrictionsForRepo: ["DELETE /repos/{owner}/{repo}/interaction-limits"], + removeRestrictionsForYourPublicRepos: [ + "DELETE /user/interaction-limits", + {}, + { renamed: ["interactions", "removeRestrictionsForAuthenticatedUser"] } + ], + setRestrictionsForAuthenticatedUser: ["PUT /user/interaction-limits"], + setRestrictionsForOrg: ["PUT /orgs/{org}/interaction-limits"], + setRestrictionsForRepo: ["PUT /repos/{owner}/{repo}/interaction-limits"], + setRestrictionsForYourPublicRepos: [ + "PUT /user/interaction-limits", + {}, + { renamed: ["interactions", "setRestrictionsForAuthenticatedUser"] } + ] + }, + issues: { + addAssignees: ["POST /repos/{owner}/{repo}/issues/{issue_number}/assignees"], + addBlockedByDependency: ["POST /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by"], + addLabels: ["POST /repos/{owner}/{repo}/issues/{issue_number}/labels"], + addSubIssue: ["POST /repos/{owner}/{repo}/issues/{issue_number}/sub_issues"], + checkUserCanBeAssigned: ["GET /repos/{owner}/{repo}/assignees/{assignee}"], + checkUserCanBeAssignedToIssue: ["GET /repos/{owner}/{repo}/issues/{issue_number}/assignees/{assignee}"], + create: ["POST /repos/{owner}/{repo}/issues"], + createComment: ["POST /repos/{owner}/{repo}/issues/{issue_number}/comments"], + createLabel: ["POST /repos/{owner}/{repo}/labels"], + createMilestone: ["POST /repos/{owner}/{repo}/milestones"], + deleteComment: ["DELETE /repos/{owner}/{repo}/issues/comments/{comment_id}"], + deleteLabel: ["DELETE /repos/{owner}/{repo}/labels/{name}"], + deleteMilestone: ["DELETE /repos/{owner}/{repo}/milestones/{milestone_number}"], + get: ["GET /repos/{owner}/{repo}/issues/{issue_number}"], + getComment: ["GET /repos/{owner}/{repo}/issues/comments/{comment_id}"], + getEvent: ["GET /repos/{owner}/{repo}/issues/events/{event_id}"], + getLabel: ["GET /repos/{owner}/{repo}/labels/{name}"], + getMilestone: ["GET /repos/{owner}/{repo}/milestones/{milestone_number}"], + getParent: ["GET /repos/{owner}/{repo}/issues/{issue_number}/parent"], + list: ["GET /issues"], + listAssignees: ["GET /repos/{owner}/{repo}/assignees"], + listComments: ["GET /repos/{owner}/{repo}/issues/{issue_number}/comments"], + listCommentsForRepo: ["GET /repos/{owner}/{repo}/issues/comments"], + listDependenciesBlockedBy: ["GET /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by"], + listDependenciesBlocking: ["GET /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocking"], + listEvents: ["GET /repos/{owner}/{repo}/issues/{issue_number}/events"], + listEventsForRepo: ["GET /repos/{owner}/{repo}/issues/events"], + listEventsForTimeline: ["GET /repos/{owner}/{repo}/issues/{issue_number}/timeline"], + listForAuthenticatedUser: ["GET /user/issues"], + listForOrg: ["GET /orgs/{org}/issues"], + listForRepo: ["GET /repos/{owner}/{repo}/issues"], + listLabelsForMilestone: ["GET /repos/{owner}/{repo}/milestones/{milestone_number}/labels"], + listLabelsForRepo: ["GET /repos/{owner}/{repo}/labels"], + listLabelsOnIssue: ["GET /repos/{owner}/{repo}/issues/{issue_number}/labels"], + listMilestones: ["GET /repos/{owner}/{repo}/milestones"], + listSubIssues: ["GET /repos/{owner}/{repo}/issues/{issue_number}/sub_issues"], + lock: ["PUT /repos/{owner}/{repo}/issues/{issue_number}/lock"], + removeAllLabels: ["DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels"], + removeAssignees: ["DELETE /repos/{owner}/{repo}/issues/{issue_number}/assignees"], + removeDependencyBlockedBy: ["DELETE /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by/{issue_id}"], + removeLabel: ["DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{name}"], + removeSubIssue: ["DELETE /repos/{owner}/{repo}/issues/{issue_number}/sub_issue"], + reprioritizeSubIssue: ["PATCH /repos/{owner}/{repo}/issues/{issue_number}/sub_issues/priority"], + setLabels: ["PUT /repos/{owner}/{repo}/issues/{issue_number}/labels"], + unlock: ["DELETE /repos/{owner}/{repo}/issues/{issue_number}/lock"], + update: ["PATCH /repos/{owner}/{repo}/issues/{issue_number}"], + updateComment: ["PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}"], + updateLabel: ["PATCH /repos/{owner}/{repo}/labels/{name}"], + updateMilestone: ["PATCH /repos/{owner}/{repo}/milestones/{milestone_number}"] + }, + licenses: { + get: ["GET /licenses/{license}"], + getAllCommonlyUsed: ["GET /licenses"], + getForRepo: ["GET /repos/{owner}/{repo}/license"] + }, + markdown: { + render: ["POST /markdown"], + renderRaw: ["POST /markdown/raw", { headers: { "content-type": "text/plain; charset=utf-8" } }] + }, + meta: { + get: ["GET /meta"], + getAllVersions: ["GET /versions"], + getOctocat: ["GET /octocat"], + getZen: ["GET /zen"], + root: ["GET /"] + }, + migrations: { + deleteArchiveForAuthenticatedUser: ["DELETE /user/migrations/{migration_id}/archive"], + deleteArchiveForOrg: ["DELETE /orgs/{org}/migrations/{migration_id}/archive"], + downloadArchiveForOrg: ["GET /orgs/{org}/migrations/{migration_id}/archive"], + getArchiveForAuthenticatedUser: ["GET /user/migrations/{migration_id}/archive"], + getStatusForAuthenticatedUser: ["GET /user/migrations/{migration_id}"], + getStatusForOrg: ["GET /orgs/{org}/migrations/{migration_id}"], + listForAuthenticatedUser: ["GET /user/migrations"], + listForOrg: ["GET /orgs/{org}/migrations"], + listReposForAuthenticatedUser: ["GET /user/migrations/{migration_id}/repositories"], + listReposForOrg: ["GET /orgs/{org}/migrations/{migration_id}/repositories"], + listReposForUser: [ + "GET /user/migrations/{migration_id}/repositories", + {}, + { renamed: ["migrations", "listReposForAuthenticatedUser"] } + ], + startForAuthenticatedUser: ["POST /user/migrations"], + startForOrg: ["POST /orgs/{org}/migrations"], + unlockRepoForAuthenticatedUser: ["DELETE /user/migrations/{migration_id}/repos/{repo_name}/lock"], + unlockRepoForOrg: ["DELETE /orgs/{org}/migrations/{migration_id}/repos/{repo_name}/lock"] + }, + oidc: { + getOidcCustomSubTemplateForOrg: ["GET /orgs/{org}/actions/oidc/customization/sub"], + updateOidcCustomSubTemplateForOrg: ["PUT /orgs/{org}/actions/oidc/customization/sub"] + }, + orgs: { + addSecurityManagerTeam: [ + "PUT /orgs/{org}/security-managers/teams/{team_slug}", + {}, + { deprecated: "octokit.rest.orgs.addSecurityManagerTeam() is deprecated, see https://docs.github.com/rest/orgs/security-managers#add-a-security-manager-team" } + ], + assignTeamToOrgRole: ["PUT /orgs/{org}/organization-roles/teams/{team_slug}/{role_id}"], + assignUserToOrgRole: ["PUT /orgs/{org}/organization-roles/users/{username}/{role_id}"], + blockUser: ["PUT /orgs/{org}/blocks/{username}"], + cancelInvitation: ["DELETE /orgs/{org}/invitations/{invitation_id}"], + checkBlockedUser: ["GET /orgs/{org}/blocks/{username}"], + checkMembershipForUser: ["GET /orgs/{org}/members/{username}"], + checkPublicMembershipForUser: ["GET /orgs/{org}/public_members/{username}"], + convertMemberToOutsideCollaborator: ["PUT /orgs/{org}/outside_collaborators/{username}"], + createArtifactStorageRecord: ["POST /orgs/{org}/artifacts/metadata/storage-record"], + createInvitation: ["POST /orgs/{org}/invitations"], + createIssueType: ["POST /orgs/{org}/issue-types"], + createWebhook: ["POST /orgs/{org}/hooks"], + customPropertiesForOrgsCreateOrUpdateOrganizationValues: ["PATCH /organizations/{org}/org-properties/values"], + customPropertiesForOrgsGetOrganizationValues: ["GET /organizations/{org}/org-properties/values"], + customPropertiesForReposCreateOrUpdateOrganizationDefinition: ["PUT /orgs/{org}/properties/schema/{custom_property_name}"], + customPropertiesForReposCreateOrUpdateOrganizationDefinitions: ["PATCH /orgs/{org}/properties/schema"], + customPropertiesForReposCreateOrUpdateOrganizationValues: ["PATCH /orgs/{org}/properties/values"], + customPropertiesForReposDeleteOrganizationDefinition: ["DELETE /orgs/{org}/properties/schema/{custom_property_name}"], + customPropertiesForReposGetOrganizationDefinition: ["GET /orgs/{org}/properties/schema/{custom_property_name}"], + customPropertiesForReposGetOrganizationDefinitions: ["GET /orgs/{org}/properties/schema"], + customPropertiesForReposGetOrganizationValues: ["GET /orgs/{org}/properties/values"], + delete: ["DELETE /orgs/{org}"], + deleteAttestationsBulk: ["POST /orgs/{org}/attestations/delete-request"], + deleteAttestationsById: ["DELETE /orgs/{org}/attestations/{attestation_id}"], + deleteAttestationsBySubjectDigest: ["DELETE /orgs/{org}/attestations/digest/{subject_digest}"], + deleteIssueType: ["DELETE /orgs/{org}/issue-types/{issue_type_id}"], + deleteWebhook: ["DELETE /orgs/{org}/hooks/{hook_id}"], + disableSelectedRepositoryImmutableReleasesOrganization: ["DELETE /orgs/{org}/settings/immutable-releases/repositories/{repository_id}"], + enableSelectedRepositoryImmutableReleasesOrganization: ["PUT /orgs/{org}/settings/immutable-releases/repositories/{repository_id}"], + get: ["GET /orgs/{org}"], + getImmutableReleasesSettings: ["GET /orgs/{org}/settings/immutable-releases"], + getImmutableReleasesSettingsRepositories: ["GET /orgs/{org}/settings/immutable-releases/repositories"], + getMembershipForAuthenticatedUser: ["GET /user/memberships/orgs/{org}"], + getMembershipForUser: ["GET /orgs/{org}/memberships/{username}"], + getOrgRole: ["GET /orgs/{org}/organization-roles/{role_id}"], + getOrgRulesetHistory: ["GET /orgs/{org}/rulesets/{ruleset_id}/history"], + getOrgRulesetVersion: ["GET /orgs/{org}/rulesets/{ruleset_id}/history/{version_id}"], + getWebhook: ["GET /orgs/{org}/hooks/{hook_id}"], + getWebhookConfigForOrg: ["GET /orgs/{org}/hooks/{hook_id}/config"], + getWebhookDelivery: ["GET /orgs/{org}/hooks/{hook_id}/deliveries/{delivery_id}"], + list: ["GET /organizations"], + listAppInstallations: ["GET /orgs/{org}/installations"], + listArtifactStorageRecords: ["GET /orgs/{org}/artifacts/{subject_digest}/metadata/storage-records"], + listAttestationRepositories: ["GET /orgs/{org}/attestations/repositories"], + listAttestations: ["GET /orgs/{org}/attestations/{subject_digest}"], + listAttestationsBulk: ["POST /orgs/{org}/attestations/bulk-list{?per_page,before,after}"], + listBlockedUsers: ["GET /orgs/{org}/blocks"], + listFailedInvitations: ["GET /orgs/{org}/failed_invitations"], + listForAuthenticatedUser: ["GET /user/orgs"], + listForUser: ["GET /users/{username}/orgs"], + listInvitationTeams: ["GET /orgs/{org}/invitations/{invitation_id}/teams"], + listIssueTypes: ["GET /orgs/{org}/issue-types"], + listMembers: ["GET /orgs/{org}/members"], + listMembershipsForAuthenticatedUser: ["GET /user/memberships/orgs"], + listOrgRoleTeams: ["GET /orgs/{org}/organization-roles/{role_id}/teams"], + listOrgRoleUsers: ["GET /orgs/{org}/organization-roles/{role_id}/users"], + listOrgRoles: ["GET /orgs/{org}/organization-roles"], + listOrganizationFineGrainedPermissions: ["GET /orgs/{org}/organization-fine-grained-permissions"], + listOutsideCollaborators: ["GET /orgs/{org}/outside_collaborators"], + listPatGrantRepositories: ["GET /orgs/{org}/personal-access-tokens/{pat_id}/repositories"], + listPatGrantRequestRepositories: ["GET /orgs/{org}/personal-access-token-requests/{pat_request_id}/repositories"], + listPatGrantRequests: ["GET /orgs/{org}/personal-access-token-requests"], + listPatGrants: ["GET /orgs/{org}/personal-access-tokens"], + listPendingInvitations: ["GET /orgs/{org}/invitations"], + listPublicMembers: ["GET /orgs/{org}/public_members"], + listSecurityManagerTeams: [ + "GET /orgs/{org}/security-managers", + {}, + { deprecated: "octokit.rest.orgs.listSecurityManagerTeams() is deprecated, see https://docs.github.com/rest/orgs/security-managers#list-security-manager-teams" } + ], + listWebhookDeliveries: ["GET /orgs/{org}/hooks/{hook_id}/deliveries"], + listWebhooks: ["GET /orgs/{org}/hooks"], + pingWebhook: ["POST /orgs/{org}/hooks/{hook_id}/pings"], + redeliverWebhookDelivery: ["POST /orgs/{org}/hooks/{hook_id}/deliveries/{delivery_id}/attempts"], + removeMember: ["DELETE /orgs/{org}/members/{username}"], + removeMembershipForUser: ["DELETE /orgs/{org}/memberships/{username}"], + removeOutsideCollaborator: ["DELETE /orgs/{org}/outside_collaborators/{username}"], + removePublicMembershipForAuthenticatedUser: ["DELETE /orgs/{org}/public_members/{username}"], + removeSecurityManagerTeam: [ + "DELETE /orgs/{org}/security-managers/teams/{team_slug}", + {}, + { deprecated: "octokit.rest.orgs.removeSecurityManagerTeam() is deprecated, see https://docs.github.com/rest/orgs/security-managers#remove-a-security-manager-team" } + ], + reviewPatGrantRequest: ["POST /orgs/{org}/personal-access-token-requests/{pat_request_id}"], + reviewPatGrantRequestsInBulk: ["POST /orgs/{org}/personal-access-token-requests"], + revokeAllOrgRolesTeam: ["DELETE /orgs/{org}/organization-roles/teams/{team_slug}"], + revokeAllOrgRolesUser: ["DELETE /orgs/{org}/organization-roles/users/{username}"], + revokeOrgRoleTeam: ["DELETE /orgs/{org}/organization-roles/teams/{team_slug}/{role_id}"], + revokeOrgRoleUser: ["DELETE /orgs/{org}/organization-roles/users/{username}/{role_id}"], + setImmutableReleasesSettings: ["PUT /orgs/{org}/settings/immutable-releases"], + setImmutableReleasesSettingsRepositories: ["PUT /orgs/{org}/settings/immutable-releases/repositories"], + setMembershipForUser: ["PUT /orgs/{org}/memberships/{username}"], + setPublicMembershipForAuthenticatedUser: ["PUT /orgs/{org}/public_members/{username}"], + unblockUser: ["DELETE /orgs/{org}/blocks/{username}"], + update: ["PATCH /orgs/{org}"], + updateIssueType: ["PUT /orgs/{org}/issue-types/{issue_type_id}"], + updateMembershipForAuthenticatedUser: ["PATCH /user/memberships/orgs/{org}"], + updatePatAccess: ["POST /orgs/{org}/personal-access-tokens/{pat_id}"], + updatePatAccesses: ["POST /orgs/{org}/personal-access-tokens"], + updateWebhook: ["PATCH /orgs/{org}/hooks/{hook_id}"], + updateWebhookConfigForOrg: ["PATCH /orgs/{org}/hooks/{hook_id}/config"] + }, + packages: { + deletePackageForAuthenticatedUser: ["DELETE /user/packages/{package_type}/{package_name}"], + deletePackageForOrg: ["DELETE /orgs/{org}/packages/{package_type}/{package_name}"], + deletePackageForUser: ["DELETE /users/{username}/packages/{package_type}/{package_name}"], + deletePackageVersionForAuthenticatedUser: ["DELETE /user/packages/{package_type}/{package_name}/versions/{package_version_id}"], + deletePackageVersionForOrg: ["DELETE /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}"], + deletePackageVersionForUser: ["DELETE /users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}"], + getAllPackageVersionsForAPackageOwnedByAnOrg: [ + "GET /orgs/{org}/packages/{package_type}/{package_name}/versions", + {}, + { renamed: ["packages", "getAllPackageVersionsForPackageOwnedByOrg"] } + ], + getAllPackageVersionsForAPackageOwnedByTheAuthenticatedUser: [ + "GET /user/packages/{package_type}/{package_name}/versions", + {}, + { renamed: ["packages", "getAllPackageVersionsForPackageOwnedByAuthenticatedUser"] } + ], + getAllPackageVersionsForPackageOwnedByAuthenticatedUser: ["GET /user/packages/{package_type}/{package_name}/versions"], + getAllPackageVersionsForPackageOwnedByOrg: ["GET /orgs/{org}/packages/{package_type}/{package_name}/versions"], + getAllPackageVersionsForPackageOwnedByUser: ["GET /users/{username}/packages/{package_type}/{package_name}/versions"], + getPackageForAuthenticatedUser: ["GET /user/packages/{package_type}/{package_name}"], + getPackageForOrganization: ["GET /orgs/{org}/packages/{package_type}/{package_name}"], + getPackageForUser: ["GET /users/{username}/packages/{package_type}/{package_name}"], + getPackageVersionForAuthenticatedUser: ["GET /user/packages/{package_type}/{package_name}/versions/{package_version_id}"], + getPackageVersionForOrganization: ["GET /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}"], + getPackageVersionForUser: ["GET /users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}"], + listDockerMigrationConflictingPackagesForAuthenticatedUser: ["GET /user/docker/conflicts"], + listDockerMigrationConflictingPackagesForOrganization: ["GET /orgs/{org}/docker/conflicts"], + listDockerMigrationConflictingPackagesForUser: ["GET /users/{username}/docker/conflicts"], + listPackagesForAuthenticatedUser: ["GET /user/packages"], + listPackagesForOrganization: ["GET /orgs/{org}/packages"], + listPackagesForUser: ["GET /users/{username}/packages"], + restorePackageForAuthenticatedUser: ["POST /user/packages/{package_type}/{package_name}/restore{?token}"], + restorePackageForOrg: ["POST /orgs/{org}/packages/{package_type}/{package_name}/restore{?token}"], + restorePackageForUser: ["POST /users/{username}/packages/{package_type}/{package_name}/restore{?token}"], + restorePackageVersionForAuthenticatedUser: ["POST /user/packages/{package_type}/{package_name}/versions/{package_version_id}/restore"], + restorePackageVersionForOrg: ["POST /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}/restore"], + restorePackageVersionForUser: ["POST /users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}/restore"] + }, + privateRegistries: { + createOrgPrivateRegistry: ["POST /orgs/{org}/private-registries"], + deleteOrgPrivateRegistry: ["DELETE /orgs/{org}/private-registries/{secret_name}"], + getOrgPrivateRegistry: ["GET /orgs/{org}/private-registries/{secret_name}"], + getOrgPublicKey: ["GET /orgs/{org}/private-registries/public-key"], + listOrgPrivateRegistries: ["GET /orgs/{org}/private-registries"], + updateOrgPrivateRegistry: ["PATCH /orgs/{org}/private-registries/{secret_name}"] + }, + projects: { + addItemForOrg: ["POST /orgs/{org}/projectsV2/{project_number}/items"], + addItemForUser: ["POST /users/{username}/projectsV2/{project_number}/items"], + deleteItemForOrg: ["DELETE /orgs/{org}/projectsV2/{project_number}/items/{item_id}"], + deleteItemForUser: ["DELETE /users/{username}/projectsV2/{project_number}/items/{item_id}"], + getFieldForOrg: ["GET /orgs/{org}/projectsV2/{project_number}/fields/{field_id}"], + getFieldForUser: ["GET /users/{username}/projectsV2/{project_number}/fields/{field_id}"], + getForOrg: ["GET /orgs/{org}/projectsV2/{project_number}"], + getForUser: ["GET /users/{username}/projectsV2/{project_number}"], + getOrgItem: ["GET /orgs/{org}/projectsV2/{project_number}/items/{item_id}"], + getUserItem: ["GET /users/{username}/projectsV2/{project_number}/items/{item_id}"], + listFieldsForOrg: ["GET /orgs/{org}/projectsV2/{project_number}/fields"], + listFieldsForUser: ["GET /users/{username}/projectsV2/{project_number}/fields"], + listForOrg: ["GET /orgs/{org}/projectsV2"], + listForUser: ["GET /users/{username}/projectsV2"], + listItemsForOrg: ["GET /orgs/{org}/projectsV2/{project_number}/items"], + listItemsForUser: ["GET /users/{username}/projectsV2/{project_number}/items"], + updateItemForOrg: ["PATCH /orgs/{org}/projectsV2/{project_number}/items/{item_id}"], + updateItemForUser: ["PATCH /users/{username}/projectsV2/{project_number}/items/{item_id}"] + }, + pulls: { + checkIfMerged: ["GET /repos/{owner}/{repo}/pulls/{pull_number}/merge"], + create: ["POST /repos/{owner}/{repo}/pulls"], + createReplyForReviewComment: ["POST /repos/{owner}/{repo}/pulls/{pull_number}/comments/{comment_id}/replies"], + createReview: ["POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews"], + createReviewComment: ["POST /repos/{owner}/{repo}/pulls/{pull_number}/comments"], + deletePendingReview: ["DELETE /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}"], + deleteReviewComment: ["DELETE /repos/{owner}/{repo}/pulls/comments/{comment_id}"], + dismissReview: ["PUT /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/dismissals"], + get: ["GET /repos/{owner}/{repo}/pulls/{pull_number}"], + getReview: ["GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}"], + getReviewComment: ["GET /repos/{owner}/{repo}/pulls/comments/{comment_id}"], + list: ["GET /repos/{owner}/{repo}/pulls"], + listCommentsForReview: ["GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/comments"], + listCommits: ["GET /repos/{owner}/{repo}/pulls/{pull_number}/commits"], + listFiles: ["GET /repos/{owner}/{repo}/pulls/{pull_number}/files"], + listRequestedReviewers: ["GET /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers"], + listReviewComments: ["GET /repos/{owner}/{repo}/pulls/{pull_number}/comments"], + listReviewCommentsForRepo: ["GET /repos/{owner}/{repo}/pulls/comments"], + listReviews: ["GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews"], + merge: ["PUT /repos/{owner}/{repo}/pulls/{pull_number}/merge"], + removeRequestedReviewers: ["DELETE /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers"], + requestReviewers: ["POST /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers"], + submitReview: ["POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/events"], + update: ["PATCH /repos/{owner}/{repo}/pulls/{pull_number}"], + updateBranch: ["PUT /repos/{owner}/{repo}/pulls/{pull_number}/update-branch"], + updateReview: ["PUT /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}"], + updateReviewComment: ["PATCH /repos/{owner}/{repo}/pulls/comments/{comment_id}"] + }, + rateLimit: { get: ["GET /rate_limit"] }, + reactions: { + createForCommitComment: ["POST /repos/{owner}/{repo}/comments/{comment_id}/reactions"], + createForIssue: ["POST /repos/{owner}/{repo}/issues/{issue_number}/reactions"], + createForIssueComment: ["POST /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions"], + createForPullRequestReviewComment: ["POST /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions"], + createForRelease: ["POST /repos/{owner}/{repo}/releases/{release_id}/reactions"], + createForTeamDiscussionCommentInOrg: ["POST /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions"], + createForTeamDiscussionInOrg: ["POST /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions"], + deleteForCommitComment: ["DELETE /repos/{owner}/{repo}/comments/{comment_id}/reactions/{reaction_id}"], + deleteForIssue: ["DELETE /repos/{owner}/{repo}/issues/{issue_number}/reactions/{reaction_id}"], + deleteForIssueComment: ["DELETE /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions/{reaction_id}"], + deleteForPullRequestComment: ["DELETE /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions/{reaction_id}"], + deleteForRelease: ["DELETE /repos/{owner}/{repo}/releases/{release_id}/reactions/{reaction_id}"], + deleteForTeamDiscussion: ["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions/{reaction_id}"], + deleteForTeamDiscussionComment: ["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions/{reaction_id}"], + listForCommitComment: ["GET /repos/{owner}/{repo}/comments/{comment_id}/reactions"], + listForIssue: ["GET /repos/{owner}/{repo}/issues/{issue_number}/reactions"], + listForIssueComment: ["GET /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions"], + listForPullRequestReviewComment: ["GET /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions"], + listForRelease: ["GET /repos/{owner}/{repo}/releases/{release_id}/reactions"], + listForTeamDiscussionCommentInOrg: ["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions"], + listForTeamDiscussionInOrg: ["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions"] + }, + repos: { + acceptInvitation: [ + "PATCH /user/repository_invitations/{invitation_id}", + {}, + { renamed: ["repos", "acceptInvitationForAuthenticatedUser"] } + ], + acceptInvitationForAuthenticatedUser: ["PATCH /user/repository_invitations/{invitation_id}"], + addAppAccessRestrictions: [ + "POST /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps", + {}, + { mapToData: "apps" } + ], + addCollaborator: ["PUT /repos/{owner}/{repo}/collaborators/{username}"], + addStatusCheckContexts: [ + "POST /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts", + {}, + { mapToData: "contexts" } + ], + addTeamAccessRestrictions: [ + "POST /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams", + {}, + { mapToData: "teams" } + ], + addUserAccessRestrictions: [ + "POST /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users", + {}, + { mapToData: "users" } + ], + cancelPagesDeployment: ["POST /repos/{owner}/{repo}/pages/deployments/{pages_deployment_id}/cancel"], + checkAutomatedSecurityFixes: ["GET /repos/{owner}/{repo}/automated-security-fixes"], + checkCollaborator: ["GET /repos/{owner}/{repo}/collaborators/{username}"], + checkImmutableReleases: ["GET /repos/{owner}/{repo}/immutable-releases"], + checkPrivateVulnerabilityReporting: ["GET /repos/{owner}/{repo}/private-vulnerability-reporting"], + checkVulnerabilityAlerts: ["GET /repos/{owner}/{repo}/vulnerability-alerts"], + codeownersErrors: ["GET /repos/{owner}/{repo}/codeowners/errors"], + compareCommits: ["GET /repos/{owner}/{repo}/compare/{base}...{head}"], + compareCommitsWithBasehead: ["GET /repos/{owner}/{repo}/compare/{basehead}"], + createAttestation: ["POST /repos/{owner}/{repo}/attestations"], + createAutolink: ["POST /repos/{owner}/{repo}/autolinks"], + createCommitComment: ["POST /repos/{owner}/{repo}/commits/{commit_sha}/comments"], + createCommitSignatureProtection: ["POST /repos/{owner}/{repo}/branches/{branch}/protection/required_signatures"], + createCommitStatus: ["POST /repos/{owner}/{repo}/statuses/{sha}"], + createDeployKey: ["POST /repos/{owner}/{repo}/keys"], + createDeployment: ["POST /repos/{owner}/{repo}/deployments"], + createDeploymentBranchPolicy: ["POST /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies"], + createDeploymentProtectionRule: ["POST /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules"], + createDeploymentStatus: ["POST /repos/{owner}/{repo}/deployments/{deployment_id}/statuses"], + createDispatchEvent: ["POST /repos/{owner}/{repo}/dispatches"], + createForAuthenticatedUser: ["POST /user/repos"], + createFork: ["POST /repos/{owner}/{repo}/forks"], + createInOrg: ["POST /orgs/{org}/repos"], + createOrUpdateEnvironment: ["PUT /repos/{owner}/{repo}/environments/{environment_name}"], + createOrUpdateFileContents: ["PUT /repos/{owner}/{repo}/contents/{path}"], + createOrgRuleset: ["POST /orgs/{org}/rulesets"], + createPagesDeployment: ["POST /repos/{owner}/{repo}/pages/deployments"], + createPagesSite: ["POST /repos/{owner}/{repo}/pages"], + createRelease: ["POST /repos/{owner}/{repo}/releases"], + createRepoRuleset: ["POST /repos/{owner}/{repo}/rulesets"], + createUsingTemplate: ["POST /repos/{template_owner}/{template_repo}/generate"], + createWebhook: ["POST /repos/{owner}/{repo}/hooks"], + customPropertiesForReposCreateOrUpdateRepositoryValues: ["PATCH /repos/{owner}/{repo}/properties/values"], + customPropertiesForReposGetRepositoryValues: ["GET /repos/{owner}/{repo}/properties/values"], + declineInvitation: [ + "DELETE /user/repository_invitations/{invitation_id}", + {}, + { renamed: ["repos", "declineInvitationForAuthenticatedUser"] } + ], + declineInvitationForAuthenticatedUser: ["DELETE /user/repository_invitations/{invitation_id}"], + delete: ["DELETE /repos/{owner}/{repo}"], + deleteAccessRestrictions: ["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions"], + deleteAdminBranchProtection: ["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/enforce_admins"], + deleteAnEnvironment: ["DELETE /repos/{owner}/{repo}/environments/{environment_name}"], + deleteAutolink: ["DELETE /repos/{owner}/{repo}/autolinks/{autolink_id}"], + deleteBranchProtection: ["DELETE /repos/{owner}/{repo}/branches/{branch}/protection"], + deleteCommitComment: ["DELETE /repos/{owner}/{repo}/comments/{comment_id}"], + deleteCommitSignatureProtection: ["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_signatures"], + deleteDeployKey: ["DELETE /repos/{owner}/{repo}/keys/{key_id}"], + deleteDeployment: ["DELETE /repos/{owner}/{repo}/deployments/{deployment_id}"], + deleteDeploymentBranchPolicy: ["DELETE /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies/{branch_policy_id}"], + deleteFile: ["DELETE /repos/{owner}/{repo}/contents/{path}"], + deleteInvitation: ["DELETE /repos/{owner}/{repo}/invitations/{invitation_id}"], + deleteOrgRuleset: ["DELETE /orgs/{org}/rulesets/{ruleset_id}"], + deletePagesSite: ["DELETE /repos/{owner}/{repo}/pages"], + deletePullRequestReviewProtection: ["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviews"], + deleteRelease: ["DELETE /repos/{owner}/{repo}/releases/{release_id}"], + deleteReleaseAsset: ["DELETE /repos/{owner}/{repo}/releases/assets/{asset_id}"], + deleteRepoRuleset: ["DELETE /repos/{owner}/{repo}/rulesets/{ruleset_id}"], + deleteWebhook: ["DELETE /repos/{owner}/{repo}/hooks/{hook_id}"], + disableAutomatedSecurityFixes: ["DELETE /repos/{owner}/{repo}/automated-security-fixes"], + disableDeploymentProtectionRule: ["DELETE /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/{protection_rule_id}"], + disableImmutableReleases: ["DELETE /repos/{owner}/{repo}/immutable-releases"], + disablePrivateVulnerabilityReporting: ["DELETE /repos/{owner}/{repo}/private-vulnerability-reporting"], + disableVulnerabilityAlerts: ["DELETE /repos/{owner}/{repo}/vulnerability-alerts"], + downloadArchive: [ + "GET /repos/{owner}/{repo}/zipball/{ref}", + {}, + { renamed: ["repos", "downloadZipballArchive"] } + ], + downloadTarballArchive: ["GET /repos/{owner}/{repo}/tarball/{ref}"], + downloadZipballArchive: ["GET /repos/{owner}/{repo}/zipball/{ref}"], + enableAutomatedSecurityFixes: ["PUT /repos/{owner}/{repo}/automated-security-fixes"], + enableImmutableReleases: ["PUT /repos/{owner}/{repo}/immutable-releases"], + enablePrivateVulnerabilityReporting: ["PUT /repos/{owner}/{repo}/private-vulnerability-reporting"], + enableVulnerabilityAlerts: ["PUT /repos/{owner}/{repo}/vulnerability-alerts"], + generateReleaseNotes: ["POST /repos/{owner}/{repo}/releases/generate-notes"], + get: ["GET /repos/{owner}/{repo}"], + getAccessRestrictions: ["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions"], + getAdminBranchProtection: ["GET /repos/{owner}/{repo}/branches/{branch}/protection/enforce_admins"], + getAllDeploymentProtectionRules: ["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules"], + getAllEnvironments: ["GET /repos/{owner}/{repo}/environments"], + getAllStatusCheckContexts: ["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts"], + getAllTopics: ["GET /repos/{owner}/{repo}/topics"], + getAppsWithAccessToProtectedBranch: ["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps"], + getAutolink: ["GET /repos/{owner}/{repo}/autolinks/{autolink_id}"], + getBranch: ["GET /repos/{owner}/{repo}/branches/{branch}"], + getBranchProtection: ["GET /repos/{owner}/{repo}/branches/{branch}/protection"], + getBranchRules: ["GET /repos/{owner}/{repo}/rules/branches/{branch}"], + getClones: ["GET /repos/{owner}/{repo}/traffic/clones"], + getCodeFrequencyStats: ["GET /repos/{owner}/{repo}/stats/code_frequency"], + getCollaboratorPermissionLevel: ["GET /repos/{owner}/{repo}/collaborators/{username}/permission"], + getCombinedStatusForRef: ["GET /repos/{owner}/{repo}/commits/{ref}/status"], + getCommit: ["GET /repos/{owner}/{repo}/commits/{ref}"], + getCommitActivityStats: ["GET /repos/{owner}/{repo}/stats/commit_activity"], + getCommitComment: ["GET /repos/{owner}/{repo}/comments/{comment_id}"], + getCommitSignatureProtection: ["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_signatures"], + getCommunityProfileMetrics: ["GET /repos/{owner}/{repo}/community/profile"], + getContent: ["GET /repos/{owner}/{repo}/contents/{path}"], + getContributorsStats: ["GET /repos/{owner}/{repo}/stats/contributors"], + getCustomDeploymentProtectionRule: ["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/{protection_rule_id}"], + getDeployKey: ["GET /repos/{owner}/{repo}/keys/{key_id}"], + getDeployment: ["GET /repos/{owner}/{repo}/deployments/{deployment_id}"], + getDeploymentBranchPolicy: ["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies/{branch_policy_id}"], + getDeploymentStatus: ["GET /repos/{owner}/{repo}/deployments/{deployment_id}/statuses/{status_id}"], + getEnvironment: ["GET /repos/{owner}/{repo}/environments/{environment_name}"], + getLatestPagesBuild: ["GET /repos/{owner}/{repo}/pages/builds/latest"], + getLatestRelease: ["GET /repos/{owner}/{repo}/releases/latest"], + getOrgRuleSuite: ["GET /orgs/{org}/rulesets/rule-suites/{rule_suite_id}"], + getOrgRuleSuites: ["GET /orgs/{org}/rulesets/rule-suites"], + getOrgRuleset: ["GET /orgs/{org}/rulesets/{ruleset_id}"], + getOrgRulesets: ["GET /orgs/{org}/rulesets"], + getPages: ["GET /repos/{owner}/{repo}/pages"], + getPagesBuild: ["GET /repos/{owner}/{repo}/pages/builds/{build_id}"], + getPagesDeployment: ["GET /repos/{owner}/{repo}/pages/deployments/{pages_deployment_id}"], + getPagesHealthCheck: ["GET /repos/{owner}/{repo}/pages/health"], + getParticipationStats: ["GET /repos/{owner}/{repo}/stats/participation"], + getPullRequestReviewProtection: ["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviews"], + getPunchCardStats: ["GET /repos/{owner}/{repo}/stats/punch_card"], + getReadme: ["GET /repos/{owner}/{repo}/readme"], + getReadmeInDirectory: ["GET /repos/{owner}/{repo}/readme/{dir}"], + getRelease: ["GET /repos/{owner}/{repo}/releases/{release_id}"], + getReleaseAsset: ["GET /repos/{owner}/{repo}/releases/assets/{asset_id}"], + getReleaseByTag: ["GET /repos/{owner}/{repo}/releases/tags/{tag}"], + getRepoRuleSuite: ["GET /repos/{owner}/{repo}/rulesets/rule-suites/{rule_suite_id}"], + getRepoRuleSuites: ["GET /repos/{owner}/{repo}/rulesets/rule-suites"], + getRepoRuleset: ["GET /repos/{owner}/{repo}/rulesets/{ruleset_id}"], + getRepoRulesetHistory: ["GET /repos/{owner}/{repo}/rulesets/{ruleset_id}/history"], + getRepoRulesetVersion: ["GET /repos/{owner}/{repo}/rulesets/{ruleset_id}/history/{version_id}"], + getRepoRulesets: ["GET /repos/{owner}/{repo}/rulesets"], + getStatusChecksProtection: ["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks"], + getTeamsWithAccessToProtectedBranch: ["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams"], + getTopPaths: ["GET /repos/{owner}/{repo}/traffic/popular/paths"], + getTopReferrers: ["GET /repos/{owner}/{repo}/traffic/popular/referrers"], + getUsersWithAccessToProtectedBranch: ["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users"], + getViews: ["GET /repos/{owner}/{repo}/traffic/views"], + getWebhook: ["GET /repos/{owner}/{repo}/hooks/{hook_id}"], + getWebhookConfigForRepo: ["GET /repos/{owner}/{repo}/hooks/{hook_id}/config"], + getWebhookDelivery: ["GET /repos/{owner}/{repo}/hooks/{hook_id}/deliveries/{delivery_id}"], + listActivities: ["GET /repos/{owner}/{repo}/activity"], + listAttestations: ["GET /repos/{owner}/{repo}/attestations/{subject_digest}"], + listAutolinks: ["GET /repos/{owner}/{repo}/autolinks"], + listBranches: ["GET /repos/{owner}/{repo}/branches"], + listBranchesForHeadCommit: ["GET /repos/{owner}/{repo}/commits/{commit_sha}/branches-where-head"], + listCollaborators: ["GET /repos/{owner}/{repo}/collaborators"], + listCommentsForCommit: ["GET /repos/{owner}/{repo}/commits/{commit_sha}/comments"], + listCommitCommentsForRepo: ["GET /repos/{owner}/{repo}/comments"], + listCommitStatusesForRef: ["GET /repos/{owner}/{repo}/commits/{ref}/statuses"], + listCommits: ["GET /repos/{owner}/{repo}/commits"], + listContributors: ["GET /repos/{owner}/{repo}/contributors"], + listCustomDeploymentRuleIntegrations: ["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/apps"], + listDeployKeys: ["GET /repos/{owner}/{repo}/keys"], + listDeploymentBranchPolicies: ["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies"], + listDeploymentStatuses: ["GET /repos/{owner}/{repo}/deployments/{deployment_id}/statuses"], + listDeployments: ["GET /repos/{owner}/{repo}/deployments"], + listForAuthenticatedUser: ["GET /user/repos"], + listForOrg: ["GET /orgs/{org}/repos"], + listForUser: ["GET /users/{username}/repos"], + listForks: ["GET /repos/{owner}/{repo}/forks"], + listInvitations: ["GET /repos/{owner}/{repo}/invitations"], + listInvitationsForAuthenticatedUser: ["GET /user/repository_invitations"], + listLanguages: ["GET /repos/{owner}/{repo}/languages"], + listPagesBuilds: ["GET /repos/{owner}/{repo}/pages/builds"], + listPublic: ["GET /repositories"], + listPullRequestsAssociatedWithCommit: ["GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls"], + listReleaseAssets: ["GET /repos/{owner}/{repo}/releases/{release_id}/assets"], + listReleases: ["GET /repos/{owner}/{repo}/releases"], + listTags: ["GET /repos/{owner}/{repo}/tags"], + listTeams: ["GET /repos/{owner}/{repo}/teams"], + listWebhookDeliveries: ["GET /repos/{owner}/{repo}/hooks/{hook_id}/deliveries"], + listWebhooks: ["GET /repos/{owner}/{repo}/hooks"], + merge: ["POST /repos/{owner}/{repo}/merges"], + mergeUpstream: ["POST /repos/{owner}/{repo}/merge-upstream"], + pingWebhook: ["POST /repos/{owner}/{repo}/hooks/{hook_id}/pings"], + redeliverWebhookDelivery: ["POST /repos/{owner}/{repo}/hooks/{hook_id}/deliveries/{delivery_id}/attempts"], + removeAppAccessRestrictions: [ + "DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps", + {}, + { mapToData: "apps" } + ], + removeCollaborator: ["DELETE /repos/{owner}/{repo}/collaborators/{username}"], + removeStatusCheckContexts: [ + "DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts", + {}, + { mapToData: "contexts" } + ], + removeStatusCheckProtection: ["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks"], + removeTeamAccessRestrictions: [ + "DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams", + {}, + { mapToData: "teams" } + ], + removeUserAccessRestrictions: [ + "DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users", + {}, + { mapToData: "users" } + ], + renameBranch: ["POST /repos/{owner}/{repo}/branches/{branch}/rename"], + replaceAllTopics: ["PUT /repos/{owner}/{repo}/topics"], + requestPagesBuild: ["POST /repos/{owner}/{repo}/pages/builds"], + setAdminBranchProtection: ["POST /repos/{owner}/{repo}/branches/{branch}/protection/enforce_admins"], + setAppAccessRestrictions: [ + "PUT /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps", + {}, + { mapToData: "apps" } + ], + setStatusCheckContexts: [ + "PUT /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts", + {}, + { mapToData: "contexts" } + ], + setTeamAccessRestrictions: [ + "PUT /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams", + {}, + { mapToData: "teams" } + ], + setUserAccessRestrictions: [ + "PUT /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users", + {}, + { mapToData: "users" } + ], + testPushWebhook: ["POST /repos/{owner}/{repo}/hooks/{hook_id}/tests"], + transfer: ["POST /repos/{owner}/{repo}/transfer"], + update: ["PATCH /repos/{owner}/{repo}"], + updateBranchProtection: ["PUT /repos/{owner}/{repo}/branches/{branch}/protection"], + updateCommitComment: ["PATCH /repos/{owner}/{repo}/comments/{comment_id}"], + updateDeploymentBranchPolicy: ["PUT /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies/{branch_policy_id}"], + updateInformationAboutPagesSite: ["PUT /repos/{owner}/{repo}/pages"], + updateInvitation: ["PATCH /repos/{owner}/{repo}/invitations/{invitation_id}"], + updateOrgRuleset: ["PUT /orgs/{org}/rulesets/{ruleset_id}"], + updatePullRequestReviewProtection: ["PATCH /repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviews"], + updateRelease: ["PATCH /repos/{owner}/{repo}/releases/{release_id}"], + updateReleaseAsset: ["PATCH /repos/{owner}/{repo}/releases/assets/{asset_id}"], + updateRepoRuleset: ["PUT /repos/{owner}/{repo}/rulesets/{ruleset_id}"], + updateStatusCheckPotection: [ + "PATCH /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks", + {}, + { renamed: ["repos", "updateStatusCheckProtection"] } + ], + updateStatusCheckProtection: ["PATCH /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks"], + updateWebhook: ["PATCH /repos/{owner}/{repo}/hooks/{hook_id}"], + updateWebhookConfigForRepo: ["PATCH /repos/{owner}/{repo}/hooks/{hook_id}/config"], + uploadReleaseAsset: ["POST /repos/{owner}/{repo}/releases/{release_id}/assets{?name,label}", { baseUrl: "https://uploads.github.com" }] + }, + search: { + code: ["GET /search/code"], + commits: ["GET /search/commits"], + issuesAndPullRequests: ["GET /search/issues"], + labels: ["GET /search/labels"], + repos: ["GET /search/repositories"], + topics: ["GET /search/topics"], + users: ["GET /search/users"] + }, + secretScanning: { + createPushProtectionBypass: ["POST /repos/{owner}/{repo}/secret-scanning/push-protection-bypasses"], + getAlert: ["GET /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}"], + getScanHistory: ["GET /repos/{owner}/{repo}/secret-scanning/scan-history"], + listAlertsForOrg: ["GET /orgs/{org}/secret-scanning/alerts"], + listAlertsForRepo: ["GET /repos/{owner}/{repo}/secret-scanning/alerts"], + listLocationsForAlert: ["GET /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}/locations"], + listOrgPatternConfigs: ["GET /orgs/{org}/secret-scanning/pattern-configurations"], + updateAlert: ["PATCH /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}"], + updateOrgPatternConfigs: ["PATCH /orgs/{org}/secret-scanning/pattern-configurations"] + }, + securityAdvisories: { + createFork: ["POST /repos/{owner}/{repo}/security-advisories/{ghsa_id}/forks"], + createPrivateVulnerabilityReport: ["POST /repos/{owner}/{repo}/security-advisories/reports"], + createRepositoryAdvisory: ["POST /repos/{owner}/{repo}/security-advisories"], + createRepositoryAdvisoryCveRequest: ["POST /repos/{owner}/{repo}/security-advisories/{ghsa_id}/cve"], + getGlobalAdvisory: ["GET /advisories/{ghsa_id}"], + getRepositoryAdvisory: ["GET /repos/{owner}/{repo}/security-advisories/{ghsa_id}"], + listGlobalAdvisories: ["GET /advisories"], + listOrgRepositoryAdvisories: ["GET /orgs/{org}/security-advisories"], + listRepositoryAdvisories: ["GET /repos/{owner}/{repo}/security-advisories"], + updateRepositoryAdvisory: ["PATCH /repos/{owner}/{repo}/security-advisories/{ghsa_id}"] + }, + teams: { + addOrUpdateMembershipForUserInOrg: ["PUT /orgs/{org}/teams/{team_slug}/memberships/{username}"], + addOrUpdateRepoPermissionsInOrg: ["PUT /orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}"], + checkPermissionsForRepoInOrg: ["GET /orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}"], + create: ["POST /orgs/{org}/teams"], + createDiscussionCommentInOrg: ["POST /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments"], + createDiscussionInOrg: ["POST /orgs/{org}/teams/{team_slug}/discussions"], + deleteDiscussionCommentInOrg: ["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}"], + deleteDiscussionInOrg: ["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}"], + deleteInOrg: ["DELETE /orgs/{org}/teams/{team_slug}"], + getByName: ["GET /orgs/{org}/teams/{team_slug}"], + getDiscussionCommentInOrg: ["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}"], + getDiscussionInOrg: ["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}"], + getMembershipForUserInOrg: ["GET /orgs/{org}/teams/{team_slug}/memberships/{username}"], + list: ["GET /orgs/{org}/teams"], + listChildInOrg: ["GET /orgs/{org}/teams/{team_slug}/teams"], + listDiscussionCommentsInOrg: ["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments"], + listDiscussionsInOrg: ["GET /orgs/{org}/teams/{team_slug}/discussions"], + listForAuthenticatedUser: ["GET /user/teams"], + listMembersInOrg: ["GET /orgs/{org}/teams/{team_slug}/members"], + listPendingInvitationsInOrg: ["GET /orgs/{org}/teams/{team_slug}/invitations"], + listReposInOrg: ["GET /orgs/{org}/teams/{team_slug}/repos"], + removeMembershipForUserInOrg: ["DELETE /orgs/{org}/teams/{team_slug}/memberships/{username}"], + removeRepoInOrg: ["DELETE /orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}"], + updateDiscussionCommentInOrg: ["PATCH /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}"], + updateDiscussionInOrg: ["PATCH /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}"], + updateInOrg: ["PATCH /orgs/{org}/teams/{team_slug}"] + }, + users: { + addEmailForAuthenticated: [ + "POST /user/emails", + {}, + { renamed: ["users", "addEmailForAuthenticatedUser"] } + ], + addEmailForAuthenticatedUser: ["POST /user/emails"], + addSocialAccountForAuthenticatedUser: ["POST /user/social_accounts"], + block: ["PUT /user/blocks/{username}"], + checkBlocked: ["GET /user/blocks/{username}"], + checkFollowingForUser: ["GET /users/{username}/following/{target_user}"], + checkPersonIsFollowedByAuthenticated: ["GET /user/following/{username}"], + createGpgKeyForAuthenticated: [ + "POST /user/gpg_keys", + {}, + { renamed: ["users", "createGpgKeyForAuthenticatedUser"] } + ], + createGpgKeyForAuthenticatedUser: ["POST /user/gpg_keys"], + createPublicSshKeyForAuthenticated: [ + "POST /user/keys", + {}, + { renamed: ["users", "createPublicSshKeyForAuthenticatedUser"] } + ], + createPublicSshKeyForAuthenticatedUser: ["POST /user/keys"], + createSshSigningKeyForAuthenticatedUser: ["POST /user/ssh_signing_keys"], + deleteAttestationsBulk: ["POST /users/{username}/attestations/delete-request"], + deleteAttestationsById: ["DELETE /users/{username}/attestations/{attestation_id}"], + deleteAttestationsBySubjectDigest: ["DELETE /users/{username}/attestations/digest/{subject_digest}"], + deleteEmailForAuthenticated: [ + "DELETE /user/emails", + {}, + { renamed: ["users", "deleteEmailForAuthenticatedUser"] } + ], + deleteEmailForAuthenticatedUser: ["DELETE /user/emails"], + deleteGpgKeyForAuthenticated: [ + "DELETE /user/gpg_keys/{gpg_key_id}", + {}, + { renamed: ["users", "deleteGpgKeyForAuthenticatedUser"] } + ], + deleteGpgKeyForAuthenticatedUser: ["DELETE /user/gpg_keys/{gpg_key_id}"], + deletePublicSshKeyForAuthenticated: [ + "DELETE /user/keys/{key_id}", + {}, + { renamed: ["users", "deletePublicSshKeyForAuthenticatedUser"] } + ], + deletePublicSshKeyForAuthenticatedUser: ["DELETE /user/keys/{key_id}"], + deleteSocialAccountForAuthenticatedUser: ["DELETE /user/social_accounts"], + deleteSshSigningKeyForAuthenticatedUser: ["DELETE /user/ssh_signing_keys/{ssh_signing_key_id}"], + follow: ["PUT /user/following/{username}"], + getAuthenticated: ["GET /user"], + getById: ["GET /user/{account_id}"], + getByUsername: ["GET /users/{username}"], + getContextForUser: ["GET /users/{username}/hovercard"], + getGpgKeyForAuthenticated: [ + "GET /user/gpg_keys/{gpg_key_id}", + {}, + { renamed: ["users", "getGpgKeyForAuthenticatedUser"] } + ], + getGpgKeyForAuthenticatedUser: ["GET /user/gpg_keys/{gpg_key_id}"], + getPublicSshKeyForAuthenticated: [ + "GET /user/keys/{key_id}", + {}, + { renamed: ["users", "getPublicSshKeyForAuthenticatedUser"] } + ], + getPublicSshKeyForAuthenticatedUser: ["GET /user/keys/{key_id}"], + getSshSigningKeyForAuthenticatedUser: ["GET /user/ssh_signing_keys/{ssh_signing_key_id}"], + list: ["GET /users"], + listAttestations: ["GET /users/{username}/attestations/{subject_digest}"], + listAttestationsBulk: ["POST /users/{username}/attestations/bulk-list{?per_page,before,after}"], + listBlockedByAuthenticated: [ + "GET /user/blocks", + {}, + { renamed: ["users", "listBlockedByAuthenticatedUser"] } + ], + listBlockedByAuthenticatedUser: ["GET /user/blocks"], + listEmailsForAuthenticated: [ + "GET /user/emails", + {}, + { renamed: ["users", "listEmailsForAuthenticatedUser"] } + ], + listEmailsForAuthenticatedUser: ["GET /user/emails"], + listFollowedByAuthenticated: [ + "GET /user/following", + {}, + { renamed: ["users", "listFollowedByAuthenticatedUser"] } + ], + listFollowedByAuthenticatedUser: ["GET /user/following"], + listFollowersForAuthenticatedUser: ["GET /user/followers"], + listFollowersForUser: ["GET /users/{username}/followers"], + listFollowingForUser: ["GET /users/{username}/following"], + listGpgKeysForAuthenticated: [ + "GET /user/gpg_keys", + {}, + { renamed: ["users", "listGpgKeysForAuthenticatedUser"] } + ], + listGpgKeysForAuthenticatedUser: ["GET /user/gpg_keys"], + listGpgKeysForUser: ["GET /users/{username}/gpg_keys"], + listPublicEmailsForAuthenticated: [ + "GET /user/public_emails", + {}, + { renamed: ["users", "listPublicEmailsForAuthenticatedUser"] } + ], + listPublicEmailsForAuthenticatedUser: ["GET /user/public_emails"], + listPublicKeysForUser: ["GET /users/{username}/keys"], + listPublicSshKeysForAuthenticated: [ + "GET /user/keys", + {}, + { renamed: ["users", "listPublicSshKeysForAuthenticatedUser"] } + ], + listPublicSshKeysForAuthenticatedUser: ["GET /user/keys"], + listSocialAccountsForAuthenticatedUser: ["GET /user/social_accounts"], + listSocialAccountsForUser: ["GET /users/{username}/social_accounts"], + listSshSigningKeysForAuthenticatedUser: ["GET /user/ssh_signing_keys"], + listSshSigningKeysForUser: ["GET /users/{username}/ssh_signing_keys"], + setPrimaryEmailVisibilityForAuthenticated: [ + "PATCH /user/email/visibility", + {}, + { renamed: ["users", "setPrimaryEmailVisibilityForAuthenticatedUser"] } + ], + setPrimaryEmailVisibilityForAuthenticatedUser: ["PATCH /user/email/visibility"], + unblock: ["DELETE /user/blocks/{username}"], + unfollow: ["DELETE /user/following/{username}"], + updateAuthenticated: ["PATCH /user"] + } +}; +//#endregion +//#region node_modules/@octokit/plugin-rest-endpoint-methods/dist-src/endpoints-to-methods.js +var endpointMethodsMap = /* @__PURE__ */ new Map(); +for (const [scope, endpoints] of Object.entries(endpoints_default)) for (const [methodName, endpoint] of Object.entries(endpoints)) { + const [route, defaults, decorations] = endpoint; + const [method, url] = route.split(/ /); + const endpointDefaults = Object.assign({ + method, + url + }, defaults); + if (!endpointMethodsMap.has(scope)) endpointMethodsMap.set(scope, /* @__PURE__ */ new Map()); + endpointMethodsMap.get(scope).set(methodName, { + scope, + methodName, + endpointDefaults, + decorations + }); +} +var handler = { + has({ scope }, methodName) { + return endpointMethodsMap.get(scope).has(methodName); + }, + getOwnPropertyDescriptor(target, methodName) { + return { + value: this.get(target, methodName), + configurable: true, + writable: true, + enumerable: true + }; + }, + defineProperty(target, methodName, descriptor) { + Object.defineProperty(target.cache, methodName, descriptor); + return true; + }, + deleteProperty(target, methodName) { + delete target.cache[methodName]; + return true; + }, + ownKeys({ scope }) { + return [...endpointMethodsMap.get(scope).keys()]; + }, + set(target, methodName, value) { + return target.cache[methodName] = value; + }, + get({ octokit, scope, cache }, methodName) { + if (cache[methodName]) return cache[methodName]; + const method = endpointMethodsMap.get(scope).get(methodName); + if (!method) return; + const { endpointDefaults, decorations } = method; + if (decorations) cache[methodName] = decorate(octokit, scope, methodName, endpointDefaults, decorations); + else cache[methodName] = octokit.request.defaults(endpointDefaults); + return cache[methodName]; + } +}; +function endpointsToMethods(octokit) { + const newMethods = {}; + for (const scope of endpointMethodsMap.keys()) newMethods[scope] = new Proxy({ + octokit, + scope, + cache: {} + }, handler); + return newMethods; +} +function decorate(octokit, scope, methodName, defaults, decorations) { + const requestWithDefaults = octokit.request.defaults(defaults); + function withDecorations(...args) { + let options = requestWithDefaults.endpoint.merge(...args); + if (decorations.mapToData) { + options = Object.assign({}, options, { + data: options[decorations.mapToData], + [decorations.mapToData]: void 0 + }); + return requestWithDefaults(options); + } + if (decorations.renamed) { + const [newScope, newMethodName] = decorations.renamed; + octokit.log.warn(`octokit.${scope}.${methodName}() has been renamed to octokit.${newScope}.${newMethodName}()`); + } + if (decorations.deprecated) octokit.log.warn(decorations.deprecated); + if (decorations.renamedParameters) { + const options2 = requestWithDefaults.endpoint.merge(...args); + for (const [name, alias] of Object.entries(decorations.renamedParameters)) if (name in options2) { + octokit.log.warn(`"${name}" parameter is deprecated for "octokit.${scope}.${methodName}()". Use "${alias}" instead`); + if (!(alias in options2)) options2[alias] = options2[name]; + delete options2[name]; + } + return requestWithDefaults(options2); + } + return requestWithDefaults(...args); + } + return Object.assign(withDecorations, requestWithDefaults); +} +//#endregion +//#region node_modules/@octokit/plugin-rest-endpoint-methods/dist-src/index.js +function restEndpointMethods(octokit) { + return { rest: endpointsToMethods(octokit) }; +} +restEndpointMethods.VERSION = VERSION$2; +function legacyRestEndpointMethods(octokit) { + const api = endpointsToMethods(octokit); + return { + ...api, + rest: api + }; +} +legacyRestEndpointMethods.VERSION = VERSION$2; +//#endregion +//#region node_modules/@octokit/plugin-paginate-rest/dist-bundle/index.js +var VERSION$1 = "0.0.0-development"; +function normalizePaginatedListResponse(response) { + if (!response.data) return { + ...response, + data: [] + }; + if (!(("total_count" in response.data || "total_commits" in response.data) && !("url" in response.data))) return response; + const incompleteResults = response.data.incomplete_results; + const repositorySelection = response.data.repository_selection; + const totalCount = response.data.total_count; + const totalCommits = response.data.total_commits; + delete response.data.incomplete_results; + delete response.data.repository_selection; + delete response.data.total_count; + delete response.data.total_commits; + const namespaceKey = Object.keys(response.data)[0]; + response.data = response.data[namespaceKey]; + if (typeof incompleteResults !== "undefined") response.data.incomplete_results = incompleteResults; + if (typeof repositorySelection !== "undefined") response.data.repository_selection = repositorySelection; + response.data.total_count = totalCount; + response.data.total_commits = totalCommits; + return response; +} +function iterator(octokit, route, parameters) { + const options = typeof route === "function" ? route.endpoint(parameters) : octokit.request.endpoint(route, parameters); + const requestMethod = typeof route === "function" ? route : octokit.request; + const method = options.method; + const headers = options.headers; + let url = options.url; + return { [Symbol.asyncIterator]: () => ({ async next() { + if (!url) return { done: true }; + try { + const normalizedResponse = normalizePaginatedListResponse(await requestMethod({ + method, + url, + headers + })); + url = ((normalizedResponse.headers.link || "").match(/<([^<>]+)>;\s*rel="next"/) || [])[1]; + if (!url && "total_commits" in normalizedResponse.data) { + const parsedUrl = new URL(normalizedResponse.url); + const params = parsedUrl.searchParams; + const page = parseInt(params.get("page") || "1", 10); + if (page * parseInt(params.get("per_page") || "250", 10) < normalizedResponse.data.total_commits) { + params.set("page", String(page + 1)); + url = parsedUrl.toString(); + } + } + return { value: normalizedResponse }; + } catch (error) { + if (error.status !== 409) throw error; + url = ""; + return { value: { + status: 200, + headers: {}, + data: [] + } }; + } + } }) }; +} +function paginate(octokit, route, parameters, mapFn) { + if (typeof parameters === "function") { + mapFn = parameters; + parameters = void 0; + } + return gather(octokit, [], iterator(octokit, route, parameters)[Symbol.asyncIterator](), mapFn); +} +function gather(octokit, results, iterator2, mapFn) { + return iterator2.next().then((result) => { + if (result.done) return results; + let earlyExit = false; + function done() { + earlyExit = true; + } + results = results.concat(mapFn ? mapFn(result.value, done) : result.value.data); + if (earlyExit) return results; + return gather(octokit, results, iterator2, mapFn); + }); +} +Object.assign(paginate, { iterator }); +function paginateRest(octokit) { + return { paginate: Object.assign(paginate.bind(null, octokit), { iterator: iterator.bind(null, octokit) }) }; +} +paginateRest.VERSION = VERSION$1; +new Context(); +var baseUrl = getApiBaseUrl(); +var defaults = { + baseUrl, + request: { + agent: getProxyAgent(baseUrl), + fetch: getProxyFetch(baseUrl) + } +}; +var GitHub = Octokit.plugin(restEndpointMethods, paginateRest).defaults(defaults); +/** +* Convience function to correctly format Octokit Options to pass into the constructor. +* +* @param token the repo PAT or GITHUB_TOKEN +* @param options other options to set +*/ +function getOctokitOptions(token, options) { + const opts = Object.assign({}, options || {}); + const auth = getAuthString(token, opts); + if (auth) opts.auth = auth; + const userAgent = getUserAgentWithOrchestrationId(opts.userAgent); + if (userAgent) opts.userAgent = userAgent; + return opts; +} +//#endregion +//#region node_modules/@actions/github/lib/github.js +var context = new Context(); +/** +* Returns a hydrated octokit ready to use for GitHub Actions +* +* @param token the repo PAT or GITHUB_TOKEN +* @param options other options to set +*/ +function getOctokit$1(token, options, ...additionalPlugins) { + return new (GitHub.plugin(...additionalPlugins))(getOctokitOptions(token, options)); +} +//#endregion +//#region node_modules/@octokit/plugin-paginate-graphql/dist-bundle/index.js +var generateMessage = (path, cursorValue) => `The cursor at "${path.join(",")}" did not change its value "${cursorValue}" after a page transition. Please make sure your that your query is set up correctly.`; +var MissingCursorChange = class extends Error { + constructor(pageInfo, cursorValue) { + super(generateMessage(pageInfo.pathInQuery, cursorValue)); + this.pageInfo = pageInfo; + this.cursorValue = cursorValue; + if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor); + } + name = "MissingCursorChangeError"; +}; +var MissingPageInfo = class extends Error { + constructor(response) { + super(`No pageInfo property found in response. Please make sure to specify the pageInfo in your query. Response-Data: ${JSON.stringify(response, null, 2)}`); + this.response = response; + if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor); + } + name = "MissingPageInfo"; +}; +var isObject = (value) => Object.prototype.toString.call(value) === "[object Object]"; +function findPaginatedResourcePath(responseData) { + const paginatedResourcePath = deepFindPathToProperty(responseData, "pageInfo"); + if (paginatedResourcePath.length === 0) throw new MissingPageInfo(responseData); + return paginatedResourcePath; +} +var deepFindPathToProperty = (object, searchProp, path = []) => { + for (const key of Object.keys(object)) { + const currentPath = [...path, key]; + const currentValue = object[key]; + if (isObject(currentValue)) { + if (currentValue.hasOwnProperty(searchProp)) return currentPath; + const result = deepFindPathToProperty(currentValue, searchProp, currentPath); + if (result.length > 0) return result; + } + } + return []; +}; +var get = (object, path) => { + return path.reduce((current, nextProperty) => current[nextProperty], object); +}; +var set = (object, path, mutator) => { + const lastProperty = path[path.length - 1]; + const parent = get(object, [...path].slice(0, -1)); + if (typeof mutator === "function") parent[lastProperty] = mutator(parent[lastProperty]); + else parent[lastProperty] = mutator; +}; +var extractPageInfos = (responseData) => { + const pageInfoPath = findPaginatedResourcePath(responseData); + return { + pathInQuery: pageInfoPath, + pageInfo: get(responseData, [...pageInfoPath, "pageInfo"]) + }; +}; +var isForwardSearch = (givenPageInfo) => { + return givenPageInfo.hasOwnProperty("hasNextPage"); +}; +var getCursorFrom = (pageInfo) => isForwardSearch(pageInfo) ? pageInfo.endCursor : pageInfo.startCursor; +var hasAnotherPage = (pageInfo) => isForwardSearch(pageInfo) ? pageInfo.hasNextPage : pageInfo.hasPreviousPage; +var createIterator = (octokit) => { + return (query, initialParameters = {}) => { + let nextPageExists = true; + let parameters = { ...initialParameters }; + return { [Symbol.asyncIterator]: () => ({ async next() { + if (!nextPageExists) return { + done: true, + value: {} + }; + const response = await octokit.graphql(query, parameters); + const pageInfoContext = extractPageInfos(response); + const nextCursorValue = getCursorFrom(pageInfoContext.pageInfo); + nextPageExists = hasAnotherPage(pageInfoContext.pageInfo); + if (nextPageExists && nextCursorValue === parameters.cursor) throw new MissingCursorChange(pageInfoContext, nextCursorValue); + parameters = { + ...parameters, + cursor: nextCursorValue + }; + return { + done: false, + value: response + }; + } }) }; + }; +}; +var mergeResponses = (response1, response2) => { + if (Object.keys(response1).length === 0) return Object.assign(response1, response2); + const path = findPaginatedResourcePath(response1); + const nodesPath = [...path, "nodes"]; + const newNodes = get(response2, nodesPath); + if (newNodes) set(response1, nodesPath, (values) => { + return [...values, ...newNodes]; + }); + const edgesPath = [...path, "edges"]; + const newEdges = get(response2, edgesPath); + if (newEdges) set(response1, edgesPath, (values) => { + return [...values, ...newEdges]; + }); + const pageInfoPath = [...path, "pageInfo"]; + set(response1, pageInfoPath, get(response2, pageInfoPath)); + return response1; +}; +var createPaginate = (octokit) => { + const iterator = createIterator(octokit); + return async (query, initialParameters = {}) => { + let mergedResponse = {}; + for await (const response of iterator(query, initialParameters)) mergedResponse = mergeResponses(mergedResponse, response); + return mergedResponse; + }; +}; +function paginateGraphQL(octokit) { + return { graphql: Object.assign(octokit.graphql, { paginate: Object.assign(createPaginate(octokit), { iterator: createIterator(octokit) }) }) }; +} +//#endregion +//#region node_modules/@octokit/plugin-retry/dist-bundle/index.js +var import_light = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => { + /** + * This file contains the Bottleneck library (MIT), compiled to ES2017, and without Clustering support. + * https://github.com/SGrondin/bottleneck + */ + (function(global, factory) { + typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory() : typeof define === "function" && define.amd ? define(factory) : global.Bottleneck = factory(); + })(exports, (function() { + "use strict"; + var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {}; + function getCjsExportFromNamespace(n) { + return n && n["default"] || n; + } + var load = function(received, defaults, onto = {}) { + var k, ref, v; + for (k in defaults) { + v = defaults[k]; + onto[k] = (ref = received[k]) != null ? ref : v; + } + return onto; + }; + var overwrite = function(received, defaults, onto = {}) { + var k, v; + for (k in received) { + v = received[k]; + if (defaults[k] !== void 0) onto[k] = v; + } + return onto; + }; + var parser = { + load, + overwrite + }; + var DLList_1 = class DLList { + constructor(incr, decr) { + this.incr = incr; + this.decr = decr; + this._first = null; + this._last = null; + this.length = 0; + } + push(value) { + var node; + this.length++; + if (typeof this.incr === "function") this.incr(); + node = { + value, + prev: this._last, + next: null + }; + if (this._last != null) { + this._last.next = node; + this._last = node; + } else this._first = this._last = node; + } + shift() { + var value; + if (this._first == null) return; + else { + this.length--; + if (typeof this.decr === "function") this.decr(); + } + value = this._first.value; + if ((this._first = this._first.next) != null) this._first.prev = null; + else this._last = null; + return value; + } + first() { + if (this._first != null) return this._first.value; + } + getArray() { + var node = this._first, ref, results = []; + while (node != null) results.push((ref = node, node = node.next, ref.value)); + return results; + } + forEachShift(cb) { + var node = this.shift(); + while (node != null) cb(node), node = this.shift(); + } + debug() { + var node = this._first, ref, ref1, ref2, results = []; + while (node != null) results.push((ref = node, node = node.next, { + value: ref.value, + prev: (ref1 = ref.prev) != null ? ref1.value : void 0, + next: (ref2 = ref.next) != null ? ref2.value : void 0 + })); + return results; + } + }; + var Events_1 = class Events { + constructor(instance) { + this.instance = instance; + this._events = {}; + if (this.instance.on != null || this.instance.once != null || this.instance.removeAllListeners != null) throw new Error("An Emitter already exists for this object"); + this.instance.on = (name, cb) => { + return this._addListener(name, "many", cb); + }; + this.instance.once = (name, cb) => { + return this._addListener(name, "once", cb); + }; + this.instance.removeAllListeners = (name = null) => { + if (name != null) return delete this._events[name]; + else return this._events = {}; + }; + } + _addListener(name, status, cb) { + var base; + if ((base = this._events)[name] == null) base[name] = []; + this._events[name].push({ + cb, + status + }); + return this.instance; + } + listenerCount(name) { + if (this._events[name] != null) return this._events[name].length; + else return 0; + } + async trigger(name, ...args) { + var e, promises; + try { + if (name !== "debug") this.trigger("debug", `Event triggered: ${name}`, args); + if (this._events[name] == null) return; + this._events[name] = this._events[name].filter(function(listener) { + return listener.status !== "none"; + }); + promises = this._events[name].map(async (listener) => { + var e, returned; + if (listener.status === "none") return; + if (listener.status === "once") listener.status = "none"; + try { + returned = typeof listener.cb === "function" ? listener.cb(...args) : void 0; + if (typeof (returned != null ? returned.then : void 0) === "function") return await returned; + else return returned; + } catch (error) { + e = error; + this.trigger("error", e); + return null; + } + }); + return (await Promise.all(promises)).find(function(x) { + return x != null; + }); + } catch (error) { + e = error; + this.trigger("error", e); + return null; + } + } + }; + var DLList$1 = DLList_1, Events$1 = Events_1; + var Queues_1 = class Queues { + constructor(num_priorities) { + this.Events = new Events$1(this); + this._length = 0; + this._lists = (function() { + var j, ref, results = []; + for (j = 1, ref = num_priorities; 1 <= ref ? j <= ref : j >= ref; 1 <= ref ? ++j : --j) results.push(new DLList$1((() => { + return this.incr(); + }), (() => { + return this.decr(); + }))); + return results; + }).call(this); + } + incr() { + if (this._length++ === 0) return this.Events.trigger("leftzero"); + } + decr() { + if (--this._length === 0) return this.Events.trigger("zero"); + } + push(job) { + return this._lists[job.options.priority].push(job); + } + queued(priority) { + if (priority != null) return this._lists[priority].length; + else return this._length; + } + shiftAll(fn) { + return this._lists.forEach(function(list) { + return list.forEachShift(fn); + }); + } + getFirst(arr = this._lists) { + var j, len, list; + for (j = 0, len = arr.length; j < len; j++) { + list = arr[j]; + if (list.length > 0) return list; + } + return []; + } + shiftLastFrom(priority) { + return this.getFirst(this._lists.slice(priority).reverse()).shift(); + } + }; + var BottleneckError_1 = class BottleneckError extends Error {}; + var BottleneckError$1, DEFAULT_PRIORITY, Job, NUM_PRIORITIES = 10, parser$1; + DEFAULT_PRIORITY = 5; + parser$1 = parser; + BottleneckError$1 = BottleneckError_1; + Job = class Job { + constructor(task, args, options, jobDefaults, rejectOnDrop, Events, _states, Promise) { + this.task = task; + this.args = args; + this.rejectOnDrop = rejectOnDrop; + this.Events = Events; + this._states = _states; + this.Promise = Promise; + this.options = parser$1.load(options, jobDefaults); + this.options.priority = this._sanitizePriority(this.options.priority); + if (this.options.id === jobDefaults.id) this.options.id = `${this.options.id}-${this._randomIndex()}`; + this.promise = new this.Promise((_resolve, _reject) => { + this._resolve = _resolve; + this._reject = _reject; + }); + this.retryCount = 0; + } + _sanitizePriority(priority) { + var sProperty = ~~priority !== priority ? DEFAULT_PRIORITY : priority; + if (sProperty < 0) return 0; + else if (sProperty > NUM_PRIORITIES - 1) return NUM_PRIORITIES - 1; + else return sProperty; + } + _randomIndex() { + return Math.random().toString(36).slice(2); + } + doDrop({ error, message = "This job has been dropped by Bottleneck" } = {}) { + if (this._states.remove(this.options.id)) { + if (this.rejectOnDrop) this._reject(error != null ? error : new BottleneckError$1(message)); + this.Events.trigger("dropped", { + args: this.args, + options: this.options, + task: this.task, + promise: this.promise + }); + return true; + } else return false; + } + _assertStatus(expected) { + var status = this._states.jobStatus(this.options.id); + if (!(status === expected || expected === "DONE" && status === null)) throw new BottleneckError$1(`Invalid job status ${status}, expected ${expected}. Please open an issue at https://github.com/SGrondin/bottleneck/issues`); + } + doReceive() { + this._states.start(this.options.id); + return this.Events.trigger("received", { + args: this.args, + options: this.options + }); + } + doQueue(reachedHWM, blocked) { + this._assertStatus("RECEIVED"); + this._states.next(this.options.id); + return this.Events.trigger("queued", { + args: this.args, + options: this.options, + reachedHWM, + blocked + }); + } + doRun() { + if (this.retryCount === 0) { + this._assertStatus("QUEUED"); + this._states.next(this.options.id); + } else this._assertStatus("EXECUTING"); + return this.Events.trigger("scheduled", { + args: this.args, + options: this.options + }); + } + async doExecute(chained, clearGlobalState, run, free) { + var error, eventInfo, passed; + if (this.retryCount === 0) { + this._assertStatus("RUNNING"); + this._states.next(this.options.id); + } else this._assertStatus("EXECUTING"); + eventInfo = { + args: this.args, + options: this.options, + retryCount: this.retryCount + }; + this.Events.trigger("executing", eventInfo); + try { + passed = await (chained != null ? chained.schedule(this.options, this.task, ...this.args) : this.task(...this.args)); + if (clearGlobalState()) { + this.doDone(eventInfo); + await free(this.options, eventInfo); + this._assertStatus("DONE"); + return this._resolve(passed); + } + } catch (error1) { + error = error1; + return this._onFailure(error, eventInfo, clearGlobalState, run, free); + } + } + doExpire(clearGlobalState, run, free) { + var error, eventInfo; + if (this._states.jobStatus(this.options.id === "RUNNING")) this._states.next(this.options.id); + this._assertStatus("EXECUTING"); + eventInfo = { + args: this.args, + options: this.options, + retryCount: this.retryCount + }; + error = new BottleneckError$1(`This job timed out after ${this.options.expiration} ms.`); + return this._onFailure(error, eventInfo, clearGlobalState, run, free); + } + async _onFailure(error, eventInfo, clearGlobalState, run, free) { + var retry, retryAfter; + if (clearGlobalState()) { + retry = await this.Events.trigger("failed", error, eventInfo); + if (retry != null) { + retryAfter = ~~retry; + this.Events.trigger("retry", `Retrying ${this.options.id} after ${retryAfter} ms`, eventInfo); + this.retryCount++; + return run(retryAfter); + } else { + this.doDone(eventInfo); + await free(this.options, eventInfo); + this._assertStatus("DONE"); + return this._reject(error); + } + } + } + doDone(eventInfo) { + this._assertStatus("EXECUTING"); + this._states.next(this.options.id); + return this.Events.trigger("done", eventInfo); + } + }; + var Job_1 = Job; + var BottleneckError$2, LocalDatastore, parser$2 = parser; + BottleneckError$2 = BottleneckError_1; + LocalDatastore = class LocalDatastore { + constructor(instance, storeOptions, storeInstanceOptions) { + this.instance = instance; + this.storeOptions = storeOptions; + this.clientId = this.instance._randomIndex(); + parser$2.load(storeInstanceOptions, storeInstanceOptions, this); + this._nextRequest = this._lastReservoirRefresh = this._lastReservoirIncrease = Date.now(); + this._running = 0; + this._done = 0; + this._unblockTime = 0; + this.ready = this.Promise.resolve(); + this.clients = {}; + this._startHeartbeat(); + } + _startHeartbeat() { + var base; + if (this.heartbeat == null && (this.storeOptions.reservoirRefreshInterval != null && this.storeOptions.reservoirRefreshAmount != null || this.storeOptions.reservoirIncreaseInterval != null && this.storeOptions.reservoirIncreaseAmount != null)) return typeof (base = this.heartbeat = setInterval(() => { + var amount, incr, maximum, now = Date.now(), reservoir; + if (this.storeOptions.reservoirRefreshInterval != null && now >= this._lastReservoirRefresh + this.storeOptions.reservoirRefreshInterval) { + this._lastReservoirRefresh = now; + this.storeOptions.reservoir = this.storeOptions.reservoirRefreshAmount; + this.instance._drainAll(this.computeCapacity()); + } + if (this.storeOptions.reservoirIncreaseInterval != null && now >= this._lastReservoirIncrease + this.storeOptions.reservoirIncreaseInterval) { + ({reservoirIncreaseAmount: amount, reservoirIncreaseMaximum: maximum, reservoir} = this.storeOptions); + this._lastReservoirIncrease = now; + incr = maximum != null ? Math.min(amount, maximum - reservoir) : amount; + if (incr > 0) { + this.storeOptions.reservoir += incr; + return this.instance._drainAll(this.computeCapacity()); + } + } + }, this.heartbeatInterval)).unref === "function" ? base.unref() : void 0; + else return clearInterval(this.heartbeat); + } + async __publish__(message) { + await this.yieldLoop(); + return this.instance.Events.trigger("message", message.toString()); + } + async __disconnect__(flush) { + await this.yieldLoop(); + clearInterval(this.heartbeat); + return this.Promise.resolve(); + } + yieldLoop(t = 0) { + return new this.Promise(function(resolve, reject) { + return setTimeout(resolve, t); + }); + } + computePenalty() { + var ref; + return (ref = this.storeOptions.penalty) != null ? ref : 15 * this.storeOptions.minTime || 5e3; + } + async __updateSettings__(options) { + await this.yieldLoop(); + parser$2.overwrite(options, options, this.storeOptions); + this._startHeartbeat(); + this.instance._drainAll(this.computeCapacity()); + return true; + } + async __running__() { + await this.yieldLoop(); + return this._running; + } + async __queued__() { + await this.yieldLoop(); + return this.instance.queued(); + } + async __done__() { + await this.yieldLoop(); + return this._done; + } + async __groupCheck__(time) { + await this.yieldLoop(); + return this._nextRequest + this.timeout < time; + } + computeCapacity() { + var maxConcurrent, reservoir; + ({maxConcurrent, reservoir} = this.storeOptions); + if (maxConcurrent != null && reservoir != null) return Math.min(maxConcurrent - this._running, reservoir); + else if (maxConcurrent != null) return maxConcurrent - this._running; + else if (reservoir != null) return reservoir; + else return null; + } + conditionsCheck(weight) { + var capacity = this.computeCapacity(); + return capacity == null || weight <= capacity; + } + async __incrementReservoir__(incr) { + var reservoir; + await this.yieldLoop(); + reservoir = this.storeOptions.reservoir += incr; + this.instance._drainAll(this.computeCapacity()); + return reservoir; + } + async __currentReservoir__() { + await this.yieldLoop(); + return this.storeOptions.reservoir; + } + isBlocked(now) { + return this._unblockTime >= now; + } + check(weight, now) { + return this.conditionsCheck(weight) && this._nextRequest - now <= 0; + } + async __check__(weight) { + var now; + await this.yieldLoop(); + now = Date.now(); + return this.check(weight, now); + } + async __register__(index, weight, expiration) { + var now, wait; + await this.yieldLoop(); + now = Date.now(); + if (this.conditionsCheck(weight)) { + this._running += weight; + if (this.storeOptions.reservoir != null) this.storeOptions.reservoir -= weight; + wait = Math.max(this._nextRequest - now, 0); + this._nextRequest = now + wait + this.storeOptions.minTime; + return { + success: true, + wait, + reservoir: this.storeOptions.reservoir + }; + } else return { success: false }; + } + strategyIsBlock() { + return this.storeOptions.strategy === 3; + } + async __submit__(queueLength, weight) { + var blocked, now, reachedHWM; + await this.yieldLoop(); + if (this.storeOptions.maxConcurrent != null && weight > this.storeOptions.maxConcurrent) throw new BottleneckError$2(`Impossible to add a job having a weight of ${weight} to a limiter having a maxConcurrent setting of ${this.storeOptions.maxConcurrent}`); + now = Date.now(); + reachedHWM = this.storeOptions.highWater != null && queueLength === this.storeOptions.highWater && !this.check(weight, now); + blocked = this.strategyIsBlock() && (reachedHWM || this.isBlocked(now)); + if (blocked) { + this._unblockTime = now + this.computePenalty(); + this._nextRequest = this._unblockTime + this.storeOptions.minTime; + this.instance._dropAllQueued(); + } + return { + reachedHWM, + blocked, + strategy: this.storeOptions.strategy + }; + } + async __free__(index, weight) { + await this.yieldLoop(); + this._running -= weight; + this._done += weight; + this.instance._drainAll(this.computeCapacity()); + return { running: this._running }; + } + }; + var LocalDatastore_1 = LocalDatastore; + var BottleneckError$3 = BottleneckError_1; + var States_1 = class States { + constructor(status1) { + this.status = status1; + this._jobs = {}; + this.counts = this.status.map(function() { + return 0; + }); + } + next(id) { + var current = this._jobs[id], next = current + 1; + if (current != null && next < this.status.length) { + this.counts[current]--; + this.counts[next]++; + return this._jobs[id]++; + } else if (current != null) { + this.counts[current]--; + return delete this._jobs[id]; + } + } + start(id) { + var initial = 0; + this._jobs[id] = initial; + return this.counts[initial]++; + } + remove(id) { + var current = this._jobs[id]; + if (current != null) { + this.counts[current]--; + delete this._jobs[id]; + } + return current != null; + } + jobStatus(id) { + var ref; + return (ref = this.status[this._jobs[id]]) != null ? ref : null; + } + statusJobs(status) { + var k, pos, ref, results, v; + if (status != null) { + pos = this.status.indexOf(status); + if (pos < 0) throw new BottleneckError$3(`status must be one of ${this.status.join(", ")}`); + ref = this._jobs; + results = []; + for (k in ref) { + v = ref[k]; + if (v === pos) results.push(k); + } + return results; + } else return Object.keys(this._jobs); + } + statusCounts() { + return this.counts.reduce(((acc, v, i) => { + acc[this.status[i]] = v; + return acc; + }), {}); + } + }; + var DLList$2 = DLList_1; + var Sync_1 = class Sync { + constructor(name, Promise) { + this.schedule = this.schedule.bind(this); + this.name = name; + this.Promise = Promise; + this._running = 0; + this._queue = new DLList$2(); + } + isEmpty() { + return this._queue.length === 0; + } + async _tryToRun() { + var args, cb, error, reject, resolve, returned, task; + if (this._running < 1 && this._queue.length > 0) { + this._running++; + ({task, args, resolve, reject} = this._queue.shift()); + cb = await (async function() { + try { + returned = await task(...args); + return function() { + return resolve(returned); + }; + } catch (error1) { + error = error1; + return function() { + return reject(error); + }; + } + })(); + this._running--; + this._tryToRun(); + return cb(); + } + } + schedule(task, ...args) { + var promise, reject, resolve = reject = null; + promise = new this.Promise(function(_resolve, _reject) { + resolve = _resolve; + return reject = _reject; + }); + this._queue.push({ + task, + args, + resolve, + reject + }); + this._tryToRun(); + return promise; + } + }; + var version = "2.19.5"; + var version$2 = /*#__PURE__*/ Object.freeze({ + version, + default: { version } + }); + var require$$2 = () => console.log("You must import the full version of Bottleneck in order to use this feature."); + var require$$3 = () => console.log("You must import the full version of Bottleneck in order to use this feature."); + var require$$4 = () => console.log("You must import the full version of Bottleneck in order to use this feature."); + var Events$2, Group, IORedisConnection$1, RedisConnection$1, Scripts$1, parser$3 = parser; + Events$2 = Events_1; + RedisConnection$1 = require$$2; + IORedisConnection$1 = require$$3; + Scripts$1 = require$$4; + Group = (function() { + class Group { + constructor(limiterOptions = {}) { + this.deleteKey = this.deleteKey.bind(this); + this.limiterOptions = limiterOptions; + parser$3.load(this.limiterOptions, this.defaults, this); + this.Events = new Events$2(this); + this.instances = {}; + this.Bottleneck = Bottleneck_1; + this._startAutoCleanup(); + this.sharedConnection = this.connection != null; + if (this.connection == null) { + if (this.limiterOptions.datastore === "redis") this.connection = new RedisConnection$1(Object.assign({}, this.limiterOptions, { Events: this.Events })); + else if (this.limiterOptions.datastore === "ioredis") this.connection = new IORedisConnection$1(Object.assign({}, this.limiterOptions, { Events: this.Events })); + } + } + key(key = "") { + var ref; + return (ref = this.instances[key]) != null ? ref : (() => { + var limiter = this.instances[key] = new this.Bottleneck(Object.assign(this.limiterOptions, { + id: `${this.id}-${key}`, + timeout: this.timeout, + connection: this.connection + })); + this.Events.trigger("created", limiter, key); + return limiter; + })(); + } + async deleteKey(key = "") { + var deleted, instance = this.instances[key]; + if (this.connection) deleted = await this.connection.__runCommand__(["del", ...Scripts$1.allKeys(`${this.id}-${key}`)]); + if (instance != null) { + delete this.instances[key]; + await instance.disconnect(); + } + return instance != null || deleted > 0; + } + limiters() { + var k, ref = this.instances, results = [], v; + for (k in ref) { + v = ref[k]; + results.push({ + key: k, + limiter: v + }); + } + return results; + } + keys() { + return Object.keys(this.instances); + } + async clusterKeys() { + var cursor, end, found, i, k, keys, len, next, start; + if (this.connection == null) return this.Promise.resolve(this.keys()); + keys = []; + cursor = null; + start = `b_${this.id}-`.length; + end = 9; + while (cursor !== 0) { + [next, found] = await this.connection.__runCommand__([ + "scan", + cursor != null ? cursor : 0, + "match", + `b_${this.id}-*_settings`, + "count", + 1e4 + ]); + cursor = ~~next; + for (i = 0, len = found.length; i < len; i++) { + k = found[i]; + keys.push(k.slice(start, -end)); + } + } + return keys; + } + _startAutoCleanup() { + var base; + clearInterval(this.interval); + return typeof (base = this.interval = setInterval(async () => { + var e, k, ref, results, time = Date.now(), v; + ref = this.instances; + results = []; + for (k in ref) { + v = ref[k]; + try { + if (await v._store.__groupCheck__(time)) results.push(this.deleteKey(k)); + else results.push(void 0); + } catch (error) { + e = error; + results.push(v.Events.trigger("error", e)); + } + } + return results; + }, this.timeout / 2)).unref === "function" ? base.unref() : void 0; + } + updateSettings(options = {}) { + parser$3.overwrite(options, this.defaults, this); + parser$3.overwrite(options, options, this.limiterOptions); + if (options.timeout != null) return this._startAutoCleanup(); + } + disconnect(flush = true) { + var ref; + if (!this.sharedConnection) return (ref = this.connection) != null ? ref.disconnect(flush) : void 0; + } + } + Group.prototype.defaults = { + timeout: 1e3 * 60 * 5, + connection: null, + Promise, + id: "group-key" + }; + return Group; + }).call(commonjsGlobal); + var Group_1 = Group; + var Batcher, Events$3, parser$4 = parser; + Events$3 = Events_1; + Batcher = (function() { + class Batcher { + constructor(options = {}) { + this.options = options; + parser$4.load(this.options, this.defaults, this); + this.Events = new Events$3(this); + this._arr = []; + this._resetPromise(); + this._lastFlush = Date.now(); + } + _resetPromise() { + return this._promise = new this.Promise((res, rej) => { + return this._resolve = res; + }); + } + _flush() { + clearTimeout(this._timeout); + this._lastFlush = Date.now(); + this._resolve(); + this.Events.trigger("batch", this._arr); + this._arr = []; + return this._resetPromise(); + } + add(data) { + var ret; + this._arr.push(data); + ret = this._promise; + if (this._arr.length === this.maxSize) this._flush(); + else if (this.maxTime != null && this._arr.length === 1) this._timeout = setTimeout(() => { + return this._flush(); + }, this.maxTime); + return ret; + } + } + Batcher.prototype.defaults = { + maxTime: null, + maxSize: null, + Promise + }; + return Batcher; + }).call(commonjsGlobal); + var Batcher_1 = Batcher; + var require$$4$1 = () => console.log("You must import the full version of Bottleneck in order to use this feature."); + var require$$8 = getCjsExportFromNamespace(version$2); + var Bottleneck, DEFAULT_PRIORITY$1, Events$4, Job$1, LocalDatastore$1, NUM_PRIORITIES$1, Queues$1, RedisDatastore$1, States$1, Sync$1, parser$5, splice = [].splice; + NUM_PRIORITIES$1 = 10; + DEFAULT_PRIORITY$1 = 5; + parser$5 = parser; + Queues$1 = Queues_1; + Job$1 = Job_1; + LocalDatastore$1 = LocalDatastore_1; + RedisDatastore$1 = require$$4$1; + Events$4 = Events_1; + States$1 = States_1; + Sync$1 = Sync_1; + Bottleneck = (function() { + class Bottleneck { + constructor(options = {}, ...invalid) { + var storeInstanceOptions, storeOptions; + this._addToQueue = this._addToQueue.bind(this); + this._validateOptions(options, invalid); + parser$5.load(options, this.instanceDefaults, this); + this._queues = new Queues$1(NUM_PRIORITIES$1); + this._scheduled = {}; + this._states = new States$1([ + "RECEIVED", + "QUEUED", + "RUNNING", + "EXECUTING" + ].concat(this.trackDoneStatus ? ["DONE"] : [])); + this._limiter = null; + this.Events = new Events$4(this); + this._submitLock = new Sync$1("submit", this.Promise); + this._registerLock = new Sync$1("register", this.Promise); + storeOptions = parser$5.load(options, this.storeDefaults, {}); + this._store = (function() { + if (this.datastore === "redis" || this.datastore === "ioredis" || this.connection != null) { + storeInstanceOptions = parser$5.load(options, this.redisStoreDefaults, {}); + return new RedisDatastore$1(this, storeOptions, storeInstanceOptions); + } else if (this.datastore === "local") { + storeInstanceOptions = parser$5.load(options, this.localStoreDefaults, {}); + return new LocalDatastore$1(this, storeOptions, storeInstanceOptions); + } else throw new Bottleneck.prototype.BottleneckError(`Invalid datastore type: ${this.datastore}`); + }).call(this); + this._queues.on("leftzero", () => { + var ref; + return (ref = this._store.heartbeat) != null ? typeof ref.ref === "function" ? ref.ref() : void 0 : void 0; + }); + this._queues.on("zero", () => { + var ref; + return (ref = this._store.heartbeat) != null ? typeof ref.unref === "function" ? ref.unref() : void 0 : void 0; + }); + } + _validateOptions(options, invalid) { + if (!(options != null && typeof options === "object" && invalid.length === 0)) throw new Bottleneck.prototype.BottleneckError("Bottleneck v2 takes a single object argument. Refer to https://github.com/SGrondin/bottleneck#upgrading-to-v2 if you're upgrading from Bottleneck v1."); + } + ready() { + return this._store.ready; + } + clients() { + return this._store.clients; + } + channel() { + return `b_${this.id}`; + } + channel_client() { + return `b_${this.id}_${this._store.clientId}`; + } + publish(message) { + return this._store.__publish__(message); + } + disconnect(flush = true) { + return this._store.__disconnect__(flush); + } + chain(_limiter) { + this._limiter = _limiter; + return this; + } + queued(priority) { + return this._queues.queued(priority); + } + clusterQueued() { + return this._store.__queued__(); + } + empty() { + return this.queued() === 0 && this._submitLock.isEmpty(); + } + running() { + return this._store.__running__(); + } + done() { + return this._store.__done__(); + } + jobStatus(id) { + return this._states.jobStatus(id); + } + jobs(status) { + return this._states.statusJobs(status); + } + counts() { + return this._states.statusCounts(); + } + _randomIndex() { + return Math.random().toString(36).slice(2); + } + check(weight = 1) { + return this._store.__check__(weight); + } + _clearGlobalState(index) { + if (this._scheduled[index] != null) { + clearTimeout(this._scheduled[index].expiration); + delete this._scheduled[index]; + return true; + } else return false; + } + async _free(index, job, options, eventInfo) { + var e, running; + try { + ({running} = await this._store.__free__(index, options.weight)); + this.Events.trigger("debug", `Freed ${options.id}`, eventInfo); + if (running === 0 && this.empty()) return this.Events.trigger("idle"); + } catch (error1) { + e = error1; + return this.Events.trigger("error", e); + } + } + _run(index, job, wait) { + var clearGlobalState, free, run; + job.doRun(); + clearGlobalState = this._clearGlobalState.bind(this, index); + run = this._run.bind(this, index, job); + free = this._free.bind(this, index, job); + return this._scheduled[index] = { + timeout: setTimeout(() => { + return job.doExecute(this._limiter, clearGlobalState, run, free); + }, wait), + expiration: job.options.expiration != null ? setTimeout(function() { + return job.doExpire(clearGlobalState, run, free); + }, wait + job.options.expiration) : void 0, + job + }; + } + _drainOne(capacity) { + return this._registerLock.schedule(() => { + var args, index, next, options, queue; + if (this.queued() === 0) return this.Promise.resolve(null); + queue = this._queues.getFirst(); + ({options, args} = next = queue.first()); + if (capacity != null && options.weight > capacity) return this.Promise.resolve(null); + this.Events.trigger("debug", `Draining ${options.id}`, { + args, + options + }); + index = this._randomIndex(); + return this._store.__register__(index, options.weight, options.expiration).then(({ success, wait, reservoir }) => { + var empty; + this.Events.trigger("debug", `Drained ${options.id}`, { + success, + args, + options + }); + if (success) { + queue.shift(); + empty = this.empty(); + if (empty) this.Events.trigger("empty"); + if (reservoir === 0) this.Events.trigger("depleted", empty); + this._run(index, next, wait); + return this.Promise.resolve(options.weight); + } else return this.Promise.resolve(null); + }); + }); + } + _drainAll(capacity, total = 0) { + return this._drainOne(capacity).then((drained) => { + var newCapacity; + if (drained != null) { + newCapacity = capacity != null ? capacity - drained : capacity; + return this._drainAll(newCapacity, total + drained); + } else return this.Promise.resolve(total); + }).catch((e) => { + return this.Events.trigger("error", e); + }); + } + _dropAllQueued(message) { + return this._queues.shiftAll(function(job) { + return job.doDrop({ message }); + }); + } + stop(options = {}) { + var done, waitForExecuting; + options = parser$5.load(options, this.stopDefaults); + waitForExecuting = (at) => { + var finished = () => { + var counts = this._states.counts; + return counts[0] + counts[1] + counts[2] + counts[3] === at; + }; + return new this.Promise((resolve, reject) => { + if (finished()) return resolve(); + else return this.on("done", () => { + if (finished()) { + this.removeAllListeners("done"); + return resolve(); + } + }); + }); + }; + done = options.dropWaitingJobs ? (this._run = function(index, next) { + return next.doDrop({ message: options.dropErrorMessage }); + }, this._drainOne = () => { + return this.Promise.resolve(null); + }, this._registerLock.schedule(() => { + return this._submitLock.schedule(() => { + var k, ref = this._scheduled, v; + for (k in ref) { + v = ref[k]; + if (this.jobStatus(v.job.options.id) === "RUNNING") { + clearTimeout(v.timeout); + clearTimeout(v.expiration); + v.job.doDrop({ message: options.dropErrorMessage }); + } + } + this._dropAllQueued(options.dropErrorMessage); + return waitForExecuting(0); + }); + })) : this.schedule({ + priority: NUM_PRIORITIES$1 - 1, + weight: 0 + }, () => { + return waitForExecuting(1); + }); + this._receive = function(job) { + return job._reject(new Bottleneck.prototype.BottleneckError(options.enqueueErrorMessage)); + }; + this.stop = () => { + return this.Promise.reject(new Bottleneck.prototype.BottleneckError("stop() has already been called")); + }; + return done; + } + async _addToQueue(job) { + var args, blocked, error, options, reachedHWM, shifted, strategy; + ({args, options} = job); + try { + ({reachedHWM, blocked, strategy} = await this._store.__submit__(this.queued(), options.weight)); + } catch (error1) { + error = error1; + this.Events.trigger("debug", `Could not queue ${options.id}`, { + args, + options, + error + }); + job.doDrop({ error }); + return false; + } + if (blocked) { + job.doDrop(); + return true; + } else if (reachedHWM) { + shifted = strategy === Bottleneck.prototype.strategy.LEAK ? this._queues.shiftLastFrom(options.priority) : strategy === Bottleneck.prototype.strategy.OVERFLOW_PRIORITY ? this._queues.shiftLastFrom(options.priority + 1) : strategy === Bottleneck.prototype.strategy.OVERFLOW ? job : void 0; + if (shifted != null) shifted.doDrop(); + if (shifted == null || strategy === Bottleneck.prototype.strategy.OVERFLOW) { + if (shifted == null) job.doDrop(); + return reachedHWM; + } + } + job.doQueue(reachedHWM, blocked); + this._queues.push(job); + await this._drainAll(); + return reachedHWM; + } + _receive(job) { + if (this._states.jobStatus(job.options.id) != null) { + job._reject(new Bottleneck.prototype.BottleneckError(`A job with the same id already exists (id=${job.options.id})`)); + return false; + } else { + job.doReceive(); + return this._submitLock.schedule(this._addToQueue, job); + } + } + submit(...args) { + var cb, fn, job, options, ref, ref1, task; + if (typeof args[0] === "function") { + ref = args, [fn, ...args] = ref, [cb] = splice.call(args, -1); + options = parser$5.load({}, this.jobDefaults); + } else { + ref1 = args, [options, fn, ...args] = ref1, [cb] = splice.call(args, -1); + options = parser$5.load(options, this.jobDefaults); + } + task = (...args) => { + return new this.Promise(function(resolve, reject) { + return fn(...args, function(...args) { + return (args[0] != null ? reject : resolve)(args); + }); + }); + }; + job = new Job$1(task, args, options, this.jobDefaults, this.rejectOnDrop, this.Events, this._states, this.Promise); + job.promise.then(function(args) { + return typeof cb === "function" ? cb(...args) : void 0; + }).catch(function(args) { + if (Array.isArray(args)) return typeof cb === "function" ? cb(...args) : void 0; + else return typeof cb === "function" ? cb(args) : void 0; + }); + return this._receive(job); + } + schedule(...args) { + var job, options, task; + if (typeof args[0] === "function") { + [task, ...args] = args; + options = {}; + } else [options, task, ...args] = args; + job = new Job$1(task, args, options, this.jobDefaults, this.rejectOnDrop, this.Events, this._states, this.Promise); + this._receive(job); + return job.promise; + } + wrap(fn) { + var schedule = this.schedule.bind(this), wrapped = function(...args) { + return schedule(fn.bind(this), ...args); + }; + wrapped.withOptions = function(options, ...args) { + return schedule(options, fn, ...args); + }; + return wrapped; + } + async updateSettings(options = {}) { + await this._store.__updateSettings__(parser$5.overwrite(options, this.storeDefaults)); + parser$5.overwrite(options, this.instanceDefaults, this); + return this; + } + currentReservoir() { + return this._store.__currentReservoir__(); + } + incrementReservoir(incr = 0) { + return this._store.__incrementReservoir__(incr); + } + } + Bottleneck.default = Bottleneck; + Bottleneck.Events = Events$4; + Bottleneck.version = Bottleneck.prototype.version = require$$8.version; + Bottleneck.strategy = Bottleneck.prototype.strategy = { + LEAK: 1, + OVERFLOW: 2, + OVERFLOW_PRIORITY: 4, + BLOCK: 3 + }; + Bottleneck.BottleneckError = Bottleneck.prototype.BottleneckError = BottleneckError_1; + Bottleneck.Group = Bottleneck.prototype.Group = Group_1; + Bottleneck.RedisConnection = Bottleneck.prototype.RedisConnection = require$$2; + Bottleneck.IORedisConnection = Bottleneck.prototype.IORedisConnection = require$$3; + Bottleneck.Batcher = Bottleneck.prototype.Batcher = Batcher_1; + Bottleneck.prototype.jobDefaults = { + priority: DEFAULT_PRIORITY$1, + weight: 1, + expiration: null, + id: "" + }; + Bottleneck.prototype.storeDefaults = { + maxConcurrent: null, + minTime: 0, + highWater: null, + strategy: Bottleneck.prototype.strategy.LEAK, + penalty: null, + reservoir: null, + reservoirRefreshInterval: null, + reservoirRefreshAmount: null, + reservoirIncreaseInterval: null, + reservoirIncreaseAmount: null, + reservoirIncreaseMaximum: null + }; + Bottleneck.prototype.localStoreDefaults = { + Promise, + timeout: null, + heartbeatInterval: 250 + }; + Bottleneck.prototype.redisStoreDefaults = { + Promise, + timeout: null, + heartbeatInterval: 5e3, + clientTimeout: 1e4, + Redis: null, + clientOptions: {}, + clusterNodes: null, + clearDatastore: false, + connection: null + }; + Bottleneck.prototype.instanceDefaults = { + datastore: "local", + connection: null, + id: "", + rejectOnDrop: true, + trackDoneStatus: false, + Promise + }; + Bottleneck.prototype.stopDefaults = { + enqueueErrorMessage: "This limiter has been stopped and cannot accept new jobs.", + dropWaitingJobs: true, + dropErrorMessage: "This limiter has been stopped." + }; + return Bottleneck; + }).call(commonjsGlobal); + var Bottleneck_1 = Bottleneck; + return Bottleneck_1; + })); +})))(), 1); +var VERSION = "0.0.0-development"; +function isRequestError(error) { + return error.request !== void 0; +} +async function errorRequest(state, octokit, error, options) { + if (!isRequestError(error) || !error?.request.request) throw error; + if (error.status >= 400 && !state.doNotRetry.includes(error.status)) { + const retries = options.request.retries != null ? options.request.retries : state.retries; + const retryAfter = Math.pow((options.request.retryCount || 0) + 1, 2); + throw octokit.retry.retryRequest(error, retries, retryAfter); + } + throw error; +} +async function wrapRequest(state, octokit, request, options) { + const limiter = new import_light.default(); + limiter.on("failed", function(error, info) { + const maxRetries = ~~error.request.request?.retries; + const after = ~~error.request.request?.retryAfter; + options.request.retryCount = info.retryCount + 1; + if (maxRetries > info.retryCount) return after * state.retryAfterBaseValue; + }); + return limiter.schedule(requestWithGraphqlErrorHandling.bind(null, state, octokit, request), options); +} +async function requestWithGraphqlErrorHandling(state, octokit, request, options) { + const response = await request(options); + if (response.data && response.data.errors && response.data.errors.length > 0 && /Something went wrong while executing your query/.test(response.data.errors[0].message)) return errorRequest(state, octokit, new RequestError(response.data.errors[0].message, 500, { + request: options, + response + }), options); + return response; +} +function retry(octokit, octokitOptions) { + const state = Object.assign({ + enabled: true, + retryAfterBaseValue: 1e3, + doNotRetry: [ + 400, + 401, + 403, + 404, + 410, + 422, + 451 + ], + retries: 3 + }, octokitOptions.retry); + const retryPlugin = { retry: { retryRequest: (error, retries, retryAfter) => { + error.request.request = Object.assign({}, error.request.request, { + retries, + retryAfter + }); + return error; + } } }; + if (state.enabled) { + octokit.hook.error("request", errorRequest.bind(null, state, retryPlugin)); + octokit.hook.wrap("request", wrapRequest.bind(null, state, retryPlugin)); + } + return retryPlugin; +} +retry.VERSION = VERSION; +//#endregion +//#region src/common/get-octokit.ts +var getOctokit = () => { + return getOctokit$1(process$1.env.GITHUB_TOKEN || "", { log: { + ...core_exports, + warn: warning + } }, paginateGraphQL, retry); +}; +//#endregion +//#region src/common/config/get-config-file-from-repo.ts +var getConfigFileFromRepo = async (configTarget) => { + const octokit = getOctokit(); + let res; + try { + res = await octokit.rest.repos.getContent({ + owner: configTarget.repo.owner, + repo: configTarget.repo.repo, + path: configTarget.filepath, + ref: configTarget.ref, + mediaType: { format: "raw" } + }); + } catch (error) { + if (error.status === 404) throw new Error(`Config file not found with error 404. (target: ${configTarget.repo.owner ? `${configTarget.repo.owner}/` : ""}${configTarget.repo.repo}:${configTarget.filepath}${configTarget.ref ? `@${configTarget.ref}` : ""})`); + throw new Error(`Failed to fetch config from repo: ${error.message}`); + } + if (Array.isArray(res.data)) throw new Error(`Fetched content is a directory (array), expected a file. (target: ${configTarget.repo.owner ? `${configTarget.repo.owner}/` : ""}${configTarget.repo.repo}:${configTarget.filepath}${configTarget.ref ? `@${configTarget.ref}` : ""})`); + if (!res.headers["content-type"]?.startsWith("application/vnd.github.v3.raw")) throw new Error(`Fetched content has wrong content-type (${res.headers["content-type"]}), expected a raw file. (target: ${configTarget.repo.owner ? `${configTarget.repo.owner}/` : ""}${configTarget.repo.repo}:${configTarget.filepath}${configTarget.ref ? `@${configTarget.ref}` : ""})`); + if (typeof res.data !== "string") throw new Error(`Fetched content is not a string. (target: ${configTarget.repo.owner ? `${configTarget.repo.owner}/` : ""}${configTarget.repo.repo}:${configTarget.filepath}${configTarget.ref ? `@${configTarget.ref}` : ""})`); + return res.data; +}; +//#endregion +//#region src/common/config/normalize-filepath.ts +/** +* current path is assumed to be the ".github" folder in your repo +* root path is assumed to be the root of your repo +* @example +* filepath: release-drafter.yml +* output: [repo root]/.github/release-drafter.yml +* @example +* filepath: /src/../configs/release-drafter.yml +* output: [repo root]/configs/release-drafter.yml +* @example +* filepath: ../configs/release-drafter.yml +* output: [repo root]/configs/release-drafter.yml +* @example +* filepath: /src/../configs/release-drafter.yml +* output: [repo root]/configs/release-drafter.yml +* +* When specifying a target using _extends in the same repo & ref, current path is assumed to be +* the dirname of the current (parent) config file, instead of the .github repository. +* This allows files to reference each-other in a more natural way. +*/ +var normalizeFilepath = (config, parentConfig) => { + const _filepath = normalize(config.filepath); + if (isAbsolute(_filepath)) if (_filepath.startsWith("/")) return _filepath.slice(1); + else throw new Error(`Encountered malformed absolute path ${_filepath}`); + else if (parentConfig && parentConfig.repo.owner === config.repo.owner && parentConfig.repo.repo === config.repo.repo && config.ref === parentConfig.ref) return normalize(join(dirname(parentConfig.filepath), _filepath)); + else { + if (_filepath.startsWith(".github/")) return _filepath; + return join(".github", _filepath); + } +}; +//#endregion +//#region src/common/config/parse-config-target.ts +var describeConfigTarget = (target) => `${target.scheme}:${target.filepath}${target.repo ? ` (${target.repo.owner}/${target.repo.repo})` : ""}`; +/** +* Parses a config target string into its components +* @param target - Target string in format `[github:][[owner/]repo]:filepath[@ref]` or `file:filepath` +* @param currentContext - Current runtime context (repo owner, name, and ref) +* @returns Parsed config target with resolved components +*/ +function parseConfigTarget(target, context) { + let _target = structuredClone(target).trim(); + const getErr = (m) => /* @__PURE__ */ new Error(`invalid format: "${_target}". Expected format [github:][owner/repo:]filepath[@ref] or file:filepath. ${m}`); + if (_target.includes(" ")) throw getErr("Target must not contain spaces."); + const scheme = _target.startsWith("file:") ? "file" : "github"; + if (_target.startsWith("file:")) _target = _target.slice(5); + if (_target.startsWith("github:")) _target = _target.slice(7); + let hasRepoSpecifier = _target.includes(":"); + const hasRefSpecifier = _target.includes("@"); + if (scheme === "file") { + if (hasRepoSpecifier) throw getErr("Local file targets cannot have \":\" github specifiers."); + if (hasRefSpecifier) throw getErr("Local file targets cannot have \"@\" github specifiers."); + } + if (!hasRepoSpecifier && scheme !== "file") { + const targetWithoutRef = hasRefSpecifier ? _target.slice(0, _target.indexOf("@")) : _target; + const repoName = targetWithoutRef.split("/").at(-1) || ""; + if (!repoName.includes(".") || repoName === ".github") { + if (hasRefSpecifier) _target = `${targetWithoutRef}:${_target.slice(_target.indexOf("@"))}`; + else _target = `${_target}:`; + hasRepoSpecifier = true; + } + } + const parts = _target.split(":").flatMap((part) => part.split("@")); + let targetRepo; + let targetRef; + if (parts.length > 3) throw getErr("\":\" or \"@\" was specified more than once."); + if (hasRepoSpecifier) { + if (parts.length < 2) throw getErr("Missing repo specifier."); + const repoParts = parts[0].split("/"); + let targetRepoOwner; + let targetRepoName; + if (!repoParts.length) throw getErr("Missing repo specifier."); + if (repoParts.length > 2) throw getErr("\"/\" specified more than once."); + if (repoParts.length === 2) { + targetRepoOwner = repoParts[0]; + targetRepoName = repoParts[1]; + } else { + targetRepoName = repoParts[0]; + targetRepoOwner = context.repo.owner; + } + targetRepo = { + owner: targetRepoOwner, + repo: targetRepoName + }; + } else targetRepo = context.repo; + const isCurrentRepo = context.repo.owner === targetRepo.owner && context.repo.repo === targetRepo.repo; + if (hasRefSpecifier) { + if (parts.length < 2) throw getErr("Too short to contain ref specifier."); + const refSpecifier = parts.at(-1); + if (!refSpecifier) throw getErr("Missing ref specifier."); + if (!refSpecifier.length) throw getErr("Ref specifier is empty."); + targetRef = refSpecifier; + } else targetRef = isCurrentRepo ? context.ref : void 0; + const filepathIndex = hasRepoSpecifier ? 1 : 0; + return { + scheme, + filepath: parts.at(filepathIndex) || "", + ref: targetRef, + repo: targetRepo + }; +} +//#endregion +//#region src/common/config/get-config-file.ts +var SUPPORTED_FILE_EXTENSIONS = [ + "json", + "yml", + "yaml" +]; +var getConfigFile = async (configTarget, parentTarget) => { + const _configTarget = structuredClone(configTarget); + const fileExtension = _configTarget.filepath.split(".").pop().toLowerCase(); + if (!SUPPORTED_FILE_EXTENSIONS.includes(fileExtension)) throw new Error(`Unsupported file extension: .${fileExtension}. Supported extensions are: ${SUPPORTED_FILE_EXTENSIONS.join(", ")}`); + if (parentTarget?.scheme) { + if (parentTarget?.scheme === "github" && _configTarget.scheme === "file") throw new Error(`The '_extends' import-chain cannot contain github: to file: scheme transitions. Please change '_extends: ${configTarget.scheme}:${configTarget.filepath}' to use the github: scheme. ex: '_extends: ${parentTarget.repo.owner}/${parentTarget.repo.repo}:${configTarget.filepath}'`); + } + _configTarget.filepath = normalizeFilepath(_configTarget, parentTarget); + const loadFromFs = _configTarget.scheme === "file"; + let configRaw; + if (loadFromFs) try { + configRaw = getConfigFileFromFs(_configTarget.filepath); + } catch (error) { + throw new Error(`Local load failed. ${error.message}`); + } + else try { + configRaw = await getConfigFileFromRepo(_configTarget); + } catch (error) { + throw new Error(`Repo load failed. ${error.message}`); + } + const rawConfig = fileExtension === "json" ? JSON.parse(configRaw) : parse$1(configRaw); + let config; + try { + config = configFileSchema.parse(rawConfig); + } catch (error) { + if (error instanceof ZodError) throw new Error(`Invalid config in ${describeConfigTarget(_configTarget)}:\n${prettifyError$1(error)}`, { cause: error }); + throw error; + } + return { + config, + fetchedFrom: _configTarget + }; +}; +//#endregion +//#region src/common/config/get-config-files.ts +var getConfigFiles = async (configFilename, currentContext) => { + debug(`getConfigFiles: Starting with filename: ${configFilename}`); + let configTarget = parseConfigTarget(configFilename, currentContext); + debug(`getConfigFiles: Parsed config target - scheme: ${configTarget.scheme}, filepath: ${configTarget.filepath}`); + const canFallBackToOrgRepo = configTarget.scheme === "github" && configTarget.repo.owner === currentContext.repo.owner && configTarget.repo.repo === currentContext.repo.repo && currentContext.repo.repo !== ".github"; + let requestedRepoConfig; + try { + requestedRepoConfig = await getConfigFile(configTarget); + } catch (error) { + if (canFallBackToOrgRepo && error instanceof Error && error.message.includes("Config file not found") && configTarget.scheme === "github") { + info(`Config not found in ${currentContext.repo.owner}/${currentContext.repo.repo}, falling back to ${currentContext.repo.owner}/.github`); + requestedRepoConfig = await getConfigFile({ + ...configTarget, + repo: { + owner: currentContext.repo.owner, + repo: ".github" + }, + ref: void 0 + }); + } else throw error; + } + debug(`getConfigFiles: Fetched initial config from ${requestedRepoConfig.fetchedFrom.scheme}:${requestedRepoConfig.fetchedFrom.filepath}`); + const files = [requestedRepoConfig]; + let lastFetchedFrom = requestedRepoConfig.fetchedFrom; + let lastExtends = requestedRepoConfig.config._extends; + if (!lastExtends) { + debug(`getConfigFiles: No _extends found in config, returning single file`); + return files; + } + debug(`getConfigFiles: Found _extends directive: ${lastExtends.from}`); + const MAX_EXTENDS_DEPTH = 33; + let extendsDepth = 0; + do { + extendsDepth++; + debug(`getConfigFiles: Processing _extends depth ${extendsDepth}: ${lastExtends.from}`); + if (extendsDepth > MAX_EXTENDS_DEPTH) { + const error$1 = `Maximum extends depth (${MAX_EXTENDS_DEPTH}) exceeded. Check for circular dependencies or reduce the chain of extended configurations.`; + error(`getConfigFiles: ${error$1}`); + throw new Error(error$1); + } + configTarget = parseConfigTarget(lastExtends.from, lastFetchedFrom); + if (!configTarget.filepath) configTarget.filepath = basename(lastFetchedFrom.filepath); + debug(`getConfigFiles: Parsed _extends target - scheme: ${configTarget.scheme}, filepath: ${configTarget.filepath}`); + const normalizedFilepath = normalizeFilepath(configTarget, lastFetchedFrom); + const preCheckTarget = { + ...configTarget, + filepath: normalizedFilepath + }; + if (files.find(({ fetchedFrom: loadedFrom }) => { + const sameFilepath = loadedFrom.filepath === preCheckTarget.filepath; + const sameRepo = loadedFrom.repo.owner === preCheckTarget.repo.owner && loadedFrom.repo.repo === preCheckTarget.repo.repo; + const crossScheme = loadedFrom.scheme === "file" && preCheckTarget.scheme === "github"; + return sameFilepath && sameRepo && (crossScheme || loadedFrom.ref === preCheckTarget.ref); + })) { + warning(`Recursion detected. Ignoring "_extends: ${lastExtends.from}".`); + debug(`getConfigFiles: Recursion detected, stopping extends chain`); + return files; + } + const extendRepoConfig = await getConfigFile(configTarget, lastFetchedFrom); + debug(`getConfigFiles: Fetched extended config from ${extendRepoConfig.fetchedFrom.scheme}:${extendRepoConfig.fetchedFrom.filepath}`); + lastFetchedFrom = extendRepoConfig.fetchedFrom; + lastExtends = extendRepoConfig.config._extends; + files.push(extendRepoConfig); + debug(`getConfigFiles: Added extended config to chain. Total files: ${files.length}, next _extends: ${lastExtends?.from || "none"}`); + } while (lastExtends); + debug(`getConfigFiles: Extends chain complete with ${files.length} file(s)`); + return files; +}; +//#endregion +//#region src/common/config/merge-config-chain.ts +var toMergeableList = (value, strategy, key, description) => { + if (value === void 0 || value === null) return []; + if (!Array.isArray(value)) throw new Error(`Cannot ${strategy} '${key}': ${description} is not a list (got ${typeof value}).`); + return value; +}; +/** +* Merges an `_extends` chain (ordered leaf-first, as returned by +* `getConfigFiles`) into a single config object. +* +* Keys merge shallowly by default: the extending file's value replaces the +* inherited one. A file can opt into appending or prepending a list key +* to/onto the inherited list via the mapping form of `_extends` +* (`_extends: {from: ..., strategy: {: append|prepend}}`). A file's +* strategy governs only the step where that file itself is merged onto the +* configs it extends; it is not inherited by files extending it. The +* `_extends` key is stripped from the result. +*/ +var mergeConfigChain = (configResults) => { + const merged = {}; + for (const { config, fetchedFrom } of [...configResults].reverse()) { + const { _extends, ...rest } = config; + const strategies = _extends?.strategy ?? {}; + for (const key of Object.keys(strategies)) if (!Object.hasOwn(rest, key)) warning(`_extends strategy declares '${key}' in ${describeConfigTarget(fetchedFrom)}, but the file does not set '${key}'; the strategy has no effect.`); + for (const [key, value] of Object.entries(rest)) { + const strategy = (Object.hasOwn(strategies, key) ? strategies[key] : void 0) ?? "override"; + if (strategy === "override") { + merged[key] = value; + continue; + } + const inherited = toMergeableList(Object.hasOwn(merged, key) ? merged[key] : void 0, strategy, key, `the value inherited by ${describeConfigTarget(fetchedFrom)}`); + const own = toMergeableList(value, strategy, key, `the value in ${describeConfigTarget(fetchedFrom)}`); + merged[key] = strategy === "append" ? [...inherited, ...own] : [...own, ...inherited]; + info(`_extends strategy: ${strategy}ed ${own.length} '${key}' item(s) from ${describeConfigTarget(fetchedFrom)} onto ${inherited.length} inherited item(s)`); + } + } + return merged; +}; +//#endregion +//#region src/common/config/index.ts +/** +* Loads configuration from one or multiple files and resolves with +* the combined configuration as well as the list of contexts the configuration +* was loaded from +*/ +async function composeConfigGet(configFilename, currentContext) { + debug(`composeConfigGet: Starting config composition with filename: ${configFilename}`); + debug(`composeConfigGet: Current context - repo: ${currentContext.repo.owner}/${currentContext.repo.repo}, ref: ${currentContext.ref}`); + const configResults = await getConfigFiles(configFilename, currentContext); + debug(`composeConfigGet: Retrieved ${configResults.length} config file(s)`); + const contexts = configResults.map((c) => c.fetchedFrom).filter(Boolean); + debug(`composeConfigGet: Resolved ${contexts.length} context(s)`); + contexts.forEach((ctx, idx) => { + debug(`composeConfigGet: Context[${idx}] - scheme: ${ctx.scheme}, filepath: ${ctx.filepath}${ctx.repo ? `, repo: ${ctx.repo.owner}/${ctx.repo.repo}` : ""}`); + }); + const result = { + contexts, + config: mergeConfigChain(configResults) + }; + debug(`composeConfigGet: Config composition complete with ${Object.keys(result.config).length} keys`); + return result; +} +//#endregion +//#region src/common/get-pull-request-changed-files.ts +var PULL_REQUEST_FILES_PER_PAGE = 50; +var getPullRequestChangedFiles = async (octokit, params) => octokit.paginate(octokit.rest.pulls.listFiles, { + ...params, + per_page: PULL_REQUEST_FILES_PER_PAGE +}, (response) => response.data.map((file) => file.filename)); +var getPullRequestsChangedFiles = async (params) => { + const octokit = params.octokit ?? getOctokit(); + const changedFileEntries = await Promise.all(params.pullRequests.map(async (pullRequest) => { + const key = `${pullRequest.baseRepository?.nameWithOwner}#${pullRequest.number}`; + try { + return [key, await getPullRequestChangedFiles(octokit, { + owner: params.owner, + repo: params.repo, + pull_number: pullRequest.number + })]; + } catch (error) { + throw new Error(`Failed to list changed files for pull request #${pullRequest.number}.`, { cause: error }); + } + })); + return new Map(changedFileEntries); +}; +//#endregion +//#region src/common/graphql.ts +var executeGraphql = (client, document, variables) => client(document.toString(), variables); +/** +* Execute a generated GraphQL document and merge its paginated connection. +* +* The document must follow the plugin's conventions: a single `$cursor` +* variable and a connection containing `pageInfo` plus `nodes` or `edges`. +*/ +var paginateGraphql = (client, document, variables) => client.paginate(document.toString(), variables); +//#endregion +//#region src/types/github.graphql.generated.ts +var TypedDocumentString = class extends String { + __apiType; + value; + __meta__; + constructor(value, __meta__) { + super(value); + this.value = value; + this.__meta__ = __meta__; + } + toString() { + return this.value; + } +}; +new TypedDocumentString(` + fragment PullRequestFields on PullRequest { + __typename + title + number + url @include(if: $withPullRequestURL) + body @include(if: $withPullRequestBody) + author { + __typename + login + url + } + baseRepository { + __typename + nameWithOwner + } + mergedAt + isCrossRepository + labels(first: 100) { + __typename + nodes { + __typename + name + } + } + merged + baseRefName @include(if: $withBaseRefName) + headRefName @include(if: $withHeadRefName) +} + `, { "fragmentName": "PullRequestFields" }); +var FindCommitsInComparisonDocument = new TypedDocumentString(` + query findCommitsInComparison($name: String!, $owner: String!, $baseRef: String!, $headRef: String!, $withPullRequestBody: Boolean!, $withPullRequestURL: Boolean!, $cursor: String, $withBaseRefName: Boolean!, $withHeadRefName: Boolean!, $pullRequestLimit: Int!, $historyLimit: Int!) { + repository(name: $name, owner: $owner) { + ref(qualifiedName: $baseRef) { + compare(headRef: $headRef) { + commits(first: $historyLimit, after: $cursor) { + __typename + pageInfo { + __typename + hasNextPage + endCursor + } + nodes { + __typename + id + oid + committedDate + message + author { + __typename + name + user { + __typename + login + } + } + authors(first: 100) { + nodes { + __typename + name + user { + __typename + login + } + } + } + associatedPullRequests(first: $pullRequestLimit) { + __typename + nodes { + ...PullRequestFields + } + } + } + } + } + } + } +} + fragment PullRequestFields on PullRequest { + __typename + title + number + url @include(if: $withPullRequestURL) + body @include(if: $withPullRequestBody) + author { + __typename + login + url + } + baseRepository { + __typename + nameWithOwner + } + mergedAt + isCrossRepository + labels(first: 100) { + __typename + nodes { + __typename + name + } + } + merged + baseRefName @include(if: $withBaseRefName) + headRefName @include(if: $withHeadRefName) +}`); +new TypedDocumentString(` + query findCommitsWithPathChangesQuery($name: String!, $owner: String!, $targetCommitish: String!, $after: String, $path: String) { + repository(name: $name, owner: $owner) { + object(expression: $targetCommitish) { + ... on Commit { + __typename + history(path: $path, after: $after) { + __typename + pageInfo { + __typename + hasNextPage + endCursor + } + nodes { + __typename + id + } + } + } + } + } +} + `); +var FindRecentMergedPullRequestsDocument = new TypedDocumentString(` + query findRecentMergedPullRequests($name: String!, $owner: String!, $baseRefName: String, $limit: Int!, $withPullRequestBody: Boolean!, $withPullRequestURL: Boolean!, $withBaseRefName: Boolean!, $withHeadRefName: Boolean!) { + repository(name: $name, owner: $owner) { + pullRequests( + states: [MERGED] + baseRefName: $baseRefName + orderBy: { field: UPDATED_AT, direction: DESC } + first: $limit + ) { + __typename + nodes { + ...PullRequestFields + mergeCommit { + __typename + oid + } + } + } + } +} + fragment PullRequestFields on PullRequest { + __typename + title + number + url @include(if: $withPullRequestURL) + body @include(if: $withPullRequestBody) + author { + __typename + login + url + } + baseRepository { + __typename + nameWithOwner + } + mergedAt + isCrossRepository + labels(first: 100) { + __typename + nodes { + __typename + name + } + } + merged + baseRefName @include(if: $withBaseRefName) + headRefName @include(if: $withHeadRefName) +}`); +var ResolveCommitishDocument = new TypedDocumentString(` + query resolveCommitish($name: String!, $owner: String!, $expression: String!) { + repository(name: $name, owner: $owner) { + object(expression: $expression) { + __typename + oid + } + } +} + `); +var ResolvePullRequestCommitishDocument = new TypedDocumentString(` + query resolvePullRequestCommitish($name: String!, $owner: String!, $number: Int!) { + repository(name: $name, owner: $owner) { + pullRequest(number: $number) { + headRefOid + mergeCommit { + oid + } + potentialMergeCommit { + oid + } + } + } +} + `); +//#endregion +//#region src/common/parse-commitish.ts +var resolveTagToCommitSha = async (params) => { + const { octokit, tagRef } = params; + const target = (await executeGraphql(octokit.graphql, ResolveCommitishDocument, { + name: context.repo.repo, + owner: context.repo.owner, + expression: `${tagRef}^{commit}` + })).repository?.object; + if (target?.__typename !== "Commit") throw new Error(`Tag ${tagRef} does not point to a commit`); + return target.oid; +}; +var resolvePullRequestToCommitSha = async (params) => { + const { octokit, pullRequestNumber, refType } = params; + const pullRequest = (await executeGraphql(octokit.graphql, ResolvePullRequestCommitishDocument, { + name: context.repo.repo, + owner: context.repo.owner, + number: pullRequestNumber + })).repository?.pullRequest; + const commitSha = refType === "head" ? pullRequest?.headRefOid : pullRequest?.potentialMergeCommit?.oid ?? pullRequest?.mergeCommit?.oid; + if (!commitSha) throw new Error(`Pull request #${pullRequestNumber} does not have a ${refType} commit`); + return commitSha; +}; +/** +* GitHub's Releases API accepts a branch name or commit SHA as +* `target_commitish`. Normalize fully qualified branch refs, resolve fully +* qualified tag and pull request refs to commit SHAs before building the API +* payload. +* +* A tag without the `refs/tags/` prefix cannot be distinguished reliably from +* a branch with the same name, so it is passed through unchanged. +* +* If ref resolution fails, preserve the existing fallback to the repository's +* default branch. +*/ +var parseCommitishForRelease = async (commitish, octokit) => { + if (commitish.startsWith("refs/heads/")) return commitish.replace(/^refs\/heads\//, ""); + if (commitish.startsWith("refs/tags/")) return resolveTagToCommitSha({ + octokit: octokit ?? getOctokit(), + tagRef: commitish + }).catch(() => { + warning(`${commitish} could not be resolved to a commit SHA, falling back to default branch`); + return ""; + }); + if (commitish.startsWith("refs/pull/")) { + const pullRequestRef = /^refs\/pull\/(\d+)\/(head|merge)$/.exec(commitish); + if (pullRequestRef) { + const [, pullRequestNumber, refType] = pullRequestRef; + return resolvePullRequestToCommitSha({ + octokit: octokit ?? getOctokit(), + pullRequestNumber: Number(pullRequestNumber), + refType + }).catch(() => { + warning(`${commitish} could not be resolved to a commit SHA, falling back to default branch`); + return ""; + }); + } + warning(`${commitish} is not a supported pull request ref, falling back to default branch`); + return ""; + } + return commitish; +}; +//#endregion +//#region src/common/shared-input.schema.ts +/** +* Inputs shared by release-drafter and autolabeler +*/ +var sharedInputSchema = object({ + /** + * Access token used to make requests against the GitHub API. + * + * Defaults to ${{ github.token }}, or the GITHUB_TOKEN environment variable. + */ + token: string$1().min(1).default(() => process$1.env.GITHUB_TOKEN || ""), + /** + * When enabled, no write operations (creating/updating releases or adding + * labels) are performed. Instead, the action logs what it would have done. + */ + "dry-run": stringbool().or(boolean()).optional() +}).superRefine((data, ctx) => { + if (data.token && !process$1.env.GITHUB_TOKEN) process$1.env.GITHUB_TOKEN = data.token; + if (!process$1.env.GITHUB_TOKEN) ctx.addIssue({ + code: "custom", + message: "Unable to find a token. Please see input 'token'.", + path: ["token"] + }); +}); +//#endregion +//#region node_modules/escape-string-regexp/index.js +function escapeStringRegexp(string) { + if (typeof string !== "string") throw new TypeError("Expected a string"); + return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d"); +} +//#endregion +//#region src/common/string-to-regex.ts +var import_lib = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => { + module.exports = function(input) { + if (typeof input !== "string") throw new Error("Invalid input. Input must be a string"); + var m = input.match(/(\/?)(.+)\1([a-z]*)/i); + if (!m) throw new Error("Invalid regular expression format."); + var validFlags = Array.from(new Set(m[3])).filter(function(flag) { + return "gimsuy".includes(flag); + }).join(""); + return new RegExp(m[2], validFlags); + }; +})))(), 1); +var stringToRegex = (search) => { + return /^\/.+\/[AJUXgimsux]*$/.test(search) ? (0, import_lib.default)(search) : new RegExp(escapeStringRegexp(search), "g"); +}; +//#endregion +//#region node_modules/ignore/index.js +var require_ignore = /* @__PURE__ */ __commonJSMin(((exports, module) => { + function makeArray(subject) { + return Array.isArray(subject) ? subject : [subject]; + } + var UNDEFINED = void 0; + var EMPTY = ""; + var SPACE = " "; + var ESCAPE = "\\"; + var REGEX_TEST_BLANK_LINE = /^\s+$/; + var REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/; + var REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/; + var REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/; + var REGEX_SPLITALL_CRLF = /\r?\n/g; + var REGEX_TEST_INVALID_PATH = /^\.{0,2}\/|^\.{1,2}$/; + var REGEX_TEST_TRAILING_SLASH = /\/$/; + var SLASH = "/"; + var TMP_KEY_IGNORE = "node-ignore"; + /* istanbul ignore else */ + if (typeof Symbol !== "undefined") TMP_KEY_IGNORE = Symbol.for("node-ignore"); + var KEY_IGNORE = TMP_KEY_IGNORE; + var define = (object, key, value) => { + Object.defineProperty(object, key, { value }); + return value; + }; + var REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g; + var RETURN_FALSE = () => false; + var sanitizeRange = (range) => range.replace(REGEX_REGEXP_RANGE, (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) ? match : EMPTY); + var negateRange = (range) => range.startsWith("!") || range.startsWith("\\^") ? `^${range.slice(range[0] === "!" ? 1 : 2)}` : range; + var cleanRangeBackSlash = (slashes) => { + const { length } = slashes; + return slashes.slice(0, length - length % 2); + }; + var REPLACERS = [ + [/^\uFEFF/, () => EMPTY], + [/((?:\\\\)*?)(\\?\s+)$/, (_, m1, m2) => m1 + (m2.indexOf("\\") === 0 ? SPACE : EMPTY)], + [/(\\+?)\s/g, (_, m1) => { + const { length } = m1; + return m1.slice(0, length - length % 2) + SPACE; + }], + [/[\\$.|*+(){^]/g, (match) => `\\${match}`], + [/(?!\\)\?/g, () => "[^/]"], + [/^\//, () => "^"], + [/\//g, () => "\\/"], + [/^\^*(?:\\\*\\\*\\\/)+/, () => "^(?:.*\\/)?"], + [/^(?=[^^])/, function startingReplacer() { + return !/\/(?!$)/.test(this) ? "(?:^|\\/)" : "^"; + }], + [/\\\/\\\*\\\*(?=\\\/|$)/g, (_, index, str) => index + 6 < str.length ? "(?:\\/[^\\/]+)*" : "\\/.+"], + [/(^|[^\\]+)(\\\*)+(?=.+)/g, (_, p1, p2) => { + return p1 + p2.replace(/\\\*/g, "[^\\/]*"); + }], + [/\\\\\\(?=[$.|*+(){^])/g, () => ESCAPE], + [/\\\\/g, () => ESCAPE], + [/(\\)?\[([^\]/]*?)(\\*)($|\])/g, (match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}` : close === "]" ? endEscape.length % 2 === 0 ? `[${negateRange(sanitizeRange(range))}${endEscape}]` : "[]" : "[]"], + [/(?:[^*])$/, (match) => /\/$/.test(match) ? `${match}$` : `${match}(?=$|\\/$)`] + ]; + var REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\\\*$/; + var MODE_IGNORE = "regex"; + var MODE_CHECK_IGNORE = "checkRegex"; + var TRAILING_WILD_CARD_REPLACERS = { + [MODE_IGNORE](_, p1) { + return `${p1 ? `${p1}[^/]+` : "[^/]*"}(?=$|\\/$)`; + }, + [MODE_CHECK_IGNORE](_, p1) { + return `${p1 ? `${p1}[^/]*` : "[^/]*"}(?=$|\\/$)`; + } + }; + var makeRegexPrefix = (pattern) => REPLACERS.reduce((prev, [matcher, replacer]) => prev.replace(matcher, replacer.bind(pattern)), pattern); + var isString = (subject) => typeof subject === "string"; + var checkPattern = (pattern) => pattern && isString(pattern) && !REGEX_TEST_BLANK_LINE.test(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern) && pattern.indexOf("#") !== 0; + var splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF).filter(Boolean); + var IgnoreRule = class { + constructor(pattern, mark, body, ignoreCase, negative, prefix) { + this.pattern = pattern; + this.mark = mark; + this.negative = negative; + define(this, "body", body); + define(this, "ignoreCase", ignoreCase); + define(this, "regexPrefix", prefix); + } + get regex() { + const key = "_regex"; + if (this[key]) return this[key]; + return this._make(MODE_IGNORE, key); + } + get checkRegex() { + const key = "_checkRegex"; + if (this[key]) return this[key]; + return this._make(MODE_CHECK_IGNORE, key); + } + _make(mode, key) { + const str = this.regexPrefix.replace(REGEX_REPLACE_TRAILING_WILDCARD, TRAILING_WILD_CARD_REPLACERS[mode]); + const regex = this.ignoreCase ? new RegExp(str, "i") : new RegExp(str); + return define(this, key, regex); + } + }; + var createRule = ({ pattern, mark }, ignoreCase) => { + let negative = false; + let body = pattern; + if (body.indexOf("!") === 0) { + negative = true; + body = body.substr(1); + } + body = body.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#"); + const regexPrefix = makeRegexPrefix(body); + return new IgnoreRule(pattern, mark, body, ignoreCase, negative, regexPrefix); + }; + var RuleManager = class { + constructor(ignoreCase) { + this._ignoreCase = ignoreCase; + this._rules = []; + } + _add(pattern) { + if (pattern && pattern[KEY_IGNORE]) { + this._rules = this._rules.concat(pattern._rules._rules); + this._added = true; + return; + } + if (isString(pattern)) pattern = { pattern }; + if (checkPattern(pattern.pattern)) { + const rule = createRule(pattern, this._ignoreCase); + this._added = true; + this._rules.push(rule); + } + } + add(pattern) { + this._added = false; + makeArray(isString(pattern) ? splitPattern(pattern) : pattern).forEach(this._add, this); + return this._added; + } + test(path, checkUnignored, mode) { + let ignored = false; + let unignored = false; + let matchedRule; + this._rules.forEach((rule) => { + const { negative } = rule; + if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) return; + if (!rule[mode].test(path)) return; + ignored = !negative; + unignored = negative; + matchedRule = negative ? UNDEFINED : rule; + }); + const ret = { + ignored, + unignored + }; + if (matchedRule) ret.rule = matchedRule; + return ret; + } + }; + var throwError = (message, Ctor) => { + throw new Ctor(message); + }; + var checkPath = (path, originalPath, doThrow) => { + if (!isString(path)) return doThrow(`path must be a string, but got \`${originalPath}\``, TypeError); + if (!path) return doThrow(`path must not be empty`, TypeError); + if (checkPath.isNotRelative(path)) return doThrow(`path should be a \`path.relative()\`d string, but got "${originalPath}"`, RangeError); + return true; + }; + var isNotRelative = (path) => REGEX_TEST_INVALID_PATH.test(path); + checkPath.isNotRelative = isNotRelative; + /* istanbul ignore next */ + checkPath.convert = (p) => p; + var Ignore = class { + constructor({ ignorecase = true, ignoreCase = ignorecase, allowRelativePaths = false } = {}) { + define(this, KEY_IGNORE, true); + this._rules = new RuleManager(ignoreCase); + this._strictPathCheck = !allowRelativePaths; + this._initCache(); + } + _initCache() { + this._ignoreCache = Object.create(null); + this._testCache = Object.create(null); + } + add(pattern) { + if (this._rules.add(pattern)) this._initCache(); + return this; + } + addPattern(pattern) { + return this.add(pattern); + } + _test(originalPath, cache, checkUnignored, slices) { + const path = originalPath && checkPath.convert(originalPath); + checkPath(path, originalPath, this._strictPathCheck ? throwError : RETURN_FALSE); + return this._t(path, cache, checkUnignored, slices); + } + checkIgnore(path) { + if (!REGEX_TEST_TRAILING_SLASH.test(path)) return this.test(path); + const slices = path.split(SLASH).filter(Boolean); + slices.pop(); + if (slices.length) { + const parent = this._t(slices.join(SLASH) + SLASH, this._testCache, true, slices); + if (parent.ignored) return parent; + } + return this._rules.test(path, false, MODE_CHECK_IGNORE); + } + _t(path, cache, checkUnignored, slices) { + if (path in cache) return cache[path]; + if (!slices) slices = path.split(SLASH).filter(Boolean); + slices.pop(); + if (!slices.length) return cache[path] = this._rules.test(path, checkUnignored, MODE_IGNORE); + const parent = this._t(slices.join(SLASH) + SLASH, cache, checkUnignored, slices); + return cache[path] = parent.ignored ? parent : this._rules.test(path, checkUnignored, MODE_IGNORE); + } + ignores(path) { + return this._test(path, this._ignoreCache, false).ignored; + } + createFilter() { + return (path) => !this.ignores(path); + } + filter(paths) { + return makeArray(paths).filter(this.createFilter()); + } + test(path) { + return this._test(path, this._testCache, true); + } + }; + var factory = (options) => new Ignore(options); + var isPathValid = (path) => checkPath(path && checkPath.convert(path), path, RETURN_FALSE); + /* istanbul ignore next */ + var setupWindows = () => { + const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/"); + checkPath.convert = makePosix; + const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i; + checkPath.isNotRelative = (path) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path) || isNotRelative(path); + }; + /* istanbul ignore next */ + if (typeof process !== "undefined" && process.platform === "win32") setupWindows(); + module.exports = factory; + factory.default = factory; + module.exports.isPathValid = isPathValid; + define(module.exports, Symbol.for("setupWindows"), setupWindows); +})); +//#endregion +export { setOutput as A, stringbool as C, getInput as D, error as E, __commonJSMin as M, __toESM as N, info as O, string$1 as S, debug as T, array as _, parseCommitishForRelease as a, number as b, executeGraphql as c, getPullRequestsChangedFiles as d, composeConfigGet as f, _enum as g, ZodDefault as h, sharedInputSchema as i, warning as j, setFailed as k, paginateGraphql as l, context as m, stringToRegex as n, FindCommitsInComparisonDocument as o, getOctokit as p, escapeStringRegexp as r, FindRecentMergedPullRequestsDocument as s, require_ignore as t, getPullRequestChangedFiles as u, boolean as v, union as w, object as x, literal as y }; diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index a099147086..0000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,11 +0,0 @@ -version: '3.4' -services: - # We override much of the default GitHub Actions oriented Dockerfile for - # making it easier to run the tests using Docker Compose - app: - build: - context: . - args: - NODE_ENV: development - command: ['npm', 'test'] - entrypoint: '' diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..312ac269a4 --- /dev/null +++ b/docs/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of +experience, nationality, personal appearance, race, religion, or sexual identity +and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, or to ban temporarily or permanently any +contributor for other behaviors that they deem inappropriate, threatening, +offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at t@toolmantim.com. The project team +will review and investigate all complaints, and will respond in a way that it +deems appropriate to the circumstances. The project team is obligated to +maintain confidentiality with regard to the reporter of an incident. Further +details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 0000000000..b5f2d75366 --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,102 @@ +# Contributing + +[fork]: https://github.com/release-drafter/release-drafter/fork +[pr]: https://github.com/release-drafter/release-drafter/compare +[style]: https://standardjs.com/ +[code-of-conduct]: CODE_OF_CONDUCT.md + +Hi there! We are thrilled that you'd like to contribute to this project. Your +help is essential for keeping it great. + +Please note that this project is released with a [Contributor Code of +Conduct][code-of-conduct]. By participating in this project you agree to abide +by its terms. + +## Submitting a pull request + +1. [Fork][fork] and clone the repository +2. Configure and install the dependencies: `npm install` +3. Create a new branch: `git checkout -b my-branch-name` +4. Make your change, add tests, and run `npm run all` before pushing — this runs + formatting, linting, type checking, tests, and builds the `dist/` directory. + The CI pipeline enforces that the repository has no uncommitted changes after + these steps, so **you must run `npm run all` locally before pushing** to + avoid build failures. +5. Push to your fork and [submit a pull request][pr] +6. Give yourself a high five, and wait for your pull request to be reviewed and + merged. + +Here are a few things you can do that will increase the likelihood of your pull +request being accepted: + +- Follow the [style guide][style] which is using standard. Any linting errors + should be shown when running `npm run all` +- Write and update tests. +- Keep your change as focused as possible. If there are multiple changes you + would like to make that are not dependent upon each other, consider submitting + them as separate pull requests. +- Write a + [convetional commit message](https://www.conventionalcommits.org/en/v1.0.0/). + +Work in Progress pull requests are also welcome to get feedback early on, or if +there is something blocked you. + +## Issue Management Policy + +To maintain project health and keep issues actionable, we automatically manage +stale issues using the following policy: + +**Stale Issue Closure**: Issues labeled with `info-needed` that remain inactive +for 30 days will be automatically marked as stale. After an additional 7-day +grace period, the issue will be closed if no response is provided. + +When an issue is marked as stale, we'll post a comment asking you to provide the +requested information. If you respond with the information or show continued +interest, the stale label will be removed and the issue will remain open. + +This policy helps us: + +- Keep the issue tracker focused on active issues +- Encourage timely responses to information requests +- Ensure discussions don't get lost in an ever-growing issue backlog + +If your issue was closed due to inactivity but you still have relevant +information or context, please feel free to reopen it by commenting on the issue +or opening a new one. + +## Releasing + +Run the following command: + +```bash +git checkout master +git pull +npm version [major | minor | patch] --ignore-scripts=false +``` + +> [!IMPORTANT] +> +> - You may want the version increment to correspond to the last drafted release. +> - You can use a version number instead of `major | minor | patch` if needed. +> - This repository sets `ignore-scripts=true` in `.npmrc`, so the flag above is +> required when you want `npm version` to run the release lifecycle scripts. + +The command does the following: + +- Run tests (`preversion` script) +- Bumps the version number in [package.json](../package.json) and create + corresponding tag +- Stage changes for git (`version` script) +- Commit and tag +- Push & push tag (`postversion` script) + +After pushing, the `release.yml` workflow will trigger (`on: push: tag`), and : + +- publish the release draft +- update major tag (ex: pushing `v6.2.1` bumps `v6` to the same commit) + +## Resources + +- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) +- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) +- [GitHub Help](https://help.github.com) diff --git a/docs/configuration-loading.md b/docs/configuration-loading.md new file mode 100644 index 0000000000..e468810abe --- /dev/null +++ b/docs/configuration-loading.md @@ -0,0 +1,331 @@ +# Configuration Loading + +When specifying your `config-name:` inside the action's inputs, you can leverage +a variety of syntax combinations to fetch your config file(s). + +- [Configuration Loading](#configuration-loading) + - [The syntax](#the-syntax) + - [Recipes](#recipes) + - [Target a different folder](#target-a-different-folder) + - [Load your config from another repo](#load-your-config-from-another-repo) + - [Load a file on the runner's file-system (dynamic config)](#load-a-file-on-the-runners-file-system-dynamic-config) + - [Extend other config files using `_extends`](#extend-other-config-files-using-_extends) + - [Merge strategies for `_extends`](#merge-strategies-for-_extends) + - [Org-wide config via the `.github` repo](#org-wide-config-via-the-github-repo) + - [Edge-cases](#edge-cases) + - [Load config from your default branch](#load-config-from-your-default-branch) + - [Fetching from a repo named `github`](#fetching-from-a-repo-named-github) + - [Avoid changing _schemes_ during `_extends:` import chain](#avoid-changing-schemes-during-_extends-import-chain) + +## The syntax + +The full syntax can be represented as follows : + +- either `[github:][[owner/]repo]:filepath[@ref]` +- or `file:filepath` + +... discriminated by the _scheme_ : `file` or `github` (the default). + +

+ image +
Grammar diagram - generated by +RR - Railroad Diagram Generator +

+ +Everything besides the `filepath` is optional. This path is interpreted such as +you can easily navigate your repo's files : + +- relative _filepaths_ are resolved your repo's `.github` folder +- absolute _filepaths_ are resolved at your repo's root folder + +When using the `github:` scheme, you'll be able to specify a different repo +and/or ref to fetch the config at. If you do not specify any/some of them, +they'll be inferred using the runner's context. See +[Variables reference](https://docs.github.com/en/actions/reference/workflows-and-actions/variables) +and +[`@actions/github`'s `context`](https://github.com/actions/toolkit/blob/main/packages/github/src/context.ts) +for more. + +The `file:` schemes requires the runtime's file-system (the runner's) to have +the file at hand. If the desired file is in your repo, make sure to use +`actions/checkout`. + +> [!note] +> If you want to use an unmodified copy of the config that was pushed to your +> repo on the same event your workflow is running for, you need neither the +> `file:` scheme nor the `actions/checkout` action. +> +> Simply use the default `github:` scheme : +> +> ```yaml +> uses: release-drafter/release-drafter@v7 +> with: +> config-name: relative/path/to/my/config.yaml +> ``` +> +> ... release-drafter will automatically fetch your file from your +> working/current branch using octokit. + +## Recipes + +### Target a different folder + +- `config-name: release-drafter.yaml` + - resolves `.github/release-drafter.yaml` using the + [contents Github API endpoint](https://docs.github.com/en/rest/repos/contents) +- `config-name: /configs/release-drafter.yaml` + - resolves `configs/release-drafter.yaml` using the + [contents Github API endpoint](https://docs.github.com/en/rest/repos/contents) +- `config-name: file:../src/common/release-drafter.yaml` + - resolves `/home/runner/workspace/src/common/release-drafter.yaml` using + `node:fs` + +### Load your config from another repo + +- `config-name: .github:release-drafter.yaml@v2` + - resolves `.github/release-drafter.yaml` inside your `.github` repo at tag + `v2` +- `config-name: my_configs:release-drafter.yaml` + - resolves `.github/release-drafter.yaml` inside your `my_configs` repo on the + default branch +- `config-name: torvalds/dotfiles:/ci/release-drafter.yml@v6.19-rc8` + - resolves Linus's release-drafter config + +### Load a file on the runner's file-system (dynamic config) + +```yaml +# .github/release-drafter-template.yml +template: | + $CHANGES + + **Full Changelog**: generated by {{CURR_REF}} +``` + +```yaml +# .github/workflows/release.yml +jobs: + release-drafter: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Generate dynamic config from template using 'sed' + run: + sed "s|{{CURR_REF}}|${{ github.ref }}|g" + .github/release-drafter-template.yml > + .github/release-drafter-parsed.yml + - name: Use dynamic release-drafter configuration + uses: release-drafter/release-drafter@v7 + with: + config-name: file:release-drafter-parsed.yml +``` + +### Extend other config files using `_extends` + +```yml +# configs/release-drafter-common.yml +template: | + ## What’s Changed + + $CHANGES +``` + +```yml +# .github/release-drafter/backend.yml +_extends: /configs/release-drafter-common.yml +tag-template: 'backend/v$RESOLVED_VERSION' +``` + +```yaml +steps: + - uses: release-drafter/release-drafter@v7 + with: + config-name: release-drafter/backend.yml +``` + +Imported config will be : + +```yml +tag-template: 'backend/v$RESOLVED_VERSION' +template: | + ## What’s Changed + + $CHANGES +``` + +> [!note] +> The same syntax as `config_name:` applies to `_extends` (and to the `from` +> key of its mapping form below). Below all produce the same output : +> +> - `_extends: ../configs/release-drafter-common.yml` +> - `_extends: github:/configs/release-drafter-common.yml` +> - `_extends: github:../configs/release-drafter-common.yml` +> - `_extends: file:../configs/release-drafter-common.yml` +> - make sure to `actions/checkout@v6` the repo beforehand + +### Merge strategies for `_extends` + +Configs in an `_extends` chain are merged shallowly: a key in the extending +file replaces the inherited value entirely. That is usually what you want, +but for list keys (`categories`, `autolabeler`, `replacers`, ...) it means an +extending file cannot add entries without repeating the whole inherited list. + +The mapping form of `_extends` lets the extending file pick a merge strategy +per key: `from` is the target (same syntax as the string form), `strategy` +maps config keys to how they merge: + +```yml +# your-org/.github: .github/release-drafter.yml +categories: + - title: '🚀 Features' + when: + label: 'feature' +``` + +```yml +# your-repo: .github/release-drafter.yml +_extends: + from: your-org/.github + strategy: + categories: append +categories: + - type: 'pre-exclude' + when: + label: 'skip-changelog' +``` + +Imported config will be : + +```yml +categories: + - title: '🚀 Features' + when: + label: 'feature' + - type: 'pre-exclude' + when: + label: 'skip-changelog' +``` + +Semantics : + +- Available strategies are `override` (the default for every key, identical + to the shallow merge above), `append` (the inherited list plus the + extending file's entries, in that order), and `prepend` (the extending + file's entries first). Categories are evaluated in the order they are + defined, so the strategy decides where the file's own entries land. +- `append` and `prepend` only work on list values. Merging a key whose own + or inherited value is present and not a list fails with an error naming + the key and the file. An absent or empty (`categories:`) value counts as + an empty list. +- A file's `strategy` governs only the step where that file itself is + merged onto the configs it extends; it is not inherited. In a chain + `A extends B extends C`, `B`'s strategy applies when `B` is merged onto + `C`, and `A`'s strategy applies when `A` is merged onto that result. A + file without a strategy overrides, even if a file below it appended. +- Like the string form, the `_extends` key is stripped from the composed + configuration and never reaches the config schema. + +> [!warning] +> Older release-drafter versions only understand the plain string form of +> `_extends` and fail on the mapping form. Make sure every repository using +> it runs an action version that supports it. + +### Org-wide config via the `.github` repo + +If release-drafter cannot find the config file in the current repository, it +will automatically look for it in your organisation's +[`.github` repository](https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/creating-a-default-community-health-file). + +This means you can keep a single shared config in `/.github` and all +repositories in your organisation will pick it up without any per-repo +configuration: + +```yaml +# /.github/.github/release-drafter.yml +template: | + ## What's Changed + + $CHANGES +``` + +> [!note] +> The fallback only applies when: +> +> - the `config-name:` does not explicitly target another repository, and +> - release-drafter is **not** already running inside the `.github` repository +> itself (to avoid an infinite loop). + +## Edge-cases + +### Load config from your default branch + +When not specifying a ref (with `@ref`), octokit will fetch from the default +branch of the repo. + +However, our implementation of the config-loading will automatically set a value +for this ref parameter whenever the target repo and repo owner are the same +release-drafter is running for. + +For instance, if you are running : + +```yaml +# github.com/john_doe/my_repo/.github/workflows/release.yaml +on: + push: + branches: + - feature/implement-auth + +jobs: + release-drafter: + runs-on: ubuntu-latest + steps: + - uses: release-drafter/release-drafter@v7 + with: + config-name: release-drafter.yaml +``` + +... This will fetch +`https://github.com/john_doe/my_repo/.github/workflows/release.yaml?ref=feature/implement-auth`. + +If you need to fetch to your default branch instead, you will need to know the +name of your default branch : + +```yaml +config-name: release-drafter.yaml@main +``` + +... or get it from your workflow's context : + +```yaml +config-name: release-drafter.yaml@${{ github.event.repository.default_branch }} +``` + +### Fetching from a repo named `github` + +The `github:` prefix is used internally to recognize you want to explicitly +fetch from a remote (using octokit) instead of loading a file on the runtime's +filesystem. + +The same prefix could also be used if you wanted to specify fetching from a repo +you own named `github`. + +For instance, `github:release-drafter.yaml` would mean both : + +- fetch from current repo at current ref +- and fetch from my repo named `github` + +If you intent the later, you will need to be more explicit. Use either : + +- `my_org_or_name/github:release-drafter.yaml` +- or `github:github:release-drafter.yaml` +- or even `github:my_org_or_name/github:release-drafter.yaml` + +### Avoid changing _schemes_ during `_extends:` import chain + +When configs _extend_ each-other, release-drafter will need to walk the +import-chain using the scheme that whas specified in every file. + +Using the `file:` scheme with `_extends` is supported, but you need to make sure +all files are on the system and on the correct path. + +If you start extending configs using the `github:` (default, recommended) +scheme, you cannot switch to `file:` _mid-flight_. diff --git a/docs/design/app-logo.svg b/docs/design/app-logo.svg new file mode 100644 index 0000000000..5e5bb994cb --- /dev/null +++ b/docs/design/app-logo.svg @@ -0,0 +1,11 @@ + + + + logo + Created with Sketch. + + + diff --git a/docs/design/config-name-syntax.png b/docs/design/config-name-syntax.png new file mode 100644 index 0000000000..96b53369ea Binary files /dev/null and b/docs/design/config-name-syntax.png differ diff --git a/design/github-app-logo.png b/docs/design/github-app-logo.png similarity index 100% rename from design/github-app-logo.png rename to docs/design/github-app-logo.png diff --git a/design/install-button.svg b/docs/design/install-button.svg similarity index 100% rename from design/install-button.svg rename to docs/design/install-button.svg diff --git a/design/logo.png b/docs/design/logo.png similarity index 100% rename from design/logo.png rename to docs/design/logo.png diff --git a/design/logo.sketch b/docs/design/logo.sketch similarity index 100% rename from design/logo.sketch rename to docs/design/logo.sketch diff --git a/design/logo.svg b/docs/design/logo.svg similarity index 99% rename from design/logo.svg rename to docs/design/logo.svg index 20c5238777..5b77d8e811 100644 --- a/design/logo.svg +++ b/docs/design/logo.svg @@ -7,6 +7,6 @@ - \ No newline at end of file + diff --git a/design/logo@2x.png b/docs/design/logo@2x.png similarity index 100% rename from design/logo@2x.png rename to docs/design/logo@2x.png diff --git a/design/logo@4x.png b/docs/design/logo@4x.png similarity index 100% rename from design/logo@4x.png rename to docs/design/logo@4x.png diff --git a/design/opengraph@2x.png b/docs/design/opengraph@2x.png similarity index 100% rename from design/opengraph@2x.png rename to docs/design/opengraph@2x.png diff --git a/design/screenshot-2.png b/docs/design/screenshot-2.png similarity index 100% rename from design/screenshot-2.png rename to docs/design/screenshot-2.png diff --git a/design/screenshot.png b/docs/design/screenshot.png similarity index 100% rename from design/screenshot.png rename to docs/design/screenshot.png diff --git a/docs/github-app.md b/docs/github-app.md deleted file mode 100644 index 1673d2c1d9..0000000000 --- a/docs/github-app.md +++ /dev/null @@ -1,11 +0,0 @@ -# Release Drafter GitHub App [![NPM package](https://img.shields.io/npm/v/release-drafter-github-app.svg)](https://www.npmjs.com/package/release-drafter-github-app) - -If you're unable to use GitHub Actions, you can use the now deprecated [Release Drafter GitHub App](https://github.com/apps/release-drafter). Keep in mind that the Release Drafter GitHub App won't be receiving any new features (see: [#335](https://github.com/release-drafter/release-drafter/issues/335)). - -## Usage - -Install the [Release Drafter GitHub App](https://github.com/apps/release-drafter) and choose the repositories for which you want releases automatically created. - -## Installation permissions - -Release Drafter requires full write access, because GitHub does not offer a limited scope for only writing releases. **Don't install Release Drafter to your entire GitHub account — only add the repositories you want to draft releases on.** diff --git a/drafter/README.md b/drafter/README.md new file mode 100644 index 0000000000..2073b5e6d7 --- /dev/null +++ b/drafter/README.md @@ -0,0 +1,12 @@ +# Drafter action + +This folder only serves as an alias when users reference the action, which +requires the presence of the [`action.yml`](./action.yml) file + +```yaml +steps: + # targets root `action.yaml` - runs drafter + - uses: release-drafter/release-drafter@latest + # also runs drafter + - uses: release-drafter/release-drafter/drafter@latest +``` diff --git a/drafter/action.yml b/drafter/action.yml new file mode 100644 index 0000000000..a7cbe85286 --- /dev/null +++ b/drafter/action.yml @@ -0,0 +1,124 @@ +name: 'Release Drafter' +description: + 'Drafts your next release notes as pull requests are merged into your + branch(es).' +author: release-drafter +branding: + icon: edit-2 + color: orange +runs: + using: node24 + main: ../dist/actions/drafter/run.js + +inputs: + config-name: + description: | + If your workflow requires multiple release-drafter configs it be helpful to override the config-name. + The config should still be located inside `.github` as that's where we are looking for config files. + required: false + default: 'release-drafter.yml' + token: + description: > + Access token used to make requests against the GitHub API. Defaults to + github.token. + default: ${{ github.token }} + name: + description: | + The name that will be used in the GitHub release that's created or updated. + This will override any `name-template` specified in your `release-drafter.yml` if defined. + required: false + tag: + description: | + The tag name to be associated with the GitHub release that's created or updated. + This will override any `tag-template` specified in your `release-drafter.yml` if defined. + required: false + version: + description: | + The version to be associated with the GitHub release that's created or updated. + This will override any version calculated by the release-drafter. + required: false + publish: + description: | + A boolean indicating whether the release being created or updated should be immediately published. + required: false + default: '' + latest: + description: | + A boolean indicating whether the release being created or updated should be marked as latest. + required: false + default: '' + prerelease: + description: | + Whether to draft a prerelease, with changes since another prerelease (if applicable). Default `false`. + required: false + default: '' + prerelease-identifier: + description: | + A string indicating an identifier (alpha, beta, rc, etc), to increment the prerelease version. This automatically enables `prerelease` when both values come from the same config location; explicit action inputs still take precedence. + required: false + default: '' + 'include-pre-releases': + description: | + When looking for the last published release to scan changes up-to, include pre-releases. Has no effect if using `prerelease: true` (already enabled). Default `false`. + required: false + default: '' + commitish: + description: | + The release target, i.e. branch, commit SHA, or fully qualified tag or pull request ref it should point to. + Tag and pull request refs are resolved to commit SHAs. + Pull request merge refs force dry-run mode and disable publishing because they point to ephemeral merge commits. + Defaults to the branch that release-drafter runs for, e.g. `master` when configured to run on pushes to `master`. + required: false + default: '' + header: + description: | + A string that would be added before the template body. + required: false + default: '' + footer: + description: | + A string that would be added after the template body. + required: false + default: '' + dry-run: + description: | + A boolean indicating whether to run without performing any write operations. + When enabled, the action logs what it would have done instead of creating or updating releases. + required: false + default: '' + filter-by-range: + description: | + Filter releases that satisfies this semver range. Evaluates the tag name againts node's semver.satisfies(). + required: false + default: '' + +outputs: + id: + description: The ID of the release that was created or updated. + name: + description: The name of the release + tag_name: + description: The name of the tag associated with the release. + body: + description: The body of the drafted release. + html_url: + description: The URL users can navigate to in order to view the release + upload_url: + description: + The URL for uploading assets to the release, which could be used by GitHub + Actions for additional uses, for example the @actions/upload-release-asset + GitHub Action. + major_version: + description: + The next major version number. For example, if the last tag or release was + v1.2.3, the value would be v2.0.0. + minor_version: + description: + The next minor version number. For example, if the last tag or release was + v1.2.3, the value would be v1.3.0. + patch_version: + description: + The next patch version number. For example, if the last tag or release was + v1.2.3, the value would be v1.2.4. + resolved_version: + description: The next resolved version number, based on GitHub labels. diff --git a/drafter/schema.json b/drafter/schema.json new file mode 100644 index 0000000000..f5d83cb711 --- /dev/null +++ b/drafter/schema.json @@ -0,0 +1,505 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "_extends": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + }, + { + "type": "object", + "properties": { + "from": { + "type": "string", + "pattern": "\\S" + }, + "strategy": { + "anyOf": [ + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string", + "enum": ["override", "append", "prepend"] + } + }, + { + "type": "null" + } + ] + } + }, + "required": ["from"], + "additionalProperties": false + } + ] + }, + "change-template": { + "default": "* $TITLE (#$NUMBER) $AUTHORS", + "type": "string" + }, + "change-author-template": { + "default": "$AUTHOR_MENTION", + "type": "string" + }, + "change-authors-separator": { + "default": ", ", + "type": "string" + }, + "change-authors-final-separator": { + "type": "string" + }, + "change-title-escapes": { + "type": "string" + }, + "no-changes-template": { + "default": "* No changes", + "type": "string" + }, + "version-template": { + "default": "$MAJOR.$MINOR.$PATCH$PRERELEASE", + "type": "string" + }, + "name-template": { + "type": "string" + }, + "tag-prefix": { + "type": "string" + }, + "tag-template": { + "type": "string" + }, + "exclude-labels": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "include-labels": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "include-paths": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "exclude-paths": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "exclude-contributors": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "new-contributor-template": { + "default": "* $AUTHOR_MENTION made their first contribution in #$NUMBER", + "type": "string" + }, + "no-contributors-template": { + "default": "No contributors", + "type": "string" + }, + "sort-by": { + "default": "merged_at", + "type": "string", + "enum": ["merged_at", "title"] + }, + "sort-direction": { + "default": "descending", + "type": "string", + "enum": ["ascending", "descending"] + }, + "filter-by-commitish": { + "default": false, + "type": "boolean" + }, + "pull-request-limit": { + "default": 5, + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "history-limit": { + "default": 15, + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "replacers": { + "default": [], + "type": "array", + "items": { + "type": "object", + "properties": { + "search": { + "type": "string", + "minLength": 1 + }, + "replace": { + "type": "string", + "minLength": 0 + } + }, + "required": ["search", "replace"] + } + }, + "categories": { + "default": [], + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string", + "minLength": 1 + }, + "type": { + "default": "changelog", + "type": "string", + "enum": [ + "changelog", + "pre-include", + "pre-exclude", + "version-resolver" + ] + }, + "exclusive": { + "default": false, + "type": "boolean" + }, + "collapse-after": { + "default": -1, + "type": "integer", + "minimum": -1, + "maximum": 9007199254740991 + }, + "semver-increment": { + "default": "patch", + "type": "string", + "enum": ["major", "minor", "patch"] + }, + "labels": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "label": { + "type": "string", + "minLength": 1 + }, + "when": { + "default": [], + "anyOf": [ + { + "type": "object", + "properties": { + "conventional": { + "anyOf": [ + { + "type": "boolean", + "const": true + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "minLength": 1 + }, + "types": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "scope": { + "type": "string", + "minLength": 1 + }, + "scopes": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "breaking": { + "type": "boolean" + } + } + } + ] + }, + "label": { + "type": "string", + "minLength": 1 + }, + "labels": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "labels-mode": { + "default": "any", + "type": "string", + "enum": ["any", "all", "only", "exactly"] + }, + "path": { + "type": "string", + "minLength": 1 + }, + "paths": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "paths-mode": { + "default": "any", + "type": "string", + "enum": ["any", "all", "only", "exactly"] + } + } + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "conventional": { + "anyOf": [ + { + "type": "boolean", + "const": true + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "minLength": 1 + }, + "types": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "scope": { + "type": "string", + "minLength": 1 + }, + "scopes": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "breaking": { + "type": "boolean" + } + } + } + ] + }, + "label": { + "type": "string", + "minLength": 1 + }, + "labels": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "labels-mode": { + "default": "any", + "type": "string", + "enum": ["any", "all", "only", "exactly"] + }, + "path": { + "type": "string", + "minLength": 1 + }, + "paths": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "paths-mode": { + "default": "any", + "type": "string", + "enum": ["any", "all", "only", "exactly"] + } + } + } + } + ] + } + } + } + }, + "version-resolver": { + "default": { + "major": { + "labels": [] + }, + "minor": { + "labels": [] + }, + "patch": { + "labels": [] + }, + "default": "patch" + }, + "type": "object", + "properties": { + "major": { + "default": { + "labels": [] + }, + "type": "object", + "properties": { + "labels": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "required": ["labels"] + }, + "minor": { + "default": { + "labels": [] + }, + "type": "object", + "properties": { + "labels": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "required": ["labels"] + }, + "patch": { + "default": { + "labels": [] + }, + "type": "object", + "properties": { + "labels": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "required": ["labels"] + }, + "default": { + "default": "patch", + "type": "string", + "enum": ["major", "minor", "patch"] + } + } + }, + "category-template": { + "default": "## $TITLE", + "type": "string" + }, + "template": { + "default": "", + "type": "string" + }, + "latest": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "boolean" + } + ] + }, + "prerelease": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "boolean" + } + ] + }, + "prerelease-identifier": { + "type": "string" + }, + "include-pre-releases": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "boolean" + } + ] + }, + "commitish": { + "type": "string" + }, + "header": { + "type": "string" + }, + "footer": { + "type": "string" + }, + "filter-by-range": { + "type": "string" + } + } +} diff --git a/index.js b/index.js deleted file mode 100644 index a641b03eaa..0000000000 --- a/index.js +++ /dev/null @@ -1,125 +0,0 @@ -const { getConfig } = require('./lib/config') -const { isTriggerableReference } = require('./lib/triggerable-reference') -const { - findReleases, - generateReleaseInfo, - createRelease, - updateRelease, -} = require('./lib/releases') -const { findCommitsWithAssociatedPullRequests } = require('./lib/commits') -const { sortPullRequests } = require('./lib/sort-pull-requests') -const log = require('./lib/log') -const core = require('@actions/core') - -module.exports = (app) => { - app.on('push', async (context) => { - const { shouldDraft, configName, version, tag, name } = getInput() - - const config = await getConfig({ - app, - context, - configName, - }) - - const { isPreRelease } = getInput({ config }) - - if (config === null) return - - // GitHub Actions merge payloads slightly differ, in that their ref points - // to the PR branch instead of refs/heads/master - const ref = process.env['GITHUB_REF'] || context.payload.ref - - if (!isTriggerableReference({ ref, app, context, config })) { - return - } - - const { draftRelease, lastRelease } = await findReleases({ app, context }) - const { - commits, - pullRequests: mergedPullRequests, - } = await findCommitsWithAssociatedPullRequests({ - app, - context, - ref, - lastRelease, - config, - }) - - const sortedMergedPullRequests = sortPullRequests( - mergedPullRequests, - config['sort-by'], - config['sort-direction'] - ) - - const releaseInfo = generateReleaseInfo({ - commits, - config, - lastRelease, - mergedPullRequests: sortedMergedPullRequests, - version, - tag, - name, - isPreRelease, - shouldDraft, - }) - - let createOrUpdateReleaseResponse - if (!draftRelease) { - log({ app, context, message: 'Creating new release' }) - createOrUpdateReleaseResponse = await createRelease({ - context, - releaseInfo, - config, - }) - } else { - log({ app, context, message: 'Updating existing release' }) - createOrUpdateReleaseResponse = await updateRelease({ - context, - draftRelease, - releaseInfo, - config, - }) - } - - setActionOutput(createOrUpdateReleaseResponse, releaseInfo) - }) -} - -function getInput({ config } = {}) { - // Returns all the inputs that doesn't need a merge with the config file - if (!config) { - return { - shouldDraft: core.getInput('publish').toLowerCase() !== 'true', - configName: core.getInput('config-name'), - version: core.getInput('version') || undefined, - tag: core.getInput('tag') || undefined, - name: core.getInput('name') || undefined, - } - } - - // Merges the config file with the input - // the input takes precedence, because it's more easy to change at runtime - const preRelease = core.getInput('prerelease').toLowerCase() - return { - isPreRelease: preRelease === 'true' || (!preRelease && config.prerelease), - } -} - -function setActionOutput(releaseResponse, { body }) { - const { - data: { - id: releaseId, - html_url: htmlUrl, - upload_url: uploadUrl, - tag_name: tagName, - name: name, - }, - } = releaseResponse - if (releaseId && Number.isInteger(releaseId)) - core.setOutput('id', releaseId.toString()) - if (htmlUrl) core.setOutput('html_url', htmlUrl) - if (uploadUrl) core.setOutput('upload_url', uploadUrl) - if (tagName) core.setOutput('tag_name', tagName) - if (name) core.setOutput('name', name) - core.setOutput('body', body) -} diff --git a/lib/base64.js b/lib/base64.js deleted file mode 100644 index e7066d2e5c..0000000000 --- a/lib/base64.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports.encodeContent = (content) => { - return Buffer.from(content).toString('base64') -} diff --git a/lib/commits.js b/lib/commits.js deleted file mode 100644 index 52386ab99c..0000000000 --- a/lib/commits.js +++ /dev/null @@ -1,120 +0,0 @@ -const log = require('./log') -const paginate = require('./pagination') -const _ = require('lodash') - -module.exports.findCommitsWithAssociatedPullRequestsQuery = /* GraphQL */ ` - query findCommitsWithAssociatedPullRequests( - $name: String! - $owner: String! - $ref: String! - $withPullRequestBody: Boolean! - $withPullRequestURL: Boolean! - $since: GitTimestamp - $after: String - ) { - repository(name: $name, owner: $owner) { - ref(qualifiedName: $ref) { - target { - ... on Commit { - history(first: 100, since: $since, after: $after) { - totalCount - pageInfo { - hasNextPage - endCursor - } - nodes { - id - committedDate - message - author { - name - user { - login - } - } - associatedPullRequests(first: 5) { - nodes { - title - number - url @include(if: $withPullRequestURL) - body @include(if: $withPullRequestBody) - author { - login - } - baseRepository { - nameWithOwner - } - mergedAt - isCrossRepository - labels(first: 10) { - nodes { - name - } - } - } - } - } - } - } - } - } - } - } -` - -module.exports.findCommitsWithAssociatedPullRequests = async ({ - app, - context, - ref, - lastRelease, - config, -}) => { - const { owner, repo } = context.repo() - const variables = { - name: repo, - owner, - ref, - withPullRequestBody: config['change-template'].includes('$BODY'), - withPullRequestURL: config['change-template'].includes('$URL'), - } - const dataPath = ['repository', 'ref', 'target', 'history'] - const repoNameWithOwner = `${owner}/${repo}` - - let data, commits - if (lastRelease) { - log({ - app, - context, - message: `Fetching all commits for reference ${ref} since ${lastRelease.created_at}`, - }) - - data = await paginate( - context.github.graphql, - module.exports.findCommitsWithAssociatedPullRequestsQuery, - { ...variables, since: lastRelease.created_at }, - dataPath - ) - // GraphQL call is inclusive of commits from the specified dates. This means the final - // commit from the last tag is included, so we remove this here. - commits = _.get(data, [...dataPath, 'nodes']).filter( - (commit) => commit.committedDate != lastRelease.created_at - ) - } else { - log({ app, context, message: `Fetching all commits for reference ${ref}` }) - - data = await paginate( - context.github.graphql, - module.exports.findCommitsWithAssociatedPullRequestsQuery, - variables, - dataPath - ) - commits = _.get(data, [...dataPath, 'nodes']) - } - - const pullRequests = _.uniqBy( - _.flatten(commits.map((commit) => commit.associatedPullRequests.nodes)), - 'number' - ).filter((pr) => pr.baseRepository.nameWithOwner === repoNameWithOwner) - - return { commits, pullRequests } -} diff --git a/lib/config.js b/lib/config.js deleted file mode 100644 index 697c61521b..0000000000 --- a/lib/config.js +++ /dev/null @@ -1,55 +0,0 @@ -const core = require('@actions/core') -const { validateSchema } = require('./schema') -const { DEFAULT_CONFIG } = require('./default-config') -const log = require('./log') -const { runnerIsActions } = require('./utils') -const Table = require('cli-table3') - -const DEFAULT_CONFIG_NAME = 'release-drafter.yml' - -module.exports.getConfig = async function getConfig({ - app, - context, - configName, -}) { - try { - const repoConfig = await context.config( - configName || DEFAULT_CONFIG_NAME, - DEFAULT_CONFIG - ) - - const config = validateSchema(app, context, repoConfig) - - return config - } catch (error) { - log({ app, context, error, message: 'Invalid config file' }) - - if (error.isJoi) { - log({ - app, - context, - message: - 'Config validation errors, please fix the following issues in release-drafter.yml:\n' + - joiValidationErrorsAsTable(error), - }) - } - - if (runnerIsActions()) { - core.setFailed('Invalid config file') - } - return null - } -} - -function joiValidationErrorsAsTable(error) { - const table = new Table({ head: ['Property', 'Error'] }) - error.details.forEach(({ path, message }) => { - const prettyPath = path - .map((pathPart) => - Number.isInteger(pathPart) ? `[${pathPart}]` : pathPart - ) - .join('.') - table.push([prettyPath, message]) - }) - return table.toString() -} diff --git a/lib/default-config.js b/lib/default-config.js deleted file mode 100644 index 79d1d7790a..0000000000 --- a/lib/default-config.js +++ /dev/null @@ -1,25 +0,0 @@ -const { SORT_BY, SORT_DIRECTIONS } = require('./sort-pull-requests') - -const DEFAULT_CONFIG = Object.freeze({ - 'name-template': '', - 'tag-template': '', - 'change-template': `* $TITLE (#$NUMBER) @$AUTHOR`, - 'change-title-escapes': '', - 'no-changes-template': `* No changes`, - 'version-template': `$MAJOR.$MINOR.$PATCH`, - 'version-resolver': { - major: { labels: [] }, - minor: { labels: [] }, - patch: { labels: [] }, - default: 'patch', - }, - categories: [], - 'exclude-labels': [], - 'include-labels': [], - replacers: [], - 'sort-by': SORT_BY.mergedAt, - 'sort-direction': SORT_DIRECTIONS.descending, - prerelease: false, -}) - -module.exports.DEFAULT_CONFIG = DEFAULT_CONFIG diff --git a/lib/log.js b/lib/log.js deleted file mode 100644 index aaf09a6d91..0000000000 --- a/lib/log.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = ({ app, context, message, error }) => { - const repo = context.payload.repository - const prefix = repo ? `${repo.full_name}: ` : '' - const logString = `${prefix}${message}` - - if (error) { - app.log.warn(error, logString) - } else { - app.log.info(logString) - } -} diff --git a/lib/pagination.js b/lib/pagination.js deleted file mode 100644 index 232a963645..0000000000 --- a/lib/pagination.js +++ /dev/null @@ -1,51 +0,0 @@ -const _ = require('lodash') - -/** - * Utility function to paginate a GraphQL function using Relay-style cursor pagination. - * - * @param {Function} queryFn - function used to query the GraphQL API - * @param {string} query - GraphQL query, must include `nodes` and `pageInfo` fields for the field that will be paginated - * @param {Object} variables - * @param {string[]} paginatePath - path to field to paginate - */ -async function paginate(queryFn, query, variables, paginatePath) { - const nodesPath = [...paginatePath, 'nodes'] - const pageInfoPath = [...paginatePath, 'pageInfo'] - const endCursorPath = [...pageInfoPath, 'endCursor'] - const hasNextPagePath = [...pageInfoPath, 'hasNextPage'] - const hasNextPage = (data) => _.get(data, hasNextPagePath) - - let data = await queryFn(query, variables) - - if (!_.has(data, nodesPath)) { - throw new Error( - "Data doesn't contain `nodes` field. Make sure the `paginatePath` is set to the field you wish to paginate and that the query includes the `nodes` field." - ) - } - - if ( - !_.has(data, pageInfoPath) || - !_.has(data, endCursorPath) || - !_.has(data, hasNextPagePath) - ) { - throw new Error( - "Data doesn't contain `pageInfo` field with `endCursor` and `hasNextPage` fields. Make sure the `paginatePath` is set to the field you wish to paginate and that the query includes the `pageInfo` field." - ) - } - - while (hasNextPage(data)) { - const newData = await queryFn(query, { - ...variables, - after: _.get(data, [...pageInfoPath, 'endCursor']), - }) - const newNodes = _.get(newData, nodesPath) - const newPageInfo = _.get(newData, pageInfoPath) - - _.set(data, pageInfoPath, newPageInfo) - _.update(data, nodesPath, (d) => d.concat(newNodes)) - } - - return data -} - -module.exports = paginate diff --git a/lib/releases.js b/lib/releases.js deleted file mode 100644 index ec832aaca9..0000000000 --- a/lib/releases.js +++ /dev/null @@ -1,334 +0,0 @@ -const compareVersions = require('compare-versions') -const regexEscape = require('escape-string-regexp') - -const { getVersionInfo } = require('./versions') -const { template } = require('./template') -const log = require('./log') - -const sortReleases = (releases) => { - // For semver, we find the greatest release number - // For non-semver, we use the most recently merged - try { - return releases.sort((r1, r2) => compareVersions(r1.tag_name, r2.tag_name)) - } catch (error) { - return releases.sort( - (r1, r2) => new Date(r1.created_at) - new Date(r2.created_at) - ) - } -} - -module.exports.findReleases = async ({ app, context }) => { - let releases = await context.github.paginate( - context.github.repos.listReleases.endpoint.merge( - context.repo({ - per_page: 100, - }) - ) - ) - - log({ app, context, message: `Found ${releases.length} releases` }) - - const sortedPublishedReleases = sortReleases(releases.filter((r) => !r.draft)) - const draftRelease = releases.find((r) => r.draft) - const lastRelease = - sortedPublishedReleases[sortedPublishedReleases.length - 1] - - if (draftRelease) { - log({ app, context, message: `Draft release: ${draftRelease.tag_name}` }) - } else { - log({ app, context, message: `No draft release found` }) - } - - if (lastRelease) { - log({ app, context, message: `Last release: ${lastRelease.tag_name}` }) - } else { - log({ app, context, message: `No last release found` }) - } - - return { draftRelease, lastRelease } -} - -const contributorsSentence = ({ commits, pullRequests }) => { - const contributors = new Set() - - commits.forEach((commit) => { - if (commit.author.user) { - contributors.add(`@${commit.author.user.login}`) - } else { - contributors.add(commit.author.name) - } - }) - - pullRequests.forEach((pullRequest) => { - if (pullRequest.author) { - contributors.add(`@${pullRequest.author.login}`) - } - }) - - const sortedContributors = Array.from(contributors).sort() - if (sortedContributors.length > 1) { - return ( - sortedContributors.slice(0, sortedContributors.length - 1).join(', ') + - ' and ' + - sortedContributors.slice(-1) - ) - } else { - return sortedContributors[0] - } -} - -const getFilterExcludedPullRequests = (excludeLabels) => { - return (pullRequest) => { - const labels = pullRequest.labels.nodes - if (labels.some((label) => excludeLabels.includes(label.name))) { - return false - } - return true - } -} - -const getFilterIncludedPullRequests = (includeLabels) => { - return (pullRequest) => { - const labels = pullRequest.labels.nodes - if ( - includeLabels.length == 0 || - labels.some((label) => includeLabels.includes(label.name)) - ) { - return true - } - return false - } -} - -const categorizePullRequests = (pullRequests, config) => { - const { - 'exclude-labels': excludeLabels, - 'include-labels': includeLabels, - categories, - } = config - const allCategoryLabels = categories.flatMap((category) => category.labels) - const uncategorizedPullRequests = [] - const categorizedPullRequests = [...categories].map((category) => { - return { ...category, pullRequests: [] } - }) - - const filterUncategorizedPullRequests = (pullRequest) => { - const labels = pullRequest.labels.nodes - - if ( - labels.length === 0 || - !labels.some((label) => allCategoryLabels.includes(label.name)) - ) { - uncategorizedPullRequests.push(pullRequest) - return false - } - return true - } - - // we only want pull requests that have yet to be categorized - const filteredPullRequests = pullRequests - .filter(getFilterExcludedPullRequests(excludeLabels)) - .filter(getFilterIncludedPullRequests(includeLabels)) - .filter(filterUncategorizedPullRequests) - - categorizedPullRequests.map((category) => { - filteredPullRequests.map((pullRequest) => { - // lets categorize some pull request based on labels - // note that having the same label in multiple categories - // then it is intended to "duplicate" the pull request into each category - const labels = pullRequest.labels.nodes - if (labels.some((label) => category.labels.includes(label.name))) { - category.pullRequests.push(pullRequest) - } - }) - }) - - return [uncategorizedPullRequests, categorizedPullRequests] -} - -const generateChangeLog = (mergedPullRequests, config) => { - if (mergedPullRequests.length === 0) { - return config['no-changes-template'] - } - - const [ - uncategorizedPullRequests, - categorizedPullRequests, - ] = categorizePullRequests(mergedPullRequests, config) - - const escapeTitle = (title) => - // If config['change-title-escapes'] contains backticks, then they will be escaped along with content contained inside backticks - // If not, the entire backtick block is matched so that it will become a markdown code block without escaping any of its content - title.replace( - new RegExp( - `[${regexEscape(config['change-title-escapes'])}]|\`.*?\``, - 'g' - ), - (match) => { - if (match.length > 1) return match - if (match == '@' || match == '#') return `${match}` - return `\\${match}` - } - ) - - const pullRequestToString = (pullRequests) => - pullRequests - .map((pullRequest) => - template(config['change-template'], { - $TITLE: escapeTitle(pullRequest.title), - $NUMBER: pullRequest.number, - $AUTHOR: pullRequest.author ? pullRequest.author.login : 'ghost', - $BODY: pullRequest.body, - $URL: pullRequest.url, - }) - ) - .join('\n') - - const changeLog = [] - - if (uncategorizedPullRequests.length) { - changeLog.push(pullRequestToString(uncategorizedPullRequests)) - changeLog.push('\n\n') - } - - categorizedPullRequests.map((category, index) => { - if (category.pullRequests.length) { - changeLog.push(`## ${category.title}\n\n`) - - changeLog.push(pullRequestToString(category.pullRequests)) - - if (index + 1 !== categorizedPullRequests.length) changeLog.push('\n\n') - } - }) - - return changeLog.join('').trim() -} - -const resolveVersionKeyIncrement = (mergedPullRequests, config) => { - const priorityMap = { - patch: 1, - minor: 2, - major: 3, - } - const labelToKeyMap = Object.fromEntries( - Object.keys(priorityMap) - .flatMap((key) => [ - config['version-resolver'][key].labels.map((label) => [label, key]), - ]) - .flat() - ) - const keys = mergedPullRequests - .filter(getFilterExcludedPullRequests(config['exclude-labels'])) - .filter(getFilterIncludedPullRequests(config['include-labels'])) - .flatMap((pr) => pr.labels.nodes.map((node) => labelToKeyMap[node.name])) - .filter(Boolean) - const keyPriorities = keys.map((key) => priorityMap[key]) - const priority = Math.max(...keyPriorities) - const versionKey = Object.keys(priorityMap).find( - (key) => priorityMap[key] === priority - ) - return versionKey || config['version-resolver'].default -} - -module.exports.generateChangeLog = generateChangeLog - -module.exports.generateReleaseInfo = ({ - commits, - config, - lastRelease, - mergedPullRequests, - version = undefined, - tag = undefined, - name = undefined, - isPreRelease, - shouldDraft, -}) => { - let body = config.template - - body = template( - body, - { - $PREVIOUS_TAG: lastRelease ? lastRelease.tag_name : '', - $CHANGES: generateChangeLog(mergedPullRequests, config), - $CONTRIBUTORS: contributorsSentence({ - commits, - pullRequests: mergedPullRequests, - }), - }, - config.replacers - ) - - const versionInfo = getVersionInfo( - lastRelease, - config['version-template'], - // Use the first override parameter to identify - // a version, from the most accurate to the least - version || tag || name, - resolveVersionKeyIncrement(mergedPullRequests, config) - ) - - if (versionInfo) { - body = template(body, versionInfo) - } - - if (tag === undefined) { - tag = versionInfo ? template(config['tag-template'] || '', versionInfo) : '' - } - - if (name === undefined) { - name = versionInfo - ? template(config['name-template'] || '', versionInfo) - : '' - } - - return { - name, - tag, - body, - prerelease: isPreRelease, - draft: shouldDraft, - } -} - -module.exports.createRelease = ({ context, releaseInfo }) => { - return context.github.repos.createRelease( - context.repo({ - name: releaseInfo.name, - tag_name: releaseInfo.tag, - body: releaseInfo.body, - draft: releaseInfo.draft, - prerelease: releaseInfo.prerelease, - }) - ) -} - -module.exports.updateRelease = ({ context, draftRelease, releaseInfo }) => { - const updateReleaseParams = updateDraftReleaseParams({ - name: releaseInfo.name || draftRelease.name, - tag_name: releaseInfo.tag || draftRelease.tag_name, - }) - - return context.github.repos.updateRelease( - context.repo({ - release_id: draftRelease.id, - body: releaseInfo.body, - draft: releaseInfo.draft, - prerelease: releaseInfo.prerelease, - ...updateReleaseParams, - }) - ) -} - -function updateDraftReleaseParams(params) { - const updateReleaseParams = { ...params } - - // Let GitHub figure out `name` and `tag_name` if undefined - if (!updateReleaseParams.name) { - delete updateReleaseParams.name - } - if (!updateReleaseParams.tag_name) { - delete updateReleaseParams.tag_name - } - - return updateReleaseParams -} diff --git a/lib/schema.js b/lib/schema.js deleted file mode 100644 index 8b7543b262..0000000000 --- a/lib/schema.js +++ /dev/null @@ -1,137 +0,0 @@ -const _ = require('lodash') -const Joi = require('@hapi/joi') -const { SORT_BY, SORT_DIRECTIONS } = require('./sort-pull-requests') -const { DEFAULT_CONFIG } = require('./default-config') -const { validateReplacers } = require('./template') - -const schema = (context) => { - const defaultBranch = _.get( - context, - 'payload.repository.default_branch', - 'master' - ) - return Joi.object() - .keys({ - references: Joi.array().items(Joi.string()).default([defaultBranch]), - - 'change-template': Joi.string().default( - DEFAULT_CONFIG['change-template'] - ), - - 'change-title-escapes': Joi.string() - .allow('') - .default(DEFAULT_CONFIG['change-title-escapes']), - - 'no-changes-template': Joi.string().default( - DEFAULT_CONFIG['no-changes-template'] - ), - - 'version-template': Joi.string().default( - DEFAULT_CONFIG['version-template'] - ), - - 'name-template': Joi.string() - .allow('') - .default(DEFAULT_CONFIG['name-template']), - - 'tag-template': Joi.string() - .allow('') - .default(DEFAULT_CONFIG['tag-template']), - - 'exclude-labels': Joi.array() - .items(Joi.string()) - .default(DEFAULT_CONFIG['exclude-labels']), - - 'include-labels': Joi.array() - .items(Joi.string()) - .default(DEFAULT_CONFIG['include-labels']), - - 'sort-by': Joi.string() - .valid(SORT_BY.mergedAt, SORT_BY.title) - .default(DEFAULT_CONFIG['sort-by']), - - 'sort-direction': Joi.string() - .valid(SORT_DIRECTIONS.ascending, SORT_DIRECTIONS.descending) - .default(DEFAULT_CONFIG['sort-direction']), - - prerelease: Joi.boolean().default(DEFAULT_CONFIG.prerelease), - - replacers: Joi.array() - .items( - Joi.object().keys({ - search: Joi.string() - .required() - .error( - () => '"search" is required and must be a regexp or a string' - ), - replace: Joi.string().allow('').required(), - }) - ) - .default(DEFAULT_CONFIG.replacers), - - categories: Joi.array() - .items( - Joi.object() - .keys({ - title: Joi.string().required(), - label: Joi.string(), - labels: Joi.array().items(Joi.string()).single().default([]), - }) - .rename('label', 'labels', { - ignoreUndefined: true, - override: true, - }) - ) - .default(DEFAULT_CONFIG.categories), - - 'version-resolver': Joi.object() - .keys({ - major: Joi.object({ - labels: Joi.array().items(Joi.string()).single(), - }), - minor: Joi.object({ - labels: Joi.array().items(Joi.string()).single(), - }), - patch: Joi.object({ - labels: Joi.array().items(Joi.string()).single(), - }), - default: Joi.string() - .valid('major', 'minor', 'patch') - .default('patch'), - }) - .default(DEFAULT_CONFIG['version-resolver']), - - template: Joi.string().required(), - - _extends: Joi.string(), - }) - .rename('branches', 'references', { - ignoreUndefined: true, - override: true, - }) -} - -module.exports.schema = schema - -const validateSchema = (app, context, repoConfig) => { - const { error, value: config } = schema(context).validate(repoConfig, { - abortEarly: false, - allowUnknown: true, - }) - - if (error) throw error - - try { - config.replacers = validateReplacers({ - app, - context, - replacers: config.replacers, - }) - } catch (error) { - config.replacers = [] - } - - return config -} - -module.exports.validateSchema = validateSchema diff --git a/lib/sort-pull-requests.js b/lib/sort-pull-requests.js deleted file mode 100644 index 0ad0f3300b..0000000000 --- a/lib/sort-pull-requests.js +++ /dev/null @@ -1,45 +0,0 @@ -const SORT_BY = { - mergedAt: 'merged_at', - title: 'title', -} - -const SORT_DIRECTIONS = { - ascending: 'ascending', - descending: 'descending', -} - -module.exports.SORT_BY = SORT_BY -module.exports.SORT_DIRECTIONS = SORT_DIRECTIONS - -module.exports.sortPullRequests = (pullRequests, sortBy, sortDirection) => { - const getSortFieldFn = sortBy === SORT_BY.title ? getTitle : getMergedAt - - const sortFn = - sortDirection === SORT_DIRECTIONS.ascending - ? dateSortAscending - : dateSortDescending - - return pullRequests - .slice() - .sort((a, b) => sortFn(getSortFieldFn(a), getSortFieldFn(b))) -} - -function getMergedAt(pullRequest) { - return new Date(pullRequest.mergedAt) -} - -function getTitle(pullRequest) { - return pullRequest.title -} - -function dateSortAscending(date1, date2) { - if (date1 > date2) return 1 - if (date1 < date2) return -1 - return 0 -} - -function dateSortDescending(date1, date2) { - if (date1 > date2) return -1 - if (date1 < date2) return 1 - return 0 -} diff --git a/lib/template.js b/lib/template.js deleted file mode 100644 index 10d7ef0963..0000000000 --- a/lib/template.js +++ /dev/null @@ -1,57 +0,0 @@ -const log = require('./log') -const regexParser = require('regex-parser') -const regexEscape = require('escape-string-regexp') - -/** - * replaces all uppercase dollar templates with their string representation from obj - * if replacement is undefined in obj the dollar template string is left untouched - */ - -const template = (string, obj, customReplacers) => { - let str = string.replace(/(\$[A-Z_]+)/g, (_, k) => { - let result - if (obj[k] === undefined || obj[k] === null) { - result = k - } else if (typeof obj[k] === 'object') { - result = template(obj[k].template, obj[k]) - } else { - result = `${obj[k]}` - } - return result - }) - if (customReplacers) { - customReplacers.forEach(({ search, replace }) => { - str = str.replace(search, replace) - }) - } - return str -} - -function toRegex(search) { - if (search.match(/^\/.+\/[gmixXsuUAJ]*$/)) { - return regexParser(search) - } else { - // plain string - return new RegExp(regexEscape(search), 'g') - } -} - -function validateReplacers({ app, context, replacers }) { - return replacers - .map((replacer) => { - try { - return { ...replacer, search: toRegex(replacer.search) } - } catch (e) { - log({ - app, - context, - message: `Bad replacer regex: '${replacer.search}'`, - }) - return false - } - }) - .filter(Boolean) -} - -module.exports.template = template -module.exports.validateReplacers = validateReplacers diff --git a/lib/triggerable-reference.js b/lib/triggerable-reference.js deleted file mode 100644 index eec4d17dbf..0000000000 --- a/lib/triggerable-reference.js +++ /dev/null @@ -1,25 +0,0 @@ -const log = require('./log') - -module.exports.isTriggerableReference = ({ app, context, ref, config }) => { - const { GITHUB_ACTIONS } = process.env - if (GITHUB_ACTIONS) { - // Let GitHub Action determine when to run the action based on the workflow's on syntax - // See https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#on - return true - } - const refRegex = /^refs\/(?:heads|tags)\// - const refernces = config.references.map((r) => r.replace(refRegex, '')) - const shortRef = ref.replace(refRegex, '') - const validReference = new RegExp(refernces.join('|')) - const relevant = validReference.test(shortRef) - if (!relevant) { - log({ - app, - context, - message: `Ignoring push. ${shortRef} does not match: ${refernces.join( - ', ' - )}`, - }) - } - return relevant -} diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index 8b45cb8e85..0000000000 --- a/lib/utils.js +++ /dev/null @@ -1,5 +0,0 @@ -function runnerIsActions() { - return process.env['GITHUB_ACTION'] !== undefined -} - -module.exports.runnerIsActions = runnerIsActions diff --git a/lib/versions.js b/lib/versions.js deleted file mode 100644 index 157551fd53..0000000000 --- a/lib/versions.js +++ /dev/null @@ -1,70 +0,0 @@ -const semver = require('semver') - -const splitSemVer = (input, versionKey = 'version') => { - if (!input[versionKey]) { - return null - } - - const version = input.inc - ? semver.inc(input[versionKey], input.inc, true) - : semver.parse(input[versionKey]) - - return { - ...input, - version, - $MAJOR: semver.major(version), - $MINOR: semver.minor(version), - $PATCH: semver.patch(version), - } -} - -const getTemplatableVersion = (input) => { - const templatableVersion = { - $NEXT_MAJOR_VERSION: splitSemVer({ ...input, inc: 'major' }), - $NEXT_MINOR_VERSION: splitSemVer({ ...input, inc: 'minor' }), - $NEXT_PATCH_VERSION: splitSemVer({ ...input, inc: 'patch' }), - $INPUT_VERSION: splitSemVer(input, 'inputVersion'), - $RESOLVED_VERSION: splitSemVer({ - ...input, - inc: input.versionKeyIncrement || 'patch', - }), - } - - templatableVersion.$RESOLVED_VERSION = - templatableVersion.$INPUT_VERSION || templatableVersion.$RESOLVED_VERSION - - return templatableVersion -} - -const coerceVersion = (input) => { - if (!input) { - return null - } - - return typeof input === 'object' - ? semver.coerce(input.tag_name) || semver.coerce(input.name) - : semver.coerce(input) -} - -module.exports.getVersionInfo = ( - release, - template, - inputVersion = null, - versionKeyIncrement = null -) => { - const version = coerceVersion(release) - inputVersion = coerceVersion(inputVersion) - - if (!version && !inputVersion) { - return undefined - } - - return { - ...getTemplatableVersion({ - version, - template, - inputVersion, - versionKeyIncrement, - }), - } -} diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000000..f64bc5656d --- /dev/null +++ b/mise.toml @@ -0,0 +1,2 @@ +[settings] +idiomatic_version_file_enable_tools = ['node'] diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000..f432fd228e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5875 @@ +{ + "name": "release-drafter", + "version": "7.6.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "release-drafter", + "version": "7.6.0", + "license": "MIT", + "dependencies": { + "@actions/core": "^3.0.1", + "@actions/github": "^9.1.1", + "@graphql-typed-document-node/core": "^3.2.0", + "@octokit/graphql": "^9.0.3", + "@octokit/plugin-paginate-graphql": "^6.0.0", + "@octokit/plugin-rest-endpoint-methods": "^17.0.0", + "@octokit/plugin-retry": "^8.1.0", + "@octokit/request-error": "^7.1.0", + "@octokit/types": "^16.0.0", + "compare-versions": "^6.1.1", + "conventional-commits-parser": "^6.4.0", + "escape-string-regexp": "^5.0.0", + "graphql": "^17.0.2", + "ignore": "^7.0.6", + "regex-parser": "^2.3.1", + "semver": "^7.8.5", + "yaml": "^2.9.0", + "zod": "^4.4.3" + }, + "devDependencies": { + "@biomejs/biome": "2.5.3", + "@graphql-codegen/cli": "^7.2.0", + "@graphql-codegen/typed-document-node": "^7.1.0", + "@graphql-codegen/typescript": "^6.1.0", + "@graphql-codegen/typescript-operations": "^6.1.0", + "@octokit/webhooks-types": "^7.6.1", + "@types/node": "^24.13.3", + "@types/semver": "^7.7.1", + "@vitest/coverage-v8": "^4.1.10", + "nock": "^14.0.16", + "typescript": "^7.0.2", + "vite": "^8.1.4", + "vitest": "^4.1.10" + }, + "engines": { + "node": ">=24.0.0", + "npm": ">=12.0.1" + } + }, + "node_modules/@actions/core": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.1.tgz", + "integrity": "sha512-a6d/Nwahm9fliVGRhdhofo40HjHQasUPusmc7vBfyky+7Z+P2A1J68zyFVaNcEclc/Se+eO595oAr5nwEIoIUA==", + "license": "MIT", + "dependencies": { + "@actions/exec": "^3.0.0", + "@actions/http-client": "^4.0.0" + } + }, + "node_modules/@actions/exec": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "@actions/io": "^3.0.2" + } + }, + "node_modules/@actions/github": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@actions/github/-/github-9.1.1.tgz", + "integrity": "sha512-tL5JbYOBZHc0ngEnCsaDcryUizIUIlQyIMwy1Wkx93H5HzbBJ7TbiPx2PnFjBwZW0Vh05JmfFZhecE6gglYegA==", + "license": "MIT", + "dependencies": { + "@actions/http-client": "^3.0.2", + "@octokit/core": "^7.0.6", + "@octokit/plugin-paginate-rest": "^14.0.0", + "@octokit/plugin-rest-endpoint-methods": "^17.0.0", + "@octokit/request": "^10.0.7", + "@octokit/request-error": "^7.1.0", + "undici": "^6.23.0" + } + }, + "node_modules/@actions/github/node_modules/@actions/http-client": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-3.0.2.tgz", + "integrity": "sha512-JP38FYYpyqvUsz+Igqlc/JG6YO9PaKuvqjM3iGvaLqFnJ7TFmcLyy2IDrY0bI0qCQug8E9K+elv5ZNfw62ZJzA==", + "license": "MIT", + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^6.23.0" + } + }, + "node_modules/@actions/http-client": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^6.23.0" + } + }, + "node_modules/@actions/io": { + "version": "3.0.2", + "license": "MIT" + }, + "node_modules/@ardatan/relay-compiler": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/@ardatan/relay-compiler/-/relay-compiler-13.0.1.tgz", + "integrity": "sha512-afG3YPwuSA0E5foouZusz5GlXKs74dObv4cuWyLyfKsYFj2r7oGRNB28v18HvwuLSQtQFCi+DpIe0TZkgQDYyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "immutable": "^5.1.5", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "graphql": "*" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@biomejs/biome": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.5.3.tgz", + "integrity": "sha512-MrJswFdei9EfDwwUy2tQrPDpK0AO+RmMFvBoaaJ6ayBc3sUbHdCE+XG5N8vp+5So41ZupZJQm0roHFFhMGVD7A==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.5.3", + "@biomejs/cli-darwin-x64": "2.5.3", + "@biomejs/cli-linux-arm64": "2.5.3", + "@biomejs/cli-linux-arm64-musl": "2.5.3", + "@biomejs/cli-linux-x64": "2.5.3", + "@biomejs/cli-linux-x64-musl": "2.5.3", + "@biomejs/cli-win32-arm64": "2.5.3", + "@biomejs/cli-win32-x64": "2.5.3" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.5.3.tgz", + "integrity": "sha512-QhYP9muVQ0nUO5zztFuPbEwi4+94sJWVjaZds9aMi1l/KNZBiUjdiSUrGHsTaMGDXrYl+r4AS2sUKfgH3w+V3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.5.3.tgz", + "integrity": "sha512-NC1Ss13UaW7QZX+y8j44bF7AP0jSJdBl6iRhe0MAkvaSqZy+mWg3GaXsrb+eSoHoGDBtaXWEbMVV0iVN2cZ7cQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.5.3.tgz", + "integrity": "sha512-ksx1KWeyYW18ILL04msF/J4ZBtBDN33znYK8Z/aNv/vlBVxL9/g3mGP+omgHJKy4+KWbK87vcmmpmurfNjSgiA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.5.3.tgz", + "integrity": "sha512-fccix0w6xp6csCXgxeC0dU/3ecgRQal0y+cv2SP9ajNlhe7Yrk2Ug7UDe2j9AT9ZDYitkXpvUKgZjjuoYeP4Vg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.5.3.tgz", + "integrity": "sha512-yMkJtilsgvILDcVkh187aVLTb64xYsrxYajx5kym+r1ULkO5HUOfu9AYKLGQbOVLwJtT2utNw7hhFNg+17mUYA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.5.3.tgz", + "integrity": "sha512-O/yU9YKRUiHhmcjF2f38PSjseVk3G4VLWYc0G2HWpzdBVREV6G8IGWIVEFf7MFPfWIzNUIvPsEjeAZQIOgnLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.5.3.tgz", + "integrity": "sha512-cX5z+GYwRcqEok0AH3KSfQGgqYd0Nomfp6Fbe1uiTtELE38hdH2k842wQ9wLNaF/JJ7r4rjJQ4VR+ce+fRmQbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.5.3.tgz", + "integrity": "sha512-ExSaJWi4/u6+GXCszlSKpWSjKNbDseAYqqkCznsCsZ/4uidZ/BEqsCc5/3ctlq6dfIubdIIRSVLC/PG9xPl70Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@envelop/core": { + "version": "5.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@envelop/instrumentation": "^1.0.0", + "@envelop/types": "^5.2.1", + "@whatwg-node/promise-helpers": "^1.2.4", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@envelop/instrumentation": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@whatwg-node/promise-helpers": "^1.2.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@envelop/types": { + "version": "5.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@whatwg-node/promise-helpers": "^1.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@graphql-codegen/add": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/add/-/add-7.1.0.tgz", + "integrity": "sha512-bytJg1kel5zfgK3JSYbGwtpbNe6F9OPZSR6DiMDe9RVxblAgl6w4zEEPd/mM3rhNJ1VmGYLbNnf5e1eUfXQEbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-codegen/plugin-helpers": "^7.1.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-codegen/cli": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-7.2.0.tgz", + "integrity": "sha512-JPJw2vquEIpO3b8XJyxFVTrYi6WRn/OKu/SlzQA+IwAVT7GZPeG+AHmfRXAvpVMj31899nTpQYEQGUxx3ZqubQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/generator": "^7.18.13", + "@babel/template": "^7.18.10", + "@babel/types": "^7.18.13", + "@graphql-codegen/client-preset": "^6.1.0", + "@graphql-codegen/core": "^6.2.0", + "@graphql-codegen/plugin-helpers": "^7.1.0", + "@graphql-tools/apollo-engine-loader": "^8.0.28", + "@graphql-tools/code-file-loader": "^8.1.28", + "@graphql-tools/git-loader": "^8.0.32", + "@graphql-tools/github-loader": "^9.0.6", + "@graphql-tools/graphql-file-loader": "^8.1.11", + "@graphql-tools/json-file-loader": "^8.0.26", + "@graphql-tools/load": "^8.1.8", + "@graphql-tools/merge": "^9.0.6", + "@graphql-tools/url-loader": "^9.0.6", + "@graphql-tools/utils": "^11.2.0", + "@inquirer/prompts": "^8.3.2", + "@whatwg-node/fetch": "^0.10.0", + "chalk": "^5.6.0", + "cosmiconfig": "^9.0.0", + "debounce": "^3.0.0", + "detect-indent": "^7.0.0", + "graphql-config": "^5.1.6", + "is-glob": "^4.0.1", + "jiti": "^2.3.0", + "json-to-pretty-yaml": "^1.2.2", + "listr2": "^10.2.1", + "log-symbols": "^7.0.0", + "micromatch": "^4.0.5", + "shell-quote": "^1.7.3", + "string-env-interpolation": "^1.0.1", + "ts-log": "^3.0.0", + "tslib": "^2.4.0", + "yaml": "^2.3.1", + "yargs": "^18.0.0" + }, + "bin": { + "gql-gen": "esm/bin.js", + "graphql-code-generator": "esm/bin.js", + "graphql-codegen": "esm/bin.js", + "graphql-codegen-cjs": "cjs/bin.js", + "graphql-codegen-esm": "esm/bin.js" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@parcel/watcher": "^2.1.0", + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "@parcel/watcher": { + "optional": true + } + } + }, + "node_modules/@graphql-codegen/cli/node_modules/graphql-config": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/graphql-config/-/graphql-config-5.1.6.tgz", + "integrity": "sha512-fCkYnm4Kdq3un0YIM4BCZHVR5xl0UeLP6syxxO7KAstdY7QVyVvTHP0kRPDYEP1v08uwtJVgis5sj3IOTLOniQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/graphql-file-loader": "^8.0.0", + "@graphql-tools/json-file-loader": "^8.0.0", + "@graphql-tools/load": "^8.1.0", + "@graphql-tools/merge": "^9.0.0", + "@graphql-tools/url-loader": "^9.0.0", + "@graphql-tools/utils": "^11.0.0", + "cosmiconfig": "^8.1.0", + "jiti": "^2.0.0", + "minimatch": "^10.0.0", + "string-env-interpolation": "^1.0.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "cosmiconfig-toml-loader": "^1.0.0", + "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + }, + "peerDependenciesMeta": { + "cosmiconfig-toml-loader": { + "optional": true + } + } + }, + "node_modules/@graphql-codegen/cli/node_modules/graphql-config/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@graphql-codegen/client-preset": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/client-preset/-/client-preset-6.1.0.tgz", + "integrity": "sha512-mGmBuwrOU5oRoaWFodx8g9xu1jecYIiydqvk88QsAIsyMcZwuoybs1lyne85TovpBHjH5CC2wnZGsbDQfcgOCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/template": "^7.20.7", + "@graphql-codegen/add": "^7.1.0", + "@graphql-codegen/gql-tag-operations": "^6.1.0", + "@graphql-codegen/plugin-helpers": "^7.1.0", + "@graphql-codegen/typed-document-node": "^7.1.0", + "@graphql-codegen/typescript": "^6.1.0", + "@graphql-codegen/typescript-operations": "^6.1.0", + "@graphql-codegen/visitor-plugin-common": "^7.2.0", + "@graphql-tools/documents": "^1.0.0", + "@graphql-tools/utils": "^11.2.0", + "@graphql-typed-document-node/core": "3.2.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0", + "graphql-sock": "^1.0.0" + }, + "peerDependenciesMeta": { + "graphql-sock": { + "optional": true + } + } + }, + "node_modules/@graphql-codegen/core": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/core/-/core-6.2.0.tgz", + "integrity": "sha512-RZadhhwYhuy2ZdIGK40vYVBMzXEFGkCC+58MUC/F2af/gKznEYNzHgmNBUBCk/BTklyUsNu0mIXmyGE4tTA0PA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-codegen/plugin-helpers": "^7.1.0", + "@graphql-tools/schema": "^10.0.0", + "@graphql-tools/utils": "^11.2.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-codegen/gql-tag-operations": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/gql-tag-operations/-/gql-tag-operations-6.1.0.tgz", + "integrity": "sha512-AmMcZFwonufvWJnQm7I0lBxKpAm+35BcCrOOvUlBoviohiR17aPoTGAOaNAEtpcpI86lnZ9m9AXUdiKMdm8nnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-codegen/plugin-helpers": "^7.1.0", + "@graphql-codegen/visitor-plugin-common": "^7.2.0", + "@graphql-tools/utils": "^11.2.0", + "auto-bind": "^5.0.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-codegen/plugin-helpers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-7.1.0.tgz", + "integrity": "sha512-ieJH7kZ5oSZKBPJs7CvHMrFY/CLYLklqv74ir93qMwRna6geZsbIMoJzTDBXohxcQTITiProiYSGrEtZjIpYGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^11.2.0", + "change-case-all": "^2.1.0", + "common-tags": "1.8.2", + "import-from": "4.0.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-codegen/schema-ast": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/schema-ast/-/schema-ast-6.1.0.tgz", + "integrity": "sha512-/xuGkM5gUNFRoaQLumKbENdX7Hc8ha49z9OXsEZY8E+46mMjqzXGF0NtCJ892cmoX7EUgI5c8T+LZqS2upx2Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-codegen/plugin-helpers": "^7.1.0", + "@graphql-tools/utils": "^11.2.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-codegen/typed-document-node": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typed-document-node/-/typed-document-node-7.1.0.tgz", + "integrity": "sha512-V6H+ItyqXtYY+JQb76LAoN627Xfzpn29/ifwCFAv61iEepzNzh86sa+yZclflr0G8LDmhcVY5hpPJd3a1qbOfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-codegen/plugin-helpers": "^7.1.0", + "@graphql-codegen/visitor-plugin-common": "^7.2.0", + "auto-bind": "^5.0.0", + "change-case-all": "^2.1.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-codegen/typescript": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-6.1.0.tgz", + "integrity": "sha512-2Hu3111O/AwV28Ap7tNsixlmXSAJuQbQArQklx+IC/tNswpckZnCfmlcBtTJrGU1+mJXEneJXGfb2XWvKjbhlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-codegen/plugin-helpers": "^7.1.0", + "@graphql-codegen/schema-ast": "^6.1.0", + "@graphql-codegen/visitor-plugin-common": "^7.2.0", + "auto-bind": "^5.0.0", + "tslib": "~2.8.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-codegen/typescript-operations": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-6.1.0.tgz", + "integrity": "sha512-zv0ohBJqLP1G/Kgiq1MhLTdNmakChtJivpaMaBmuPz9gasKWJkc9MhxuVytF6xS27PkJOh81TNM9srAkqpdejA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-codegen/plugin-helpers": "^7.1.0", + "@graphql-codegen/schema-ast": "^6.1.0", + "@graphql-codegen/visitor-plugin-common": "^7.2.0", + "auto-bind": "^5.0.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0", + "graphql-sock": "^1.0.0" + }, + "peerDependenciesMeta": { + "graphql-sock": { + "optional": true + } + } + }, + "node_modules/@graphql-codegen/visitor-plugin-common": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-7.2.0.tgz", + "integrity": "sha512-qqtTY8taONuxlR0HvD8z+zgWC3CfJ47M2rATx+geWNIN8v8Itc5TflHYxjkWAtNL6E3q7WZRY5P7se+2S3EjBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-codegen/plugin-helpers": "^7.1.0", + "@graphql-tools/optimize": "^2.0.0", + "@graphql-tools/relay-operation-optimizer": "^7.1.1", + "@graphql-tools/utils": "^11.2.0", + "auto-bind": "^5.0.0", + "change-case-all": "^2.1.0", + "dependency-graph": "^1.0.0", + "graphql-tag": "^2.11.0", + "parse-filepath": "^1.0.2", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-hive/signal": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@graphql-tools/apollo-engine-loader": { + "version": "8.0.28", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^11.0.0", + "@whatwg-node/fetch": "^0.10.13", + "sync-fetch": "0.6.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/batch-execute": { + "version": "10.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^11.0.0", + "@whatwg-node/promise-helpers": "^1.3.2", + "dataloader": "^2.2.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/code-file-loader": { + "version": "8.1.28", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/graphql-tag-pluck": "8.3.27", + "@graphql-tools/utils": "^11.0.0", + "globby": "^11.0.3", + "tslib": "^2.4.0", + "unixify": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/delegate": { + "version": "12.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/batch-execute": "^10.0.5", + "@graphql-tools/executor": "^1.4.13", + "@graphql-tools/schema": "^10.0.29", + "@graphql-tools/utils": "^11.0.0", + "@repeaterjs/repeater": "^3.0.6", + "@whatwg-node/promise-helpers": "^1.3.2", + "dataloader": "^2.2.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/documents": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/executor": { + "version": "1.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^11.0.0", + "@graphql-typed-document-node/core": "^3.2.0", + "@repeaterjs/repeater": "^3.0.4", + "@whatwg-node/disposablestack": "^0.0.6", + "@whatwg-node/promise-helpers": "^1.0.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/executor-common": { + "version": "1.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@envelop/core": "^5.4.0", + "@graphql-tools/utils": "^11.0.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/executor-graphql-ws": { + "version": "3.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/executor-common": "^1.0.6", + "@graphql-tools/utils": "^11.0.0", + "@whatwg-node/disposablestack": "^0.0.6", + "graphql-ws": "^6.0.6", + "isows": "^1.0.7", + "tslib": "^2.8.1", + "ws": "^8.18.3" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/executor-http": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-hive/signal": "^2.0.0", + "@graphql-tools/executor-common": "^1.0.6", + "@graphql-tools/utils": "^11.0.0", + "@repeaterjs/repeater": "^3.0.4", + "@whatwg-node/disposablestack": "^0.0.6", + "@whatwg-node/fetch": "^0.10.13", + "@whatwg-node/promise-helpers": "^1.3.2", + "meros": "^1.3.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/executor-legacy-ws": { + "version": "1.1.25", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^11.0.0", + "@types/ws": "^8.0.0", + "isomorphic-ws": "^5.0.0", + "tslib": "^2.4.0", + "ws": "^8.19.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/git-loader": { + "version": "8.0.32", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/graphql-tag-pluck": "8.3.27", + "@graphql-tools/utils": "^11.0.0", + "is-glob": "4.0.3", + "micromatch": "^4.0.8", + "tslib": "^2.4.0", + "unixify": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/github-loader": { + "version": "9.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/executor-http": "^3.0.6", + "@graphql-tools/graphql-tag-pluck": "^8.3.27", + "@graphql-tools/utils": "^11.0.0", + "@whatwg-node/fetch": "^0.10.13", + "@whatwg-node/promise-helpers": "^1.0.0", + "sync-fetch": "0.6.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/graphql-file-loader": { + "version": "8.1.12", + "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-8.1.12.tgz", + "integrity": "sha512-Nma7gBgJoUbqXWTmdHjouo36tjzewA8MptVcHoH7widzkciaUVzBhriHzqICFB/dVxig//g9MX8s1XawZo7UAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/import": "^7.1.12", + "@graphql-tools/utils": "^11.0.0", + "globby": "^11.0.3", + "tslib": "^2.4.0", + "unixify": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/graphql-tag-pluck": { + "version": "8.3.27", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/plugin-syntax-import-assertions": "^7.26.0", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "@graphql-tools/utils": "^11.0.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/import": { + "version": "7.1.12", + "resolved": "https://registry.npmjs.org/@graphql-tools/import/-/import-7.1.12.tgz", + "integrity": "sha512-QSsdPsdJ7yCgQ5XODyKYpC7NlB9R1Koi0R3418PT7GiRm+9O8gYXSs/23dumcOnpiLrnf4qR2aytBn1+JOAhnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^11.0.0", + "resolve-from": "5.0.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/import/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@graphql-tools/json-file-loader": { + "version": "8.0.26", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^11.0.0", + "globby": "^11.0.3", + "tslib": "^2.4.0", + "unixify": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/load": { + "version": "8.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/schema": "^10.0.31", + "@graphql-tools/utils": "^11.0.0", + "p-limit": "3.1.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/merge": { + "version": "9.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^11.0.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/optimize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/optimize/-/optimize-2.0.0.tgz", + "integrity": "sha512-nhdT+CRGDZ+bk68ic+Jw1OZ99YCDIKYA5AlVAnBHJvMawSx9YQqQAIj4refNc1/LRieGiuWvhbG3jvPVYho0Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/relay-operation-optimizer": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-7.1.4.tgz", + "integrity": "sha512-cwOD/GEo/R//1uGCP0/urIxsMFoUgzkJVyMt9BDM2HhQhU6rSgH5l6lFukAFTJyPJVdyeOdYm2i0Jj5vYWbHTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ardatan/relay-compiler": "^13.0.1", + "@graphql-tools/utils": "^11.1.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/schema": { + "version": "10.0.31", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/merge": "^9.1.7", + "@graphql-tools/utils": "^11.0.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/url-loader": { + "version": "9.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/executor-graphql-ws": "^3.1.2", + "@graphql-tools/executor-http": "^3.0.6", + "@graphql-tools/executor-legacy-ws": "^1.1.25", + "@graphql-tools/utils": "^11.0.0", + "@graphql-tools/wrap": "^11.1.1", + "@types/ws": "^8.0.0", + "@whatwg-node/fetch": "^0.10.13", + "@whatwg-node/promise-helpers": "^1.0.0", + "isomorphic-ws": "^5.0.0", + "sync-fetch": "0.6.0", + "tslib": "^2.4.0", + "ws": "^8.19.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/utils": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-11.2.0.tgz", + "integrity": "sha512-eu9h1R3j/wWc4rvmYJF5AKtlwniDzstrZ/c6KSz+HdI+n7I7iog9xyKmBfpUwSbG1TqPNZBzWjFMkzdYOKq6Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "@whatwg-node/promise-helpers": "^1.0.0", + "cross-inspect": "1.0.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/wrap": { + "version": "11.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/delegate": "^12.0.4", + "@graphql-tools/schema": "^10.0.29", + "@graphql-tools/utils": "^11.0.0", + "@whatwg-node/promise-helpers": "^1.3.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "license": "MIT", + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@inquirer/ansi": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.5.tgz", + "integrity": "sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.1.4.tgz", + "integrity": "sha512-w6KF8ZYRvqHhROkOTHXYC3qIV/KYEu5o12oLqQySvch61vrYtRxNSHTONSdJqWiFJPlCUQAHT5OgOIyuTr+MHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.5", + "@inquirer/core": "^11.1.9", + "@inquirer/figures": "^2.0.5", + "@inquirer/type": "^4.0.5" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.0.12.tgz", + "integrity": "sha512-h9FgGun3QwVYNj5TWIZZ+slii73bMoBFjPfVIGtnFuL4t8gBiNDV9PcSfIzkuxvgquJKt9nr1QzszpBzTbH8Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.1.9", + "@inquirer/type": "^4.0.5" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "11.1.9", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.1.9.tgz", + "integrity": "sha512-BDE4fG22uYh1bGSifcj7JSx119TVYNViMhMu85usp4Fswrzh6M0DV3yld64jA98uOAa2GSQ4Bg4bZRm2d2cwSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.5", + "@inquirer/figures": "^2.0.5", + "@inquirer/type": "^4.0.5", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.1.1.tgz", + "integrity": "sha512-6y11LgmNpmn5D2aB5FgnCfBUBK8ZstwLCalyJmORcJZ/WrhOjm16mu6eSqIx8DnErxDqSLr+Jkp+GP8/Nwd5tA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.1.9", + "@inquirer/external-editor": "^3.0.0", + "@inquirer/type": "^4.0.5" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.0.13.tgz", + "integrity": "sha512-dF2zvrFo9LshkcB23/O1il13kBkBltWIXzut1evfbuBLXMiGIuC45c+ZQ0uukjCDsvI8OWqun4FRYMnzFCQa3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.1.9", + "@inquirer/type": "^4.0.5" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-3.0.0.tgz", + "integrity": "sha512-lDSwMgg+M5rq6JKBYaJwSX6T9e/HK2qqZ1oxmOwn4AQoJE5D+7TumsxLGC02PWS//rkIVqbZv3XA3ejsc9FYvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.2" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.5.tgz", + "integrity": "sha512-NsSs4kzfm12lNetHwAn3GEuH317IzpwrMCbOuMIVytpjnJ90YYHNwdRgYGuKmVxwuIqSgqk3M5qqQt1cDk0tGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + } + }, + "node_modules/@inquirer/input": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.0.12.tgz", + "integrity": "sha512-uiMFBl4LqFzJClh80Q3f9hbOFJ6kgkDWI4LjAeBuyO6EanVVMF69AgOvpi1qdqjDSjDN6578B6nky9ceEpI+1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.1.9", + "@inquirer/type": "^4.0.5" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.0.12.tgz", + "integrity": "sha512-/vrwhEf7Xsuh+YlHF4IjSy3g1cyrQuPaSiHIxCEbLu8qnfvrcvJyCkoktOOF+xV9gSb77/G0n3h04RbMDW2sIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.1.9", + "@inquirer/type": "^4.0.5" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.0.12.tgz", + "integrity": "sha512-CBh7YHju623lxJRcAOo498ZUwIuMy63bqW/vVq0tQAZVv+lkWlHkP9ealYE1utWSisEShY5VMdzIXRmyEODzcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.5", + "@inquirer/core": "^11.1.9", + "@inquirer/type": "^4.0.5" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.4.2.tgz", + "integrity": "sha512-XJmn/wY4AX56l1BRU+ZjDrFtg9+2uBEi4JvJQj82kwJDQKiPgSn4CEsbfGGygS4Gw6rkL4W18oATjfVfaqub2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^5.1.4", + "@inquirer/confirm": "^6.0.12", + "@inquirer/editor": "^5.1.1", + "@inquirer/expand": "^5.0.13", + "@inquirer/input": "^5.0.12", + "@inquirer/number": "^4.0.12", + "@inquirer/password": "^5.0.12", + "@inquirer/rawlist": "^5.2.8", + "@inquirer/search": "^4.1.8", + "@inquirer/select": "^5.1.4" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.2.8.tgz", + "integrity": "sha512-Su7FQvp5buZmCymN3PPoYv31ZQQX4ve2j02k7piGgKAWgE+AQRB5YoYVveGXcl3TZ9ldgRMSxj56YfDFmmaqLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.1.9", + "@inquirer/type": "^4.0.5" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.1.8.tgz", + "integrity": "sha512-fGiHKGD6DyPIYUWxoXnQTeXeyYqSOUrasDMABBmMHUalH/LxkuzY0xVRtimXAt1sUeeyYkVuKQx1bebMuN11Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.1.9", + "@inquirer/figures": "^2.0.5", + "@inquirer/type": "^4.0.5" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.1.4.tgz", + "integrity": "sha512-2kWcGKPMLAXAWRp1AH1SLsQmX+j0QjeljyXMUji9WMZC8nRDO0b7qquIGr6143E7KMLt3VAIGNXzwa/6PXQs4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.5", + "@inquirer/core": "^11.1.9", + "@inquirer/figures": "^2.0.5", + "@inquirer/type": "^4.0.5" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.5.tgz", + "integrity": "sha512-aetVUNeKNc/VriqXlw1NRSW0zhMBB0W4bNbWRJgzRl/3d0QNDQFfk0GO5SDdtjMZVg6o8ZKEiadd7SCCzoOn5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mswjs/interceptors": { + "version": "0.41.3", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.41.3.tgz", + "integrity": "sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@octokit/auth-token": { + "version": "6.0.0", + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/core": { + "version": "7.0.6", + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.3", + "@octokit/request": "^10.0.6", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "before-after-hook": "^4.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/endpoint": { + "version": "11.0.2", + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/graphql": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", + "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "27.0.0", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-graphql": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-6.0.0.tgz", + "integrity": "sha512-crfpnIoFiBtRkvPqOyLOsw12XsveYuY2ieP6uYDosoUegBJpSVxGwut9sxUgFFcll3VTOTqpUf8yGd8x1OmAkQ==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "14.0.0", + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz", + "integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-retry": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.1.0.tgz", + "integrity": "sha512-O1FZgXeiGb2sowEr/hYTr6YunGdSAFWnr2fyW39Ah85H8O33ELASQxcvOFF5LE6Tjekcyu2ms4qAzJVhSaJxTw==", + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=7" + } + }, + "node_modules/@octokit/request": { + "version": "10.0.7", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^11.0.2", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "fast-content-type-parse": "^3.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/request-error": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", + "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^27.0.0" + } + }, + "node_modules/@octokit/webhooks-types": { + "version": "7.6.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@oxc-project/types": { + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@repeaterjs/repeater": { + "version": "3.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@simple-libs/stream-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-1.2.0.tgz", + "integrity": "sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/semver": { + "version": "7.7.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.10.tgz", + "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.10", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.10", + "vitest": "4.1.10" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.10", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.10", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@whatwg-node/disposablestack": { + "version": "0.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@whatwg-node/promise-helpers": "^1.0.0", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@whatwg-node/fetch": { + "version": "0.10.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@whatwg-node/node-fetch": "^0.8.3", + "urlpattern-polyfill": "^10.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@whatwg-node/node-fetch": { + "version": "0.8.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^3.1.1", + "@whatwg-node/disposablestack": "^0.0.6", + "@whatwg-node/promise-helpers": "^1.3.2", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@whatwg-node/node-fetch/node_modules/@fastify/busboy": { + "version": "3.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@whatwg-node/promise-helpers": { + "version": "1.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/ansi-escapes": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-union": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", + "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/auto-bind": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.38", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz", + "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/before-after-hook": { + "version": "4.0.0", + "license": "Apache-2.0" + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/change-case-all": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/change-case-all/-/change-case-all-2.1.0.tgz", + "integrity": "sha512-v6b0WWWkZUMHVuYk82l+WROgkUm4qEN2w5hKRNWtEOYwWqUGoi8C6xH0l1RLF1EoWqDFK6MFclmN3od6ws3/uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "change-case": "^5.2.0", + "sponge-case": "^2.0.2", + "swap-case": "^3.0.2", + "title-case": "^3.0.3" + } + }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "license": "MIT" + }, + "node_modules/conventional-commits-parser": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.4.0.tgz", + "integrity": "sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==", + "license": "MIT", + "dependencies": { + "@simple-libs/stream-utils": "^1.2.0", + "meow": "^13.0.0" + }, + "bin": { + "conventional-commits-parser": "dist/cli/index.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", + "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-inspect": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/dataloader": { + "version": "2.2.3", + "dev": true, + "license": "MIT" + }, + "node_modules/debounce": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-3.0.0.tgz", + "integrity": "sha512-64byRbF0/AirwbuHqB3/ZpMG9/nckDa6ZA0yd6UnaQNwbbemCOwvz2sL5sjXLHhZHADyiwLm0M5qMhltUUx+TA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dependency-graph": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz", + "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/detect-indent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-7.0.2.tgz", + "integrity": "sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.375", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.375.tgz", + "integrity": "sha512-ZWP5eB4BVPW/ZYo9252hQZHZ5XavtsTgpbhcmMmRwymavC5AsLWQWBPaKMeNd2LW0KGby5HPXvj7+sr4ta5j/Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/env-paths": { + "version": "2.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-content-type-parse": { + "version": "3.0.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.0.tgz", + "integrity": "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/graphql": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-17.0.2.tgz", + "integrity": "sha512-FRWbddMxfkjiB7z+aQDWIR+E34xo9I8c9mtK2RPv8PmMzKRvrdsreHL/Ui/TmwHJfhHChEtsFPyMHKI+xuarQQ==", + "license": "MIT", + "engines": { + "node": "^22.0.0 || ^24.0.0 || ^25.0.0 || >=26.0.0" + } + }, + "node_modules/graphql-tag": { + "version": "2.12.7", + "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.7.tgz", + "integrity": "sha512-xnE/NFzy+0eIesvAsREJZ284zTl/wYuBAvpsFSDhRGRdRHdnE90M21Q3xAWyYInb0J756c6x0pIQ62+vtvOs1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/graphql-ws": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-6.1.0.tgz", + "integrity": "sha512-7ft6KWkuaLnLABwzEIimjUMeF0iByo2ThD6q0MICgsvp6nDuT5ppubKzEHniu8Kmlp5GNsLgr5dil8JMrIwUEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@fastify/websocket": "^10 || ^11", + "crossws": "~0.3", + "graphql": "^15.10.1 || ^16 || ^17", + "ws": "^8" + }, + "peerDependenciesMeta": { + "@fastify/websocket": { + "optional": true + }, + "crossws": { + "optional": true + }, + "ws": { + "optional": true + } + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", + "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-4.0.0.tgz", + "integrity": "sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "dev": true, + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-node-process": { + "version": "1.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-relative": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isomorphic-ws": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/isows": { + "version": "1.0.7", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "dev": true, + "license": "ISC" + }, + "node_modules/json-to-pretty-yaml": { + "version": "1.2.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "remedial": "^1.0.7", + "remove-trailing-spaces": "^1.0.6" + }, + "engines": { + "node": ">= 0.2.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-10.2.1.tgz", + "integrity": "sha512-7I5knELsJKTUjXG+A6BkKAiGkW1i25fNa/xlUl9hFtk15WbE9jndA89xu5FzQKrY5llajE1hfZZFMILXkDHk/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.2.0", + "eventemitter3": "^5.0.4", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^10.0.0" + }, + "engines": { + "node": ">=22.13.0" + } + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", + "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loose-envify/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/meros": { + "version": "1.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=13" + }, + "peerDependencies": { + "@types/node": ">=13" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", + "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nock": { + "version": "14.0.16", + "resolved": "https://registry.npmjs.org/nock/-/nock-14.0.16.tgz", + "integrity": "sha512-8r4KEc6nT1D/fdLD/R1BO1CPaVEL8o40u/guFRJlXabN7vr3RmMqyjsY5Krt0nMwhsOAwXQ/mtN5vy5Jh3aErg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mswjs/interceptors": "^0.41.0", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">=18.20.0 <20 || >=20.12.1" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.48", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.48.tgz", + "integrity": "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/onetime": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/outvariant": { + "version": "1.4.3", + "dev": true, + "license": "MIT" + }, + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-filepath": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-root": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-root-regex": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-root-regex": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/propagate": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/regex-parser": { + "version": "2.3.1", + "license": "MIT" + }, + "node_modules/remedial": { + "version": "1.0.8", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "engines": { + "node": "*" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "dev": true, + "license": "ISC" + }, + "node_modules/remove-trailing-spaces": { + "version": "1.0.9", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/rolldown": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.139.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shell-quote": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", + "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "7.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sponge-case": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sponge-case/-/sponge-case-2.0.3.tgz", + "integrity": "sha512-i4h9ZGRfxV6Xw3mpZSFOfbXjf0cQcYmssGWutgNIfFZ2VM+YIWfD71N/kjjwK6X/AAHzBr+rciEcn/L34S8TGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "dev": true, + "license": "MIT" + }, + "node_modules/string-env-interpolation": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/swap-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/swap-case/-/swap-case-3.0.3.tgz", + "integrity": "sha512-6p4op8wE9CQv7uDFzulI6YXUw4lD9n4oQierdbFThEKVWVQcbQcUjdP27W8XE7V4QnWmnq9jueSHceyyQnqQVA==", + "dev": true, + "license": "MIT" + }, + "node_modules/sync-fetch": { + "version": "0.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "node-fetch": "^3.3.2", + "timeout-signal": "^2.0.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/sync-fetch/node_modules/node-fetch": { + "version": "3.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/timeout-signal": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/title-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz", + "integrity": "sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-log": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/ts-log/-/ts-log-3.0.2.tgz", + "integrity": "sha512-esq6hx2lM66sQV1YcFkIYTqrWWabmqBqobKHyn1CswdI5FgfQhkmiKiRWVGBNlIbdjBxEIkNvMIwLKKPgRYZLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20", + "npm": ">=10" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel": { + "version": "0.0.6", + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/typescript": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, + "node_modules/unc-path-regex": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/undici": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz", + "integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/universal-user-agent": { + "version": "7.0.3", + "license": "ISC" + }, + "node_modules/unixify": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "normalize-path": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unixify/node_modules/normalize-path": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/urlpattern-polyfill": { + "version": "10.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.4.tgz", + "integrity": "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.16", + "rolldown": "~1.1.4", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-10.0.0.tgz", + "integrity": "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "string-width": "^8.2.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json index 6a9dda870a..bd8771b7ed 100644 --- a/package.json +++ b/package.json @@ -1,73 +1,84 @@ { - "name": "release-drafter-github-app", - "version": "5.11.0", - "description": "A GitHub app that draft releases based on pull-request labels", - "author": "Tim Lucas (https://github.com/toolmantim)", - "license": "ISC", - "repository": "https://github.com/toolmantim/release-drafter", - "scripts": { - "dev": "nodemon --exec \"npm start\"", - "start": "probot run ./index.js", - "test": "jest", - "test:watch": "jest --watch --notify --notifyMode=change --coverage", - "generate-schema": "node ./bin/generate-schema.js print", - "generate-fixtures": "node ./bin/generate-fixtures.js", - "lint": "eslint **/*.js", - "prettier": "prettier --write **/*.{js,md,json,yml,yaml}", - "postversion": "npm run test && git push && git push --tags && npm publish && npm run deploy && npm run open-releases", - "open-releases": "open \"$(node -e 'console.log(`${require(\"./package.json\").repository}/releases`)')\"", - "deploy": "git push -f heroku \"$(node -e 'console.log(`v${require(\"./package.json\").version}`)')\":master" - }, - "dependencies": { - "@actions/core": "^1.2.0", - "@hapi/joi": "15.1.1", - "cli-table3": "^0.6.0", - "compare-versions": "3.6.0", - "escape-string-regexp": "4.0.0", - "lodash": "4.17.19", - "probot": "9.11.5", - "regex-parser": "2.2.10", - "request": "2.88.2", - "semver": "7.3.2" + "name": "release-drafter", + "description": "Drafts your next release notes as pull requests are merged into your branch(es).", + "version": "7.6.0", + "author": "", + "type": "module", + "private": true, + "homepage": "https://github.com/release-drafter/release-drafter", + "repository": { + "type": "git", + "url": "git+https://github.com/release-drafter/release-drafter.git" }, - "devDependencies": { - "@types/hapi__joi": "^15.0.4", - "eslint": "7.2.0", - "eslint-config-prettier": "6.11.0", - "eslint-plugin-prettier": "3.1.4", - "husky": "4.2.5", - "jest": "26.0.1", - "joi-to-json-schema": "^5.1.0", - "lint-staged": "10.2.11", - "mocked-env": "^1.3.2", - "nock": "12.0.3", - "nock-knock": "2.0.0", - "node-fetch": "2.6.0", - "nodemon": "2.0.4", - "prettier": "2.0.5", - "smee-client": "1.1.0" + "bugs": { + "url": "https://github.com/release-drafter/release-drafter/issues" }, + "keywords": [ + "actions", + "release", + "release-notes", + "release-automation" + ], "engines": { - "node": ">= 10.x" + "node": ">=24.0.0", + "npm": ">=12.0.1" }, - "jest": { - "collectCoverage": true, - "collectCoverageFrom": [ - "index.js", - "lib/**" - ] + "imports": { + "#src/*": "./src/*", + "#tests/*": "./src/tests/*" }, - "husky": { - "hooks": { - "pre-commit": "lint-staged" - } + "scripts": { + "build": "vite build", + "test": "vitest", + "test:run": "vitest run", + "coverage": "node src/scripts/coverage-summary.ts", + "tsc:check": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json", + "format:write": "biome format --write .", + "format:check": "biome format .", + "lint": "biome lint .", + "check": "biome check --write .", + "codegen": "graphql-codegen -c src/scripts/graphql.codegen-config.ts -v", + "schemas": "node src/scripts/json-schema.ts", + "all": "npm run check && npm run tsc:check && npm run test:run && npm run schemas && npm run build", + "check:clean": "node src/scripts/check-clean.ts", + "preversion": "npm run all && npm run check:clean", + "version": "git add package.json package-lock.json", + "postversion": "git push && git push origin v$npm_package_version" + }, + "license": "MIT", + "dependencies": { + "@actions/core": "^3.0.1", + "@actions/github": "^9.1.1", + "@graphql-typed-document-node/core": "^3.2.0", + "@octokit/graphql": "^9.0.3", + "@octokit/plugin-paginate-graphql": "^6.0.0", + "@octokit/plugin-rest-endpoint-methods": "^17.0.0", + "@octokit/plugin-retry": "^8.1.0", + "@octokit/request-error": "^7.1.0", + "@octokit/types": "^16.0.0", + "compare-versions": "^6.1.1", + "conventional-commits-parser": "^6.4.0", + "escape-string-regexp": "^5.0.0", + "graphql": "^17.0.2", + "ignore": "^7.0.6", + "regex-parser": "^2.3.1", + "semver": "^7.8.5", + "yaml": "^2.9.0", + "zod": "^4.4.3" }, - "lint-staged": { - "*.js": [ - "eslint --fix" - ], - "*.{json,css,md}": [ - "prettier --write" - ] + "devDependencies": { + "@biomejs/biome": "2.5.3", + "@graphql-codegen/cli": "^7.2.0", + "@graphql-codegen/typed-document-node": "^7.1.0", + "@graphql-codegen/typescript": "^6.1.0", + "@graphql-codegen/typescript-operations": "^6.1.0", + "@octokit/webhooks-types": "^7.6.1", + "@types/node": "^24.13.3", + "@types/semver": "^7.7.1", + "@vitest/coverage-v8": "^4.1.10", + "nock": "^14.0.16", + "typescript": "^7.0.2", + "vite": "^8.1.4", + "vitest": "^4.1.10" } } diff --git a/schema.json b/schema.json index c04124ccb4..f5d83cb711 100644 --- a/schema.json +++ b/schema.json @@ -1,63 +1,79 @@ { - "title": "JSON schema for Release Drafter yaml files", - "id": "https://github.com/release-drafter/release-drafter/blob/master/schema.json", - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { - "references": { - "default": ["master"], - "type": "array", - "items": { - "type": "string" - } - }, - "change-template": { - "default": "* $TITLE (#$NUMBER) @$AUTHOR", - "type": "string" - }, - "change-title-escapes": { + "_extends": { "anyOf": [ { - "type": "string", - "enum": [""] + "type": "string" }, { - "default": "", - "type": "string" + "type": "null" + }, + { + "type": "object", + "properties": { + "from": { + "type": "string", + "pattern": "\\S" + }, + "strategy": { + "anyOf": [ + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string", + "enum": ["override", "append", "prepend"] + } + }, + { + "type": "null" + } + ] + } + }, + "required": ["from"], + "additionalProperties": false } ] }, + "change-template": { + "default": "* $TITLE (#$NUMBER) $AUTHORS", + "type": "string" + }, + "change-author-template": { + "default": "$AUTHOR_MENTION", + "type": "string" + }, + "change-authors-separator": { + "default": ", ", + "type": "string" + }, + "change-authors-final-separator": { + "type": "string" + }, + "change-title-escapes": { + "type": "string" + }, "no-changes-template": { "default": "* No changes", "type": "string" }, "version-template": { - "default": "$MAJOR.$MINOR.$PATCH", + "default": "$MAJOR.$MINOR.$PATCH$PRERELEASE", "type": "string" }, "name-template": { - "anyOf": [ - { - "type": "string", - "enum": [""] - }, - { - "default": "", - "type": "string" - } - ] + "type": "string" + }, + "tag-prefix": { + "type": "string" }, "tag-template": { - "anyOf": [ - { - "type": "string", - "enum": [""] - }, - { - "default": "", - "type": "string" - } - ] + "type": "string" }, "exclude-labels": { "default": [], @@ -73,20 +89,61 @@ "type": "string" } }, + "include-paths": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "exclude-paths": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "exclude-contributors": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "new-contributor-template": { + "default": "* $AUTHOR_MENTION made their first contribution in #$NUMBER", + "type": "string" + }, + "no-contributors-template": { + "default": "No contributors", + "type": "string" + }, "sort-by": { "default": "merged_at", - "enum": ["merged_at", "title"], - "type": "string" + "type": "string", + "enum": ["merged_at", "title"] }, "sort-direction": { "default": "descending", - "enum": ["ascending", "descending"], - "type": "string" + "type": "string", + "enum": ["ascending", "descending"] }, - "prerelease": { + "filter-by-commitish": { "default": false, "type": "boolean" }, + "pull-request-limit": { + "default": 5, + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "history-limit": { + "default": 15, + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, "replacers": { "default": [], "type": "array", @@ -94,22 +151,14 @@ "type": "object", "properties": { "search": { - "type": "string" + "type": "string", + "minLength": 1 }, "replace": { - "anyOf": [ - { - "type": "string", - "enum": [""] - }, - { - "type": "string" - } - ] + "type": "string", + "minLength": 0 } }, - "additionalProperties": false, - "patterns": [], "required": ["search", "replace"] } }, @@ -120,22 +169,213 @@ "type": "object", "properties": { "title": { - "type": "string" + "type": "string", + "minLength": 1 }, - "label": { - "type": "string" + "type": { + "default": "changelog", + "type": "string", + "enum": [ + "changelog", + "pre-include", + "pre-exclude", + "version-resolver" + ] + }, + "exclusive": { + "default": false, + "type": "boolean" + }, + "collapse-after": { + "default": -1, + "type": "integer", + "minimum": -1, + "maximum": 9007199254740991 + }, + "semver-increment": { + "default": "patch", + "type": "string", + "enum": ["major", "minor", "patch"] }, "labels": { "default": [], "type": "array", "items": { - "type": "string" + "type": "string", + "minLength": 1 } + }, + "label": { + "type": "string", + "minLength": 1 + }, + "when": { + "default": [], + "anyOf": [ + { + "type": "object", + "properties": { + "conventional": { + "anyOf": [ + { + "type": "boolean", + "const": true + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "minLength": 1 + }, + "types": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "scope": { + "type": "string", + "minLength": 1 + }, + "scopes": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "breaking": { + "type": "boolean" + } + } + } + ] + }, + "label": { + "type": "string", + "minLength": 1 + }, + "labels": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "labels-mode": { + "default": "any", + "type": "string", + "enum": ["any", "all", "only", "exactly"] + }, + "path": { + "type": "string", + "minLength": 1 + }, + "paths": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "paths-mode": { + "default": "any", + "type": "string", + "enum": ["any", "all", "only", "exactly"] + } + } + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "conventional": { + "anyOf": [ + { + "type": "boolean", + "const": true + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "minLength": 1 + }, + "types": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "scope": { + "type": "string", + "minLength": 1 + }, + "scopes": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "breaking": { + "type": "boolean" + } + } + } + ] + }, + "label": { + "type": "string", + "minLength": 1 + }, + "labels": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "labels-mode": { + "default": "any", + "type": "string", + "enum": ["any", "all", "only", "exactly"] + }, + "path": { + "type": "string", + "minLength": 1 + }, + "paths": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "paths-mode": { + "default": "any", + "type": "string", + "enum": ["any", "all", "only", "exactly"] + } + } + } + } + ] } - }, - "additionalProperties": false, - "patterns": [], - "required": ["title"] + } } }, "version-resolver": { @@ -154,61 +394,112 @@ "type": "object", "properties": { "major": { + "default": { + "labels": [] + }, "type": "object", "properties": { "labels": { "type": "array", "items": { - "type": "string" + "type": "string", + "minLength": 1 } } }, - "additionalProperties": false, - "patterns": [] + "required": ["labels"] }, "minor": { + "default": { + "labels": [] + }, "type": "object", "properties": { "labels": { "type": "array", "items": { - "type": "string" + "type": "string", + "minLength": 1 } } }, - "additionalProperties": false, - "patterns": [] + "required": ["labels"] }, "patch": { + "default": { + "labels": [] + }, "type": "object", "properties": { "labels": { "type": "array", "items": { - "type": "string" + "type": "string", + "minLength": 1 } } }, - "additionalProperties": false, - "patterns": [] + "required": ["labels"] }, "default": { "default": "patch", - "enum": ["major", "minor", "patch"], - "type": "string" + "type": "string", + "enum": ["major", "minor", "patch"] } - }, - "additionalProperties": false, - "patterns": [] + } + }, + "category-template": { + "default": "## $TITLE", + "type": "string" }, "template": { + "default": "", "type": "string" }, - "_extends": { + "latest": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "boolean" + } + ] + }, + "prerelease": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "boolean" + } + ] + }, + "prerelease-identifier": { + "type": "string" + }, + "include-pre-releases": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "boolean" + } + ] + }, + "commitish": { + "type": "string" + }, + "header": { + "type": "string" + }, + "footer": { + "type": "string" + }, + "filter-by-range": { "type": "string" } - }, - "additionalProperties": false, - "patterns": [], - "required": [] + } } diff --git a/src/actions/autolabeler/config/action-input.schema.ts b/src/actions/autolabeler/config/action-input.schema.ts new file mode 100644 index 0000000000..bf32b04c0a --- /dev/null +++ b/src/actions/autolabeler/config/action-input.schema.ts @@ -0,0 +1,17 @@ +import type * as z from 'zod' +import { object, string } from 'zod' +import { sharedInputSchema } from '#src/common/index.ts' + +export const actionInputSchema = object({ + /** + * If your workflow requires multiple release-drafter configs it be helpful to override the config-name. + * The config should still be located inside `.github` as that's where we are looking for config files. + * @default 'release-drafter.yml' + */ + 'config-name': string().optional().default('release-drafter.yml'), +}).and(sharedInputSchema) + +/** + * Full action inputs + */ +export type ActionInput = z.infer diff --git a/src/actions/autolabeler/config/config.schema.ts b/src/actions/autolabeler/config/config.schema.ts new file mode 100644 index 0000000000..2297bf8a8a --- /dev/null +++ b/src/actions/autolabeler/config/config.schema.ts @@ -0,0 +1,24 @@ +import type * as z from 'zod' +import { array, object, string } from 'zod' + +export const configSchema = object({ + /** + * You can add automatically a label into a pull request. + * Available matchers are `files` (glob), `branch` (regex), `title` (regex) and `body` (regex). + * Matchers are evaluated independently; the label will be set if at least one of the matchers meets the criteria. + */ + autolabeler: array( + object({ + label: string().min(1), + files: array(string().min(1)).optional().default([]), + branch: array(string().min(1)).optional().default([]), + title: array(string().min(1)).optional().default([]), + body: array(string().min(1)).optional().default([]), + }), + ).min(1), +}).meta({ + title: "JSON schema for Release Drafter's autolabeler action config.", + id: 'https://github.com/release-drafter/release-drafter/blob/master/autolabeler/schema.json', +}) + +export type Config = z.output diff --git a/src/actions/autolabeler/config/get-action-inputs.ts b/src/actions/autolabeler/config/get-action-inputs.ts new file mode 100644 index 0000000000..1eb1748eb3 --- /dev/null +++ b/src/actions/autolabeler/config/get-action-inputs.ts @@ -0,0 +1,16 @@ +import * as core from '@actions/core' +import { type ActionInput, actionInputSchema } from './action-input.schema.ts' + +export const getActionInput = (): ActionInput => { + // getInput returns an empty string if the value is not defined. + // We want to convert that to undefined for optional inputs. + const getInput = (name: keyof ActionInput) => core.getInput(name) || undefined + + // Boolean inputs are handled by zod's stringBool() during parsing. + + return actionInputSchema.parse({ + 'config-name': getInput('config-name'), + token: getInput('token'), + 'dry-run': getInput('dry-run'), + }) +} diff --git a/src/actions/autolabeler/config/get-config.ts b/src/actions/autolabeler/config/get-config.ts new file mode 100644 index 0000000000..a80f4f966e --- /dev/null +++ b/src/actions/autolabeler/config/get-config.ts @@ -0,0 +1,18 @@ +import * as core from '@actions/core' +import { context } from '@actions/github' +import { composeConfigGet } from '#src/common/index.ts' +import { configSchema } from './config.schema.ts' + +export const getConfig = async (configName: string) => { + const { config, contexts } = await composeConfigGet(configName, context) + + if (contexts.length > 1) { + core.info(`Config was fetched from ${contexts.length} different contexts.`) + } else if (contexts.length === 1) { + core.info( + `Config fetched ${contexts[0].scheme === 'file' ? 'locally' : `on remote "${contexts[0].repo.owner}/${contexts[0].repo.repo}${contexts[0].ref ? `@${contexts[0].ref}` : ''}"${!contexts[0].ref ? ' on the default branch' : ''}`}.`, + ) + } + + return configSchema.parse(config) +} diff --git a/src/actions/autolabeler/config/index.ts b/src/actions/autolabeler/config/index.ts new file mode 100644 index 0000000000..7f9bf799bd --- /dev/null +++ b/src/actions/autolabeler/config/index.ts @@ -0,0 +1,8 @@ +/* node:coverage ignore file -- @preserve */ + +export { type ActionInput, actionInputSchema } from './action-input.schema.ts' +export { type Config, configSchema } from './config.schema.ts' +export { getActionInput } from './get-action-inputs.ts' +export { getConfig } from './get-config.ts' +export { type ParsedConfig, parseConfig } from './parse-config.ts' +export { parseConfigFile } from './parse-config-file.ts' diff --git a/src/actions/autolabeler/config/parse-config-file.ts b/src/actions/autolabeler/config/parse-config-file.ts new file mode 100644 index 0000000000..dfbbb0be85 --- /dev/null +++ b/src/actions/autolabeler/config/parse-config-file.ts @@ -0,0 +1,6 @@ +import { parse as yamlparse } from 'yaml' +import { type Config, configSchema } from './config.schema.ts' + +export const parseConfigFile = async (configFile: string): Promise => { + return configSchema.parse(yamlparse(configFile)) +} diff --git a/src/actions/autolabeler/config/parse-config.ts b/src/actions/autolabeler/config/parse-config.ts new file mode 100644 index 0000000000..3d3b0ed208 --- /dev/null +++ b/src/actions/autolabeler/config/parse-config.ts @@ -0,0 +1,55 @@ +import * as core from '@actions/core' +import { stringToRegex } from '#src/common/index.ts' +import type { Config } from './config.schema.ts' + +/** + * Returns a copy of `config`, updated with values from `input`. + * + * Also performs some validation. + * + * Input takes precedence, because it's more easy to change at runtime + */ +export const parseConfig = ({ config: originalConfig }: { config: Config }) => { + const config = structuredClone(originalConfig) + + // Apply some transformations + const autolabeler = config.autolabeler + // convert 'branch', 'title' and 'body' to regex and remove invalid entries + .map((autolabel) => { + try { + return { + ...autolabel, + branch: autolabel.branch.map((reg) => { + return stringToRegex(reg) + }), + title: autolabel.title.map((reg) => { + return stringToRegex(reg) + }), + body: autolabel.body.map((reg) => { + return stringToRegex(reg) + }), + } + } catch { + core.warning( + `Bad autolabeler regex: '${autolabel.branch}', '${autolabel.title}' or '${autolabel.body}'`, + ) + return false + } + }) + .filter((a) => !!a) + + // Build parsed config object - alters original type + const parsedConfig = { + ...config, + autolabeler, + } + + return parsedConfig +} + +/** + * Similar to Config, but with input values merged in and defaults applied. + * + * @see mergeInputAndConfig + */ +export type ParsedConfig = ReturnType diff --git a/src/actions/autolabeler/main.ts b/src/actions/autolabeler/main.ts new file mode 100644 index 0000000000..b877167db9 --- /dev/null +++ b/src/actions/autolabeler/main.ts @@ -0,0 +1,109 @@ +import * as core from '@actions/core' +import { context } from '@actions/github' +import type { PullRequestEvent } from '@octokit/webhooks-types' +import ignore from 'ignore' +import { getOctokit, getPullRequestChangedFiles } from '#src/common/index.ts' +import type { ParsedConfig } from './config/index.ts' + +export const main = async (params: { + config: ParsedConfig + dryRun?: boolean +}) => { + core.info( + `Running for event "${context.eventName || '[undefined]'}.${context.payload.action || '[undefined]'}"`, + ) + + if ( + context.eventName !== 'pull_request' && + context.eventName !== 'pull_request_target' + ) { + throw new Error( + `Event type is wrong. Expected 'pull_request' or 'pull_request_target', received '${context.eventName}'`, + ) + } + const octokit = getOctokit() + + /** + * @see https://docs.github.com/en/webhooks/webhook-events-and-payloads#pull_request + */ + const payload = context.payload as PullRequestEvent + + const changedFiles = await getPullRequestChangedFiles(octokit, { + ...context.repo, + pull_number: payload.number, + }) + const labels = new Set() + + for (const autolabel of params.config.autolabeler) { + let found = false + + // check modified files + if (!found && autolabel.files.length > 0) { + const matcher = ignore().add(autolabel.files) + if (changedFiles.some((file) => matcher.ignores(file))) { + labels.add(autolabel.label) + found = true + core.info(`Found label for files: '${autolabel.label}'`) + } + } + + // check branch names + if (!found && autolabel.branch.length > 0) { + for (const matcher of autolabel.branch) { + if (matcher.test(payload.pull_request.head.ref)) { + labels.add(autolabel.label) + found = true + core.info(`Found label for branch: '${autolabel.label}'`) + break + } + } + } + + // check pr title + if (!found && autolabel.title.length > 0) { + for (const matcher of autolabel.title) { + if (matcher.test(payload.pull_request.title)) { + labels.add(autolabel.label) + found = true + core.info(`Found label for title: '${autolabel.label}'`) + break + } + } + } + + // check pr body + if ( + !found && + payload.pull_request.body != null && + autolabel.body.length > 0 + ) { + for (const matcher of autolabel.body) { + if (matcher.test(payload.pull_request.body)) { + labels.add(autolabel.label) + found = true + core.info(`Found label for body: '${autolabel.label}'`) + break + } + } + } + } + + if (labels.size > 0) { + if (params.dryRun) { + core.info( + `[dry-run] Would add labels [${Array.from(labels).join(', ')}] to PR #${payload.number}`, + ) + } else { + await octokit.rest.issues.addLabels({ + ...context.repo, + issue_number: payload.number, + labels: Array.from(labels), + }) + } + } + + return { + pr_number: payload.number.toString(), + labels: labels.size ? Array.from(labels).join(',') : undefined, + } +} diff --git a/src/actions/autolabeler/run.ts b/src/actions/autolabeler/run.ts new file mode 100644 index 0000000000..3f75b219a1 --- /dev/null +++ b/src/actions/autolabeler/run.ts @@ -0,0 +1,13 @@ +/* node:coverage ignore file -- @preserve */ +/** + * The entrypoint for the action. This file simply imports and runs the action's + * main logic. + * + * Do not add any logic to this file; instead, add it to `runner.ts`. + * + * `runner.ts` is the entrypoint for tests and should contain all the action's + * main logic. + */ +import { run } from './runner.ts' + +await run() diff --git a/src/actions/autolabeler/runner.ts b/src/actions/autolabeler/runner.ts new file mode 100644 index 0000000000..c0e68cea75 --- /dev/null +++ b/src/actions/autolabeler/runner.ts @@ -0,0 +1,28 @@ +import * as core from '@actions/core' +import { getActionInput, getConfig, parseConfig } from './config/index.ts' +import { main } from './main.ts' + +/** + * The main function for the action. + * + * @returns Resolves when the action is complete. + */ +export async function run(): Promise { + try { + const input = getActionInput() + const config = parseConfig({ + config: await getConfig(input['config-name']), + }) + + const { labels, pr_number } = await main({ + config, + dryRun: input['dry-run'], + }) + + if (pr_number) core.setOutput('number', pr_number) + if (labels) core.setOutput('labels', labels) + } catch (error) { + // Fail the workflow run if an error occurs + if (error instanceof Error) core.setFailed(error.message) + } +} diff --git a/src/actions/drafter/common/category-matching.ts b/src/actions/drafter/common/category-matching.ts new file mode 100644 index 0000000000..e6cfa044b0 --- /dev/null +++ b/src/actions/drafter/common/category-matching.ts @@ -0,0 +1,225 @@ +import { CommitParser } from 'conventional-commits-parser' +import ignore from 'ignore' +import type { ParsedConfig } from '../config/index.ts' + +type ParsedCategory = ParsedConfig['categories'][number] +type ParsedCondition = ParsedCategory['when'][number] +type LabelsMode = ParsedCondition['labels-mode'] +type PathsMode = ParsedCondition['paths-mode'] + +type PullRequestLike = { + title?: string + labels?: { + nodes?: ({ name?: string | null } | null)[] | null + } | null + changedFiles?: string[] +} + +// Matches conventional-changelog's conventionalcommits preset so `!` breaking +// markers are parsed into notes instead of rejected by the default parser. +// https://github.com/conventional-changelog/conventional-changelog/blob/74a977a970f0eb5cdf317538baec8290eb909a05/packages/conventional-changelog-conventionalcommits/src/parser.js#L4 +const conventionalParser = new CommitParser({ + headerPattern: /^(\w*)(?:\((.*)\))?!?: (.*)$/, + breakingHeaderPattern: /^(\w*)(?:\((.*)\))?!: (.*)$/, +}) + +export type ChangelogCategory = Extract +export type VersionResolverCategory = Extract< + ParsedCategory, + { type: 'version-resolver' } +> + +const getPullRequestLabels = (pullRequest: PullRequestLike) => + (pullRequest.labels?.nodes ?? []) + .filter((label): label is NonNullable & { name: string } => + Boolean(label?.name), + ) + .map((label) => label.name) + +const unique = (values: string[]) => [...new Set(values)] + +const matchesValues = ( + actualValues: string[], + expectedValues: string[], + mode: LabelsMode | PathsMode, +) => { + const actual = unique(actualValues) + const expected = unique(expectedValues) + + if (expected.length === 0) { + return true + } + + switch (mode) { + case 'all': + return expected.every((value) => actual.includes(value)) + case 'only': + // An empty matched set vacuously passes `every`, but `only` should still + // require at least one configured match before the condition counts. + return ( + actual.length > 0 && actual.every((value) => expected.includes(value)) + ) + case 'exactly': + return ( + actual.length === expected.length && + actual.every((value) => expected.includes(value)) + ) + default: + return ( + expected.length === 0 || + expected.some((value) => actual.includes(value)) + ) + } +} + +const matchesPullRequestPaths = ( + condition: ParsedCondition, + pullRequest: PullRequestLike, +) => { + if (condition.paths.length === 0) { + return true + } + + const changedFiles = unique(pullRequest.changedFiles ?? []) + if (changedFiles.length === 0) { + return false + } + + const expectedMatchers = unique(condition.paths).map((path) => ({ + path, + matcher: ignore().add(path), + })) + const matchesAllConfiguredPaths = expectedMatchers.every(({ matcher }) => + changedFiles.some((file) => matcher.ignores(file)), + ) + const matchesOnlyConfiguredPaths = + changedFiles.length > 0 && + changedFiles.every((file) => + expectedMatchers.some(({ matcher }) => matcher.ignores(file)), + ) + + switch (condition['paths-mode']) { + case 'all': + return matchesAllConfiguredPaths + case 'only': + return matchesOnlyConfiguredPaths + case 'exactly': + return matchesAllConfiguredPaths && matchesOnlyConfiguredPaths + default: + return changedFiles.some((file) => + expectedMatchers.some(({ matcher }) => matcher.ignores(file)), + ) + } +} + +const parseConventionalTitle = (title?: string) => { + if (!title) return undefined + + const parsed = conventionalParser.parse(title) + if (typeof parsed.type !== 'string') return undefined + + return { + type: parsed.type, + scope: typeof parsed.scope === 'string' ? parsed.scope : undefined, + // By default, only breaking changes are added to notes + // see https://conventional-changelog.js.org/commits-parser/#breaking-changes-and-notes + breaking: parsed.notes.length > 0, + } +} + +const matchesConventionalTitle = ( + condition: ParsedCondition, + pullRequest: PullRequestLike, +) => { + if (!condition.conventional) { + return true + } + + const parsed = parseConventionalTitle(pullRequest.title) + if (!parsed) { + return false + } + + const { types, scopes, breaking } = condition.conventional + + return ( + (types.length === 0 || types.includes(parsed.type)) && + (scopes.length === 0 || + (parsed.scope !== undefined && scopes.includes(parsed.scope))) && + (breaking === undefined || breaking === parsed.breaking) + ) +} + +export const matchesCategoryCondition = ( + condition: ParsedCondition, + pullRequest: PullRequestLike, +) => + matchesValues( + getPullRequestLabels(pullRequest), + condition.labels, + condition['labels-mode'], + ) && + matchesPullRequestPaths(condition, pullRequest) && + matchesConventionalTitle(condition, pullRequest) + +export const matchesCategory = ( + category: ParsedCategory, + pullRequest: PullRequestLike, +) => + category.when.length === 0 || + category.when.some((condition) => + matchesCategoryCondition(condition, pullRequest), + ) + +export const filterPullRequestsByPreCategories = ( + pullRequests: Pr[], + categories: ParsedConfig['categories'], +) => { + const preIncludeCategories = categories.filter( + (category) => category.type === 'pre-include', + ) + const preExcludeCategories = categories.filter( + (category) => category.type === 'pre-exclude', + ) + + return pullRequests.filter((pullRequest) => { + const isIncluded = + preIncludeCategories.length === 0 || + preIncludeCategories.some((category) => + matchesCategory(category, pullRequest), + ) + + if (!isIncluded) { + return false + } + + return !preExcludeCategories.some((category) => + matchesCategory(category, pullRequest), + ) + }) +} + +/** + * Determines if any of the categories require loading pull request changed files. + */ +export const needsPullRequestChangedFiles = ( + categories: ParsedConfig['categories'], +) => + categories.some((category) => + category.when.some((condition) => condition.paths.length > 0), + ) + +export const getChangelogCategories = ( + categories: ParsedConfig['categories'], +): ChangelogCategory[] => + categories.filter( + (category): category is ChangelogCategory => category.type === 'changelog', + ) + +export const getVersionResolverCategories = ( + categories: ParsedConfig['categories'], +): VersionResolverCategory[] => + categories.filter( + (category): category is VersionResolverCategory => + category.type === 'version-resolver', + ) diff --git a/src/actions/drafter/config/get-action-inputs.ts b/src/actions/drafter/config/get-action-inputs.ts new file mode 100644 index 0000000000..2ec709bacf --- /dev/null +++ b/src/actions/drafter/config/get-action-inputs.ts @@ -0,0 +1,37 @@ +import * as core from '@actions/core' +import { + type ActionInput, + actionInputSchema, +} from './schemas/action-input.schema.ts' + +export const getActionInput = (): ActionInput => { + // getInput returns an empty string if the value is not defined. + // We want to convert that to undefined for optional inputs. + const getInput = (name: keyof ActionInput) => core.getInput(name) || undefined + + // Boolean inputs are handled by zod's stringBool() during parsing. + + // Make sure we don't miss any inputs from the schema + const actionInput: Record> = { + // exclusive to action input + 'config-name': getInput('config-name'), + name: getInput('name'), + tag: getInput('tag'), + version: getInput('version'), + publish: getInput('publish'), + token: getInput('token'), + + // can override the config + latest: getInput('latest'), + prerelease: getInput('prerelease'), + 'prerelease-identifier': getInput('prerelease-identifier'), + 'include-pre-releases': getInput('include-pre-releases'), + commitish: getInput('commitish'), + header: getInput('header'), + footer: getInput('footer'), + 'dry-run': getInput('dry-run'), + 'filter-by-range': getInput('filter-by-range'), + } + + return actionInputSchema.parse(actionInput) +} diff --git a/src/actions/drafter/config/get-config.ts b/src/actions/drafter/config/get-config.ts new file mode 100644 index 0000000000..0d0fdfa9e5 --- /dev/null +++ b/src/actions/drafter/config/get-config.ts @@ -0,0 +1,20 @@ +import * as core from '@actions/core' +import { context } from '@actions/github' +import { composeConfigGet } from '#src/common/index.ts' +import { configSchema } from './schemas/config.schema.ts' + +export const getConfig = async (configName: string) => { + const { config, contexts } = await composeConfigGet(configName, context) + + contexts.forEach(({ filepath, ref, repo, scheme }) => { + const remotePath = `${repo.owner}/${repo.repo}/${filepath}${ref ? `@${ref}` : ''}` + const location = + scheme === 'file' + ? `locally from "${filepath}"` + : `from "${remotePath}"${ref ? '' : ' on the default branch'}` + + core.info(`Config fetched ${location}.`) + }) + + return configSchema.parse(config) +} diff --git a/src/actions/drafter/config/index.ts b/src/actions/drafter/config/index.ts new file mode 100644 index 0000000000..5f2a789a0a --- /dev/null +++ b/src/actions/drafter/config/index.ts @@ -0,0 +1,10 @@ +/* node:coverage ignore file -- @preserve */ + +export { getActionInput } from './get-action-inputs.ts' +export { getConfig } from './get-config.ts' +export { + mergeInputAndConfig, + type ParsedConfig, +} from './merge-input-and-config.ts' +export * from './schemas/index.ts' +export { setActionOutput } from './set-action-output.ts' diff --git a/src/actions/drafter/config/merge-input-and-config.ts b/src/actions/drafter/config/merge-input-and-config.ts new file mode 100644 index 0000000000..003b74e81f --- /dev/null +++ b/src/actions/drafter/config/merge-input-and-config.ts @@ -0,0 +1,225 @@ +import * as core from '@actions/core' +import { context } from '@actions/github' +import validRange from 'semver/ranges/valid.js' +import { stringToRegex } from '#src/common/index.ts' +import { parseCategories } from './parse-categories.ts' +import type { Config } from './schemas/config.schema.ts' +import type { CommonConfig } from './schemas/index.ts' + +type DeprecatedCategoryConfig = Pick< + Config, + | 'exclude-labels' + | 'include-labels' + | 'include-paths' + | 'exclude-paths' + | 'version-resolver' +> +type MutableConfig = Omit< + ReturnType>, + keyof DeprecatedCategoryConfig +> + +type SharedConfigKey = keyof CommonConfig & keyof MutableConfig +type SharedKeysByValue = { + [Key in SharedConfigKey]: Exclude extends Value + ? Exclude extends Value + ? Key + : never + : never +}[SharedConfigKey] + +type SharedStringKey = SharedKeysByValue +type SharedBooleanKey = SharedKeysByValue + +/** + * Similar to Config, but with input values merged in and defaults applied. + * + * @see mergeInputAndConfig + */ +export type ParsedConfig = ReturnType + +/** + * Returns a copy of `config`, updated with values from `input`. + * + * Also performs some validation. + * + * Input takes precedence, because it's more easy to change at runtime + */ +export const mergeInputAndConfig = (params: { + config: Config + input: CommonConfig +}) => { + const { config: originalConfig, input } = params + const { + 'exclude-labels': excludeLabels, + 'include-labels': includeLabels, + 'include-paths': includePaths, + 'exclude-paths': excludePaths, + 'version-resolver': versionResolver, + ...config + } = structuredClone(originalConfig) + const deprecatedCategoryConfig: DeprecatedCategoryConfig = { + 'exclude-labels': excludeLabels, + 'include-labels': includeLabels, + 'include-paths': includePaths, + 'exclude-paths': excludePaths, + 'version-resolver': versionResolver, + } + + applyOverrides(config, input) + + const { commitish, latest, prerelease } = getParsedDefaults(config) + const replacers = getTransformedReplacers(config) + const categories = getTransformedCategories(config, deprecatedCategoryConfig) + + // Build parsed config object - alters original type + const parsedConfig = { + ...config, + commitish, + latest, + prerelease, + replacers, + categories, + } + + validateParsedConfig(parsedConfig) + + return parsedConfig +} + +const applyOverrides = (config: MutableConfig, input: CommonConfig) => { + applyStringOverride(config, input, 'commitish') + applyStringOverride(config, input, 'header') + applyStringOverride(config, input, 'footer') + + applyStringOverride(config, input, 'prerelease-identifier') + + applyBooleanOverride(config, input, 'prerelease') + applyBooleanOverride(config, input, 'include-pre-releases') + applyBooleanOverride(config, input, 'latest') + applyStringOverride(config, input, 'filter-by-range') + + applyReleaseModeOverrides(config, input) +} + +const applyReleaseModeOverrides = ( + config: MutableConfig, + input: CommonConfig, +) => { + if (config.latest && config.prerelease) { + core.warning( + "'prerelease' and 'latest' cannot be both true. Switch 'latest' to false - release will be a pre-release.", + ) + config.latest = false + } + + const hasInputPrerelease = typeof input.prerelease === 'boolean' + const hasInputPrereleaseIdentifier = !!input['prerelease-identifier'] + + if ( + config['prerelease-identifier'] && + !config.prerelease && + (!hasInputPrerelease || hasInputPrereleaseIdentifier) + ) { + core.warning( + `You specified a 'prerelease-identifier' (${config['prerelease-identifier']}), but 'prerelease' is set to false. Switching to true.`, + ) + config.prerelease = true + } +} + +const applyBooleanOverride = ( + config: MutableConfig, + input: CommonConfig, + key: SharedBooleanKey, +) => { + const inputValue = input[key] + if (typeof inputValue !== 'boolean') { + return + } + + const configValue = config[key] + if (typeof configValue === 'boolean' && configValue !== inputValue) { + core.info( + `Input's ${key} "${inputValue}" overrides config's ${key} "${configValue}"`, + ) + } + + config[key] = inputValue +} + +const applyStringOverride = ( + config: MutableConfig, + input: CommonConfig, + key: SharedStringKey, +) => { + const inputValue = input[key] + if (!inputValue) { + return + } + + const configValue = config[key] + if (configValue && configValue !== inputValue) { + core.info( + `Input's ${key} "${inputValue}" overrides config's ${key} "${configValue}"`, + ) + } + + config[key] = inputValue +} + +const getParsedDefaults = (config: MutableConfig) => ({ + commitish: config.commitish || context.ref || (context.payload.ref as string), + latest: typeof config.latest !== 'boolean' ? true : config.latest, + prerelease: + typeof config.prerelease !== 'boolean' ? false : config.prerelease, +}) + +const getTransformedReplacers = (config: MutableConfig) => + config.replacers + .map((r) => { + try { + return { ...r, search: stringToRegex(r.search) } + } catch { + core.warning(`Bad replacer regex: '${r.search}'`) + return false + } + }) + .filter((r) => !!r) + +const getTransformedCategories = ( + config: Pick, + deprecatedCategoryConfig: DeprecatedCategoryConfig, +) => parseCategories(config, deprecatedCategoryConfig) + +const validateParsedConfig = (parsedConfig: ParsedConfig) => { + if (!parsedConfig.commitish) { + throw new Error( + "'commitish' is required. Please set 'commitish' to a valid value. (defaults to the current ref, but it seems to be undefined in this context)", + ) + } + const changelogCategoriesMissingTitle = parsedConfig.categories.filter( + (category) => category.type === 'changelog' && !category.title, + ) + if (changelogCategoriesMissingTitle.length > 0) { + throw new Error( + "Every 'type: \"changelog\"' category must define a non-empty 'title'.", + ) + } + const uncategorizedChangelogCategories = parsedConfig.categories.filter( + (category) => category.type === 'changelog' && category.when.length === 0, + ) + if (uncategorizedChangelogCategories.length > 1) { + throw new Error( + "Multiple 'type: \"changelog\"' categories detected with no 'when' condition. Only one such category is supported for uncategorized changes.", + ) + } + if ( + parsedConfig['filter-by-range'] && + !validRange(parsedConfig['filter-by-range']) + ) { + throw new Error( + `'filter-by-range' value "${parsedConfig['filter-by-range']}" could not be parsed as a valid semver range.`, + ) + } +} diff --git a/src/actions/drafter/config/parse-categories.ts b/src/actions/drafter/config/parse-categories.ts new file mode 100644 index 0000000000..d5aa1459f2 --- /dev/null +++ b/src/actions/drafter/config/parse-categories.ts @@ -0,0 +1,385 @@ +import * as core from '@actions/core' +import { + type CategoryConfig, + type ChangeConditionConfig, + type Config, + categorySchemaDefaults, + changeConditionSchemaDefaults, + configSchemaDefaults, +} from './schemas/config.schema.ts' + +type RawCategory = CategoryConfig + +const categoryMigrationDocumentationUrl = + 'https://github.com/release-drafter/release-drafter/pull/1558' + +const withMigrationDocumentationLink = (message: string) => + `${message} Migration documentation: ${categoryMigrationDocumentationUrl}` + +const normalizeConventional = ( + conventional: ChangeConditionConfig['conventional'], +) => { + if (!conventional) { + return undefined + } + + if (conventional === true) { + return { types: [], scopes: [], breaking: undefined } + } + + if (Object.keys(conventional).length === 0) { + core.warning( + "Use 'conventional: true' instead of 'conventional: {}' to match any conventional title.", + ) + } + + return { + types: [ + ...(conventional.types || []), + ...(conventional.type ? [conventional.type] : []), + ], + scopes: [ + ...(conventional.scopes || []), + ...(conventional.scope ? [conventional.scope] : []), + ], + breaking: conventional.breaking, + } +} + +/** + * Parses all categories from the config, normalizing conditions and + * handling backward compatibility with deprecated fields. + * + * This function: + * - Normalizes a missing `type` to `changelog` to match schema defaults + * - Normalizes the `when` field to always be an array of conditions + * - Applies deprecated category-level `label`/`labels` shorthands to every + * normalized `when` condition + * - Warns when deprecated compatibility fields are used + * - Preserves all other category fields as-is + * + * Accepts both fully-typed and partial category objects for flexibility. + * + * @param categories - Categories from the raw config + * @returns Array of fully parsed categories with normalized conditions + */ +export function parseCategories( + categories: { categories: RawCategory[] }, + deprecatedConfig: Pick< + Config, + | 'exclude-labels' + | 'include-labels' + | 'include-paths' + | 'exclude-paths' + | 'version-resolver' + >, +) { + const _categories = structuredClone(categories.categories) + + const parsedCategories = _categories.map((cat) => { + const { + // Deprecated fields + labels, + label, + + when: _when, + + // Potentially removed, depends on type + 'collapse-after': rawCollapseAfter, + 'semver-increment': rawSemverIncrement, + exclusive: rawExclusive, + title, + + // Rest + ..._cat + } = cat + + const collapseAfter = + rawCollapseAfter ?? categorySchemaDefaults['collapse-after'] + const semverIncrement = + rawSemverIncrement ?? categorySchemaDefaults['semver-increment'] + const exclusive = rawExclusive ?? categorySchemaDefaults.exclusive + + const deprecatedLabels = [...(labels || []), ...(label ? [label] : [])] + + if (deprecatedLabels.length > 0) { + core.warning( + withMigrationDocumentationLink( + `Use of deprecated 'categories[*].label' or 'categories[*].labels' field detected${title ? ` on category "${title}"` : ''}. Please migrate. This field will be removed in a future release. To migrate, move the labels into the category's 'when' condition.`, + ), + ) + } + + const whenConditions = + _when !== undefined + ? Array.isArray(_when) + ? _when.length > 0 || deprecatedLabels.length === 0 + ? _when + : [{}] + : [_when] + : deprecatedLabels.length > 0 + ? [{}] + : [] + + const parsedWhenConditions = whenConditions + .map((condition) => { + const { path, label, conventional, ..._cond } = condition + const normalizedConventional = normalizeConventional(conventional) + + // Deprecated category-level labels are shorthand for adding the same + // label predicate to every `when` branch + return { + ..._cond, + 'labels-mode': + condition['labels-mode'] ?? + changeConditionSchemaDefaults['labels-mode'], + 'paths-mode': + condition['paths-mode'] ?? + changeConditionSchemaDefaults['paths-mode'], + paths: [...(condition.paths || []), ...(path ? [path] : [])], + labels: [ + ...deprecatedLabels, + ...(condition.labels || []), + ...(label ? [label] : []), + ], + ...(normalizedConventional + ? { conventional: normalizedConventional } + : {}), + } + }) + // Filter-out empty conditions + .filter( + (condition) => + condition.paths.length > 0 || + condition.labels.length > 0 || + !!condition.conventional, + ) + + const categoryType = _cat.type ?? categorySchemaDefaults.type + + switch (categoryType) { + case 'changelog': + return { + type: 'changelog' as const, + when: parsedWhenConditions, + 'collapse-after': collapseAfter, + 'semver-increment': semverIncrement, + exclusive, + title, + } + case 'version-resolver': + if (title) { + core.warning( + `Title "${title}" ignored for category of type "${categoryType}"`, + ) + } + if (collapseAfter !== -1) { + core.warning( + `"collapse-after" "${collapseAfter}" ignored for category of type "${categoryType}"`, + ) + } + return { + type: 'version-resolver' as const, + when: parsedWhenConditions, + 'semver-increment': semverIncrement, + exclusive, + } + case 'pre-exclude': + case 'pre-include': + if (title) { + core.warning( + `Title "${title}" ignored for category of type "${categoryType}"`, + ) + } + if (collapseAfter !== -1) { + core.warning( + `"collapse-after" "${collapseAfter}" ignored for category of type "${categoryType}"`, + ) + } + if (exclusive) { + throw new Error( + `"exclusive" can only be set on categories of type "changelog" or "version-resolver"; it cannot be used on category of type "${categoryType}".`, + ) + } + if (semverIncrement !== 'patch') { + core.warning( + `"semver-increment" "${semverIncrement}" ignored for category of type "${categoryType}"`, + ) + } + return { + type: categoryType, + when: parsedWhenConditions, + } + default: + throw new Error(`Unsupported category type: ${categoryType}`) + } + }) + + // Handle top_level deprecated configs + if ( + (deprecatedConfig['exclude-labels'] && + deprecatedConfig['exclude-labels'].length > 0) || + (deprecatedConfig['exclude-paths'] && + deprecatedConfig['exclude-paths'].length > 0) + ) { + core.warning( + withMigrationDocumentationLink( + `Use of deprecated 'exclude-labels' or 'exclude-paths' field detected. Please migrate. This field will be removed in a future release. To migrate, add the correspoding labels or paths to a 'type: "pre-exclude"' category.`, + ), + ) + } + if ( + (deprecatedConfig['exclude-labels'] && + deprecatedConfig['exclude-labels'].length > 0) || + (deprecatedConfig['exclude-paths'] && + deprecatedConfig['exclude-paths'].length > 0) + ) { + if ( + parsedCategories.findIndex((cat) => cat.type === 'pre-exclude') !== -1 + ) { + throw new Error( + "A 'pre-exclude' category already exists. Cannot migrate deprecated exclude-labels field. Please either remove the deprecated field or remove the existing 'pre-exclude' category to resolve this conflict.", + ) + } + parsedCategories.push({ + type: 'pre-exclude', + when: [ + { + labels: deprecatedConfig['exclude-labels'] || [], + 'labels-mode': 'any', + paths: deprecatedConfig['exclude-paths'] || [], + 'paths-mode': 'any', + }, + ], + }) + } + if ( + (deprecatedConfig['include-labels'] && + deprecatedConfig['include-labels'].length > 0) || + (deprecatedConfig['include-paths'] && + deprecatedConfig['include-paths'].length > 0) + ) { + core.warning( + withMigrationDocumentationLink( + `Use of deprecated 'include-labels' or 'include-paths' field detected. Please migrate. This field will be removed in a future release. To migrate, add the correspoding labels or paths to a 'type: "pre-include"' category.`, + ), + ) + if ( + parsedCategories.findIndex((cat) => cat.type === 'pre-include') !== -1 + ) { + throw new Error( + "A 'pre-include' category already exists. Cannot migrate deprecated include-labels or include-paths fields. Please either remove the deprecated fields or remove the existing 'pre-include' category to resolve this conflict.", + ) + } + parsedCategories.push({ + type: 'pre-include', + when: [ + { + labels: deprecatedConfig['include-labels'] || [], + 'labels-mode': 'any', + paths: deprecatedConfig['include-paths'] || [], + 'paths-mode': 'any', + }, + ], + }) + } + if ( + deprecatedConfig['version-resolver'].default !== + configSchemaDefaults['version-resolver'].default + ) { + core.warning( + withMigrationDocumentationLink( + `Use of deprecated 'version-resolver.default' field detected. Please migrate. This field will be removed in a future release. To migrate, either add 'semver-increment: "${deprecatedConfig['version-resolver'].default}"' to 'type: changelog' category with no 'when' condition (uncategorized changes), or move the default resolver to a new category with type 'version-resolver' and 'semver-increment' set to "${deprecatedConfig['version-resolver'].default}" - also without 'when' conditions.`, + ), + ) + if ( + parsedCategories.findIndex( + (cat) => cat.type === 'version-resolver' && cat.when.length === 0, + ) !== -1 + ) { + throw new Error( + "A 'version-resolver' category with no 'when' condition already exists. Cannot migrate deprecated 'version-resolver.default' field. Please either remove the deprecated field or remove the existing 'version-resolver' category to resolve this conflict.", + ) + } + parsedCategories.push({ + type: 'version-resolver', + 'semver-increment': deprecatedConfig['version-resolver'].default, + when: [], + exclusive: false, + }) + } + if ( + deprecatedConfig['version-resolver'].major.labels !== + configSchemaDefaults['version-resolver'].major.labels && + deprecatedConfig['version-resolver'].major.labels.length > 0 + ) { + core.warning( + withMigrationDocumentationLink( + `Use of deprecated 'version-resolver.major.labels' field detected. Please migrate. This field will be removed in a future release. To migrate, either add 'semver-increment: "major"' to a pre-existing 'type: changelog' category, or move the labels from 'version-resolver.major.labels' to a new category with type 'version-resolver' and 'semver-increment' set to 'major'.`, + ), + ) + parsedCategories.push({ + type: 'version-resolver', + 'semver-increment': 'major', + when: [ + { + labels: deprecatedConfig['version-resolver'].major.labels || [], + 'labels-mode': 'any', + paths: [], + 'paths-mode': 'any', + }, + ], + exclusive: false, + }) + } + if ( + deprecatedConfig['version-resolver'].minor.labels !== + configSchemaDefaults['version-resolver'].minor.labels && + deprecatedConfig['version-resolver'].minor.labels.length > 0 + ) { + core.warning( + withMigrationDocumentationLink( + `Use of deprecated 'version-resolver.minor.labels' field detected. Please migrate. This field will be removed in a future release. To migrate, either add 'semver-increment: "minor"' to a pre-existing 'type: changelog' category, or move the labels from 'version-resolver.minor.labels' to a new category with type 'version-resolver' and 'semver-increment' set to 'minor'.`, + ), + ) + parsedCategories.push({ + type: 'version-resolver', + 'semver-increment': 'minor', + when: [ + { + labels: deprecatedConfig['version-resolver'].minor.labels || [], + 'labels-mode': 'any', + paths: [], + 'paths-mode': 'any', + }, + ], + exclusive: false, + }) + } + if ( + deprecatedConfig['version-resolver'].patch.labels !== + configSchemaDefaults['version-resolver'].patch.labels && + deprecatedConfig['version-resolver'].patch.labels.length > 0 + ) { + core.warning( + withMigrationDocumentationLink( + `Use of deprecated 'version-resolver.patch.labels' field detected. Please migrate. This field will be removed in a future release. To migrate, either add 'semver-increment: "patch"' to a pre-existing 'type: changelog' category, or move the labels from 'version-resolver.patch.labels' to a new category with type 'version-resolver' and 'semver-increment' set to 'patch'.`, + ), + ) + parsedCategories.push({ + type: 'version-resolver', + 'semver-increment': 'patch', + when: [ + { + labels: deprecatedConfig['version-resolver'].patch.labels || [], + 'labels-mode': 'any', + paths: [], + 'paths-mode': 'any', + }, + ], + exclusive: false, + }) + } + + return parsedCategories +} diff --git a/src/actions/drafter/config/schemas/action-input.schema.ts b/src/actions/drafter/config/schemas/action-input.schema.ts new file mode 100644 index 0000000000..b63324a043 --- /dev/null +++ b/src/actions/drafter/config/schemas/action-input.schema.ts @@ -0,0 +1,52 @@ +import type * as z from 'zod' +import { object, string, stringbool } from 'zod' +import { sharedInputSchema } from '#src/common/index.ts' +import { commonConfigSchema } from './common-config.schema.ts' + +export const exclusiveInputSchema = object({ + /** + * If your workflow requires multiple release-drafter configs it be helpful to override the config-name. + * The config should still be located inside `.github` as that's where we are looking for config files. + * @default 'release-drafter.yml' + */ + 'config-name': string().optional().default('release-drafter.yml'), + /** + * The name that will be used in the GitHub release that's created or updated. + * This will override any `name-template` specified in your `release-drafter.yml` if defined. + */ + name: string().optional(), + /** + * The tag name to be associated with the GitHub release that's created or updated. + * This will override any `tag-template` specified in your `release-drafter.yml` if defined. + */ + tag: string().optional(), + /** + * The version to be associated with the GitHub release that's created or updated. + * This will override any version calculated by the release-drafter. + */ + version: string().optional(), + /** + * A boolean indicating whether the release being created or updated should be immediately published. + */ + publish: stringbool().optional().default(false), +}).and(sharedInputSchema) + +export const actionInputSchema = exclusiveInputSchema.and(commonConfigSchema) + +/** + * Full action inputs + * + * For the action inputs exclusive to the action input, see `ExclusiveInput` + * + * For the action inputs that override configurations from the config-file, see `CommonConfig` + */ +export type ActionInput = z.infer + +/** + * Inputs exclusive to the action input + * + * For the full action inputs, see `ActionInput` + * + * For the action inputs that override configurations from the config-file, see `CommonConfig` + */ +export type ExclusiveInput = z.infer diff --git a/src/actions/drafter/config/schemas/common-config.schema.ts b/src/actions/drafter/config/schemas/common-config.schema.ts new file mode 100644 index 0000000000..9b31bae65d --- /dev/null +++ b/src/actions/drafter/config/schemas/common-config.schema.ts @@ -0,0 +1,52 @@ +import type * as z from 'zod' +import { boolean, object, string, stringbool } from 'zod' + +/** + * Configuration parameters that can be specified in both + * the config file or the action input. + * + * Default values cannot be defined here, + * as action inputs may override config file values. + * + * @see merge-input-and-config.ts for how the merging of config and input is handled, including default values. + */ +export const commonConfigSchema = object({ + /** + * A boolean indicating whether the release being created or updated should be marked as latest. + */ + latest: stringbool().or(boolean()).optional(), + /** + * Whether to draft a prerelease, with changes since another prerelease (if applicable). Default `false`. + */ + prerelease: stringbool().or(boolean()).optional(), + /** + * A string indicating an identifier (alpha, beta, rc, etc), to increment the prerelease version. This automatically enables `prerelease` when both values come from the same config location; explicit action inputs still take precedence. Default `''`. + */ + 'prerelease-identifier': string().optional(), + /** + * When looking for the last published release to scan changes up-to, include pre-releases. Has no effect if using `prerelease: true` (already enabled). Default `false`. + */ + 'include-pre-releases': stringbool().or(boolean()).optional(), + /** + * The release target, i.e. branch, commit SHA, or fully qualified tag or pull request ref it should point to. Tag and pull request refs are resolved to commit SHAs. Defaults to the branch that release-drafter runs for, e.g. `master` when configured to run on pushes to `master`. + */ + commitish: string().optional(), + /** + * A string that would be added before the template body. + */ + header: string().optional(), + /** + * A string that would be added after the template body. + */ + footer: string().optional(), + /** + * Filter releases that satisfies this semver range. Evaluates the tag name againts node's semver.satisfies(). + */ + 'filter-by-range': string().optional(), +}) + +/** + * Configuration parameters that can be specified in both + * the config file or the action input. + */ +export type CommonConfig = z.infer diff --git a/src/actions/drafter/config/schemas/config.schema.ts b/src/actions/drafter/config/schemas/config.schema.ts new file mode 100644 index 0000000000..a9bd55fb50 --- /dev/null +++ b/src/actions/drafter/config/schemas/config.schema.ts @@ -0,0 +1,434 @@ +import type * as z from 'zod' +import { + array, + boolean, + literal, + number, + object, + string, + union, + ZodDefault, + enum as zenum, +} from 'zod' +import { commonConfigSchema } from './common-config.schema.ts' + +/** + * A single set of predicates that are combined with AND logic. + * All specified predicates must be satisfied for a change to match. + */ +const changeConditionSchema = object({ + /** + * Conventional commit predicate: matches a change whose title or message + * follows the conventional commit shape, e.g. `feat(api)!: add endpoint`. + */ + conventional: union([ + literal(true), + object({ + /** Shorthand for one `types` entry. */ + type: string().min(1).optional(), + /** Conventional commit types to match, e.g. `feat` or `fix`. */ + types: array(string().min(1)).optional().default([]), + /** Shorthand for one `scopes` entry. */ + scope: string().min(1).optional(), + /** Conventional commit scopes to match, e.g. `api` or `ui`. */ + scopes: array(string().min(1)).optional().default([]), + /** Match titles with (`true`) or without (`false`) a breaking `!`. */ + breaking: boolean().optional(), + }), + ]).optional(), + /** + * Label predicate: matches a change that carries this label. + * + * Shorthand for adding a single value to `labels`. + * If `label` and `labels` are both specified, they are combined. + * + * Use `labels-mode` to configure how this label is compared to change labels. + */ + label: string().min(1).optional(), + /** + * Labels predicate: matches a change that carries these labels. + * + * `labels-mode` defaults to `any`, so the condition matches when the change + * shares at least one configured label unless another mode is set. + * + * Use `labels-mode` to configure how these labels are compared to change labels. + */ + labels: array(string().min(1)).optional().default([]), + /** + * Matching mode for the `labels` predicate. + * + * Has no effect unless `label` or `labels` is configured in the same condition. + * + * The comparison is set-based (label order is ignored). + * + * - `any`: Change and configured labels overlap (current behavior). + * - `all`: Change contains every configured label. Change can have more labels. + * - `only`: Every change label is included in configured labels. Configured labels can specify more. + * - `exactly`: Change labels and configured labels are the same set. + */ + 'labels-mode': zenum(['any', 'all', 'only', 'exactly']) + .optional() + .default('any'), + /** + * Path predicate: matches a change that touched this path pattern. Supports glob patterns. + * + * Same as specifying a single `paths` value. + * If `path` and `paths` are both specified, they are combined. + * + * Use `paths-mode` to configure how this path is matched against the pull + * request's changed files. + */ + path: string().min(1).optional(), + /** + * Paths predicate: matches a change that touched any of these path patterns. + * Values support glob patterns. + * + * If `path` and `paths` are both specified, they are combined before + * `paths-mode` is applied. + * + * Use `paths-mode` to configure how these path patterns are compared to the + * pull request's changed files. + */ + paths: array(string().min(1)).optional().default([]), + /** + * Matching mode for the `paths` predicate. + * + * Has no effect unless `path` or `paths` is configured in the same condition. + * + * The comparison is set-based (path order is ignored). + * + * - `any`: At least one changed file matched a configured path pattern. + * - `all`: Every configured path pattern matched at least one changed file. + * - `only`: Every changed file matched a configured path pattern. + * - `exactly`: Every changed file matched a configured path pattern and every + * configured path pattern matched at least one changed file. + */ + 'paths-mode': zenum(['any', 'all', 'only', 'exactly']) + .optional() + .default('any'), +}) + +export const changeConditionSchemaDefaults = changeConditionSchema.parse({}) +export type ChangeConditionConfig = z.input + +const categorySchema = object({ + /** + * Expanded in $TITLE in the category-template. + * + * Required when `type` is `changelog` (default). + * This is enforced during merged-config validation rather than by this schema alone. + * + * May be omitted for non-changelog categories because + * they are not rendered in the changelog output. + */ + title: string().min(1).optional(), + + /** + * The type of the category. + * + * - `changelog`: Included in the generated changelog. + * - `pre-include`: Keep only matching changes for later changelog categorization. + * - `pre-exclude`: Exclude matching changes for later changelog categorization. Is run against changes that were included in category type `pre-include` if specified. + * - `version-resolver`: Used solely to determine `$RESOLVED_VERSION` from the changes this category matches, without rendering a changelog section. Use `type: 'changelog'` (default) and `categories[*].semver-increment` instead if you mean this category to also be included in the changelog. + * + * `pre-include` always runs before `pre-exclude` in the pipeline. + * Omitted values default to `changelog`. + * + * @default "changelog" + */ + type: zenum(['changelog', 'pre-include', 'pre-exclude', 'version-resolver']) + .optional() + .default('changelog'), + + /** + * Whether changes included in this category should be excluded from other categories. + * + * Default behavior allows changes to appear in multiple categories if they match multiple category criteria. + * + * Only applicable to categories of `type: changelog` or `type: version-resolver`. + * This only controls inclusion for a single category type at a time, so a change can still match + * one exclusive changelog category and one exclusive version-resolver category. + * + * @default false + */ + exclusive: boolean().optional().default(false), + + /** + * Collapses the category's change list into a `
`/`` block + * when the number of changes is greater than this value. + * + * Only applicable to categories of `type: changelog`. + * + * Set to `0` to always collapse. Set to `-1` to disable collapsing. + * + * @default -1 + */ + 'collapse-after': number().int().min(-1).optional().default(-1), + + /** + * Which version increment this category contributes to `$RESOLVED_VERSION`. + * + * For `type: changelog` categories, this applies to changes that end up assigned + * to the category after changelog matching and `exclusive` handling. + * For `type: version-resolver` categories, this applies to changes the category + * matches directly, with a category that omits `when` acting as the fallback + * when no other `type: version-resolver` category matches. + * + * If multiple categories contribute, the most severe increment wins. + * For example, if one contributing category has `semver-increment: 'minor'` + * and another has `semver-increment: 'patch'`, the resulting increment will + * be `minor`. + * + * Applicable to categories of `type: changelog` and `type: version-resolver`. + * Ignored for `type: pre-include` and `type: pre-exclude`. + * + * @default "patch" + */ + 'semver-increment': zenum(['major', 'minor', 'patch']) + .optional() + .default('patch'), + + /** + * Compatibility shorthand for adding label matching to this category. + * + * Equivalent to adding the same `labels` predicate to every `when` condition. + * + * @deprecated Use `when.labels` instead. + */ + labels: array(string().min(1)).optional().default([]), + /** + * Compatibility shorthand for adding a single label match to this category. + * + * Equivalent to adding the same `label` predicate to every `when` condition. + * + * @deprecated Use `when.label` instead. + */ + label: string().min(1).optional(), + + /** + * Conditions that determine whether a change belongs to this category. + * + * Can be specified as: + * - A **single condition** (object): the change must satisfy all predicates in that condition. + * - An **array of conditions**: the change must satisfy all predicates of **at least one** + * condition (OR logic across conditions, AND logic within each condition). + * + * An empty array (default) matches all changes. + * + * @example + * # Shorthand: single condition (must have label "bug" AND touch "src/") + * when: + * labels: [bug] + * paths: [src/**] + * + * @example + * # Array: (label "bug" AND path "src/") OR (label "enhancement") + * when: + * - labels: [bug] + * paths: [src/**] + * - labels: [enhancement] + */ + when: changeConditionSchema + .or(array(changeConditionSchema)) + .optional() + .default([]), +}) + +export const categorySchemaDefaults = categorySchema.parse({}) +export type CategoryConfig = z.input + +export const exclusiveConfigSchema = object({ + /** + * The template to use for each merged change. + */ + 'change-template': string() + .optional() + .default('* $TITLE (#$NUMBER) $AUTHORS'), + /** + * The template to use for each author in `$AUTHORS`. + */ + 'change-author-template': string().optional().default('$AUTHOR_MENTION'), + /** + * The separator to use between authors in `$AUTHORS`. + */ + 'change-authors-separator': string().optional().default(', '), + /** + * An optional separator to use before the final author in `$AUTHORS`. + */ + 'change-authors-final-separator': string().optional(), + /** + * Characters to escape in `$TITLE` when inserting into `change-template` so that they are not interpreted as Markdown format characters. + */ + 'change-title-escapes': string().optional(), + /** + * The template to use for when there’s no changes. + */ + 'no-changes-template': string().optional().default('* No changes'), + /** + * The template to use when calculating the next version number for the release. Useful for projects that don't use semantic versioning. + */ + 'version-template': string() + .optional() + .default('$MAJOR.$MINOR.$PATCH$PRERELEASE'), + /** + * The template for the name of the draft release. + */ + 'name-template': string().optional(), + /** + * A known prefix used to filter release tags. For matching tags, this prefix is stripped before attempting to parse the version. + */ + 'tag-prefix': string().optional(), + /** + * The template for the tag of the draft release. + */ + 'tag-template': string().optional(), + /** + * Exclude changes using labels. + * + * @deprecated Use a `type: pre-exclude` category with `when.labels` instead. + */ + 'exclude-labels': array(string()).optional().default([]), + /** + * Include only the specified changes using labels. + * + * @deprecated Use a `type: pre-include` category with `when.labels` instead. + */ + 'include-labels': array(string()).optional().default([]), + /** + * Restrict changes included in the release notes to only the changes that modified any of the paths in this array. + * Supports files and directories. + * + * @deprecated Use a `type: pre-include` category with `when.paths` instead. + */ + 'include-paths': array(string()).optional().default([]), + /** + * Exclude changes from the release notes if they modified any of the paths in this array. + * Supports files and directories. If used with `include-paths`, the exclusion takes precedence. + * + * @deprecated Use a `type: pre-exclude` category with `when.paths` instead. + */ + 'exclude-paths': array(string()).optional().default([]), + /** + * Exclude specific usernames from the generated `$CONTRIBUTORS` variable. + */ + 'exclude-contributors': array(string()).optional().default([]), + /** + * The template to use for each new contributor in `$NEW_CONTRIBUTORS`. + */ + 'new-contributor-template': string() + .optional() + .default('* $AUTHOR_MENTION made their first contribution in #$NUMBER'), + /** + * The template to use for `$CONTRIBUTORS` when there's no contributors to list. + */ + 'no-contributors-template': string().optional().default('No contributors'), + /** + * Sort changelog by merged_at or title. + */ + 'sort-by': zenum(['merged_at', 'title']).optional().default('merged_at'), + /** + * Sort changelog in ascending or descending order. + */ + 'sort-direction': zenum(['ascending', 'descending']) + .optional() + .default('descending'), + /** + * Filter previous releases to consider only those with the target matching `commitish`. + */ + 'filter-by-commitish': boolean().optional().default(false), + 'pull-request-limit': number().int().positive().optional().default(5), + /** + * Size of the pagination window when walking the repo. Can avoid erratic 502s from Github. Default: `15` + */ + 'history-limit': number().int().positive().optional().default(15), + /** + * Search and replace content in the generated changelog body. + */ + replacers: array( + object({ + search: string().min(1), + replace: string().min(0), + }), + ) + .optional() + .default([]), + + /** + * Categorize changes + */ + categories: array(categorySchema).optional().default([]), + + /** + * Adjust the `$RESOLVED_VERSION` variable using labels. + * + * @deprecated Use a category with a `semver-increment` instead. Use category[ies] with `type: version-resolver` to separate version resolution from changelog inclusion concerns. + */ + 'version-resolver': object({ + major: object({ + labels: array(string().min(1)), + }) + .optional() + .default({ labels: [] }), + minor: object({ + labels: array(string().min(1)), + }) + .optional() + .default({ labels: [] }), + patch: object({ + labels: array(string().min(1)), + }) + .optional() + .default({ labels: [] }), + default: zenum(['major', 'minor', 'patch']).optional().default('patch'), + }) + .optional() + .default({ + major: { labels: [] }, + minor: { labels: [] }, + patch: { labels: [] }, + default: 'patch', + }), + + /** + * The template to use for each category. + */ + 'category-template': string().optional().default('## $TITLE'), + /** + * The template for the body of the draft release. + * Optional as it may be inherited via `_extends`. + */ + template: string().optional().default(''), +}).meta({ + title: 'JSON schema for Release Drafter yaml files', + id: 'https://github.com/release-drafter/release-drafter/blob/master/drafter/schema.json', +}) + +export const configSchema = exclusiveConfigSchema.and(commonConfigSchema) + +/** + * Configs exclusive to the config-file + * + * For the full config params, see `Config` + * + * For the config params that can be overwritten by the action's input, see `CommonConfig` + */ +export type ExclusiveConfig = z.output + +/** + * Full config params (from the config-file) + * + * For the config params exclusive to the config-file, see `ExclusiveConfig` + * + * For the config params that can be overwritten by the action's input, see `CommonConfig` + */ +export type Config = z.output + +export const configSchemaDefaults = Object.fromEntries( + Object.entries({ + ...exclusiveConfigSchema.shape, + ...commonConfigSchema.shape, + }).map(([key, value]) => { + if (value instanceof ZodDefault) return [key, value.def.defaultValue] + return [key, undefined] + }), +) as Config diff --git a/src/actions/drafter/config/schemas/index.ts b/src/actions/drafter/config/schemas/index.ts new file mode 100644 index 0000000000..11add5c41b --- /dev/null +++ b/src/actions/drafter/config/schemas/index.ts @@ -0,0 +1,18 @@ +/* node:coverage ignore file -- @preserve */ +export { + type ActionInput, + actionInputSchema, + type ExclusiveInput, + exclusiveInputSchema, +} from './action-input.schema.ts' +export { + type CommonConfig, + commonConfigSchema, +} from './common-config.schema.ts' +export { + type Config, + configSchema, + configSchemaDefaults, + type ExclusiveConfig, + exclusiveConfigSchema, +} from './config.schema.ts' diff --git a/src/actions/drafter/config/set-action-output.ts b/src/actions/drafter/config/set-action-output.ts new file mode 100644 index 0000000000..07fa7bb807 --- /dev/null +++ b/src/actions/drafter/config/set-action-output.ts @@ -0,0 +1,48 @@ +import * as core from '@actions/core' +import type { RestEndpointMethodTypes } from '@octokit/plugin-rest-endpoint-methods' +import type { buildReleasePayload } from '../lib/index.ts' + +export const setActionOutput = (params: { + upsertedRelease: + | RestEndpointMethodTypes['repos']['createRelease']['response'] + | RestEndpointMethodTypes['repos']['updateRelease']['response'] + | undefined + releasePayload: Awaited> +}) => { + const { releasePayload, upsertedRelease } = params + + core.info('Set action outputs...') + + const { + resolvedVersion, + majorVersion, + minorVersion, + patchVersion, + body, + name: releaseName, + tag: releaseTagName, + } = releasePayload + const outputName = upsertedRelease?.data.name ?? releaseName + const outputTagName = upsertedRelease?.data.tag_name ?? releaseTagName + + if (upsertedRelease) { + const { + data: { id: releaseId, html_url: htmlUrl, upload_url: uploadUrl }, + } = upsertedRelease + + if (releaseId && Number.isInteger(releaseId)) + core.setOutput('id', releaseId.toString()) + if (htmlUrl) core.setOutput('html_url', htmlUrl) + if (uploadUrl) core.setOutput('upload_url', uploadUrl) + } + + if (outputTagName) core.setOutput('tag_name', outputTagName) + if (outputName) core.setOutput('name', outputName) + if (resolvedVersion) core.setOutput('resolved_version', resolvedVersion) + if (majorVersion) core.setOutput('major_version', majorVersion) + if (minorVersion) core.setOutput('minor_version', minorVersion) + if (patchVersion) core.setOutput('patch_version', patchVersion) + core.setOutput('body', body) + + core.info('Outputs set!') +} diff --git a/src/actions/drafter/lib/build-release-payload/build-release-payload.ts b/src/actions/drafter/lib/build-release-payload/build-release-payload.ts new file mode 100644 index 0000000000..b4e23953a2 --- /dev/null +++ b/src/actions/drafter/lib/build-release-payload/build-release-payload.ts @@ -0,0 +1,159 @@ +import * as core from '@actions/core' +import { context } from '@actions/github' +import { parseCommitishForRelease } from '#src/common/parse-commitish.ts' +import type { ExclusiveInput, ParsedConfig } from '../../config/index.ts' +import type { findPreviousReleases } from '../find-previous-releases/index.ts' +import type { findPullRequests } from '../find-pull-requests/index.ts' +import { generateChangeLog } from './generate-changelog.ts' +import { + generateContributorsSentence, + generateNewContributorsList, +} from './generate-contributors-sentence.ts' +import { getVersionInfo } from './get-version-info.ts' +import { renderReleaseName } from './render-release-name.ts' +import { renderTagName } from './render-tag-name.ts' +import { renderTemplate } from './render-template/index.ts' +import { resolveVersionKeyIncrement } from './resolve-version-increment.ts' +import { sortPullRequests } from './sort-pull-requests.ts' +import lastNotFoundTemplate from './static/last-not-found.md?raw' + +/** + * Outputs the payload for creating or updating a release. + * + * Previously known as `generateReleaseInfo`. + */ +export const buildReleasePayload = async (params: { + commits: Awaited>['commits'] + config: Pick< + ParsedConfig, + | 'sort-by' + | 'sort-direction' + | 'header' + | 'footer' + | 'template' + | 'replacers' + | 'change-title-escapes' + | 'no-changes-template' + | 'categories' + | 'change-template' + | 'change-author-template' + | 'change-authors-separator' + | 'change-authors-final-separator' + | 'category-template' + | 'exclude-contributors' + | 'new-contributor-template' + | 'no-contributors-template' + | 'prerelease' + | 'version-template' + | 'tag-prefix' + | 'prerelease-identifier' + | 'tag-template' + | 'name-template' + | 'commitish' + | 'latest' + > + input: ExclusiveInput + lastRelease: Awaited>['lastRelease'] + newContributorLogins?: ReadonlySet + pullRequests: Awaited>['pullRequests'] +}) => { + const { + commits, + config, + input, + lastRelease, + newContributorLogins = new Set(), + pullRequests, + } = params + + core.info(`Building release payload and body...`) + + const sortedPullRequests = sortPullRequests({ + pullRequests, + config, + }) + + let body = + (config.header || '') + + config.template + + (!lastRelease + ? `\n---\n${renderTemplate({ template: lastNotFoundTemplate, object: { $OWNER: context.repo.owner, $REPOSITORY: context.repo.repo } })}\n---\n` + : '') + + (config.footer || '') + + body = renderTemplate({ + template: body, + object: { + $PREVIOUS_TAG: lastRelease ? lastRelease.tag_name : '', + $CHANGES: generateChangeLog({ + commits, + pullRequests: sortedPullRequests, + config, + }), + $CONTRIBUTORS: generateContributorsSentence({ + commits, + pullRequests: sortedPullRequests, + config, + }), + $NEW_CONTRIBUTORS: generateNewContributorsList({ + pullRequests: sortedPullRequests, + newContributorLogins, + config, + }), + $OWNER: context.repo.owner, + $REPOSITORY: context.repo.repo, + }, + replacers: config.replacers, + }) + + const versionKeyIncrement = resolveVersionKeyIncrement({ + pullRequests, + config, + }) + + const versionInfo = getVersionInfo({ + lastRelease, + config, + input, + versionKeyIncrement, + }) + + core.debug(`versionInfo: ${JSON.stringify(versionInfo, null, 2)}`) + + if (versionInfo) { + body = renderTemplate({ template: body, object: versionInfo }) + } + + const res = { + name: renderReleaseName({ inputName: input.name, config, versionInfo }), + tag: renderTagName({ inputTagName: input.tag, config, versionInfo }), + body, + targetCommitish: await parseCommitishForRelease(config.commitish), + prerelease: config.prerelease, + make_latest: config.latest, + draft: !input.publish, + resolvedVersion: versionInfo?.$RESOLVED_VERSION, + majorVersion: versionInfo?.$RESOLVED_VERSION_MAJOR, + minorVersion: versionInfo?.$RESOLVED_VERSION_MINOR, + patchVersion: versionInfo?.$RESOLVED_VERSION_PATCH, + prereleaseVersion: versionInfo?.$RESOLVED_VERSION_PRERELEASE, + } + + core.info(`Release payload built successfully`) + core.info(` name: ${res.name}`) + core.info(` tag: ${res.tag}`) + core.info(` body: ${res.body.length} characters long`) + core.info(` targetCommitish: ${res.targetCommitish}`) + core.info(` prerelease: ${res.prerelease}`) + core.info(` make_latest: ${res.make_latest}`) + core.info( + ` draft: ${res.draft}${!res.draft ? ' (will be published !)' : ''}`, + ) + core.info(` RESOLVED_VERSION: ${res.resolvedVersion}`) + core.info(` RESOLVED_VERSION_MAJOR: ${res.majorVersion}`) + core.info(` RESOLVED_VERSION_MINOR: ${res.minorVersion}`) + core.info(` RESOLVED_VERSION_PATCH: ${res.patchVersion}`) + core.info(` RESOLVED_VERSION_PRERELEASE: ${res.prereleaseVersion}`) + + return res +} diff --git a/src/actions/drafter/lib/build-release-payload/categorize-pull-requests.ts b/src/actions/drafter/lib/build-release-payload/categorize-pull-requests.ts new file mode 100644 index 0000000000..227f3e5162 --- /dev/null +++ b/src/actions/drafter/lib/build-release-payload/categorize-pull-requests.ts @@ -0,0 +1,63 @@ +import { + type ChangelogCategory, + filterPullRequestsByPreCategories, + getChangelogCategories, + matchesCategory, +} from '../../common/category-matching.ts' +import type { ParsedConfig } from '../../config/index.ts' +import type { findPullRequests } from '../find-pull-requests/index.ts' + +type Pr = Awaited>['pullRequests'][number] + +export const categorizePullRequests = (params: { + pullRequests: Pr[] + config: Pick +}): [Pr[], (ChangelogCategory & { pullRequests: Pr[] })[]] => { + const { pullRequests, config } = params + const changelogCategories = getChangelogCategories(config.categories) + const uncategorizedPullRequests: Pr[] = [] + const categorizedPullRequests: (ChangelogCategory & { + pullRequests: Pr[] + })[] = changelogCategories.map((category) => { + return { ...category, pullRequests: [] } + }) + + const uncategorizedCategoryIndex = changelogCategories.findIndex( + (category) => category.when.length === 0, + ) + const filteredPullRequests = filterPullRequestsByPreCategories( + pullRequests, + config.categories, + ) + + for (const pullRequest of filteredPullRequests) { + let matchedAnyCategory = false + + for (const category of categorizedPullRequests) { + if (category.when.length === 0) { + continue + } + + if (matchesCategory(category, pullRequest)) { + category.pullRequests.push(pullRequest) + matchedAnyCategory = true + + if (category.exclusive) { + break + } + } + } + + if (!matchedAnyCategory) { + if (uncategorizedCategoryIndex === -1) { + uncategorizedPullRequests.push(pullRequest) + } else { + categorizedPullRequests[uncategorizedCategoryIndex].pullRequests.push( + pullRequest, + ) + } + } + } + + return [uncategorizedPullRequests, categorizedPullRequests] +} diff --git a/src/actions/drafter/lib/build-release-payload/generate-changelog.ts b/src/actions/drafter/lib/build-release-payload/generate-changelog.ts new file mode 100644 index 0000000000..383317143e --- /dev/null +++ b/src/actions/drafter/lib/build-release-payload/generate-changelog.ts @@ -0,0 +1,98 @@ +import type { ParsedConfig } from '../../config/index.ts' +import type { findPullRequests } from '../find-pull-requests/index.ts' +import { categorizePullRequests } from './categorize-pull-requests.ts' +import { pullRequestToString } from './pull-request-to-string.ts' +import { renderTemplate } from './render-template/index.ts' + +export const generateChangeLog = (params: { + commits?: Awaited>['commits'] + pullRequests: Awaited>['pullRequests'] + config: Pick< + ParsedConfig, + | 'change-title-escapes' + | 'no-changes-template' + | 'categories' + | 'change-template' + | 'change-author-template' + | 'change-authors-separator' + | 'change-authors-final-separator' + | 'category-template' + > +}) => { + const { commits = [], pullRequests, config } = params + + const [uncategorizedPullRequests, categorizedPullRequests] = + categorizePullRequests({ pullRequests, config }) + + const categorizedPullRequestsCount = categorizedPullRequests.reduce( + (sum, category) => sum + category.pullRequests.length, + 0, + ) + + const totalPullRequestsInChangelog = + categorizedPullRequestsCount + uncategorizedPullRequests.length + + if (totalPullRequestsInChangelog === 0) { + return config['no-changes-template'] + } + + const changeLog: string[] = [] + + if (uncategorizedPullRequests.length > 0) { + changeLog.push( + pullRequestToString({ + commits, + pullRequests: uncategorizedPullRequests, + config, + }), + '\n\n', + ) + } + + for (const [index, category] of categorizedPullRequests.entries()) { + if (category.pullRequests.length === 0) { + continue + } + + // Add the category title to the changelog. + const categoryTitle = renderTemplate({ + template: config['category-template'], + object: { $TITLE: category.title }, + }) + if (categoryTitle) changeLog.push(categoryTitle, '\n\n') + + // Define the pull requests into a single string. + const pullRequestString = pullRequestToString({ + category: category.title, + commits, + pullRequests: category.pullRequests, + config, + }) + + // Determine the collapse status. + const shouldCollapse = + category['collapse-after'] !== -1 && + category.pullRequests.length > category['collapse-after'] + + // Add the pull requests to the changelog. + if (shouldCollapse) { + changeLog.push( + '
', + '\n', + `${category.pullRequests.length} change${ + category.pullRequests.length > 1 ? 's' : '' + }`, + '\n\n', + pullRequestString, + '\n', + '
', + ) + } else { + changeLog.push(pullRequestString) + } + + if (index + 1 !== categorizedPullRequests.length) changeLog.push('\n\n') + } + + return changeLog.join('').trim() +} diff --git a/src/actions/drafter/lib/build-release-payload/generate-contributors-sentence.ts b/src/actions/drafter/lib/build-release-payload/generate-contributors-sentence.ts new file mode 100644 index 0000000000..27187a1139 --- /dev/null +++ b/src/actions/drafter/lib/build-release-payload/generate-contributors-sentence.ts @@ -0,0 +1,226 @@ +import { context } from '@actions/github' +import { filterPullRequestsByPreCategories } from '../../common/category-matching.ts' +import type { ParsedConfig } from '../../config/index.ts' +import type { findPullRequests } from '../find-pull-requests/index.ts' +import { renderTemplate } from './render-template/index.ts' + +type PullRequest = Awaited< + ReturnType +>['pullRequests'][number] + +type Contributor = { login: string; botUrl?: string } | { name: string } + +const botSuffix = '[bot]' +const pullRequestKey = (pullRequest: PullRequest) => + `${pullRequest.baseRepository?.nameWithOwner}#${pullRequest.number}` +const normalizeLogin = (login: string, isBot = false) => + isBot && !login.endsWith(botSuffix) ? `${login}${botSuffix}` : login +const renderAuthorMention = (contributor: Contributor) => { + if ('name' in contributor) return contributor.name + const botUrl = contributor.login.endsWith(botSuffix) + ? (contributor.botUrl ?? + `${context.serverUrl.replace(/\/$/, '')}/apps/${contributor.login.slice(0, -botSuffix.length)}`) + : undefined + if (botUrl) { + return `[@${contributor.login}](${botUrl})` + } + return `@${contributor.login}` +} + +export const generateContributorsSentence = (params: { + commits: Awaited>['commits'] + pullRequests: Awaited>['pullRequests'] + config: Pick< + ParsedConfig, + 'categories' | 'exclude-contributors' | 'no-contributors-template' + > +}) => { + const { commits, pullRequests, config } = params + + const includedPullRequests = filterPullRequestsByPreCategories( + pullRequests, + config.categories, + ) + return generateAuthorsSentence({ + commits, + pullRequests: includedPullRequests, + excludeContributors: config['exclude-contributors'], + noAuthorsTemplate: config['no-contributors-template'], + }) +} + +export const generateAuthorsSentence = (params: { + commits: Awaited>['commits'] + pullRequests: Awaited>['pullRequests'] + excludeContributors?: string[] + noAuthorsTemplate?: string + authorTemplate?: string + authorsSeparator?: string + authorsFinalSeparator?: string +}) => { + const { commits, pullRequests } = params + const includedPullRequestKeys = new Set(pullRequests.map(pullRequestKey)) + const includedMergeCommitOids = new Set( + pullRequests.flatMap((pullRequest) => + 'mergeCommit' in pullRequest && pullRequest.mergeCommit?.oid + ? [pullRequest.mergeCommit.oid] + : [], + ), + ) + const contributors = new Map() + const pullRequestAuthorLogins = new Set() + + // Add from commits belonging to included pull requests + for (const commit of commits) { + if ( + !includedMergeCommitOids.has(commit.oid) && + !commit.associatedPullRequests?.nodes?.some( + (pullRequest) => + pullRequest && + includedPullRequestKeys.has(pullRequestKey(pullRequest)), + ) + ) { + continue + } + + for (const author of commit.authors?.nodes ?? + (commit.author ? [commit.author] : [])) { + if (author?.user) { + const login = normalizeLogin(author.user.login) + contributors.set(`login:${login}`, { login }) + } else if (author?.name) { + contributors.set(`name:${author.name}`, { name: author.name }) + } + } + } + + // Add from pull requests + for (const pullRequest of pullRequests) { + if (pullRequest.author) { + const isBot = pullRequest.author.__typename === 'Bot' + const login = normalizeLogin(pullRequest.author.login, isBot) + pullRequestAuthorLogins.add(login) + contributors.set(`login:${login}`, { + login, + botUrl: isBot ? pullRequest.author.url : undefined, + }) + } + } + + const sortedContributors = [...contributors.values()] + .filter( + (contributor) => + 'name' in contributor || + !(params.excludeContributors ?? []).some( + (excluded) => + excluded === contributor.login || + `${excluded}${botSuffix}` === contributor.login, + ), + ) + .sort((a, b) => { + const aIsPullRequestAuthor = + 'login' in a && pullRequestAuthorLogins.has(a.login) + const bIsPullRequestAuthor = + 'login' in b && pullRequestAuthorLogins.has(b.login) + if (aIsPullRequestAuthor !== bIsPullRequestAuthor) { + return aIsPullRequestAuthor ? -1 : 1 + } + + const aIsBot = + 'login' in a && (a.botUrl !== undefined || a.login.endsWith(botSuffix)) + const bIsBot = + 'login' in b && (b.botUrl !== undefined || b.login.endsWith(botSuffix)) + if (aIsBot !== bIsBot) return aIsBot ? 1 : -1 + + const aName = 'name' in a ? a.name : a.login + const bName = 'name' in b ? b.name : b.login + return aName.localeCompare(bName) + }) + if (sortedContributors.length === 0) { + return params.noAuthorsTemplate ?? '' + } + + if (params.authorTemplate !== undefined) { + const authorTemplate = params.authorTemplate + const authors = sortedContributors.map((contributor) => { + const author = + 'name' in contributor ? contributor.name : contributor.login + return renderTemplate({ + template: authorTemplate, + object: { + $AUTHOR: author, + $AUTHOR_MENTION: renderAuthorMention(contributor), + }, + }) + }) + const separator = params.authorsSeparator ?? ', ' + if (params.authorsFinalSeparator !== undefined && authors.length > 1) { + return `${authors.slice(0, -1).join(separator)}${params.authorsFinalSeparator}${authors.at(-1)}` + } + return authors.join(separator) + } + + const mentions = sortedContributors.map(renderAuthorMention) + if (mentions.length > 1) { + return `${mentions.slice(0, -1).join(', ')} and ${mentions.slice(-1)}` + } + return mentions[0] +} + +export const generateNewContributorsList = (params: { + pullRequests: Awaited>['pullRequests'] + newContributorLogins: ReadonlySet + config: Pick< + ParsedConfig, + 'categories' | 'exclude-contributors' | 'new-contributor-template' + > +}) => { + const { pullRequests, newContributorLogins, config } = params + const firstPullRequestByLogin = new Map() + const includedPullRequestKeys = new Set( + filterPullRequestsByPreCategories(pullRequests, config.categories).map( + pullRequestKey, + ), + ) + + for (const pullRequest of pullRequests) { + if ( + !pullRequest.author || + !newContributorLogins.has(pullRequest.author.login) || + config['exclude-contributors'].includes(pullRequest.author.login) + ) { + continue + } + + const previous = firstPullRequestByLogin.get(pullRequest.author.login) + if (!previous || (pullRequest.mergedAt ?? '') < (previous.mergedAt ?? '')) { + firstPullRequestByLogin.set(pullRequest.author.login, pullRequest) + } + } + + const entries = [...firstPullRequestByLogin.entries()] + .filter(([, pullRequest]) => + includedPullRequestKeys.has(pullRequestKey(pullRequest)), + ) + .sort( + ([, a], [, b]) => + (a.mergedAt ?? '').localeCompare(b.mergedAt ?? '') || + a.number - b.number, + ) + if (entries.length === 0) return '' + + return entries + .map(([login, pullRequest]) => + renderTemplate({ + template: config['new-contributor-template'], + object: { + $AUTHOR: login, + $AUTHOR_MENTION: `@${login}`, + $AUTHOR_URL: pullRequest.author?.url, + $NUMBER: pullRequest.number, + $URL: pullRequest.url, + }, + }), + ) + .join('\n') +} diff --git a/src/actions/drafter/lib/build-release-payload/get-version-info.ts b/src/actions/drafter/lib/build-release-payload/get-version-info.ts new file mode 100644 index 0000000000..808783519d --- /dev/null +++ b/src/actions/drafter/lib/build-release-payload/get-version-info.ts @@ -0,0 +1,135 @@ +import * as core from '@actions/core' +import type { ReleaseType } from 'semver' +import type { Config, ExclusiveInput } from '../../config/index.ts' +import type { findPreviousReleases } from '../find-previous-releases/index.ts' +import type { resolveVersionKeyIncrement } from './resolve-version-increment.ts' +import { VersionDescriptor } from './version-descriptor.ts' + +type Release = Exclude< + Awaited>['lastRelease'], + undefined +> + +export const getVersionInfo = (params: { + lastRelease: Pick | undefined + config: Pick< + Config, + 'version-template' | 'tag-prefix' | 'prerelease-identifier' + > + input: Pick + versionKeyIncrement: ReturnType +}) => { + const { + lastRelease, + config, + input, + versionKeyIncrement: _versionKeyIncrement, + } = params + + core.info(`Resolving version info based on:`) + core.info(` - last release: ${lastRelease?.tag_name || 'none'}`) + core.info( + ` - version input: ${input.version || input.tag || input.name || 'none'}`, + ) + core.info(` - version key increment: ${_versionKeyIncrement}`) + + let _localIncrement: ReleaseType | 'no_increment' = + structuredClone(_versionKeyIncrement) // local mutable copy + + core.info(`Coerce and parse versions from last release...`) + const versionFromLastRelease = new VersionDescriptor(lastRelease, { + tagPrefix: config['tag-prefix'], + preReleaseIdentifier: config['prerelease-identifier'], + }) + core.info( + `Parsed version from last release: ${versionFromLastRelease.version?.format() || 'none'}.`, + ) + + core.info(`Coerce and parse versions from input...`) + const versionFromInput = new VersionDescriptor( + input.version || input.tag || input.name, + { + tagPrefix: config['tag-prefix'], + preReleaseIdentifier: config['prerelease-identifier'], + }, + ) + core.info( + `Parsed version from input: ${versionFromInput.version?.format() || 'none'}.`, + ) + + let referenceVersion: VersionDescriptor + if (versionFromInput.version) { + // Use version input + _localIncrement = 'no_increment' // use that exact input version + referenceVersion = versionFromInput + } else if (versionFromLastRelease.version) { + // Use previous published release + referenceVersion = versionFromLastRelease + + // Handle prereleases + const incrementsToPrerelease = _localIncrement?.startsWith('pre') + const lastReleaseIsPrerelease = referenceVersion?.prerelease?.length + if (incrementsToPrerelease) { + if (lastReleaseIsPrerelease) { + // Set local increment to 'prerelease', so that we simply + // increment the prerelease number (e.g., 1.2.3-beta.6 -> 1.2.3-beta.7). + // When publishing prerelease releases, the first published prerelease is supposed to set + // the stage for the semver increment (e.g. 1.2.2 --(prepatch)--> 1.2.3-beta.0). + // Subsequent prerelease increments should only increment the prerelease number (e.g. 1.2.3-beta.0 --(prerelease)--> 1.2.3-beta.1). + // The following increments are considered invalid : + // - 1.2.3-beta.1 --(prepatch)--> ?????? + // - 1.2.3-beta.1 --(preminor)--> ?????? + // - 1.2.3-beta.1 --(premajor)--> ?????? + if (_localIncrement !== 'prerelease') { + core.info( + `versionKeyIncrement is set to "${_localIncrement}", but the last release is already a prerelease (${referenceVersion.version?.format() || 'none'}). The version will be incremented as a prerelease instead.`, + ) + _localIncrement = 'prerelease' + } + } + } + } else { + // No previous release and no input version + referenceVersion = new VersionDescriptor('0.0.0', { + preReleaseIdentifier: config['prerelease-identifier'], + tagPrefix: config['tag-prefix'], + }) + } + + return { + $NEXT_MAJOR_VERSION: referenceVersion + .incremented('major') + .rendered(config['version-template']), + $NEXT_MAJOR_VERSION_MAJOR: referenceVersion.incremented('major').major, + $NEXT_MAJOR_VERSION_MINOR: referenceVersion.incremented('major').minor, + $NEXT_MAJOR_VERSION_PATCH: referenceVersion.incremented('major').patch, + $NEXT_MINOR_VERSION: referenceVersion + .incremented('minor') + .rendered(config['version-template']), + $NEXT_MINOR_VERSION_MAJOR: referenceVersion.incremented('minor').major, + $NEXT_MINOR_VERSION_MINOR: referenceVersion.incremented('minor').minor, + $NEXT_MINOR_VERSION_PATCH: referenceVersion.incremented('minor').patch, + $NEXT_PATCH_VERSION: referenceVersion + .incremented('patch') + .rendered(config['version-template']), + $NEXT_PATCH_VERSION_MAJOR: referenceVersion.incremented('patch').major, + $NEXT_PATCH_VERSION_MINOR: referenceVersion.incremented('patch').minor, + $NEXT_PATCH_VERSION_PATCH: referenceVersion.incremented('patch').patch, + $NEXT_PRERELEASE_VERSION: referenceVersion + .incremented('prerelease') + .rendered(config['version-template']), + $NEXT_PRERELEASE_VERSION_PRERELEASE: + referenceVersion.incremented('prerelease').prerelease, + $RESOLVED_VERSION: referenceVersion + .incremented(_localIncrement) + .rendered(config['version-template']), + $RESOLVED_VERSION_MAJOR: + referenceVersion.incremented(_localIncrement).major, + $RESOLVED_VERSION_MINOR: + referenceVersion.incremented(_localIncrement).minor, + $RESOLVED_VERSION_PATCH: + referenceVersion.incremented(_localIncrement).patch, + $RESOLVED_VERSION_PRERELEASE: + referenceVersion.incremented(_localIncrement).prerelease, + } +} diff --git a/src/actions/drafter/lib/build-release-payload/index.ts b/src/actions/drafter/lib/build-release-payload/index.ts new file mode 100644 index 0000000000..db98e80ad1 --- /dev/null +++ b/src/actions/drafter/lib/build-release-payload/index.ts @@ -0,0 +1,2 @@ +/* node:coverage ignore file -- @preserve */ +export { buildReleasePayload } from './build-release-payload.ts' diff --git a/src/actions/drafter/lib/build-release-payload/pull-request-to-string.ts b/src/actions/drafter/lib/build-release-payload/pull-request-to-string.ts new file mode 100644 index 0000000000..135778d0f0 --- /dev/null +++ b/src/actions/drafter/lib/build-release-payload/pull-request-to-string.ts @@ -0,0 +1,82 @@ +import regexEscape from 'escape-string-regexp' +import type { Config } from '../../config/index.ts' +import type { findPullRequests } from '../find-pull-requests/index.ts' +import { generateAuthorsSentence } from './generate-contributors-sentence.ts' +import { renderTemplate } from './render-template/index.ts' + +type Pr = Awaited>['pullRequests'][number] + +export const pullRequestToString = (params: { + category?: string + commits: Awaited>['commits'] + pullRequests: Pr[] + config: Pick< + Config, + | 'change-template' + | 'change-title-escapes' + | 'change-author-template' + | 'change-authors-separator' + | 'change-authors-final-separator' + > +}) => + params.pullRequests + .map((pullRequest) => { + let pullAuthor = 'ghost' + if (pullRequest.author) { + pullAuthor = + pullRequest.author.__typename && + pullRequest.author.__typename === 'Bot' + ? `[${pullRequest.author.login}[bot]](${pullRequest.author.url})` + : pullRequest.author.login + } + const authorTemplate = params.config['change-author-template'] + + return renderTemplate({ + template: params.config['change-template'], + object: { + $CATEGORY: params.category ?? '', + $TITLE: escapeTitle({ + title: pullRequest.title, + escapes: params.config['change-title-escapes'], + }), + $NUMBER: pullRequest.number.toString(), + $AUTHORS: generateAuthorsSentence({ + commits: params.commits, + pullRequests: [pullRequest], + noAuthorsTemplate: renderTemplate({ + template: authorTemplate, + object: { + $AUTHOR: 'ghost', + $AUTHOR_MENTION: '@ghost', + }, + }), + authorTemplate, + authorsSeparator: params.config['change-authors-separator'], + authorsFinalSeparator: + params.config['change-authors-final-separator'], + }), + $AUTHOR: pullAuthor, + $AUTHOR_URL: pullRequest.author?.url ?? '', + $BODY: pullRequest.body, + $URL: pullRequest.url, + $BASE_REF_NAME: pullRequest.baseRefName, + $HEAD_REF_NAME: pullRequest.headRefName, + }, + }) + }) + .join('\n') + +const escapeTitle = (params: { + title: Pr['title'] + escapes: Config['change-title-escapes'] +}) => + // If config['change-title-escapes'] contains backticks, then they will be escaped along with content contained inside backticks + // If not, the entire backtick block is matched so that it will become a markdown code block without escaping any of its content + params.title.replace( + new RegExp(`[${regexEscape(params.escapes || '')}]|\`.*?\``, 'g'), + (match: string) => { + if (match.length > 1) return match + if (match === '@' || match === '#') return `${match}` + return `\\${match}` + }, + ) diff --git a/src/actions/drafter/lib/build-release-payload/render-release-name.ts b/src/actions/drafter/lib/build-release-payload/render-release-name.ts new file mode 100644 index 0000000000..93de7f7420 --- /dev/null +++ b/src/actions/drafter/lib/build-release-payload/render-release-name.ts @@ -0,0 +1,34 @@ +import * as core from '@actions/core' +import type { ParsedConfig } from '../../config/index.ts' +import type { getVersionInfo } from './get-version-info.ts' +import { renderTemplate } from './render-template/index.ts' + +/** + * Renders the release name, + * based on the input and config. + */ +export const renderReleaseName = (params: { + inputName: string | undefined + config: Pick + versionInfo: ReturnType +}) => { + let name = structuredClone(params.inputName) + const { config, versionInfo } = params + + if (name === undefined) { + name = versionInfo + ? renderTemplate({ + template: config['name-template'] || '', + object: versionInfo, + }) + : '' + } else if (versionInfo) { + name = renderTemplate({ + template: name, + object: versionInfo, + }) + } + + core.debug(`name: ${name}`) + return name +} diff --git a/src/actions/drafter/lib/build-release-payload/render-tag-name.ts b/src/actions/drafter/lib/build-release-payload/render-tag-name.ts new file mode 100644 index 0000000000..4dd8bab837 --- /dev/null +++ b/src/actions/drafter/lib/build-release-payload/render-tag-name.ts @@ -0,0 +1,34 @@ +import * as core from '@actions/core' +import type { ParsedConfig } from '../../config/index.ts' +import type { getVersionInfo } from './get-version-info.ts' +import { renderTemplate } from './render-template/index.ts' + +/** + * Renders the tag name for the release, + * based on the input and config. + */ +export const renderTagName = (params: { + inputTagName: string | undefined + config: Pick + versionInfo: ReturnType +}) => { + let tagName = structuredClone(params.inputTagName) + const { config, versionInfo } = params + + if (tagName === undefined) { + tagName = versionInfo + ? renderTemplate({ + template: config['tag-template'] || '', + object: versionInfo, + }) + : '' + } else if (versionInfo) { + tagName = renderTemplate({ + template: tagName, + object: versionInfo, + }) + } + + core.debug(`tag: ${tagName}`) + return tagName +} diff --git a/src/actions/drafter/lib/build-release-payload/render-template/index.ts b/src/actions/drafter/lib/build-release-payload/render-template/index.ts new file mode 100644 index 0000000000..222fcf152a --- /dev/null +++ b/src/actions/drafter/lib/build-release-payload/render-template/index.ts @@ -0,0 +1,5 @@ +export { + type NestedTemplate, + renderTemplate, + type Template, +} from './render-template.ts' diff --git a/src/actions/drafter/lib/build-release-payload/render-template/render-template.ts b/src/actions/drafter/lib/build-release-payload/render-template/render-template.ts new file mode 100644 index 0000000000..b1c3aa6de8 --- /dev/null +++ b/src/actions/drafter/lib/build-release-payload/render-template/render-template.ts @@ -0,0 +1,81 @@ +import type { ParsedConfig } from '#src/actions/drafter/config/index.ts' +import { parseReplaceString } from './util/index.ts' + +type TemplateReplacer = NonNullable[number] + +const getReplaceMatches = (args: unknown[]): string[] => { + const lastArg = args[args.length - 1] + const hasGroups = typeof lastArg === 'object' && lastArg !== null + const matchCount = args.length - (hasGroups ? 3 : 2) + + return args.slice(0, matchCount) as string[] +} + +const applyReplacer = (input: string, replacer: TemplateReplacer): string => { + const replacePattern = parseReplaceString(replacer.replace) + + return input.replace(replacer.search, (...args) => { + const matches = getReplaceMatches(args) + return replacePattern.buildReplaceString(matches) + }) +} + +export type Template = { + [key: `$${Uppercase}`]: + | string + | number + | null + | undefined + | NestedTemplate +} +export type NestedTemplate = { + template: string + [key: `$${Uppercase}`]: + | string + | number + | null + | undefined + | NestedTemplate +} + +/** + * replaces all uppercase dollar templates with their string representation from object + * if replacement is undefined in object the dollar template string is left untouched + */ +export const renderTemplate = (params: { + template: string + object: Template + replacers?: ParsedConfig['replacers'] +}) => { + const { template, object, replacers } = params + + let input = template.replace(/(\$[A-Z_]+)/g, (_, k: string): string => { + let result: string + + const isValidKey = (key: unknown): key is keyof typeof object => + (key as keyof typeof object) in object && + object[key as keyof typeof object] !== undefined && + object[key as keyof typeof object] !== null + + if (!isValidKey(k)) { + result = k + } else if (typeof object[k] === 'object') { + const nested = object[k] as NestedTemplate + result = renderTemplate({ + template: nested.template, + object: nested, + }) + } else { + result = `${object[k]}` + } + return result + }) + + if (replacers) { + for (const replacer of replacers) { + input = applyReplacer(input, replacer) + } + } + + return input +} diff --git a/src/actions/drafter/lib/build-release-payload/render-template/util/charCode.ts b/src/actions/drafter/lib/build-release-payload/render-template/util/charCode.ts new file mode 100644 index 0000000000..cc4651b414 --- /dev/null +++ b/src/actions/drafter/lib/build-release-payload/render-template/util/charCode.ts @@ -0,0 +1,29 @@ +// Minimal CharCode subset for replacePattern integration. +export enum CharCode { + Backslash = 92, + Tab = 9, + LineFeed = 10, + CarriageReturn = 13, + Space = 32, + Ampersand = 38, + DollarSign = 36, + Digit0 = 48, + Digit1 = 49, + Digit2 = 50, + Digit3 = 51, + Digit4 = 52, + Digit5 = 53, + Digit6 = 54, + Digit7 = 55, + Digit8 = 56, + Digit9 = 57, + A = 65, + E = 69, + L = 76, + U = 85, + a = 97, + l = 108, + n = 110, + t = 116, + u = 117, +} diff --git a/src/actions/drafter/lib/build-release-payload/render-template/util/index.ts b/src/actions/drafter/lib/build-release-payload/render-template/util/index.ts new file mode 100644 index 0000000000..464330a6d2 --- /dev/null +++ b/src/actions/drafter/lib/build-release-payload/render-template/util/index.ts @@ -0,0 +1 @@ +export { parseReplaceString } from './replacePattern.ts' diff --git a/src/actions/drafter/lib/build-release-payload/render-template/util/replacePattern.ts b/src/actions/drafter/lib/build-release-payload/render-template/util/replacePattern.ts new file mode 100644 index 0000000000..141c865e06 --- /dev/null +++ b/src/actions/drafter/lib/build-release-payload/render-template/util/replacePattern.ts @@ -0,0 +1,380 @@ +/* eslint-disable no-fallthrough */ +// Ported from VSCode commit 6b924c51528e663dda5091a1493229a361676aca. +// Source: src/vs/editor/contrib/find/browser/replacePattern.ts +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CharCode } from './charCode.ts' +import { buildReplaceStringWithCasePreserved } from './search.ts' + +enum ReplacePatternKind { + StaticValue = 0, + DynamicPieces = 1, +} + +/** + * Assigned when the replace pattern is entirely static. + */ +class StaticValueReplacePattern { + public readonly kind = ReplacePatternKind.StaticValue + constructor(public readonly staticValue: string) {} +} + +/** + * Assigned when the replace pattern has replacement patterns. + */ +class DynamicPiecesReplacePattern { + public readonly kind = ReplacePatternKind.DynamicPieces + constructor(public readonly pieces: ReplacePiece[]) {} +} + +export class ReplacePattern { + public static fromStaticValue(value: string): ReplacePattern { + return new ReplacePattern([ReplacePiece.staticValue(value)]) + } + + private readonly _state: + | StaticValueReplacePattern + | DynamicPiecesReplacePattern + + public get hasReplacementPatterns(): boolean { + return this._state.kind === ReplacePatternKind.DynamicPieces + } + + constructor(pieces: ReplacePiece[] | null) { + if (!pieces || pieces.length === 0) { + this._state = new StaticValueReplacePattern('') + } else if (pieces.length === 1 && pieces[0].staticValue !== null) { + this._state = new StaticValueReplacePattern(pieces[0].staticValue) + } else { + this._state = new DynamicPiecesReplacePattern(pieces) + } + } + + public buildReplaceString( + matches: string[] | null, + preserveCase?: boolean, + ): string { + if (this._state.kind === ReplacePatternKind.StaticValue) { + if (preserveCase) { + return buildReplaceStringWithCasePreserved( + matches, + this._state.staticValue, + ) + } else { + return this._state.staticValue + } + } + + let result = '' + for (let i = 0, len = this._state.pieces.length; i < len; i++) { + const piece = this._state.pieces[i] + if (piece.staticValue !== null) { + // static value ReplacePiece + result += piece.staticValue + continue + } + + // match index ReplacePiece + let match: string = ReplacePattern._substitute(piece.matchIndex, matches) + if (piece.caseOps !== null && piece.caseOps.length > 0) { + const repl: string[] = [] + const lenOps: number = piece.caseOps.length + let opIdx: number = 0 + for ( + let idx: number = 0, len: number = match.length; + idx < len; + idx++ + ) { + if (opIdx >= lenOps) { + repl.push(match.slice(idx)) + break + } + switch (piece.caseOps[opIdx]) { + case 'U': + repl.push(match[idx].toUpperCase()) + break + case 'u': + repl.push(match[idx].toUpperCase()) + opIdx++ + break + case 'L': + repl.push(match[idx].toLowerCase()) + break + case 'l': + repl.push(match[idx].toLowerCase()) + opIdx++ + break + case 'E': + repl.push(match.slice(idx)) + idx = len // force loop exit after this iteration + break + default: + repl.push(match[idx]) + } + } + match = repl.join('') + } + result += match + } + + return result + } + + private static _substitute( + matchIndex: number, + matches: string[] | null, + ): string { + if (matches === null) { + return '' + } + if (matchIndex === 0) { + return matches[0] + } + + let remainder = '' + while (matchIndex > 0) { + if (matchIndex < matches.length) { + // A match can be undefined + const match = matches[matchIndex] || '' + return match + remainder + } + remainder = String(matchIndex % 10) + remainder + matchIndex = Math.floor(matchIndex / 10) + } + return `$${remainder}` + } +} + +/** + * A replace piece can either be a static string or an index to a specific match. + */ +export class ReplacePiece { + public static staticValue(value: string): ReplacePiece { + return new ReplacePiece(value, -1, null) + } + + public static matchIndex(index: number): ReplacePiece { + return new ReplacePiece(null, index, null) + } + + public static caseOps(index: number, caseOps: string[]): ReplacePiece { + return new ReplacePiece(null, index, caseOps) + } + + public readonly staticValue: string | null + public readonly matchIndex: number + public readonly caseOps: string[] | null + + private constructor( + staticValue: string | null, + matchIndex: number, + caseOps: string[] | null, + ) { + this.staticValue = staticValue + this.matchIndex = matchIndex + if (!caseOps || caseOps.length === 0) { + this.caseOps = null + } else { + this.caseOps = caseOps.slice(0) + } + } +} + +class ReplacePieceBuilder { + private readonly _source: string + private _lastCharIndex: number + private readonly _result: ReplacePiece[] + private _resultLen: number + private _currentStaticPiece: string + + constructor(source: string) { + this._source = source + this._lastCharIndex = 0 + this._result = [] + this._resultLen = 0 + this._currentStaticPiece = '' + } + + public emitUnchanged(toCharIndex: number): void { + this._emitStatic(this._source.substring(this._lastCharIndex, toCharIndex)) + this._lastCharIndex = toCharIndex + } + + public emitStatic(value: string, toCharIndex: number): void { + this._emitStatic(value) + this._lastCharIndex = toCharIndex + } + + private _emitStatic(value: string): void { + if (value.length === 0) { + return + } + this._currentStaticPiece += value + } + + public emitMatchIndex( + index: number, + toCharIndex: number, + caseOps: string[], + ): void { + if (this._currentStaticPiece.length !== 0) { + this._result[this._resultLen++] = ReplacePiece.staticValue( + this._currentStaticPiece, + ) + this._currentStaticPiece = '' + } + this._result[this._resultLen++] = ReplacePiece.caseOps(index, caseOps) + this._lastCharIndex = toCharIndex + } + + public finalize(): ReplacePattern { + this.emitUnchanged(this._source.length) + if (this._currentStaticPiece.length !== 0) { + this._result[this._resultLen++] = ReplacePiece.staticValue( + this._currentStaticPiece, + ) + this._currentStaticPiece = '' + } + return new ReplacePattern(this._result) + } +} + +/** + * \n => inserts a LF + * \t => inserts a TAB + * \\ => inserts a "\\". + * \u => upper-cases one character in a match. + * \U => upper-cases ALL remaining characters in a match. + * \l => lower-cases one character in a match. + * \L => lower-cases ALL remaining characters in a match. + * \E => ends a \U or \L case-change sequence. + * $$ => inserts a "$". + * $& and $0 => inserts the matched substring. + * $n => Where n is a non-negative integer lesser than 100, inserts the nth parenthesized submatch string + * everything else stays untouched + * + * Also see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_string_as_a_parameter + */ +export function parseReplaceString(replaceString: string): ReplacePattern { + if (!replaceString || replaceString.length === 0) { + return new ReplacePattern(null) + } + + const caseOps: string[] = [] + const result = new ReplacePieceBuilder(replaceString) + + for (let i = 0, len = replaceString.length; i < len; i++) { + const chCode = replaceString.charCodeAt(i) + + if (chCode === CharCode.Backslash) { + // move to next char + i++ + + if (i >= len) { + // string ends with a \ + break + } + + const nextChCode = replaceString.charCodeAt(i) + // let replaceWithCharacter: string | null = null; + + switch (nextChCode) { + case CharCode.Backslash: + // \\ => inserts a "\\" + result.emitUnchanged(i - 1) + result.emitStatic('\\', i + 1) + break + case CharCode.n: + // \n => inserts a LF + result.emitUnchanged(i - 1) + result.emitStatic('\n', i + 1) + break + case CharCode.t: + // \t => inserts a TAB + result.emitUnchanged(i - 1) + result.emitStatic('\t', i + 1) + break + // Case modification of string replacements, patterned after Boost, but only applied + // to the replacement text, not subsequent content. + case CharCode.u: + // \u => upper-cases one character. + case CharCode.U: + // \U => upper-cases ALL following characters. + case CharCode.l: + // \l => lower-cases one character. + case CharCode.L: + // \L => lower-cases ALL following characters. + case CharCode.E: + // \E => ends case-change sequence. + result.emitUnchanged(i - 1) + result.emitStatic('', i + 1) + caseOps.push(String.fromCharCode(nextChCode)) + break + } + + continue + } + + if (chCode === CharCode.DollarSign) { + // move to next char + i++ + + if (i >= len) { + // string ends with a $ + break + } + + const nextChCode = replaceString.charCodeAt(i) + + if (nextChCode === CharCode.DollarSign) { + // $$ => inserts a "$" + result.emitUnchanged(i - 1) + result.emitStatic('$', i + 1) + continue + } + + if (nextChCode === CharCode.Digit0 || nextChCode === CharCode.Ampersand) { + // $& and $0 => inserts the matched substring. + result.emitUnchanged(i - 1) + result.emitMatchIndex(0, i + 1, caseOps) + caseOps.length = 0 + continue + } + + if (CharCode.Digit1 <= nextChCode && nextChCode <= CharCode.Digit9) { + // $n + + let matchIndex = nextChCode - CharCode.Digit0 + + // peek next char to probe for $nn + if (i + 1 < len) { + const nextNextChCode = replaceString.charCodeAt(i + 1) + if ( + CharCode.Digit0 <= nextNextChCode && + nextNextChCode <= CharCode.Digit9 + ) { + // $nn + + // move to next char + i++ + matchIndex = matchIndex * 10 + (nextNextChCode - CharCode.Digit0) + + result.emitUnchanged(i - 2) + result.emitMatchIndex(matchIndex, i + 1, caseOps) + caseOps.length = 0 + continue + } + } + + result.emitUnchanged(i - 1) + result.emitMatchIndex(matchIndex, i + 1, caseOps) + caseOps.length = 0 + } + } + } + + return result.finalize() +} diff --git a/src/actions/drafter/lib/build-release-payload/render-template/util/search.ts b/src/actions/drafter/lib/build-release-payload/render-template/util/search.ts new file mode 100644 index 0000000000..cee3cd4d9c --- /dev/null +++ b/src/actions/drafter/lib/build-release-payload/render-template/util/search.ts @@ -0,0 +1,94 @@ +// Minimal helper used by replacePattern. + +function containsUppercaseCharacter(target: string): boolean { + if (!target) { + return false + } + + return target.toLowerCase() !== target +} + +export function buildReplaceStringWithCasePreserved( + matches: string[] | null, + pattern: string, +): string { + if (matches && matches[0] !== '') { + const containsHyphens = validateSpecificSpecialCharacter( + matches, + pattern, + '-', + ) + const containsUnderscores = validateSpecificSpecialCharacter( + matches, + pattern, + '_', + ) + if (containsHyphens && !containsUnderscores) { + return buildReplaceStringForSpecificSpecialCharacter( + matches, + pattern, + '-', + ) + } else if (!containsHyphens && containsUnderscores) { + return buildReplaceStringForSpecificSpecialCharacter( + matches, + pattern, + '_', + ) + } + if (matches[0].toUpperCase() === matches[0]) { + return pattern.toUpperCase() + } else if (matches[0].toLowerCase() === matches[0]) { + return pattern.toLowerCase() + } else if ( + containsUppercaseCharacter(matches[0][0]) && + pattern.length > 0 + ) { + return pattern[0].toUpperCase() + pattern.substring(1) + } else if ( + matches[0][0].toUpperCase() !== matches[0][0] && + pattern.length > 0 + ) { + return pattern[0].toLowerCase() + pattern.substring(1) + } else { + // We do not understand its pattern yet. + return pattern + } + } else { + return pattern + } +} + +function validateSpecificSpecialCharacter( + matches: string[], + pattern: string, + specialCharacter: string, +): boolean { + const doesContainSpecialCharacter = + matches[0].indexOf(specialCharacter) !== -1 && + pattern.indexOf(specialCharacter) !== -1 + return ( + doesContainSpecialCharacter && + matches[0].split(specialCharacter).length === + pattern.split(specialCharacter).length + ) +} + +function buildReplaceStringForSpecificSpecialCharacter( + matches: string[], + pattern: string, + specialCharacter: string, +): string { + const splitPatternAtSpecialCharacter = pattern.split(specialCharacter) + const splitMatchAtSpecialCharacter = matches[0].split(specialCharacter) + let replaceString: string = '' + splitPatternAtSpecialCharacter.forEach((splitValue, index) => { + replaceString += + buildReplaceStringWithCasePreserved( + [splitMatchAtSpecialCharacter[index]], + splitValue, + ) + specialCharacter + }) + + return replaceString.slice(0, -1) +} diff --git a/src/actions/drafter/lib/build-release-payload/resolve-version-increment.ts b/src/actions/drafter/lib/build-release-payload/resolve-version-increment.ts new file mode 100644 index 0000000000..19fc14c997 --- /dev/null +++ b/src/actions/drafter/lib/build-release-payload/resolve-version-increment.ts @@ -0,0 +1,143 @@ +import * as core from '@actions/core' +import type { ReleaseType } from 'semver' +import { + filterPullRequestsByPreCategories, + getChangelogCategories, + getVersionResolverCategories, + matchesCategory, +} from '../../common/category-matching.ts' +import type { ParsedConfig } from '../../config/index.ts' +import type { findPullRequests } from '../find-pull-requests/index.ts' + +const priorityMap = { + patch: 1, + minor: 2, + major: 3, +} as const +type Priority = number + +type PullRequest = Awaited< + ReturnType +>['pullRequests'][number] +type IncrementCategory = Extract< + ParsedConfig['categories'][number], + { 'semver-increment': keyof typeof priorityMap } +> + +const getHighestPriority = (params: { + pullRequests: PullRequest[] + categories: IncrementCategory[] + emptyWhenBehavior: 'fallback' | 'uncategorized' +}) => { + const { pullRequests, categories, emptyWhenBehavior } = params + const emptyWhenCategory = categories.find( + (category) => category.when.length === 0, + ) + const matchedPullRequests = new Set() + let highestPriority: Priority | undefined + let remainingPullRequests = [...pullRequests] + + for (const category of categories) { + if (category.when.length === 0) { + continue + } + + const matchingPullRequests = remainingPullRequests.filter((pullRequest) => + matchesCategory(category, pullRequest), + ) + if (matchingPullRequests.length === 0) { + continue + } + + highestPriority = Math.max( + highestPriority ?? 0, + priorityMap[category['semver-increment']], + ) + + for (const pullRequest of matchingPullRequests) { + matchedPullRequests.add(pullRequest) + } + + if (category.exclusive) { + const matchedPullRequestsSet = new Set(matchingPullRequests) + remainingPullRequests = remainingPullRequests.filter( + (pullRequest) => !matchedPullRequestsSet.has(pullRequest), + ) + } + } + + if (!emptyWhenCategory) { + return highestPriority + } + + // Version-resolver categories treat an empty `when` as the default fallback. + if (emptyWhenBehavior === 'fallback') { + return highestPriority ?? priorityMap[emptyWhenCategory['semver-increment']] + } + + // Changelog categories only apply their empty `when` bucket to still-unmatched PRs. + const hasUncategorizedPullRequests = pullRequests.some( + (pullRequest) => !matchedPullRequests.has(pullRequest), + ) + if (!hasUncategorizedPullRequests) { + return highestPriority + } + + return Math.max( + highestPriority ?? 0, + priorityMap[emptyWhenCategory['semver-increment']], + ) +} + +export const resolveVersionKeyIncrement = (params: { + pullRequests: Awaited>['pullRequests'] + config: Pick< + ParsedConfig, + 'categories' | 'prerelease' | 'prerelease-identifier' + > +}): ReleaseType => { + const { pullRequests, config } = params + + const filteredPullRequests = filterPullRequestsByPreCategories( + pullRequests, + config.categories, + ) + // Changelog categories contribute only for PRs that end up assigned to them. + const changelogPriority = getHighestPriority({ + pullRequests: filteredPullRequests, + categories: getChangelogCategories(config.categories), + emptyWhenBehavior: 'uncategorized', + }) + // Version-resolver categories contribute from their own matches plus fallback. + const versionResolverPriority = + getHighestPriority({ + pullRequests: filteredPullRequests, + categories: getVersionResolverCategories(config.categories), + emptyWhenBehavior: 'fallback', + }) ?? priorityMap.patch + // The resolved version follows the most severe increment contributed by either category type. + const resolvedPriority = Math.max( + changelogPriority ?? 0, + versionResolverPriority, + ) + const versionKey = Object.entries(priorityMap).find( + ([, priority]) => priority === resolvedPriority, + )?.[0] as keyof typeof priorityMap + + core.debug(`versionKey: ${versionKey}`) + + let versionKeyIncrement: ReleaseType = versionKey + + const shouldIncrementAsPrerelease = + config.prerelease && config['prerelease-identifier'] + + if (shouldIncrementAsPrerelease) { + versionKeyIncrement = `pre${versionKeyIncrement}` + } + + core.info( + `Version increment: ${versionKeyIncrement}${!versionKey ? ' (default)' : ''}`, + ) + + return versionKeyIncrement +} diff --git a/src/actions/drafter/lib/build-release-payload/sort-pull-requests.ts b/src/actions/drafter/lib/build-release-payload/sort-pull-requests.ts new file mode 100644 index 0000000000..9de2922876 --- /dev/null +++ b/src/actions/drafter/lib/build-release-payload/sort-pull-requests.ts @@ -0,0 +1,54 @@ +import * as core from '@actions/core' +import type { Config } from '../../config/index.ts' +import type { findPullRequests } from '../find-pull-requests/index.ts' + +type Pr = Awaited>['pullRequests'][number] + +export const sortPullRequests = (params: { + pullRequests: Pr[] + config: Pick +}) => { + const { + pullRequests, + config: { 'sort-by': sortBy, 'sort-direction': sortDirection }, + } = params + + const getSortField = sortBy === 'title' ? getTitle : getMergedAt + + const sort = sortDirection === 'ascending' ? sortAscending : sortDescending + + return structuredClone(pullRequests).sort((a, b) => { + try { + return sort(getSortField(a), getSortField(b)) + } catch (error) { + core.warning( + `Failed to sort pull-requests ${a.number} and ${b.number} by ${sortBy} in ${sortDirection} order. Returning unsorted.`, + ) + core.error(error as Error) + return 0 + } + }) +} + +const getTitle = (pr: Pr) => pr.title +const getMergedAt = (pr: Pr) => pr.mergedAt + +type TData = ReturnType | ReturnType + +const sortAscending = (a: TData, b: TData) => { + if (a == null && b == null) return 0 + if (a == null) return 1 + if (b == null) return -1 + if (a > b) return 1 + if (a < b) return -1 + return 0 +} + +const sortDescending = (a: TData, b: TData) => { + if (a == null && b == null) return 0 + if (a == null) return -1 + if (b == null) return 1 + if (a > b) return -1 + if (a < b) return 1 + return 0 +} diff --git a/src/actions/drafter/lib/build-release-payload/static/last-not-found.md b/src/actions/drafter/lib/build-release-payload/static/last-not-found.md new file mode 100644 index 0000000000..ba68afeb66 --- /dev/null +++ b/src/actions/drafter/lib/build-release-payload/static/last-not-found.md @@ -0,0 +1,8 @@ +> [!WARNING] +> Release Drafter could not find a previous **published release** for `$OWNER/$REPOSITORY`. This draft was created **without a comparison baseline**. + +> [!IMPORTANT] +> Treat this draft as a manual starting point. +> Review the proposed version, tag, and notes before publishing. + +If you did not expect this to happen, [open an issue](https://github.com/release-drafter/release-drafter/issues/new?template=previous-published-release-not-found.yml). diff --git a/src/actions/drafter/lib/build-release-payload/version-descriptor.ts b/src/actions/drafter/lib/build-release-payload/version-descriptor.ts new file mode 100644 index 0000000000..cc8a67fdba --- /dev/null +++ b/src/actions/drafter/lib/build-release-payload/version-descriptor.ts @@ -0,0 +1,154 @@ +import * as core from '@actions/core' +import type { ReleaseType, SemVer } from 'semver' +import coerce from 'semver/functions/coerce.js' +import inc from 'semver/functions/inc.js' +import major from 'semver/functions/major.js' +import minor from 'semver/functions/minor.js' +import parse from 'semver/functions/parse.js' +import patch from 'semver/functions/patch.js' +import prerelease from 'semver/functions/prerelease.js' +import type { Config } from '#src/actions/drafter/config/index.ts' +import type { findPreviousReleases } from '../find-previous-releases/index.ts' +import { renderTemplate } from './render-template/index.ts' + +type Release = Exclude< + Awaited>['lastRelease'], + undefined +> + +export class VersionDescriptor { + public version: SemVer | null = null + + public major: string | null = null + public minor: string | null = null + public patch: string | null = null + public prerelease: string | null = null + + public preReleaseIdentifier?: string + public tagPrefix?: string + + constructor( + from: SemVer | Pick | string | undefined, + opt?: { + preReleaseIdentifier?: string + tagPrefix?: Config['tag-prefix'] + }, + ) { + this.preReleaseIdentifier = opt?.preReleaseIdentifier + this.tagPrefix = opt?.tagPrefix + + this.version = this._coerce(from) + + this.major = this.version ? major(this.version).toString() : null + this.minor = this.version ? minor(this.version).toString() : null + this.patch = this.version ? patch(this.version).toString() : null + this.prerelease = + this.version === null + ? null + : prerelease(this.version) + ? `-${prerelease(this.version)?.join('.')}` + : '' + } + + private _coerce( + from: SemVer | Pick | string | undefined, + ) { + if (from) { + const ver = + typeof from === 'object' + ? this._isRelease(from) + ? this._toSemver(this._stripTag(from.tag_name)) || + this._toSemver(this._stripTag(from.name)) + : this._toSemver(from) + : this._toSemver(this._stripTag(from)) + + if (!ver) { + core.warning( + `Failed to parse version from input ${from}. Defaulting coerced version to null.`, + ) + return null + } + return ver + } else { + core.debug( + `Building version descriptor without version input. Defaulting coerced version to null.`, + ) + return null + } + } + + private _isRelease( + input: unknown, + ): input is Pick { + return ( + typeof input === 'object' && + input !== null && + (typeof (input as Pick)?.tag_name === + 'string' || + typeof (input as Pick)?.name === 'string') + ) + } + + private _stripTag(input?: string | null) { + return this.tagPrefix && input?.startsWith(this.tagPrefix) + ? input.slice(this.tagPrefix.length) + : input + } + + private _toSemver(version?: string | SemVer | null | undefined) { + const result = parse(version) + if (result) { + return result + } + + // doesn't handle prerelease + return coerce(version) + } + + /** + * Alters version in-place by incrementing it according to the specified release type (major, minor, patch, prerelease). + */ + public incremented(increment: ReleaseType | 'no_increment') { + if (!this.version || increment === 'no_increment') { + return this + } + + const _incrementedVersion = inc( + this.version, + increment, + true, + this.preReleaseIdentifier, + ) + + if (!_incrementedVersion) { + throw new Error( + `Failed to increment version ${this.version} with increment ${increment}`, + ) + } + + const _incrementedSemver = this._toSemver(_incrementedVersion) + + if (!_incrementedSemver) { + throw new Error( + `Failed to parse version ${_incrementedVersion} after incrementing ${this.version} with increment ${increment}`, + ) + } + + return new VersionDescriptor(_incrementedSemver, { + tagPrefix: this.tagPrefix, + preReleaseIdentifier: this.preReleaseIdentifier, + }) + } + + public rendered(template: string) { + return renderTemplate({ + template, + object: { + $MAJOR: this.major ?? undefined, + $MINOR: this.minor ?? undefined, + $PATCH: this.patch ?? undefined, + $PRERELEASE: this.prerelease ?? undefined, + }, + }) + } +} diff --git a/src/actions/drafter/lib/find-previous-releases/find-previous-releases.ts b/src/actions/drafter/lib/find-previous-releases/find-previous-releases.ts new file mode 100644 index 0000000000..12be90747d --- /dev/null +++ b/src/actions/drafter/lib/find-previous-releases/find-previous-releases.ts @@ -0,0 +1,167 @@ +import * as core from '@actions/core' +import { context } from '@actions/github' +import coerce from 'semver/functions/coerce.js' +import satisfies from 'semver/functions/satisfies.js' +import validRange from 'semver/ranges/valid.js' +import { getOctokit } from '#src/common/index.ts' +import type { ParsedConfig } from '../../config/index.ts' +import { sortReleases } from './sort-releases.ts' + +// GitHub API currently returns a 500 HTTP response if you attempt to fetch over 1000 releases. +const RELEASE_COUNT_LIMIT = 1000 + +/** + * Lists every release and : + * - filters by commitish if specified + * - filters by tag-prefix if specified + * - filters out pre-releases unless specified + * - extracts the first draft releases (according to return-order of GitHub API) + * - get latest published release according to ./sort-releases.ts implementation + * + * Returns one of (or both) draft release and latest published release + * The last stable release is used to determine the range of commits to include in the changelog, + * and to resolve the next version number. + * + * The draft release is used to determine if we should create a new release or update the existing one. + */ +export const findPreviousReleases = async ( + params: Pick< + ParsedConfig, + | 'commitish' + | 'filter-by-commitish' + | 'tag-prefix' + | 'prerelease' + | 'include-pre-releases' + | 'filter-by-range' + >, +) => { + const { + commitish, + 'filter-by-commitish': filterByCommitish, + 'tag-prefix': tagPrefix, + prerelease: isPreRelease, + 'include-pre-releases': includePreReleases, + 'filter-by-range': filterByRange, + } = params + const octokit = getOctokit() + + core.info('Fetching releases from GitHub...') + + let releaseCount = 0 + const releases = await octokit.paginate( + octokit.rest.repos.listReleases, + { + ...context.repo, + per_page: 100, + }, + (response, done) => { + releaseCount += response.data.length + if (releaseCount >= RELEASE_COUNT_LIMIT) { + done() + } + return response.data + }, + ) + + core.info(`Found ${releases.length} releases`) + + // Filter releases + const headRefRegex = /^refs\/heads\// // `refs/heads/branch` and `branch` are the same thing in this context + const targetCommitishName = commitish.replace(headRefRegex, '') + const commitishFilteredReleases = filterByCommitish + ? releases.filter( + (r) => + targetCommitishName === r.target_commitish.replace(headRefRegex, ''), + ) + : releases + const semverRangeFilteredReleases = + filterByRange && filterByRange !== '*' + ? commitishFilteredReleases.filter((r) => { + const parsedRange = validRange(filterByRange) + if (!parsedRange) return false + const parsedVersion = coerce(r.tag_name, { loose: true })?.version + + if (!parsedVersion) { + core.warning( + `Failed to coerce semver version for "${r.tag_name}" : will be excluded from releases considered for drafting.`, + ) + return false + } + + const doesSatisfy = !!satisfies(parsedVersion, parsedRange, { + loose: true, + }) + + core.debug( + `Range "${parsedRange}" ${ + doesSatisfy ? 'satisfies' : 'does not satisfy' + } version "${parsedVersion}" `, + ) + + return doesSatisfy + }) + : commitishFilteredReleases + const filteredReleases = tagPrefix + ? semverRangeFilteredReleases.filter((r) => + r.tag_name.startsWith(tagPrefix), + ) + : semverRangeFilteredReleases + + // Split drafts and published releases + let publishedReleases = filteredReleases.filter((r) => !r.draft) + let draftReleases = filteredReleases.filter((r) => r.draft) + + // Handle prereleases + publishedReleases = publishedReleases.filter( + (publishedRelease) => + isPreRelease || includePreReleases + ? publishedRelease.prerelease || !publishedRelease.prerelease // Both prerelease and regular published-releases + : !publishedRelease.prerelease, // Only regular published-releases + ) + draftReleases = draftReleases.filter( + (draftRelease) => + isPreRelease + ? draftRelease.prerelease // Only pre-releases drafts + : !draftRelease.prerelease, // Only regular drafts + ) + + // Sort results + const draftRelease = draftReleases[0] // Should this be sorted ? + const lastRelease = sortReleases({ + releases: publishedReleases, + tagPrefix, + })?.at(-1) + + if (draftRelease) { + if (draftReleases.length > 1) { + core.warning( + `Multiple draft releases found : ${draftReleases + .map((r) => r.tag_name) + .join(', ')}`, + ) + core.warning( + `Using the first one returned by GitHub API: ${draftRelease.tag_name}`, + ) + } + + core.info(`Draft release${isPreRelease ? ' (which is a prerelease)' : ''}:`) + core.info(` tag_name: ${draftRelease.tag_name}`) + core.info(` name: ${draftRelease.name}`) + } else { + core.info( + `No draft release found${isPreRelease ? ' (among prerelease drafts)' : ''}`, + ) + } + + if (lastRelease) { + core.info(`Last release${isPreRelease ? ' (including prerelease)' : ''}:`) + core.info(` tag_name: ${lastRelease.tag_name}`) + core.info(` name: ${lastRelease.name}`) + } else { + core.warning( + `No published release found${isPreRelease ? ' (including prerelease)' : ''}`, + ) + } + + return { draftRelease, lastRelease } +} diff --git a/src/actions/drafter/lib/find-previous-releases/index.ts b/src/actions/drafter/lib/find-previous-releases/index.ts new file mode 100644 index 0000000000..74167d09ba --- /dev/null +++ b/src/actions/drafter/lib/find-previous-releases/index.ts @@ -0,0 +1,2 @@ +/* node:coverage ignore file -- @preserve */ +export { findPreviousReleases } from './find-previous-releases.ts' diff --git a/src/actions/drafter/lib/find-previous-releases/sort-releases.ts b/src/actions/drafter/lib/find-previous-releases/sort-releases.ts new file mode 100644 index 0000000000..70a1ef177a --- /dev/null +++ b/src/actions/drafter/lib/find-previous-releases/sort-releases.ts @@ -0,0 +1,34 @@ +import { compareVersions } from 'compare-versions' +import regexEscape from 'escape-string-regexp' +import type { Octokit } from '#src/common/get-octokit.ts' +import type { Config } from '../../config/index.ts' + +export const sortReleases = (params: { + releases: Awaited< + ReturnType + >['data'] + tagPrefix: Config['tag-prefix'] +}) => { + // For semver, we find the greatest release number + // For non-semver, we use the most recently merged + const tagPrefixRexExp = params.tagPrefix + ? new RegExp(`^${regexEscape(params.tagPrefix)}`) + : undefined + + return params.releases.sort((r1, r2) => { + const tag_name_1 = tagPrefixRexExp + ? r1.tag_name.replace(tagPrefixRexExp, '') + : r1.tag_name + const tag_name_2 = tagPrefixRexExp + ? r2.tag_name.replace(tagPrefixRexExp, '') + : r2.tag_name + + try { + return compareVersions(tag_name_1, tag_name_2) + } catch { + return ( + new Date(r1.created_at).getTime() - new Date(r2.created_at).getTime() + ) + } + }) +} diff --git a/src/actions/drafter/lib/find-pull-requests/find-commits-in-comparison.ts b/src/actions/drafter/lib/find-pull-requests/find-commits-in-comparison.ts new file mode 100644 index 0000000000..e53bdcd88c --- /dev/null +++ b/src/actions/drafter/lib/find-pull-requests/find-commits-in-comparison.ts @@ -0,0 +1,25 @@ +import { getOctokit, paginateGraphql } from '#src/common/index.ts' +import type { FindCommitsInComparisonQueryVariables } from '#src/types/github.graphql.generated.ts' +import { FindCommitsInComparisonDocument } from '#src/types/github.graphql.generated.ts' + +export const findCommitsInComparison = async ( + params: FindCommitsInComparisonQueryVariables, +) => { + const octokit = getOctokit() + + const data = await paginateGraphql( + octokit.graphql, + FindCommitsInComparisonDocument, + params, + ) + + if (!data.repository?.ref?.compare) { + throw new Error( + 'Query returned an unexpected result: ref or comparison not found', + ) + } + + return (data.repository.ref.compare.commits.nodes || []).filter( + (commit): commit is NonNullable => commit != null, + ) +} diff --git a/src/actions/drafter/lib/find-pull-requests/find-pull-requests.ts b/src/actions/drafter/lib/find-pull-requests/find-pull-requests.ts new file mode 100644 index 0000000000..fbf1c49dfd --- /dev/null +++ b/src/actions/drafter/lib/find-pull-requests/find-pull-requests.ts @@ -0,0 +1,188 @@ +import * as core from '@actions/core' +import { context } from '@actions/github' +import { getOctokit, getPullRequestsChangedFiles } from '#src/common/index.ts' +import { needsPullRequestChangedFiles } from '../../common/category-matching.ts' +import type { ParsedConfig } from '../../config/index.ts' +import type { findPreviousReleases } from '../find-previous-releases/index.ts' +import { findCommitsInComparison } from './find-commits-in-comparison.ts' +import { + findRecentMergedPullRequests, + type RecentMergedPullRequest, +} from './find-recent-merged-pull-requests.ts' + +const findNewContributorLogins = async ( + pullRequests: Array<{ + author?: { __typename?: string; login: string } | null + mergedAt?: string | null + }>, +) => { + const firstMergedAtByLogin = new Map() + + for (const pullRequest of pullRequests) { + if (pullRequest.author?.__typename !== 'User' || !pullRequest.mergedAt) + continue + + const previous = firstMergedAtByLogin.get(pullRequest.author.login) + if (!previous || pullRequest.mergedAt < previous) { + firstMergedAtByLogin.set(pullRequest.author.login, pullRequest.mergedAt) + } + } + + const candidates = [...firstMergedAtByLogin] + if (candidates.length === 0) return new Set() + + const variables = Object.fromEntries( + candidates.map(([login, mergedAt], index) => [ + `query${index}`, + `repo:${context.repo.owner}/${context.repo.repo} is:pr is:merged author:${login} merged:<${mergedAt}`, + ]), + ) + const data = await getOctokit().graphql< + Record + >( + `query findPreviousContributions(${candidates.map((_, index) => `$query${index}: String!`).join(', ')}) { + ${candidates.map((_, index) => `author${index}: search(query: $query${index}, type: ISSUE, first: 1) { issueCount }`).join('\n')} + }`, + variables, + ) + + return new Set( + candidates.flatMap(([login], index) => + data[`author${index}`]?.issueCount === 0 ? [login] : [], + ), + ) +} + +export const findPullRequests = async (params: { + lastRelease: Awaited>['lastRelease'] + config: ParsedConfig +}) => { + const sharedComparisonParams = { + name: context.repo.repo, + owner: context.repo.owner, + headRef: params.config.commitish, + withPullRequestBody: params.config['change-template'].includes('$BODY'), + withPullRequestURL: params.config['change-template'].includes('$URL'), + withBaseRefName: + params.config['change-template'].includes('$BASE_REF_NAME'), + withHeadRefName: + params.config['change-template'].includes('$HEAD_REF_NAME'), + pullRequestLimit: params.config['pull-request-limit'], + historyLimit: params.config['history-limit'], + } + + if (!params.lastRelease?.tag_name) { + core.warning('A previous (published) release is required to find changes') + return { + commits: [], + newContributorLogins: new Set(), + pullRequests: [], + } + } + + core.info( + `Finding commits between refs/tags/${params.lastRelease.tag_name} and ${params.config.commitish}...`, + ) + const commits = await findCommitsInComparison({ + baseRef: `refs/tags/${params.lastRelease.tag_name}`, + ...sharedComparisonParams, + }) + + core.info(`Found ${commits.length} commits.`) + + // Extract unique PRs from commits, deduplicated by repo + PR number + const pullRequestsByKey = new Map( + commits + .flatMap((commit) => commit.associatedPullRequests?.nodes ?? []) + .filter((pr) => pr != null) + .map( + (pr) => + [`${pr.baseRepository?.nameWithOwner}#${pr.number}`, pr] as const, + ), + ) + const pullRequestsRaw = [...pullRequestsByKey.values()] + + // GitHub's associatedPullRequests index lags for very recently merged PRs; + // query the PR table directly to recover any whose merge commit is in range. + const comparisonCommitOids = new Set( + commits.flatMap((c) => (c.oid ? [c.oid] : [])), + ) + // Filter by branch only when commitish is a confirmed branch ref + // (refs/heads/...). For bare values (e.g. "main", "v1.2.3") we can't tell + // branch from tag, so fall back to no filter and rely on OID intersection. + // Skip the safety net entirely for tag/pull refs since PRs don't merge into + // those. + const { commitish } = params.config + const isBranchRef = commitish.startsWith('refs/heads/') + const isUnsupportedRef = + commitish.startsWith('refs/tags/') || commitish.startsWith('refs/pull/') + const recoveredPRs = + comparisonCommitOids.size === 0 || isUnsupportedRef + ? [] + : await findRecentMergedPullRequests({ + baseRefName: isBranchRef + ? commitish.replace(/^refs\/heads\//, '') + : null, + commitOids: comparisonCommitOids, + foundPrKeys: new Set(pullRequestsByKey.keys()), + fieldFlags: { + withPullRequestBody: sharedComparisonParams.withPullRequestBody, + withPullRequestURL: sharedComparisonParams.withPullRequestURL, + withBaseRefName: sharedComparisonParams.withBaseRefName, + withHeadRefName: sharedComparisonParams.withHeadRefName, + }, + }) + const pullRequests: Array< + (typeof pullRequestsRaw)[number] | RecentMergedPullRequest + > = [...pullRequestsRaw, ...recoveredPRs].filter( + (pr) => + // `baseRepository` is the repository the PR targets, not the head/fork repo. + // Keep fork PRs that target the current repository, and exclude associated + // PRs that belong to some other repository but share the same commit. + pr.baseRepository?.nameWithOwner === + `${context.repo.owner}/${context.repo.repo}` && + // Ensure PR is merged + pr.merged, + ) + const shouldLoadPullRequestChangedFiles = needsPullRequestChangedFiles( + params.config.categories, + ) + const pullRequestChangedFiles = shouldLoadPullRequestChangedFiles + ? await getPullRequestsChangedFiles({ + owner: context.repo.owner, + repo: context.repo.repo, + pullRequests, + }) + : new Map() + const usesNewContributors = [ + params.config.header, + params.config.template, + params.config.footer, + ].some((template) => template?.includes('$NEW_CONTRIBUTORS')) + const newContributorLogins = usesNewContributors + ? await findNewContributorLogins(pullRequests) + : new Set() + + core.info( + `Found ${pullRequests.length} merged pull requests targeting ${context.repo.owner}/${context.repo.repo}${ + pullRequests.length > 0 + ? `: ${pullRequests.map((pr) => `#${pr.number}`).join(', ')}` + : '.' + }`, + ) + + return { + commits, + newContributorLogins, + pullRequests: pullRequests.map((pullRequest) => + shouldLoadPullRequestChangedFiles + ? { + ...pullRequest, + changedFiles: pullRequestChangedFiles.get( + `${pullRequest.baseRepository?.nameWithOwner}#${pullRequest.number}`, + ), + } + : pullRequest, + ), + } +} diff --git a/src/actions/drafter/lib/find-pull-requests/find-recent-merged-pull-requests.ts b/src/actions/drafter/lib/find-pull-requests/find-recent-merged-pull-requests.ts new file mode 100644 index 0000000000..1a296d5934 --- /dev/null +++ b/src/actions/drafter/lib/find-pull-requests/find-recent-merged-pull-requests.ts @@ -0,0 +1,65 @@ +import * as core from '@actions/core' +import { context } from '@actions/github' +import { executeGraphql, getOctokit } from '#src/common/index.ts' +import { + FindRecentMergedPullRequestsDocument, + type FindRecentMergedPullRequestsQuery, +} from '#src/types/github.graphql.generated.ts' + +export type PullRequestFieldFlags = { + withPullRequestBody: boolean + withPullRequestURL: boolean + withBaseRefName: boolean + withHeadRefName: boolean +} + +const RECENT_PR_LOOKBACK = 5 + +type RecentMergedPullRequestNode = NonNullable< + NonNullable< + FindRecentMergedPullRequestsQuery['repository'] + >['pullRequests']['nodes'] +>[number] + +export type RecentMergedPullRequest = NonNullable + +export const findRecentMergedPullRequests = async (params: { + baseRefName: string | null + commitOids: Set + foundPrKeys: Set + fieldFlags: PullRequestFieldFlags +}): Promise => { + const octokit = getOctokit() + const nameWithOwner = `${context.repo.owner}/${context.repo.repo}` + + const data = await executeGraphql( + octokit.graphql, + FindRecentMergedPullRequestsDocument, + { + name: context.repo.repo, + owner: context.repo.owner, + baseRefName: params.baseRefName, + limit: RECENT_PR_LOOKBACK, + ...params.fieldFlags, + }, + ) + + const prNodes = data.repository?.pullRequests.nodes ?? [] + + const missingPRs = prNodes.filter((pr) => { + if (!pr?.mergeCommit?.oid) return false + const prKey = `${nameWithOwner}#${pr.number}` + return ( + params.commitOids.has(pr.mergeCommit.oid) && + !params.foundPrKeys.has(prKey) + ) + }) + + if (missingPRs.length === 0) return [] + + core.info( + `Found ${missingPRs.length} recently merged PR(s) missing from GraphQL index, recovering: ${missingPRs.map((pr) => `#${pr?.number}`).join(', ')}`, + ) + + return missingPRs.filter((pr): pr is RecentMergedPullRequest => pr != null) +} diff --git a/src/actions/drafter/lib/find-pull-requests/graphql/find-commits-in-comparison.gql b/src/actions/drafter/lib/find-pull-requests/graphql/find-commits-in-comparison.gql new file mode 100644 index 0000000000..94afd49e28 --- /dev/null +++ b/src/actions/drafter/lib/find-pull-requests/graphql/find-commits-in-comparison.gql @@ -0,0 +1,59 @@ +query findCommitsInComparison( + $name: String! + $owner: String! + $baseRef: String! + $headRef: String! + $withPullRequestBody: Boolean! + $withPullRequestURL: Boolean! + $cursor: String + $withBaseRefName: Boolean! + $withHeadRefName: Boolean! + $pullRequestLimit: Int! + $historyLimit: Int! +) { + repository(name: $name, owner: $owner) { + ref(qualifiedName: $baseRef) { + compare(headRef: $headRef) { + commits(first: $historyLimit, after: $cursor) { + __typename + pageInfo { + __typename + hasNextPage + endCursor + } + nodes { + __typename + id + oid + committedDate + message + author { + __typename + name + user { + __typename + login + } + } + authors(first: 100) { + nodes { + __typename + name + user { + __typename + login + } + } + } + associatedPullRequests(first: $pullRequestLimit) { + __typename + nodes { + ...PullRequestFields + } + } + } + } + } + } + } +} diff --git a/src/actions/drafter/lib/find-pull-requests/graphql/find-commits-with-path-changes.gql b/src/actions/drafter/lib/find-pull-requests/graphql/find-commits-with-path-changes.gql new file mode 100644 index 0000000000..5d96a74538 --- /dev/null +++ b/src/actions/drafter/lib/find-pull-requests/graphql/find-commits-with-path-changes.gql @@ -0,0 +1,27 @@ +query findCommitsWithPathChangesQuery( + $name: String! + $owner: String! + $targetCommitish: String! + $after: String + $path: String +) { + repository(name: $name, owner: $owner) { + object(expression: $targetCommitish) { + ... on Commit { + __typename + history(path: $path, after: $after) { + __typename + pageInfo { + __typename + hasNextPage + endCursor + } + nodes { + __typename + id + } + } + } + } + } +} diff --git a/src/actions/drafter/lib/find-pull-requests/graphql/find-recent-merged-pull-requests.gql b/src/actions/drafter/lib/find-pull-requests/graphql/find-recent-merged-pull-requests.gql new file mode 100644 index 0000000000..f8529cbb7e --- /dev/null +++ b/src/actions/drafter/lib/find-pull-requests/graphql/find-recent-merged-pull-requests.gql @@ -0,0 +1,28 @@ +query findRecentMergedPullRequests( + $name: String! + $owner: String! + $baseRefName: String + $limit: Int! + $withPullRequestBody: Boolean! + $withPullRequestURL: Boolean! + $withBaseRefName: Boolean! + $withHeadRefName: Boolean! +) { + repository(name: $name, owner: $owner) { + pullRequests( + states: [MERGED] + baseRefName: $baseRefName + orderBy: { field: UPDATED_AT, direction: DESC } + first: $limit + ) { + __typename + nodes { + ...PullRequestFields + mergeCommit { + __typename + oid + } + } + } + } +} diff --git a/src/actions/drafter/lib/find-pull-requests/graphql/pull-request-fields.fragment.gql b/src/actions/drafter/lib/find-pull-requests/graphql/pull-request-fields.fragment.gql new file mode 100644 index 0000000000..ab184234e4 --- /dev/null +++ b/src/actions/drafter/lib/find-pull-requests/graphql/pull-request-fields.fragment.gql @@ -0,0 +1,28 @@ +fragment PullRequestFields on PullRequest { + __typename + title + number + url @include(if: $withPullRequestURL) + body @include(if: $withPullRequestBody) + author { + __typename + login + url + } + baseRepository { + __typename + nameWithOwner + } + mergedAt + isCrossRepository + labels(first: 100) { + __typename + nodes { + __typename + name + } + } + merged + baseRefName @include(if: $withBaseRefName) + headRefName @include(if: $withHeadRefName) +} diff --git a/src/actions/drafter/lib/find-pull-requests/index.ts b/src/actions/drafter/lib/find-pull-requests/index.ts new file mode 100644 index 0000000000..604d5c3b8f --- /dev/null +++ b/src/actions/drafter/lib/find-pull-requests/index.ts @@ -0,0 +1,2 @@ +/* node:coverage ignore file -- @preserve */ +export { findPullRequests } from './find-pull-requests.ts' diff --git a/src/actions/drafter/lib/index.ts b/src/actions/drafter/lib/index.ts new file mode 100644 index 0000000000..2e3c7ede6c --- /dev/null +++ b/src/actions/drafter/lib/index.ts @@ -0,0 +1,6 @@ +/* node:coverage ignore file -- @preserve */ + +export { buildReleasePayload } from './build-release-payload/index.ts' +export { findPreviousReleases } from './find-previous-releases/index.ts' +export { findPullRequests } from './find-pull-requests/index.ts' +export { upsertRelease } from './upsert-release/index.ts' diff --git a/src/actions/drafter/lib/upsert-release/create-release.ts b/src/actions/drafter/lib/upsert-release/create-release.ts new file mode 100644 index 0000000000..e7b342bfad --- /dev/null +++ b/src/actions/drafter/lib/upsert-release/create-release.ts @@ -0,0 +1,24 @@ +import { context } from '@actions/github' +import { getOctokit } from '#src/common/index.ts' +import type { buildReleasePayload } from '../build-release-payload/index.ts' + +export const createRelease = async (params: { + releasePayload: Awaited> +}) => { + const octokit = getOctokit() + const { releasePayload } = params + + return octokit.rest.repos.createRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + target_commitish: releasePayload.targetCommitish, + name: releasePayload.name, + tag_name: releasePayload.tag, + body: releasePayload.body, + draft: releasePayload.draft, + prerelease: releasePayload.prerelease, + make_latest: releasePayload.prerelease + ? 'false' + : (releasePayload.make_latest.toString() as 'true' | 'false'), + }) +} diff --git a/src/actions/drafter/lib/upsert-release/index.ts b/src/actions/drafter/lib/upsert-release/index.ts new file mode 100644 index 0000000000..4b91b85959 --- /dev/null +++ b/src/actions/drafter/lib/upsert-release/index.ts @@ -0,0 +1,2 @@ +/* node:coverage ignore file -- @preserve */ +export { upsertRelease } from './upsert-release.ts' diff --git a/src/actions/drafter/lib/upsert-release/update-release.ts b/src/actions/drafter/lib/upsert-release/update-release.ts new file mode 100644 index 0000000000..fee08fa9f4 --- /dev/null +++ b/src/actions/drafter/lib/upsert-release/update-release.ts @@ -0,0 +1,55 @@ +import { context } from '@actions/github' +import type { Endpoints } from '@octokit/types' +import { getOctokit } from '#src/common/index.ts' +import type { buildReleasePayload } from '../build-release-payload/index.ts' +import type { findPreviousReleases } from '../find-previous-releases/index.ts' + +export const updateRelease = async (params: { + draftRelease: Exclude< + Awaited>['draftRelease'], + undefined + > + releasePayload: Awaited> +}) => { + const octokit = getOctokit() + const { draftRelease, releasePayload } = params + + type UpdateParams = + Endpoints['PATCH /repos/{owner}/{repo}/releases/{release_id}']['parameters'] + + const updateReleaseParameters: Pick< + UpdateParams, + 'name' | 'tag_name' | 'target_commitish' + > = { + name: releasePayload.name || draftRelease.name || undefined, + tag_name: releasePayload.tag || draftRelease.tag_name, + target_commitish: releasePayload.targetCommitish, + } + + // Let GitHub figure out `name` and `tag_name` if undefined + if (!updateReleaseParameters.name) { + delete updateReleaseParameters.name + } + if (!updateReleaseParameters.tag_name) { + delete updateReleaseParameters.tag_name + } + + // Keep existing `target_commitish` if not overridden + // (sending `null` resets it to the default branch) + if (!updateReleaseParameters.target_commitish) { + delete updateReleaseParameters.target_commitish + } + + return octokit.rest.repos.updateRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: draftRelease.id, + body: releasePayload.body, + draft: releasePayload.draft, + prerelease: releasePayload.prerelease, + make_latest: releasePayload.prerelease + ? 'false' + : (releasePayload.make_latest.toString() as 'true' | 'false'), + ...updateReleaseParameters, + }) +} diff --git a/src/actions/drafter/lib/upsert-release/upsert-release.ts b/src/actions/drafter/lib/upsert-release/upsert-release.ts new file mode 100644 index 0000000000..72f928ee1d --- /dev/null +++ b/src/actions/drafter/lib/upsert-release/upsert-release.ts @@ -0,0 +1,43 @@ +import * as core from '@actions/core' +import type { buildReleasePayload } from '../build-release-payload/index.ts' +import type { findPreviousReleases } from '../find-previous-releases/index.ts' +import { createRelease } from './create-release.ts' +import { updateRelease } from './update-release.ts' + +export const upsertRelease = async (params: { + draftRelease: Awaited>['draftRelease'] + releasePayload: Awaited> + dryRun?: boolean +}) => { + const { draftRelease, releasePayload, dryRun } = params + + if (dryRun) { + if (!draftRelease) { + core.info( + `[dry-run] Would create a new release with payload: ${JSON.stringify(releasePayload, null, 2)}`, + ) + } else { + core.info( + `[dry-run] Would update existing release (id: ${draftRelease.id}) with payload: ${JSON.stringify(releasePayload, null, 2)}`, + ) + } + return undefined + } + + if (!draftRelease) { + core.info('Creating new release...') + const res = await createRelease({ + releasePayload, + }) + core.info('Release created!') + return res + } else { + core.info('Updating existing release...') + const res = await updateRelease({ + draftRelease, + releasePayload, + }) + core.info('Release updated!') + return res + } +} diff --git a/src/actions/drafter/main.ts b/src/actions/drafter/main.ts new file mode 100644 index 0000000000..5b5829b3e9 --- /dev/null +++ b/src/actions/drafter/main.ts @@ -0,0 +1,63 @@ +import * as core from '@actions/core' +import type { ExclusiveInput, ParsedConfig } from './config/index.ts' +import { + buildReleasePayload, + findPreviousReleases, + findPullRequests, + upsertRelease, +} from './lib/index.ts' + +export const main = async (params: { + config: ParsedConfig + input: ExclusiveInput +}) => { + /** + * 1. find previous releases - returns latest release + * 2. find commits since latest release, with their associated pull-requests + * 3. sort those pull-requests according to the desired config (for release-body) + * 4. generate release info + * 5. create a release (may be a draft) or update previous draft + * 6. set action outputs + */ + const { config, input } = params + const isPullRequestMergeRef = /^refs\/pull\/\d+\/merge$/.test( + config.commitish, + ) + const effectiveInput = isPullRequestMergeRef + ? { ...input, 'dry-run': true, publish: false } + : input + + if (isPullRequestMergeRef && !input['dry-run']) { + core.warning( + `${config.commitish} points to an ephemeral pull request merge commit; forcing dry-run mode and disabling publish. Set dry-run: true explicitly to suppress this warning.`, + ) + } + + const { draftRelease, lastRelease } = await findPreviousReleases(config) + + const { commits, newContributorLogins, pullRequests } = + await findPullRequests({ + lastRelease, + config, + }) + + const releasePayload = await buildReleasePayload({ + commits, + config, + input: effectiveInput, + lastRelease, + newContributorLogins, + pullRequests, + }) + + const upsertedRelease = await upsertRelease({ + draftRelease, + releasePayload, + dryRun: effectiveInput['dry-run'], + }) + + return { + upsertedRelease, + releasePayload, + } +} diff --git a/src/actions/drafter/run.ts b/src/actions/drafter/run.ts new file mode 100644 index 0000000000..3f75b219a1 --- /dev/null +++ b/src/actions/drafter/run.ts @@ -0,0 +1,13 @@ +/* node:coverage ignore file -- @preserve */ +/** + * The entrypoint for the action. This file simply imports and runs the action's + * main logic. + * + * Do not add any logic to this file; instead, add it to `runner.ts`. + * + * `runner.ts` is the entrypoint for tests and should contain all the action's + * main logic. + */ +import { run } from './runner.ts' + +await run() diff --git a/src/actions/drafter/runner.ts b/src/actions/drafter/runner.ts new file mode 100644 index 0000000000..db35a88ee8 --- /dev/null +++ b/src/actions/drafter/runner.ts @@ -0,0 +1,34 @@ +import * as core from '@actions/core' +import { + getActionInput, + getConfig, + mergeInputAndConfig, + setActionOutput, +} from './config/index.ts' +import { main } from './main.ts' + +/** + * The main function for the action. + * + * @returns Resolves when the action is complete. + */ +export async function run(): Promise { + try { + core.info('Parsing inputs and configuration...') + const input = getActionInput() + const config = mergeInputAndConfig({ + config: await getConfig(input['config-name']), + input, + }) + + const { upsertedRelease, releasePayload } = await main({ input, config }) + + setActionOutput({ + upsertedRelease, + releasePayload, + }) + } catch (error) { + // Fail the workflow run if an error occurs + if (error instanceof Error) core.setFailed(error.message) + } +} diff --git a/src/common/config/extends.schema.ts b/src/common/config/extends.schema.ts new file mode 100644 index 0000000000..537e420822 --- /dev/null +++ b/src/common/config/extends.schema.ts @@ -0,0 +1,64 @@ +import type * as z from 'zod' +import { + looseObject, + record, + strictObject, + string, + union, + enum as zenum, + null as znull, +} from 'zod' + +export const MERGE_STRATEGIES = ['override', 'append', 'prepend'] as const +export const mergeStrategySchema = zenum(MERGE_STRATEGIES) +export type MergeStrategy = z.output + +const mergeStrategiesSchema = record(string(), mergeStrategySchema) + +/** + * Schema for the `_extends` key: either a plain target string, or a mapping + * with `from` (same target syntax) and an optional per-key merge `strategy`. + * The mapping is strict because composition errors on unknown keys; this way + * editors flag the same typos the action would reject at runtime. + * + * Empty string and null values remain valid no-ops for compatibility with the + * historical string form. Zod normalizes every active declaration to the + * mapping shape consumed by config loading and composition. + */ +export const extendsDeclarationSchema = union([ + string(), + znull(), + strictObject({ + from: string().regex(/\S/, "'from' must not be blank"), + // YAML parses an empty value (`strategy:`) to null; treat it as absent. + strategy: mergeStrategiesSchema.nullish(), + }), +]) + .optional() + .transform((value) => { + if (value == null || (typeof value === 'string' && value.trim() === '')) { + return undefined + } + + if (typeof value === 'string') { + return { from: value.trim(), strategy: {} } + } + + return { + from: value.from.trim(), + strategy: value.strategy ?? {}, + } + }) + +export type ExtendsDeclaration = Exclude< + z.output, + undefined +> + +/** + * Parses the common envelope of a raw config file while retaining all + * action-specific keys for composition and later validation. + */ +export const configFileSchema = looseObject({ + _extends: extendsDeclarationSchema, +}) diff --git a/src/common/config/get-config-file-from-fs.ts b/src/common/config/get-config-file-from-fs.ts new file mode 100644 index 0000000000..f8416ea1c2 --- /dev/null +++ b/src/common/config/get-config-file-from-fs.ts @@ -0,0 +1,40 @@ +import { existsSync, readFileSync } from 'node:fs' +import path, { isAbsolute } from 'node:path' +import process from 'node:process' +import * as core from '@actions/core' + +export const getConfigFileFromFs = (normalizedFilepath: string) => { + // normalizeFilepath should have already handled this + // this is just a safety net + if (isAbsolute(normalizedFilepath)) { + throw new Error( + `Absolute paths are not supported for config file path: ${normalizedFilepath}`, + ) + } + + if (!process.env.GITHUB_WORKSPACE) { + throw new Error( + `env GITHUB_WORKSPACE is not set. Cannot resolve local repo path.`, + ) + } + + const repoRoot = process.env.GITHUB_WORKSPACE + + const configPath = path.join(repoRoot, normalizedFilepath) + + core.info(`Looking for config locally at ${configPath}...`) + + if (!existsSync(repoRoot)) { + throw new Error(`Root repo path does not exist: ${repoRoot}`) + } + + if (!existsSync(configPath)) { + throw new Error( + `Config file not found: ${configPath}. Did you clone your sources ? (ex: using @actions/checkout)`, + ) + } + + core.info(`Loading from file: ${configPath}`) + + return readFileSync(configPath, 'utf8') +} diff --git a/src/common/config/get-config-file-from-repo.ts b/src/common/config/get-config-file-from-repo.ts new file mode 100644 index 0000000000..0b365f21e0 --- /dev/null +++ b/src/common/config/get-config-file-from-repo.ts @@ -0,0 +1,52 @@ +import type { RequestError } from '@octokit/request-error' +import { getOctokit } from '../get-octokit.ts' +import type { ConfigTarget } from './parse-config-target.ts' + +export const getConfigFileFromRepo = async ( + configTarget: ConfigTarget, +): Promise => { + const octokit = getOctokit() + + let res: Awaited> + try { + // see: https://docs.github.com/en/rest/repos/contents + res = await octokit.rest.repos.getContent({ + owner: configTarget.repo.owner, + repo: configTarget.repo.repo, + path: configTarget.filepath, + ref: configTarget.ref, + mediaType: { format: 'raw' }, + }) + } catch (error) { + if ((error as RequestError).status === 404) { + throw new Error( + `Config file not found with error 404. (target: ${configTarget.repo.owner ? `${configTarget.repo.owner}/` : ''}${configTarget.repo.repo}:${configTarget.filepath}${configTarget.ref ? `@${configTarget.ref}` : ''})`, + ) + } + throw new Error( + `Failed to fetch config from repo: ${(error as Error).message}`, + ) + } + + if (Array.isArray(res.data)) { + throw new Error( + `Fetched content is a directory (array), expected a file. (target: ${configTarget.repo.owner ? `${configTarget.repo.owner}/` : ''}${configTarget.repo.repo}:${configTarget.filepath}${configTarget.ref ? `@${configTarget.ref}` : ''})`, + ) + } + + if ( + !res.headers['content-type']?.startsWith('application/vnd.github.v3.raw') + ) { + throw new Error( + `Fetched content has wrong content-type (${res.headers['content-type']}), expected a raw file. (target: ${configTarget.repo.owner ? `${configTarget.repo.owner}/` : ''}${configTarget.repo.repo}:${configTarget.filepath}${configTarget.ref ? `@${configTarget.ref}` : ''})`, + ) + } + + if (typeof res.data !== 'string') { + throw new Error( + `Fetched content is not a string. (target: ${configTarget.repo.owner ? `${configTarget.repo.owner}/` : ''}${configTarget.repo.repo}:${configTarget.filepath}${configTarget.ref ? `@${configTarget.ref}` : ''})`, + ) + } + + return res.data as string // octokit does not type the "mediaType: { format: 'raw' }" path +} diff --git a/src/common/config/get-config-file.ts b/src/common/config/get-config-file.ts new file mode 100644 index 0000000000..7242db938b --- /dev/null +++ b/src/common/config/get-config-file.ts @@ -0,0 +1,76 @@ +import { parse as parseYaml } from 'yaml' +import { prettifyError, ZodError } from 'zod' +import { configFileSchema } from './extends.schema.ts' +import { getConfigFileFromFs } from './get-config-file-from-fs.ts' +import { getConfigFileFromRepo } from './get-config-file-from-repo.ts' +import { normalizeFilepath } from './normalize-filepath.ts' +import { + type ConfigTarget, + describeConfigTarget, +} from './parse-config-target.ts' + +const SUPPORTED_FILE_EXTENSIONS = ['json', 'yml', 'yaml'] + +export const getConfigFile = async ( + configTarget: ConfigTarget, + parentTarget?: ConfigTarget, +) => { + const _configTarget = structuredClone(configTarget) + const fileExtension = ( + _configTarget.filepath.split('.').pop() as string + ).toLowerCase() + + if (!SUPPORTED_FILE_EXTENSIONS.includes(fileExtension)) { + throw new Error( + `Unsupported file extension: .${fileExtension}. Supported extensions are: ${SUPPORTED_FILE_EXTENSIONS.join(', ')}`, + ) + } + + if (parentTarget?.scheme) { + if (parentTarget?.scheme === 'github' && _configTarget.scheme === 'file') { + throw new Error( + `The '_extends' import-chain cannot contain github: to file: scheme transitions. Please change '_extends: ${configTarget.scheme}:${configTarget.filepath}' to use the github: scheme. ex: '_extends: ${parentTarget.repo.owner}/${parentTarget.repo.repo}:${configTarget.filepath}'`, + ) + } + } + + const filepath = normalizeFilepath(_configTarget, parentTarget) + + _configTarget.filepath = filepath + + const loadFromFs = _configTarget.scheme === 'file' + + // Fetch config file + let configRaw: string + if (loadFromFs) { + try { + configRaw = getConfigFileFromFs(_configTarget.filepath) + } catch (error) { + throw new Error(`Local load failed. ${(error as Error).message}`) + } + } else { + try { + configRaw = await getConfigFileFromRepo(_configTarget) + } catch (error) { + throw new Error(`Repo load failed. ${(error as Error).message}`) + } + } + + const rawConfig: unknown = + fileExtension === 'json' ? JSON.parse(configRaw) : parseYaml(configRaw) + + let config: ReturnType + try { + config = configFileSchema.parse(rawConfig) + } catch (error) { + if (error instanceof ZodError) { + throw new Error( + `Invalid config in ${describeConfigTarget(_configTarget)}:\n${prettifyError(error)}`, + { cause: error }, + ) + } + throw error + } + + return { config, fetchedFrom: _configTarget } +} diff --git a/src/common/config/get-config-files.ts b/src/common/config/get-config-files.ts new file mode 100644 index 0000000000..2219e5e480 --- /dev/null +++ b/src/common/config/get-config-files.ts @@ -0,0 +1,142 @@ +import { basename } from 'node:path' +import * as core from '@actions/core' +import { getConfigFile } from './get-config-file.ts' +import { normalizeFilepath } from './normalize-filepath.ts' +import { parseConfigTarget } from './parse-config-target.ts' + +export const getConfigFiles = async ( + configFilename: string, + currentContext: { + repo: { owner: string; repo: string } + ref: string + }, +) => { + core.debug(`getConfigFiles: Starting with filename: ${configFilename}`) + let configTarget = parseConfigTarget(configFilename, currentContext) + core.debug( + `getConfigFiles: Parsed config target - scheme: ${configTarget.scheme}, filepath: ${configTarget.filepath}`, + ) + + const isCurrentRepoGithubScheme = + configTarget.scheme === 'github' && + configTarget.repo.owner === currentContext.repo.owner && + configTarget.repo.repo === currentContext.repo.repo + + // No point falling back to .github if we're already running in it + const canFallBackToOrgRepo = + isCurrentRepoGithubScheme && currentContext.repo.repo !== '.github' + + let requestedRepoConfig: Awaited> + try { + requestedRepoConfig = await getConfigFile(configTarget) + } catch (error) { + if ( + canFallBackToOrgRepo && + error instanceof Error && + error.message.includes('Config file not found') && + configTarget.scheme === 'github' + ) { + core.info( + `Config not found in ${currentContext.repo.owner}/${currentContext.repo.repo}, falling back to ${currentContext.repo.owner}/.github`, + ) + const orgFallbackTarget = { + ...configTarget, + repo: { owner: currentContext.repo.owner, repo: '.github' }, + ref: undefined, + } + requestedRepoConfig = await getConfigFile(orgFallbackTarget) + } else { + throw error + } + } + core.debug( + `getConfigFiles: Fetched initial config from ${requestedRepoConfig.fetchedFrom.scheme}:${requestedRepoConfig.fetchedFrom.filepath}`, + ) + + const files = [requestedRepoConfig] + let lastFetchedFrom = requestedRepoConfig.fetchedFrom + let lastExtends = requestedRepoConfig.config._extends + + // if the configuration has no `_extends` key, we are done here. + if (!lastExtends) { + core.debug( + `getConfigFiles: No _extends found in config, returning single file`, + ) + return files + } + core.debug(`getConfigFiles: Found _extends directive: ${lastExtends.from}`) + + const MAX_EXTENDS_DEPTH = 33 + let extendsDepth = 0 + + do { + extendsDepth++ + core.debug( + `getConfigFiles: Processing _extends depth ${extendsDepth}: ${lastExtends.from}`, + ) + + if (extendsDepth > MAX_EXTENDS_DEPTH) { + const error = `Maximum extends depth (${MAX_EXTENDS_DEPTH}) exceeded. Check for circular dependencies or reduce the chain of extended configurations.` + core.error(`getConfigFiles: ${error}`) + throw new Error(error) + } + + configTarget = parseConfigTarget(lastExtends.from, lastFetchedFrom) + + // Support repo-only _extends (e.g., "org/repo" or "repo") by defaulting + // to the parent config's filename when no filepath is specified. + if (!configTarget.filepath) { + configTarget.filepath = basename(lastFetchedFrom.filepath) + } + + core.debug( + `getConfigFiles: Parsed _extends target - scheme: ${configTarget.scheme}, filepath: ${configTarget.filepath}`, + ) + + // Pre-fetch duplicate check: compute what fetchedFrom will be (same logic as + // getConfigFile) so we can detect loops before making any network request. + // Rules: + // - file: takes priority over github: for the same filepath+repo at any ref + // (local checkout is authoritative regardless of what ref the chain targets) + // - same-scheme comparisons also require a matching ref + const normalizedFilepath = normalizeFilepath(configTarget, lastFetchedFrom) + const preCheckTarget = { ...configTarget, filepath: normalizedFilepath } + const alreadyLoaded = files.find(({ fetchedFrom: loadedFrom }) => { + const sameFilepath = loadedFrom.filepath === preCheckTarget.filepath + const sameRepo = + loadedFrom.repo.owner === preCheckTarget.repo.owner && + loadedFrom.repo.repo === preCheckTarget.repo.repo + const crossScheme = + loadedFrom.scheme === 'file' && preCheckTarget.scheme === 'github' + return ( + sameFilepath && + sameRepo && + (crossScheme || loadedFrom.ref === preCheckTarget.ref) + ) + }) + if (alreadyLoaded) { + core.warning( + `Recursion detected. Ignoring "_extends: ${lastExtends.from}".`, + ) + core.debug(`getConfigFiles: Recursion detected, stopping extends chain`) + return files + } + + const extendRepoConfig = await getConfigFile(configTarget, lastFetchedFrom) + core.debug( + `getConfigFiles: Fetched extended config from ${extendRepoConfig.fetchedFrom.scheme}:${extendRepoConfig.fetchedFrom.filepath}`, + ) + + lastFetchedFrom = extendRepoConfig.fetchedFrom + lastExtends = extendRepoConfig.config._extends + files.push(extendRepoConfig) + core.debug( + `getConfigFiles: Added extended config to chain. Total files: ${files.length}, next _extends: ${lastExtends?.from || 'none'}`, + ) + } while (lastExtends) + core.debug( + `getConfigFiles: Extends chain complete with ${files.length} file(s)`, + ) + + return files +} diff --git a/src/common/config/index.ts b/src/common/config/index.ts new file mode 100644 index 0000000000..f4629d074f --- /dev/null +++ b/src/common/config/index.ts @@ -0,0 +1,45 @@ +import * as core from '@actions/core' +import { getConfigFiles } from './get-config-files.ts' +import { mergeConfigChain } from './merge-config-chain.ts' + +/** + * Loads configuration from one or multiple files and resolves with + * the combined configuration as well as the list of contexts the configuration + * was loaded from + */ +export async function composeConfigGet( + configFilename: string, + currentContext: { + repo: { owner: string; repo: string } + ref: string + }, +) { + core.debug( + `composeConfigGet: Starting config composition with filename: ${configFilename}`, + ) + core.debug( + `composeConfigGet: Current context - repo: ${currentContext.repo.owner}/${currentContext.repo.repo}, ref: ${currentContext.ref}`, + ) + + const configResults = await getConfigFiles(configFilename, currentContext) + core.debug( + `composeConfigGet: Retrieved ${configResults.length} config file(s)`, + ) + + const contexts = configResults.map((c) => c.fetchedFrom).filter(Boolean) + core.debug(`composeConfigGet: Resolved ${contexts.length} context(s)`) + contexts.forEach((ctx, idx) => { + core.debug( + `composeConfigGet: Context[${idx}] - scheme: ${ctx.scheme}, filepath: ${ctx.filepath}${ctx.repo ? `, repo: ${ctx.repo.owner}/${ctx.repo.repo}` : ''}`, + ) + }) + + const result = { + contexts, + config: mergeConfigChain(configResults), + } + core.debug( + `composeConfigGet: Config composition complete with ${Object.keys(result.config).length} keys`, + ) + return result +} diff --git a/src/common/config/merge-config-chain.ts b/src/common/config/merge-config-chain.ts new file mode 100644 index 0000000000..6df002586e --- /dev/null +++ b/src/common/config/merge-config-chain.ts @@ -0,0 +1,82 @@ +import * as core from '@actions/core' +import type { MergeStrategy } from './extends.schema.ts' +import type { getConfigFiles } from './get-config-files.ts' +import { describeConfigTarget } from './parse-config-target.ts' + +const toMergeableList = ( + value: unknown, + strategy: MergeStrategy, + key: string, + description: string, +): unknown[] => { + // YAML parses an empty value (`categories:`) to null; treat it like an + // absent key so merging to/from it works. + if (value === undefined || value === null) return [] + if (!Array.isArray(value)) { + throw new Error( + `Cannot ${strategy} '${key}': ${description} is not a list (got ${typeof value}).`, + ) + } + return value +} + +/** + * Merges an `_extends` chain (ordered leaf-first, as returned by + * `getConfigFiles`) into a single config object. + * + * Keys merge shallowly by default: the extending file's value replaces the + * inherited one. A file can opt into appending or prepending a list key + * to/onto the inherited list via the mapping form of `_extends` + * (`_extends: {from: ..., strategy: {: append|prepend}}`). A file's + * strategy governs only the step where that file itself is merged onto the + * configs it extends; it is not inherited by files extending it. The + * `_extends` key is stripped from the result. + */ +export const mergeConfigChain = ( + configResults: Awaited>, +) => { + const merged: Record = {} + // reverse to base-first so each file merges onto everything it extends + for (const { config, fetchedFrom } of [...configResults].reverse()) { + const { _extends, ...rest } = config + const strategies = _extends?.strategy ?? {} + // A strategy for a key the file does not set is inert (likely a typo). + for (const key of Object.keys(strategies)) { + if (!Object.hasOwn(rest, key)) { + core.warning( + `_extends strategy declares '${key}' in ${describeConfigTarget(fetchedFrom)}, but the file does not set '${key}'; the strategy has no effect.`, + ) + } + } + for (const [key, value] of Object.entries(rest)) { + // Object.hasOwn guards against config keys that collide with + // Object.prototype members (e.g. `toString`, `constructor`). + const declared = Object.hasOwn(strategies, key) + ? strategies[key] + : undefined + const strategy = declared ?? 'override' + if (strategy === 'override') { + merged[key] = value + continue + } + const inherited = toMergeableList( + Object.hasOwn(merged, key) ? merged[key] : undefined, + strategy, + key, + `the value inherited by ${describeConfigTarget(fetchedFrom)}`, + ) + const own = toMergeableList( + value, + strategy, + key, + `the value in ${describeConfigTarget(fetchedFrom)}`, + ) + merged[key] = + strategy === 'append' ? [...inherited, ...own] : [...own, ...inherited] + core.info( + `_extends strategy: ${strategy}ed ${own.length} '${key}' item(s) from ${describeConfigTarget(fetchedFrom)} onto ${inherited.length} inherited item(s)`, + ) + } + } + return merged +} diff --git a/src/common/config/normalize-filepath.ts b/src/common/config/normalize-filepath.ts new file mode 100644 index 0000000000..35063fc2eb --- /dev/null +++ b/src/common/config/normalize-filepath.ts @@ -0,0 +1,55 @@ +import { dirname, isAbsolute, join, normalize } from 'node:path' +import type { ConfigTarget } from './parse-config-target.ts' + +/** + * current path is assumed to be the ".github" folder in your repo + * root path is assumed to be the root of your repo + * @example + * filepath: release-drafter.yml + * output: [repo root]/.github/release-drafter.yml + * @example + * filepath: /src/../configs/release-drafter.yml + * output: [repo root]/configs/release-drafter.yml + * @example + * filepath: ../configs/release-drafter.yml + * output: [repo root]/configs/release-drafter.yml + * @example + * filepath: /src/../configs/release-drafter.yml + * output: [repo root]/configs/release-drafter.yml + * + * When specifying a target using _extends in the same repo & ref, current path is assumed to be + * the dirname of the current (parent) config file, instead of the .github repository. + * This allows files to reference each-other in a more natural way. + */ +export const normalizeFilepath = ( + config: Pick, + parentConfig?: Pick, +): string => { + const _filepath = normalize(config.filepath) + + if (isAbsolute(_filepath)) { + if (_filepath.startsWith('/')) { + // Remove leading slash to make it relative to repo root + return _filepath.slice(1) + } else { + throw new Error(`Encountered malformed absolute path ${_filepath}`) + } + } else { + if ( + parentConfig && + // repo & refs are identical + parentConfig.repo.owner === config.repo.owner && + parentConfig.repo.repo === config.repo.repo && + config.ref === parentConfig.ref + ) { + // Resolve relative to the parent config file's directory + return normalize(join(dirname(parentConfig.filepath), _filepath)) + } else { + // Prepend .github/ unless the path already starts with .github/ + if (_filepath.startsWith('.github/')) { + return _filepath + } + return join('.github', _filepath) + } + } +} diff --git a/src/common/config/parse-config-target.ts b/src/common/config/parse-config-target.ts new file mode 100644 index 0000000000..bcb22ffb68 --- /dev/null +++ b/src/common/config/parse-config-target.ts @@ -0,0 +1,128 @@ +export type ConfigTarget = { + scheme: 'file' | 'github' + repo: { owner: string; repo: string } + ref?: string + filepath: string +} + +export const describeConfigTarget = (target: ConfigTarget) => + `${target.scheme}:${target.filepath}${target.repo ? ` (${target.repo.owner}/${target.repo.repo})` : ''}` + +/** + * Parses a config target string into its components + * @param target - Target string in format `[github:][[owner/]repo]:filepath[@ref]` or `file:filepath` + * @param currentContext - Current runtime context (repo owner, name, and ref) + * @returns Parsed config target with resolved components + */ +export function parseConfigTarget( + target: string, + context: Pick, +): ConfigTarget { + let _target = structuredClone(target).trim() + + const getErr = (m: string) => + new Error( + `invalid format: "${_target}". Expected format [github:][owner/repo:]filepath[@ref] or file:filepath. ${m}`, + ) + + if (_target.includes(' ')) { + throw getErr('Target must not contain spaces.') + } + + // Parse & pop the scheme, default to "github" + const scheme = _target.startsWith('file:') ? 'file' : 'github' + if (_target.startsWith('file:')) _target = _target.slice(5) + if (_target.startsWith('github:')) _target = _target.slice(7) + + // Check if target is a local file path (no repo specifier) + let hasRepoSpecifier = _target.includes(':') + + const hasRefSpecifier = _target.includes('@') + + // Ban github specifiers with file scheme + if (scheme === 'file') { + if (hasRepoSpecifier) + throw getErr('Local file targets cannot have ":" github specifiers.') + if (hasRefSpecifier) + throw getErr('Local file targets cannot have "@" github specifiers.') + } + + // Detect legacy repo-only references without ":" (e.g., "org/repo", + // "org/.github", or "org/repo@ref"). Config filepaths have extensions; + // extensionless targets are repo-only. ".github" is the legacy exception. + if (!hasRepoSpecifier && scheme !== 'file') { + const targetWithoutRef = hasRefSpecifier + ? _target.slice(0, _target.indexOf('@')) + : _target + const repoName = targetWithoutRef.split('/').at(-1) || '' + if (!repoName.includes('.') || repoName === '.github') { + // Treat as repo-only: rewrite to "repo:@ref" or "repo:" so normal parsing handles it + if (hasRefSpecifier) { + _target = `${targetWithoutRef}:${_target.slice(_target.indexOf('@'))}` + } else { + _target = `${_target}:` + } + hasRepoSpecifier = true + } + } + + const parts = _target.split(':').flatMap((part) => part.split('@')) + + let targetRepo: { owner: string; repo: string } + let targetRef: string | undefined + + if (parts.length > 3) throw getErr('":" or "@" was specified more than once.') + + if (hasRepoSpecifier) { + if (parts.length < 2) throw getErr('Missing repo specifier.') + + const repoSpecifier = parts[0] + + const repoParts = repoSpecifier.split('/') + + let targetRepoOwner: string + let targetRepoName: string + + if (!repoParts.length) throw getErr('Missing repo specifier.') + if (repoParts.length > 2) throw getErr('"/" specified more than once.') + if (repoParts.length === 2) { + // ex: cchanche/release-drafter:.github/release-drafter.yml@main + targetRepoOwner = repoParts[0] + targetRepoName = repoParts[1] + } else { + // ex: release-drafter:.github/release-drafter.yml@main + targetRepoName = repoParts[0] + targetRepoOwner = context.repo.owner + } + targetRepo = { owner: targetRepoOwner, repo: targetRepoName } + } else { + // ex: .github/release-drafter.yml@main + targetRepo = context.repo + } + + const isCurrentRepo = + context.repo.owner === targetRepo.owner && + context.repo.repo === targetRepo.repo + + if (hasRefSpecifier) { + if (parts.length < 2) throw getErr('Too short to contain ref specifier.') + + const refSpecifier = parts.at(-1) + + if (!refSpecifier) throw getErr('Missing ref specifier.') + if (!refSpecifier.length) throw getErr('Ref specifier is empty.') + targetRef = refSpecifier + } else { + targetRef = isCurrentRepo ? context.ref : undefined // default branch + } + + const filepathIndex = hasRepoSpecifier ? 1 : 0 + const targetFilepath = parts.at(filepathIndex) || '' + + return { + scheme: scheme, + filepath: targetFilepath, + ref: targetRef, + repo: targetRepo, + } +} diff --git a/src/common/get-octokit.ts b/src/common/get-octokit.ts new file mode 100644 index 0000000000..44b4dc5c4f --- /dev/null +++ b/src/common/get-octokit.ts @@ -0,0 +1,21 @@ +import process from 'node:process' +import * as core from '@actions/core' +import { getOctokit as createOctokit } from '@actions/github' +import { + paginateGraphQL, + type paginateGraphQLInterface, +} from '@octokit/plugin-paginate-graphql' +import { type RetryPlugin, retry } from '@octokit/plugin-retry' + +export const getOctokit = () => { + return createOctokit( + process.env.GITHUB_TOKEN || '', + { + log: { ...core, warn: core.warning }, + }, + paginateGraphQL, + retry, + ) as ReturnType & paginateGraphQLInterface & RetryPlugin +} + +export type Octokit = ReturnType diff --git a/src/common/get-pull-request-changed-files.ts b/src/common/get-pull-request-changed-files.ts new file mode 100644 index 0000000000..99c385a264 --- /dev/null +++ b/src/common/get-pull-request-changed-files.ts @@ -0,0 +1,63 @@ +import type { Octokit } from './get-octokit.ts' +import { getOctokit } from './get-octokit.ts' + +const PULL_REQUEST_FILES_PER_PAGE = 50 + +type PullRequestRef = { + number: number + baseRepository?: { + nameWithOwner?: string | null + } | null +} + +export const getPullRequestChangedFiles = async ( + octokit: Octokit, + params: { + owner: string + repo: string + pull_number: number + }, +) => + // Octokit follows the REST pagination links for us; per_page only controls + // how many files each HTTP request retrieves. + octokit.paginate( + octokit.rest.pulls.listFiles, + { + ...params, + per_page: PULL_REQUEST_FILES_PER_PAGE, + }, + (response) => response.data.map((file) => file.filename), + ) + +export const getPullRequestsChangedFiles = async (params: { + owner: string + repo: string + pullRequests: PullRequestRef[] + octokit?: Octokit +}) => { + const octokit = params.octokit ?? getOctokit() + const changedFileEntries = await Promise.all( + params.pullRequests.map(async (pullRequest) => { + const key = `${pullRequest.baseRepository?.nameWithOwner}#${pullRequest.number}` + + try { + const changedFiles = await getPullRequestChangedFiles(octokit, { + owner: params.owner, + repo: params.repo, + pull_number: pullRequest.number, + }) + + return [key, changedFiles] as const + } catch (error) { + throw new Error( + `Failed to list changed files for pull request #${pullRequest.number}.`, + { + cause: error, + }, + ) + } + }), + ) + + return new Map(changedFileEntries) +} diff --git a/src/common/graphql.ts b/src/common/graphql.ts new file mode 100644 index 0000000000..6a19f750ad --- /dev/null +++ b/src/common/graphql.ts @@ -0,0 +1,27 @@ +import type { graphql } from '@octokit/graphql' +import type { Octokit } from '#src/common/get-octokit.ts' +import type { TypedDocumentString } from '#src/types/github.graphql.generated.ts' + +export const executeGraphql = < + TData, + TVariables extends Record, +>( + client: typeof graphql, + document: TypedDocumentString, + variables: TVariables, +): Promise => client(document.toString(), variables) + +/** + * Execute a generated GraphQL document and merge its paginated connection. + * + * The document must follow the plugin's conventions: a single `$cursor` + * variable and a connection containing `pageInfo` plus `nodes` or `edges`. + */ +export const paginateGraphql = < + TData extends object, + TVariables extends Record, +>( + client: Octokit['graphql'], + document: TypedDocumentString, + variables: TVariables, +): Promise => client.paginate(document.toString(), variables) diff --git a/src/common/graphql/resolve-commitish.gql b/src/common/graphql/resolve-commitish.gql new file mode 100644 index 0000000000..a3d4f64cd0 --- /dev/null +++ b/src/common/graphql/resolve-commitish.gql @@ -0,0 +1,26 @@ +query resolveCommitish($name: String!, $owner: String!, $expression: String!) { + repository(name: $name, owner: $owner) { + object(expression: $expression) { + __typename + oid + } + } +} + +query resolvePullRequestCommitish( + $name: String! + $owner: String! + $number: Int! +) { + repository(name: $name, owner: $owner) { + pullRequest(number: $number) { + headRefOid + mergeCommit { + oid + } + potentialMergeCommit { + oid + } + } + } +} diff --git a/src/common/index.ts b/src/common/index.ts new file mode 100644 index 0000000000..023ff07ab5 --- /dev/null +++ b/src/common/index.ts @@ -0,0 +1,11 @@ +/* node:coverage ignore file -- @preserve */ +export { composeConfigGet } from './config/index.ts' +export { getOctokit } from './get-octokit.ts' +export { + getPullRequestChangedFiles, + getPullRequestsChangedFiles, +} from './get-pull-request-changed-files.ts' +export { executeGraphql, paginateGraphql } from './graphql.ts' +export { parseCommitishForRelease } from './parse-commitish.ts' +export { sharedInputSchema } from './shared-input.schema.ts' +export { stringToRegex } from './string-to-regex.ts' diff --git a/src/common/parse-commitish.ts b/src/common/parse-commitish.ts new file mode 100644 index 0000000000..707e82e418 --- /dev/null +++ b/src/common/parse-commitish.ts @@ -0,0 +1,120 @@ +import * as core from '@actions/core' +import { context } from '@actions/github' +import type { ParsedConfig } from '#src/actions/drafter/config/index.ts' +import { + ResolveCommitishDocument, + ResolvePullRequestCommitishDocument, +} from '#src/types/github.graphql.generated.ts' +import { getOctokit, type Octokit } from './get-octokit.ts' +import { executeGraphql } from './graphql.ts' + +const resolveTagToCommitSha = async (params: { + octokit: Octokit + tagRef: string +}) => { + const { octokit, tagRef } = params + const data = await executeGraphql(octokit.graphql, ResolveCommitishDocument, { + name: context.repo.repo, + owner: context.repo.owner, + expression: `${tagRef}^{commit}`, + }) + const target = data.repository?.object + + if (target?.__typename !== 'Commit') { + throw new Error(`Tag ${tagRef} does not point to a commit`) + } + + return target.oid +} + +const resolvePullRequestToCommitSha = async (params: { + octokit: Octokit + pullRequestNumber: number + refType: 'head' | 'merge' +}) => { + const { octokit, pullRequestNumber, refType } = params + const data = await executeGraphql( + octokit.graphql, + ResolvePullRequestCommitishDocument, + { + name: context.repo.repo, + owner: context.repo.owner, + number: pullRequestNumber, + }, + ) + const pullRequest = data.repository?.pullRequest + const commitSha = + refType === 'head' + ? pullRequest?.headRefOid + : (pullRequest?.potentialMergeCommit?.oid ?? + pullRequest?.mergeCommit?.oid) + + if (!commitSha) { + throw new Error( + `Pull request #${pullRequestNumber} does not have a ${refType} commit`, + ) + } + + return commitSha +} + +/** + * GitHub's Releases API accepts a branch name or commit SHA as + * `target_commitish`. Normalize fully qualified branch refs, resolve fully + * qualified tag and pull request refs to commit SHAs before building the API + * payload. + * + * A tag without the `refs/tags/` prefix cannot be distinguished reliably from + * a branch with the same name, so it is passed through unchanged. + * + * If ref resolution fails, preserve the existing fallback to the repository's + * default branch. + */ +export const parseCommitishForRelease = async ( + commitish: ParsedConfig['commitish'], + octokit?: Octokit, +) => { + if (commitish.startsWith('refs/heads/')) { + return commitish.replace(/^refs\/heads\//, '') + } + + if (commitish.startsWith('refs/tags/')) { + return resolveTagToCommitSha({ + octokit: octokit ?? getOctokit(), + tagRef: commitish, + }).catch(() => { + core.warning( + `${commitish} could not be resolved to a commit SHA, falling back to default branch`, + ) + + return '' + }) + } + + if (commitish.startsWith('refs/pull/')) { + const pullRequestRef = /^refs\/pull\/(\d+)\/(head|merge)$/.exec(commitish) + + if (pullRequestRef) { + const [, pullRequestNumber, refType] = pullRequestRef + + return resolvePullRequestToCommitSha({ + octokit: octokit ?? getOctokit(), + pullRequestNumber: Number(pullRequestNumber), + refType: refType as 'head' | 'merge', + }).catch(() => { + core.warning( + `${commitish} could not be resolved to a commit SHA, falling back to default branch`, + ) + + return '' + }) + } + + core.warning( + `${commitish} is not a supported pull request ref, falling back to default branch`, + ) + return '' + } + + return commitish +} diff --git a/src/common/shared-input.schema.ts b/src/common/shared-input.schema.ts new file mode 100644 index 0000000000..a8bf4b46c4 --- /dev/null +++ b/src/common/shared-input.schema.ts @@ -0,0 +1,34 @@ +import process from 'node:process' +import { boolean, object, string, stringbool } from 'zod' + +/** + * Inputs shared by release-drafter and autolabeler + */ +export const sharedInputSchema = object({ + /** + * Access token used to make requests against the GitHub API. + * + * Defaults to ${{ github.token }}, or the GITHUB_TOKEN environment variable. + */ + token: string() + .min(1) + .default(() => process.env.GITHUB_TOKEN || ''), // use a function to defer evaluation until parse time + /** + * When enabled, no write operations (creating/updating releases or adding + * labels) are performed. Instead, the action logs what it would have done. + */ + 'dry-run': stringbool().or(boolean()).optional(), +}).superRefine((data, ctx) => { + // Inject token into environment variable for use by octokit + if (data.token && !process.env.GITHUB_TOKEN) { + process.env.GITHUB_TOKEN = data.token + } + + if (!process.env.GITHUB_TOKEN) { + ctx.addIssue({ + code: 'custom', + message: "Unable to find a token. Please see input 'token'.", + path: ['token'], + }) + } +}) diff --git a/src/common/string-to-regex.ts b/src/common/string-to-regex.ts new file mode 100644 index 0000000000..bbcc64de54 --- /dev/null +++ b/src/common/string-to-regex.ts @@ -0,0 +1,8 @@ +import regexEscape from 'escape-string-regexp' +import regexParser from 'regex-parser' + +export const stringToRegex = (search: string) => { + return /^\/.+\/[AJUXgimsux]*$/.test(search) + ? regexParser(search) + : new RegExp(regexEscape(search), 'g') +} diff --git a/src/scripts/check-clean.ts b/src/scripts/check-clean.ts new file mode 100644 index 0000000000..ab074cad46 --- /dev/null +++ b/src/scripts/check-clean.ts @@ -0,0 +1,17 @@ +import { execSync } from 'node:child_process' + +const status = execSync('git status --porcelain', { encoding: 'utf-8' }).trim() + +if (status) { + const diff = execSync('git diff --ignore-space-at-eol --text', { + encoding: 'utf-8', + }) + console.error( + '💥 Detected uncommitted or untracked changes after build checks. No diffs are allowed.', + ) + console.error(execSync('git status --short', { encoding: 'utf-8' }).trim()) + if (diff) { + console.error(diff) + } + process.exit(1) +} diff --git a/src/scripts/coverage-summary.ts b/src/scripts/coverage-summary.ts new file mode 100644 index 0000000000..c6735374bb --- /dev/null +++ b/src/scripts/coverage-summary.ts @@ -0,0 +1,58 @@ +import { appendFileSync, readFileSync } from 'node:fs' +import { resolve } from 'node:path' + +type CoverageSummary = { + total?: { + statements?: { pct?: number; total?: number; covered?: number } + branches?: { pct?: number; total?: number; covered?: number } + functions?: { pct?: number; total?: number; covered?: number } + lines?: { pct?: number; total?: number; covered?: number } + } +} + +const coverageSummaryPath = resolve( + import.meta.dirname, + '../..', + 'coverage', + 'coverage-summary.json', +) + +const coverageSummaryContent = readFileSync(coverageSummaryPath, { + encoding: 'utf-8', +}) +const coverageSummary = JSON.parse(coverageSummaryContent) as CoverageSummary + +const total = coverageSummary.total +if (!total?.statements?.pct && total?.statements?.pct !== 0) { + throw new Error('Unable to read coverage data from coverage-summary.json') +} + +const pct = total.statements.pct +const threshold = Number(process.env.COVERAGE_THRESHOLD ?? '90') +const meetsThreshold = pct >= threshold + +// Print coverage percentage for CI to capture +console.log(pct.toFixed(2)) + +// Write GitHub Actions job summary if running in CI +const summaryFile = process.env.GITHUB_STEP_SUMMARY +if (summaryFile) { + const emoji = meetsThreshold ? '🟢' : '🔴' + const status = meetsThreshold ? 'meets' : 'is below' + + const summary = [ + `## ${emoji} Code Coverage: ${pct.toFixed(2)}%`, + '', + `Coverage ${status} the ${threshold.toFixed(0)}% threshold.`, + '', + '| Metric | Coverage | Covered | Total |', + '| --- | --- | --- | --- |', + `| Statements | ${total.statements.pct?.toFixed(2)}% | ${total.statements.covered} | ${total.statements.total} |`, + `| Branches | ${total.branches?.pct?.toFixed(2)}% | ${total.branches?.covered} | ${total.branches?.total} |`, + `| Functions | ${total.functions?.pct?.toFixed(2)}% | ${total.functions?.covered} | ${total.functions?.total} |`, + `| Lines | ${total.lines?.pct?.toFixed(2)}% | ${total.lines?.covered} | ${total.lines?.total} |`, + '', + ].join('\n') + + appendFileSync(summaryFile, summary) +} diff --git a/src/scripts/graphql.codegen-config.ts b/src/scripts/graphql.codegen-config.ts new file mode 100644 index 0000000000..9973556d68 --- /dev/null +++ b/src/scripts/graphql.codegen-config.ts @@ -0,0 +1,36 @@ +import type { CodegenConfig } from '@graphql-codegen/cli' + +/** + * This config is meant to be executed by the codegen CLI tool + * @see package.json#scripts.codegen + */ +const githubConfig: CodegenConfig = { + schema: { + 'https://docs.github.com/public/fpt/schema.docs.graphql': { + headers: { + 'User-Agent': 'graphql-federation-graphql', + }, + }, + }, + documents: 'src/**/*.gql', + config: { + // GitHub's published schema has deprecation mismatches between interfaces + // and their implementations that GraphQL 17 rejects during schema validation. + assumeValid: true, + documentMode: 'string', + enumsAsTypes: true, + useTypeImports: true, + scalars: { + URI: 'string', + DateTime: 'string', + GitObjectID: 'string', + }, + }, + generates: { + 'src/types/github.graphql.generated.ts': { + plugins: ['typescript', 'typescript-operations', 'typed-document-node'], + }, + }, +} + +export default githubConfig diff --git a/src/scripts/json-schema.ts b/src/scripts/json-schema.ts new file mode 100644 index 0000000000..1c51eb2271 --- /dev/null +++ b/src/scripts/json-schema.ts @@ -0,0 +1,68 @@ +import { execFileSync } from 'node:child_process' +import { writeFile } from 'node:fs/promises' +import { resolve } from 'node:path' +import { globalRegistry, object, toJSONSchema } from 'zod' +import { configSchema as autolabelerConfigSchema } from '#src/actions/autolabeler/config/index.ts' +import { + commonConfigSchema, + configSchema as drafterConfigSchema, + exclusiveConfigSchema, +} from '#src/actions/drafter/config/index.ts' +import { extendsDeclarationSchema } from '#src/common/config/extends.schema.ts' + +// `_extends` is normalized by the raw config-file schema and stripped while +// the config chain is composed. Add the same input schema here so editors +// validate the raw YAML users write. +const drafterSchema = toJSONSchema( + object({ + _extends: extendsDeclarationSchema, + ...exclusiveConfigSchema.shape, + ...commonConfigSchema.shape, + }).meta({ ...globalRegistry.get(drafterConfigSchema) }), + { io: 'input' }, +) +const { id: _autolabelerSchemaId, ...autolabelerSchemaMetadata } = + globalRegistry.get(autolabelerConfigSchema) ?? {} +const autolabelerSchema = toJSONSchema( + object({ + _extends: extendsDeclarationSchema, + ...autolabelerConfigSchema.shape, + }).meta(autolabelerSchemaMetadata), + { io: 'input' }, +) + +const drafterFilePath = resolve( + import.meta.dirname, + '../..', + 'drafter', + 'schema.json', +) + +// Also place in root folder for json schema store to keep working +// see https://github.com/SchemaStore/schemastore/pull/895 +const alternateDrafterFilePath = resolve( + import.meta.dirname, + '../..', + 'schema.json', +) + +const autolabelerFilePath = resolve( + import.meta.dirname, + '../..', + 'autolabeler', + 'schema.json', +) + +async function writeFormatted(filePath: string, content: unknown) { + const raw = JSON.stringify(content, null, 2) + const formatted = execFileSync( + 'npx', + ['biome', 'format', '--stdin-file-path', filePath], + { input: raw, encoding: 'utf-8' }, + ) + await writeFile(filePath, formatted, { encoding: 'utf-8', flag: 'w' }) +} + +await writeFormatted(drafterFilePath, drafterSchema) +await writeFormatted(alternateDrafterFilePath, drafterSchema) +await writeFormatted(autolabelerFilePath, autolabelerSchema) diff --git a/src/tests/autolabeler/autolabeler.e2e.test.ts b/src/tests/autolabeler/autolabeler.e2e.test.ts new file mode 100644 index 0000000000..620e84c109 --- /dev/null +++ b/src/tests/autolabeler/autolabeler.e2e.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from 'vitest' +import { runAutolabeler } from '#tests/helpers/index.ts' +import { + mockContext, + mockInput, + mocks, + nockGetPrFiles, +} from '#tests/mocks/index.ts' +import { nockPostPrLabels } from '#tests/mocks/pull_requests.ts' + +describe('autolabeler e2e', async () => { + it('should label the PRs', async () => { + await mockContext('pull_request-synchronize') + mocks.config.mockReturnValue('config-autolabeler') + + const getScope = nockGetPrFiles({ files: 'files' }) + const postScope = nockPostPrLabels({}) + + await runAutolabeler() + + expect(mocks.postPrLabelsBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "labels": [ + "chore", + ], + }, + ] + `) + expect(getScope.isDone()).toBe(true) // should call the mocked endpoints + expect(postScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + describe('dry-run', () => { + it('does not add labels and logs what it would have done', async () => { + await mockContext('pull_request-synchronize') + await mockInput('dry-run', 'true') + mocks.config.mockReturnValue('config-autolabeler') + + // Only a GET scope — no POST scope, so any attempt to add labels + // would trigger an unmatched-request error from nock. + const getScope = nockGetPrFiles({ files: 'files' }) + + await runAutolabeler() + + // No write request should have been made + expect(mocks.postPrLabelsBody).not.toHaveBeenCalled() + + // Dry-run message should have been logged + const infoMessages = mocks.core.info.mock.calls.flat() + expect(infoMessages.some((msg) => msg.includes('[dry-run]'))).toBe(true) + + expect(getScope.isDone()).toBe(true) // GET PR files was still called + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) +}) diff --git a/src/tests/drafter/build-release-payload.test.ts b/src/tests/drafter/build-release-payload.test.ts new file mode 100644 index 0000000000..73e5772fdb --- /dev/null +++ b/src/tests/drafter/build-release-payload.test.ts @@ -0,0 +1,1222 @@ +import { context } from '@actions/github' +import nock from 'nock' +import { beforeEach, describe, expect, it } from 'vitest' +import { parse } from 'yaml' +import { + actionInputSchema, + configSchema, + mergeInputAndConfig, +} from '#src/actions/drafter/config/index.ts' +import { generateChangeLog } from '#src/actions/drafter/lib/build-release-payload/generate-changelog.ts' +import { + generateContributorsSentence, + generateNewContributorsList, +} from '#src/actions/drafter/lib/build-release-payload/generate-contributors-sentence.ts' +import { buildReleasePayload } from '#src/actions/drafter/lib/index.ts' +import { mockContext, mocks as sharedMocks } from '#tests/mocks/index.ts' + +describe('generate changelog', () => { + let config: ReturnType + + beforeEach(async () => { + await mockContext('push') + config = mergeInputAndConfig({ + config: configSchema.parse({ + template: '$CHANGES', + }), + input: actionInputSchema.parse({ + token: 'test', + }), + }) + }) + + it('does not escape titles without setting change-title-escapes', () => { + const changelog = generateChangeLog({ + config, + pullRequests, + }) + + expect(changelog).toMatchInlineSnapshot(` + "* A1 (#1) @ghost + * B2 (#2) @ghost + * Adds missing (#3) @jetersen + * \`#code_block\` (#4) @jetersen + * Fixes #4 (#5) @Happypig375 + * 2*2 should equal to 4*1 (#6) @jetersen + * Rename __confgs\\confg.yml to __configs\\config.yml (#7) @ghost + * Adds @nullable annotations to the 1*1+2*4 test in \`tests.java\` (#0) @Happypig375 + * Bump golang.org/x/crypto from 0.14.0 to 0.17.0 in /examples (#0) [@dependabot[bot]](https://github.com/apps/dependabot)" + `) + }) + + it('escapes titles with \\s correctly', () => { + const changelog = generateChangeLog({ + config: { ...config, 'change-title-escapes': '\\' }, + pullRequests, + }) + + expect(changelog).toMatchInlineSnapshot(` + "* A1 (#1) @ghost + * B2 (#2) @ghost + * Adds missing (#3) @jetersen + * \`#code_block\` (#4) @jetersen + * Fixes #4 (#5) @Happypig375 + * 2*2 should equal to 4*1 (#6) @jetersen + * Rename __confgs\\\\confg.yml to __configs\\\\config.yml (#7) @ghost + * Adds @nullable annotations to the 1*1+2*4 test in \`tests.java\` (#0) @Happypig375 + * Bump golang.org/x/crypto from 0.14.0 to 0.17.0 in /examples (#0) [@dependabot[bot]](https://github.com/apps/dependabot)" + `) + }) + + it('escapes titles with \\<*_& correctly', () => { + const changelog = generateChangeLog({ + config: { ...config, 'change-title-escapes': '\\<*_&' }, + pullRequests, + }) + + expect(changelog).toMatchInlineSnapshot(` + "* A1 (#1) @ghost + * B2 (#2) @ghost + * Adds missing \\ (#3) @jetersen + * \`#code_block\` (#4) @jetersen + * Fixes #4 (#5) @Happypig375 + * 2\\*2 should equal to 4\\*1 (#6) @jetersen + * Rename \\_\\_confgs\\\\confg.yml to \\_\\_configs\\\\config.yml (#7) @ghost + * Adds @nullable annotations to the 1\\*1+2\\*4 test in \`tests.java\` (#0) @Happypig375 + * Bump golang.org/x/crypto from 0.14.0 to 0.17.0 in /examples (#0) [@dependabot[bot]](https://github.com/apps/dependabot)" + `) + }) + + it('escapes titles with @s correctly', () => { + const changelog = generateChangeLog({ + config: { ...config, 'change-title-escapes': '@' }, + pullRequests, + }) + expect(changelog).toMatchInlineSnapshot(` + "* A1 (#1) @ghost + * B2 (#2) @ghost + * Adds missing (#3) @jetersen + * \`#code_block\` (#4) @jetersen + * Fixes #4 (#5) @Happypig375 + * 2*2 should equal to 4*1 (#6) @jetersen + * Rename __confgs\\confg.yml to __configs\\config.yml (#7) @ghost + * Adds @nullable annotations to the 1*1+2*4 test in \`tests.java\` (#0) @Happypig375 + * Bump golang.org/x/crypto from 0.14.0 to 0.17.0 in /examples (#0) [@dependabot[bot]](https://github.com/apps/dependabot)" + `) + }) + + it('escapes titles with @s and #s correctly', () => { + const changelog = generateChangeLog({ + config: { ...config, 'change-title-escapes': '@#' }, + pullRequests, + }) + + expect(changelog).toMatchInlineSnapshot(` + "* A1 (#1) @ghost + * B2 (#2) @ghost + * Adds missing (#3) @jetersen + * \`#code_block\` (#4) @jetersen + * Fixes #4 (#5) @Happypig375 + * 2*2 should equal to 4*1 (#6) @jetersen + * Rename __confgs\\confg.yml to __configs\\config.yml (#7) @ghost + * Adds @nullable annotations to the 1*1+2*4 test in \`tests.java\` (#0) @Happypig375 + * Bump golang.org/x/crypto from 0.14.0 to 0.17.0 in /examples (#0) [@dependabot[bot]](https://github.com/apps/dependabot)" + `) + }) + + it('escapes titles with \\<@*_&`# correctly', () => { + const changelog = generateChangeLog({ + config: { ...config, 'change-title-escapes': '\\<@*_&`#' }, + pullRequests, + }) + + expect(changelog).toMatchInlineSnapshot(` + "* A1 (#1) @ghost + * B2 (#2) @ghost + * Adds missing \\ (#3) @jetersen + * \\\`#code\\_block\\\` (#4) @jetersen + * Fixes #4 (#5) @Happypig375 + * 2\\*2 should equal to 4\\*1 (#6) @jetersen + * Rename \\_\\_confgs\\\\confg.yml to \\_\\_configs\\\\config.yml (#7) @ghost + * Adds @nullable annotations to the 1\\*1+2\\*4 test in \\\`tests.java\\\` (#0) @Happypig375 + * Bump golang.org/x/crypto from 0.14.0 to 0.17.0 in /examples (#0) [@dependabot[bot]](https://github.com/apps/dependabot)" + `) + }) + + it('adds proper details/summary markdown when collapse-after is set and more than 3 PRs', () => { + const categorizedConfig = mergeInputAndConfig({ + config: configSchema.parse({ + template: '$CHANGES', + categories: [ + { title: 'Bugs', 'collapse-after': 3, when: { labels: ['bug'] } }, + ], + }), + input: actionInputSchema.parse({ + token: 'test', + }), + }) + const changelog = generateChangeLog({ + config: categorizedConfig, + pullRequests, + }) + expect(changelog).toMatchInlineSnapshot(` + "* B2 (#2) @ghost + * Rename __confgs\\confg.yml to __configs\\config.yml (#7) @ghost + * Adds @nullable annotations to the 1*1+2*4 test in \`tests.java\` (#0) @Happypig375 + * Bump golang.org/x/crypto from 0.14.0 to 0.17.0 in /examples (#0) [@dependabot[bot]](https://github.com/apps/dependabot) + + ## Bugs + +
+ 5 changes + + * A1 (#1) @ghost + * Adds missing (#3) @jetersen + * \`#code_block\` (#4) @jetersen + * Fixes #4 (#5) @Happypig375 + * 2*2 should equal to 4*1 (#6) @jetersen +
" + `) + }) + + it('adds proper details/summary markdown when collapse-after is set to 0 and has a PR', () => { + const categorizedConfig = mergeInputAndConfig({ + config: configSchema.parse({ + template: '$CHANGES', + categories: [ + { title: 'Bugs', 'collapse-after': 0, when: { labels: ['bug'] } }, + ], + }), + input: actionInputSchema.parse({ + token: 'test', + }), + }) + const changelog = generateChangeLog({ + config: categorizedConfig, + pullRequests: pullRequests.slice(0, 1), + }) + expect(changelog).toMatchInlineSnapshot(` + "## Bugs + +
+ 1 change + + * A1 (#1) @ghost +
" + `) + }) + + it('does not collapse when the category has exactly collapse-after pull requests', () => { + const categorizedConfig = mergeInputAndConfig({ + config: configSchema.parse({ + template: '$CHANGES', + categories: [ + { + title: 'Feature', + 'collapse-after': 2, + when: { labels: ['feature'] }, + }, + ], + }), + input: actionInputSchema.parse({ + token: 'test', + }), + }) + const changelog = generateChangeLog({ + config: categorizedConfig, + pullRequests, + }) + + expect(changelog).not.toContain('
') + expect(changelog).toContain('## Feature') + }) + + it('does not collapse when collapse-after is disabled with -1', () => { + const categorizedConfig = mergeInputAndConfig({ + config: configSchema.parse({ + template: '$CHANGES', + categories: [ + { title: 'Bugs', 'collapse-after': -1, when: { labels: ['bug'] } }, + ], + }), + input: actionInputSchema.parse({ + token: 'test', + }), + }) + const changelog = generateChangeLog({ + config: categorizedConfig, + pullRequests, + }) + + expect(changelog).not.toContain('
') + expect(changelog).toContain('## Bugs') + }) + + it('does not add proper details/summary markdown when collapse-after is set and less than 3 PRs', () => { + const categorizedConfig = mergeInputAndConfig({ + config: configSchema.parse({ + template: '$CHANGES', + categories: [ + { + title: 'Feature', + 'collapse-after': 3, + when: { labels: ['feature'] }, + }, + ], + }), + input: actionInputSchema.parse({ + token: 'test', + }), + }) + const changelog = generateChangeLog({ + config: categorizedConfig, + pullRequests, + }) + + expect(changelog).toMatchInlineSnapshot(` + "* A1 (#1) @ghost + * Adds missing (#3) @jetersen + * \`#code_block\` (#4) @jetersen + * Fixes #4 (#5) @Happypig375 + * 2*2 should equal to 4*1 (#6) @jetersen + * Rename __confgs\\confg.yml to __configs\\config.yml (#7) @ghost + * Bump golang.org/x/crypto from 0.14.0 to 0.17.0 in /examples (#0) [@dependabot[bot]](https://github.com/apps/dependabot) + + ## Feature + + * B2 (#2) @ghost + * Adds @nullable annotations to the 1*1+2*4 test in \`tests.java\` (#0) @Happypig375" + `) + }) + + it('returns no-changes-template when no pull requests are provided', () => { + const changelog = generateChangeLog({ + config, + pullRequests: [], + }) + + expect(changelog).toBe('* No changes') + }) + + it('returns no-changes-template when all pull requests are excluded by exclude-labels', () => { + const excludedConfig = mergeInputAndConfig({ + config: configSchema.parse({ + template: '$CHANGES', + 'exclude-labels': ['bug', 'feature', 'bugfix', 'dependencies'], + }), + input: actionInputSchema.parse({ + token: 'test', + }), + }) + const changelog = generateChangeLog({ + config: excludedConfig, + pullRequests, + }) + + expect(changelog).toBe('* No changes') + }) + + it('returns no-changes-template when no pull requests match include-labels', () => { + const includedConfig = mergeInputAndConfig({ + config: configSchema.parse({ + template: '$CHANGES', + 'include-labels': ['non-existent-label'], + }), + input: actionInputSchema.parse({ + token: 'test', + }), + }) + const changelog = generateChangeLog({ + config: includedConfig, + pullRequests, + }) + + expect(changelog).toBe('* No changes') + }) +}) + +describe('build release payload', () => { + let config: ReturnType + + beforeEach(async () => { + await mockContext('push') + config = mergeInputAndConfig({ + config: configSchema.parse({ + template: '$CHANGES', + }), + input: actionInputSchema.parse({ + token: 'test', + }), + }) + }) + + it('resolves tag refs recursively to commit SHAs', async () => { + const commitSha = '0123456789abcdef0123456789abcdef01234567' + const scope = nock('https://api.github.com') + .post( + '/graphql', + (body) => + body.query.includes('query resolveCommitish') && + body.variables.expression === 'refs/tags/v1.2.3^{commit}', + ) + .reply(200, { + data: { + repository: { + object: { __typename: 'Commit', oid: commitSha }, + }, + }, + }) + + const releasePayload = await buildReleasePayload({ + commits: [], + config: { ...config, commitish: 'refs/tags/v1.2.3' }, + input: actionInputSchema.parse({ token: 'test' }), + lastRelease: undefined, + pullRequests: [], + }) + + expect(releasePayload.targetCommitish).toBe(commitSha) + expect(sharedMocks.core.warning).not.toHaveBeenCalled() + expect(scope.pendingMocks()).toHaveLength(0) + }) + + it('falls back to the default branch when a tag cannot be resolved', async () => { + const scope = nock('https://api.github.com') + .post( + '/graphql', + (body) => + body.query.includes('query resolveCommitish') && + body.variables.expression === 'refs/tags/missing^{commit}', + ) + .reply(200, { data: { repository: { object: null } } }) + + const releasePayload = await buildReleasePayload({ + commits: [], + config: { ...config, commitish: 'refs/tags/missing' }, + input: actionInputSchema.parse({ token: 'test' }), + lastRelease: undefined, + pullRequests: [], + }) + + expect(releasePayload.targetCommitish).toBe('') + expect(sharedMocks.core.warning).toHaveBeenCalledWith( + 'refs/tags/missing could not be resolved to a commit SHA, falling back to default branch', + ) + expect(scope.pendingMocks()).toHaveLength(0) + }) + + it.each([ + { + commitish: 'refs/pull/123/head', + expectedCommitSha: '1111111111111111111111111111111111111111', + pullRequest: { + headRefOid: '1111111111111111111111111111111111111111', + mergeCommit: null, + potentialMergeCommit: null, + }, + }, + { + commitish: 'refs/pull/123/merge', + expectedCommitSha: '2222222222222222222222222222222222222222', + pullRequest: { + headRefOid: '1111111111111111111111111111111111111111', + mergeCommit: null, + potentialMergeCommit: { + oid: '2222222222222222222222222222222222222222', + }, + }, + }, + { + commitish: 'refs/pull/123/merge', + expectedCommitSha: '3333333333333333333333333333333333333333', + pullRequest: { + headRefOid: '1111111111111111111111111111111111111111', + mergeCommit: { oid: '3333333333333333333333333333333333333333' }, + potentialMergeCommit: null, + }, + }, + ])('resolves $commitish to its commit SHA', async ({ + commitish, + expectedCommitSha, + pullRequest, + }) => { + const scope = nock('https://api.github.com') + .post( + '/graphql', + (body) => + body.query.includes('query resolvePullRequestCommitish') && + body.variables.number === 123, + ) + .reply(200, { data: { repository: { pullRequest } } }) + + const releasePayload = await buildReleasePayload({ + commits: [], + config: { ...config, commitish }, + input: actionInputSchema.parse({ token: 'test' }), + lastRelease: undefined, + pullRequests: [], + }) + + expect(releasePayload.targetCommitish).toBe(expectedCommitSha) + expect(sharedMocks.core.warning).not.toHaveBeenCalled() + expect(scope.pendingMocks()).toHaveLength(0) + }) + + it('falls back to the default branch when a pull request ref cannot be resolved', async () => { + const scope = nock('https://api.github.com') + .post( + '/graphql', + (body) => + body.query.includes('query resolvePullRequestCommitish') && + body.variables.number === 123, + ) + .reply(200, { data: { repository: { pullRequest: null } } }) + + const releasePayload = await buildReleasePayload({ + commits: [], + config: { ...config, commitish: 'refs/pull/123/merge' }, + input: actionInputSchema.parse({ token: 'test' }), + lastRelease: undefined, + pullRequests: [], + }) + + expect(releasePayload.targetCommitish).toBe('') + expect(sharedMocks.core.warning).toHaveBeenCalledWith( + 'refs/pull/123/merge could not be resolved to a commit SHA, falling back to default branch', + ) + expect(scope.pendingMocks()).toHaveLength(0) + }) + + it('normalizes fully qualified branch refs', async () => { + const releasePayload = await buildReleasePayload({ + commits: [], + config, + input: actionInputSchema.parse({ token: 'test' }), + lastRelease: undefined, + pullRequests: [], + }) + + expect(releasePayload.targetCommitish).toBe('master') + expect(sharedMocks.core.warning).not.toHaveBeenCalled() + }) + + it.each([ + ['refs/heads/release/next', 'release/next'], + ['main', 'main'], + [ + '0123456789abcdef0123456789abcdef01234567', + '0123456789abcdef0123456789abcdef01234567', + ], + ])('maps commitish %s to %s', async (commitish, expectedTargetCommitish) => { + const releasePayload = await buildReleasePayload({ + commits: [], + config: { ...config, commitish }, + input: actionInputSchema.parse({ token: 'test' }), + lastRelease: undefined, + pullRequests: [], + }) + + expect(releasePayload.targetCommitish).toBe(expectedTargetCommitish) + expect(sharedMocks.core.warning).not.toHaveBeenCalled() + }) +}) + +const pullRequests: Parameters[0]['pullRequests'] = + [ + { + __typename: 'PullRequest', + title: 'A1', + number: 1, + body: 'A1 body', + url: 'https://github.com', + labels: { + __typename: 'LabelConnection', + nodes: [{ __typename: 'Label', name: 'bug' }], + }, + author: { + __typename: 'User', + login: 'ghost', + url: 'https://github.com/ghost', + }, + baseRefName: 'master', + headRefName: 'fix-bug', + isCrossRepository: false, + merged: true, + mergedAt: '2024-01-01T00:00:00Z', + baseRepository: { + __typename: 'Repository', + nameWithOwner: 'toolmantim/release-drafter', + }, + }, + { + __typename: 'PullRequest', + title: 'B2', + number: 2, + body: 'B2 body', + url: 'https://github.com', + labels: { + __typename: 'LabelConnection', + nodes: [{ __typename: 'Label', name: 'feature' }], + }, + author: { + __typename: 'User', + login: 'ghost', + url: 'https://github.com/ghost', + }, + baseRefName: 'master', + headRefName: 'implement-feature', + isCrossRepository: false, + merged: true, + mergedAt: '2024-01-01T00:00:00Z', + baseRepository: { + __typename: 'Repository', + nameWithOwner: 'toolmantim/release-drafter', + }, + }, + { + __typename: 'PullRequest', + title: 'Adds missing ', + number: 3, + body: 'Adds missing body', + url: 'https://github.com', + labels: { + __typename: 'LabelConnection', + nodes: [{ __typename: 'Label', name: 'bug' }], + }, + author: { + __typename: 'User', + login: 'jetersen', + url: 'https://github.com/jetersen', + }, + baseRefName: 'master', + headRefName: 'fix-bug', + isCrossRepository: false, + merged: true, + mergedAt: '2024-01-01T00:00:00Z', + baseRepository: { + __typename: 'Repository', + nameWithOwner: 'toolmantim/release-drafter', + }, + }, + { + __typename: 'PullRequest', + title: '`#code_block`', + number: 4, + body: '`#code block` body', + url: 'https://github.com', + labels: { + __typename: 'LabelConnection', + nodes: [{ __typename: 'Label', name: 'bug' }], + }, + author: { + __typename: 'User', + login: 'jetersen', + url: 'https://github.com/jetersen', + }, + baseRefName: 'master', + headRefName: 'fix-bug', + isCrossRepository: false, + merged: true, + mergedAt: '2024-01-01T00:00:00Z', + baseRepository: { + __typename: 'Repository', + nameWithOwner: 'toolmantim/release-drafter', + }, + }, + { + __typename: 'PullRequest', + title: 'Fixes #4', + number: 5, + body: 'Fixes #4 body', + url: 'https://github.com', + labels: { + __typename: 'LabelConnection', + nodes: [{ __typename: 'Label', name: 'bug' }], + }, + author: { + __typename: 'User', + login: 'Happypig375', + url: 'https://github.com/Happypig375', + }, + baseRefName: 'master', + headRefName: 'fix-bug', + isCrossRepository: false, + merged: true, + mergedAt: '2024-01-01T00:00:00Z', + baseRepository: { + __typename: 'Repository', + nameWithOwner: 'toolmantim/release-drafter', + }, + }, + { + __typename: 'PullRequest', + title: '2*2 should equal to 4*1', + number: 6, + body: '2*2 should equal to 4*1 body', + url: 'https://github.com', + labels: { + __typename: 'LabelConnection', + nodes: [{ __typename: 'Label', name: 'bug' }], + }, + author: { + __typename: 'User', + login: 'jetersen', + url: 'https://github.com/jetersen', + }, + baseRefName: 'master', + headRefName: 'fix-bug', + isCrossRepository: false, + merged: true, + mergedAt: '2024-01-01T00:00:00Z', + baseRepository: { + __typename: 'Repository', + nameWithOwner: 'toolmantim/release-drafter', + }, + }, + { + __typename: 'PullRequest', + title: 'Rename __confgs\\confg.yml to __configs\\config.yml', + number: 7, + body: 'Rename __confg to __config body', + url: 'https://github.com', + labels: { + __typename: 'LabelConnection', + nodes: [{ __typename: 'Label', name: 'bugfix' }], + }, + author: { + __typename: 'User', + login: 'ghost', + url: 'https://github.com/ghost', + }, + baseRefName: 'master', + headRefName: 'fix-bug', + isCrossRepository: false, + merged: true, + mergedAt: '2024-01-01T00:00:00Z', + baseRepository: { + __typename: 'Repository', + nameWithOwner: 'toolmantim/release-drafter', + }, + }, + { + __typename: 'PullRequest', + title: 'Adds @nullable annotations to the 1*1+2*4 test in `tests.java`', + number: 0, + body: 'Adds @nullable annotations to the 1*1+2*4 test in `tests.java`', + url: 'https://github.com', + labels: { + __typename: 'LabelConnection', + nodes: [{ __typename: 'Label', name: 'feature' }], + }, + author: { + __typename: 'User', + login: 'Happypig375', + url: 'https://github.com/Happypig375', + }, + baseRefName: 'master', + headRefName: 'implement-feature', + isCrossRepository: false, + merged: true, + mergedAt: '2024-01-01T00:00:00Z', + baseRepository: { + __typename: 'Repository', + nameWithOwner: 'toolmantim/release-drafter', + }, + }, + { + __typename: 'PullRequest', + title: 'Bump golang.org/x/crypto from 0.14.0 to 0.17.0 in /examples', + number: 0, + body: 'Updates the dependency ....', + url: 'https://github.com', + labels: { + __typename: 'LabelConnection', + nodes: [{ __typename: 'Label', name: 'dependencies' }], + }, + author: { + login: 'dependabot', + // although the RESTful API returns a `type: "Bot"`, GraphQL only allows us to look up based on the `__typename` + __typename: 'Bot', + url: 'https://github.com/apps/dependabot', + }, + baseRefName: 'master', + headRefName: 'dependabot/go_modules/examples/golang.org/x/crypto-0.17.0', + isCrossRepository: false, + merged: true, + mergedAt: '2024-01-01T00:00:00Z', + baseRepository: { + __typename: 'Repository', + nameWithOwner: 'toolmantim/release-drafter', + }, + }, + ] + +describe('generate contributors sentence', () => { + let config: ReturnType + + beforeEach(async () => { + await mockContext('push') + config = mergeInputAndConfig({ + config: configSchema.parse({ template: '$CONTRIBUTORS' }), + input: actionInputSchema.parse({ token: 'test' }), + }) + }) + + const botPullRequest = pullRequests.at(-1) + if (!botPullRequest) throw new Error('Missing bot pull request fixture') + const ghostPullRequest = pullRequests.at(0) + if (!ghostPullRequest) throw new Error('Missing ghost pull request fixture') + const todoPullRequest = pullRequests.at(1) + if (!todoPullRequest) throw new Error('Missing todo pull request fixture') + const userPullRequest = pullRequests.find( + (pullRequest) => pullRequest.author?.login === 'jetersen', + ) + if (!userPullRequest) throw new Error('Missing user pull request fixture') + const botCommit = { + __typename: 'Commit', + id: 'commit-id', + oid: 'commit-oid', + committedDate: '2024-01-01T00:00:00Z', + message: 'Update dependencies', + author: { + __typename: 'GitActor', + name: 'dependabot[bot]', + user: { __typename: 'User', login: 'dependabot[bot]' }, + }, + authors: { + __typename: 'GitActorConnection', + nodes: [ + { + __typename: 'GitActor', + name: 'dependabot[bot]', + user: { __typename: 'User', login: 'dependabot[bot]' }, + }, + ], + }, + associatedPullRequests: { + __typename: 'PullRequestConnection', + nodes: [botPullRequest], + }, + } as Parameters[0]['commits'][number] + const pullRequest = { + ...userPullRequest, + author: { + __typename: 'User' as const, + login: 'octocat', + url: 'https://github.com/octocat', + }, + } + const commit = { + ...botCommit, + authors: { + __typename: 'GitActorConnection' as const, + nodes: [ + { + __typename: 'GitActor' as const, + name: 'The Octocat', + user: { + __typename: 'User' as const, + login: 'octocat', + }, + }, + { + __typename: 'GitActor' as const, + name: 'Joseph Petersen', + user: { __typename: 'User' as const, login: 'jetersen' }, + }, + { + __typename: 'GitActor' as const, + name: 'Clément Chanchevrier', + user: { __typename: 'User' as const, login: 'cchanche' }, + }, + ], + }, + associatedPullRequests: { + __typename: 'PullRequestConnection' as const, + nodes: [pullRequest], + }, + } + const commitWithBotCoAuthor = { + ...commit, + authors: { + ...commit.authors, + nodes: [ + ...commit.authors.nodes, + { + __typename: 'GitActor' as const, + name: 'Automation', + user: { + __typename: 'User' as const, + login: 'github-actions[bot]', + }, + }, + ], + }, + } + + it('normalizes and deduplicates bot contributors before rendering', () => { + expect( + generateContributorsSentence({ + commits: [botCommit], + pullRequests: [userPullRequest, botPullRequest], + config, + }), + ).toBe( + '@jetersen and [@dependabot[bot]](https://github.com/apps/dependabot)', + ) + }) + + it('includes co-authors after the pull request author', () => { + expect( + generateContributorsSentence({ + commits: [commit], + pullRequests: [pullRequest], + config, + }), + ).toBe('@octocat, @cchanche and @jetersen') + + expect( + generateChangeLog({ + commits: [commit], + pullRequests: [pullRequest], + config: { ...config, 'change-template': '$AUTHORS' }, + }), + ).toBe('@octocat, @cchanche, @jetersen') + + expect( + generateChangeLog({ + commits: [commit], + pullRequests: [pullRequest], + config: { + ...config, + 'change-template': '$AUTHORS', + 'change-authors-final-separator': ' and ', + }, + }), + ).toBe('@octocat, @cchanche and @jetersen') + }) + + it('sorts and links commit-only bots after human co-authors', () => { + const params = { + commits: [commitWithBotCoAuthor], + pullRequests: [pullRequest], + config, + } + + expect(generateContributorsSentence(params)).toBe( + '@octocat, @cchanche, @jetersen and [@github-actions[bot]](https://github.com/apps/github-actions)', + ) + + const serverUrl = context.serverUrl + try { + context.serverUrl = 'https://github.example.com/' + expect(generateContributorsSentence(params)).toBe( + '@octocat, @cchanche, @jetersen and [@github-actions[bot]](https://github.example.com/apps/github-actions)', + ) + } finally { + context.serverUrl = serverUrl + } + }) + + it('renders author placeholders in pull request changelogs', () => { + expect( + generateChangeLog({ + commits: [commitWithBotCoAuthor], + pullRequests: [pullRequest], + config: { + ...config, + 'change-template': '$AUTHOR | $AUTHORS', + 'change-author-template': '$AUTHOR: $AUTHOR_MENTION', + }, + }), + ).toBe( + 'octocat | octocat: @octocat, cchanche: @cchanche, jetersen: @jetersen, github-actions[bot]: [@github-actions[bot]](https://github.com/apps/github-actions)', + ) + }) + + it('renders pull request authors as markdown links', () => { + expect( + generateChangeLog({ + pullRequests: [pullRequest], + config: { + ...config, + 'change-template': + '* $TITLE ([#$NUMBER]($URL)) [$AUTHOR]($AUTHOR_URL)', + }, + }), + ).toMatchInlineSnapshot( + `"* Adds missing ([#3](https://github.com)) [octocat](https://github.com/octocat)"`, + ) + }) + + it('renders deleted pull request authors with the author template', () => { + expect( + generateChangeLog({ + pullRequests: [{ ...pullRequest, author: null }], + config: { + ...config, + 'change-template': '$AUTHORS', + 'change-author-template': '$AUTHOR: $AUTHOR_MENTION', + }, + }), + ).toBe('ghost: @ghost') + }) + + it('includes co-authors for a pull request recovered by merge commit', () => { + expect( + generateChangeLog({ + commits: [ + { + ...commit, + associatedPullRequests: { + __typename: 'PullRequestConnection' as const, + nodes: [], + }, + }, + ], + pullRequests: [ + { + ...pullRequest, + mergeCommit: { + __typename: 'Commit' as const, + oid: commit.oid, + }, + }, + ], + config: { ...config, 'change-template': '$AUTHORS' }, + }), + ).toBe('@octocat, @cchanche, @jetersen') + }) + + it('renders the README multiline author configuration as valid YAML', () => { + const readmeConfig = mergeInputAndConfig({ + config: configSchema.parse( + parse(` + template: "$CHANGES" + categories: + - title: bug + when: + label: bug + - title: todo + category-template: "" + change-template: |- + - type: $CATEGORY + message: |- + $TITLE + pull: $NUMBER + authors: + $AUTHORS + change-author-template: "- $AUTHOR" + change-authors-separator: "\\n " + `), + ), + input: actionInputSchema.parse({ token: 'test' }), + }) + + const changelog = generateChangeLog({ + commits: [commit], + pullRequests: [pullRequest, todoPullRequest], + config: readmeConfig, + }) + + expect(parse(changelog)).toEqual([ + { + type: 'bug', + message: 'Adds missing ', + pull: 3, + authors: ['octocat', 'cchanche', 'jetersen'], + }, + { + type: 'todo', + message: 'B2', + pull: 2, + authors: ['ghost'], + }, + ]) + expect(changelog).toMatchInlineSnapshot(` + "- type: bug + message: |- + Adds missing + pull: 3 + authors: + - octocat + - cchanche + - jetersen + + - type: todo + message: |- + B2 + pull: 2 + authors: + - ghost" + `) + }) + + it('sorts users before bots regardless of pull request order', () => { + const renovatePullRequest = { + ...botPullRequest, + number: 10, + author: { + __typename: 'Bot' as const, + login: 'renovate', + url: 'https://github.com/apps/renovate', + }, + } + const cchanchePullRequest = { + ...userPullRequest, + number: 11, + author: { + __typename: 'User' as const, + login: 'cchanche', + url: 'https://github.com/cchanche', + }, + } + + expect( + generateContributorsSentence({ + commits: [], + pullRequests: [ + renovatePullRequest, + userPullRequest, + botPullRequest, + cchanchePullRequest, + ], + config, + }), + ).toBe( + '@cchanche, @jetersen, [@dependabot[bot]](https://github.com/apps/dependabot) and [@renovate[bot]](https://github.com/apps/renovate)', + ) + }) + + it('renders deleted users as @ghost', () => { + expect( + generateContributorsSentence({ + commits: [], + pullRequests: [ghostPullRequest], + config, + }), + ).toBe('@ghost') + }) + + it('renders the GitHub-style new contributors list', () => { + const newContributorPullRequest = { + ...userPullRequest, + number: 42, + author: { + __typename: 'User' as const, + login: 'first-timer', + url: 'https://github.com/first-timer', + }, + } + + expect( + generateNewContributorsList({ + pullRequests: [userPullRequest, newContributorPullRequest], + newContributorLogins: new Set(['first-timer']), + config, + }), + ).toBe('* @first-timer made their first contribution in #42') + }) + + it('renders new contributors with a custom template', () => { + const newContributorPullRequest = { + ...userPullRequest, + number: 42, + url: 'https://github.com/release-drafter/release-drafter/pull/42', + author: { + __typename: 'User' as const, + login: 'first-timer', + url: 'https://github.com/first-timer', + }, + } + + expect( + generateNewContributorsList({ + pullRequests: [newContributorPullRequest], + newContributorLogins: new Set(['first-timer']), + config: { + ...config, + 'new-contributor-template': + '* [$AUTHOR]($AUTHOR_URL) made their first contribution in [#$NUMBER]($URL)', + }, + }), + ).toMatchInlineSnapshot( + `"* [first-timer](https://github.com/first-timer) made their first contribution in [#42](https://github.com/release-drafter/release-drafter/pull/42)"`, + ) + }) + + it('omits a contributor whose actual first pull request is excluded', () => { + const author = { + __typename: 'User' as const, + login: 'first-timer', + url: 'https://github.com/first-timer', + } + const firstPullRequest = { + ...userPullRequest, + number: 41, + mergedAt: '2024-01-01T00:00:00Z', + author, + labels: { + __typename: 'LabelConnection' as const, + nodes: [{ __typename: 'Label' as const, name: 'skip-changelog' }], + }, + } + const laterPullRequest = { + ...userPullRequest, + number: 42, + mergedAt: '2024-01-02T00:00:00Z', + author, + } + const skipConfig = mergeInputAndConfig({ + config: configSchema.parse({ + template: '$NEW_CONTRIBUTORS', + categories: [ + { type: 'pre-exclude', when: { label: 'skip-changelog' } }, + ], + }), + input: actionInputSchema.parse({ token: 'test' }), + }) + + expect( + generateNewContributorsList({ + pullRequests: [laterPullRequest, firstPullRequest], + newContributorLogins: new Set(['first-timer']), + config: skipConfig, + }), + ).toBe('') + }) + + it('excludes contributors whose pull requests are excluded', () => { + const skippedPullRequest = { + ...botPullRequest, + labels: { + __typename: 'LabelConnection' as const, + nodes: [{ __typename: 'Label' as const, name: 'skip-changelog' }], + }, + } + const skippedCommit = { + ...botCommit, + associatedPullRequests: { + __typename: 'PullRequestConnection' as const, + nodes: [skippedPullRequest], + }, + } + const skipConfig = mergeInputAndConfig({ + config: configSchema.parse({ + template: '$CONTRIBUTORS', + categories: [ + { type: 'pre-exclude', when: { label: 'skip-changelog' } }, + ], + }), + input: actionInputSchema.parse({ token: 'test' }), + }) + + expect( + generateContributorsSentence({ + commits: [skippedCommit], + pullRequests: [skippedPullRequest], + config: skipConfig, + }), + ).toBe('No contributors') + }) +}) diff --git a/src/tests/drafter/category-model.test.ts b/src/tests/drafter/category-model.test.ts new file mode 100644 index 0000000000..f4bfdb1f7f --- /dev/null +++ b/src/tests/drafter/category-model.test.ts @@ -0,0 +1,722 @@ +import { describe, expect, it } from 'vitest' +import type * as z from 'zod' +import { + filterPullRequestsByPreCategories, + matchesCategoryCondition, +} from '#src/actions/drafter/common/category-matching.ts' +import { mergeInputAndConfig } from '#src/actions/drafter/config/index.ts' +import { commonConfigSchema } from '#src/actions/drafter/config/schemas/common-config.schema.ts' +import { configSchema } from '#src/actions/drafter/config/schemas/config.schema.ts' +import { categorizePullRequests } from '#src/actions/drafter/lib/build-release-payload/categorize-pull-requests.ts' +import { resolveVersionKeyIncrement } from '#src/actions/drafter/lib/build-release-payload/resolve-version-increment.ts' + +const makeParsedConfig = ( + categories: NonNullable['categories']>, +) => + mergeInputAndConfig({ + config: configSchema.parse({ + template: '$CHANGES', + commitish: 'refs/heads/main', + categories, + }), + input: commonConfigSchema.parse({}), + }) + +type PullRequest = Parameters< + typeof categorizePullRequests +>[0]['pullRequests'][number] + +const makePullRequest = ( + labels: string[], + changedFiles: string[] = [], + title = 'Change', +) => + ({ + title, + labels: { + nodes: labels.map((name) => ({ name })), + }, + changedFiles, + }) as unknown as PullRequest + +describe('category model', () => { + it('supports label and path matching modes in a single condition', () => { + expect( + matchesCategoryCondition( + { + labels: ['bug', 'urgent'], + 'labels-mode': 'all', + paths: ['src/**', 'docs/**'], + 'paths-mode': 'exactly', + }, + makePullRequest(['bug', 'urgent', 'triaged'], ['docs/**', 'src/**']), + ), + ).toBe(true) + + expect( + matchesCategoryCondition( + { + labels: ['bug', 'urgent'], + 'labels-mode': 'all', + paths: ['src/**', 'docs/**'], + 'paths-mode': 'exactly', + }, + makePullRequest(['bug'], ['src/**']), + ), + ).toBe(false) + }) + + it('treats when.label as shorthand for a single labels value', () => { + const labelConfig = makeParsedConfig([ + { + title: 'Bug fixes', + when: { label: 'bug' }, + }, + ]) + const labelsConfig = makeParsedConfig([ + { + title: 'Bug fixes', + when: { labels: ['bug'] }, + }, + ]) + const pullRequests = [makePullRequest(['bug'])] + + const [, labelCategories] = categorizePullRequests({ + pullRequests, + config: labelConfig, + }) + const [, labelsCategories] = categorizePullRequests({ + pullRequests, + config: labelsConfig, + }) + + expect(labelConfig.categories[0]?.when).toEqual( + labelsConfig.categories[0]?.when, + ) + expect(labelCategories[0]?.pullRequests).toEqual( + labelsCategories[0]?.pullRequests, + ) + }) + + it('treats when.path as shorthand for a single paths value', () => { + const pathConfig = makeParsedConfig([ + { + title: 'Source changes', + when: { path: 'src/**' }, + }, + ]) + const pathsConfig = makeParsedConfig([ + { + title: 'Source changes', + when: { paths: ['src/**'] }, + }, + ]) + const pullRequests = [makePullRequest([], ['src/**'])] + + const [, pathCategories] = categorizePullRequests({ + pullRequests, + config: pathConfig, + }) + const [, pathsCategories] = categorizePullRequests({ + pullRequests, + config: pathsConfig, + }) + + expect(pathConfig.categories[0]?.when).toEqual( + pathsConfig.categories[0]?.when, + ) + expect(pathCategories[0]?.pullRequests).toEqual( + pathsCategories[0]?.pullRequests, + ) + }) + + it('uses labels-mode any by default for when.labels', () => { + const config = makeParsedConfig([ + { + title: 'User-facing changes', + when: { labels: ['feature', 'enhancement'] }, + }, + ]) + const condition = config.categories[0]?.when[0] + + expect(condition).toBeDefined() + if (!condition) { + throw new Error('Expected a normalized category condition') + } + + expect( + matchesCategoryCondition(condition, makePullRequest(['feature'])), + ).toBe(true) + expect( + matchesCategoryCondition(condition, makePullRequest(['chore'])), + ).toBe(false) + }) + + it('combines when.label and when.labels before applying labels-mode', () => { + const config = makeParsedConfig([ + { + title: 'Urgent bug fixes', + when: { + label: 'bug', + labels: ['urgent'], + 'labels-mode': 'all', + }, + }, + ]) + const condition = config.categories[0]?.when[0] + + expect(condition).toBeDefined() + if (!condition) { + throw new Error('Expected a normalized category condition') + } + + expect(matchesCategoryCondition(condition, makePullRequest(['bug']))).toBe( + false, + ) + expect( + matchesCategoryCondition(condition, makePullRequest(['bug', 'urgent'])), + ).toBe(true) + }) + + it('combines when.path and when.paths before applying paths-mode', () => { + const config = makeParsedConfig([ + { + title: 'Source and docs changes', + when: { + path: 'src/**', + paths: ['docs/**'], + 'paths-mode': 'all', + }, + }, + ]) + const condition = config.categories[0]?.when[0] + + expect(condition).toBeDefined() + if (!condition) { + throw new Error('Expected a normalized category condition') + } + + expect( + matchesCategoryCondition(condition, makePullRequest([], ['src/**'])), + ).toBe(false) + expect( + matchesCategoryCondition( + condition, + makePullRequest([], ['src/**', 'docs/**']), + ), + ).toBe(true) + }) + + it('matches conventional commit type, scope, and breaking marker from PR titles', () => { + const config = makeParsedConfig([ + { + title: 'Breaking API features', + when: { + conventional: { + type: 'feat', + scope: 'api', + breaking: true, + }, + }, + }, + ]) + const pullRequests = [ + makePullRequest([], [], 'feat(api)!: add v2 endpoint'), + makePullRequest([], [], 'feat(ui)!: add button'), + makePullRequest([], [], 'fix(api)!: patch endpoint'), + ] + + const [uncategorized, categories] = categorizePullRequests({ + pullRequests, + config, + }) + + expect(categories[0]?.pullRequests).toEqual([pullRequests[0]]) + expect(uncategorized).toEqual([pullRequests[1], pullRequests[2]]) + }) + + it('matches conventional breaking notes when the title contains a full commit message', () => { + const config = makeParsedConfig([ + { + title: 'Breaking fixes', + when: { + conventional: { + type: 'fix', + breaking: true, + }, + }, + }, + ]) + const pullRequests = [ + makePullRequest( + [], + [], + // Unrealistic : PR titles dont contain bodies like commit messages do. + // This is simply future-proofing parsing actual commit messages + 'fix(api): cap retries\n\nBREAKING CHANGE: retries are now capped', + ), + makePullRequest([], [], 'fix(api): cap retries'), + ] + + const [uncategorized, categories] = categorizePullRequests({ + pullRequests, + config, + }) + + expect(categories[0]?.pullRequests).toEqual([pullRequests[0]]) + expect(uncategorized).toEqual([pullRequests[1]]) + }) + + it('matches any valid conventional title with conventional true', () => { + const config = makeParsedConfig([ + { + title: 'Conventional', + when: { conventional: true }, + }, + ]) + const pullRequests = [ + makePullRequest([], [], 'feat: add thing'), + makePullRequest([], [], 'plain title'), + ] + + const [uncategorized, categories] = categorizePullRequests({ + pullRequests, + config, + }) + + expect(categories[0]?.pullRequests).toEqual([pullRequests[0]]) + expect(uncategorized).toEqual([pullRequests[1]]) + }) + + it('combines label, path, and conventional predicates in one condition', () => { + const config = makeParsedConfig([ + { + title: 'UI fixes', + when: { + label: 'bug', + paths: ['src/ui/**'], + conventional: { type: 'fix', scope: 'ui' }, + }, + }, + ]) + const pullRequests = [ + makePullRequest(['bug'], ['src/ui/button.ts'], 'fix(ui): repair button'), + makePullRequest(['bug'], ['src/api/index.ts'], 'fix(ui): repair button'), + makePullRequest(['bug'], ['src/ui/button.ts'], 'feat(ui): add button'), + ] + + const [uncategorized, categories] = categorizePullRequests({ + pullRequests, + config, + }) + + expect(categories[0]?.pullRequests).toEqual([pullRequests[0]]) + expect(uncategorized).toEqual([pullRequests[1], pullRequests[2]]) + }) + + it('ignores labels-mode when a when branch does not configure labels', () => { + const config = makeParsedConfig([ + { + title: 'Source changes', + when: { + paths: ['src/**'], + 'labels-mode': 'exactly', + }, + }, + ]) + const condition = config.categories[0]?.when[0] + + expect(condition).toBeDefined() + if (!condition) { + throw new Error('Expected a normalized category condition') + } + + expect( + matchesCategoryCondition(condition, makePullRequest([], ['src/**'])), + ).toBe(true) + expect( + matchesCategoryCondition( + condition, + makePullRequest(['feature', 'bug'], ['src/**']), + ), + ).toBe(true) + }) + + it.each([ + { + mode: 'any' as const, + matchingLabels: ['feature'], + nonMatchingLabels: ['chore'], + }, + { + mode: 'all' as const, + matchingLabels: ['feature', 'enhancement', 'bug'], + nonMatchingLabels: ['feature'], + }, + { + mode: 'only' as const, + matchingLabels: ['feature'], + nonMatchingLabels: ['feature', 'chore'], + }, + { + mode: 'exactly' as const, + matchingLabels: ['feature', 'enhancement'], + nonMatchingLabels: ['feature'], + }, + ])('applies labels-mode $mode within when conditions', ({ + mode, + matchingLabels, + nonMatchingLabels, + }) => { + const config = makeParsedConfig([ + { + title: 'Features', + when: { + labels: ['feature', 'enhancement'], + 'labels-mode': mode, + }, + }, + ]) + const condition = config.categories[0]?.when[0] + + expect(condition).toBeDefined() + if (!condition) { + throw new Error('Expected a normalized category condition') + } + + expect( + matchesCategoryCondition(condition, makePullRequest(matchingLabels)), + ).toBe(true) + expect( + matchesCategoryCondition(condition, makePullRequest(nonMatchingLabels)), + ).toBe(false) + }) + + it.each([ + { + mode: 'any' as const, + matchingPaths: ['src/**'], + nonMatchingPaths: ['tests/**'], + }, + { + mode: 'all' as const, + matchingPaths: ['src/**', 'docs/**', 'tests/**'], + nonMatchingPaths: ['src/**'], + }, + { + mode: 'only' as const, + matchingPaths: ['src/**'], + nonMatchingPaths: ['src/**', 'tests/**'], + }, + { + mode: 'exactly' as const, + matchingPaths: ['src/**', 'docs/**'], + nonMatchingPaths: ['src/**'], + }, + ])('applies paths-mode $mode within when conditions', ({ + mode, + matchingPaths, + nonMatchingPaths, + }) => { + const config = makeParsedConfig([ + { + title: 'Source and docs changes', + when: { + paths: ['src/**', 'docs/**'], + 'paths-mode': mode, + }, + }, + ]) + const condition = config.categories[0]?.when[0] + + expect(condition).toBeDefined() + if (!condition) { + throw new Error('Expected a normalized category condition') + } + + expect( + matchesCategoryCondition(condition, makePullRequest([], matchingPaths)), + ).toBe(true) + expect( + matchesCategoryCondition( + condition, + makePullRequest([], nonMatchingPaths), + ), + ).toBe(false) + }) + + it('does not treat empty labels or paths as a match for mode only', () => { + const labelsOnlyConfig = makeParsedConfig([ + { + title: 'Features', + when: { + labels: ['feature', 'enhancement'], + 'labels-mode': 'only', + }, + }, + ]) + const pathsOnlyConfig = makeParsedConfig([ + { + title: 'Source and docs changes', + when: { + paths: ['src/**', 'docs/**'], + 'paths-mode': 'only', + }, + }, + ]) + + const labelsOnlyCondition = labelsOnlyConfig.categories[0]?.when[0] + const pathsOnlyCondition = pathsOnlyConfig.categories[0]?.when[0] + + expect(labelsOnlyCondition).toBeDefined() + expect(pathsOnlyCondition).toBeDefined() + + if (!labelsOnlyCondition || !pathsOnlyCondition) { + throw new Error('Expected normalized category conditions') + } + + expect( + matchesCategoryCondition(labelsOnlyCondition, makePullRequest([])), + ).toBe(false) + expect( + matchesCategoryCondition(pathsOnlyCondition, makePullRequest([], [])), + ).toBe(false) + }) + + it('uses changed files for paths-mode only and exactly', () => { + const onlyCondition = { + labels: [], + 'labels-mode': 'any' as const, + paths: ['docs/**'], + 'paths-mode': 'only' as const, + } + const exactlyCondition = { + ...onlyCondition, + paths: ['docs/**', 'src/**'], + 'paths-mode': 'exactly' as const, + } + + expect( + matchesCategoryCondition( + onlyCondition, + makePullRequest([], ['docs/readme.md', 'src/index.ts']), + ), + ).toBe(false) + expect( + matchesCategoryCondition( + onlyCondition, + makePullRequest([], ['docs/readme.md', 'docs/index.md']), + ), + ).toBe(true) + expect( + matchesCategoryCondition( + exactlyCondition, + makePullRequest([], ['docs/readme.md', 'src/index.ts']), + ), + ).toBe(true) + }) + + it('applies pre-include and pre-exclude categories before changelog categorization', () => { + const config = makeParsedConfig([ + { + type: 'pre-include', + when: { labels: ['app'] }, + }, + { + type: 'pre-exclude', + when: { paths: ['docs/**'] }, + }, + { + title: 'App changes', + when: { labels: ['app'] }, + }, + ]) + + const pullRequests = [ + makePullRequest(['app'], ['src/**']), + makePullRequest(['app'], ['docs/**']), + makePullRequest(['infra'], ['src/**']), + ] + + expect( + filterPullRequestsByPreCategories(pullRequests, config.categories), + ).toEqual([pullRequests[0]]) + }) + + it('duplicates changelog categories by default and respects exclusive categories', () => { + const duplicateConfig = makeParsedConfig([ + { + title: 'Bugs', + when: { labels: ['bug'] }, + }, + { + title: 'Urgent', + when: { labels: ['bug'] }, + }, + ]) + const exclusiveConfig = makeParsedConfig([ + { + title: 'Bugs', + when: { labels: ['bug'] }, + exclusive: true, + }, + { + title: 'Urgent', + when: { labels: ['bug'] }, + }, + ]) + const pullRequests = [makePullRequest(['bug'])] + + const [, duplicateCategories] = categorizePullRequests({ + pullRequests, + config: duplicateConfig, + }) + const [, exclusiveCategories] = categorizePullRequests({ + pullRequests, + config: exclusiveConfig, + }) + + expect( + duplicateCategories.map((category) => category.pullRequests), + ).toEqual([[pullRequests[0]], [pullRequests[0]]]) + expect( + exclusiveCategories.map((category) => category.pullRequests), + ).toEqual([[pullRequests[0]], []]) + }) + + it('lets exclusive version-resolver categories stop lower-priority matches', () => { + const config = makeParsedConfig([ + { + type: 'version-resolver', + 'semver-increment': 'minor', + when: { labels: ['feature'] }, + exclusive: true, + }, + { + type: 'version-resolver', + 'semver-increment': 'major', + when: { labels: ['breaking'] }, + }, + { + type: 'version-resolver', + 'semver-increment': 'patch', + }, + ]) + + expect( + resolveVersionKeyIncrement({ + pullRequests: [makePullRequest(['feature', 'breaking'])], + config, + }), + ).toBe('minor') + }) + + it('uses changelog category semver increments when resolving version increment', () => { + const config = makeParsedConfig([ + { + title: 'Features', + 'semver-increment': 'minor', + when: { labels: ['feature'] }, + }, + ]) + + expect( + resolveVersionKeyIncrement({ + pullRequests: [makePullRequest(['feature'])], + config, + }), + ).toBe('minor') + }) + + it('respects exclusive changelog categories when resolving version increment', () => { + const config = makeParsedConfig([ + { + title: 'Features', + 'semver-increment': 'minor', + when: { labels: ['feature'] }, + exclusive: true, + }, + { + title: 'Breaking features', + 'semver-increment': 'major', + when: { labels: ['feature'] }, + }, + ]) + + expect( + resolveVersionKeyIncrement({ + pullRequests: [makePullRequest(['feature'])], + config, + }), + ).toBe('minor') + }) + + it('only applies uncategorized changelog semver increments to unmatched changes', () => { + const config = makeParsedConfig([ + { + title: 'Features', + 'semver-increment': 'minor', + when: { labels: ['feature'] }, + }, + { + title: 'Other changes', + 'semver-increment': 'major', + }, + ]) + + expect( + resolveVersionKeyIncrement({ + pullRequests: [makePullRequest(['feature'])], + config, + }), + ).toBe('minor') + + expect( + resolveVersionKeyIncrement({ + pullRequests: [makePullRequest(['docs'])], + config, + }), + ).toBe('major') + }) + + it('scopes exclusive matching independently for changelog and version-resolver categories', () => { + const config = makeParsedConfig([ + { + title: 'Bugs', + when: { labels: ['bug'] }, + exclusive: true, + }, + { + title: 'Urgent', + when: { labels: ['bug'] }, + }, + { + type: 'version-resolver', + 'semver-increment': 'minor', + when: { labels: ['bug'] }, + exclusive: true, + }, + { + type: 'version-resolver', + 'semver-increment': 'major', + when: { labels: ['breaking'] }, + }, + ]) + const pullRequests = [makePullRequest(['bug', 'breaking'])] + + const [, categories] = categorizePullRequests({ + pullRequests, + config, + }) + const versionIncrement = resolveVersionKeyIncrement({ + pullRequests, + config, + }) + + expect(categories.map((category) => category.pullRequests)).toEqual([ + [pullRequests[0]], + [], + ]) + expect(versionIncrement).toBe('minor') + }) +}) diff --git a/src/tests/drafter/config-schema.test.ts b/src/tests/drafter/config-schema.test.ts new file mode 100644 index 0000000000..03f4b525b3 --- /dev/null +++ b/src/tests/drafter/config-schema.test.ts @@ -0,0 +1,335 @@ +import { describe, expect, it } from 'vitest' +import type * as z from 'zod' +import { ZodError } from 'zod' +import { configSchema } from '#src/actions/drafter/config/index.ts' + +type SuiteParams = + | { + parseInput: z.input + parseOutput?: z.output + parseValid: true + } + | { + parseInput: unknown + errorContains: string | string[] + parseValid: false + } + +const template = '$CHANGES' + +const suites: SuiteParams[] = [ + { parseInput: { template }, parseValid: true }, + { + parseInput: { template, replacers: [{ search: '123', replace: '' }] }, + parseValid: true, + }, + { + parseInput: { template, replacers: [{ search: '/123/gi', replace: '' }] }, + parseValid: true, + }, + { + parseInput: { + template, + replacers: [{ search: '/123/gi', replace: '123' }], + }, + parseValid: true, + }, + { parseInput: { template, header: 'I am on top' }, parseValid: true }, + { parseInput: { template, footer: 'I am on bottm' }, parseValid: true }, + { + parseInput: { template, header: 'I am on top', footer: 'I am on bottm' }, + parseValid: true, + }, + { + parseInput: { template, 'pull-request-limit': 49 }, + parseValid: true, + }, + { + parseInput: { template, 'exclude-paths': ['docs/'] }, + parseValid: true, + }, + { parseInput: { template, 'history-limit': 17 }, parseValid: true }, + { + parseInput: { template: true }, + errorContains: 'Invalid input: expected string, received boolean', + parseValid: false, + }, + { + parseInput: { template: 1 }, + errorContains: 'Invalid input: expected string, received number', + parseValid: false, + }, + { + parseInput: { template: ['👶'] }, + errorContains: 'Invalid input: expected string, received array', + parseValid: false, + }, + { + parseInput: { template: { '👶': 'a' } }, + errorContains: 'Invalid input: expected string, received object', + parseValid: false, + }, + { + parseInput: { template: null }, + errorContains: 'Invalid input: expected string, received null', + parseValid: false, + }, + { + parseInput: { template, header: true }, + errorContains: 'Invalid input: expected string, received boolean', + parseValid: false, + }, + { + parseInput: { template, header: 1 }, + errorContains: 'Invalid input: expected string, received number', + parseValid: false, + }, + { + parseInput: { template, header: ['👶'] }, + errorContains: 'Invalid input: expected string, received array', + parseValid: false, + }, + { + parseInput: { template, header: { '👶': 'a' } }, + errorContains: 'Invalid input: expected string, received object', + parseValid: false, + }, + { + parseInput: { template, header: null }, + errorContains: 'Invalid input: expected string, received null', + parseValid: false, + }, + { + parseInput: { template, footer: true }, + errorContains: 'Invalid input: expected string, received boolean', + parseValid: false, + }, + { + parseInput: { template, footer: 1 }, + errorContains: 'Invalid input: expected string, received number', + parseValid: false, + }, + { + parseInput: { template, footer: ['👶'] }, + errorContains: 'Invalid input: expected string, received array', + parseValid: false, + }, + { + parseInput: { template, footer: { '👶': 'a' } }, + errorContains: 'Invalid input: expected string, received object', + parseValid: false, + }, + { + parseInput: { template, footer: null }, + errorContains: 'Invalid input: expected string, received null', + parseValid: false, + }, + { + parseInput: { template, 'category-template': ['## $TITLE'] }, + errorContains: 'Invalid input: expected string, received array', + parseValid: false, + }, + { + parseInput: { template, 'category-template': null }, + errorContains: 'Invalid input: expected string, received null', + parseValid: false, + }, + { + parseInput: { + template, + 'change-template': ['* $TITLE (#$NUMBER) @$AUTHOR'], + }, + errorContains: 'Invalid input: expected string, received array', + parseValid: false, + }, + { + parseInput: { template, 'change-template': null }, + errorContains: 'Invalid input: expected string, received null', + parseValid: false, + }, + { + parseInput: { template, 'change-title-escapes': ['<_*'] }, + errorContains: 'Invalid input: expected string, received array', + parseValid: false, + }, + { + parseInput: { template, 'change-title-escapes': null }, + errorContains: 'Invalid input: expected string, received null', + parseValid: false, + }, + { + parseInput: { template, replacers: [{ search: 123 }] }, + errorContains: [ + 'Invalid input: expected string, received number', + 'Invalid input: expected string, received undefined', + ], + parseValid: false, + }, + { + parseInput: { template, replacers: [{ search: '123' }] }, + errorContains: 'Invalid input: expected string, received undefined', + parseValid: false, + }, + { + parseInput: { template, replacers: [{ replace: 123 }] }, + errorContains: 'Invalid input: expected string, received number', + parseValid: false, + }, + { + parseInput: { template, replacers: [{ search: '123', replace: 123 }] }, + errorContains: 'Invalid input: expected string, received number', + parseValid: false, + }, + { + parseInput: { template, commitish: false }, + errorContains: 'Invalid input: expected string, received boolean', + parseValid: false, + }, + { + parseInput: { template, 'pull-request-limit': 'forty nine' }, + errorContains: 'Invalid input: expected number, received string', + parseValid: false, + }, + { + parseInput: { template, 'history-limit': 'seventeen' }, + errorContains: 'Invalid input: expected number, received string', + parseValid: false, + }, + { + parseInput: { + template, + categories: [ + { + title: '📝 Other Changes', + }, + ], + }, + parseValid: true, + }, + { + parseInput: { + template, + categories: [ + { + title: '📦 Source Changes', + when: { + path: 'src/**', + }, + }, + ], + }, + parseValid: true, + }, + { + parseInput: { + template, + categories: [ + { + title: '🚀 Features', + exclusive: true, + 'collapse-after': 0, + 'semver-increment': 'minor', + when: [ + { + labels: ['feature'], + 'labels-mode': 'all', + paths: ['src/**'], + 'paths-mode': 'any', + }, + ], + }, + { + type: 'version-resolver', + 'semver-increment': 'major', + when: { + conventional: { + type: 'feat', + scope: 'api', + breaking: true, + }, + }, + }, + ], + }, + parseValid: true, + }, + { + parseInput: { + template, + categories: [{ title: 'Conventional', when: { conventional: true } }], + }, + parseValid: true, + }, + { + parseInput: { + template, + categories: [{ title: 'Conventional', when: { conventional: {} } }], + }, + parseValid: true, + }, + { + parseInput: { + template, + categories: [{ title: 'Conventional', when: { conventional: false } }], + }, + errorContains: 'Invalid input', + parseValid: false, + }, + { + parseInput: { + template, + categories: [{ title: 'Conventional', when: { conventional: null } }], + }, + errorContains: 'Invalid input', + parseValid: false, + }, + { + parseInput: { + template, + categories: [{ title: 'Conventional', when: { conventional: 'true' } }], + }, + errorContains: 'Invalid input', + parseValid: false, + }, + { + parseInput: { + template, + categories: [{ title: 'Conventional', when: { conventional: [] } }], + }, + errorContains: 'Invalid input', + parseValid: false, + }, + { + parseInput: { + template, + categories: [ + { + title: '🚀 Features', + 'collapse-after': -2, + }, + ], + }, + errorContains: 'Too small: expected number to be >=-1', + parseValid: false, + }, +] + +describe('schema parsing', () => { + it.each(suites)(`$parseInput`, (params) => { + const { parseInput, parseValid } = params + const output = configSchema.safeParse(parseInput) + expect(output.success).toBe(parseValid) + if (parseValid) { + expect(output.data).toMatchObject(params.parseOutput || parseInput) + } else { + expect(output.error).toBeInstanceOf(ZodError) + if (Array.isArray(params.errorContains)) { + for (const error of params.errorContains) { + expect(output.error?.message).toContain(error) + } + } else { + expect(output.error?.message).toContain(params.errorContains) + } + } + }) +}) diff --git a/src/tests/drafter/config.test.ts b/src/tests/drafter/config.test.ts new file mode 100644 index 0000000000..51ffe1ff81 --- /dev/null +++ b/src/tests/drafter/config.test.ts @@ -0,0 +1,1334 @@ +import * as core from '@actions/core' +import nock from 'nock' +import { describe, expect, it, vi } from 'vitest' +import { composeConfigGet } from '#src/common/config/index.ts' + +const mocks = vi.hoisted(() => ({ + existsSync: vi.fn(), + readFileSync: vi.fn(), +})) + +vi.mock(import('node:fs'), async (iom) => { + const fs = await iom() + return { + ...fs, + existsSync: mocks.existsSync, + readFileSync: mocks.readFileSync, + } +}) + +// disable mocks from setup file +vi.unmock(import('#src/common/config/index.ts')) + +const getContentEndpoint = (params: { + path: string + repo: { owner: string; repo: string } + ref?: string +}) => { + return `/repos/${encodeURIComponent(params.repo.owner)}/${encodeURIComponent( + params.repo.repo, + )}/contents/${encodeURIComponent(params.path)}${params.ref ? `?ref=${encodeURIComponent(params.ref)}` : ''}` +} + +describe('get config file', () => { + describe('with no _extends', () => { + it('should return config content using the github: scheme', async () => { + vi.stubEnv('GITHUB_TOKEN', 'test') + + const inputConfigName = 'release-drafter.yml' + const endpointFilepath = '.github/release-drafter.yml' + const context = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + + const endpoint = getContentEndpoint({ + ...context, + path: endpointFilepath, + }) + const scope = nock('https://api.github.com') + .get(endpoint) + .reply(200, `template: fake-template-content`, { + 'content-type': 'application/vnd.github.v3.raw; charset=utf-8', + }) + + const res = await composeConfigGet(inputConfigName, context) + + expect(mocks.existsSync).not.toHaveBeenCalled() + expect(mocks.readFileSync).not.toHaveBeenCalled() + + expect(scope.isDone()).toBe(true) + + expect(res.contexts.length).toBe(1) + expect(res.config).toMatchInlineSnapshot(` + { + "template": "fake-template-content", + } + `) + expect(res.contexts[0]).toMatchInlineSnapshot(` + { + "filepath": ".github/release-drafter.yml", + "ref": "main", + "repo": { + "owner": "octocat", + "repo": "hello-world", + }, + "scheme": "github", + } + `) + }) + it('should fall back to org .github repo if config not found in current repo', async () => { + vi.stubEnv('GITHUB_TOKEN', 'test') + + const inputConfigName = 'release-drafter.yml' + const context = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + + const currentRepoEndpoint = getContentEndpoint({ + ...context, + path: '.github/release-drafter.yml', + }) + const orgFallbackEndpoint = getContentEndpoint({ + repo: { owner: 'octocat', repo: '.github' }, + path: '.github/release-drafter.yml', + }) + + const scope = nock('https://api.github.com') + .get(currentRepoEndpoint) + .reply(404) + .get(orgFallbackEndpoint) + .reply(200, `template: org-template-content`, { + 'content-type': 'application/vnd.github.v3.raw; charset=utf-8', + }) + + const res = await composeConfigGet(inputConfigName, context) + + expect(scope.isDone()).toBe(true) + + expect(core.info).toHaveBeenCalledWith( + 'Config not found in octocat/hello-world, falling back to octocat/.github', + ) + + expect(res.contexts.length).toBe(1) + expect(res.config).toMatchInlineSnapshot(` + { + "template": "org-template-content", + } + `) + expect(res.contexts[0]).toMatchInlineSnapshot(` + { + "filepath": ".github/release-drafter.yml", + "ref": undefined, + "repo": { + "owner": "octocat", + "repo": ".github", + }, + "scheme": "github", + } + `) + }) + it('should fall back to org .github repo for a non-default config filename', async () => { + vi.stubEnv('GITHUB_TOKEN', 'test') + + const inputConfigName = 'other-release-drafter.yml' + const context = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + + const currentRepoEndpoint = getContentEndpoint({ + ...context, + path: '.github/other-release-drafter.yml', + }) + const orgFallbackEndpoint = getContentEndpoint({ + repo: { owner: 'octocat', repo: '.github' }, + path: '.github/other-release-drafter.yml', + }) + + const scope = nock('https://api.github.com') + .get(currentRepoEndpoint) + .reply(404) + .get(orgFallbackEndpoint) + .reply(200, `template: org-template-content`, { + 'content-type': 'application/vnd.github.v3.raw; charset=utf-8', + }) + + const res = await composeConfigGet(inputConfigName, context) + + expect(scope.isDone()).toBe(true) + + expect(core.info).toHaveBeenCalledWith( + 'Config not found in octocat/hello-world, falling back to octocat/.github', + ) + + expect(res.contexts.length).toBe(1) + expect(res.config).toMatchInlineSnapshot(` + { + "template": "org-template-content", + } + `) + expect(res.contexts[0]).toMatchInlineSnapshot(` + { + "filepath": ".github/other-release-drafter.yml", + "ref": undefined, + "repo": { + "owner": "octocat", + "repo": ".github", + }, + "scheme": "github", + } + `) + }) + it('should error if config not found in current repo and org .github repo', async () => { + vi.stubEnv('GITHUB_TOKEN', 'test') + + const inputConfigName = 'release-drafter.yml' + const context = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + + const currentRepoEndpoint = getContentEndpoint({ + ...context, + path: '.github/release-drafter.yml', + }) + const orgFallbackEndpoint = getContentEndpoint({ + repo: { owner: 'octocat', repo: '.github' }, + path: '.github/release-drafter.yml', + }) + + const scope = nock('https://api.github.com') + .get(currentRepoEndpoint) + .reply(404) + .get(orgFallbackEndpoint) + .reply(404) + + await expect( + composeConfigGet(inputConfigName, context), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Repo load failed. Config file not found with error 404. (target: octocat/.github:.github/release-drafter.yml)]`, + ) + + expect(scope.isDone()).toBe(true) + }) + it('should not fall back to org .github repo when already running in .github', async () => { + vi.stubEnv('GITHUB_TOKEN', 'test') + + const inputConfigName = 'release-drafter.yml' + const context = { + repo: { owner: 'octocat', repo: '.github' }, + ref: 'main', + } + + const endpoint = getContentEndpoint({ + ...context, + path: '.github/release-drafter.yml', + }) + const scope = nock('https://api.github.com').get(endpoint).reply(404) + + await expect( + composeConfigGet(inputConfigName, context), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Repo load failed. Config file not found with error 404. (target: octocat/.github:.github/release-drafter.yml@main)]`, + ) + + // only one request — no fallback attempted + expect(scope.isDone()).toBe(true) + }) + it('should not fall back to org .github repo when using the file: scheme', async () => { + const inputConfigName = 'file:my-config.yml' + const workspace = '/home/runner/workspace' + const context = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + + vi.stubEnv('GITHUB_WORKSPACE', workspace) + mocks.existsSync.mockImplementation((path: string) => path === workspace) // workspace exists but file doesn't + + // set up an interceptor for the .github fallback — it must NOT be called + const orgFallbackEndpoint = getContentEndpoint({ + repo: { owner: 'octocat', repo: '.github' }, + path: '.github/my-config.yml', + }) + const scope = nock('https://api.github.com') + .get(orgFallbackEndpoint) + .reply(200, 'template: should-not-be-reached') + + await expect( + composeConfigGet(inputConfigName, context), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Local load failed. Config file not found: /home/runner/workspace/.github/my-config.yml. Did you clone your sources ? (ex: using @actions/checkout)]`, + ) + + // fallback endpoint was never hit + expect(scope.isDone()).toBe(false) + nock.cleanAll() + }) + it('should not fall back to org .github repo when configTarget is on another repo', async () => { + vi.stubEnv('GITHUB_TOKEN', 'test') + + const inputConfigName = 'other-repo:release-drafter.yml' + const context = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + + const otherRepoEndpoint = getContentEndpoint({ + repo: { owner: 'octocat', repo: 'other-repo' }, + path: '.github/release-drafter.yml', + }) + + const scope = nock('https://api.github.com') + .get(otherRepoEndpoint) + .reply(404) + + await expect( + composeConfigGet(inputConfigName, context), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Repo load failed. Config file not found with error 404. (target: octocat/other-repo:.github/release-drafter.yml)]`, + ) + + // only one request — no fallback attempted + expect(scope.isDone()).toBe(true) + }) + it('should error if config not exists using the github: scheme', async () => { + vi.stubEnv('GITHUB_TOKEN', 'test') + + const inputConfigName = 'release-drafter.yml' + const endpointFilepath = '.github/release-drafter.yml' + const context = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + + const currentRepoEndpoint = getContentEndpoint({ + ...context, + path: endpointFilepath, + }) + const orgFallbackEndpoint = getContentEndpoint({ + repo: { owner: 'octocat', repo: '.github' }, + path: endpointFilepath, + }) + const scope = nock('https://api.github.com') + .get(currentRepoEndpoint) + .reply(404) + .get(orgFallbackEndpoint) + .reply(404) + + await expect( + composeConfigGet(inputConfigName, context), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Repo load failed. Config file not found with error 404. (target: octocat/.github:.github/release-drafter.yml)]`, + ) + + expect(mocks.existsSync).not.toHaveBeenCalled() + expect(mocks.readFileSync).not.toHaveBeenCalled() + + expect(scope.isDone()).toBe(true) + }) + it('should return config content using the file: scheme', async () => { + const inputConfigName = 'file:../configs/release-drafter.yml' + const workspace = '/home/runner/workspace' + const endpointFilepath = `${workspace}/configs/release-drafter.yml` + const context = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + + vi.stubEnv('GITHUB_WORKSPACE', workspace) + mocks.existsSync.mockReturnValue(true) + mocks.readFileSync.mockReturnValue(`template: fake-template-content`) + + const res = await composeConfigGet(inputConfigName, context) + + expect(mocks.existsSync).toHaveBeenCalledTimes(2) + expect(mocks.existsSync).toHaveBeenNthCalledWith(1, workspace) + expect(mocks.existsSync).toHaveBeenNthCalledWith(2, endpointFilepath) + expect(res.contexts.length).toBe(1) + expect(res.contexts[0].scheme).toBe('file') + expect(res.contexts[0].filepath).toMatchInlineSnapshot( + `"configs/release-drafter.yml"`, + ) + expect(res.config).toMatchInlineSnapshot(` + { + "template": "fake-template-content", + } + `) + }) + it('should error if config not exists using the file: scheme', async () => { + const inputConfigName = 'file:../configs/release-drafter.yml' + const workspace = '/home/runner/workspace' + const endpointFilepath = `${workspace}/configs/release-drafter.yml` + const context = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + + vi.stubEnv('GITHUB_WORKSPACE', workspace) + mocks.existsSync.mockImplementation((path: string) => path === workspace) // workspace exists but file doesn't + mocks.readFileSync.mockReturnValue(`template: fake-template-content`) + + await expect( + composeConfigGet(inputConfigName, context), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Local load failed. Config file not found: /home/runner/workspace/configs/release-drafter.yml. Did you clone your sources ? (ex: using @actions/checkout)]`, + ) + + expect(mocks.existsSync).toHaveBeenCalledTimes(2) + expect(mocks.existsSync).toHaveBeenNthCalledWith(1, workspace) + expect(mocks.existsSync).toHaveBeenNthCalledWith(2, endpointFilepath) + }) + }) + describe('with _extends', () => { + describe('using the github: scheme', () => { + it('should aggregate two configs in the same context', async () => { + vi.stubEnv('GITHUB_TOKEN', 'test') + const initialContext = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + const configChain = [ + { + inputConfigName: 'release-drafter.yml', + file: `_extends: common.yml\ntemplate: fake-template-content`, + endpointFilepath: '.github/release-drafter.yml', + endpoint: '', + context: initialContext, + }, + { + file: `template: overridden-content\nsaul: goodman`, + endpointFilepath: '.github/common.yml', + endpoint: '', + context: { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + }, + }, + ].map((c) => ({ + ...c, + endpoint: getContentEndpoint({ + ...c.context, + path: c.endpointFilepath, + }), + })) + + const scope = nock('https://api.github.com') + .get((uri) => configChain.map((c) => c.endpoint).includes(uri)) + .times(configChain.length) + .reply( + 200, + (uri) => configChain.find((c) => c.endpoint === uri)?.file, + { + 'content-type': 'application/vnd.github.v3.raw; charset=utf-8', + }, + ) + + const res = await composeConfigGet( + configChain[0].inputConfigName as string, + initialContext, + ) + + expect(mocks.existsSync).not.toHaveBeenCalled() + expect(mocks.readFileSync).not.toHaveBeenCalled() + + expect(scope.isDone()).toBe(true) + + expect(res.contexts.length).toBe(2) + expect(res.config).toMatchInlineSnapshot(` + { + "saul": "goodman", + "template": "fake-template-content", + } + `) + }) + it('should append inherited list keys when the extending config uses an _extends strategy', async () => { + vi.stubEnv('GITHUB_TOKEN', 'test') + const initialContext = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + const configChain = [ + { + inputConfigName: 'release-drafter.yml', + file: `_extends:\n from: common.yml\n strategy:\n categories: append\ncategories:\n - type: pre-exclude\n when:\n label: skip-changelog`, + endpointFilepath: '.github/release-drafter.yml', + endpoint: '', + context: initialContext, + }, + { + file: `template: fake-template-content\ncategories:\n - title: Features\n when:\n label: feature`, + endpointFilepath: '.github/common.yml', + endpoint: '', + context: { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + }, + }, + ].map((c) => ({ + ...c, + endpoint: getContentEndpoint({ + ...c.context, + path: c.endpointFilepath, + }), + })) + + const scope = nock('https://api.github.com') + .get((uri) => configChain.map((c) => c.endpoint).includes(uri)) + .times(configChain.length) + .reply( + 200, + (uri) => configChain.find((c) => c.endpoint === uri)?.file, + { + 'content-type': 'application/vnd.github.v3.raw; charset=utf-8', + }, + ) + + const res = await composeConfigGet( + configChain[0].inputConfigName as string, + initialContext, + ) + + expect(scope.isDone()).toBe(true) + + // the extending config's categories are appended to the inherited + // list instead of replacing it + expect(res.config.categories).toEqual([ + { title: 'Features', when: { label: 'feature' } }, + { type: 'pre-exclude', when: { label: 'skip-changelog' } }, + ]) + expect(res.config.template).toBe('fake-template-content') + expect(res.config).not.toHaveProperty('_extends') + }) + it('should aggregate two configs when the second is in another repo and uses absolute paths', async () => { + vi.stubEnv('GITHUB_TOKEN', 'test') + const initialContext = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + const configChain = [ + { + inputConfigName: 'release-drafter.yml', + file: `_extends: .github:/configs/common.yml\ntemplate: fake-template-content`, + endpointFilepath: '.github/release-drafter.yml', + endpoint: '', + context: initialContext, + }, + { + file: `template: overridden-content\nsaul: goodman`, + endpointFilepath: 'configs/common.yml', + endpoint: '', + context: { + repo: { owner: 'octocat', repo: '.github' }, + }, + }, + ].map((c) => ({ + ...c, + endpoint: getContentEndpoint({ + ...c.context, + path: c.endpointFilepath, + }), + })) + + const scope = nock('https://api.github.com') + .get((uri) => configChain.map((c) => c.endpoint).includes(uri)) + .times(configChain.length) + .reply( + 200, + (uri) => configChain.find((c) => c.endpoint === uri)?.file, + { + 'content-type': 'application/vnd.github.v3.raw; charset=utf-8', + }, + ) + + const res = await composeConfigGet( + configChain[0].inputConfigName as string, + initialContext, + ) + + expect(mocks.existsSync).not.toHaveBeenCalled() + expect(mocks.readFileSync).not.toHaveBeenCalled() + + expect(scope.isDone()).toBe(true) + + expect(res.contexts.length).toBe(2) + expect(res.config).toMatchInlineSnapshot(` + { + "saul": "goodman", + "template": "fake-template-content", + } + `) + }) + it('should prevent recursions', async () => { + vi.stubEnv('GITHUB_TOKEN', 'test') + const initialContext = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + const configChain = [ + { + inputConfigName: 'release-drafter.yml', + file: `_extends: .github:/configs/common.yml\ntemplate: fake-template-content`, + endpointFilepath: '.github/release-drafter.yml', + endpoint: '', + context: initialContext, + }, + { + file: `_extends: hello-world:release-drafter.yml@main\ntemplate: overridden-content\nsaul: goodman`, + endpointFilepath: 'configs/common.yml', + endpoint: '', + context: { + repo: { owner: 'octocat', repo: '.github' }, + }, + }, + ].map((c) => ({ + ...c, + endpoint: getContentEndpoint({ + ...c.context, + path: c.endpointFilepath, + }), + })) + + const scope = nock('https://api.github.com') + .get((uri) => configChain.map((c) => c.endpoint).includes(uri)) + .times(configChain.length) // no extra call — pre-fetch check stops the loop + .reply( + 200, + (uri) => configChain.find((c) => c.endpoint === uri)?.file, + { + 'content-type': 'application/vnd.github.v3.raw; charset=utf-8', + }, + ) + + const res = await composeConfigGet( + configChain[0].inputConfigName as string, + initialContext, + ) + + expect(mocks.existsSync).not.toHaveBeenCalled() + expect(mocks.readFileSync).not.toHaveBeenCalled() + + expect(core.warning).toHaveBeenCalledWith( + 'Recursion detected. Ignoring "_extends: hello-world:release-drafter.yml@main".', + ) + expect(scope.isDone()).toBe(true) + + expect(res.contexts.length).toBe(2) + expect(res.config).toMatchInlineSnapshot(` + { + "saul": "goodman", + "template": "fake-template-content", + } + `) + }) + it('should prevent recursions triggering the max depth', async () => { + vi.stubEnv('GITHUB_TOKEN', 'test') + const initialContext = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + const configChain = Array.from({ length: 34 }, (_, i) => i) + .map((i) => ({ + inputConfigName: `release-drafter-${i}.yml`, + file: `_extends: ./release-drafter-${i + 1}.yml\ntemplate: fake-template-content\niteration: ${i}`, + endpointFilepath: `.github/release-drafter-${i}.yml`, + endpoint: '', + context: initialContext, + })) + .map((c) => ({ + ...c, + endpoint: getContentEndpoint({ + ...c.context, + path: c.endpointFilepath, + }), + })) + + const scope = nock('https://api.github.com') + .get((uri) => configChain.map((c) => c.endpoint).includes(uri)) + .times(configChain.length) + .reply( + 200, + (uri) => configChain.find((c) => c.endpoint === uri)?.file, + { + 'content-type': 'application/vnd.github.v3.raw; charset=utf-8', + }, + ) + + await expect( + composeConfigGet( + configChain[0].inputConfigName as string, + initialContext, + ), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Maximum extends depth (33) exceeded. Check for circular dependencies or reduce the chain of extended configurations.]`, + ) + + expect(mocks.existsSync).not.toHaveBeenCalled() + expect(mocks.readFileSync).not.toHaveBeenCalled() + + expect(scope.isDone()).toBe(true) + }) + }) + describe('using the file: scheme', () => { + it('should aggregate configs', async () => { + const workspace = '/home/runner/workspace' + vi.stubEnv('GITHUB_WORKSPACE', workspace) + const context = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + const inputConfigName = 'file:release-drafter.yml' + const configChain = [ + { + pathToFile: `${workspace}/.github/release-drafter.yml`, + file: `_extends: file:./common.json\ntemplate: fake-template-content`, + }, + { + pathToFile: `${workspace}/.github/common.json`, + file: `{"template": "overridden-content","saul": "goodman", "_extends": "file:../top-level.yaml"}`, + }, + { + pathToFile: `${workspace}/top-level.yaml`, + file: `better: call`, + }, + ] + + mocks.existsSync.mockReturnValue(true) + mocks.readFileSync.mockImplementation( + (path: string) => + configChain.find((c) => c.pathToFile === path)?.file, + ) + + const res = await composeConfigGet(inputConfigName, context) + + expect(mocks.existsSync).toHaveBeenCalledTimes(configChain.length * 2) + configChain + .flatMap((c) => [workspace, c.pathToFile]) + .forEach((p, i) => { + expect(mocks.existsSync).toHaveBeenNthCalledWith(i + 1, p) + }) + + expect(res.contexts.length).toBe(configChain.length) + expect(res.config).toMatchInlineSnapshot(` + { + "better": "call", + "saul": "goodman", + "template": "fake-template-content", + } + `) + }) + it('should aggregate two configs when the second uses absolute paths', async () => { + const workspace = '/home/runner/workspace' + vi.stubEnv('GITHUB_WORKSPACE', workspace) + const context = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + const inputConfigName = 'file:release-drafter.yml' + const configChain = [ + { + pathToFile: `${workspace}/.github/release-drafter.yml`, + file: `_extends: file:/configs/common.yml\ntemplate: fake-template-content`, + }, + { + pathToFile: `${workspace}/configs/common.yml`, + file: `template: overridden-content\nsaul: goodman`, + }, + ] + + mocks.existsSync.mockReturnValue(true) + mocks.readFileSync.mockImplementation( + (path: string) => + configChain.find((c) => c.pathToFile === path)?.file, + ) + + const res = await composeConfigGet(inputConfigName, context) + + expect(mocks.existsSync).toHaveBeenCalledTimes(configChain.length * 2) + configChain + .flatMap((c) => [workspace, c.pathToFile]) + .forEach((p, i) => { + expect(mocks.existsSync).toHaveBeenNthCalledWith(i + 1, p) + }) + + expect(res.contexts.length).toBe(configChain.length) + expect(res.config).toMatchInlineSnapshot(` + { + "saul": "goodman", + "template": "fake-template-content", + } + `) + }) + }) + describe('using the file: and then the github: scheme', () => { + it('should aggregate configs', async () => { + const workspace = '/home/runner/workspace' + vi.stubEnv('GITHUB_TOKEN', 'test') + vi.stubEnv('GITHUB_WORKSPACE', workspace) + const context = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + const inputConfigName = 'file:release-drafter.yml' + const configChain = [ + { + pathToFile: `${workspace}/.github/release-drafter.yml`, + file: `_extends: file:./common.json\ntemplate: fake-template-content`, + }, + { + pathToFile: `${workspace}/.github/common.json`, + file: `{"template": "overridden-content","saul": "goodman", "_extends": ".github:/top-level.yaml"}`, + }, + { + file: `better: call`, + endpointFilepath: 'top-level.yaml', + endpoint: '', + context: { + repo: { owner: 'octocat', repo: '.github' }, + }, + }, + ].map((c) => ({ + ...c, + endpoint: c.endpointFilepath + ? getContentEndpoint({ + ...c.context, + path: c.endpointFilepath, + }) + : undefined, + })) + + mocks.existsSync.mockReturnValue(true) + mocks.readFileSync.mockImplementation( + (path: string) => + configChain.find((c) => c.pathToFile === path)?.file, + ) + + const scope = nock('https://api.github.com') + .get((uri) => + configChain + .filter((c) => !!c.endpoint) + .map((c) => c.endpoint) + .includes(uri), + ) + .times(configChain.filter((c) => !!c.endpoint).length) + .reply( + 200, + (uri) => + configChain + .filter((c) => !!c.endpoint) + .find((c) => c.endpoint === uri)?.file, + { + 'content-type': 'application/vnd.github.v3.raw; charset=utf-8', + }, + ) + + const res = await composeConfigGet(inputConfigName, context) + + expect(mocks.existsSync).toHaveBeenCalledTimes( + configChain.filter((c) => !!c.pathToFile).length * 2, + ) + configChain + .filter((c) => !!c.pathToFile) + .flatMap((c) => [workspace, c.pathToFile]) + .forEach((p, i) => { + expect(mocks.existsSync).toHaveBeenNthCalledWith(i + 1, p) + }) + + expect(scope.isDone()).toBe(true) + + expect(res.contexts.length).toBe(configChain.length) + expect(res.config).toMatchInlineSnapshot(` + { + "better": "call", + "saul": "goodman", + "template": "fake-template-content", + } + `) + }) + }) + describe('using the file: scheme with circular references', () => { + it('should prevent direct self-recursion in file: scheme', async () => { + const workspace = '/home/runner/workspace' + vi.stubEnv('GITHUB_WORKSPACE', workspace) + const context = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + const inputConfigName = 'file:release-drafter.yml' + const configChain = [ + { + pathToFile: `${workspace}/.github/release-drafter.yml`, + file: `_extends: file:./release-drafter.yml\ntemplate: initial-content`, + }, + ] + + mocks.existsSync.mockReturnValue(true) + mocks.readFileSync.mockImplementation( + (path: string) => + configChain.find((c) => c.pathToFile === path)?.file, + ) + + const res = await composeConfigGet(inputConfigName, context) + + expect(core.warning).toHaveBeenCalledWith( + 'Recursion detected. Ignoring "_extends: file:./release-drafter.yml".', + ) + + expect(res.contexts.length).toBe(1) + expect(res.config).toMatchInlineSnapshot(` + { + "template": "initial-content", + } + `) + }) + + it('should prevent mutual recursion between two local file: configs (A extends B extends A)', async () => { + const workspace = '/home/runner/workspace' + vi.stubEnv('GITHUB_WORKSPACE', workspace) + const context = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + const inputConfigName = 'file:release-drafter.yml' + const configChain = [ + { + pathToFile: `${workspace}/.github/release-drafter.yml`, + file: `_extends: file:./common.yml\ntemplate: initial-content`, + }, + { + pathToFile: `${workspace}/.github/common.yml`, + file: `_extends: file:./release-drafter.yml\nbase: setting`, + }, + ] + + mocks.existsSync.mockReturnValue(true) + mocks.readFileSync.mockImplementation( + (path: string) => + configChain.find((c) => c.pathToFile === path)?.file, + ) + + const res = await composeConfigGet(inputConfigName, context) + + // lastExtends at detection time is file B's _extends pointing back to file A + expect(core.warning).toHaveBeenCalledWith( + 'Recursion detected. Ignoring "_extends: file:./release-drafter.yml".', + ) + + expect(res.contexts.length).toBe(2) + expect(res.config).toMatchInlineSnapshot(` + { + "base": "setting", + "template": "initial-content", + } + `) + }) + }) + describe('using the file: and then the github: scheme with circular references in the github: portion', () => { + it('should prevent a github: config from looping back to itself within a mixed chain', async () => { + const workspace = '/home/runner/workspace' + vi.stubEnv('GITHUB_TOKEN', 'test') + vi.stubEnv('GITHUB_WORKSPACE', workspace) + const context = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + const inputConfigName = 'file:release-drafter.yml' + const configChain = [ + { + pathToFile: `${workspace}/.github/release-drafter.yml`, + file: `_extends: file:./base.yml\ntemplate: initial-content`, + }, + { + pathToFile: `${workspace}/.github/base.yml`, + file: `_extends: .github:/shared.yml\nbase: setting`, + }, + { + // github: octocat/.github shared.yml — points back to itself + file: `_extends: .github:/shared.yml\nshared: value`, + endpointFilepath: 'shared.yml', + endpoint: '', + context: { + repo: { owner: 'octocat', repo: '.github' }, + }, + }, + ].map((c) => ({ + ...c, + endpoint: c.endpointFilepath + ? getContentEndpoint({ + ...c.context, + path: c.endpointFilepath, + }) + : undefined, + })) + + mocks.existsSync.mockReturnValue(true) + mocks.readFileSync.mockImplementation( + (path: string) => + configChain.find((c) => c.pathToFile === path)?.file, + ) + + const scope = nock('https://api.github.com') + .get((uri) => + configChain + .filter((c) => !!c.endpoint) + .map((c) => c.endpoint) + .includes(uri), + ) + .times(configChain.filter((c) => !!c.endpoint).length) // pre-fetch check stops the self-loop + .reply( + 200, + (uri) => + configChain + .filter((c) => !!c.endpoint) + .find((c) => c.endpoint === uri)?.file, + { + 'content-type': 'application/vnd.github.v3.raw; charset=utf-8', + }, + ) + + const res = await composeConfigGet(inputConfigName, context) + + expect(scope.isDone()).toBe(true) + + expect(core.warning).toHaveBeenCalledWith( + 'Recursion detected. Ignoring "_extends: .github:/shared.yml".', + ) + + // 2 file: configs + 1 github: config + expect(res.contexts.length).toBe(3) + expect(res.config).toMatchInlineSnapshot(` + { + "base": "setting", + "shared": "value", + "template": "initial-content", + } + `) + }) + + it('should prevent mutual recursion between two github: configs within a mixed chain (B extends C extends B)', async () => { + const workspace = '/home/runner/workspace' + vi.stubEnv('GITHUB_TOKEN', 'test') + vi.stubEnv('GITHUB_WORKSPACE', workspace) + const context = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + const inputConfigName = 'file:release-drafter.yml' + const configChain = [ + { + pathToFile: `${workspace}/.github/release-drafter.yml`, + file: `_extends: .github:/shared.yml\ntemplate: initial-content`, + }, + { + // github: octocat/.github shared.yml — extends to another github: config + file: `_extends: .github:/base.yml\nshared: value`, + endpointFilepath: 'shared.yml', + endpoint: '', + context: { + repo: { owner: 'octocat', repo: '.github' }, + }, + }, + { + // github: octocat/.github base.yml — loops back to shared.yml + file: `_extends: .github:/shared.yml\nbase: setting`, + endpointFilepath: 'base.yml', + endpoint: '', + context: { + repo: { owner: 'octocat', repo: '.github' }, + }, + }, + ].map((c) => ({ + ...c, + endpoint: c.endpointFilepath + ? getContentEndpoint({ + ...c.context, + path: c.endpointFilepath, + }) + : undefined, + })) + + mocks.existsSync.mockReturnValue(true) + mocks.readFileSync.mockImplementation( + (path: string) => + configChain.find((c) => c.pathToFile === path)?.file, + ) + + const scope = nock('https://api.github.com') + .get((uri) => + configChain + .filter((c) => !!c.endpoint) + .map((c) => c.endpoint) + .includes(uri), + ) + .times(configChain.filter((c) => !!c.endpoint).length) // pre-fetch check stops the loop + .reply( + 200, + (uri) => + configChain + .filter((c) => !!c.endpoint) + .find((c) => c.endpoint === uri)?.file, + { + 'content-type': 'application/vnd.github.v3.raw; charset=utf-8', + }, + ) + + const res = await composeConfigGet(inputConfigName, context) + + expect(scope.isDone()).toBe(true) + + // lastExtends at detection time is github:C's _extends pointing back to github:B + expect(core.warning).toHaveBeenCalledWith( + 'Recursion detected. Ignoring "_extends: .github:/shared.yml".', + ) + + // 1 file: config + 2 github: configs + expect(res.contexts.length).toBe(3) + expect(res.config).toMatchInlineSnapshot(` + { + "base": "setting", + "shared": "value", + "template": "initial-content", + } + `) + }) + + it('should detect cross-scheme loop: file:A → github:B → github:A (same file as A, loaded remotely)', async () => { + // Exact scenario from PR review comment: + // file:A (local) extends github:B (remote) + // github:B extends github:A (same file as A but fetched via github: — different scheme, same filepath+ref+repo) + // Without scheme-agnostic check, github:A would be loaded unnecessarily before the loop is caught + // With the fix (file: takes priority), github:A matches file:A and the chain stops at [file:A, github:B] + const workspace = '/home/runner/workspace' + vi.stubEnv('GITHUB_TOKEN', 'test') + vi.stubEnv('GITHUB_WORKSPACE', workspace) + const context = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + const inputConfigName = 'file:release-drafter.yml' + // file:A is local; github:B is fetched; github:A is caught pre-fetch (no endpoint needed) + const localChain = [ + { + pathToFile: `${workspace}/.github/release-drafter.yml`, + // file:A — extends github:B + file: `_extends: .github:/remote-base.yml\ntemplate: local-content`, + }, + ] + const remoteChain = [ + { + // github:B (octocat/.github:remote-base.yml) — extends the remote version of file:A + file: `_extends: hello-world:release-drafter.yml@main\nremote: base`, + endpointFilepath: 'remote-base.yml', + context: { repo: { owner: 'octocat', repo: '.github' } }, + }, + // github:A would be octocat/hello-world:.github/release-drafter.yml@main — + // same filepath+ref+repo as file:A, different scheme — caught by pre-fetch check + ].map((c) => ({ + ...c, + endpoint: getContentEndpoint({ + ...c.context, + path: c.endpointFilepath, + }), + })) + + mocks.existsSync.mockReturnValue(true) + mocks.readFileSync.mockImplementation( + (path: string) => localChain.find((c) => c.pathToFile === path)?.file, + ) + + const scope = nock('https://api.github.com') + .get((uri) => remoteChain.map((c) => c.endpoint).includes(uri)) + .times(remoteChain.length) + .reply( + 200, + (uri) => remoteChain.find((c) => c.endpoint === uri)?.file, + { + 'content-type': 'application/vnd.github.v3.raw; charset=utf-8', + }, + ) + + const res = await composeConfigGet(inputConfigName, context) + + expect(scope.isDone()).toBe(true) + + // lastExtends at detection is github:B's _extends that points to github:A + expect(core.warning).toHaveBeenCalledWith( + 'Recursion detected. Ignoring "_extends: hello-world:release-drafter.yml@main".', + ) + + // file:A and github:B only — github:A is not added to the chain + expect(res.contexts.length).toBe(2) + expect(res.config).toMatchInlineSnapshot(` + { + "remote": "base", + "template": "local-content", + } + `) + }) + + it('should prefer the local file:A over github:A even when the remote targets a different ref', async () => { + // Variant of the PR comment scenario where the remote chain targets a different ref. + // file:A is loaded locally (ref from context = 'main'). + // github:B extends github:A@v1.0 — same filepath+repo as file:A but a different ref. + // The local file must take priority: ref is ignored when one side is file: scheme. + const workspace = '/home/runner/workspace' + vi.stubEnv('GITHUB_TOKEN', 'test') + vi.stubEnv('GITHUB_WORKSPACE', workspace) + const context = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + const inputConfigName = 'file:release-drafter.yml' + // file:A is local; github:B is fetched; github:A@v1.0 is caught pre-fetch (no endpoint needed) + const localChain = [ + { + pathToFile: `${workspace}/.github/release-drafter.yml`, + file: `_extends: .github:/remote-base.yml\ntemplate: local-content`, + }, + ] + const remoteChain = [ + { + // github:B — extends github:A at a different ref (v1.0 instead of main) + file: `_extends: hello-world:release-drafter.yml@v1.0\nremote: base`, + endpointFilepath: 'remote-base.yml', + context: { repo: { owner: 'octocat', repo: '.github' } }, + }, + // github:A@v1.0 would be octocat/hello-world:.github/release-drafter.yml@v1.0 — + // same filepath+repo as file:A (different ref) — caught by pre-fetch check + ].map((c) => ({ + ...c, + endpoint: getContentEndpoint({ + ...c.context, + path: c.endpointFilepath, + }), + })) + + mocks.existsSync.mockReturnValue(true) + mocks.readFileSync.mockImplementation( + (path: string) => localChain.find((c) => c.pathToFile === path)?.file, + ) + + const scope = nock('https://api.github.com') + .get((uri) => remoteChain.map((c) => c.endpoint).includes(uri)) + .times(remoteChain.length) + .reply( + 200, + (uri) => remoteChain.find((c) => c.endpoint === uri)?.file, + { + 'content-type': 'application/vnd.github.v3.raw; charset=utf-8', + }, + ) + + const res = await composeConfigGet(inputConfigName, context) + + expect(scope.isDone()).toBe(true) + + // lastExtends at detection is github:B's _extends pointing to github:A@v1.0 + expect(core.warning).toHaveBeenCalledWith( + 'Recursion detected. Ignoring "_extends: hello-world:release-drafter.yml@v1.0".', + ) + + // file:A and github:B only — github:A@v1.0 is not added despite the ref mismatch + expect(res.contexts.length).toBe(2) + expect(res.config).toMatchInlineSnapshot(` + { + "remote": "base", + "template": "local-content", + } + `) + }) + }) + describe('using the github: and then the file: scheme', () => { + it('should throw an error', async () => { + const workspace = '/home/runner/workspace' + vi.stubEnv('GITHUB_TOKEN', 'test') + vi.stubEnv('GITHUB_WORKSPACE', workspace) + const context = { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + } + const inputConfigName = 'file:release-drafter.yml' + const configChain = [ + { + pathToFile: `${workspace}/.github/release-drafter.yml`, + file: `_extends: file:./common.json\ntemplate: fake-template-content`, + }, + { + pathToFile: `${workspace}/.github/common.json`, + file: `{"template": "overridden-content","saul": "goodman", "_extends": ".github:/top-level.yaml"}`, + }, + { + file: `better: call\n_extends: this-is-remote.json@v6`, + endpointFilepath: 'top-level.yaml', + endpoint: '', + context: { + repo: { owner: 'octocat', repo: '.github' }, + }, + }, + { + file: `{"_extends": "file:/sould-not-be-imported.yaml", "the": {"cake": {"is": "a lie"}}}`, + endpointFilepath: '.github/this-is-remote.json', + endpoint: '', + context: { + repo: { owner: 'octocat', repo: '.github' }, + ref: 'v6', + }, + }, + ].map((c) => ({ + ...c, + endpoint: c.endpointFilepath + ? getContentEndpoint({ + ...c.context, + path: c.endpointFilepath, + }) + : undefined, + })) + + mocks.existsSync.mockReturnValue(true) + mocks.readFileSync.mockImplementation( + (path: string) => + configChain.find((c) => c.pathToFile === path)?.file, + ) + + const scope = nock('https://api.github.com') + .get((uri) => + configChain + .filter((c) => !!c.endpoint) + .map((c) => c.endpoint) + .includes(uri), + ) + .times(configChain.filter((c) => !!c.endpoint).length) + .reply( + 200, + (uri) => + configChain + .filter((c) => !!c.endpoint) + .find((c) => c.endpoint === uri)?.file, + { + 'content-type': 'application/vnd.github.v3.raw; charset=utf-8', + }, + ) + + await expect( + composeConfigGet(inputConfigName, context), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: The '_extends' import-chain cannot contain github: to file: scheme transitions. Please change '_extends: file:/sould-not-be-imported.yaml' to use the github: scheme. ex: '_extends: octocat/.github:/sould-not-be-imported.yaml']`, + ) + + expect(mocks.existsSync).toHaveBeenCalledTimes( + configChain.filter((c) => !!c.pathToFile).length * 2, + ) + configChain + .filter((c) => !!c.pathToFile) + .flatMap((c) => [workspace, c.pathToFile]) + .forEach((p, i) => { + expect(mocks.existsSync).toHaveBeenNthCalledWith(i + 1, p) + }) + + expect(scope.isDone()).toBe(true) + }) + }) + }) +}) diff --git a/src/tests/drafter/drafter.e2e.test.ts b/src/tests/drafter/drafter.e2e.test.ts new file mode 100644 index 0000000000..def56326de --- /dev/null +++ b/src/tests/drafter/drafter.e2e.test.ts @@ -0,0 +1,3697 @@ +import nock from 'nock' +import { describe, expect, it } from 'vitest' +import { runDrafter } from '#tests/helpers/index.ts' +import { + getGqlPayload, + mockContext, + mockGraphqlQuery, + mockInput, + mocks, + nockGetAndPatchReleases, + nockGetAndPostReleases, + nockGetPrFiles, + nockGetReleases, +} from '#tests/mocks/index.ts' + +describe('drafter e2e', () => { + describe('push', () => { + describe('to a master branch', () => { + it('creates a release draft targeting that branch', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config') + mocks.getContextsConfigWasFetchedFrom.mockReturnValue([ + { + filepath: '.github/release-drafter.yml', + scheme: 'github', + ref: 'master', + repo: { owner: 'toolmantim', repo: 'release-drafter' }, + }, + { + filepath: '.github/release-drafter-base.yml', + scheme: 'github', + ref: undefined, + repo: { owner: 'toolmantim', repo: '.github' }, + }, + ]) + + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-no-prs', + }) + + const scope = nockGetAndPostReleases({ fetchedReleases: ['release'] }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * No changes + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + expect( + mocks.core.info.mock.calls + .flat() + .filter((message) => message.startsWith('Config fetched')), + ).toEqual([ + 'Config fetched from "toolmantim/release-drafter/.github/release-drafter.yml@master".', + 'Config fetched from "toolmantim/.github/.github/release-drafter-base.yml" on the default branch.', + ]) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.pendingMocks().length).toBe(0) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('to a non-master branch', () => { + it('creates a release draft targeting that branch', async () => { + await mockContext('push-non-master-branch') + mocks.config.mockReturnValue('config') + + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-no-prs', + }) + + const scope = nockGetAndPostReleases({ fetchedReleases: ['release'] }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * No changes + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "some-branch", + }, + ] + `) + + expect(scope.pendingMocks().length).toBe(0) // should call the mocked endpoints + expect(gqlScope.pendingMocks().length).toBe(0) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('to a tag', () => { + it('creates a release draft', async () => { + await mockContext('push-tag') + mocks.config.mockReturnValue('config') + + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + + const scope = nockGetAndPostReleases({ fetchedReleases: ['release'] }) + const tagScope = nock('https://api.github.com') + .post( + '/graphql', + (body) => + body.query.includes('query resolveCommitish') && + body.variables.expression === 'refs/tags/v1.0.0^{commit}', + ) + .reply(200, { + data: { + repository: { + object: { + __typename: 'Commit', + oid: '1496a1f82f32f240f7cbe1a42eb0b0c7a06a5093', + }, + }, + }, + }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Add documentation (#5) @TimonVS + * Update dependencies (#4) @TimonVS + * Bug fixes (#3) @TimonVS + * Add big feature (#2) @TimonVS + * 👽 Add alien technology (#1) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "1496a1f82f32f240f7cbe1a42eb0b0c7a06a5093", + }, + ] + `) + + expect(scope.pendingMocks().length).toBe(0) // should call the mocked endpoints + expect(tagScope.pendingMocks().length).toBe(0) + expect(gqlScope.pendingMocks().length).toBe(0) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with no past releases', () => { + it('inserts no comparison baseline warning, and $PREVIOUS_TAG to blank', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-previous-tag') + + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + + const scope = nockGetAndPostReleases({ fetchedReleases: [] }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "Changes: + * No changes + + Previous tag: '' + + --- + > [!WARNING] + > Release Drafter could not find a previous **published release** for \`toolmantim/release-drafter-test-project\`. This draft was created **without a comparison baseline**. + + > [!IMPORTANT] + > Treat this draft as a manual starting point. + > Review the proposed version, tag, and notes before publishing. + + If you did not expect this to happen, [open an issue](https://github.com/release-drafter/release-drafter/issues/new?template=previous-published-release-not-found.yml). + + --- + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(false) // gql not called + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with past releases', () => { + it('creates a new draft listing the changes', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config') + + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release-2', 'release', 'release-3'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Add documentation (#5) @TimonVS + * Update dependencies (#4) @TimonVS + * Bug fixes (#3) @TimonVS + * Add big feature (#2) @TimonVS + * 👽 Add alien technology (#1) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('creates a new draft non-master-branch', async () => { + await mockContext('push-non-master-branch') + mocks.config.mockReturnValue('config') + + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release-2', 'release', 'release-3'], + }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Add documentation (#5) @TimonVS + * Update dependencies (#4) @TimonVS + * Bug fixes (#3) @TimonVS + * Add big feature (#2) @TimonVS + * 👽 Add alien technology (#1) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "some-branch", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('makes next versions available as template placeholders', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-next-versioning') + + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "Placeholder with example. Automatically calculated values are next major=3.0.0 (major=3, minor=0, patch=0), minor=2.1.0 (major=2, minor=1, patch=0), patch=2.0.1 (major=2, minor=0, patch=1)", + "draft": true, + "make_latest": "true", + "name": "v2.0.1 (Code name: Placeholder)", + "prerelease": false, + "tag_name": "v2.0.1", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + describe('with custom changes-template config', () => { + it('creates a new draft using the template', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-changes-templates') + + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "* Change: #5 'Add documentation' @TimonVS + * Change: #4 'Update dependencies' @TimonVS + * Change: #3 'Bug fixes' @TimonVS + * Change: #2 'Add big feature' @TimonVS + * Change: #1 '👽 Add alien technology' @TimonVS", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with custom changes-template config that includes a pull request body', () => { + it('creates a new draft using the template', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-changes-templates-and-body') + + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "* Change: #5 'Add documentation' ✍️ writing docs all day + * Change: #4 'Update dependencies' 📦 Package time! 📦 + * Change: #3 'Bug fixes' 🐛 squashing + * Change: #2 'Add big feature' ![I'm kind of a big deal](https://media.giphy.com/media/9LFBOD8a1Ip2M/giphy.gif) + * Change: #1 '👽 Add alien technology' Space invasion 👾", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with custom changes-template config that includes a pull request URL', () => { + it('creates a new draft using the template', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-changes-templates-and-url') + + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "* Change: https://github.com/toolmantim/release-drafter-test-project/pull/5 'Add documentation' @TimonVS + * Change: https://github.com/toolmantim/release-drafter-test-project/pull/4 'Update dependencies' @TimonVS + * Change: https://github.com/toolmantim/release-drafter-test-project/pull/3 'Bug fixes' @TimonVS + * Change: https://github.com/toolmantim/release-drafter-test-project/pull/2 'Add big feature' @TimonVS + * Change: https://github.com/toolmantim/release-drafter-test-project/pull/1 '👽 Add alien technology' @TimonVS", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with contributors config', () => { + it('adds the contributors', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-contributors') + + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "A big thanks to: @TimonVS", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('uses no-contributors-template when there are no contributors', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-contributors') + + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-empty', + }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "A big thanks to: Nobody", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with exclude-contributors config', () => { + it('excludes matching contributors by username', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-exclude-contributors') + + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "A big thanks to: No contributors", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + }) + + describe('with no changes since the last release', () => { + it('creates a new draft with no changes', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config') + + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-empty', + }) + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release-2', 'release', 'release-3'], + }) + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * No changes + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + describe('with custom no-changes-template config', () => { + it('creates a new draft with the template', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-changes-templates') + + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-empty', + }) + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "* No changes mmkay", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + }) + + describe('with an existing draft release', () => { + it("updates the existing release's body", async () => { + await mockContext('push') + mocks.config.mockReturnValue('config') + + const scope = nockGetAndPatchReleases({ + fetchedReleases: ['release', 'release-draft'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + + await runDrafter() + + expect(mocks.patchReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Add documentation (#5) @TimonVS + * Update dependencies (#4) @TimonVS + * Bug fixes (#3) @TimonVS + * Add big feature (#2) @TimonVS + * 👽 Add alien technology (#1) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "v3.0.0-beta", + "prerelease": false, + "tag_name": "v3.0.0-beta", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with owner and repository templating', () => { + it('include full-changelog link in output', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-compare-link') + + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Add documentation (#5) @TimonVS + * Update dependencies (#4) @TimonVS + + ## 🚀 Features + + * Add big feature (#2) @TimonVS + * 👽 Add alien technology (#1) @TimonVS + + ## 🐛 Bug Fixes + + * Bug fixes (#3) @TimonVS + + **Full Changelog**: https://github.com/toolmantim/release-drafter-test-project/compare/v2.0.0...v2.0.1 + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with categories config', () => { + it('categorizes pull requests with single label', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-categories') + + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Add documentation (#5) @TimonVS + * Update dependencies (#4) @TimonVS + + ## 🚀 Features + + * Add big feature (#2) @TimonVS + * 👽 Add alien technology (#1) @TimonVS + + ## 🐛 Bug Fixes + + * Bug fixes (#3) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('categorizes pull requests with other category at the bottom', async () => { + await mockContext('push') + mocks.config.mockReturnValue( + 'config-with-categories-with-other-category', + ) + + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + ## 🚀 Features + + * Add big feature (#2) @TimonVS + * 👽 Add alien technology (#1) @TimonVS + + ## 🐛 Bug Fixes + + * Bug fixes (#3) @TimonVS + + ## 📝 Other Changes + + * Add documentation (#5) @TimonVS + * Update dependencies (#4) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('categorizes pull requests with multiple labels', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-categories-2') + + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Add documentation (#5) @TimonVS + * Update dependencies (#4) @TimonVS + + ## 🚀 Features + + * Add big feature (#2) @TimonVS + * 👽 Add alien technology (#1) @TimonVS + + ## 🐛 Bug Fixes + + * Bug fixes (#3) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('categorizes pull requests with overlapping labels', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-categories-3') + + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-overlapping-label', + }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Add documentation (#22) @jetersen + * Update dependencies (#21) @jetersen + + ## 🚀 Features + + * Add big feature (#19) @jetersen + * Add alien technology (#18) @jetersen + + ## 🐛 Bug Fixes + + * Bug fixes (#20) @jetersen + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('categorizes pull requests with overlapping labels into multiple categories', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-categories-4') + + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-overlapping-label', + }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Add documentation (#22) @jetersen + * Update dependencies (#21) @jetersen + + ## 🚀 Features + + * Add big feature (#19) @jetersen + * Add alien technology (#18) @jetersen + + ## 🐛 Bug Fixes + + * Bug fixes (#20) @jetersen + + ## 🎖️ Sentry + + * Bug fixes (#20) @jetersen + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('categorizes pull requests with a collapsed category', async () => { + await mockContext('push') + mocks.config.mockReturnValue( + 'config-with-categories-with-collapse-after', + ) + + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Update dependencies (#4) @TimonVS + + ## 🚀 All the things! + +
+ 4 changes + + * Add documentation (#5) @TimonVS + * Bug fixes (#3) @TimonVS + * Add big feature (#2) @TimonVS + * 👽 Add alien technology (#1) @TimonVS +
+ ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with include-pre-releases true config', () => { + it('includes pre releases', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-include-pre-releases-true') + + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release-2', 'pre-release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Add documentation (#5) @TimonVS + * Update dependencies (#4) @TimonVS + * Bug fixes (#3) @TimonVS + * Add big feature (#2) @TimonVS + * 👽 Add alien technology (#1) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "v1.5.0", + "prerelease": false, + "tag_name": "v1.5.0", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with include-pre-releases input override', () => { + it('includes pre releases when the input is true', async () => { + await mockContext('push') + await mockInput('include-pre-releases', 'true') + mocks.config.mockReturnValue('config-with-include-pre-releases-false') + + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release-2', 'pre-release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Add documentation (#5) @TimonVS + * Update dependencies (#4) @TimonVS + * Bug fixes (#3) @TimonVS + * Add big feature (#2) @TimonVS + * 👽 Add alien technology (#1) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "v1.5.0", + "prerelease": false, + "tag_name": "v1.5.0", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('does not include pre releases when the input is false', async () => { + await mockContext('push') + await mockInput('include-pre-releases', 'false') + mocks.config.mockReturnValue('config-with-include-pre-releases-true') + + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release-2', 'pre-release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + + await runDrafter() + + const lastCallBody = mocks.postReleaseBody.mock.lastCall?.at( + 0, + ) as unknown as { name: string; tag_name: string } | undefined + expect(lastCallBody?.name).not.toBe('v1.5.0') + expect(lastCallBody?.tag_name).not.toBe('v1.5.0') + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with exclude-labels config', () => { + it('excludes pull requests', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-exclude-labels') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Update dependencies (#4) @TimonVS + + ## 🚀 Features + + * Add big feature (#2) @TimonVS + * 👽 Add alien technology (#1) @TimonVS + + ## 🐛 Bug Fixes + + * Bug fixes (#3) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with include-labels config', () => { + it('includes pull requests', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-include-labels') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + ## 🚀 Features + + * Add big feature (#2) @TimonVS + * 👽 Add alien technology (#1) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with version-template config', () => { + it('generates next version variables as major.minor.patch', async () => { + await mockContext('push') + mocks.config.mockReturnValue( + 'config-with-major-minor-patch-version-template', + ) + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "Placeholder with example. Automatically calculated values are next major=3.0.0 (major=3, minor=0, patch=0), minor=2.1.0 (major=2, minor=1, patch=0), patch=2.0.1 (major=2, minor=0, patch=1)", + "draft": true, + "make_latest": "true", + "name": "v2.0.1 (Code name: Placeholder)", + "prerelease": false, + "tag_name": "v2.0.1", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('generates next version variables as major.minor', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-major-minor-version-template') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "Placeholder with example. Automatically calculated values are next major=3.0 (major=3, minor=0, patch=0), minor=2.1 (major=2, minor=1, patch=0), patch=2.0 (major=2, minor=0, patch=1)", + "draft": true, + "make_latest": "true", + "name": "v2.1 (Code name: Placeholder)", + "prerelease": false, + "tag_name": "v2.1", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('generates next version variables as major', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-major-version-template') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "Placeholder with example. Automatically calculated values are next major=3 (major=3, minor=0, patch=0), minor=2 (major=2, minor=1, patch=0), patch=2 (major=2, minor=0, patch=1)", + "draft": true, + "make_latest": "true", + "name": "v3 (Code name: Placeholder)", + "prerelease": false, + "tag_name": "v3", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + describe('component helper variables', () => { + it('with default version template', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-component-helpers-default') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "Component helpers with default template behavior: + - MAJOR: 3.0.0 + - MAJOR_MAJOR: 3 + - MAJOR_MINOR: 0 + - MAJOR_PATCH: 0 + + - MINOR: 2.1.0 + - MINOR_MAJOR: 2 + - MINOR_MINOR: 1 + - MINOR_PATCH: 0 + + - PATCH: 2.0.1 + - PATCH_MAJOR: 2 + - PATCH_MINOR: 0 + - PATCH_PATCH: 1 + + - PRERELEASE: 2.0.1-0 + - PRERELEASE_PRE: -0 + + - Resolved : 2.0.1 + ", + "draft": true, + "make_latest": "true", + "name": "Release Drafter v2.0.1", + "prerelease": false, + "tag_name": "v2.0.1", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) + expect(gqlScope.isDone()).toBe(true) + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('with $MAJOR.$MINOR template', async () => { + await mockContext('push') + mocks.config.mockReturnValue( + 'config-with-component-helpers-major-minor', + ) + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "Component helpers with $MAJOR.$MINOR template: + - MAJOR: 3.0 + - MAJOR_MAJOR: 3 + - MAJOR_MINOR: 0 + - MAJOR_PATCH: 0 + + - MINOR: 2.1 + - MINOR_MAJOR: 2 + - MINOR_MINOR: 1 + - MINOR_PATCH: 0 + + - PATCH: 2.0 + - PATCH_MAJOR: 2 + - PATCH_MINOR: 0 + - PATCH_PATCH: 1 + + - PRERELEASE: 2.0 + - PRERELEASE_PRE: -0 + + - Resolved : 2.0 + ", + "draft": true, + "make_latest": "true", + "name": "Release Drafter v2.0", + "prerelease": false, + "tag_name": "v2.0", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) + expect(gqlScope.isDone()).toBe(true) + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('with $MAJOR template', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-component-helpers-major') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "Component helpers with $MAJOR template: + - MAJOR: 3 + - MAJOR_MAJOR: 3 + - MAJOR_MINOR: 0 + - MAJOR_PATCH: 0 + + - MINOR: 2 + - MINOR_MAJOR: 2 + - MINOR_MINOR: 1 + - MINOR_PATCH: 0 + + - PATCH: 2 + - PATCH_MAJOR: 2 + - PATCH_MINOR: 0 + - PATCH_PATCH: 1 + + - PRERELEASE: 2 + - PRERELEASE_PRE: -0 + + - Resolved : 2 + ", + "draft": true, + "make_latest": "true", + "name": "Release Drafter v3", + "prerelease": false, + "tag_name": "v3", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) + expect(gqlScope.isDone()).toBe(true) + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('with custom format template', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-component-helpers-custom') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "Component helpers with custom template: + - MAJOR: Major: 3, Minor: 0, Patch: 0, Prerelease: + - MAJOR_MAJOR: 3 + - MAJOR_MINOR: 0 + - MAJOR_PATCH: 0 + + - MINOR: Major: 2, Minor: 1, Patch: 0, Prerelease: + - MINOR_MAJOR: 2 + - MINOR_MINOR: 1 + - MINOR_PATCH: 0 + + - PATCH: Major: 2, Minor: 0, Patch: 1, Prerelease: + - PATCH_MAJOR: 2 + - PATCH_MINOR: 0 + - PATCH_PATCH: 1 + + - PRERELEASE: Major: 2, Minor: 0, Patch: 1, Prerelease: -0 + + - Resolved : Major: 2, Minor: 0, Patch: 1, Prerelease: + ", + "draft": true, + "make_latest": "true", + "name": "Release Drafter vMajor: 2, Minor: 0, Patch: 1, Prerelease: ", + "prerelease": false, + "tag_name": "vMajor: 2, Minor: 0, Patch: 1, Prerelease: ", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) + expect(gqlScope.isDone()).toBe(true) + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('with prerelease enabled', async () => { + await mockContext('push') + mocks.config.mockReturnValue( + 'config-with-component-helpers-prerelease', + ) + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "Component helpers with prerelease: + - MAJOR: 3.0.0 + - MAJOR_MAJOR: 3 + - MAJOR_MINOR: 0 + - MAJOR_PATCH: 0 + + - MINOR: 2.1.0 + - MINOR_MAJOR: 2 + - MINOR_MINOR: 1 + - MINOR_PATCH: 0 + + - PATCH: 2.0.1 + - PATCH_MAJOR: 2 + - PATCH_MINOR: 0 + - PATCH_PATCH: 1 + + - PRERELEASE: 2.0.1-0 + - PRERELEASE_PRE: -0 + + - Resolved : 2.0.1 + ", + "draft": true, + "make_latest": "false", + "name": "Release Drafter v2.0.1", + "prerelease": true, + "tag_name": "v2.0.1", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) + expect(gqlScope.isDone()).toBe(true) + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('with prerelease and prerelease-identifier', async () => { + await mockContext('push') + mocks.config.mockReturnValue( + 'config-with-component-helpers-prerelease-identifier', + ) + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "Component helpers with prerelease and identifier: + - MAJOR: 3.0.0 + - MAJOR_MAJOR: 3 + - MAJOR_MINOR: 0 + - MAJOR_PATCH: 0 + + - MINOR: 2.1.0 + - MINOR_MAJOR: 2 + - MINOR_MINOR: 1 + - MINOR_PATCH: 0 + + - PATCH: 2.0.1 + - PATCH_MAJOR: 2 + - PATCH_MINOR: 0 + - PATCH_PATCH: 1 + + - PRERELEASE: 2.0.1-beta.0 + - PRERELEASE_PRE: -beta.0 + + - Resolved : 2.0.1-beta.0 + ", + "draft": true, + "make_latest": "false", + "name": "Release Drafter v2.0.1-beta.0", + "prerelease": true, + "tag_name": "v2.0.1-beta.0", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) + expect(gqlScope.isDone()).toBe(true) + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + }) + + describe('with header and footer config', () => { + it('only header', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-header-template') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "This is at top + This is the template in the middle + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + it('only footer', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-footer-template') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "This is the template in the middle + This is at bottom + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + it('header and footer', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-header-and-footer-template') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "This is at top + This is the template in the middle + This is at bottom + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + it('header and footer without line break and without space', async () => { + await mockContext('push') + mocks.config.mockReturnValue( + 'config-with-header-and-footer-no-nl-no-space-template', + ) + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "This is at topThis is the template in the middleThis is at bottom", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + it('only header from input', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-header-template') + await mockInput( + 'header', + 'I AM AWESOME_mockenv_strips_newline_and_trailing_spaces_', + ) + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "I AM AWESOME_mockenv_strips_newline_and_trailing_spaces_This is the template in the middle + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('merging strategies', () => { + describe('merge commit', () => { + it('sets $CHANGES based on all commits', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Add documentation (#5) @TimonVS + * Update dependencies (#4) @TimonVS + * Bug fixes (#3) @TimonVS + * Add big feature (#2) @TimonVS + * 👽 Add alien technology (#1) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('rebase merging', () => { + it('sets $CHANGES based on all commits', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-rebase-merging', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Add documentation (#10) @TimonVS + * Update dependencies (#9) @TimonVS + * Bug fixes (#8) @TimonVS + * Add big feature (#7) @TimonVS + * 👽 Add alien technology (#6) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('squash merging', () => { + it('sets $CHANGES based on all commits', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-squash-merging', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Add documentation (#15) @TimonVS + * Update dependencies (#14) @TimonVS + * Bug fixes (#13) @TimonVS + * Add big feature (#12) @TimonVS + * 👽 Add alien technology (#11) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('Commit from previous release tag is not included', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release-shared-commit-date'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-squash-merging', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Add documentation (#15) @TimonVS + * Update dependencies (#14) @TimonVS + * Bug fixes (#13) @TimonVS + * Add big feature (#12) @TimonVS + * 👽 Add alien technology (#11) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with associated pull requests from another repository', () => { + it('excludes pull requests targeting another repository', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-forking', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Add documentation (#28) @jetersen + * Update dependencies (#27) @jetersen + * Bug fixes (#25) @jetersen, @TimonVS + * Add big feature (#24) @jetersen + * Add alien technology (#23) @jetersen + * Add documentation (#5) @TimonVS + * Update dependencies (#4) @TimonVS + * 👽 Add alien technology (#1) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + expect(mocks.core.info).toHaveBeenCalledWith( + 'Found 8 merged pull requests targeting toolmantim/release-drafter-test-project: #28, #27, #25, #24, #23, #5, #4, #1', + ) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + }) + + describe('pagination', () => { + it('sets $CHANGES based on all commits', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope1 = mockGraphqlQuery({ + payload: 'graphql-comparison-paginated-1', + }) + const gqlScope2 = mockGraphqlQuery({ + payload: 'graphql-comparison-paginated-2', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Added great distance (#16) @toolmantim + * Oh hai (#15) @toolmantim + * ❤️ Add MOAR THINGS (#14) @toolmantim + * Add all the tests (#13) @toolmantim + * 🤖 Add robots (#12) @toolmantim + * 🎃 More pumpkins (#11) @toolmantim + * 🐄 Moar cowbell (#10) @toolmantim + * 1️⃣ Switch to a monorepo (#9) @toolmantim + * 👽 Integrate Alien technology (#8) @toolmantim + * Add ⛰ technology (#7) @toolmantim + * 👽 Added alien technology (#6) @toolmantim + * 🙅🏼‍♂️ 🐄 (#5) @toolmantim + * 🐄 More cowbell (#4) @toolmantim + * 🐒 Add monkeys technology (#3) @toolmantim + * Adds a new Widgets API (#2) @toolmantim + * Create new-feature.md (#1) @toolmantim + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope1.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope2.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('custom replacers', () => { + it('replaces a string', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-replacers') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Add documentation (#1000) @TimonVS + * Update dependencies (#4) @TimonVS + * Bug fixes (#3) @TimonVS + * Add big feature (#2) @TimonVS + * 👽 Add alien technology (#1) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + }) + + describe('with sort-by config', () => { + it('sorts by title', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-sort-by-title') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope1 = mockGraphqlQuery({ + payload: 'graphql-comparison-paginated-1', + }) + const gqlScope2 = mockGraphqlQuery({ + payload: 'graphql-comparison-paginated-2', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * 🤖 Add robots (#12) @toolmantim + * 🙅🏼‍♂️ 🐄 (#5) @toolmantim + * 👽 Integrate Alien technology (#8) @toolmantim + * 👽 Added alien technology (#6) @toolmantim + * 🐒 Add monkeys technology (#3) @toolmantim + * 🐄 More cowbell (#4) @toolmantim + * 🐄 Moar cowbell (#10) @toolmantim + * 🎃 More pumpkins (#11) @toolmantim + * ❤️ Add MOAR THINGS (#14) @toolmantim + * Oh hai (#15) @toolmantim + * Create new-feature.md (#1) @toolmantim + * Adds a new Widgets API (#2) @toolmantim + * Added great distance (#16) @toolmantim + * Add ⛰ technology (#7) @toolmantim + * Add all the tests (#13) @toolmantim + * 1️⃣ Switch to a monorepo (#9) @toolmantim + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope1.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope2.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with sort-direction config', () => { + it('sorts ascending', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-sort-direction-ascending') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: [ + 'graphql-comparison-paginated-1', + 'graphql-comparison-paginated-2', + ], + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Create new-feature.md (#1) @toolmantim + * Adds a new Widgets API (#2) @toolmantim + * 🐒 Add monkeys technology (#3) @toolmantim + * 🐄 More cowbell (#4) @toolmantim + * 🙅🏼‍♂️ 🐄 (#5) @toolmantim + * 👽 Added alien technology (#6) @toolmantim + * Add ⛰ technology (#7) @toolmantim + * 👽 Integrate Alien technology (#8) @toolmantim + * 1️⃣ Switch to a monorepo (#9) @toolmantim + * 🐄 Moar cowbell (#10) @toolmantim + * 🎃 More pumpkins (#11) @toolmantim + * 🤖 Add robots (#12) @toolmantim + * Add all the tests (#13) @toolmantim + * ❤️ Add MOAR THINGS (#14) @toolmantim + * Oh hai (#15) @toolmantim + * Added great distance (#16) @toolmantim + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with include-paths config', () => { + it('returns the modified paths', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-include-paths') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const fileScopes = nockGetPrFiles({ + repo: { + owner: 'toolmantim', + repo: 'release-drafter-test-project', + }, + entries: [ + [1, ['src/1.md']], + [2, ['src/2.md']], + [3, ['src/3.md']], + [4, ['src/4.md', 'some/path']], + [5, ['src/5.md', 'some/path']], + ], + }) + const gqlScope = mockGraphqlQuery([ + { + query: 'query findCommitsInComparison', + payload: 'graphql-comparison-merge-commit', + }, + ]) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + * Add documentation (#5) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "v2.0.1 (Code name: Placeholder)", + "prerelease": false, + "tag_name": "v2.0.1", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(fileScopes.every((fileScope) => fileScope.isDone())).toBe(true) + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('excludes commits that touch excluded paths from the release', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-exclude-paths') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const fileScopes = nockGetPrFiles({ + repo: { + owner: 'toolmantim', + repo: 'release-drafter-test-project', + }, + entries: [ + [1, ['src/1.md']], + [2, ['src/2.md']], + [3, ['src/3.md']], + [4, ['src/4.md', 'some/path']], + [5, ['src/5.md', 'some/path']], + ], + }) + const gqlScope = mockGraphqlQuery([ + { + query: 'query findCommitsInComparison', + payload: 'graphql-comparison-merge-commit', + }, + ]) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + * Bug fixes (#3) @TimonVS + * Add big feature (#2) @TimonVS + * 👽 Add alien technology (#1) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "v2.0.1 (Code name: Placeholder)", + "prerelease": false, + "tag_name": "v2.0.1", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(fileScopes.every((fileScope) => fileScope.isDone())).toBe(true) + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('exclude takes precedence over include when both list the same path', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-include-exclude-paths') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const fileScopes = nockGetPrFiles({ + repo: { + owner: 'toolmantim', + repo: 'release-drafter-test-project', + }, + entries: [ + [1, ['src/1.md']], + [2, ['src/2.md']], + [3, ['src/3.md']], + [4, ['src/4.md', 'some/path']], + [5, ['src/5.md', 'some/path']], + ], + }) + const gqlScope = mockGraphqlQuery([ + { + query: 'query findCommitsInComparison', + payload: 'graphql-comparison-merge-commit', + }, + ]) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + * No changes + ", + "draft": true, + "make_latest": "true", + "name": "v2.0.1 (Code name: Placeholder)", + "prerelease": false, + "tag_name": "v2.0.1", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(fileScopes.every((fileScope) => fileScope.isDone())).toBe(true) + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with category-based pre-include path config', () => { + it('returns the modified paths', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-category-pre-include-paths') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const fileScopes = nockGetPrFiles({ + repo: { + owner: 'toolmantim', + repo: 'release-drafter-test-project', + }, + entries: [ + [1, ['src/1.md']], + [2, ['src/2.md']], + [3, ['src/3.md']], + [4, ['src/4.md', 'some/path']], + [5, ['src/5.md', 'some/path']], + ], + }) + const gqlScope = mockGraphqlQuery([ + { + query: 'query findCommitsInComparison', + payload: 'graphql-comparison-merge-commit', + }, + ]) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + * Add documentation (#5) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "v2.0.1 (Code name: Placeholder)", + "prerelease": false, + "tag_name": "v2.0.1", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(fileScopes.every((fileScope) => fileScope.isDone())).toBe(true) + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with pull-request-limit config', () => { + it('uses the correct default when not specified', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = nock('https://api.github.com') + .post('/graphql', (body) => { + if ( + body.query.includes('query findCommitsInComparison') && + body.variables.pullRequestLimit === 5 + ) { + return true + } + return false + }) + .reply(200, getGqlPayload('graphql-comparison-no-prs')) + + await runDrafter() + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('requests the specified number of associated PRs', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-pull-request-limit') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = nock('https://api.github.com') + .post('/graphql', (body) => { + if ( + body.query.includes('query findCommitsInComparison') && + body.variables.pullRequestLimit === 34 + ) { + return true + } + return false + }) + .reply(200, getGqlPayload('graphql-comparison-no-prs')) + + await runDrafter() + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with history-limit config', () => { + it('uses the correct default when not specified', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = nock('https://api.github.com') + .post('/graphql', (body) => { + if ( + body.query.includes('query findCommitsInComparison') && + body.variables.historyLimit === 15 + ) { + return true + } + return false + }) + .reply(200, getGqlPayload('graphql-comparison-no-prs')) + + await runDrafter() + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('requests the specified number of associated PRs', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-history-limit') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = nock('https://api.github.com') + .post('/graphql', (body) => { + if ( + body.query.includes('query findCommitsInComparison') && + body.variables.historyLimit === 42 + ) { + return true + } + return false + }) + .reply(200, getGqlPayload('graphql-comparison-no-prs')) + + await runDrafter() + + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('config error handling', () => { + it('schema error', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-schema-error') + + await runDrafter() + + expect(mocks.core.setFailed.mock.lastCall?.[0]).toMatchInlineSnapshot(` + "[ + { + "expected": "string", + "code": "invalid_type", + "path": [ + "replacers", + 0, + "search" + ], + "message": "Invalid input: expected string, received null" + } + ]" + `) + }) + + it('yaml exception', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-yaml-exception') + + await runDrafter() + + expect(mocks.core.setFailed.mock.lastCall?.[0]).toMatchInlineSnapshot(` + "Unexpected block-seq-ind on same line with key at line 1, column 18: + + change-template: - #$NUMBER '$TITLE' @$AUTHOR + ^ + " + `) + }) + }) + + describe('input publish, prerelease, version, tag and name overrides', () => { + describe('with just the version', () => { + it('forces the version on templates', async () => { + await mockContext('push') + await mockInput('version', '2.1.1') + mocks.config.mockReturnValue('config-with-input-version-template') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "Placeholder with example. Automatically calculated values based on previous releases are next major=3.0.0, minor=2.2.0, patch=2.1.2. Manual input version is 2.1.1.", + "draft": true, + "make_latest": "true", + "name": "v2.1.1 (Code name: Placeholder)", + "prerelease": false, + "tag_name": "v2.1.1", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with just the tag', () => { + it('gets the version from the tag and forces using the tag', async () => { + await mockContext('push') + await mockInput('tag', 'v2.1.1-alpha') + mocks.config.mockReturnValue('config-with-input-version-template') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "Placeholder with example. Automatically calculated values based on previous releases are next major=3.0.0, minor=2.2.0, patch=2.1.1. Manual input version is 2.1.1.", + "draft": true, + "make_latest": "true", + "name": "v2.1.1 (Code name: Placeholder)", + "prerelease": false, + "tag_name": "v2.1.1-alpha", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with just the tag containing variables', () => { + it('gets the version from the tag and expands variables in it', async () => { + await mockContext('push') + await mockInput('tag', 'v$RESOLVED_VERSION-RC1') + mocks.config.mockReturnValue('config-with-name-and-tag-template') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "Placeholder with example. Automatically calculated values based on previous releases are next major=2.0.0, minor=1.1.0, patch=1.0.1.", + "draft": true, + "make_latest": "true", + "name": "v1.0.0-beta (Code name: Hello World)", + "prerelease": false, + "tag_name": "v1.0.0-RC1", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with just the name', () => { + it('gets the version from the name and forces using the name', async () => { + await mockContext('push') + await mockInput('name', 'v2.1.1-alpha (Code name: Foxtrot Unicorn)') + mocks.config.mockReturnValue('config-with-input-version-template') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "Placeholder with example. Automatically calculated values based on previous releases are next major=3.0.0, minor=2.2.0, patch=2.1.2. Manual input version is 2.1.1.", + "draft": true, + "make_latest": "true", + "name": "v2.1.1-alpha (Code name: Foxtrot Unicorn)", + "prerelease": false, + "tag_name": "v2.1.1", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with just the name containing variables', () => { + it('gets the version from the name and expands variables in it', async () => { + await mockContext('push') + await mockInput( + 'name', + 'v$RESOLVED_VERSION-RC1 (Code name: Hello World)', + ) + mocks.config.mockReturnValue('config-with-name-and-tag-template') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "Placeholder with example. Automatically calculated values based on previous releases are next major=2.0.0, minor=1.1.0, patch=1.0.1.", + "draft": true, + "make_latest": "true", + "name": "v1.0.0-RC1 (Code name: Hello World)", + "prerelease": false, + "tag_name": "v1.0.0-beta", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with publish: true', () => { + it('immediately publishes the created draft', async () => { + await mockContext('push') + await mockInput('version', '2.1.1') + await mockInput('publish', 'true') + mocks.config.mockReturnValue('config-with-input-version-template') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "Placeholder with example. Automatically calculated values based on previous releases are next major=3.0.0, minor=2.2.0, patch=2.1.2. Manual input version is 2.1.1.", + "draft": false, + "make_latest": "true", + "name": "v2.1.1 (Code name: Placeholder)", + "prerelease": false, + "tag_name": "v2.1.1", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with input prerelease: true', () => { + it('marks the created draft as prerelease', async () => { + await mockContext('push') + await mockInput('prerelease', 'true') + mocks.config.mockReturnValue('config-with-input-version-template') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "Placeholder with example. Automatically calculated values based on previous releases are next major=3.0.0, minor=2.1.0, patch=2.0.1. Manual input version is 2.0.1.", + "draft": true, + "make_latest": "false", + "name": "v2.0.1 (Code name: Placeholder)", + "prerelease": true, + "tag_name": "v2.0.1", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('resolves tag with incremented prerelease identifier', async () => { + await mockContext('push') + await mockInput('prerelease', 'true') + mocks.config.mockReturnValue('config-with-pre-release-identifier') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "This is a Pre-release with identifier.", + "draft": true, + "make_latest": "false", + "name": "v2.0.1-alpha.0", + "prerelease": true, + "tag_name": "v2.0.1-alpha.0", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with input prerelease: true and input prerelease-identifier', () => { + it('resolves tag with incremented pre-release identifier', async () => { + await mockContext('push') + await mockInput('prerelease', 'true') + await mockInput('prerelease-identifier', 'beta') + mocks.config.mockReturnValue('config-with-pre-release-identifier') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "This is a Pre-release with identifier.", + "draft": true, + "make_latest": "false", + "name": "v2.0.1-beta.0", + "prerelease": true, + "tag_name": "v2.0.1-beta.0", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with input prerelease: false', () => { + it('doesnt mark the created draft as prerelease', async () => { + await mockContext('push') + await mockInput('prerelease', 'false') + mocks.config.mockReturnValue('config-with-input-version-template') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "Placeholder with example. Automatically calculated values based on previous releases are next major=3.0.0, minor=2.1.0, patch=2.0.1. Manual input version is 2.0.1.", + "draft": true, + "make_latest": "true", + "name": "v2.0.1 (Code name: Placeholder)", + "prerelease": false, + "tag_name": "v2.0.1", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('doesnt let a config prerelease-identifier turn the release into a prerelease', async () => { + await mockContext('push') + await mockInput('prerelease', 'false') + mocks.config.mockReturnValue('config-with-pre-release-identifier') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "This is a Pre-release with identifier.", + "draft": true, + "make_latest": "true", + "name": "v2.0.1", + "prerelease": false, + "tag_name": "v2.0.1", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with input prerelease and publish: true', () => { + it('marks the created release as a prerelease', async () => { + await mockContext('push') + await mockInput('prerelease', 'true') + await mockInput('publish', 'true') + mocks.config.mockReturnValue('config-with-input-version-template') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "Placeholder with example. Automatically calculated values based on previous releases are next major=3.0.0, minor=2.1.0, patch=2.0.1. Manual input version is 2.0.1.", + "draft": false, + "make_latest": "false", + "name": "v2.0.1 (Code name: Placeholder)", + "prerelease": true, + "tag_name": "v2.0.1", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with input prerelease: true and config file prerelease: false', () => { + it('marks the created draft as prerelease', async () => { + await mockContext('push') + await mockInput('prerelease', 'true') + mocks.config.mockReturnValue('config-without-prerelease') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "This isn't a Pre-release.", + "draft": true, + "make_latest": "false", + "name": "", + "prerelease": true, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with input prerelease: false and config file prerelease: true', () => { + it('doesnt mark the created draft as prerelease', async () => { + await mockContext('push') + await mockInput('prerelease', 'false') + mocks.config.mockReturnValue('config-with-prerelease') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "This is a Pre-release.", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('without input prerelease and config file prerelease: true', () => { + it('marks the created draft as a prerelease', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-prerelease') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "This is a Pre-release.", + "draft": true, + "make_latest": "false", + "name": "", + "prerelease": true, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('without input prerelease and config file prerelease: false', () => { + it('doesnt mark the created draft as a prerelease', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-without-prerelease') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "This isn't a Pre-release.", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with tag and name', () => { + it('gets the version from the tag and forces using the tag and name', async () => { + await mockContext('push') + await mockInput('tag', 'v2.1.1-foxtrot-unicorn-alpha') + await mockInput('name', 'Foxtrot Unicorn') + mocks.config.mockReturnValue('config-with-input-version-template') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "Placeholder with example. Automatically calculated values based on previous releases are next major=3.0.0, minor=2.2.0, patch=2.1.1. Manual input version is 2.1.1.", + "draft": true, + "make_latest": "true", + "name": "Foxtrot Unicorn", + "prerelease": false, + "tag_name": "v2.1.1-foxtrot-unicorn-alpha", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + }) + + describe('resolved version', () => { + describe('without previous releases, overriding the tag', () => { + it('resolves to the version extracted from the tag', async () => { + await mockContext('push') + await mockInput('tag', 'v1.0.2') + mocks.config.mockReturnValue('config-with-resolved-version-template') + const scope = nockGetAndPostReleases({ + fetchedReleases: [], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-empty', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "## What's changed + + * No changes + + ## Contributors + + No contributors + + ## Previous release + + + + --- + > [!WARNING] + > Release Drafter could not find a previous **published release** for \`toolmantim/release-drafter-test-project\`. This draft was created **without a comparison baseline**. + + > [!IMPORTANT] + > Treat this draft as a manual starting point. + > Review the proposed version, tag, and notes before publishing. + + If you did not expect this to happen, [open an issue](https://github.com/release-drafter/release-drafter/issues/new?template=previous-published-release-not-found.yml). + + --- + ", + "draft": true, + "make_latest": "true", + "name": "v1.0.2 🌈", + "prerelease": false, + "tag_name": "v1.0.2", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(false) // gql not called + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with previous releases, overriding the tag', () => { + it('resolves to the version extracted from the tag', async () => { + await mockContext('push') + await mockInput('tag', 'v1.0.2') + mocks.config.mockReturnValue('config-with-resolved-version-template') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-no-prs', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "## What's changed + + * No changes + + ## Contributors + + No contributors + + ## Previous release + + v2.0.0 + ", + "draft": true, + "make_latest": "true", + "name": "v1.0.2 🌈", + "prerelease": false, + "tag_name": "v1.0.2", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('without previous releases, no overrides', () => { + it('resolves to the calculated version, which will be default', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-resolved-version-template') + const scope = nockGetAndPostReleases({ + fetchedReleases: [], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-empty', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "## What's changed + + * No changes + + ## Contributors + + No contributors + + ## Previous release + + + + --- + > [!WARNING] + > Release Drafter could not find a previous **published release** for \`toolmantim/release-drafter-test-project\`. This draft was created **without a comparison baseline**. + + > [!IMPORTANT] + > Treat this draft as a manual starting point. + > Review the proposed version, tag, and notes before publishing. + + If you did not expect this to happen, [open an issue](https://github.com/release-drafter/release-drafter/issues/new?template=previous-published-release-not-found.yml). + + --- + ", + "draft": true, + "make_latest": "true", + "name": "v0.0.1 🌈", + "prerelease": false, + "tag_name": "v0.0.1", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(false) // gql not called + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with previous releases, no overrides', () => { + it('resolves to the calculated version', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-resolved-version-template') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-no-prs', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "## What's changed + + * No changes + + ## Contributors + + No contributors + + ## Previous release + + v2.0.0 + ", + "draft": true, + "make_latest": "true", + "name": "v2.0.1 🌈", + "prerelease": false, + "tag_name": "v2.0.1", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with tag-prefix', () => { + it('gets the version from the tag, stripping the prefix', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-tag-prefix') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + fetchedReleasesOverrides: [ + { tag_name: 'static-tag-prefix-v2.1.4-RC3' }, + ], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-no-prs', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "## Previous release + + static-tag-prefix-v2.1.4-RC3 + ", + "draft": true, + "make_latest": "true", + "name": "static-tag-prefix-v2.1.4 🌈", + "prerelease": false, + "tag_name": "static-tag-prefix-v2.1.4", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with custom version resolver', () => { + it('uses correct default when no labels exist', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-custom-version-resolver-none') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-forking', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "dummy", + "draft": true, + "make_latest": "true", + "name": "v2.1.0", + "prerelease": false, + "tag_name": "v2.1.0", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('when only patch label exists, use patch', async () => { + await mockContext('push') + mocks.config.mockReturnValue( + 'config-with-custom-version-resolver-patch', + ) + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-forking', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "dummy", + "draft": true, + "make_latest": "true", + "name": "v2.0.1", + "prerelease": false, + "tag_name": "v2.0.1", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('minor beats patch', async () => { + await mockContext('push') + mocks.config.mockReturnValue( + 'config-with-custom-version-resolver-minor', + ) + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-forking', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "dummy", + "draft": true, + "make_latest": "true", + "name": "v2.1.0", + "prerelease": false, + "tag_name": "v2.1.0", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('major beats others', async () => { + await mockContext('push') + mocks.config.mockReturnValue( + 'config-with-custom-version-resolver-major', + ) + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-forking', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "dummy", + "draft": true, + "make_latest": "true", + "name": "v3.0.0", + "prerelease": false, + "tag_name": "v3.0.0", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('major beats others partial config', async () => { + await mockContext('push') + mocks.config.mockReturnValue( + 'config-with-custom-version-resolver-partial', + ) + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-forking', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "dummy", + "draft": true, + "make_latest": "true", + "name": "v3.0.0", + "prerelease": false, + "tag_name": "v3.0.0", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with category-based version resolver', () => { + it('major beats others', async () => { + await mockContext('push') + mocks.config.mockReturnValue( + 'config-with-category-version-resolver-major', + ) + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-forking', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "dummy", + "draft": true, + "make_latest": "true", + "name": "v3.0.0", + "prerelease": false, + "tag_name": "v3.0.0", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with commitish', () => { + it('allows specification of a target commitish', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-commitish') + const scope = nockGetAndPostReleases({ + fetchedReleases: ['release'], + }) + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-forking', + }) + await runDrafter() + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "dummy", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "staging", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(true) // should call the mocked endpoints + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('with filter-by-range', () => { + it('allows specification of a filter-by-range', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-filter-range') + const scope = nockGetAndPatchReleases({ + fetchedReleases: [ + 'release-2', + 'release', + 'release-3', + 'release-draft', + ], + }) + const gqlScope = mockGraphqlQuery({ + query: 'query findCommitsInComparison', + payload: 'graphql-comparison-empty', + }) + await runDrafter() + expect(mocks.patchReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# There's new stuff! + + --- + > [!WARNING] + > Release Drafter could not find a previous **published release** for \`toolmantim/release-drafter-test-project\`. This draft was created **without a comparison baseline**. + + > [!IMPORTANT] + > Treat this draft as a manual starting point. + > Review the proposed version, tag, and notes before publishing. + + If you did not expect this to happen, [open an issue](https://github.com/release-drafter/release-drafter/issues/new?template=previous-published-release-not-found.yml). + + --- + ", + "draft": true, + "make_latest": "true", + "name": "v3.0.0-beta", + "prerelease": false, + "tag_name": "v3.0.0-beta", + "target_commitish": "master", + }, + ] + `) + expect(scope.isDone()).toBe(true) // should call the mocked endpoints + expect(gqlScope.isDone()).toBe(false) // gql not called + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + }) + + describe('dry-run', () => { + describe('with a pull request merge ref', () => { + it('forces output-only mode, disables publishing, and warns when dry-run is not enabled', async () => { + await mockContext('push') + await mockInput('commitish', 'refs/pull/123/merge') + await mockInput('publish', 'true') + mocks.config.mockReturnValue('config') + + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-no-prs', + }) + const pullRequestScope = nock('https://api.github.com') + .post( + '/graphql', + (body) => + body.query.includes('query resolvePullRequestCommitish') && + body.variables.number === 123, + ) + .reply(200, { + data: { + repository: { + pullRequest: { + headRefOid: '1111111111111111111111111111111111111111', + mergeCommit: null, + potentialMergeCommit: { + oid: '2222222222222222222222222222222222222222', + }, + }, + }, + }, + }) + const scope = nockGetReleases({ releaseFiles: ['release'] }) + + await runDrafter() + + expect(mocks.postReleaseBody).not.toHaveBeenCalled() + expect(mocks.core.warning).toHaveBeenCalledWith( + 'refs/pull/123/merge points to an ephemeral pull request merge commit; forcing dry-run mode and disabling publish. Set dry-run: true explicitly to suppress this warning.', + ) + expect( + mocks.core.info.mock.calls + .flat() + .some( + (message) => + message.includes('[dry-run]') && + message.includes('"draft": true'), + ), + ).toBe(true) + expect(scope.isDone()).toBe(true) + expect(gqlScope.pendingMocks()).toHaveLength(0) + expect(pullRequestScope.pendingMocks()).toHaveLength(0) + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('does not warn when dry-run is explicitly enabled', async () => { + await mockContext('push') + await mockInput('commitish', 'refs/pull/123/merge') + await mockInput('dry-run', 'true') + mocks.config.mockReturnValue('config') + + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-no-prs', + }) + const pullRequestScope = nock('https://api.github.com') + .post('/graphql', (body) => + body.query.includes('query resolvePullRequestCommitish'), + ) + .reply(200, { + data: { + repository: { + pullRequest: { + headRefOid: '1111111111111111111111111111111111111111', + mergeCommit: null, + potentialMergeCommit: { + oid: '2222222222222222222222222222222222222222', + }, + }, + }, + }, + }) + const scope = nockGetReleases({ releaseFiles: ['release'] }) + + await runDrafter() + + expect(mocks.postReleaseBody).not.toHaveBeenCalled() + expect(mocks.core.warning).not.toHaveBeenCalled() + expect(scope.isDone()).toBe(true) + expect(gqlScope.pendingMocks()).toHaveLength(0) + expect(pullRequestScope.pendingMocks()).toHaveLength(0) + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('when no existing draft release exists (create)', () => { + it('does not perform any write operations, logs the payload, and sets computed outputs', async () => { + await mockContext('push') + await mockInput('dry-run', 'true') + mocks.config.mockReturnValue('config-with-resolved-version-template') + + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-no-prs', + }) + + // Only a GET scope — no POST scope, so any attempt to create a release + // would trigger an unmatched-request error from nock. + const scope = nockGetReleases({ releaseFiles: ['release'] }) + + await runDrafter() + + // No write request should have been made + expect(mocks.postReleaseBody).not.toHaveBeenCalled() + + // Dry-run message should have been logged + const infoMessages = mocks.core.info.mock.calls.flat() + expect(infoMessages.some((msg) => msg.includes('[dry-run]'))).toBe(true) + expect(mocks.core.setOutput).toHaveBeenCalledWith('tag_name', 'v2.0.1') + expect(mocks.core.setOutput).toHaveBeenCalledWith('name', 'v2.0.1 🌈') + + expect(scope.isDone()).toBe(true) // GET releases was called + expect(gqlScope.pendingMocks().length).toBe(0) + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + + describe('when an existing draft release exists (update)', () => { + it('does not perform any write operations, logs the payload, and sets computed outputs', async () => { + await mockContext('push') + await mockInput('dry-run', 'true') + mocks.config.mockReturnValue('config-with-resolved-version-template') + + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-no-prs', + }) + + // Only a GET scope — no PATCH scope, so any attempt to update a release + // would trigger an unmatched-request error from nock. + const scope = nockGetReleases({ + releaseFiles: ['release', 'release-draft'], + }) + + await runDrafter() + + // No write request should have been made + expect(mocks.patchReleaseBody).not.toHaveBeenCalled() + + // Dry-run message should have been logged + const infoMessages = mocks.core.info.mock.calls.flat() + expect(infoMessages.some((msg) => msg.includes('[dry-run]'))).toBe(true) + expect(mocks.core.setOutput).toHaveBeenCalledWith('tag_name', 'v2.0.1') + expect(mocks.core.setOutput).toHaveBeenCalledWith('name', 'v2.0.1 🌈') + + expect(scope.isDone()).toBe(true) // GET releases was called + expect(gqlScope.pendingMocks().length).toBe(0) + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) + }) + + describe('recent PR safety net', () => { + it('recovers a PR missing from GraphQL associatedPullRequests index via direct PR query', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config') + + const gqlScope = mockGraphqlQuery([ + { payload: 'graphql-comparison-missing-pr' }, + { + query: 'query findRecentMergedPullRequests', + payload: 'graphql-recent-merged-prs', + }, + ]) + + const scope = nockGetAndPostReleases({ fetchedReleases: ['release'] }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + + * Add new feature (#6) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) + expect(gqlScope.pendingMocks().length).toBe(0) + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + + it('respects include-paths when recovering missing PRs', async () => { + await mockContext('push') + mocks.config.mockReturnValue('config-with-include-paths') + + const gqlScope = mockGraphqlQuery([ + { + query: 'query findCommitsInComparison', + payload: 'graphql-comparison-missing-pr-with-paths', + }, + { + query: 'query findRecentMergedPullRequests', + payload: 'graphql-recent-merged-prs-with-paths', + }, + ]) + + const scope = nockGetAndPostReleases({ fetchedReleases: ['release'] }) + const fileScopes = nockGetPrFiles({ + repo: { + owner: 'toolmantim', + repo: 'release-drafter-test-project', + }, + entries: [ + [100, ['src/5.md']], + [101, ['other/file.md']], + ], + }) + + await runDrafter() + + expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot(` + [ + { + "body": "# What's Changed + * Touches src (#100) @TimonVS + ", + "draft": true, + "make_latest": "true", + "name": "v2.0.1 (Code name: Placeholder)", + "prerelease": false, + "tag_name": "v2.0.1", + "target_commitish": "master", + }, + ] + `) + + expect(scope.isDone()).toBe(true) + expect(fileScopes.every((fileScope) => fileScope.isDone())).toBe(true) + expect(gqlScope.pendingMocks().length).toBe(0) + expect(mocks.core.setFailed).not.toHaveBeenCalled() + }) + }) +}) diff --git a/src/tests/drafter/find-previous-releases.test.ts b/src/tests/drafter/find-previous-releases.test.ts new file mode 100644 index 0000000000..0c2b66f8b2 --- /dev/null +++ b/src/tests/drafter/find-previous-releases.test.ts @@ -0,0 +1,293 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { findPreviousReleases } from '#src/actions/drafter/lib/index.ts' +import type { Octokit } from '#src/common/get-octokit.ts' +import { mockContext, mocks as sharedMocks } from '#tests/mocks/index.ts' + +const localMocks = vi.hoisted(() => { + return { + releases: vi.fn(), + } +}) + +vi.mock(import('#src/common/get-octokit.ts'), async (iom) => { + const om = await iom() + process.env.GITHUB_TOKEN = 'test' + return { + ...om, + getOctokit: () => ({ + ...om.getOctokit(), + paginate: localMocks.releases as unknown as Octokit['paginate'], + }), + } +}) + +describe('find previous releases', () => { + beforeEach(async () => { + await mockContext('push') + }) + + it('should retrieve last release respecting semver, stripped prefix', async () => { + localMocks.releases.mockResolvedValueOnce([ + { + tag_name: 'test-1.0.1', + target_commitish: 'master', + created_at: '2021-06-29T05:45:15Z', + }, + { + tag_name: 'test-1.0.0', + target_commitish: 'master', + created_at: '2022-06-29T05:45:15Z', + }, + ]) + + const { lastRelease } = await findPreviousReleases({ + commitish: 'refs/heads/master', + 'filter-by-commitish': false, + 'tag-prefix': 'test-', + prerelease: false, + }) + + expect(lastRelease?.tag_name).toEqual('test-1.0.1') + }) + + it('should return last release without draft and prerelease', async () => { + localMocks.releases.mockResolvedValueOnce([ + { tag_name: 'v1.0.0', draft: true, prerelease: false }, + { tag_name: 'v1.0.1', draft: false, prerelease: false }, + { tag_name: 'v1.0.2-rc.1', draft: false, prerelease: true }, + ]) + + const { lastRelease } = await findPreviousReleases({ + commitish: 'refs/heads/master', + 'filter-by-commitish': false, + 'tag-prefix': '', + prerelease: false, + }) + + expect(lastRelease).toEqual({ + tag_name: 'v1.0.1', + draft: false, + prerelease: false, + }) + }) + + it('should return non-prerelease draft when isPreRelease is false', async () => { + localMocks.releases.mockResolvedValueOnce([ + { tag_name: 'v1.0.0', draft: true, prerelease: false }, + { tag_name: 'v1.0.1', draft: false, prerelease: false }, + { tag_name: 'v1.0.2-rc.1', draft: false, prerelease: true }, + ]) + + const { draftRelease } = await findPreviousReleases({ + commitish: 'refs/heads/master', + 'filter-by-commitish': false, + 'tag-prefix': '', + prerelease: false, + }) + + expect(draftRelease).toEqual({ + tag_name: 'v1.0.0', + draft: true, + prerelease: false, + }) + }) + + it('should skip prerelease draft when isPreRelease is false', async () => { + localMocks.releases.mockResolvedValueOnce([ + { tag_name: 'v1.0.2-rc.1', draft: true, prerelease: true }, + { tag_name: 'v1.0.1', draft: false, prerelease: false }, + ]) + + const { draftRelease } = await findPreviousReleases({ + commitish: 'refs/heads/master', + 'filter-by-commitish': false, + 'tag-prefix': '', + prerelease: false, + }) + + expect(draftRelease).toBeUndefined() + }) + + it('should return prerelease draft when isPreRelease is true', async () => { + localMocks.releases.mockResolvedValueOnce([ + { tag_name: 'v1.0.0', draft: true, prerelease: false }, + { tag_name: 'v1.0.2-rc.1', draft: true, prerelease: true }, + { tag_name: 'v1.0.1', draft: false, prerelease: false }, + ]) + + const { draftRelease } = await findPreviousReleases({ + commitish: 'refs/heads/master', + 'filter-by-commitish': false, + 'tag-prefix': '', + prerelease: true, + }) + + expect(draftRelease).toEqual({ + tag_name: 'v1.0.2-rc.1', + draft: true, + prerelease: true, + }) + }) + + it('should prefer matching draft when both types exist', async () => { + localMocks.releases.mockResolvedValueOnce([ + { tag_name: 'v1.0.0', draft: true, prerelease: false }, + { tag_name: 'v1.0.2-rc.1', draft: true, prerelease: true }, + { tag_name: 'v1.0.1', draft: false, prerelease: false }, + ]) + + const { draftRelease: preDraft } = await findPreviousReleases({ + commitish: 'refs/heads/master', + prerelease: true, + 'tag-prefix': '', + 'filter-by-commitish': false, + }) + + expect(preDraft).toEqual({ + tag_name: 'v1.0.2-rc.1', + draft: true, + prerelease: true, + }) + + localMocks.releases.mockResolvedValueOnce([ + { tag_name: 'v1.0.0', draft: true, prerelease: false }, + { tag_name: 'v1.0.2-rc.1', draft: true, prerelease: true }, + { tag_name: 'v1.0.1', draft: false, prerelease: false }, + ]) + + const { draftRelease: stableDraft } = await findPreviousReleases({ + commitish: 'refs/heads/master', + prerelease: false, + 'tag-prefix': '', + 'filter-by-commitish': false, + }) + + expect(stableDraft).toEqual({ + tag_name: 'v1.0.0', + draft: true, + prerelease: false, + }) + }) + + it('should find non-prerelease draft when isPreRelease is false', async () => { + localMocks.releases.mockResolvedValueOnce([ + { tag_name: 'v1.0.0', draft: true, prerelease: false }, + { tag_name: 'v1.0.1', draft: false, prerelease: false }, + ]) + + const { draftRelease } = await findPreviousReleases({ + commitish: 'refs/heads/master', + 'tag-prefix': '', + 'filter-by-commitish': false, + prerelease: false, + }) + + expect(draftRelease).toEqual({ + tag_name: 'v1.0.0', + draft: true, + prerelease: false, + }) + }) + + it('should filter releases by semver range and exclude non-semver tags', async () => { + localMocks.releases.mockResolvedValueOnce([ + { + tag_name: 'not-a-semver-tag', + target_commitish: 'master', + created_at: '2024-06-29T05:45:15Z', + draft: false, + prerelease: false, + }, + { + tag_name: 'v2.9.0', + target_commitish: 'master', + created_at: '2024-06-28T05:45:15Z', + draft: false, + prerelease: false, + }, + { + tag_name: 'v3.0.0', + target_commitish: 'master', + created_at: '2024-06-27T05:45:15Z', + draft: false, + prerelease: false, + }, + { + tag_name: 'v3.1.0', + target_commitish: 'master', + created_at: '2024-06-26T05:45:15Z', + draft: true, + prerelease: false, + }, + ]) + + const { draftRelease, lastRelease } = await findPreviousReleases({ + commitish: 'refs/heads/master', + 'filter-by-commitish': false, + 'tag-prefix': '', + prerelease: false, + 'filter-by-range': '^3.0.0', + }) + + expect(draftRelease?.tag_name).toBe('v3.1.0') + expect(lastRelease?.tag_name).toBe('v3.0.0') + expect(sharedMocks.core.warning).toHaveBeenCalledWith( + 'Failed to coerce semver version for "not-a-semver-tag" : will be excluded from releases considered for drafting.', + ) + expect(sharedMocks.core.debug).toHaveBeenCalledWith( + expect.stringContaining('does not satisfy version "2.9.0"'), + ) + expect(sharedMocks.core.debug).toHaveBeenCalledWith( + expect.stringContaining('satisfies version "3.0.0"'), + ) + }) + + it('should warn when multiple matching drafts remain after semver range filtering', async () => { + localMocks.releases.mockResolvedValueOnce([ + { + tag_name: 'v3.2.0', + target_commitish: 'master', + created_at: '2024-06-29T05:45:15Z', + draft: true, + prerelease: false, + }, + { + tag_name: 'v3.1.0', + target_commitish: 'master', + created_at: '2024-06-28T05:45:15Z', + draft: true, + prerelease: false, + }, + { + tag_name: 'v3.0.0', + target_commitish: 'master', + created_at: '2024-06-27T05:45:15Z', + draft: false, + prerelease: false, + }, + { + tag_name: 'v2.9.0', + target_commitish: 'master', + created_at: '2024-06-26T05:45:15Z', + draft: true, + prerelease: false, + }, + ]) + + const { draftRelease } = await findPreviousReleases({ + commitish: 'refs/heads/master', + 'filter-by-commitish': false, + 'tag-prefix': '', + prerelease: false, + 'filter-by-range': '^3.0.0', + }) + + expect(draftRelease?.tag_name).toBe('v3.2.0') + expect(sharedMocks.core.warning).toHaveBeenCalledWith( + 'Multiple draft releases found : v3.2.0, v3.1.0', + ) + expect(sharedMocks.core.warning).toHaveBeenCalledWith( + 'Using the first one returned by GitHub API: v3.2.0', + ) + }) +}) diff --git a/src/tests/drafter/find-pull-requests.test.ts b/src/tests/drafter/find-pull-requests.test.ts new file mode 100644 index 0000000000..762ccd256b --- /dev/null +++ b/src/tests/drafter/find-pull-requests.test.ts @@ -0,0 +1,352 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import type * as z from 'zod' +import { mergeInputAndConfig } from '#src/actions/drafter/config/index.ts' +import { commonConfigSchema } from '#src/actions/drafter/config/schemas/common-config.schema.ts' +import { configSchema } from '#src/actions/drafter/config/schemas/config.schema.ts' +import { categorizePullRequests } from '#src/actions/drafter/lib/build-release-payload/categorize-pull-requests.ts' +import { findPullRequests } from '#src/actions/drafter/lib/find-pull-requests/index.ts' +import type { Octokit } from '#src/common/get-octokit.ts' +import { mockContext } from '../mocks/index.ts' + +const localMocks = vi.hoisted(() => ({ + findCommitsInComparison: vi.fn(), + graphql: vi.fn(), + paginate: vi.fn(), + listFiles: vi.fn(), +})) + +vi.mock( + import( + '#src/actions/drafter/lib/find-pull-requests/find-commits-in-comparison.ts' + ), + () => ({ + findCommitsInComparison: localMocks.findCommitsInComparison, + }), +) + +vi.mock('#src/common/get-octokit.ts', () => ({ + getOctokit: () => + ({ + graphql: localMocks.graphql as unknown as Octokit['graphql'], + paginate: localMocks.paginate as unknown as Octokit['paginate'], + rest: { + pulls: { + listFiles: + localMocks.listFiles as unknown as Octokit['rest']['pulls']['listFiles'], + }, + }, + }) as unknown as Octokit, +})) + +const makeConfig = ( + categories: NonNullable['categories']>, +) => + mergeInputAndConfig({ + config: configSchema.parse({ + template: '$CHANGES', + commitish: 'refs/heads/main', + categories, + }), + input: commonConfigSchema.parse({}), + }) + +const makePullRequest = (number: number) => ({ + title: `PR ${number}`, + number, + url: `https://github.com/toolmantim/release-drafter-test-project/pull/${number}`, + body: '', + author: { + login: 'octocat', + __typename: 'User', + url: 'https://github.com/octocat', + }, + baseRepository: { + nameWithOwner: 'toolmantim/release-drafter-test-project', + }, + mergedAt: '2026-01-01T00:00:00Z', + isCrossRepository: false, + labels: { + nodes: [], + }, + merged: true, + baseRefName: 'main', + headRefName: `branch-${number}`, +}) + +const makeCommit = (id: string, pullRequestNumber: number) => ({ + id, + associatedPullRequests: { + nodes: [makePullRequest(pullRequestNumber)], + }, +}) + +type PullRequestWithChangedFiles = Awaited< + ReturnType +>['pullRequests'][number] & { + changedFiles?: string[] +} + +describe('findPullRequests', () => { + beforeEach(async () => { + await mockContext('push') + localMocks.findCommitsInComparison.mockReset() + localMocks.graphql.mockReset() + localMocks.paginate.mockReset() + localMocks.listFiles.mockReset() + }) + + it('identifies a first-time contribution from merge history', async () => { + const commit = makeCommit('first-contribution', 42) + const pullRequest = commit.associatedPullRequests.nodes[0] + pullRequest.author.login = 'first-timer' + pullRequest.mergedAt = '2026-07-11T15:17:17Z' + localMocks.findCommitsInComparison.mockResolvedValue([ + commit, + makeCommit('earlier-contributor', 1), + ]) + localMocks.graphql.mockResolvedValue({ + author0: { issueCount: 0 }, + author1: { issueCount: 1 }, + }) + + const config = mergeInputAndConfig({ + config: configSchema.parse({ + template: '$NEW_CONTRIBUTORS', + commitish: 'refs/heads/main', + }), + input: commonConfigSchema.parse({}), + }) + + const result = await findPullRequests({ + lastRelease: { tag_name: 'v1.0.0' } as Awaited< + ReturnType< + typeof import('#src/actions/drafter/lib/find-previous-releases/index.ts').findPreviousReleases + > + >['lastRelease'], + config, + }) + + expect(result.newContributorLogins).toEqual(new Set(['first-timer'])) + expect(localMocks.graphql).toHaveBeenCalledWith( + expect.stringContaining('query findPreviousContributions'), + { + query0: + 'repo:toolmantim/release-drafter-test-project is:pr is:merged author:first-timer merged:<2026-07-11T15:17:17Z', + query1: + 'repo:toolmantim/release-drafter-test-project is:pr is:merged author:octocat merged:<2026-01-01T00:00:00Z', + }, + ) + }) + + it('loads changed files for path-based pre-include categories', async () => { + const docsCommitId = 'docs-commit' + const srcCommitId = 'src-commit' + + localMocks.findCommitsInComparison.mockResolvedValue([ + makeCommit(docsCommitId, 1), + makeCommit(srcCommitId, 1), + ]) + localMocks.paginate.mockResolvedValue(['docs/readme.md', 'src/index.ts']) + + const result = await findPullRequests({ + lastRelease: { + tag_name: 'v1.0.0', + } as Awaited< + ReturnType< + typeof import('#src/actions/drafter/lib/find-previous-releases/index.ts').findPreviousReleases + > + >['lastRelease'], + config: makeConfig([ + { + type: 'pre-include', + when: { + paths: ['src/**'], + }, + }, + { + title: 'Docs', + when: { + paths: ['docs/**'], + }, + }, + ]), + }) + + expect(result.commits.map((commit) => commit.id)).toEqual([ + docsCommitId, + srcCommitId, + ]) + expect(result.pullRequests).toHaveLength(1) + expect( + (result.pullRequests[0] as PullRequestWithChangedFiles | undefined) + ?.changedFiles, + ).toEqual(['docs/readme.md', 'src/index.ts']) + expect(localMocks.paginate.mock.calls[0]?.[1]).toMatchObject({ + owner: 'toolmantim', + repo: 'release-drafter-test-project', + pull_number: 1, + per_page: 50, + }) + }) + + it('loads changed files for path-based pre-exclude categories', async () => { + const docsCommitId = 'docs-commit' + const srcCommitId = 'src-commit' + + localMocks.findCommitsInComparison.mockResolvedValue([ + makeCommit(docsCommitId, 1), + makeCommit(srcCommitId, 1), + ]) + localMocks.paginate.mockResolvedValue(['docs/readme.md', 'src/index.ts']) + + const result = await findPullRequests({ + lastRelease: { + tag_name: 'v1.0.0', + } as Awaited< + ReturnType< + typeof import('#src/actions/drafter/lib/find-previous-releases/index.ts').findPreviousReleases + > + >['lastRelease'], + config: makeConfig([ + { + type: 'pre-exclude', + when: { + paths: ['docs/**'], + }, + }, + { + title: 'Source', + when: { + paths: ['src/**'], + }, + }, + ]), + }) + + expect(result.commits.map((commit) => commit.id)).toEqual([ + docsCommitId, + srcCommitId, + ]) + expect(result.pullRequests).toHaveLength(1) + expect( + (result.pullRequests[0] as PullRequestWithChangedFiles | undefined) + ?.changedFiles, + ).toEqual(['docs/readme.md', 'src/index.ts']) + }) + + it('treats deprecated exclude-paths as a pre-exclude alias', async () => { + const docsCommitId = 'docs-commit' + const srcCommitId = 'src-commit' + + localMocks.findCommitsInComparison.mockResolvedValue([ + makeCommit(docsCommitId, 1), + makeCommit(srcCommitId, 1), + ]) + localMocks.paginate.mockResolvedValue(['docs/readme.md', 'src/index.ts']) + + const config = mergeInputAndConfig({ + config: configSchema.parse({ + template: '$CHANGES', + commitish: 'refs/heads/main', + 'exclude-paths': ['docs/**'], + }), + input: commonConfigSchema.parse({}), + }) + + const result = await findPullRequests({ + lastRelease: { + tag_name: 'v1.0.0', + } as Awaited< + ReturnType< + typeof import('#src/actions/drafter/lib/find-previous-releases/index.ts').findPreviousReleases + > + >['lastRelease'], + config, + }) + + const [uncategorizedPullRequests] = categorizePullRequests({ + pullRequests: result.pullRequests, + config, + }) + + expect(result.commits.map((commit) => commit.id)).toEqual([ + docsCommitId, + srcCommitId, + ]) + expect(result.pullRequests).toHaveLength(1) + expect(uncategorizedPullRequests).toHaveLength(0) + }) + + it('keeps mixed-path PRs when pre-exclude uses paths-mode only', async () => { + const docsCommitId = 'docs-commit' + const srcCommitId = 'src-commit' + + localMocks.findCommitsInComparison.mockResolvedValue([ + makeCommit(docsCommitId, 1), + makeCommit(srcCommitId, 1), + ]) + localMocks.paginate.mockResolvedValue(['docs/readme.md', 'src/index.ts']) + + const config = makeConfig([ + { + type: 'pre-exclude', + when: { + paths: ['docs/**'], + 'paths-mode': 'only', + }, + }, + { + title: 'Source', + when: { + paths: ['src/**'], + }, + }, + ]) + + const result = await findPullRequests({ + lastRelease: { + tag_name: 'v1.0.0', + } as Awaited< + ReturnType< + typeof import('#src/actions/drafter/lib/find-previous-releases/index.ts').findPreviousReleases + > + >['lastRelease'], + config, + }) + + const [uncategorizedPullRequests, categories] = categorizePullRequests({ + pullRequests: result.pullRequests, + config, + }) + + expect(uncategorizedPullRequests).toHaveLength(0) + expect(categories[0]?.pullRequests).toHaveLength(1) + }) + + it('adds context when loading changed files fails', async () => { + localMocks.findCommitsInComparison.mockResolvedValue([ + makeCommit('id-1', 7), + ]) + localMocks.paginate.mockRejectedValue(new Error('boom')) + + await expect( + findPullRequests({ + lastRelease: { + tag_name: 'v1.0.0', + } as Awaited< + ReturnType< + typeof import('#src/actions/drafter/lib/find-previous-releases/index.ts').findPreviousReleases + > + >['lastRelease'], + config: makeConfig([ + { + type: 'pre-include', + when: { + paths: ['src/**'], + }, + }, + ]), + }), + ).rejects.toThrow('Failed to list changed files for pull request #7.') + }) +}) diff --git a/src/tests/drafter/find-recent-merged-pull-requests.test.ts b/src/tests/drafter/find-recent-merged-pull-requests.test.ts new file mode 100644 index 0000000000..a853c52135 --- /dev/null +++ b/src/tests/drafter/find-recent-merged-pull-requests.test.ts @@ -0,0 +1,250 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { findRecentMergedPullRequests } from '#src/actions/drafter/lib/find-pull-requests/find-recent-merged-pull-requests.ts' +import type { Octokit } from '#src/common/get-octokit.ts' +import { mockContext } from '#tests/mocks/index.ts' + +const OWNER = 'toolmantim' +const REPO = 'release-drafter-test-project' + +const makePRNode = ( + overrides: Partial<{ + number: number + title: string + merged: boolean + mergedAt: string | null + mergeCommitOid: string | null + labels: string[] + authorLogin: string + authorType: string + isCrossRepository: boolean + }> = {}, +) => { + const o = { + number: 10, + title: 'Add feature', + merged: true, + mergedAt: '2019-04-27T14:00:00Z', + mergeCommitOid: 'abc123', + labels: [], + authorLogin: 'contributor', + authorType: 'User', + isCrossRepository: false, + ...overrides, + } + return { + __typename: 'PullRequest' as const, + number: o.number, + title: o.title, + merged: o.merged, + mergedAt: o.mergedAt, + isCrossRepository: o.isCrossRepository, + baseRepository: { + __typename: 'Repository' as const, + nameWithOwner: `${OWNER}/${REPO}`, + }, + author: { + __typename: o.authorType as 'User' | 'Bot', + login: o.authorLogin, + url: `https://github.com/${o.authorLogin}`, + }, + labels: { + __typename: 'LabelConnection' as const, + nodes: o.labels.map((name) => ({ __typename: 'Label' as const, name })), + }, + mergeCommit: o.mergeCommitOid + ? { __typename: 'Commit' as const, oid: o.mergeCommitOid } + : null, + } +} + +const makeGraphqlResponse = (nodes: ReturnType[]) => ({ + repository: { + pullRequests: { + __typename: 'PullRequestConnection', + nodes, + }, + }, +}) + +const localMocks = vi.hoisted(() => ({ + graphql: vi.fn(), +})) + +vi.mock(import('#src/common/get-octokit.ts'), async (iom) => { + const om = await iom() + process.env.GITHUB_TOKEN = 'test' + return { + ...om, + getOctokit: () => ({ + ...om.getOctokit(), + graphql: localMocks.graphql as unknown as Octokit['graphql'], + }), + } +}) + +const baseOpts = { + baseRefName: 'master' as string | null, + fieldFlags: { + withPullRequestBody: false, + withPullRequestURL: false, + withBaseRefName: false, + withHeadRefName: false, + }, +} + +describe('findRecentMergedPullRequests', () => { + beforeEach(async () => { + await mockContext('push') + localMocks.graphql.mockReset() + }) + + it('returns empty array when no recent PRs match comparison OIDs', async () => { + localMocks.graphql.mockResolvedValueOnce( + makeGraphqlResponse([makePRNode({ mergeCommitOid: 'deadbeef' })]), + ) + + const result = await findRecentMergedPullRequests({ + ...baseOpts, + commitOids: new Set(['abc123', 'def456']), + foundPrKeys: new Set(), + }) + + expect(result).toEqual([]) + }) + + it('returns empty array when matching PR is already in foundPrKeys', async () => { + localMocks.graphql.mockResolvedValueOnce( + makeGraphqlResponse([ + makePRNode({ number: 10, mergeCommitOid: 'abc123' }), + ]), + ) + + const result = await findRecentMergedPullRequests({ + ...baseOpts, + commitOids: new Set(['abc123']), + foundPrKeys: new Set([`${OWNER}/${REPO}#10`]), + }) + + expect(result).toEqual([]) + }) + + it('returns empty array when mergeCommit is null', async () => { + localMocks.graphql.mockResolvedValueOnce( + makeGraphqlResponse([makePRNode({ mergeCommitOid: null })]), + ) + + const result = await findRecentMergedPullRequests({ + ...baseOpts, + commitOids: new Set(['abc123']), + foundPrKeys: new Set(), + }) + + expect(result).toEqual([]) + }) + + it('returns PR when mergeCommit.oid is in commitOids and PR is not found', async () => { + localMocks.graphql.mockResolvedValueOnce( + makeGraphqlResponse([ + makePRNode({ + number: 6, + title: 'Add new feature', + mergeCommitOid: 'abc123', + labels: ['enhancement'], + authorLogin: 'TimonVS', + }), + ]), + ) + + const result = await findRecentMergedPullRequests({ + ...baseOpts, + commitOids: new Set(['abc123']), + foundPrKeys: new Set(), + }) + + expect(result).toHaveLength(1) + expect(result[0]).toMatchObject({ + __typename: 'PullRequest', + number: 6, + title: 'Add new feature', + merged: true, + baseRepository: { nameWithOwner: `${OWNER}/${REPO}` }, + labels: { nodes: [{ name: 'enhancement' }] }, + author: { login: 'TimonVS' }, + }) + }) + + it('recovers multiple missing PRs', async () => { + localMocks.graphql.mockResolvedValueOnce( + makeGraphqlResponse([ + makePRNode({ number: 10, mergeCommitOid: 'sha-10' }), + makePRNode({ number: 11, mergeCommitOid: 'sha-11' }), + makePRNode({ number: 12, mergeCommitOid: 'sha-not-in-comparison' }), + ]), + ) + + const result = await findRecentMergedPullRequests({ + ...baseOpts, + commitOids: new Set(['sha-10', 'sha-11']), + foundPrKeys: new Set(), + }) + + expect(result).toHaveLength(2) + expect(result.map((pr) => pr?.number)).toEqual([10, 11]) + }) + + it('returns empty array when repository has no pullRequests', async () => { + localMocks.graphql.mockResolvedValueOnce({ + repository: { + pullRequests: { __typename: 'PullRequestConnection', nodes: [] }, + }, + }) + + const result = await findRecentMergedPullRequests({ + ...baseOpts, + commitOids: new Set(['abc123']), + foundPrKeys: new Set(), + }) + + expect(result).toEqual([]) + }) + + it('calls GraphQL with correct variables', async () => { + localMocks.graphql.mockResolvedValueOnce(makeGraphqlResponse([])) + + await findRecentMergedPullRequests({ + ...baseOpts, + commitOids: new Set(['abc123']), + foundPrKeys: new Set(), + }) + + expect(localMocks.graphql).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + owner: OWNER, + name: REPO, + baseRefName: 'master', + limit: 5, + withPullRequestBody: false, + withPullRequestURL: false, + withBaseRefName: false, + withHeadRefName: false, + }), + ) + }) + + it('passes null baseRefName when commitish is not a confirmed branch', async () => { + localMocks.graphql.mockResolvedValueOnce(makeGraphqlResponse([])) + + await findRecentMergedPullRequests({ + ...baseOpts, + baseRefName: null, + commitOids: new Set(['abc123']), + foundPrKeys: new Set(), + }) + + expect(localMocks.graphql).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ baseRefName: null }), + ) + }) +}) diff --git a/src/tests/drafter/get-config-files.test.ts b/src/tests/drafter/get-config-files.test.ts new file mode 100644 index 0000000000..0c46b08eff --- /dev/null +++ b/src/tests/drafter/get-config-files.test.ts @@ -0,0 +1,452 @@ +// @ts-expect-error readFileSyncOriginal is the original unmocked function, whose mock is hoisted above +import { readFileSyncOriginal } from 'node:fs' +import nock from 'nock' +import { describe, expect, it, vi } from 'vitest' +import { getConfigFile } from '#src/common/config/get-config-file.ts' +import { getConfigFiles } from '#src/common/config/get-config-files.ts' + +const mocks = vi.hoisted(() => ({ + existsSync: vi.fn(), + readFileSync: vi.fn(), +})) + +vi.mock(import('node:fs'), async (iom) => { + const fs = await iom() + return { + ...fs, + readFileSyncOriginal: fs.readFileSync, + existsSync: mocks.existsSync, + readFileSync: mocks.readFileSync, + } +}) + +const getContentEndpoint = ( + owner: string, + repo: string, + path: string, + ref?: string, +) => { + return `/repos/${encodeURIComponent(owner)}/${encodeURIComponent( + repo, + )}/contents/${encodeURIComponent(path)}${ref ? `?ref=${encodeURIComponent(ref)}` : ''}` +} + +const RAW_CONTENT_TYPE = 'application/vnd.github.v3.raw; charset=utf-8' + +describe('getConfigFile', () => { + it('should throw error on unsupported file extension', async () => { + await expect( + getConfigFile({ + scheme: 'file', + filepath: 'README.md', + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Unsupported file extension: .md. Supported extensions are: json, yml, yaml]`, + ) + }) + describe('with a file scheme', () => { + it('should return config content', async () => { + vi.stubEnv('GITHUB_WORKSPACE', '/home/runner/workspace') + mocks.existsSync.mockReturnValue(true) + mocks.readFileSync.mockReturnValue(`template: fake-template-content`) + + const res = await getConfigFile({ + scheme: 'file', + filepath: '../configs/release-drafter.yml', + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + }) + + expect(mocks.existsSync).toHaveBeenCalledTimes(2) + expect(mocks.existsSync).toHaveBeenNthCalledWith( + 1, + '/home/runner/workspace', + ) + expect(mocks.existsSync).toHaveBeenNthCalledWith( + 2, + '/home/runner/workspace/configs/release-drafter.yml', + ) + expect(res.fetchedFrom.scheme).toBe('file') + expect(res.fetchedFrom.filepath).toMatchInlineSnapshot( + `"configs/release-drafter.yml"`, + ) + expect(res.config).toMatchInlineSnapshot(` + { + "template": "fake-template-content", + } + `) + }) + it('should return error without workspace config', async () => { + vi.stubEnv('GITHUB_WORKSPACE', undefined) + await expect( + getConfigFile({ + scheme: 'file', + filepath: '../configs/release-drafter.yml', + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Local load failed. env GITHUB_WORKSPACE is not set. Cannot resolve local repo path.]`, + ) + }) + it('should return error when workspace does not exist', async () => { + vi.stubEnv('GITHUB_WORKSPACE', '/home/runner/workspace') + mocks.existsSync.mockImplementation((p) => p !== '/home/runner/workspace') + await expect( + getConfigFile({ + scheme: 'file', + filepath: '../configs/release-drafter.yml', + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Local load failed. Root repo path does not exist: /home/runner/workspace]`, + ) + }) + it('should return error when file does not exist', async () => { + vi.stubEnv('GITHUB_WORKSPACE', '/home/runner/workspace') + mocks.existsSync.mockImplementation( + (p) => p !== '/home/runner/workspace/configs/release-drafter.yml', + ) + await expect( + getConfigFile({ + scheme: 'file', + filepath: '../configs/release-drafter.yml', + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Local load failed. Config file not found: /home/runner/workspace/configs/release-drafter.yml. Did you clone your sources ? (ex: using @actions/checkout)]`, + ) + }) + it('should return error when file exists but read fails', async () => { + vi.stubEnv('GITHUB_WORKSPACE', '/this/file/should/not/exist/on/your/pc') + mocks.existsSync.mockReturnValue(true) + mocks.readFileSync.mockImplementationOnce(readFileSyncOriginal) + + await expect( + getConfigFile({ + scheme: 'file', + filepath: '../configs/release-drafter.yml', + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Local load failed. ENOENT: no such file or directory, open '/this/file/should/not/exist/on/your/pc/configs/release-drafter.yml']`, + ) + }) + }) + describe('with a github scheme', () => { + it('should return config content', async () => { + vi.stubEnv('GITHUB_TOKEN', 'test') + + const endpoint = getContentEndpoint( + 'octocat', + 'hello-world', + '.github/release-drafter.yml', + 'main', + ) + const scope = nock('https://api.github.com') + .get(endpoint) + .reply(200, `template: fake-template-content`, { + 'content-type': RAW_CONTENT_TYPE, + }) + + const res = await getConfigFile({ + scheme: 'github', + filepath: 'release-drafter.yml', + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + }) + + expect(mocks.existsSync).not.toHaveBeenCalled() + expect(mocks.readFileSync).not.toHaveBeenCalled() + + expect(scope.isDone()).toBe(true) + + expect(res.fetchedFrom.scheme).toBe('github') + expect(res.fetchedFrom.filepath).toMatchInlineSnapshot( + `".github/release-drafter.yml"`, + ) + expect(res.config).toMatchInlineSnapshot(` + { + "template": "fake-template-content", + } + `) + }) + it('should fail when file is not found', async () => { + vi.stubEnv('GITHUB_TOKEN', 'test') + + const endpoint = getContentEndpoint( + 'octocat', + 'hello-world', + '.github/release-drafter.yml', + ) + const scope = nock('https://api.github.com').get(endpoint).reply(404) + + await expect( + getConfigFile({ + scheme: 'github', + filepath: 'release-drafter.yml', + repo: { owner: 'octocat', repo: 'hello-world' }, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Repo load failed. Config file not found with error 404. (target: octocat/hello-world:.github/release-drafter.yml)]`, + ) + + expect(mocks.existsSync).not.toHaveBeenCalled() + expect(mocks.readFileSync).not.toHaveBeenCalled() + + expect(scope.isDone()).toBe(true) + }) + it('should fail when fetching a directory', async () => { + vi.stubEnv('GITHUB_TOKEN', 'test') + + const endpoint = getContentEndpoint( + 'octocat', + 'hello-world', + '.github/release-drafter.yml', + ) + const scope = nock('https://api.github.com').get(endpoint).reply(200, []) + + await expect( + getConfigFile({ + scheme: 'github', + filepath: 'release-drafter.yml', + repo: { owner: 'octocat', repo: 'hello-world' }, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Repo load failed. Fetched content is a directory (array), expected a file. (target: octocat/hello-world:.github/release-drafter.yml)]`, + ) + + expect(mocks.existsSync).not.toHaveBeenCalled() + expect(mocks.readFileSync).not.toHaveBeenCalled() + + expect(scope.isDone()).toBe(true) + }) + it('should fail when content-type is wrong', async () => { + vi.stubEnv('GITHUB_TOKEN', 'test') + + const endpoint = getContentEndpoint( + 'octocat', + 'hello-world', + '.github/release-drafter.yml', + 'main', + ) + const scope = nock('https://api.github.com').get(endpoint).reply( + 200, + { number: 12345 }, + { + 'content-type': 'application/json', + }, + ) + + await expect( + getConfigFile({ + scheme: 'github', + filepath: 'release-drafter.yml', + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Repo load failed. Fetched content has wrong content-type (application/json), expected a raw file. (target: octocat/hello-world:.github/release-drafter.yml@main)]`, + ) + + expect(mocks.existsSync).not.toHaveBeenCalled() + expect(mocks.readFileSync).not.toHaveBeenCalled() + + expect(scope.isDone()).toBe(true) + }) + }) +}) + +describe('getConfigFiles', () => { + describe('_extends with repo-only defaults filepath from parent', () => { + it('should default filepath when _extends is owner/repo', async () => { + vi.stubEnv('GITHUB_TOKEN', 'test') + + const parentEndpoint = getContentEndpoint( + 'ansible', + 'ansible-navigator', + '.github/release-drafter.yml', + 'main', + ) + const extendsEndpoint = getContentEndpoint( + 'ansible', + 'team-devtools', + '.github/release-drafter.yml', + ) + + const scope = nock('https://api.github.com') + .get(parentEndpoint) + .reply( + 200, + `_extends: ansible/team-devtools\ntemplate: child-template`, + { 'content-type': RAW_CONTENT_TYPE }, + ) + .get(extendsEndpoint) + .reply(200, `template: parent-template`, { + 'content-type': RAW_CONTENT_TYPE, + }) + + const files = await getConfigFiles('release-drafter.yml', { + repo: { owner: 'ansible', repo: 'ansible-navigator' }, + ref: 'main', + }) + + expect(scope.isDone()).toBe(true) + expect(files).toHaveLength(2) + expect(files[0].fetchedFrom.filepath).toBe('.github/release-drafter.yml') + expect(files[1].fetchedFrom.filepath).toBe('.github/release-drafter.yml') + expect(files[1].fetchedFrom.repo).toEqual({ + owner: 'ansible', + repo: 'team-devtools', + }) + }) + + it('should default filepath when _extends is repo-only (no owner)', async () => { + vi.stubEnv('GITHUB_TOKEN', 'test') + + const parentEndpoint = getContentEndpoint( + 'ansible', + 'ansible-navigator', + '.github/release-drafter.yml', + 'main', + ) + const extendsEndpoint = getContentEndpoint( + 'ansible', + 'team-devtools', + '.github/release-drafter.yml', + ) + + const scope = nock('https://api.github.com') + .get(parentEndpoint) + .reply(200, `_extends: team-devtools\ntemplate: child-template`, { + 'content-type': RAW_CONTENT_TYPE, + }) + .get(extendsEndpoint) + .reply(200, `template: parent-template`, { + 'content-type': RAW_CONTENT_TYPE, + }) + + const files = await getConfigFiles('release-drafter.yml', { + repo: { owner: 'ansible', repo: 'ansible-navigator' }, + ref: 'main', + }) + + expect(scope.isDone()).toBe(true) + expect(files).toHaveLength(2) + expect(files[1].fetchedFrom.filepath).toBe('.github/release-drafter.yml') + expect(files[1].fetchedFrom.repo).toEqual({ + owner: 'ansible', + repo: 'team-devtools', + }) + }) + + it('should default filepath when _extends is .github', async () => { + vi.stubEnv('GITHUB_TOKEN', 'test') + + const parentEndpoint = getContentEndpoint( + 'jenkinsci', + 'azure-credentials-plugin', + '.github/release-drafter.yml', + 'master', + ) + const extendsEndpoint = getContentEndpoint( + 'jenkinsci', + '.github', + '.github/release-drafter.yml', + ) + + const scope = nock('https://api.github.com') + .get(parentEndpoint) + .reply(200, `_extends: .github\ntemplate: child-template`, { + 'content-type': RAW_CONTENT_TYPE, + }) + .get(extendsEndpoint) + .reply(200, `template: parent-template`, { + 'content-type': RAW_CONTENT_TYPE, + }) + + const files = await getConfigFiles('release-drafter.yml', { + repo: { owner: 'jenkinsci', repo: 'azure-credentials-plugin' }, + ref: 'master', + }) + + expect(scope.isDone()).toBe(true) + expect(files).toHaveLength(2) + expect(files[1].fetchedFrom.filepath).toBe('.github/release-drafter.yml') + expect(files[1].fetchedFrom.repo).toEqual({ + owner: 'jenkinsci', + repo: '.github', + }) + }) + + it('should default filepath with custom config name', async () => { + vi.stubEnv('GITHUB_TOKEN', 'test') + + const parentEndpoint = getContentEndpoint( + 'myorg', + 'myrepo', + '.github/custom-drafter.yml', + 'main', + ) + const extendsEndpoint = getContentEndpoint( + 'myorg', + 'shared-configs', + '.github/custom-drafter.yml', + ) + + const scope = nock('https://api.github.com') + .get(parentEndpoint) + .reply( + 200, + `_extends: myorg/shared-configs\ntemplate: child-template`, + { 'content-type': RAW_CONTENT_TYPE }, + ) + .get(extendsEndpoint) + .reply(200, `template: parent-template`, { + 'content-type': RAW_CONTENT_TYPE, + }) + + const files = await getConfigFiles('custom-drafter.yml', { + repo: { owner: 'myorg', repo: 'myrepo' }, + ref: 'main', + }) + + expect(scope.isDone()).toBe(true) + expect(files).toHaveLength(2) + expect(files[1].fetchedFrom.filepath).toBe('.github/custom-drafter.yml') + }) + }) + + describe('no _extends', () => { + it('should return a single config file', async () => { + vi.stubEnv('GITHUB_TOKEN', 'test') + + const endpoint = getContentEndpoint( + 'octocat', + 'hello-world', + '.github/release-drafter.yml', + 'main', + ) + + const scope = nock('https://api.github.com') + .get(endpoint) + .reply(200, `template: standalone`, { + 'content-type': RAW_CONTENT_TYPE, + }) + + const files = await getConfigFiles('release-drafter.yml', { + repo: { owner: 'octocat', repo: 'hello-world' }, + ref: 'main', + }) + + expect(scope.isDone()).toBe(true) + expect(files).toHaveLength(1) + expect(files[0].config.template).toBe('standalone') + }) + }) +}) diff --git a/src/tests/drafter/get-config.test.ts b/src/tests/drafter/get-config.test.ts new file mode 100644 index 0000000000..b5c6cc9b71 --- /dev/null +++ b/src/tests/drafter/get-config.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from 'vitest' +import { getConfig } from '#src/actions/drafter/config/get-config.ts' +import { mocks } from '#tests/mocks/index.ts' + +describe('get drafter config', () => { + it('logs the file path and ref for every fetched remote config', async () => { + mocks.config.mockReturnValue('config') + mocks.getContextsConfigWasFetchedFrom.mockReturnValue([ + { + filepath: '.github/release-drafter.yml', + scheme: 'github', + ref: 'main', + repo: { owner: 'octocat', repo: 'hello-world' }, + }, + { + filepath: '.github/base.yml', + scheme: 'github', + ref: undefined, + repo: { owner: 'octocat', repo: '.github' }, + }, + ]) + + await getConfig('release-drafter.yml') + + expect(mocks.core.info).toHaveBeenCalledWith( + 'Config fetched from "octocat/hello-world/.github/release-drafter.yml@main".', + ) + expect(mocks.core.info).toHaveBeenCalledWith( + 'Config fetched from "octocat/.github/.github/base.yml" on the default branch.', + ) + }) + + it('logs the file path for a locally fetched config', async () => { + mocks.config.mockReturnValue('config') + mocks.getContextsConfigWasFetchedFrom.mockReturnValue([ + { + filepath: 'config/release-drafter.yml', + scheme: 'file', + ref: 'main', + repo: { owner: 'octocat', repo: 'hello-world' }, + }, + ]) + + await getConfig('release-drafter.yml') + + expect(mocks.core.info).toHaveBeenCalledWith( + 'Config fetched locally from "config/release-drafter.yml".', + ) + }) +}) diff --git a/src/tests/drafter/get-version-info.test.ts b/src/tests/drafter/get-version-info.test.ts new file mode 100644 index 0000000000..933457c679 --- /dev/null +++ b/src/tests/drafter/get-version-info.test.ts @@ -0,0 +1,487 @@ +import { describe, expect, it } from 'vitest' +import { configSchemaDefaults } from '#src/actions/drafter/config/index.ts' +import { getVersionInfo } from '#src/actions/drafter/lib/build-release-payload/get-version-info.ts' +import type { resolveVersionKeyIncrement } from '#src/actions/drafter/lib/build-release-payload/resolve-version-increment.ts' + +type SuiteParams = [ + string, + Omit[0], 'lastRelease'> & { + lastRelease: + | Pick< + Exclude< + Parameters[0]['lastRelease'], + undefined + >, + 'tag_name' | 'name' + > + | undefined + expected: Partial< + Record< + `$${'MAJOR' | 'MINOR' | 'PATCH' | 'PRERELEASE' | 'RESOLVED'}`, + string + > + > + }, +] + +const suites: SuiteParams[] = [ + [ + 'extracts a version-like string from the last tag', + { + lastRelease: { + tag_name: 'v10.0.3', + name: 'Some release', + }, + config: { + 'version-template': configSchemaDefaults['version-template'], + }, + input: {}, + versionKeyIncrement: 'patch', + expected: { + $MAJOR: '11.0.0', + $MINOR: '10.1.0', + $PATCH: '10.0.4', + $PRERELEASE: '10.0.4-0', + $RESOLVED: '10.0.4', + }, + }, + ], + [ + "extracts a version-like string from the last release name if the tag isn't a version", + { + lastRelease: { + tag_name: 'notaproperversion', + name: '10.0.3', + }, + config: { + 'version-template': configSchemaDefaults['version-template'], + }, + input: {}, + versionKeyIncrement: 'patch', + expected: { + $MAJOR: '11.0.0', + $MINOR: '10.1.0', + $PATCH: '10.0.4', + $RESOLVED: '10.0.4', + $PRERELEASE: '10.0.4-0', + }, + }, + ], + [ + 'preferences tags over release names', + { + lastRelease: { + tag_name: '10.0.3', + name: '8.1.0', + }, + config: { + 'version-template': configSchemaDefaults['version-template'], + }, + input: {}, + versionKeyIncrement: 'patch', + expected: { + $MAJOR: '11.0.0', + $MINOR: '10.1.0', + $PATCH: '10.0.4', + $PRERELEASE: '10.0.4-0', + $RESOLVED: '10.0.4', + }, + }, + ], + [ + 'handles alpha/beta releases', + { + lastRelease: { + tag_name: 'v10.0.3-alpha', + name: 'Some release', + }, + config: { + 'version-template': configSchemaDefaults['version-template'], + }, + input: { version: 'v10.0.3-alpha' }, + versionKeyIncrement: 'patch', + expected: { + $MAJOR: '11.0.0', + $MINOR: '10.1.0', + $PATCH: '10.0.3', + $PRERELEASE: '10.0.3-alpha.0', + $RESOLVED: '10.0.3-alpha', + }, + }, + ], + [ + 'handles incremental prereleases', + { + lastRelease: { + tag_name: 'v10.0.3', + name: 'Some release', + }, + config: { + 'version-template': configSchemaDefaults['version-template'], + 'prerelease-identifier': 'alpha', + }, + input: {}, + versionKeyIncrement: 'patch', + expected: { + $MAJOR: '11.0.0', + $MINOR: '10.1.0', + $PATCH: '10.0.4', + $PRERELEASE: '10.0.4-alpha.0', + $RESOLVED: '10.0.4', + }, + }, + ], + [ + 'handles incremental prereleases on existing prereleases', + { + lastRelease: { + tag_name: 'v10.0.3-alpha.2', + name: 'Some release', + }, + config: { + 'version-template': configSchemaDefaults['version-template'], + }, + input: {}, + versionKeyIncrement: 'patch', + expected: { + $MAJOR: '11.0.0', + $MINOR: '10.1.0', + $PATCH: '10.0.3', + $PRERELEASE: '10.0.3-alpha.3', + $RESOLVED: '10.0.3', + }, + }, + ], +] + +describe('versions', () => { + it.each(suites)(`%s`, (_, { + config, + expected, + input, + lastRelease, + versionKeyIncrement, + }) => { + const versionInfo = getVersionInfo({ + config, + input, + lastRelease, + versionKeyIncrement, + }) + + // Next major version checks + expect(versionInfo.$NEXT_MAJOR_VERSION).toEqual(expected.$MAJOR) + + // Next minor version checks + expect(versionInfo.$NEXT_MINOR_VERSION).toEqual(expected.$MINOR) + + // Next patch version checks + expect(versionInfo.$NEXT_PATCH_VERSION).toEqual(expected.$PATCH) + + // Next prerelease version checks + expect(versionInfo.$NEXT_PRERELEASE_VERSION).toEqual(expected.$PRERELEASE) + + // Input & Resolved version checks + expect(versionInfo.$RESOLVED_VERSION).toEqual(expected.$RESOLVED) + }) + + it('returns default version info if no version was found in tag or name', () => { + const versionInfo = getVersionInfo({ + lastRelease: undefined, + input: {}, + config: { 'version-template': '$MAJOR.$MINOR.$PATCH$PRERELEASE' }, + versionKeyIncrement: 'patch', + }) + + expect(versionInfo).toMatchInlineSnapshot( + ` + { + "$NEXT_MAJOR_VERSION": "1.0.0", + "$NEXT_MAJOR_VERSION_MAJOR": "1", + "$NEXT_MAJOR_VERSION_MINOR": "0", + "$NEXT_MAJOR_VERSION_PATCH": "0", + "$NEXT_MINOR_VERSION": "0.1.0", + "$NEXT_MINOR_VERSION_MAJOR": "0", + "$NEXT_MINOR_VERSION_MINOR": "1", + "$NEXT_MINOR_VERSION_PATCH": "0", + "$NEXT_PATCH_VERSION": "0.0.1", + "$NEXT_PATCH_VERSION_MAJOR": "0", + "$NEXT_PATCH_VERSION_MINOR": "0", + "$NEXT_PATCH_VERSION_PATCH": "1", + "$NEXT_PRERELEASE_VERSION": "0.0.1-0", + "$NEXT_PRERELEASE_VERSION_PRERELEASE": "-0", + "$RESOLVED_VERSION": "0.0.1", + "$RESOLVED_VERSION_MAJOR": "0", + "$RESOLVED_VERSION_MINOR": "0", + "$RESOLVED_VERSION_PATCH": "1", + "$RESOLVED_VERSION_PRERELEASE": "", + } + `, + ) + }) + + it('uses prerelease-identifier on first run when versionKeyIncrement is prerelease-based', () => { + const versionInfo = getVersionInfo({ + lastRelease: undefined, + input: {}, + config: { + 'version-template': '$MAJOR.$MINOR.$PATCH$PRERELEASE', + 'prerelease-identifier': 'rc', + }, + versionKeyIncrement: 'prepatch', + }) + + expect(versionInfo.$RESOLVED_VERSION).toEqual('0.0.1-rc.0') + expect(versionInfo.$RESOLVED_VERSION_PRERELEASE).toEqual('-rc.0') + }) + + it('uses prerelease-identifier on first run with custom prerelease identifier', () => { + const versionInfo = getVersionInfo({ + lastRelease: undefined, + input: {}, + config: { + 'version-template': '$MAJOR.$MINOR.$PATCH$PRERELEASE', + 'prerelease-identifier': 'beta', + }, + versionKeyIncrement: 'preminor', + }) + + expect(versionInfo.$RESOLVED_VERSION).toEqual('0.1.0-beta.0') + expect(versionInfo.$RESOLVED_VERSION_PRERELEASE).toEqual('-beta.0') + }) + + it('uses premajor prerelease-identifier on first run', () => { + const versionInfo = getVersionInfo({ + lastRelease: undefined, + input: {}, + config: { + 'version-template': '$MAJOR.$MINOR.$PATCH$PRERELEASE', + 'prerelease-identifier': 'rc', + }, + versionKeyIncrement: 'premajor', + }) + + expect(versionInfo.$RESOLVED_VERSION).toEqual('1.0.0-rc.0') + expect(versionInfo.$RESOLVED_VERSION_PRERELEASE).toEqual('-rc.0') + }) + + it('still returns bare 0.0.1 on first run when versionKeyIncrement is not prerelease-based', () => { + const versionInfo = getVersionInfo({ + lastRelease: undefined, + input: {}, + config: { + 'version-template': '$MAJOR.$MINOR.$PATCH', + 'prerelease-identifier': 'rc', + }, + versionKeyIncrement: 'patch', + }) + + expect(versionInfo.$RESOLVED_VERSION).toEqual('0.0.1') + }) + + it('applies custom version template when no previous releases exist', () => { + const versionInfo = getVersionInfo({ + lastRelease: undefined, + input: {}, + config: { 'version-template': '$MAJOR.$MINOR' }, // Custom template - should use MAJOR.MINOR format + versionKeyIncrement: 'minor', + }) + + expect(versionInfo.$RESOLVED_VERSION).toEqual('0.1') + expect(versionInfo.$NEXT_MINOR_VERSION).toEqual('0.1') + }) + + it('supports version template with only MINOR.PATCH format', () => { + const versionInfo = getVersionInfo({ + lastRelease: undefined, + input: {}, + config: { 'version-template': '$MINOR.$PATCH' }, // Custom template - should use MAJOR.MINOR format (no major) + versionKeyIncrement: 'patch', + }) + + expect(versionInfo.$RESOLVED_VERSION).toEqual('0.1') + expect(versionInfo.$NEXT_PATCH_VERSION).toEqual('0.1') + expect(versionInfo.$NEXT_MINOR_VERSION).toEqual('1.0') + }) + + it('supports hardcoded major with 1.MINOR.PATCH format with existing releases', () => { + const versionInfo = getVersionInfo({ + lastRelease: { + tag_name: 'v1.3', // This should be parsed as 1.3.0 internally + name: 'Some release', + }, + input: {}, + config: { 'version-template': '1.$MINOR.$PATCH' }, // Custom template - should use MAJOR.MINOR format (no major) + versionKeyIncrement: 'patch', + }) + + expect(versionInfo.$RESOLVED_VERSION).toEqual('1.3.1') // From 1.3.0 -> 1.3.1 -> formatted as "3.1" + expect(versionInfo.$NEXT_PATCH_VERSION).toEqual('1.3.1') // 1.3.0 -> 1.3.1 -> "3.1" + expect(versionInfo.$NEXT_MINOR_VERSION).toEqual('1.4.0') // 1.3.0 -> 1.4.0 -> "4.0" + }) + + it('supports MINOR.PATCH format with existing releases', () => { + const versionInfo = getVersionInfo({ + lastRelease: { + tag_name: 'v5.3', // This should be parsed as 5.3.0 internally + name: 'Some release', + }, + input: {}, + config: { 'version-template': '$MINOR.$PATCH' }, // Custom template - should use MAJOR.MINOR format (no major) + versionKeyIncrement: 'patch', + }) + + expect(versionInfo.$RESOLVED_VERSION).toEqual('3.1') // From 5.3.0 -> 5.3.1 -> formatted as "3.1" + expect(versionInfo.$NEXT_PATCH_VERSION).toEqual('3.1') // 5.3.0 -> 5.3.1 -> "3.1" + expect(versionInfo.$NEXT_MINOR_VERSION).toEqual('4.0') // 5.3.0 -> 5.4.0 -> "4.0" + }) + + it.each<[ReturnType, string]>([ + ['patch', '10.0.4'], + ['minor', '10.1.0'], + ['major', '11.0.0'], + ])("when the resolver versionKey increment is '%s'", (versionKey, expected) => { + const versionInfo = getVersionInfo({ + lastRelease: { + tag_name: 'v10.0.3', + name: 'Some release', + }, + input: {}, + config: { + 'version-template': configSchemaDefaults['version-template'], + }, + versionKeyIncrement: versionKey, + }) + + expect(versionInfo.$RESOLVED_VERSION).toEqual(expected) + }) + + it('uses full semver helper values for default template and token-rendered helper values for custom template', () => { + const baseParams = { + lastRelease: { + tag_name: 'v10.0.3', + name: 'Some release', + }, + input: {}, + versionKeyIncrement: 'patch' as const, + } + + const defaultTemplateVersionInfo = getVersionInfo({ + ...baseParams, + config: { 'version-template': configSchemaDefaults['version-template'] }, + }) + + const customTemplateVersionInfo = getVersionInfo({ + ...baseParams, + config: { 'version-template': '$MAJOR.$MINOR.$PATCH' }, + }) + + expect(defaultTemplateVersionInfo.$NEXT_MAJOR_VERSION).toEqual('11.0.0') + expect(defaultTemplateVersionInfo.$NEXT_MAJOR_VERSION_MAJOR).toEqual('11') + + expect(customTemplateVersionInfo.$NEXT_MAJOR_VERSION).toEqual('11.0.0') + expect(customTemplateVersionInfo.$NEXT_MAJOR_VERSION_MAJOR).toEqual('11') + }) + + it('documents version-template behavior: with default template, component helpers are full semver', () => { + const baseParams = { + lastRelease: { + tag_name: 'v10.0.3', + name: 'Previous release', + }, + input: {}, + versionKeyIncrement: 'patch' as const, + } + + const versionInfo = getVersionInfo({ + ...baseParams, + config: { 'version-template': configSchemaDefaults['version-template'] }, + }) + + // With default template, principal variables are full SemVer + expect(versionInfo.$NEXT_MAJOR_VERSION).toBe('11.0.0') + expect(versionInfo.$NEXT_MINOR_VERSION).toBe('10.1.0') + expect(versionInfo.$NEXT_PATCH_VERSION).toBe('10.0.4') + + // Component helpers are also full SemVer with default template + expect(versionInfo.$NEXT_MAJOR_VERSION_MAJOR).toBe('11') + expect(versionInfo.$NEXT_MAJOR_VERSION_MINOR).toBe('0') + expect(versionInfo.$NEXT_MAJOR_VERSION_PATCH).toBe('0') + + expect(versionInfo.$NEXT_MINOR_VERSION_MAJOR).toBe('10') + expect(versionInfo.$NEXT_MINOR_VERSION_MINOR).toBe('1') + expect(versionInfo.$NEXT_MINOR_VERSION_PATCH).toBe('0') + + expect(versionInfo.$NEXT_PATCH_VERSION_MAJOR).toBe('10') + expect(versionInfo.$NEXT_PATCH_VERSION_MINOR).toBe('0') + expect(versionInfo.$NEXT_PATCH_VERSION_PATCH).toBe('4') + }) + + it('documents version-template behavior: with $MAJOR.$MINOR template, component helpers are token-rendered', () => { + const baseParams = { + lastRelease: { + tag_name: 'v10.0.3', + name: 'Previous release', + }, + input: {}, + versionKeyIncrement: 'patch' as const, + } + + const versionInfo = getVersionInfo({ + ...baseParams, + config: { 'version-template': '$MAJOR.$MINOR' }, + }) + + // With custom template, principal variables use the template + expect(versionInfo.$NEXT_MAJOR_VERSION).toBe('11.0') + expect(versionInfo.$NEXT_MINOR_VERSION).toBe('10.1') + expect(versionInfo.$NEXT_PATCH_VERSION).toBe('10.0') + + // Component helpers are rendered using their own $* templates + expect(versionInfo.$NEXT_MAJOR_VERSION_MAJOR).toBe('11') + expect(versionInfo.$NEXT_MAJOR_VERSION_MINOR).toBe('0') + expect(versionInfo.$NEXT_MAJOR_VERSION_PATCH).toBe('0') + + expect(versionInfo.$NEXT_MINOR_VERSION_MAJOR).toBe('10') + expect(versionInfo.$NEXT_MINOR_VERSION_MINOR).toBe('1') + expect(versionInfo.$NEXT_MINOR_VERSION_PATCH).toBe('0') + + expect(versionInfo.$NEXT_PATCH_VERSION_MAJOR).toBe('10') + expect(versionInfo.$NEXT_PATCH_VERSION_MINOR).toBe('0') + expect(versionInfo.$NEXT_PATCH_VERSION_PATCH).toBe('4') + }) + + it('documents version-template behavior: with $MAJOR template, component helpers are individual numbers', () => { + const baseParams = { + lastRelease: { + tag_name: 'v10.0.3', + name: 'Previous release', + }, + input: {}, + versionKeyIncrement: 'patch' as const, + } + + const versionInfo = getVersionInfo({ + ...baseParams, + config: { 'version-template': '$MAJOR' }, + }) + + // With $MAJOR template, principal variables contain only major version + expect(versionInfo.$NEXT_MAJOR_VERSION).toBe('11') + expect(versionInfo.$NEXT_MINOR_VERSION).toBe('10') + expect(versionInfo.$NEXT_PATCH_VERSION).toBe('10') + + // Component helpers are rendered using their own $* templates + expect(versionInfo.$NEXT_MAJOR_VERSION_MAJOR).toBe('11') + expect(versionInfo.$NEXT_MAJOR_VERSION_MINOR).toBe('0') + expect(versionInfo.$NEXT_MAJOR_VERSION_PATCH).toBe('0') + + expect(versionInfo.$NEXT_MINOR_VERSION_MAJOR).toBe('10') + expect(versionInfo.$NEXT_MINOR_VERSION_MINOR).toBe('1') + expect(versionInfo.$NEXT_MINOR_VERSION_PATCH).toBe('0') + + expect(versionInfo.$NEXT_PATCH_VERSION_MAJOR).toBe('10') + expect(versionInfo.$NEXT_PATCH_VERSION_MINOR).toBe('0') + expect(versionInfo.$NEXT_PATCH_VERSION_PATCH).toBe('4') + }) +}) diff --git a/src/tests/drafter/json-schema.test.ts b/src/tests/drafter/json-schema.test.ts new file mode 100644 index 0000000000..ab18a62e0c --- /dev/null +++ b/src/tests/drafter/json-schema.test.ts @@ -0,0 +1,225 @@ +import { describe, expect, it } from 'vitest' +import { globalRegistry, object, toJSONSchema } from 'zod' +import { configSchema as autolabelerConfigSchema } from '#src/actions/autolabeler/config/index.ts' +import { + commonConfigSchema, + configSchema as drafterConfigSchema, + exclusiveConfigSchema, +} from '#src/actions/drafter/config/index.ts' +import { extendsDeclarationSchema } from '#src/common/config/extends.schema.ts' + +/** + * Mirrors the schema generation in src/scripts/json-schema.ts + * so that regressions are caught by tests. + */ +function generateDrafterJSONSchema() { + return toJSONSchema( + object({ + _extends: extendsDeclarationSchema, + ...exclusiveConfigSchema.shape, + ...commonConfigSchema.shape, + }).meta({ ...globalRegistry.get(drafterConfigSchema) }), + { io: 'input' }, + ) +} + +function generateAutolabelerJSONSchema() { + const { id: _autolabelerSchemaId, ...autolabelerSchemaMetadata } = + globalRegistry.get(autolabelerConfigSchema) ?? {} + + return toJSONSchema( + object({ + _extends: extendsDeclarationSchema, + ...autolabelerConfigSchema.shape, + }).meta(autolabelerSchemaMetadata), + { io: 'input' }, + ) +} + +describe('JSON schema', () => { + it('should not emit the Zod registry id as a JSON Schema keyword', () => { + expect(generateAutolabelerJSONSchema()).not.toHaveProperty('id') + }) + + /** + * Fields with defaults should not be required in the JSON schema. + * YAML LSPs use the JSON schema to validate config files, and marking + * defaulted fields as required causes false validation errors. + * + * @see https://github.com/release-drafter/release-drafter/issues/1534 + */ + it('should not mark fields with defaults as required', () => { + const schema = generateDrafterJSONSchema() + + const properties = schema.properties as Record< + string, + { default?: unknown } + > + const required = (schema.required as string[]) ?? [] + + const requiredWithDefaults = required.filter( + (key) => properties[key] && 'default' in properties[key], + ) + + expect( + requiredWithDefaults, + `These fields have defaults but are marked as required: ${requiredWithDefaults.join(', ')}`, + ).toEqual([]) + }) + + it('should not mark nested fields with defaults as required', () => { + const schema = generateDrafterJSONSchema() + + const categories = schema.properties as Record< + string, + { + items?: { + required?: string[] + properties?: Record + } + } + > + const categoryItems = categories.categories?.items + expect( + categoryItems, + 'Expected categories.categories.items to exist in schema', + ).toBeDefined() + const required = categoryItems?.required + const properties = categoryItems?.properties + if (!required || !properties) return + + const requiredWithDefaults = required.filter( + (key) => properties[key] && 'default' in properties[key], + ) + + expect( + requiredWithDefaults, + `Category item fields have defaults but are marked as required: ${requiredWithDefaults.join(', ')}`, + ).toEqual([]) + }) + + /** + * `_extends` never reaches the config schema, it is stripped while the + * config chain is composed. The JSON schema validates the raw YAML a user + * writes, so both of its forms have to be exposed there. + */ + it('should expose both forms of the optional _extends key', () => { + const schema = generateDrafterJSONSchema() + const properties = schema.properties as Record< + string, + { + anyOf?: Array<{ + type?: string + minLength?: number + required?: string[] + additionalProperties?: boolean + properties?: Record< + string, + { + type?: string + minLength?: number + pattern?: string + additionalProperties?: { enum?: string[] } + anyOf?: Array<{ + type?: string + additionalProperties?: { enum?: string[] } + }> + } + > + }> + } + > + const required = (schema.required as string[]) ?? [] + + expect(required).not.toContain('_extends') + + const forms = properties._extends?.anyOf + expect(forms, 'Expected _extends to allow more than one form').toBeDefined() + + const stringForm = forms?.find((form) => form.type === 'string') + // Empty strings are accepted as a backwards-compatible no-op. + expect(stringForm).toMatchObject({ type: 'string' }) + + expect(forms).toContainEqual({ type: 'null' }) + + const mappingForm = forms?.find((form) => form.type === 'object') + expect(mappingForm?.required).toEqual(['from']) + // composition errors on unknown keys, so editors should flag them too + expect(mappingForm?.additionalProperties).toBe(false) + expect(mappingForm?.properties?.from).toMatchObject({ + type: 'string', + pattern: '\\S', + }) + const strategyForms = mappingForm?.properties?.strategy?.anyOf + const strategyMapping = strategyForms?.find( + (form) => form.type === 'object', + ) + expect(strategyMapping?.additionalProperties).toMatchObject({ + enum: ['override', 'append', 'prepend'], + }) + expect(strategyForms).toContainEqual({ type: 'null' }) + }) + + it('should expose when.path and when.paths in the category condition JSON schema', () => { + const schema = generateDrafterJSONSchema() + const properties = schema.properties as Record< + string, + { + items?: { + properties?: Record< + string, + { + anyOf?: Array<{ + type?: string + items?: { + properties?: Record< + string, + { + type?: string + minLength?: number + } + > + } + properties?: Record< + string, + { + type?: string + minLength?: number + } + > + }> + } + > + } + } + > + const whenAnyOf = properties.categories?.items?.properties?.when?.anyOf + + expect( + whenAnyOf, + 'Expected categories[*].when.anyOf to exist', + ).toBeDefined() + + const singleConditionSchema = whenAnyOf?.find( + (entry) => entry.type === 'object', + ) + const multipleConditionsSchema = whenAnyOf?.find( + (entry) => entry.type === 'array', + )?.items + + expect(singleConditionSchema?.properties?.path).toMatchObject({ + type: 'string', + minLength: 1, + }) + expect(singleConditionSchema?.properties?.paths).toMatchObject({ + type: 'array', + }) + expect(multipleConditionsSchema?.properties?.path).toMatchObject({ + type: 'string', + minLength: 1, + }) + expect(multipleConditionsSchema?.properties?.paths).toMatchObject({ + type: 'array', + }) + }) +}) diff --git a/src/tests/drafter/merge-config-chain.test.ts b/src/tests/drafter/merge-config-chain.test.ts new file mode 100644 index 0000000000..a48d1e3087 --- /dev/null +++ b/src/tests/drafter/merge-config-chain.test.ts @@ -0,0 +1,302 @@ +import { describe, expect, it } from 'vitest' +import { + configFileSchema, + extendsDeclarationSchema, +} from '#src/common/config/extends.schema.ts' +import { mergeConfigChain } from '#src/common/config/merge-config-chain.ts' +import type { ConfigTarget } from '#src/common/config/parse-config-target.ts' + +const target = (filepath: string): ConfigTarget => ({ + scheme: 'github', + repo: { owner: 'octocat', repo: 'hello-world' }, + filepath, +}) + +/** builds a chain entry; pass entries leaf-first, like getConfigFiles returns them */ +const entry = (filepath: string, config: Record) => ({ + config: configFileSchema.parse(config), + fetchedFrom: target(filepath), +}) + +describe('mergeConfigChain', () => { + it('should let the extending file override inherited keys by default', () => { + const merged = mergeConfigChain([ + entry('.github/child.yml', { + _extends: 'base.yml', + categories: [{ title: 'Child' }], + template: 'child-template', + }), + entry('.github/base.yml', { + categories: [{ title: 'Base' }], + template: 'base-template', + 'tag-prefix': 'v', + }), + ]) + + expect(merged).toEqual({ + categories: [{ title: 'Child' }], + template: 'child-template', + 'tag-prefix': 'v', + }) + }) + + it('should default to override when the mapping form declares no strategy', () => { + const merged = mergeConfigChain([ + entry('.github/child.yml', { + _extends: { from: 'base.yml' }, + categories: [{ title: 'Child' }], + }), + entry('.github/base.yml', { categories: [{ title: 'Base' }] }), + ]) + + expect(merged.categories).toEqual([{ title: 'Child' }]) + }) + + it('should append a list key to the inherited list', () => { + const merged = mergeConfigChain([ + entry('.github/child.yml', { + _extends: { from: 'base.yml', strategy: { categories: 'append' } }, + categories: [{ title: 'Child' }], + }), + entry('.github/base.yml', { + categories: [{ title: 'Base' }], + template: 'base-template', + }), + ]) + + expect(merged).toEqual({ + categories: [{ title: 'Base' }, { title: 'Child' }], + template: 'base-template', + }) + }) + + it('should prepend a list key onto the inherited list', () => { + const merged = mergeConfigChain([ + entry('.github/child.yml', { + _extends: { from: 'base.yml', strategy: { categories: 'prepend' } }, + categories: [{ title: 'Child' }], + }), + entry('.github/base.yml', { categories: [{ title: 'Base' }] }), + ]) + + expect(merged.categories).toEqual([{ title: 'Child' }, { title: 'Base' }]) + }) + + it('should strip _extends from the merged config', () => { + const merged = mergeConfigChain([ + entry('.github/child.yml', { + _extends: { from: 'base.yml', strategy: { categories: 'append' } }, + categories: [{ title: 'Child' }], + }), + entry('.github/base.yml', { categories: [{ title: 'Base' }] }), + ]) + + expect(merged).not.toHaveProperty('_extends') + }) + + it("should apply each file's strategy only at its own merge step, not inherit it", () => { + // A extends B extends C; B appends, A does not, so A's list replaces C+B + const merged = mergeConfigChain([ + entry('.github/a.yml', { + _extends: 'b.yml', + categories: [{ title: 'A' }], + }), + entry('.github/b.yml', { + _extends: { from: 'c.yml', strategy: { categories: 'append' } }, + categories: [{ title: 'B' }], + }), + entry('.github/c.yml', { categories: [{ title: 'C' }] }), + ]) + + expect(merged.categories).toEqual([{ title: 'A' }]) + }) + + it('should chain strategies across multiple levels in base-first order', () => { + const merged = mergeConfigChain([ + entry('.github/a.yml', { + _extends: { from: 'b.yml', strategy: { categories: 'prepend' } }, + categories: [{ title: 'A' }], + }), + entry('.github/b.yml', { + _extends: { from: 'c.yml', strategy: { categories: 'append' } }, + categories: [{ title: 'B' }], + }), + entry('.github/c.yml', { categories: [{ title: 'C' }] }), + ]) + + expect(merged.categories).toEqual([ + { title: 'A' }, + { title: 'C' }, + { title: 'B' }, + ]) + }) + + it('should merge onto a key the inherited configs never set', () => { + const merged = mergeConfigChain([ + entry('.github/child.yml', { + _extends: { from: 'base.yml', strategy: { categories: 'append' } }, + categories: [{ title: 'Child' }], + }), + entry('.github/base.yml', { template: 'base-template' }), + ]) + + expect(merged.categories).toEqual([{ title: 'Child' }]) + }) + + it('should treat a null value (empty YAML key) as an empty list when merging', () => { + const merged = mergeConfigChain([ + entry('.github/child.yml', { + _extends: { from: 'base.yml', strategy: { categories: 'append' } }, + categories: null, + }), + entry('.github/base.yml', { categories: [{ title: 'Base' }] }), + ]) + + expect(merged.categories).toEqual([{ title: 'Base' }]) + }) + + it('should ignore strategies for keys the file does not set', () => { + const merged = mergeConfigChain([ + entry('.github/child.yml', { + _extends: { from: 'base.yml', strategy: { categories: 'append' } }, + template: 'child-template', + }), + entry('.github/base.yml', { categories: [{ title: 'Base' }] }), + ]) + + expect(merged.categories).toEqual([{ title: 'Base' }]) + expect(merged.template).toBe('child-template') + }) + + it('should accept an explicit override strategy', () => { + const merged = mergeConfigChain([ + entry('.github/child.yml', { + _extends: { from: 'base.yml', strategy: { categories: 'override' } }, + categories: [{ title: 'Child' }], + }), + entry('.github/base.yml', { categories: [{ title: 'Base' }] }), + ]) + + expect(merged.categories).toEqual([{ title: 'Child' }]) + }) + + it('should throw when merging a key whose own value is not a list', () => { + expect(() => + mergeConfigChain([ + entry('.github/child.yml', { + _extends: { from: 'base.yml', strategy: { template: 'append' } }, + template: 'child-template', + }), + entry('.github/base.yml', { template: 'base-template' }), + ]), + ).toThrow( + /Cannot append 'template'.*github:\.github\/child\.yml.*is not a list/, + ) + }) + + it('should throw when merging onto an inherited value that is not a list', () => { + expect(() => + mergeConfigChain([ + entry('.github/child.yml', { + _extends: { from: 'base.yml', strategy: { categories: 'prepend' } }, + categories: [{ title: 'Child' }], + }), + entry('.github/base.yml', { categories: 'oops' }), + ]), + ).toThrow(/Cannot prepend 'categories'.*inherited.*is not a list/) + }) + + it('should not crash on config keys that collide with Object.prototype', () => { + // these keys resolve to inherited prototype members on a naive lookup + const merged = mergeConfigChain([ + entry('.github/child.yml', { + toString: 'child', + constructor: 'child', + hasOwnProperty: 'child', + }), + ]) + + expect(merged.toString).toBe('child') + expect(merged.constructor).toBe('child') + expect(merged.hasOwnProperty).toBe('child') + }) +}) + +describe('extendsDeclarationSchema', () => { + it('should normalize the plain target string form', () => { + expect(extendsDeclarationSchema.parse('base.yml')).toEqual({ + from: 'base.yml', + strategy: {}, + }) + }) + + it('should normalize the mapping form with per-key strategies', () => { + expect( + extendsDeclarationSchema.parse({ + from: 'base.yml', + strategy: { categories: 'append' }, + }), + ).toEqual({ from: 'base.yml', strategy: { categories: 'append' } }) + }) + + it('should return undefined for absent or null values', () => { + expect(extendsDeclarationSchema.parse(undefined)).toBeUndefined() + expect(extendsDeclarationSchema.parse(null)).toBeUndefined() + }) + + it('should treat an empty or blank string as absent', () => { + expect(extendsDeclarationSchema.parse('')).toBeUndefined() + expect(extendsDeclarationSchema.parse(' ')).toBeUndefined() + }) + + it('should treat a null strategy (empty YAML key) as no strategies', () => { + expect( + extendsDeclarationSchema.parse({ from: 'base.yml', strategy: null }), + ).toEqual({ from: 'base.yml', strategy: {} }) + }) + + it('should throw when the mapping form has no from', () => { + expect(() => + extendsDeclarationSchema.parse({ + strategy: { categories: 'append' }, + }), + ).toThrow(/from/) + }) + + it('should throw when the mapping form has a blank from target', () => { + expect(() => extendsDeclarationSchema.parse({ from: ' ' })).toThrow( + /from/, + ) + }) + + it('should throw on unknown keys in the mapping form', () => { + expect(() => + extendsDeclarationSchema.parse({ + from: 'base.yml', + stratagy: { categories: 'append' }, + }), + ).toThrow(/stratagy/) + }) + + it('should throw on an unknown strategy value', () => { + expect(() => + extendsDeclarationSchema.parse({ + from: 'base.yml', + strategy: { categories: 'concat' }, + }), + ).toThrow(/categories/) + }) + + it('should throw when strategy is not a mapping', () => { + expect(() => + extendsDeclarationSchema.parse({ + from: 'base.yml', + strategy: 'append', + }), + ).toThrow(/strategy/) + }) + + it('should throw when _extends is neither a string nor a mapping', () => { + expect(() => extendsDeclarationSchema.parse(['base.yml'])).toThrow() + }) +}) diff --git a/src/tests/drafter/merge-input-and-config.test.ts b/src/tests/drafter/merge-input-and-config.test.ts new file mode 100644 index 0000000000..e56d697a6c --- /dev/null +++ b/src/tests/drafter/merge-input-and-config.test.ts @@ -0,0 +1,605 @@ +import { beforeEach, describe, expect, it } from 'vitest' +import { mergeInputAndConfig } from '#src/actions/drafter/config/index.ts' +import { commonConfigSchema } from '#src/actions/drafter/config/schemas/common-config.schema.ts' +import { configSchema } from '#src/actions/drafter/config/schemas/config.schema.ts' +import { mockContext, mocks } from '#tests/mocks/index.ts' + +describe('mergeInputAndConfig', () => { + beforeEach(async () => { + await mockContext('push') + }) + + describe('override handling', () => { + it('should merge input and config with input taking precedence for commitish', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'config-commitish', + }) + const input = commonConfigSchema.parse({ + commitish: 'input-commitish', + }) + + const result = mergeInputAndConfig({ config, input }) + + expect(result.commitish).toBe('input-commitish') + expect(mocks.core.info).toHaveBeenCalledWith( + 'Input\'s commitish "input-commitish" overrides config\'s commitish "config-commitish"', + ) + }) + + it('should merge input and config with input taking precedence for header', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + header: 'config-header', + }) + const input = commonConfigSchema.parse({ + header: 'input-header', + }) + + const result = mergeInputAndConfig({ config, input }) + + expect(result.header).toBe('input-header') + expect(mocks.core.info).toHaveBeenCalledWith( + 'Input\'s header "input-header" overrides config\'s header "config-header"', + ) + }) + + it('should merge input and config with input taking precedence for footer', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + footer: 'config-footer', + }) + const input = commonConfigSchema.parse({ + footer: 'input-footer', + }) + + const result = mergeInputAndConfig({ config, input }) + + expect(result.footer).toBe('input-footer') + expect(mocks.core.info).toHaveBeenCalledWith( + 'Input\'s footer "input-footer" overrides config\'s footer "config-footer"', + ) + }) + + it('should merge input and config with input taking precedence for prerelease-identifier', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + 'prerelease-identifier': 'config-identifier', + }) + const input = commonConfigSchema.parse({ + 'prerelease-identifier': 'input-identifier', + }) + + const result = mergeInputAndConfig({ config, input }) + + expect(result['prerelease-identifier']).toBe('input-identifier') + expect(mocks.core.info).toHaveBeenCalledWith( + 'Input\'s prerelease-identifier "input-identifier" overrides config\'s prerelease-identifier "config-identifier"', + ) + }) + + it('should merge input and config with input taking precedence for prerelease (boolean)', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + prerelease: true, + }) + const input = commonConfigSchema.parse({ + prerelease: false, + }) + + const result = mergeInputAndConfig({ config, input }) + + expect(result.prerelease).toBe(false) + expect(mocks.core.info).toHaveBeenCalledWith( + 'Input\'s prerelease "false" overrides config\'s prerelease "true"', + ) + }) + + it('should merge input and config with input taking precedence for latest (boolean)', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + latest: false, + }) + const input = commonConfigSchema.parse({ + latest: true, + }) + + const result = mergeInputAndConfig({ config, input }) + + expect(result.latest).toBe(true) + expect(mocks.core.info).toHaveBeenCalledWith( + 'Input\'s latest "true" overrides config\'s latest "false"', + ) + }) + + it('should merge input and config with input taking precedence for filter-by-range', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + 'filter-by-range': '^1.0.0', + }) + const input = commonConfigSchema.parse({ + 'filter-by-range': '^2.0.0', + }) + + const result = mergeInputAndConfig({ config, input }) + + expect(result['filter-by-range']).toBe('^2.0.0') + expect(mocks.core.info).toHaveBeenCalledWith( + 'Input\'s filter-by-range "^2.0.0" overrides config\'s filter-by-range "^1.0.0"', + ) + }) + + it('should use config values when input values are not provided', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'config-commitish', + header: 'config-header', + footer: 'config-footer', + 'prerelease-identifier': 'config-identifier', + prerelease: true, + latest: false, + }) + const input = commonConfigSchema.parse({}) + + const result = mergeInputAndConfig({ config, input }) + + expect(result.commitish).toBe('config-commitish') + expect(result.header).toBe('config-header') + expect(result.footer).toBe('config-footer') + expect(result['prerelease-identifier']).toBe('config-identifier') + expect(result.prerelease).toBe(true) + expect(result.latest).toBe(false) + }) + + it('should not log info message when input does not override config', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + }) + const input = commonConfigSchema.parse({ + header: 'input-header', + }) + + mergeInputAndConfig({ config, input }) + + // Should not log info when input provides value not in config + expect(mocks.core.info).not.toHaveBeenCalled() + }) + }) + + describe('validation logic', () => { + it('should set latest to false when both prerelease and latest are true', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + prerelease: true, + latest: true, + }) + const input = commonConfigSchema.parse({}) + + const result = mergeInputAndConfig({ config, input }) + + expect(result.latest).toBe(false) + expect(result.prerelease).toBe(true) + expect(mocks.core.warning).toHaveBeenCalledWith( + "'prerelease' and 'latest' cannot be both true. Switch 'latest' to false - release will be a pre-release.", + ) + }) + + it('should set prerelease to true when prerelease-identifier is set but prerelease is false', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + 'prerelease-identifier': 'alpha', + }) + const input = commonConfigSchema.parse({}) + + const result = mergeInputAndConfig({ config, input }) + + expect(result.prerelease).toBe(true) + expect(mocks.core.warning).toHaveBeenCalledWith( + "You specified a 'prerelease-identifier' (alpha), but 'prerelease' is set to false. Switching to true.", + ) + }) + + it('should not warn when both prerelease-identifier and prerelease are set correctly', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + 'prerelease-identifier': 'alpha', + prerelease: true, + }) + const input = commonConfigSchema.parse({}) + + mergeInputAndConfig({ config, input }) + + expect(mocks.core.warning).not.toHaveBeenCalled() + }) + + it('should respect explicit input prerelease false over config prerelease-identifier', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + 'prerelease-identifier': 'alpha', + }) + const input = commonConfigSchema.parse({ + prerelease: false, + }) + + const result = mergeInputAndConfig({ config, input }) + + expect(result.prerelease).toBe(false) + expect(result['prerelease-identifier']).toBe('alpha') + expect(mocks.core.warning).not.toHaveBeenCalled() + }) + }) + + describe('defaults', () => { + it('should default commitish to context.ref when not provided', async () => { + await mockContext('push') + + const config = configSchema.parse({ + template: '$CHANGES', + }) + const input = commonConfigSchema.parse({}) + + const result = mergeInputAndConfig({ config, input }) + + expect(result.commitish).toBe('refs/heads/master') + }) + + it('should default latest to true when not a boolean', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + }) + const input = commonConfigSchema.parse({}) + + const result = mergeInputAndConfig({ config, input }) + + expect(result.latest).toBe(true) + }) + + it('should default prerelease to false when not a boolean', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + }) + const input = commonConfigSchema.parse({}) + + const result = mergeInputAndConfig({ config, input }) + + expect(result.prerelease).toBe(false) + }) + + it('should respect boolean false for latest', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + latest: false, + }) + const input = commonConfigSchema.parse({}) + + const result = mergeInputAndConfig({ config, input }) + + expect(result.latest).toBe(false) + }) + + it('should respect boolean true for prerelease', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + prerelease: true, + }) + const input = commonConfigSchema.parse({}) + + const result = mergeInputAndConfig({ config, input }) + + expect(result.prerelease).toBe(true) + }) + + it('should use commitish from input when provided', async () => { + await mockContext('push') + + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'config-commitish', + }) + const input = commonConfigSchema.parse({ + commitish: 'input-commitish', + }) + + const result = mergeInputAndConfig({ config, input }) + + expect(result.commitish).toBe('input-commitish') + }) + }) + + describe('replacers transformation', () => { + it('should convert search strings to regex in replacers', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + replacers: [ + { search: 'foo', replace: 'bar' }, + { search: '/test.*/g', replace: 'replaced' }, + ], + }) + const input = commonConfigSchema.parse({}) + + const result = mergeInputAndConfig({ config, input }) + + expect(result.replacers).toHaveLength(2) + expect(result.replacers[0].search).toBeInstanceOf(RegExp) + expect(result.replacers[0].search.source).toBe('foo') + expect(result.replacers[0].search.flags).toBe('g') + expect(result.replacers[0].replace).toBe('bar') + expect(result.replacers[1].search).toBeInstanceOf(RegExp) + expect(result.replacers[1].search.source).toBe('test.*') + expect(result.replacers[1].search.flags).toBe('g') + expect(result.replacers[1].replace).toBe('replaced') + }) + + it('should filter out invalid regex patterns with warning', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + replacers: [ + { search: 'valid-pattern', replace: 'bar' }, + { search: '/(?invalid/i', replace: 'baz' }, + { search: 'another-valid', replace: 'qux' }, + ], + }) + const input = commonConfigSchema.parse({}) + + const result = mergeInputAndConfig({ config, input }) + + expect(result.replacers).toHaveLength(2) + // Note: escape-string-regexp uses \x2d for hyphen + expect(result.replacers[0].search.source).toBe('valid\\x2dpattern') + expect(result.replacers[1].search.source).toBe('another\\x2dvalid') + expect(mocks.core.warning).toHaveBeenCalledWith( + "Bad replacer regex: '/(?invalid/i'", + ) + }) + + it('should handle empty replacers array', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + replacers: [], + }) + const input = commonConfigSchema.parse({}) + + const result = mergeInputAndConfig({ config, input }) + + expect(result.replacers).toHaveLength(0) + }) + + it('should handle regex with special characters', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + replacers: [{ search: '/^v(\\d+\\.\\d+\\.\\d+)$/i', replace: '$1' }], + }) + const input = commonConfigSchema.parse({}) + + const result = mergeInputAndConfig({ config, input }) + + expect(result.replacers).toHaveLength(1) + expect(result.replacers[0].search).toBeInstanceOf(RegExp) + expect(result.replacers[0].search.source).toBe('^v(\\d+\\.\\d+\\.\\d+)$') + expect(result.replacers[0].search.flags).toBe('i') + }) + }) + + describe('error handling', () => { + it('should throw error when commitish is not provided in config or input', async () => { + // Create a context where ref is undefined + await mockContext('push') + const github = await import('@actions/github') + + // @ts-expect-error - modifying readonly property for testing + github.context.ref = undefined + github.context.payload.ref = undefined + + const config = configSchema.parse({ + template: '$CHANGES', + }) + const input = commonConfigSchema.parse({}) + + expect(() => mergeInputAndConfig({ config, input })).toThrow( + "'commitish' is required. Please set 'commitish' to a valid value. (defaults to the current ref, but it seems to be undefined in this context)", + ) + }) + it('should throw error when multiple empty categories', async () => { + const config = configSchema.parse({ + template: '$CHANGES', + categories: [ + { + title: '📝 Other Changes', + }, + { + title: '📝 Yet Other Changes', + }, + ], + }) + const input = commonConfigSchema.parse({}) + + expect(() => + mergeInputAndConfig({ config, input }), + ).toThrowErrorMatchingInlineSnapshot( + `[Error: Multiple 'type: "changelog"' categories detected with no 'when' condition. Only one such category is supported for uncategorized changes.]`, + ) + }) + it('should throw error when a changelog category does not define a title', async () => { + const config = configSchema.parse({ + template: '$CHANGES', + categories: [ + { + label: 'bug', + }, + ], + }) + const input = commonConfigSchema.parse({}) + + expect(() => + mergeInputAndConfig({ config, input }), + ).toThrowErrorMatchingInlineSnapshot( + `[Error: Every 'type: "changelog"' category must define a non-empty 'title'.]`, + ) + }) + it('should throw error when filter-by-range is invalid', async () => { + const config = configSchema.parse({ + template: '$CHANGES', + 'filter-by-range': 'not-a-range', + }) + const input = commonConfigSchema.parse({}) + + expect(() => + mergeInputAndConfig({ config, input }), + ).toThrowErrorMatchingInlineSnapshot( + `[Error: 'filter-by-range' value "not-a-range" could not be parsed as a valid semver range.]`, + ) + }) + }) + + describe('complex scenarios', () => { + it('should handle all overrides and validations together', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'config-commitish', + header: 'config-header', + footer: 'config-footer', + 'prerelease-identifier': 'config-identifier', + prerelease: true, + latest: true, + }) + const input = commonConfigSchema.parse({ + commitish: 'input-commitish', + header: 'input-header', + footer: 'input-footer', + 'prerelease-identifier': 'input-identifier', + prerelease: false, + latest: false, + }) + + const result = mergeInputAndConfig({ config, input }) + + // Check overrides took effect + expect(result.commitish).toBe('input-commitish') + expect(result.header).toBe('input-header') + expect(result.footer).toBe('input-footer') + expect(result['prerelease-identifier']).toBe('input-identifier') + expect(result.prerelease).toBe(true) + expect(result.latest).toBe(false) + + // Check that info messages were logged + expect(mocks.core.info).toHaveBeenCalledTimes(6) + }) + + it('should not mutate original config object', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'original-commitish', + replacers: [{ search: 'test', replace: 'replaced' }], + }) + const originalConfig = JSON.parse(JSON.stringify(config)) + const input = commonConfigSchema.parse({ + commitish: 'new-commitish', + }) + + mergeInputAndConfig({ config, input }) + + // Verify original config is not mutated (structuredClone protects it) + expect(config).toEqual(originalConfig) + expect(config.commitish).toBe('original-commitish') + }) + + it('should handle prerelease-identifier from input with prereleases validation', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + prerelease: false, + }) + const input = commonConfigSchema.parse({ + 'prerelease-identifier': 'beta', + }) + + const result = mergeInputAndConfig({ config, input }) + + expect(result['prerelease-identifier']).toBe('beta') + expect(result.prerelease).toBe(true) + expect(mocks.core.warning).toHaveBeenCalledWith( + "You specified a 'prerelease-identifier' (beta), but 'prerelease' is set to false. Switching to true.", + ) + }) + + it('should still auto-enable prerelease when prerelease and prerelease-identifier both come from input', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + }) + const input = commonConfigSchema.parse({ + prerelease: false, + 'prerelease-identifier': 'beta', + }) + + const result = mergeInputAndConfig({ config, input }) + + expect(result['prerelease-identifier']).toBe('beta') + expect(result.prerelease).toBe(true) + expect(mocks.core.warning).toHaveBeenCalledWith( + "You specified a 'prerelease-identifier' (beta), but 'prerelease' is set to false. Switching to true.", + ) + }) + + it('should prioritize input boolean values over config boolean values for both prerelease and latest', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + prerelease: false, + latest: false, + }) + const input = commonConfigSchema.parse({ + prerelease: true, + latest: true, + }) + + const result = mergeInputAndConfig({ config, input }) + + // Input values should take precedence + expect(result.prerelease).toBe(true) + // But validation should still apply - latest should be false when prerelease is true + expect(result.latest).toBe(false) + expect(mocks.core.warning).toHaveBeenCalledWith( + "'prerelease' and 'latest' cannot be both true. Switch 'latest' to false - release will be a pre-release.", + ) + }) + }) + + describe('type safety', () => { + it('should return ParsedConfig type with transformed replacers', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'main', + replacers: [{ search: 'test', replace: 'replaced' }], + }) + const input = commonConfigSchema.parse({}) + + const result = mergeInputAndConfig({ config, input }) + + // TypeScript should ensure this type is correct + expect(result.replacers[0].search).toBeInstanceOf(RegExp) + expect(typeof result.commitish).toBe('string') + expect(typeof result.latest).toBe('boolean') + expect(typeof result.prerelease).toBe('boolean') + }) + }) +}) diff --git a/src/tests/drafter/normalize-filepath.test.ts b/src/tests/drafter/normalize-filepath.test.ts new file mode 100644 index 0000000000..ce367fc316 --- /dev/null +++ b/src/tests/drafter/normalize-filepath.test.ts @@ -0,0 +1,229 @@ +import { describe, expect, it } from 'vitest' +import { normalizeFilepath } from '#src/common/config/normalize-filepath.ts' + +const testSuites: Array<{ + // Test cases for normalizeFilepath + input: Parameters + expected: string | Error +}> = [ + { + input: [ + { + filepath: 'release-drafter.yml', + ref: 'main', + repo: { owner: 'cchanche', repo: 'proj' }, + }, + undefined, + ], + expected: '.github/release-drafter.yml', + }, + { + input: [ + { + filepath: '/src/../configs/release-drafter.yml', + ref: 'main', + repo: { owner: 'cchanche', repo: 'proj' }, + }, + undefined, + ], + expected: 'configs/release-drafter.yml', + }, + { + input: [ + { + filepath: '../configs/release-drafter.yml', + ref: 'main', + repo: { owner: 'cchanche', repo: 'proj' }, + }, + undefined, + ], + expected: 'configs/release-drafter.yml', + }, + { + input: [ + { + filepath: 'release-drafter.yml', + ref: 'main', + repo: { owner: 'cchanche', repo: 'proj' }, + }, + { + filepath: 'src/config.yml', + ref: 'main', + repo: { owner: 'cchanche', repo: 'proj' }, + }, + ], + expected: 'src/release-drafter.yml', + }, + { + input: [ + { + filepath: '/absolute/path/to/file.yml', + ref: 'main', + repo: { owner: 'cchanche', repo: 'proj' }, + }, + undefined, + ], + expected: 'absolute/path/to/file.yml', + }, + { + input: [ + { + filepath: '../relative/../file.yml', + ref: 'main', + repo: { owner: 'cchanche', repo: 'proj' }, + }, + { + filepath: 'with/a/parent.yaml', + ref: 'main', + repo: { owner: 'cchanche', repo: 'proj' }, + }, + ], + expected: 'with/file.yml', + }, + { + input: [ + { + filepath: '../very_long_relative/../../../file.yml', + ref: 'main', + repo: { owner: 'cchanche', repo: 'proj' }, + }, + { + filepath: 'with/a/parent.yaml', + ref: 'main', + repo: { owner: 'cchanche', repo: 'proj' }, + }, + ], + expected: '../file.yml', + }, + { + input: [ + { + filepath: '/absolute/path.yaml', + ref: 'main', + repo: { owner: 'cchanche', repo: 'proj' }, + }, + { + filepath: 'with/a/parent.yaml', + ref: 'main', + repo: { owner: 'cchanche', repo: 'proj' }, + }, + ], + expected: 'absolute/path.yaml', + }, + { + input: [ + { + filepath: './here-nearby.json', + ref: 'v6', + repo: { owner: 'cchanche', repo: 'proj' }, + }, + { + filepath: 'with/a/parent.yaml', + ref: 'main', + repo: { owner: 'cchanche', repo: 'proj' }, + }, + ], + expected: '.github/here-nearby.json', + }, + { + input: [ + { + filepath: './here-nearby.json', + ref: 'main', + repo: { owner: 'cchanche', repo: 'proj' }, + }, + { + filepath: 'with/a/parent.yaml', + ref: 'main', + repo: { owner: 'torvalds', repo: 'proj' }, + }, + ], + expected: '.github/here-nearby.json', + }, + { + input: [ + { + filepath: './here-nearby.json', + ref: 'main', + repo: { owner: 'cchanche', repo: 'proj' }, + }, + { + filepath: 'with/a/parent.yaml', + ref: 'main', + repo: { owner: 'cchanche', repo: 'project' }, + }, + ], + expected: '.github/here-nearby.json', + }, + { + input: [ + { + filepath: './here-nearby.json', + ref: 'main', + repo: { owner: 'cchanche', repo: 'proj' }, + }, + { + filepath: 'with/a/parent.yaml', + ref: 'main', + repo: { owner: 'cchanche', repo: 'proj' }, + }, + ], + expected: 'with/a/here-nearby.json', + }, + { + // Should not double-prepend .github/ when filepath already starts with .github/ + input: [ + { + filepath: '.github/release-drafter.yml', + ref: 'main', + repo: { owner: 'ansible', repo: 'team-devtools' }, + }, + { + filepath: '.github/release-drafter.yml', + ref: 'main', + repo: { owner: 'cchanche', repo: 'proj' }, + }, + ], + expected: '.github/release-drafter.yml', + }, + { + // Should not double-prepend .github/ for custom config names either + input: [ + { + filepath: '.github/custom-drafter.yml', + ref: 'main', + repo: { owner: 'ansible', repo: 'team-devtools' }, + }, + { + filepath: '.github/release-drafter.yml', + ref: 'main', + repo: { owner: 'cchanche', repo: 'proj' }, + }, + ], + expected: '.github/custom-drafter.yml', + }, + { + // Should not double-prepend .github/ without parent config + input: [ + { + filepath: '.github/release-drafter.yml', + ref: 'main', + repo: { owner: 'ansible', repo: 'team-devtools' }, + }, + undefined, + ], + expected: '.github/release-drafter.yml', + }, +] + +describe('normalize filepath', () => { + testSuites.forEach(({ input, expected }) => { + it(`should normalize to ${expected instanceof Error ? 'Error' : JSON.stringify(expected)}`, () => { + if (expected instanceof Error) { + expect(() => normalizeFilepath(...input)).toThrowError(expected.message) + } else { + expect(normalizeFilepath(...input)).toBe(expected) + } + }) + }) +}) diff --git a/src/tests/drafter/paginate-graphql.test.ts b/src/tests/drafter/paginate-graphql.test.ts new file mode 100644 index 0000000000..c4b8e54dfc --- /dev/null +++ b/src/tests/drafter/paginate-graphql.test.ts @@ -0,0 +1,117 @@ +import type { graphql } from '@octokit/graphql' +import { paginateGraphQL } from '@octokit/plugin-paginate-graphql' +import { describe, expect, it, vi } from 'vitest' +import type { Octokit } from '#src/common/get-octokit.ts' +import { paginateGraphql } from '#src/common/graphql.ts' +import { TypedDocumentString } from '#src/types/github.graphql.generated.ts' + +type Query = { + repository: { + issues: { + nodes: { title: string }[] + pageInfo: { hasNextPage: boolean; endCursor: string | null } + } + } +} + +type Variables = { owner: string; cursor?: string | null } + +const document = new TypedDocumentString( + `query Issues($owner: String!, $cursor: String) { + repository(owner: $owner, name: "example") { + issues(first: 1, after: $cursor) { + nodes { title } + pageInfo { hasNextPage endCursor } + } + } + }`, +) + +const paginatingClient = (graphqlMock: ReturnType) => + paginateGraphQL({ + graphql: graphqlMock as unknown as typeof graphql, + } as Octokit).graphql + +describe('paginateGraphql', () => { + it('merges multiple pages and advances the cursor', async () => { + const graphqlMock = vi + .fn() + .mockResolvedValueOnce({ + repository: { + issues: { + nodes: [{ title: 'first' }], + pageInfo: { hasNextPage: true, endCursor: 'cursor-1' }, + }, + }, + }) + .mockResolvedValueOnce({ + repository: { + issues: { + nodes: [{ title: 'second' }], + pageInfo: { hasNextPage: false, endCursor: 'cursor-2' }, + }, + }, + }) + + const result = await paginateGraphql( + paginatingClient(graphqlMock), + document, + { + owner: 'release-drafter', + }, + ) + + expect(result.repository.issues.nodes).toEqual([ + { title: 'first' }, + { title: 'second' }, + ]) + expect(graphqlMock).toHaveBeenNthCalledWith(1, document.toString(), { + owner: 'release-drafter', + }) + expect(graphqlMock).toHaveBeenNthCalledWith(2, document.toString(), { + owner: 'release-drafter', + cursor: 'cursor-1', + }) + }) + + it('propagates GraphQL request errors', async () => { + const error = new Error('request failed') + const graphqlMock = vi.fn().mockRejectedValue(error) + + await expect( + paginateGraphql(paginatingClient(graphqlMock), document, { + owner: 'release-drafter', + }), + ).rejects.toBe(error) + }) + + it('rejects responses without pageInfo', async () => { + const graphqlMock = vi.fn().mockResolvedValue({ + repository: { issues: { nodes: [{ title: 'first' }] } }, + }) + + await expect( + paginateGraphql(paginatingClient(graphqlMock), document, { + owner: 'release-drafter', + }), + ).rejects.toThrow('No pageInfo property found in response') + }) + + it('rejects a cursor that does not advance', async () => { + const graphqlMock = vi.fn().mockResolvedValue({ + repository: { + issues: { + nodes: [{ title: 'first' }], + pageInfo: { hasNextPage: true, endCursor: 'cursor-1' }, + }, + }, + }) + + await expect( + paginateGraphql(paginatingClient(graphqlMock), document, { + owner: 'release-drafter', + cursor: 'cursor-1', + }), + ).rejects.toThrow('did not change its value "cursor-1"') + }) +}) diff --git a/src/tests/drafter/parse-categories.test.ts b/src/tests/drafter/parse-categories.test.ts new file mode 100644 index 0000000000..f1a1a06781 --- /dev/null +++ b/src/tests/drafter/parse-categories.test.ts @@ -0,0 +1,542 @@ +import { beforeEach, describe, expect, it } from 'vitest' +import { mergeInputAndConfig } from '#src/actions/drafter/config/index.ts' +import { parseCategories } from '#src/actions/drafter/config/parse-categories.ts' +import { commonConfigSchema } from '#src/actions/drafter/config/schemas/common-config.schema.ts' +import { + configSchema, + configSchemaDefaults, +} from '#src/actions/drafter/config/schemas/config.schema.ts' +import { mocks } from '#tests/mocks/index.ts' + +const migrationDocumentationUrl = + 'https://github.com/release-drafter/release-drafter/pull/1558' + +describe('parseCategories', () => { + beforeEach(() => { + mocks.core.warning.mockClear() + }) + + it('migrates deprecated top-level include/exclude and version-resolver config into categories', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'refs/heads/main', + 'exclude-labels': ['skip-release'], + 'exclude-paths': ['docs/**'], + 'include-labels': ['app'], + 'include-paths': ['src/**'], + 'version-resolver': { + default: 'minor', + major: { labels: ['breaking'] }, + minor: { labels: [] }, + patch: { labels: [] }, + }, + }) + + const parsed = mergeInputAndConfig({ + config, + input: commonConfigSchema.parse({}), + }) + + expect(parsed.categories).toEqual([ + { + type: 'pre-exclude', + when: [ + { + labels: ['skip-release'], + 'labels-mode': 'any', + paths: ['docs/**'], + 'paths-mode': 'any', + }, + ], + }, + { + type: 'pre-include', + when: [ + { + labels: ['app'], + 'labels-mode': 'any', + paths: ['src/**'], + 'paths-mode': 'any', + }, + ], + }, + { + type: 'version-resolver', + 'semver-increment': 'minor', + when: [], + exclusive: false, + }, + { + type: 'version-resolver', + 'semver-increment': 'major', + when: [ + { + labels: ['breaking'], + 'labels-mode': 'any', + paths: [], + 'paths-mode': 'any', + }, + ], + exclusive: false, + }, + ]) + expect(mocks.core.warning).toHaveBeenCalledWith( + expect.stringContaining("deprecated 'exclude-labels' or 'exclude-paths'"), + ) + expect(mocks.core.warning).toHaveBeenCalledWith( + expect.stringContaining("deprecated 'include-labels' or 'include-paths'"), + ) + expect(mocks.core.warning).toHaveBeenCalledWith( + expect.stringContaining("deprecated 'version-resolver.default'"), + ) + expect(mocks.core.warning).toHaveBeenCalledWith( + expect.stringContaining("deprecated 'version-resolver.major.labels'"), + ) + expect(mocks.core.warning).toHaveBeenCalledWith( + expect.stringContaining(migrationDocumentationUrl), + ) + }) + + it('warns when collapse-after is set on non-changelog categories', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'refs/heads/main', + categories: [ + { + type: 'pre-include', + when: { paths: ['src/**'] }, + 'collapse-after': 0, + }, + { + type: 'version-resolver', + when: { label: 'major' }, + 'semver-increment': 'major', + 'collapse-after': 1, + }, + ], + }) + + const parsed = mergeInputAndConfig({ + config, + input: commonConfigSchema.parse({}), + }) + + expect(parsed.categories).toEqual([ + { + type: 'pre-include', + when: [ + { + labels: [], + 'labels-mode': 'any', + paths: ['src/**'], + 'paths-mode': 'any', + }, + ], + }, + { + type: 'version-resolver', + when: [ + { + labels: ['major'], + 'labels-mode': 'any', + paths: [], + 'paths-mode': 'any', + }, + ], + 'semver-increment': 'major', + exclusive: false, + }, + ]) + + expect(mocks.core.warning).toHaveBeenCalledWith( + '"collapse-after" "0" ignored for category of type "pre-include"', + ) + expect(mocks.core.warning).toHaveBeenCalledWith( + '"collapse-after" "1" ignored for category of type "version-resolver"', + ) + }) + + it('applies deprecated category labels to every when branch and warns', () => { + const parsed = parseCategories( + { + categories: [ + { + title: 'Features', + labels: ['feature'], + label: 'enhancement', + when: [{ paths: ['src/**'] }, { path: 'docs/**' }], + }, + ], + }, + { + 'exclude-labels': [], + 'exclude-paths': [], + 'include-labels': [], + 'include-paths': [], + 'version-resolver': configSchemaDefaults['version-resolver'], + }, + ) + + expect(parsed).toEqual([ + { + type: 'changelog', + title: 'Features', + exclusive: false, + 'collapse-after': -1, + 'semver-increment': 'patch', + when: [ + { + labels: ['feature', 'enhancement'], + 'labels-mode': 'any', + paths: ['src/**'], + 'paths-mode': 'any', + }, + { + labels: ['feature', 'enhancement'], + 'labels-mode': 'any', + paths: ['docs/**'], + 'paths-mode': 'any', + }, + ], + }, + ]) + + expect(mocks.core.warning).toHaveBeenCalledWith( + expect.stringContaining( + "deprecated 'categories[*].label' or 'categories[*].labels'", + ), + ) + expect(mocks.core.warning).toHaveBeenCalledWith( + expect.stringContaining(migrationDocumentationUrl), + ) + }) + + it('throws when exclusive is set on pre-categories', () => { + const config = configSchema.parse({ + template: '$CHANGES', + commitish: 'refs/heads/main', + categories: [ + { + type: 'pre-include', + when: { paths: ['src/**'] }, + exclusive: true, + }, + ], + }) + + expect(() => + mergeInputAndConfig({ + config, + input: commonConfigSchema.parse({}), + }), + ).toThrow( + '"exclusive" can only be set on categories of type "changelog" or "version-resolver"; it cannot be used on category of type "pre-include".', + ) + }) + + it('defaults an omitted category type to changelog during parsing', () => { + const parsed = parseCategories( + { + categories: [ + { + title: 'Features', + exclusive: true, + 'collapse-after': 0, + 'semver-increment': 'minor', + when: { + label: 'feature', + }, + }, + ], + }, + { + 'exclude-labels': [], + 'exclude-paths': [], + 'include-labels': [], + 'include-paths': [], + 'version-resolver': configSchemaDefaults['version-resolver'], + }, + ) + + expect(parsed).toEqual([ + { + type: 'changelog', + title: 'Features', + exclusive: true, + 'collapse-after': 0, + 'semver-increment': 'minor', + when: [ + { + labels: ['feature'], + 'labels-mode': 'any', + paths: [], + 'paths-mode': 'any', + }, + ], + }, + ]) + }) + + it('normalizes when.label as a single labels entry', () => { + const parsed = parseCategories( + { + categories: [ + { + title: 'Fixes', + when: { + label: 'bug', + }, + }, + ], + }, + { + 'exclude-labels': [], + 'exclude-paths': [], + 'include-labels': [], + 'include-paths': [], + 'version-resolver': configSchemaDefaults['version-resolver'], + }, + ) + + expect(parsed[0]?.when).toEqual([ + { + labels: ['bug'], + 'labels-mode': 'any', + paths: [], + 'paths-mode': 'any', + }, + ]) + }) + + it('normalizes when.path as a single paths entry', () => { + const parsed = parseCategories( + { + categories: [ + { + title: 'Source changes', + when: { + path: 'src/**', + }, + }, + ], + }, + { + 'exclude-labels': [], + 'exclude-paths': [], + 'include-labels': [], + 'include-paths': [], + 'version-resolver': configSchemaDefaults['version-resolver'], + }, + ) + + expect(parsed[0]?.when).toEqual([ + { + labels: [], + 'labels-mode': 'any', + paths: ['src/**'], + 'paths-mode': 'any', + }, + ]) + }) + + it('combines when.label and when.labels into one label predicate set', () => { + const parsed = parseCategories( + { + categories: [ + { + title: 'Fixes', + when: { + label: 'bug', + labels: ['urgent'], + 'labels-mode': 'all', + }, + }, + ], + }, + { + 'exclude-labels': [], + 'exclude-paths': [], + 'include-labels': [], + 'include-paths': [], + 'version-resolver': configSchemaDefaults['version-resolver'], + }, + ) + + expect(parsed[0]?.when).toEqual([ + { + labels: ['urgent', 'bug'], + 'labels-mode': 'all', + paths: [], + 'paths-mode': 'any', + }, + ]) + }) + + it('combines when.path and when.paths into one path predicate set', () => { + const parsed = parseCategories( + { + categories: [ + { + title: 'Code and docs', + when: { + path: 'docs/**', + paths: ['src/**'], + 'paths-mode': 'all', + }, + }, + ], + }, + { + 'exclude-labels': [], + 'exclude-paths': [], + 'include-labels': [], + 'include-paths': [], + 'version-resolver': configSchemaDefaults['version-resolver'], + }, + ) + + expect(parsed[0]?.when).toEqual([ + { + labels: [], + 'labels-mode': 'any', + paths: ['src/**', 'docs/**'], + 'paths-mode': 'all', + }, + ]) + }) + + it('normalizes conventional matcher shorthands inside when conditions', () => { + const parsed = parseCategories( + { + categories: [ + { + title: 'Features', + when: { + conventional: { + type: 'feat', + scope: 'ui', + breaking: true, + }, + }, + }, + ], + }, + { + 'exclude-labels': [], + 'exclude-paths': [], + 'include-labels': [], + 'include-paths': [], + 'version-resolver': configSchemaDefaults['version-resolver'], + }, + ) + + expect(parsed[0]?.when).toEqual([ + { + labels: [], + 'labels-mode': 'any', + paths: [], + 'paths-mode': 'any', + conventional: { + types: ['feat'], + scopes: ['ui'], + breaking: true, + }, + }, + ]) + }) + + it('normalizes conventional true to any conventional title matcher', () => { + const parsed = parseCategories( + { + categories: [ + { + title: 'Conventional', + when: { conventional: true }, + }, + ], + }, + { + 'exclude-labels': [], + 'exclude-paths': [], + 'include-labels': [], + 'include-paths': [], + 'version-resolver': configSchemaDefaults['version-resolver'], + }, + ) + + expect(parsed[0]?.when).toEqual([ + { + labels: [], + 'labels-mode': 'any', + paths: [], + 'paths-mode': 'any', + conventional: { + types: [], + scopes: [], + breaking: undefined, + }, + }, + ]) + }) + + it('normalizes conventional empty object to any conventional title matcher with warning', () => { + const parsed = parseCategories( + { + categories: [ + { + title: 'Conventional', + when: { conventional: {} }, + }, + ], + }, + { + 'exclude-labels': [], + 'exclude-paths': [], + 'include-labels': [], + 'include-paths': [], + 'version-resolver': configSchemaDefaults['version-resolver'], + }, + ) + + expect(parsed[0]?.when).toEqual([ + { + labels: [], + 'labels-mode': 'any', + paths: [], + 'paths-mode': 'any', + conventional: { + types: [], + scopes: [], + breaking: undefined, + }, + }, + ]) + expect(mocks.core.warning).toHaveBeenCalledWith( + "Use 'conventional: true' instead of 'conventional: {}' to match any conventional title.", + ) + }) + + it('drops conditions that only set labels-mode without configuring labels or paths', () => { + const parsed = parseCategories( + { + categories: [ + { + title: 'Everything else', + when: { + 'labels-mode': 'exactly', + }, + }, + ], + }, + { + 'exclude-labels': [], + 'exclude-paths': [], + 'include-labels': [], + 'include-paths': [], + 'version-resolver': configSchemaDefaults['version-resolver'], + }, + ) + + expect(parsed[0]?.when).toEqual([]) + }) +}) diff --git a/src/tests/drafter/parse-config-target.test.ts b/src/tests/drafter/parse-config-target.test.ts new file mode 100644 index 0000000000..87ad9f1259 --- /dev/null +++ b/src/tests/drafter/parse-config-target.test.ts @@ -0,0 +1,364 @@ +import { describe, expect, it } from 'vitest' +import { + type ConfigTarget, + parseConfigTarget, +} from '#src/common/config/parse-config-target.ts' + +const testSuites: Array<{ + input: [string, Pick] + expected: ConfigTarget | Error + suiteName?: string +}> = [ + { + suiteName: 'basic github target', + input: [ + 'release-drafter.yml', + { repo: { owner: 'cchanche', repo: 'hello-world' }, ref: 'main' }, + ], + expected: { + scheme: 'github', + filepath: 'release-drafter.yml', + ref: 'main', + repo: { owner: 'cchanche', repo: 'hello-world' }, + }, + }, + { + suiteName: 'github target with explicit github: scheme', + input: [ + 'github:release-drafter.yml', + { repo: { owner: 'cchanche', repo: 'hello-world' }, ref: 'main' }, + ], + expected: { + scheme: 'github', + filepath: 'release-drafter.yml', + ref: 'main', + repo: { owner: 'cchanche', repo: 'hello-world' }, + }, + }, + { + suiteName: 'github target with repo specifier', + input: [ + 'github:bye-world:release-drafter.yml', + { repo: { owner: 'cchanche', repo: 'hello-world' }, ref: 'main' }, + ], + expected: { + scheme: 'github', + filepath: 'release-drafter.yml', + ref: undefined, + repo: { owner: 'cchanche', repo: 'bye-world' }, + }, + }, + { + suiteName: 'github target with repo specifier and no explicit ref', + input: [ + 'github:bye-world:release-drafter.yml', + { + repo: { owner: 'cchanche', repo: 'hello-world' }, + ref: 'feature/destroyer-of-worlds', + }, + ], + expected: { + scheme: 'github', + filepath: 'release-drafter.yml', + ref: undefined, + repo: { owner: 'cchanche', repo: 'bye-world' }, + }, + }, + { + suiteName: 'github target with repo and ref specifiers', + input: [ + 'github:bye-world:release-drafter.yml@main', + { + repo: { owner: 'cchanche', repo: 'hello-world' }, + ref: 'feature/destroyer-of-worlds', + }, + ], + expected: { + scheme: 'github', + filepath: 'release-drafter.yml', + ref: 'main', + repo: { owner: 'cchanche', repo: 'bye-world' }, + }, + }, + { + suiteName: + 'github target with repo and ref specifiers, no explicit github: scheme', + input: [ + 'bye-world:release-drafter.yml@main', + { + repo: { owner: 'cchanche', repo: 'hello-world' }, + ref: 'feature/destroyer-of-worlds', + }, + ], + expected: { + scheme: 'github', + filepath: 'release-drafter.yml', + ref: 'main', + repo: { owner: 'cchanche', repo: 'bye-world' }, + }, + }, + { + suiteName: 'github target with owner/repo specifier', + input: [ + 'worlds/bye-world:release-drafter.yml', + { + repo: { owner: 'cchanche', repo: 'hello-world' }, + ref: 'feature/destroyer-of-worlds', + }, + ], + expected: { + scheme: 'github', + filepath: 'release-drafter.yml', + ref: undefined, + repo: { owner: 'worlds', repo: 'bye-world' }, + }, + }, + { + suiteName: 'github target with owner/repo, filepath, and ref', + input: [ + 'worlds/bye-world:release-drafter.yml@main', + { + repo: { owner: 'cchanche', repo: 'hello-world' }, + ref: 'feature/destroyer-of-worlds', + }, + ], + expected: { + scheme: 'github', + filepath: 'release-drafter.yml', + ref: 'main', + repo: { owner: 'worlds', repo: 'bye-world' }, + }, + }, + { + suiteName: 'different config name', + input: [ + 'alternative-config-name.json', + { + repo: { owner: 'cchanche', repo: 'hello-world' }, + ref: 'feature/destroyer-of-worlds', + }, + ], + expected: { + scheme: 'github', + filepath: 'alternative-config-name.json', + ref: 'feature/destroyer-of-worlds', + repo: { owner: 'cchanche', repo: 'hello-world' }, + }, + }, + { + suiteName: 'file scheme', + input: [ + 'file:conf.yaml', + { + repo: { owner: 'does', repo: 'not' }, + ref: 'matter', + }, + ], + expected: { + scheme: 'file', + filepath: 'conf.yaml', + ref: 'matter', + repo: { owner: 'does', repo: 'not' }, + }, + }, + { + suiteName: 'file scheme with @ (invalid)', + input: [ + 'file:conf.yaml@main', + { + repo: { owner: 'does', repo: 'not' }, + ref: 'matter', + }, + ], + expected: new Error( + 'invalid format: "conf.yaml@main". Expected format [github:][owner/repo:]filepath[@ref] or file:filepath. Local file targets cannot have "@" github specifiers.', + ), + }, + { + suiteName: 'repo-only without colon (owner/repo)', + input: [ + 'ansible/team-devtools', + { repo: { owner: 'cchanche', repo: 'hello-world' }, ref: 'main' }, + ], + expected: { + scheme: 'github', + filepath: '', + ref: undefined, + repo: { owner: 'ansible', repo: 'team-devtools' }, + }, + }, + { + suiteName: 'repo-only without colon (repo)', + input: [ + 'team-devtools', + { repo: { owner: 'cchanche', repo: 'hello-world' }, ref: 'main' }, + ], + expected: { + scheme: 'github', + filepath: '', + ref: undefined, + repo: { owner: 'cchanche', repo: 'team-devtools' }, + }, + }, + { + suiteName: 'repo-only without colon with ref (owner/repo@ref)', + input: [ + 'ansible/team-devtools@v2', + { repo: { owner: 'cchanche', repo: 'hello-world' }, ref: 'main' }, + ], + expected: { + scheme: 'github', + filepath: '', + ref: 'v2', + repo: { owner: 'ansible', repo: 'team-devtools' }, + }, + }, + { + suiteName: 'repo-only with colon and no filepath', + input: [ + 'ansible/team-devtools:', + { repo: { owner: 'cchanche', repo: 'hello-world' }, ref: 'main' }, + ], + expected: { + scheme: 'github', + filepath: '', + ref: undefined, + repo: { owner: 'ansible', repo: 'team-devtools' }, + }, + }, + { + suiteName: 'repo-only with explicit github: scheme (owner/repo)', + input: [ + 'github:ansible/team-devtools', + { repo: { owner: 'cchanche', repo: 'hello-world' }, ref: 'main' }, + ], + expected: { + scheme: 'github', + filepath: '', + ref: undefined, + repo: { owner: 'ansible', repo: 'team-devtools' }, + }, + }, + { + suiteName: 'repo-only without owner with ref (repo@ref)', + input: [ + 'team-devtools@v2', + { repo: { owner: 'cchanche', repo: 'hello-world' }, ref: 'main' }, + ], + expected: { + scheme: 'github', + filepath: '', + ref: 'v2', + repo: { owner: 'cchanche', repo: 'team-devtools' }, + }, + }, + { + suiteName: 'repo-only .github special repo', + input: [ + '.github', + { + repo: { owner: 'jenkinsci', repo: 'azure-credentials-plugin' }, + ref: 'main', + }, + ], + expected: { + scheme: 'github', + filepath: '', + ref: undefined, + repo: { owner: 'jenkinsci', repo: '.github' }, + }, + }, + { + suiteName: 'repo-only owner/.github special repo', + input: [ + 'jenkinsci/.github', + { repo: { owner: 'octocat', repo: 'hello-world' }, ref: 'main' }, + ], + expected: { + scheme: 'github', + filepath: '', + ref: undefined, + repo: { owner: 'jenkinsci', repo: '.github' }, + }, + }, + { + suiteName: 'repo-only with colon, ref, and no filepath', + input: [ + 'ansible/team-devtools:@main', + { repo: { owner: 'cchanche', repo: 'hello-world' }, ref: 'develop' }, + ], + expected: { + scheme: 'github', + filepath: '', + ref: 'main', + repo: { owner: 'ansible', repo: 'team-devtools' }, + }, + }, + { + suiteName: 'repo-only with explicit github: scheme and trailing colon', + input: [ + 'github:ansible/team-devtools:', + { repo: { owner: 'cchanche', repo: 'hello-world' }, ref: 'main' }, + ], + expected: { + scheme: 'github', + filepath: '', + ref: undefined, + repo: { owner: 'ansible', repo: 'team-devtools' }, + }, + }, + { + suiteName: 'invalid string with only specifiers', + input: [ + ':@', + { + repo: { owner: 'does', repo: 'not' }, + ref: 'matter', + }, + ], + expected: new Error( + 'invalid format: ":@". Expected format [github:][owner/repo:]filepath[@ref] or file:filepath. Missing ref specifier.', + ), + }, + { + suiteName: 'repo and filepath', + input: [ + 'hello:world', + { + repo: { owner: 'does', repo: 'not' }, + ref: 'matter', + }, + ], + expected: { + scheme: 'github', + filepath: 'world', + ref: undefined, + repo: { owner: 'does', repo: 'hello' }, + }, + }, + { + suiteName: 'invalid string with too many specifiers', + input: [ + 'for:those:who:come:after', + { + repo: { owner: 'does', repo: 'not' }, + ref: 'matter', + }, + ], + expected: new Error( + 'invalid format: "for:those:who:come:after". Expected format [github:][owner/repo:]filepath[@ref] or file:filepath. ":" or "@" was specified more than once.', + ), + }, +] + +describe('parse a config target', () => { + testSuites.forEach(({ input, expected, suiteName }) => { + it(`${suiteName}`, () => { + if (expected instanceof Error) { + expect(() => parseConfigTarget(...input)).toThrowError(expected.message) + } else { + expect(parseConfigTarget(...input)).toEqual(expected) + } + }) + }) +}) diff --git a/src/tests/drafter/render-template.test.ts b/src/tests/drafter/render-template.test.ts new file mode 100644 index 0000000000..a21d67e362 --- /dev/null +++ b/src/tests/drafter/render-template.test.ts @@ -0,0 +1,271 @@ +import { describe, expect, it } from 'vitest' +import { renderTemplate } from '#src/actions/drafter/lib/build-release-payload/render-template/index.ts' + +describe('render template', () => { + it('replaces $A with B', () => { + const output = renderTemplate({ template: '$A', object: { $A: 'B' } }) + + expect(output).toMatchInlineSnapshot(`"B"`) + }) + + it('replaces $MAJOR.$MINOR.$PATCH with 1.0.0', () => { + const output = renderTemplate({ + template: '$MAJOR.$MINOR.$PATCH', + object: { + $MAJOR: 1, + $MINOR: 0, + $PATCH: 0, + }, + }) + + expect(output).toMatchInlineSnapshot(`"1.0.0"`) + }) + + it('replaces $CHANGES but leaves $NEXT_PATCH_VERSION', () => { + const input = `# v$NEXT_PATCH_VERSION + ## CHANGES + + $CHANGES + ` + const output = renderTemplate({ + template: input, + object: { + $CHANGES: 'NO CHANGES', + }, + }) + + expect(output).toEqual(expect.stringContaining('v$NEXT_PATCH_VERSION')) + expect(output).toEqual(expect.stringContaining('NO CHANGES')) + }) + + it('nested template', () => { + const output = renderTemplate({ + template: '$NEXT_MAJOR_VERSION', + object: { + $NEXT_MAJOR_VERSION: { + $MAJOR: 1, + $MINOR: 0, + $PATCH: 0, + $THIRD: { + $NEST: 'THIRD LEVEL', + template: '$NEST', + }, + template: '$MAJOR.$MINOR.$PATCH.$THIRD', + }, + }, + }) + + expect(output).toMatchInlineSnapshot(`"1.0.0.THIRD LEVEL"`) + }) + it('single custom replacer', () => { + const output = renderTemplate({ + template: 'This is my body JENKINS-1234 JENKINS-1234 JENKINS-1234', + object: {}, + replacers: [ + { + search: /\bJENKINS-(\d+)\b/g, + replace: + '[https://issues.jenkins-ci.org/browse/JENKINS-$1](JENKINS-$1)', + }, + ], + }) + + expect(output).toMatchInlineSnapshot( + `"This is my body [https://issues.jenkins-ci.org/browse/JENKINS-1234](JENKINS-1234) [https://issues.jenkins-ci.org/browse/JENKINS-1234](JENKINS-1234) [https://issues.jenkins-ci.org/browse/JENKINS-1234](JENKINS-1234)"`, + ) + }) + it('word custom replacer', () => { + const output = renderTemplate({ + template: 'This is my body JENKINS-1234', + object: {}, + replacers: [ + { + search: /JENKINS/g, + replace: 'heyyyyyyy', + }, + ], + }) + + expect(output).toMatchInlineSnapshot(`"This is my body heyyyyyyy-1234"`) + }) + it('overlapping replacer', () => { + const output = renderTemplate({ + template: 'This is my body JENKINS-1234', + object: {}, + replacers: [ + { + search: /JENKINS/g, + replace: 'heyyyyyyy', + }, + { + search: /heyyyyyyy/g, + replace: 'something else', + }, + ], + }) + + expect(output).toMatchInlineSnapshot( + `"This is my body something else-1234"`, + ) + }) + it('multiple custom replacer', () => { + const output = renderTemplate({ + template: 'This is my body [JENKINS-1234] JENKINS-456', + object: {}, + replacers: [ + { + search: /\bJENKINS-(\d+)\b/g, + replace: + '[https://issues.jenkins-ci.org/browse/JENKINS-$1](JENKINS-$1)', + }, + { + search: + /\[\[https:\/\/issues\.jenkins-ci\.org\/browse\/JENKINS-(\d+)\]\(JENKINS-(\d+)\)\]/g, + replace: + '[https://issues.jenkins-ci.org/browse/JENKINS-$1](JENKINS-$1)', + }, + ], + }) + + expect(output).toMatchInlineSnapshot( + `"This is my body [https://issues.jenkins-ci.org/browse/JENKINS-1234](JENKINS-1234) [https://issues.jenkins-ci.org/browse/JENKINS-456](JENKINS-456)"`, + ) + }) + describe('with advanced substitutions', () => { + it('supports newline, tab, and backslash escapes', () => { + const output = renderTemplate({ + template: 'X', + object: {}, + replacers: [ + { + search: /(X)/g, + replace: '\\n\\t\\\\$1', + }, + ], + }) + + expect(output).toMatchInlineSnapshot(` + " + \\X" + `) + }) + + it('supports $$, $&, and $0 substitutions', () => { + const output = renderTemplate({ + template: 'abc', + object: {}, + replacers: [ + { + search: /(b)/g, + replace: '[$$][$&][$0][$1]', + }, + ], + }) + + expect(output).toMatchInlineSnapshot('"a[$][b][b][b]c"') + }) + + it('supports $n and $nn capture substitutions', () => { + const output = renderTemplate({ + template: 'abcdefghij', + object: {}, + replacers: [ + { + search: /(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)/g, + replace: '$10$1', + }, + ], + }) + + expect(output).toMatchInlineSnapshot('"ja"') + }) + + it('supports case operations on matches', () => { + const output = renderTemplate({ + template: 'clem Nico', + object: {}, + replacers: [ + { + search: /(clem)/g, + replace: '\\u$1', + }, + { + search: /(Nico)/g, + replace: '\\l$1', + }, + ], + }) + + expect(output).toMatchInlineSnapshot('"Clem nico"') + }) + + it('supports case operations across all characters', () => { + const output = renderTemplate({ + template: 'AbC abC', + object: {}, + replacers: [ + { + search: /(AbC)/g, + replace: '\\L$1', + }, + { + search: /(abC)/g, + replace: '\\U$1', + }, + ], + }) + + expect(output).toMatchInlineSnapshot('"abc ABC"') + }) + + it('supports \\E to terminate case operations before subsequent capture groups', () => { + const output = renderTemplate({ + template: 'hello world', + object: {}, + replacers: [ + { + search: /(hello) (world)/g, + replace: '\\U$1\\E $2', + }, + ], + }) + + expect(output).toMatchInlineSnapshot('"HELLO world"') + }) + }) + + describe('semantic prefix replacers (issue #1228)', () => { + const multilineBody = [ + "# What's Changed", + '', + '* feat(foo): Add feature (#1) @user', + '* fix: Bug fix (#2) @user', + '* ci(deps): Update dependencies (#3) @user', + '* chore: Regular maintenance (#4) @user', + ].join('\n') + + it('regex using ^ with multiline flag AND accounting for the "* " prefix strips semantic prefixes', () => { + // An alternative that preserves the `^` intent: capture the `* ` prefix + // together and put it back in the replacement. + const output = renderTemplate({ + template: multilineBody, + object: {}, + replacers: [ + { + search: /^(\* )(fix|feat|ci)(\(.+?\))?: /gm, + replace: '$1', + }, + ], + }) + + expect(output).toMatchInlineSnapshot(` + "# What's Changed + + * Add feature (#1) @user + * Bug fix (#2) @user + * Update dependencies (#3) @user + * chore: Regular maintenance (#4) @user" + `) + }) + }) +}) diff --git a/src/tests/drafter/smoke-config-migration.e2e.test.ts b/src/tests/drafter/smoke-config-migration.e2e.test.ts new file mode 100644 index 0000000000..732b6b5ceb --- /dev/null +++ b/src/tests/drafter/smoke-config-migration.e2e.test.ts @@ -0,0 +1,104 @@ +import { describe, expect, it, vi } from 'vitest' +import { runDrafter } from '#tests/helpers/index.ts' +import type { AvailableConfigs } from '#tests/mocks/config.ts' +import { + mockContext, + mockGraphqlQuery, + mockInput, + mocks, + nockGetReleases, +} from '#tests/mocks/index.ts' + +const runSmokeConfigDryRun = async (params: { + config: AvailableConfigs + releaseFiles: Array<'release' | 'release-draft'> +}) => { + await mockContext('push') + await mockInput('dry-run', 'true') + mocks.config.mockReturnValue(params.config) + + const gqlScope = mockGraphqlQuery({ + payload: 'graphql-comparison-merge-commit', + }) + const releaseScope = nockGetReleases({ + releaseFiles: params.releaseFiles, + }) + + await runDrafter() + + expect(releaseScope.isDone()).toBe(true) + expect(gqlScope.pendingMocks().length).toBe(0) + expect(mocks.postReleaseBody).not.toHaveBeenCalled() + expect(mocks.patchReleaseBody).not.toHaveBeenCalled() + expect(mocks.core.setFailed).not.toHaveBeenCalled() + + return { + outputs: [...mocks.core.setOutput.mock.calls], + dryRunMessages: mocks.core.info.mock.calls + .flat() + .filter((message) => message.includes('[dry-run]')), + } +} + +describe('smoke config migration e2e', () => { + it.each([ + ['create', ['release']], + ['update', ['release', 'release-draft']], + ] as const)('produces identical dry-run outputs for legacy and migrated smoke configs on %s', async (_mode, releaseFiles) => { + const legacy = await runSmokeConfigDryRun({ + config: 'config-with-smoke-test-categories-legacy', + releaseFiles: [...releaseFiles], + }) + + vi.clearAllMocks() + + const migrated = await runSmokeConfigDryRun({ + config: 'config-with-smoke-test-categories-migrated', + releaseFiles: [...releaseFiles], + }) + + expect(migrated).toEqual(legacy) + expect(migrated.outputs).toMatchInlineSnapshot(` + [ + [ + "tag_name", + "v2.0.1", + ], + [ + "name", + "v2.0.1", + ], + [ + "resolved_version", + "2.0.1", + ], + [ + "major_version", + "2", + ], + [ + "minor_version", + "0", + ], + [ + "patch_version", + "1", + ], + [ + "body", + "# What's Changed + + ## Other changes + + * Update dependencies (#4) @TimonVS + * Bug fixes (#3) @TimonVS + * Add big feature (#2) @TimonVS + * 👽 Add alien technology (#1) @TimonVS + + **Full Changelog**: https://github.com/toolmantim/release-drafter-test-project/compare/v2.0.0...v2.0.1 + ", + ], + ] + `) + }) +}) diff --git a/src/tests/fixtures/config/config-autolabeler.yml b/src/tests/fixtures/config/config-autolabeler.yml new file mode 100644 index 0000000000..2f71ad753f --- /dev/null +++ b/src/tests/fixtures/config/config-autolabeler.yml @@ -0,0 +1,16 @@ +autolabeler: + - label: 'chore' + files: + - '*.md' + branch: + - '/docs{0,1}\/.+/' + - label: 'bug' + branch: + - '/fix\/.+/' + title: + - '/fix/i' + - label: 'enhancement' + branch: + - '/feature\/.+/' + body: + - '/JIRA-[0-9]{1,4}/' diff --git a/src/tests/fixtures/config/config-filter-range.yml b/src/tests/fixtures/config/config-filter-range.yml new file mode 100644 index 0000000000..74f47730f3 --- /dev/null +++ b/src/tests/fixtures/config/config-filter-range.yml @@ -0,0 +1,3 @@ +template: | + # There's new stuff! +filter-by-range: ^3.0.0 diff --git a/test/fixtures/config/config-name-input.yml b/src/tests/fixtures/config/config-name-input.yml similarity index 100% rename from test/fixtures/config/config-name-input.yml rename to src/tests/fixtures/config/config-name-input.yml diff --git a/test/fixtures/config/config-non-master-branch.yml b/src/tests/fixtures/config/config-non-master-branch.yml similarity index 100% rename from test/fixtures/config/config-non-master-branch.yml rename to src/tests/fixtures/config/config-non-master-branch.yml diff --git a/test/fixtures/config/config-previous-tag.yml b/src/tests/fixtures/config/config-previous-tag.yml similarity index 100% rename from test/fixtures/config/config-previous-tag.yml rename to src/tests/fixtures/config/config-previous-tag.yml diff --git a/test/fixtures/config/config-with-categories-2.yml b/src/tests/fixtures/config/config-with-categories-2.yml similarity index 100% rename from test/fixtures/config/config-with-categories-2.yml rename to src/tests/fixtures/config/config-with-categories-2.yml diff --git a/test/fixtures/config/config-with-categories-3.yml b/src/tests/fixtures/config/config-with-categories-3.yml similarity index 100% rename from test/fixtures/config/config-with-categories-3.yml rename to src/tests/fixtures/config/config-with-categories-3.yml diff --git a/test/fixtures/config/config-with-categories-4.yml b/src/tests/fixtures/config/config-with-categories-4.yml similarity index 100% rename from test/fixtures/config/config-with-categories-4.yml rename to src/tests/fixtures/config/config-with-categories-4.yml diff --git a/src/tests/fixtures/config/config-with-categories-with-collapse-after.yml b/src/tests/fixtures/config/config-with-categories-with-collapse-after.yml new file mode 100644 index 0000000000..bd4f4c1144 --- /dev/null +++ b/src/tests/fixtures/config/config-with-categories-with-collapse-after.yml @@ -0,0 +1,18 @@ +template: | + # What's Changed + + $CHANGES + +categories: + - title: 🚀 All the things! + collapse-after: 3 + labels: + - feature + - enhancement + - fix + - bugfix + - bug + - sentry + - skip-changelog + - title: 👻 Maintenance + label: chore diff --git a/src/tests/fixtures/config/config-with-categories-with-other-category.yml b/src/tests/fixtures/config/config-with-categories-with-other-category.yml new file mode 100644 index 0000000000..4017b63c7d --- /dev/null +++ b/src/tests/fixtures/config/config-with-categories-with-other-category.yml @@ -0,0 +1,13 @@ +template: | + # What's Changed + + $CHANGES + +categories: + - title: 🚀 Features + labels: + - feature + - title: 🐛 Bug Fixes + labels: + - fix + - title: 📝 Other Changes diff --git a/test/fixtures/config/config-with-categories.yml b/src/tests/fixtures/config/config-with-categories.yml similarity index 100% rename from test/fixtures/config/config-with-categories.yml rename to src/tests/fixtures/config/config-with-categories.yml diff --git a/src/tests/fixtures/config/config-with-category-pre-include-paths.yml b/src/tests/fixtures/config/config-with-category-pre-include-paths.yml new file mode 100644 index 0000000000..6b9cee8b39 --- /dev/null +++ b/src/tests/fixtures/config/config-with-category-pre-include-paths.yml @@ -0,0 +1,11 @@ +template: | + # What's Changed + $CHANGES +name-template: 'v$RESOLVED_VERSION (Code name: Placeholder)' +tag-template: v$RESOLVED_VERSION +version-template: $MAJOR.$MINOR.$PATCH +categories: + - type: pre-include + when: + paths: + - src/5.md diff --git a/src/tests/fixtures/config/config-with-category-version-resolver-major.yml b/src/tests/fixtures/config/config-with-category-version-resolver-major.yml new file mode 100644 index 0000000000..54f7e18cfd --- /dev/null +++ b/src/tests/fixtures/config/config-with-category-version-resolver-major.yml @@ -0,0 +1,36 @@ +name-template: 'v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' +branches: + - master +categories: + - title: '🚀 Features' + when: + label: 'feature' + - title: '🐛 Bug fixes' + when: + label: 'bug' + - title: '📄 Documentation' + when: + label: 'documentation' + - title: '🧰 Maintenance' + when: + label: 'maintenance' + - type: pre-exclude + when: + label: 'skip-changelog' + - type: version-resolver + 'semver-increment': major + when: + label: 'major' + - type: version-resolver + 'semver-increment': minor + when: + label: 'minor' + - type: version-resolver + 'semver-increment': patch + when: + label: 'patch' + - type: version-resolver + 'semver-increment': minor +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' +template: dummy diff --git a/test/fixtures/config/config-with-changes-templates-and-body.yml b/src/tests/fixtures/config/config-with-changes-templates-and-body.yml similarity index 100% rename from test/fixtures/config/config-with-changes-templates-and-body.yml rename to src/tests/fixtures/config/config-with-changes-templates-and-body.yml diff --git a/test/fixtures/config/config-with-changes-templates-and-url.yml b/src/tests/fixtures/config/config-with-changes-templates-and-url.yml similarity index 100% rename from test/fixtures/config/config-with-changes-templates-and-url.yml rename to src/tests/fixtures/config/config-with-changes-templates-and-url.yml diff --git a/test/fixtures/config/config-with-changes-templates.yml b/src/tests/fixtures/config/config-with-changes-templates.yml similarity index 100% rename from test/fixtures/config/config-with-changes-templates.yml rename to src/tests/fixtures/config/config-with-changes-templates.yml diff --git a/src/tests/fixtures/config/config-with-commitish.yml b/src/tests/fixtures/config/config-with-commitish.yml new file mode 100644 index 0000000000..a71736c7ea --- /dev/null +++ b/src/tests/fixtures/config/config-with-commitish.yml @@ -0,0 +1,2 @@ +commitish: 'staging' +template: 'dummy' diff --git a/src/tests/fixtures/config/config-with-compare-link.yml b/src/tests/fixtures/config/config-with-compare-link.yml new file mode 100644 index 0000000000..f726960e7d --- /dev/null +++ b/src/tests/fixtures/config/config-with-compare-link.yml @@ -0,0 +1,12 @@ +template: | + # What's Changed + + $CHANGES + + **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION + +categories: + - label: feature + title: 🚀 Features + - label: fix + title: 🐛 Bug Fixes diff --git a/src/tests/fixtures/config/config-with-component-helpers-custom.yml b/src/tests/fixtures/config/config-with-component-helpers-custom.yml new file mode 100644 index 0000000000..b4e9f8c1d5 --- /dev/null +++ b/src/tests/fixtures/config/config-with-component-helpers-custom.yml @@ -0,0 +1,24 @@ +template: | + Component helpers with custom template: + - MAJOR: $NEXT_MAJOR_VERSION + - MAJOR_MAJOR: $NEXT_MAJOR_VERSION_MAJOR + - MAJOR_MINOR: $NEXT_MAJOR_VERSION_MINOR + - MAJOR_PATCH: $NEXT_MAJOR_VERSION_PATCH + + - MINOR: $NEXT_MINOR_VERSION + - MINOR_MAJOR: $NEXT_MINOR_VERSION_MAJOR + - MINOR_MINOR: $NEXT_MINOR_VERSION_MINOR + - MINOR_PATCH: $NEXT_MINOR_VERSION_PATCH + + - PATCH: $NEXT_PATCH_VERSION + - PATCH_MAJOR: $NEXT_PATCH_VERSION_MAJOR + - PATCH_MINOR: $NEXT_PATCH_VERSION_MINOR + - PATCH_PATCH: $NEXT_PATCH_VERSION_PATCH + + - PRERELEASE: $NEXT_PRERELEASE_VERSION + + - Resolved : $RESOLVED_VERSION +name-template: 'Release Drafter v$NEXT_PATCH_VERSION' +tag-template: 'v$NEXT_PATCH_VERSION' +version-template: + 'Major: $MAJOR, Minor: $MINOR, Patch: $PATCH, Prerelease: $PRERELEASE' diff --git a/src/tests/fixtures/config/config-with-component-helpers-default.yml b/src/tests/fixtures/config/config-with-component-helpers-default.yml new file mode 100644 index 0000000000..f06fa27075 --- /dev/null +++ b/src/tests/fixtures/config/config-with-component-helpers-default.yml @@ -0,0 +1,23 @@ +template: | + Component helpers with default template behavior: + - MAJOR: $NEXT_MAJOR_VERSION + - MAJOR_MAJOR: $NEXT_MAJOR_VERSION_MAJOR + - MAJOR_MINOR: $NEXT_MAJOR_VERSION_MINOR + - MAJOR_PATCH: $NEXT_MAJOR_VERSION_PATCH + + - MINOR: $NEXT_MINOR_VERSION + - MINOR_MAJOR: $NEXT_MINOR_VERSION_MAJOR + - MINOR_MINOR: $NEXT_MINOR_VERSION_MINOR + - MINOR_PATCH: $NEXT_MINOR_VERSION_PATCH + + - PATCH: $NEXT_PATCH_VERSION + - PATCH_MAJOR: $NEXT_PATCH_VERSION_MAJOR + - PATCH_MINOR: $NEXT_PATCH_VERSION_MINOR + - PATCH_PATCH: $NEXT_PATCH_VERSION_PATCH + + - PRERELEASE: $NEXT_PRERELEASE_VERSION + - PRERELEASE_PRE: $NEXT_PRERELEASE_VERSION_PRERELEASE + + - Resolved : $RESOLVED_VERSION +name-template: 'Release Drafter v$NEXT_PATCH_VERSION' +tag-template: 'v$NEXT_PATCH_VERSION' diff --git a/src/tests/fixtures/config/config-with-component-helpers-major-minor.yml b/src/tests/fixtures/config/config-with-component-helpers-major-minor.yml new file mode 100644 index 0000000000..6d56e5d52b --- /dev/null +++ b/src/tests/fixtures/config/config-with-component-helpers-major-minor.yml @@ -0,0 +1,24 @@ +template: | + Component helpers with $MAJOR.$MINOR template: + - MAJOR: $NEXT_MAJOR_VERSION + - MAJOR_MAJOR: $NEXT_MAJOR_VERSION_MAJOR + - MAJOR_MINOR: $NEXT_MAJOR_VERSION_MINOR + - MAJOR_PATCH: $NEXT_MAJOR_VERSION_PATCH + + - MINOR: $NEXT_MINOR_VERSION + - MINOR_MAJOR: $NEXT_MINOR_VERSION_MAJOR + - MINOR_MINOR: $NEXT_MINOR_VERSION_MINOR + - MINOR_PATCH: $NEXT_MINOR_VERSION_PATCH + + - PATCH: $NEXT_PATCH_VERSION + - PATCH_MAJOR: $NEXT_PATCH_VERSION_MAJOR + - PATCH_MINOR: $NEXT_PATCH_VERSION_MINOR + - PATCH_PATCH: $NEXT_PATCH_VERSION_PATCH + + - PRERELEASE: $NEXT_PRERELEASE_VERSION + - PRERELEASE_PRE: $NEXT_PRERELEASE_VERSION_PRERELEASE + + - Resolved : $RESOLVED_VERSION +name-template: 'Release Drafter v$NEXT_PATCH_VERSION' +tag-template: 'v$NEXT_PATCH_VERSION' +version-template: '$MAJOR.$MINOR' diff --git a/src/tests/fixtures/config/config-with-component-helpers-major.yml b/src/tests/fixtures/config/config-with-component-helpers-major.yml new file mode 100644 index 0000000000..cd26ac5c48 --- /dev/null +++ b/src/tests/fixtures/config/config-with-component-helpers-major.yml @@ -0,0 +1,24 @@ +template: | + Component helpers with $MAJOR template: + - MAJOR: $NEXT_MAJOR_VERSION + - MAJOR_MAJOR: $NEXT_MAJOR_VERSION_MAJOR + - MAJOR_MINOR: $NEXT_MAJOR_VERSION_MINOR + - MAJOR_PATCH: $NEXT_MAJOR_VERSION_PATCH + + - MINOR: $NEXT_MINOR_VERSION + - MINOR_MAJOR: $NEXT_MINOR_VERSION_MAJOR + - MINOR_MINOR: $NEXT_MINOR_VERSION_MINOR + - MINOR_PATCH: $NEXT_MINOR_VERSION_PATCH + + - PATCH: $NEXT_PATCH_VERSION + - PATCH_MAJOR: $NEXT_PATCH_VERSION_MAJOR + - PATCH_MINOR: $NEXT_PATCH_VERSION_MINOR + - PATCH_PATCH: $NEXT_PATCH_VERSION_PATCH + + - PRERELEASE: $NEXT_PRERELEASE_VERSION + - PRERELEASE_PRE: $NEXT_PRERELEASE_VERSION_PRERELEASE + + - Resolved : $RESOLVED_VERSION +name-template: 'Release Drafter v$NEXT_MAJOR_VERSION' +tag-template: 'v$NEXT_MAJOR_VERSION' +version-template: '$MAJOR' diff --git a/src/tests/fixtures/config/config-with-component-helpers-prerelease-identifier.yml b/src/tests/fixtures/config/config-with-component-helpers-prerelease-identifier.yml new file mode 100644 index 0000000000..cf3a07a594 --- /dev/null +++ b/src/tests/fixtures/config/config-with-component-helpers-prerelease-identifier.yml @@ -0,0 +1,25 @@ +template: | + Component helpers with prerelease and identifier: + - MAJOR: $NEXT_MAJOR_VERSION + - MAJOR_MAJOR: $NEXT_MAJOR_VERSION_MAJOR + - MAJOR_MINOR: $NEXT_MAJOR_VERSION_MINOR + - MAJOR_PATCH: $NEXT_MAJOR_VERSION_PATCH + + - MINOR: $NEXT_MINOR_VERSION + - MINOR_MAJOR: $NEXT_MINOR_VERSION_MAJOR + - MINOR_MINOR: $NEXT_MINOR_VERSION_MINOR + - MINOR_PATCH: $NEXT_MINOR_VERSION_PATCH + + - PATCH: $NEXT_PATCH_VERSION + - PATCH_MAJOR: $NEXT_PATCH_VERSION_MAJOR + - PATCH_MINOR: $NEXT_PATCH_VERSION_MINOR + - PATCH_PATCH: $NEXT_PATCH_VERSION_PATCH + + - PRERELEASE: $NEXT_PRERELEASE_VERSION + - PRERELEASE_PRE: $NEXT_PRERELEASE_VERSION_PRERELEASE + + - Resolved : $RESOLVED_VERSION +name-template: 'Release Drafter v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' +prerelease: true +prerelease-identifier: 'beta' diff --git a/src/tests/fixtures/config/config-with-component-helpers-prerelease.yml b/src/tests/fixtures/config/config-with-component-helpers-prerelease.yml new file mode 100644 index 0000000000..1f57df9fb4 --- /dev/null +++ b/src/tests/fixtures/config/config-with-component-helpers-prerelease.yml @@ -0,0 +1,24 @@ +template: | + Component helpers with prerelease: + - MAJOR: $NEXT_MAJOR_VERSION + - MAJOR_MAJOR: $NEXT_MAJOR_VERSION_MAJOR + - MAJOR_MINOR: $NEXT_MAJOR_VERSION_MINOR + - MAJOR_PATCH: $NEXT_MAJOR_VERSION_PATCH + + - MINOR: $NEXT_MINOR_VERSION + - MINOR_MAJOR: $NEXT_MINOR_VERSION_MAJOR + - MINOR_MINOR: $NEXT_MINOR_VERSION_MINOR + - MINOR_PATCH: $NEXT_MINOR_VERSION_PATCH + + - PATCH: $NEXT_PATCH_VERSION + - PATCH_MAJOR: $NEXT_PATCH_VERSION_MAJOR + - PATCH_MINOR: $NEXT_PATCH_VERSION_MINOR + - PATCH_PATCH: $NEXT_PATCH_VERSION_PATCH + + - PRERELEASE: $NEXT_PRERELEASE_VERSION + - PRERELEASE_PRE: $NEXT_PRERELEASE_VERSION_PRERELEASE + + - Resolved : $RESOLVED_VERSION +name-template: 'Release Drafter v$NEXT_PATCH_VERSION' +tag-template: 'v$NEXT_PATCH_VERSION' +prerelease: true diff --git a/test/fixtures/config/config-with-contributors.yml b/src/tests/fixtures/config/config-with-contributors.yml similarity index 55% rename from test/fixtures/config/config-with-contributors.yml rename to src/tests/fixtures/config/config-with-contributors.yml index 4676aa8a20..e45e8b9a8e 100644 --- a/test/fixtures/config/config-with-contributors.yml +++ b/src/tests/fixtures/config/config-with-contributors.yml @@ -1 +1,2 @@ template: 'A big thanks to: $CONTRIBUTORS' +no-contributors-template: 'Nobody' diff --git a/test/fixtures/config/config-with-custom-version-resolver-major.yml b/src/tests/fixtures/config/config-with-custom-version-resolver-major.yml similarity index 100% rename from test/fixtures/config/config-with-custom-version-resolver-major.yml rename to src/tests/fixtures/config/config-with-custom-version-resolver-major.yml diff --git a/test/fixtures/config/config-with-custom-version-resolver-minor.yml b/src/tests/fixtures/config/config-with-custom-version-resolver-minor.yml similarity index 100% rename from test/fixtures/config/config-with-custom-version-resolver-minor.yml rename to src/tests/fixtures/config/config-with-custom-version-resolver-minor.yml diff --git a/test/fixtures/config/config-with-custom-version-resolver-none.yml b/src/tests/fixtures/config/config-with-custom-version-resolver-none.yml similarity index 100% rename from test/fixtures/config/config-with-custom-version-resolver-none.yml rename to src/tests/fixtures/config/config-with-custom-version-resolver-none.yml diff --git a/src/tests/fixtures/config/config-with-custom-version-resolver-partial.yml b/src/tests/fixtures/config/config-with-custom-version-resolver-partial.yml new file mode 100644 index 0000000000..9394819d68 --- /dev/null +++ b/src/tests/fixtures/config/config-with-custom-version-resolver-partial.yml @@ -0,0 +1,22 @@ +name-template: 'v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' +branches: + - master +categories: + - title: '🚀 Features' + label: 'feature' + - title: '🐛 Bug fixes' + label: 'bug' + - title: '📄 Documentation' + label: 'documentation' + - title: '🧰 Maintenance' + label: 'maintenance' +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' +template: dummy +version-resolver: + major: + labels: + - 'major' + default: minor +exclude-labels: + - skip-changelog diff --git a/test/fixtures/config/config-with-custom-version-resolver-patch.yml b/src/tests/fixtures/config/config-with-custom-version-resolver-patch.yml similarity index 100% rename from test/fixtures/config/config-with-custom-version-resolver-patch.yml rename to src/tests/fixtures/config/config-with-custom-version-resolver-patch.yml diff --git a/src/tests/fixtures/config/config-with-exclude-contributors.yml b/src/tests/fixtures/config/config-with-exclude-contributors.yml new file mode 100644 index 0000000000..09af10b104 --- /dev/null +++ b/src/tests/fixtures/config/config-with-exclude-contributors.yml @@ -0,0 +1,4 @@ +exclude-contributors: + - TimonVS + - Ada Lovelace +template: 'A big thanks to: $CONTRIBUTORS' diff --git a/test/fixtures/config/config-with-exclude-labels.yml b/src/tests/fixtures/config/config-with-exclude-labels.yml similarity index 100% rename from test/fixtures/config/config-with-exclude-labels.yml rename to src/tests/fixtures/config/config-with-exclude-labels.yml diff --git a/src/tests/fixtures/config/config-with-exclude-paths.yml b/src/tests/fixtures/config/config-with-exclude-paths.yml new file mode 100644 index 0000000000..b79a06e8c6 --- /dev/null +++ b/src/tests/fixtures/config/config-with-exclude-paths.yml @@ -0,0 +1,8 @@ +template: | + # What's Changed + $CHANGES +name-template: 'v$RESOLVED_VERSION (Code name: Placeholder)' +tag-template: v$RESOLVED_VERSION +version-template: $MAJOR.$MINOR.$PATCH +exclude-paths: + - some/path diff --git a/src/tests/fixtures/config/config-with-footer-template.yml b/src/tests/fixtures/config/config-with-footer-template.yml new file mode 100644 index 0000000000..1fe7ba3916 --- /dev/null +++ b/src/tests/fixtures/config/config-with-footer-template.yml @@ -0,0 +1,5 @@ +template: | + This is the template in the middle + +footer: | + This is at bottom diff --git a/src/tests/fixtures/config/config-with-header-and-footer-no-nl-no-space-template.yml b/src/tests/fixtures/config/config-with-header-and-footer-no-nl-no-space-template.yml new file mode 100644 index 0000000000..f363f91083 --- /dev/null +++ b/src/tests/fixtures/config/config-with-header-and-footer-no-nl-no-space-template.yml @@ -0,0 +1,3 @@ +template: This is the template in the middle +header: This is at top +footer: This is at bottom diff --git a/src/tests/fixtures/config/config-with-header-and-footer-template.yml b/src/tests/fixtures/config/config-with-header-and-footer-template.yml new file mode 100644 index 0000000000..903202e16d --- /dev/null +++ b/src/tests/fixtures/config/config-with-header-and-footer-template.yml @@ -0,0 +1,8 @@ +template: | + This is the template in the middle + +header: | + This is at top + +footer: | + This is at bottom diff --git a/src/tests/fixtures/config/config-with-header-template.yml b/src/tests/fixtures/config/config-with-header-template.yml new file mode 100644 index 0000000000..4f7681dd05 --- /dev/null +++ b/src/tests/fixtures/config/config-with-header-template.yml @@ -0,0 +1,5 @@ +template: | + This is the template in the middle + +header: | + This is at top diff --git a/src/tests/fixtures/config/config-with-history-limit.yml b/src/tests/fixtures/config/config-with-history-limit.yml new file mode 100644 index 0000000000..1450c1af3a --- /dev/null +++ b/src/tests/fixtures/config/config-with-history-limit.yml @@ -0,0 +1,2 @@ +template: '$CHANGES' +history-limit: 42 diff --git a/src/tests/fixtures/config/config-with-include-exclude-paths.yml b/src/tests/fixtures/config/config-with-include-exclude-paths.yml new file mode 100644 index 0000000000..a10e8dd69b --- /dev/null +++ b/src/tests/fixtures/config/config-with-include-exclude-paths.yml @@ -0,0 +1,10 @@ +template: | + # What's Changed + $CHANGES +name-template: 'v$RESOLVED_VERSION (Code name: Placeholder)' +tag-template: v$RESOLVED_VERSION +version-template: $MAJOR.$MINOR.$PATCH +include-paths: + - src/5.md +exclude-paths: + - src/5.md diff --git a/test/fixtures/config/config-with-include-labels.yml b/src/tests/fixtures/config/config-with-include-labels.yml similarity index 100% rename from test/fixtures/config/config-with-include-labels.yml rename to src/tests/fixtures/config/config-with-include-labels.yml diff --git a/src/tests/fixtures/config/config-with-include-paths.yml b/src/tests/fixtures/config/config-with-include-paths.yml new file mode 100644 index 0000000000..65ad7e14f2 --- /dev/null +++ b/src/tests/fixtures/config/config-with-include-paths.yml @@ -0,0 +1,8 @@ +template: | + # What's Changed + $CHANGES +name-template: 'v$RESOLVED_VERSION (Code name: Placeholder)' +tag-template: v$RESOLVED_VERSION +version-template: $MAJOR.$MINOR.$PATCH +include-paths: + - src/5.md diff --git a/src/tests/fixtures/config/config-with-include-pre-releases-false.yml b/src/tests/fixtures/config/config-with-include-pre-releases-false.yml new file mode 100644 index 0000000000..89d420dd3b --- /dev/null +++ b/src/tests/fixtures/config/config-with-include-pre-releases-false.yml @@ -0,0 +1,7 @@ +name-template: 'v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' +template: | + # What's Changed + + $CHANGES +include-pre-releases: false diff --git a/src/tests/fixtures/config/config-with-include-pre-releases-true.yml b/src/tests/fixtures/config/config-with-include-pre-releases-true.yml new file mode 100644 index 0000000000..01e9b1221d --- /dev/null +++ b/src/tests/fixtures/config/config-with-include-pre-releases-true.yml @@ -0,0 +1,7 @@ +name-template: 'v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' +template: | + # What's Changed + + $CHANGES +include-pre-releases: true diff --git a/src/tests/fixtures/config/config-with-input-version-template.yml b/src/tests/fixtures/config/config-with-input-version-template.yml new file mode 100644 index 0000000000..5df4986a0f --- /dev/null +++ b/src/tests/fixtures/config/config-with-input-version-template.yml @@ -0,0 +1,7 @@ +template: + Placeholder with example. Automatically calculated values based on previous + releases are next major=$NEXT_MAJOR_VERSION, minor=$NEXT_MINOR_VERSION, + patch=$NEXT_PATCH_VERSION. Manual input version is $RESOLVED_VERSION. +name-template: 'v$RESOLVED_VERSION (Code name: Placeholder)' +tag-template: v$RESOLVED_VERSION +version-template: $MAJOR.$MINOR.$PATCH diff --git a/src/tests/fixtures/config/config-with-major-minor-patch-version-template.yml b/src/tests/fixtures/config/config-with-major-minor-patch-version-template.yml new file mode 100644 index 0000000000..cbd1eba70d --- /dev/null +++ b/src/tests/fixtures/config/config-with-major-minor-patch-version-template.yml @@ -0,0 +1,11 @@ +template: + Placeholder with example. Automatically calculated values are next + major=$NEXT_MAJOR_VERSION (major=$NEXT_MAJOR_VERSION_MAJOR, + minor=$NEXT_MAJOR_VERSION_MINOR, patch=$NEXT_MAJOR_VERSION_PATCH), + minor=$NEXT_MINOR_VERSION (major=$NEXT_MINOR_VERSION_MAJOR, + minor=$NEXT_MINOR_VERSION_MINOR, patch=$NEXT_MINOR_VERSION_PATCH), + patch=$NEXT_PATCH_VERSION (major=$NEXT_PATCH_VERSION_MAJOR, + minor=$NEXT_PATCH_VERSION_MINOR, patch=$NEXT_PATCH_VERSION_PATCH) +name-template: 'v$NEXT_PATCH_VERSION (Code name: Placeholder)' +tag-template: v$NEXT_PATCH_VERSION +version-template: $MAJOR.$MINOR.$PATCH diff --git a/src/tests/fixtures/config/config-with-major-minor-version-template.yml b/src/tests/fixtures/config/config-with-major-minor-version-template.yml new file mode 100644 index 0000000000..8f1e7ec260 --- /dev/null +++ b/src/tests/fixtures/config/config-with-major-minor-version-template.yml @@ -0,0 +1,11 @@ +template: + Placeholder with example. Automatically calculated values are next + major=$NEXT_MAJOR_VERSION (major=$NEXT_MAJOR_VERSION_MAJOR, + minor=$NEXT_MAJOR_VERSION_MINOR, patch=$NEXT_MAJOR_VERSION_PATCH), + minor=$NEXT_MINOR_VERSION (major=$NEXT_MINOR_VERSION_MAJOR, + minor=$NEXT_MINOR_VERSION_MINOR, patch=$NEXT_MINOR_VERSION_PATCH), + patch=$NEXT_PATCH_VERSION (major=$NEXT_PATCH_VERSION_MAJOR, + minor=$NEXT_PATCH_VERSION_MINOR, patch=$NEXT_PATCH_VERSION_PATCH) +name-template: 'v$NEXT_MINOR_VERSION (Code name: Placeholder)' +tag-template: v$NEXT_MINOR_VERSION +version-template: $MAJOR.$MINOR diff --git a/src/tests/fixtures/config/config-with-major-version-template.yml b/src/tests/fixtures/config/config-with-major-version-template.yml new file mode 100644 index 0000000000..7edfff33a8 --- /dev/null +++ b/src/tests/fixtures/config/config-with-major-version-template.yml @@ -0,0 +1,11 @@ +template: + Placeholder with example. Automatically calculated values are next + major=$NEXT_MAJOR_VERSION (major=$NEXT_MAJOR_VERSION_MAJOR, + minor=$NEXT_MAJOR_VERSION_MINOR, patch=$NEXT_MAJOR_VERSION_PATCH), + minor=$NEXT_MINOR_VERSION (major=$NEXT_MINOR_VERSION_MAJOR, + minor=$NEXT_MINOR_VERSION_MINOR, patch=$NEXT_MINOR_VERSION_PATCH), + patch=$NEXT_PATCH_VERSION (major=$NEXT_PATCH_VERSION_MAJOR, + minor=$NEXT_PATCH_VERSION_MINOR, patch=$NEXT_PATCH_VERSION_PATCH) +name-template: 'v$NEXT_MAJOR_VERSION (Code name: Placeholder)' +tag-template: v$NEXT_MAJOR_VERSION +version-template: $MAJOR diff --git a/src/tests/fixtures/config/config-with-name-and-tag-template.yml b/src/tests/fixtures/config/config-with-name-and-tag-template.yml new file mode 100644 index 0000000000..f0fe2cea30 --- /dev/null +++ b/src/tests/fixtures/config/config-with-name-and-tag-template.yml @@ -0,0 +1,6 @@ +template: + Placeholder with example. Automatically calculated values based on previous + releases are next major=$NEXT_MAJOR_VERSION, minor=$NEXT_MINOR_VERSION, + patch=$NEXT_PATCH_VERSION. +name-template: 'v$RESOLVED_VERSION-beta (Code name: Hello World)' +tag-template: v$RESOLVED_VERSION-beta diff --git a/src/tests/fixtures/config/config-with-next-versioning.yml b/src/tests/fixtures/config/config-with-next-versioning.yml new file mode 100644 index 0000000000..f8a20bae49 --- /dev/null +++ b/src/tests/fixtures/config/config-with-next-versioning.yml @@ -0,0 +1,10 @@ +template: + Placeholder with example. Automatically calculated values are next + major=$NEXT_MAJOR_VERSION (major=$NEXT_MAJOR_VERSION_MAJOR, + minor=$NEXT_MAJOR_VERSION_MINOR, patch=$NEXT_MAJOR_VERSION_PATCH), + minor=$NEXT_MINOR_VERSION (major=$NEXT_MINOR_VERSION_MAJOR, + minor=$NEXT_MINOR_VERSION_MINOR, patch=$NEXT_MINOR_VERSION_PATCH), + patch=$NEXT_PATCH_VERSION (major=$NEXT_PATCH_VERSION_MAJOR, + minor=$NEXT_PATCH_VERSION_MINOR, patch=$NEXT_PATCH_VERSION_PATCH) +name-template: 'v$NEXT_PATCH_VERSION (Code name: Placeholder)' +tag-template: v$NEXT_PATCH_VERSION diff --git a/src/tests/fixtures/config/config-with-pre-release-identifier.yml b/src/tests/fixtures/config/config-with-pre-release-identifier.yml new file mode 100644 index 0000000000..0ac5971c55 --- /dev/null +++ b/src/tests/fixtures/config/config-with-pre-release-identifier.yml @@ -0,0 +1,4 @@ +template: This is a Pre-release with identifier. +name-template: 'v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' +prerelease-identifier: alpha diff --git a/test/fixtures/config/config-with-prerelease.yml b/src/tests/fixtures/config/config-with-prerelease.yml similarity index 100% rename from test/fixtures/config/config-with-prerelease.yml rename to src/tests/fixtures/config/config-with-prerelease.yml diff --git a/src/tests/fixtures/config/config-with-pull-request-limit.yml b/src/tests/fixtures/config/config-with-pull-request-limit.yml new file mode 100644 index 0000000000..5d6bebddc3 --- /dev/null +++ b/src/tests/fixtures/config/config-with-pull-request-limit.yml @@ -0,0 +1,2 @@ +template: '$CHANGES' +pull-request-limit: 34 diff --git a/test/fixtures/config/config-with-replacers.yml b/src/tests/fixtures/config/config-with-replacers.yml similarity index 100% rename from test/fixtures/config/config-with-replacers.yml rename to src/tests/fixtures/config/config-with-replacers.yml diff --git a/test/fixtures/config/config-with-resolved-version-template.yml b/src/tests/fixtures/config/config-with-resolved-version-template.yml similarity index 100% rename from test/fixtures/config/config-with-resolved-version-template.yml rename to src/tests/fixtures/config/config-with-resolved-version-template.yml diff --git a/test/fixtures/config/config-with-schema-error.yml b/src/tests/fixtures/config/config-with-schema-error.yml similarity index 100% rename from test/fixtures/config/config-with-schema-error.yml rename to src/tests/fixtures/config/config-with-schema-error.yml diff --git a/src/tests/fixtures/config/config-with-smoke-test-categories-legacy.yml b/src/tests/fixtures/config/config-with-smoke-test-categories-legacy.yml new file mode 100644 index 0000000000..af22748006 --- /dev/null +++ b/src/tests/fixtures/config/config-with-smoke-test-categories-legacy.yml @@ -0,0 +1,42 @@ +name-template: 'v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' +template: | + # What's Changed + + $CHANGES + + **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION + +categories: + - title: 'Breaking' + label: 'type: breaking' + - title: 'New' + label: 'type: feature' + - title: 'Bug Fixes' + label: 'type: bug' + - title: 'Maintenance' + label: 'type: maintenance' + - title: 'Documentation' + label: 'type: docs' + - title: 'Other changes' + - title: 'Dependency Updates' + label: 'type: dependencies' + collapse-after: 5 + +version-resolver: + major: + labels: + - 'type: breaking' + minor: + labels: + - 'type: feature' + patch: + labels: + - 'type: bug' + - 'type: maintenance' + - 'type: docs' + - 'type: dependencies' + - 'type: security' + +exclude-labels: + - 'skip-changelog' diff --git a/src/tests/fixtures/config/config-with-smoke-test-categories-migrated.yml b/src/tests/fixtures/config/config-with-smoke-test-categories-migrated.yml new file mode 100644 index 0000000000..87d5d148dd --- /dev/null +++ b/src/tests/fixtures/config/config-with-smoke-test-categories-migrated.yml @@ -0,0 +1,43 @@ +name-template: 'v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' +template: | + # What's Changed + + $CHANGES + + **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION + +categories: + - title: 'Breaking' + semver-increment: 'major' + when: + label: 'type: breaking' + - title: 'New' + semver-increment: 'minor' + when: + label: 'type: feature' + - title: 'Bug Fixes' + semver-increment: 'patch' + when: + label: 'type: bug' + - title: 'Maintenance' + semver-increment: 'patch' + when: + label: 'type: maintenance' + - title: 'Documentation' + semver-increment: 'patch' + when: + label: 'type: docs' + - title: 'Other changes' + - title: 'Dependency Updates' + semver-increment: 'patch' + when: + label: 'type: dependencies' + collapse-after: 5 + - type: 'version-resolver' + semver-increment: 'patch' + when: + label: 'type: security' + - type: 'pre-exclude' + when: + label: 'skip-changelog' diff --git a/test/fixtures/config/config-with-sort-by-title.yml b/src/tests/fixtures/config/config-with-sort-by-title.yml similarity index 100% rename from test/fixtures/config/config-with-sort-by-title.yml rename to src/tests/fixtures/config/config-with-sort-by-title.yml diff --git a/test/fixtures/config/config-with-sort-direction-ascending.yml b/src/tests/fixtures/config/config-with-sort-direction-ascending.yml similarity index 100% rename from test/fixtures/config/config-with-sort-direction-ascending.yml rename to src/tests/fixtures/config/config-with-sort-direction-ascending.yml diff --git a/src/tests/fixtures/config/config-with-tag-prefix.yml b/src/tests/fixtures/config/config-with-tag-prefix.yml new file mode 100644 index 0000000000..3d29de8b22 --- /dev/null +++ b/src/tests/fixtures/config/config-with-tag-prefix.yml @@ -0,0 +1,7 @@ +name-template: 'static-tag-prefix-v$RESOLVED_VERSION 🌈' +tag-template: 'static-tag-prefix-v$RESOLVED_VERSION' +tag-prefix: static-tag-prefix-v +template: | + ## Previous release + + $PREVIOUS_TAG diff --git a/test/fixtures/config/config-with-yaml-exception.yml b/src/tests/fixtures/config/config-with-yaml-exception.yml similarity index 100% rename from test/fixtures/config/config-with-yaml-exception.yml rename to src/tests/fixtures/config/config-with-yaml-exception.yml diff --git a/test/fixtures/config/config-without-prerelease.yml b/src/tests/fixtures/config/config-without-prerelease.yml similarity index 100% rename from test/fixtures/config/config-without-prerelease.yml rename to src/tests/fixtures/config/config-without-prerelease.yml diff --git a/test/fixtures/config/config.yml b/src/tests/fixtures/config/config.yml similarity index 100% rename from test/fixtures/config/config.yml rename to src/tests/fixtures/config/config.yml diff --git a/src/tests/fixtures/events/pull_request-synchronize.json b/src/tests/fixtures/events/pull_request-synchronize.json new file mode 100644 index 0000000000..32e73fe473 --- /dev/null +++ b/src/tests/fixtures/events/pull_request-synchronize.json @@ -0,0 +1,567 @@ +{ + "action": "synchronize", + "after": "e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "before": "d38c14adc1db69f0f496e67da0e4f2dd8fd015ca", + "number": 1475, + "organization": { + "avatar_url": "https://avatars.githubusercontent.com/u/58292376?v=4", + "description": "", + "events_url": "https://api.github.com/orgs/release-drafter/events", + "hooks_url": "https://api.github.com/orgs/release-drafter/hooks", + "id": 58292376, + "issues_url": "https://api.github.com/orgs/release-drafter/issues", + "login": "release-drafter", + "members_url": "https://api.github.com/orgs/release-drafter/members{/member}", + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU4MjkyMzc2", + "public_members_url": "https://api.github.com/orgs/release-drafter/public_members{/member}", + "repos_url": "https://api.github.com/orgs/release-drafter/repos", + "url": "https://api.github.com/orgs/release-drafter" + }, + "pull_request": { + "_links": { + "comments": { + "href": "https://api.github.com/repos/release-drafter/release-drafter/issues/1475/comments" + }, + "commits": { + "href": "https://api.github.com/repos/release-drafter/release-drafter/pulls/1475/commits" + }, + "html": { + "href": "https://github.com/release-drafter/release-drafter/pull/1475" + }, + "issue": { + "href": "https://api.github.com/repos/release-drafter/release-drafter/issues/1475" + }, + "review_comment": { + "href": "https://api.github.com/repos/release-drafter/release-drafter/pulls/comments{/number}" + }, + "review_comments": { + "href": "https://api.github.com/repos/release-drafter/release-drafter/pulls/1475/comments" + }, + "self": { + "href": "https://api.github.com/repos/release-drafter/release-drafter/pulls/1475" + }, + "statuses": { + "href": "https://api.github.com/repos/release-drafter/release-drafter/statuses/e3c3ac3e28b6b32e849895782416a8fc9df47a11" + } + }, + "active_lock_reason": null, + "additions": 132597, + "assignee": null, + "assignees": [], + "author_association": "COLLABORATOR", + "auto_merge": null, + "base": { + "label": "release-drafter:master", + "ref": "master", + "repo": { + "allow_auto_merge": true, + "allow_forking": true, + "allow_merge_commit": false, + "allow_rebase_merge": true, + "allow_squash_merge": true, + "allow_update_branch": true, + "archive_url": "https://api.github.com/repos/release-drafter/release-drafter/{archive_format}{/ref}", + "archived": false, + "assignees_url": "https://api.github.com/repos/release-drafter/release-drafter/assignees{/user}", + "blobs_url": "https://api.github.com/repos/release-drafter/release-drafter/git/blobs{/sha}", + "branches_url": "https://api.github.com/repos/release-drafter/release-drafter/branches{/branch}", + "clone_url": "https://github.com/release-drafter/release-drafter.git", + "collaborators_url": "https://api.github.com/repos/release-drafter/release-drafter/collaborators{/collaborator}", + "comments_url": "https://api.github.com/repos/release-drafter/release-drafter/comments{/number}", + "commits_url": "https://api.github.com/repos/release-drafter/release-drafter/commits{/sha}", + "compare_url": "https://api.github.com/repos/release-drafter/release-drafter/compare/{base}...{head}", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/{+path}", + "contributors_url": "https://api.github.com/repos/release-drafter/release-drafter/contributors", + "created_at": "2018-06-28T13:31:05Z", + "default_branch": "master", + "delete_branch_on_merge": true, + "deployments_url": "https://api.github.com/repos/release-drafter/release-drafter/deployments", + "description": "Drafts your next release notes as pull requests are merged into master. ", + "disabled": false, + "downloads_url": "https://api.github.com/repos/release-drafter/release-drafter/downloads", + "events_url": "https://api.github.com/repos/release-drafter/release-drafter/events", + "fork": false, + "forks": 364, + "forks_count": 364, + "forks_url": "https://api.github.com/repos/release-drafter/release-drafter/forks", + "full_name": "release-drafter/release-drafter", + "git_commits_url": "https://api.github.com/repos/release-drafter/release-drafter/git/commits{/sha}", + "git_refs_url": "https://api.github.com/repos/release-drafter/release-drafter/git/refs{/sha}", + "git_tags_url": "https://api.github.com/repos/release-drafter/release-drafter/git/tags{/sha}", + "git_url": "git://github.com/release-drafter/release-drafter.git", + "has_discussions": true, + "has_downloads": true, + "has_issues": true, + "has_pages": false, + "has_projects": false, + "has_pull_requests": true, + "has_wiki": false, + "homepage": "https://github.com/marketplace/actions/release-drafter", + "hooks_url": "https://api.github.com/repos/release-drafter/release-drafter/hooks", + "html_url": "https://github.com/release-drafter/release-drafter", + "id": 139022359, + "is_template": false, + "issue_comment_url": "https://api.github.com/repos/release-drafter/release-drafter/issues/comments{/number}", + "issue_events_url": "https://api.github.com/repos/release-drafter/release-drafter/issues/events{/number}", + "issues_url": "https://api.github.com/repos/release-drafter/release-drafter/issues{/number}", + "keys_url": "https://api.github.com/repos/release-drafter/release-drafter/keys{/key_id}", + "labels_url": "https://api.github.com/repos/release-drafter/release-drafter/labels{/name}", + "language": "JavaScript", + "languages_url": "https://api.github.com/repos/release-drafter/release-drafter/languages", + "license": { + "key": "isc", + "name": "ISC License", + "node_id": "MDc6TGljZW5zZTEw", + "spdx_id": "ISC", + "url": "https://api.github.com/licenses/isc" + }, + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE", + "merges_url": "https://api.github.com/repos/release-drafter/release-drafter/merges", + "milestones_url": "https://api.github.com/repos/release-drafter/release-drafter/milestones{/number}", + "mirror_url": null, + "name": "release-drafter", + "node_id": "MDEwOlJlcG9zaXRvcnkxMzkwMjIzNTk=", + "notifications_url": "https://api.github.com/repos/release-drafter/release-drafter/notifications{?since,all,participating}", + "open_issues": 170, + "open_issues_count": 170, + "owner": { + "avatar_url": "https://avatars.githubusercontent.com/u/58292376?v=4", + "events_url": "https://api.github.com/users/release-drafter/events{/privacy}", + "followers_url": "https://api.github.com/users/release-drafter/followers", + "following_url": "https://api.github.com/users/release-drafter/following{/other_user}", + "gists_url": "https://api.github.com/users/release-drafter/gists{/gist_id}", + "gravatar_id": "", + "html_url": "https://github.com/release-drafter", + "id": 58292376, + "login": "release-drafter", + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU4MjkyMzc2", + "organizations_url": "https://api.github.com/users/release-drafter/orgs", + "received_events_url": "https://api.github.com/users/release-drafter/received_events", + "repos_url": "https://api.github.com/users/release-drafter/repos", + "site_admin": false, + "starred_url": "https://api.github.com/users/release-drafter/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/release-drafter/subscriptions", + "type": "Organization", + "url": "https://api.github.com/users/release-drafter", + "user_view_type": "public" + }, + "private": false, + "pull_request_creation_policy": "all", + "pulls_url": "https://api.github.com/repos/release-drafter/release-drafter/pulls{/number}", + "pushed_at": "2026-02-17T11:26:56Z", + "releases_url": "https://api.github.com/repos/release-drafter/release-drafter/releases{/id}", + "size": 30643, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "ssh_url": "git@github.com:release-drafter/release-drafter.git", + "stargazers_count": 3831, + "stargazers_url": "https://api.github.com/repos/release-drafter/release-drafter/stargazers", + "statuses_url": "https://api.github.com/repos/release-drafter/release-drafter/statuses/{sha}", + "subscribers_url": "https://api.github.com/repos/release-drafter/release-drafter/subscribers", + "subscription_url": "https://api.github.com/repos/release-drafter/release-drafter/subscription", + "svn_url": "https://github.com/release-drafter/release-drafter", + "tags_url": "https://api.github.com/repos/release-drafter/release-drafter/tags", + "teams_url": "https://api.github.com/repos/release-drafter/release-drafter/teams", + "topics": [ + "action", + "github-app", + "hacktoberfest", + "probot-app", + "release-automation", + "release-notes" + ], + "trees_url": "https://api.github.com/repos/release-drafter/release-drafter/git/trees{/sha}", + "updated_at": "2026-02-14T20:17:52Z", + "url": "https://api.github.com/repos/release-drafter/release-drafter", + "use_squash_pr_title_as_default": false, + "visibility": "public", + "watchers": 3831, + "watchers_count": 3831, + "web_commit_signoff_required": false + }, + "sha": "40bb31b945af999c14bc1339eae4c0371d97cac7", + "user": { + "avatar_url": "https://avatars.githubusercontent.com/u/58292376?v=4", + "events_url": "https://api.github.com/users/release-drafter/events{/privacy}", + "followers_url": "https://api.github.com/users/release-drafter/followers", + "following_url": "https://api.github.com/users/release-drafter/following{/other_user}", + "gists_url": "https://api.github.com/users/release-drafter/gists{/gist_id}", + "gravatar_id": "", + "html_url": "https://github.com/release-drafter", + "id": 58292376, + "login": "release-drafter", + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU4MjkyMzc2", + "organizations_url": "https://api.github.com/users/release-drafter/orgs", + "received_events_url": "https://api.github.com/users/release-drafter/received_events", + "repos_url": "https://api.github.com/users/release-drafter/repos", + "site_admin": false, + "starred_url": "https://api.github.com/users/release-drafter/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/release-drafter/subscriptions", + "type": "Organization", + "url": "https://api.github.com/users/release-drafter", + "user_view_type": "public" + } + }, + "body": "see #1476\r\n\r\nSo far, I focused on implementing atomic features, and immediately implement corresponding tests.\r\n\r\nIf you have prior experience in refactoring such a codebase, please share 🙏😊\r\n\r\nMy intent for this new version is to :\r\n\r\n- [x] Introduce node 24 (runtime & development)\r\n- [x] Transition to Typescript\r\n- [x] Transition to ESM\r\n- [x] Split drafter and autolabeler into two separate actions (same repo)\r\n- [x] Drop Probot and drop support for the (legacy) app\r\n- [x] Read config from runner instead of / on top of default branch\r\n- [x] Port core v6 feature\r\n- [x] Config loading strategy\r\n - [x] Support v6 loading (probot config plugin)\r\n - [x] Config from runner\r\n- [x] Port every test\r\n- [x] JSON schema from configs\r\n- [ ] Cleanup docs\r\n- [x] CI/CD\r\n\r\nfixes #335\r\nfixes #1338\r\nfixes #1460\r\nfixes #1334\r\nfixes #1274\r\nfixes #1014\r\nfixes #336\r\nsuperseds #1423 \r\nhelps with #1450\r\nhelps with #1469\r\nhelps with #1335\r\nhelps with #1313\r\n\r\nI believe this will simplify maintenance, and working on the following will be easier :\r\n\r\n- [ ] Introduce drafting changes with individual commits 🆕 \r\n - #1448, #672, #552, #98\r\n - development : #1409\r\n- [ ] Introduce [conventional commits](https://www.conventionalcommits.org/) support ! 🆕\r\n - #1438\r\n - categorize changes based on the conventional spec instead of labels\r\n - suggest version increments based on the conventional spec", + "changed_files": 347, + "closed_at": null, + "comments": 6, + "comments_url": "https://api.github.com/repos/release-drafter/release-drafter/issues/1475/comments", + "commits": 79, + "commits_url": "https://api.github.com/repos/release-drafter/release-drafter/pulls/1475/commits", + "created_at": "2026-01-12T17:07:32Z", + "deletions": 209249, + "diff_url": "https://github.com/release-drafter/release-drafter/pull/1475.diff", + "draft": true, + "head": { + "label": "release-drafter:refactor/use-typescript-template", + "ref": "refactor/use-typescript-template", + "repo": { + "allow_auto_merge": true, + "allow_forking": true, + "allow_merge_commit": false, + "allow_rebase_merge": true, + "allow_squash_merge": true, + "allow_update_branch": true, + "archive_url": "https://api.github.com/repos/release-drafter/release-drafter/{archive_format}{/ref}", + "archived": false, + "assignees_url": "https://api.github.com/repos/release-drafter/release-drafter/assignees{/user}", + "blobs_url": "https://api.github.com/repos/release-drafter/release-drafter/git/blobs{/sha}", + "branches_url": "https://api.github.com/repos/release-drafter/release-drafter/branches{/branch}", + "clone_url": "https://github.com/release-drafter/release-drafter.git", + "collaborators_url": "https://api.github.com/repos/release-drafter/release-drafter/collaborators{/collaborator}", + "comments_url": "https://api.github.com/repos/release-drafter/release-drafter/comments{/number}", + "commits_url": "https://api.github.com/repos/release-drafter/release-drafter/commits{/sha}", + "compare_url": "https://api.github.com/repos/release-drafter/release-drafter/compare/{base}...{head}", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/{+path}", + "contributors_url": "https://api.github.com/repos/release-drafter/release-drafter/contributors", + "created_at": "2018-06-28T13:31:05Z", + "default_branch": "master", + "delete_branch_on_merge": true, + "deployments_url": "https://api.github.com/repos/release-drafter/release-drafter/deployments", + "description": "Drafts your next release notes as pull requests are merged into master. ", + "disabled": false, + "downloads_url": "https://api.github.com/repos/release-drafter/release-drafter/downloads", + "events_url": "https://api.github.com/repos/release-drafter/release-drafter/events", + "fork": false, + "forks": 364, + "forks_count": 364, + "forks_url": "https://api.github.com/repos/release-drafter/release-drafter/forks", + "full_name": "release-drafter/release-drafter", + "git_commits_url": "https://api.github.com/repos/release-drafter/release-drafter/git/commits{/sha}", + "git_refs_url": "https://api.github.com/repos/release-drafter/release-drafter/git/refs{/sha}", + "git_tags_url": "https://api.github.com/repos/release-drafter/release-drafter/git/tags{/sha}", + "git_url": "git://github.com/release-drafter/release-drafter.git", + "has_discussions": true, + "has_downloads": true, + "has_issues": true, + "has_pages": false, + "has_projects": false, + "has_pull_requests": true, + "has_wiki": false, + "homepage": "https://github.com/marketplace/actions/release-drafter", + "hooks_url": "https://api.github.com/repos/release-drafter/release-drafter/hooks", + "html_url": "https://github.com/release-drafter/release-drafter", + "id": 139022359, + "is_template": false, + "issue_comment_url": "https://api.github.com/repos/release-drafter/release-drafter/issues/comments{/number}", + "issue_events_url": "https://api.github.com/repos/release-drafter/release-drafter/issues/events{/number}", + "issues_url": "https://api.github.com/repos/release-drafter/release-drafter/issues{/number}", + "keys_url": "https://api.github.com/repos/release-drafter/release-drafter/keys{/key_id}", + "labels_url": "https://api.github.com/repos/release-drafter/release-drafter/labels{/name}", + "language": "JavaScript", + "languages_url": "https://api.github.com/repos/release-drafter/release-drafter/languages", + "license": { + "key": "isc", + "name": "ISC License", + "node_id": "MDc6TGljZW5zZTEw", + "spdx_id": "ISC", + "url": "https://api.github.com/licenses/isc" + }, + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE", + "merges_url": "https://api.github.com/repos/release-drafter/release-drafter/merges", + "milestones_url": "https://api.github.com/repos/release-drafter/release-drafter/milestones{/number}", + "mirror_url": null, + "name": "release-drafter", + "node_id": "MDEwOlJlcG9zaXRvcnkxMzkwMjIzNTk=", + "notifications_url": "https://api.github.com/repos/release-drafter/release-drafter/notifications{?since,all,participating}", + "open_issues": 170, + "open_issues_count": 170, + "owner": { + "avatar_url": "https://avatars.githubusercontent.com/u/58292376?v=4", + "events_url": "https://api.github.com/users/release-drafter/events{/privacy}", + "followers_url": "https://api.github.com/users/release-drafter/followers", + "following_url": "https://api.github.com/users/release-drafter/following{/other_user}", + "gists_url": "https://api.github.com/users/release-drafter/gists{/gist_id}", + "gravatar_id": "", + "html_url": "https://github.com/release-drafter", + "id": 58292376, + "login": "release-drafter", + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU4MjkyMzc2", + "organizations_url": "https://api.github.com/users/release-drafter/orgs", + "received_events_url": "https://api.github.com/users/release-drafter/received_events", + "repos_url": "https://api.github.com/users/release-drafter/repos", + "site_admin": false, + "starred_url": "https://api.github.com/users/release-drafter/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/release-drafter/subscriptions", + "type": "Organization", + "url": "https://api.github.com/users/release-drafter", + "user_view_type": "public" + }, + "private": false, + "pull_request_creation_policy": "all", + "pulls_url": "https://api.github.com/repos/release-drafter/release-drafter/pulls{/number}", + "pushed_at": "2026-02-17T11:26:56Z", + "releases_url": "https://api.github.com/repos/release-drafter/release-drafter/releases{/id}", + "size": 30643, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "ssh_url": "git@github.com:release-drafter/release-drafter.git", + "stargazers_count": 3831, + "stargazers_url": "https://api.github.com/repos/release-drafter/release-drafter/stargazers", + "statuses_url": "https://api.github.com/repos/release-drafter/release-drafter/statuses/{sha}", + "subscribers_url": "https://api.github.com/repos/release-drafter/release-drafter/subscribers", + "subscription_url": "https://api.github.com/repos/release-drafter/release-drafter/subscription", + "svn_url": "https://github.com/release-drafter/release-drafter", + "tags_url": "https://api.github.com/repos/release-drafter/release-drafter/tags", + "teams_url": "https://api.github.com/repos/release-drafter/release-drafter/teams", + "topics": [ + "action", + "github-app", + "hacktoberfest", + "probot-app", + "release-automation", + "release-notes" + ], + "trees_url": "https://api.github.com/repos/release-drafter/release-drafter/git/trees{/sha}", + "updated_at": "2026-02-14T20:17:52Z", + "url": "https://api.github.com/repos/release-drafter/release-drafter", + "use_squash_pr_title_as_default": false, + "visibility": "public", + "watchers": 3831, + "watchers_count": 3831, + "web_commit_signoff_required": false + }, + "sha": "e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "user": { + "avatar_url": "https://avatars.githubusercontent.com/u/58292376?v=4", + "events_url": "https://api.github.com/users/release-drafter/events{/privacy}", + "followers_url": "https://api.github.com/users/release-drafter/followers", + "following_url": "https://api.github.com/users/release-drafter/following{/other_user}", + "gists_url": "https://api.github.com/users/release-drafter/gists{/gist_id}", + "gravatar_id": "", + "html_url": "https://github.com/release-drafter", + "id": 58292376, + "login": "release-drafter", + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU4MjkyMzc2", + "organizations_url": "https://api.github.com/users/release-drafter/orgs", + "received_events_url": "https://api.github.com/users/release-drafter/received_events", + "repos_url": "https://api.github.com/users/release-drafter/repos", + "site_admin": false, + "starred_url": "https://api.github.com/users/release-drafter/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/release-drafter/subscriptions", + "type": "Organization", + "url": "https://api.github.com/users/release-drafter", + "user_view_type": "public" + } + }, + "html_url": "https://github.com/release-drafter/release-drafter/pull/1475", + "id": 3166630748, + "issue_url": "https://api.github.com/repos/release-drafter/release-drafter/issues/1475", + "labels": [ + { + "color": "f4a69a", + "default": false, + "description": "breaking change", + "id": 2101130304, + "name": "type: breaking", + "node_id": "MDU6TGFiZWwyMTAxMTMwMzA0", + "url": "https://api.github.com/repos/release-drafter/release-drafter/labels/type:%20breaking" + } + ], + "locked": false, + "maintainer_can_modify": false, + "merge_commit_sha": "047e917c46e9ce4574d10b03b1e988d12577f1e4", + "mergeable": null, + "mergeable_state": "unknown", + "merged": false, + "merged_at": null, + "merged_by": null, + "milestone": null, + "node_id": "PR_kwDOCElQF868vvNc", + "number": 1475, + "patch_url": "https://github.com/release-drafter/release-drafter/pull/1475.patch", + "rebaseable": null, + "requested_reviewers": [], + "requested_teams": [], + "review_comment_url": "https://api.github.com/repos/release-drafter/release-drafter/pulls/comments{/number}", + "review_comments": 16, + "review_comments_url": "https://api.github.com/repos/release-drafter/release-drafter/pulls/1475/comments", + "state": "open", + "statuses_url": "https://api.github.com/repos/release-drafter/release-drafter/statuses/e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "title": "break/v7", + "updated_at": "2026-02-17T11:26:57Z", + "url": "https://api.github.com/repos/release-drafter/release-drafter/pulls/1475", + "user": { + "avatar_url": "https://avatars.githubusercontent.com/u/67628843?v=4", + "events_url": "https://api.github.com/users/cchanche/events{/privacy}", + "followers_url": "https://api.github.com/users/cchanche/followers", + "following_url": "https://api.github.com/users/cchanche/following{/other_user}", + "gists_url": "https://api.github.com/users/cchanche/gists{/gist_id}", + "gravatar_id": "", + "html_url": "https://github.com/cchanche", + "id": 67628843, + "login": "cchanche", + "node_id": "MDQ6VXNlcjY3NjI4ODQz", + "organizations_url": "https://api.github.com/users/cchanche/orgs", + "received_events_url": "https://api.github.com/users/cchanche/received_events", + "repos_url": "https://api.github.com/users/cchanche/repos", + "site_admin": false, + "starred_url": "https://api.github.com/users/cchanche/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/cchanche/subscriptions", + "type": "User", + "url": "https://api.github.com/users/cchanche", + "user_view_type": "public" + } + }, + "repository": { + "allow_forking": true, + "archive_url": "https://api.github.com/repos/release-drafter/release-drafter/{archive_format}{/ref}", + "archived": false, + "assignees_url": "https://api.github.com/repos/release-drafter/release-drafter/assignees{/user}", + "blobs_url": "https://api.github.com/repos/release-drafter/release-drafter/git/blobs{/sha}", + "branches_url": "https://api.github.com/repos/release-drafter/release-drafter/branches{/branch}", + "clone_url": "https://github.com/release-drafter/release-drafter.git", + "collaborators_url": "https://api.github.com/repos/release-drafter/release-drafter/collaborators{/collaborator}", + "comments_url": "https://api.github.com/repos/release-drafter/release-drafter/comments{/number}", + "commits_url": "https://api.github.com/repos/release-drafter/release-drafter/commits{/sha}", + "compare_url": "https://api.github.com/repos/release-drafter/release-drafter/compare/{base}...{head}", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/{+path}", + "contributors_url": "https://api.github.com/repos/release-drafter/release-drafter/contributors", + "created_at": "2018-06-28T13:31:05Z", + "custom_properties": {}, + "default_branch": "master", + "deployments_url": "https://api.github.com/repos/release-drafter/release-drafter/deployments", + "description": "Drafts your next release notes as pull requests are merged into master. ", + "disabled": false, + "downloads_url": "https://api.github.com/repos/release-drafter/release-drafter/downloads", + "events_url": "https://api.github.com/repos/release-drafter/release-drafter/events", + "fork": false, + "forks": 364, + "forks_count": 364, + "forks_url": "https://api.github.com/repos/release-drafter/release-drafter/forks", + "full_name": "release-drafter/release-drafter", + "git_commits_url": "https://api.github.com/repos/release-drafter/release-drafter/git/commits{/sha}", + "git_refs_url": "https://api.github.com/repos/release-drafter/release-drafter/git/refs{/sha}", + "git_tags_url": "https://api.github.com/repos/release-drafter/release-drafter/git/tags{/sha}", + "git_url": "git://github.com/release-drafter/release-drafter.git", + "has_discussions": true, + "has_downloads": true, + "has_issues": true, + "has_pages": false, + "has_projects": false, + "has_pull_requests": true, + "has_wiki": false, + "homepage": "https://github.com/marketplace/actions/release-drafter", + "hooks_url": "https://api.github.com/repos/release-drafter/release-drafter/hooks", + "html_url": "https://github.com/release-drafter/release-drafter", + "id": 139022359, + "is_template": false, + "issue_comment_url": "https://api.github.com/repos/release-drafter/release-drafter/issues/comments{/number}", + "issue_events_url": "https://api.github.com/repos/release-drafter/release-drafter/issues/events{/number}", + "issues_url": "https://api.github.com/repos/release-drafter/release-drafter/issues{/number}", + "keys_url": "https://api.github.com/repos/release-drafter/release-drafter/keys{/key_id}", + "labels_url": "https://api.github.com/repos/release-drafter/release-drafter/labels{/name}", + "language": "JavaScript", + "languages_url": "https://api.github.com/repos/release-drafter/release-drafter/languages", + "license": { + "key": "isc", + "name": "ISC License", + "node_id": "MDc6TGljZW5zZTEw", + "spdx_id": "ISC", + "url": "https://api.github.com/licenses/isc" + }, + "merges_url": "https://api.github.com/repos/release-drafter/release-drafter/merges", + "milestones_url": "https://api.github.com/repos/release-drafter/release-drafter/milestones{/number}", + "mirror_url": null, + "name": "release-drafter", + "node_id": "MDEwOlJlcG9zaXRvcnkxMzkwMjIzNTk=", + "notifications_url": "https://api.github.com/repos/release-drafter/release-drafter/notifications{?since,all,participating}", + "open_issues": 170, + "open_issues_count": 170, + "owner": { + "avatar_url": "https://avatars.githubusercontent.com/u/58292376?v=4", + "events_url": "https://api.github.com/users/release-drafter/events{/privacy}", + "followers_url": "https://api.github.com/users/release-drafter/followers", + "following_url": "https://api.github.com/users/release-drafter/following{/other_user}", + "gists_url": "https://api.github.com/users/release-drafter/gists{/gist_id}", + "gravatar_id": "", + "html_url": "https://github.com/release-drafter", + "id": 58292376, + "login": "release-drafter", + "node_id": "MDEyOk9yZ2FuaXphdGlvbjU4MjkyMzc2", + "organizations_url": "https://api.github.com/users/release-drafter/orgs", + "received_events_url": "https://api.github.com/users/release-drafter/received_events", + "repos_url": "https://api.github.com/users/release-drafter/repos", + "site_admin": false, + "starred_url": "https://api.github.com/users/release-drafter/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/release-drafter/subscriptions", + "type": "Organization", + "url": "https://api.github.com/users/release-drafter", + "user_view_type": "public" + }, + "private": false, + "pull_request_creation_policy": "all", + "pulls_url": "https://api.github.com/repos/release-drafter/release-drafter/pulls{/number}", + "pushed_at": "2026-02-17T11:26:56Z", + "releases_url": "https://api.github.com/repos/release-drafter/release-drafter/releases{/id}", + "size": 30643, + "ssh_url": "git@github.com:release-drafter/release-drafter.git", + "stargazers_count": 3831, + "stargazers_url": "https://api.github.com/repos/release-drafter/release-drafter/stargazers", + "statuses_url": "https://api.github.com/repos/release-drafter/release-drafter/statuses/{sha}", + "subscribers_url": "https://api.github.com/repos/release-drafter/release-drafter/subscribers", + "subscription_url": "https://api.github.com/repos/release-drafter/release-drafter/subscription", + "svn_url": "https://github.com/release-drafter/release-drafter", + "tags_url": "https://api.github.com/repos/release-drafter/release-drafter/tags", + "teams_url": "https://api.github.com/repos/release-drafter/release-drafter/teams", + "topics": [ + "action", + "github-app", + "hacktoberfest", + "probot-app", + "release-automation", + "release-notes" + ], + "trees_url": "https://api.github.com/repos/release-drafter/release-drafter/git/trees{/sha}", + "updated_at": "2026-02-14T20:17:52Z", + "url": "https://api.github.com/repos/release-drafter/release-drafter", + "visibility": "public", + "watchers": 3831, + "watchers_count": 3831, + "web_commit_signoff_required": false + }, + "sender": { + "avatar_url": "https://avatars.githubusercontent.com/u/67628843?v=4", + "events_url": "https://api.github.com/users/cchanche/events{/privacy}", + "followers_url": "https://api.github.com/users/cchanche/followers", + "following_url": "https://api.github.com/users/cchanche/following{/other_user}", + "gists_url": "https://api.github.com/users/cchanche/gists{/gist_id}", + "gravatar_id": "", + "html_url": "https://github.com/cchanche", + "id": 67628843, + "login": "cchanche", + "node_id": "MDQ6VXNlcjY3NjI4ODQz", + "organizations_url": "https://api.github.com/users/cchanche/orgs", + "received_events_url": "https://api.github.com/users/cchanche/received_events", + "repos_url": "https://api.github.com/users/cchanche/repos", + "site_admin": false, + "starred_url": "https://api.github.com/users/cchanche/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/cchanche/subscriptions", + "type": "User", + "url": "https://api.github.com/users/cchanche", + "user_view_type": "public" + } +} diff --git a/test/fixtures/push-non-master-branch.json b/src/tests/fixtures/events/push-non-master-branch.json similarity index 100% rename from test/fixtures/push-non-master-branch.json rename to src/tests/fixtures/events/push-non-master-branch.json diff --git a/test/fixtures/push-tag.json b/src/tests/fixtures/events/push-tag.json similarity index 100% rename from test/fixtures/push-tag.json rename to src/tests/fixtures/events/push-tag.json diff --git a/test/fixtures/push.json b/src/tests/fixtures/events/push.json similarity index 100% rename from test/fixtures/push.json rename to src/tests/fixtures/events/push.json diff --git a/test/fixtures/graphql-commits-empty.json b/src/tests/fixtures/graphql/graphql-comparison-empty.json similarity index 65% rename from test/fixtures/graphql-commits-empty.json rename to src/tests/fixtures/graphql/graphql-comparison-empty.json index 41381b088b..7b70bf15b3 100644 --- a/test/fixtures/graphql-commits-empty.json +++ b/src/tests/fixtures/graphql/graphql-comparison-empty.json @@ -2,10 +2,11 @@ "data": { "repository": { "ref": { - "target": { - "history": { - "totalCount": 5, + "compare": { + "commits": { + "__typename": "ComparisonCommitConnection", "pageInfo": { + "__typename": "PageInfo", "hasNextPage": false, "endCursor": "83041a4962107d6bd0282e518ae402dd97ca8daf 24" }, diff --git a/test/fixtures/__generated__/graphql-commits-forking.json b/src/tests/fixtures/graphql/graphql-comparison-forking.json similarity index 73% rename from test/fixtures/__generated__/graphql-commits-forking.json rename to src/tests/fixtures/graphql/graphql-comparison-forking.json index d2fb96e73f..1395891c51 100644 --- a/test/fixtures/__generated__/graphql-commits-forking.json +++ b/src/tests/fixtures/graphql/graphql-comparison-forking.json @@ -2,21 +2,25 @@ "data": { "repository": { "ref": { - "target": { - "history": { - "totalCount": 14, + "compare": { + "commits": { + "__typename": "ComparisonCommitConnection", "pageInfo": { + "__typename": "PageInfo", "hasNextPage": false, "endCursor": "96cf76bb3470913a449d505f2011544b661652f4 13" }, "nodes": [ { + "__typename": "Commit", "id": "MDY6Q29tbWl0MjY3MTQyMTYxOjk2Y2Y3NmJiMzQ3MDkxM2E0NDlkNTA1ZjIwMTE1NDRiNjYxNjUyZjQ=", "committedDate": "2020-05-26T20:33:00Z", "message": "Merge pull request #1 from TimonVS/forking", "author": { + "__typename": "GitActor", "name": "Joseph Petersen", "user": { + "__typename": "User", "login": "jetersen" } }, @@ -28,7 +32,9 @@ "url": "https://github.com/jetersen/release-drafter-test-repo/pull/1", "body": "No thanks to forking PRs", "author": { - "login": "jetersen" + "login": "jetersen", + "__typename": "User", + "url": "https://github.com/jetersen" }, "baseRepository": { "nameWithOwner": "jetersen/release-drafter-test-repo" @@ -37,18 +43,24 @@ "isCrossRepository": true, "labels": { "nodes": [] - } + }, + "merged": true, + "baseRefName": "forking", + "headRefName": "forking" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MjY3MTQyMTYxOjlhZjJlZWMyYjY4NDA0MWY5ZDE3YmFlM2IwNTAwMjYwNGU5NGE4MzM=", "committedDate": "2020-05-26T20:30:59Z", "message": "Merge pull request #28 from TimonVS/add-documentation", "author": { + "__typename": "GitActor", "name": "Joseph Petersen", "user": { + "__typename": "User", "login": "jetersen" } }, @@ -60,7 +72,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/28", "body": "✍️ writing docs all day", "author": { - "login": "jetersen" + "login": "jetersen", + "__typename": "User", + "url": "https://github.com/jetersen" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -73,7 +87,10 @@ "name": "skip-changelog" } ] - } + }, + "merged": true, + "baseRefName": "forking", + "headRefName": "add-documentation" }, { "title": "🙅‍♂️ Forking PR 🙅‍♂️", @@ -81,7 +98,9 @@ "url": "https://github.com/jetersen/release-drafter-test-repo/pull/1", "body": "No thanks to forking PRs", "author": { - "login": "jetersen" + "login": "jetersen", + "__typename": "User", + "url": "https://github.com/jetersen" }, "baseRepository": { "nameWithOwner": "jetersen/release-drafter-test-repo" @@ -90,18 +109,24 @@ "isCrossRepository": true, "labels": { "nodes": [] - } + }, + "merged": true, + "baseRefName": "forking", + "headRefName": "forking" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MjY3MTQyMTYxOjYxMTAwMDY2OWJmZTRlYjQ2NjUzYTQ0YjU2MGUzNjFkYjRhZDc5MjQ=", "committedDate": "2020-05-26T20:29:54Z", "message": "Merge pull request #27 from TimonVS/chore/update-dependencies", "author": { + "__typename": "GitActor", "name": "Joseph Petersen", "user": { + "__typename": "User", "login": "jetersen" } }, @@ -113,7 +138,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/27", "body": "📦 Package time! 📦", "author": { - "login": "jetersen" + "login": "jetersen", + "__typename": "User", + "url": "https://github.com/jetersen" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -122,7 +149,10 @@ "isCrossRepository": false, "labels": { "nodes": [] - } + }, + "merged": true, + "baseRefName": "forking", + "headRefName": "chore/update-dependencies" }, { "title": "🙅‍♂️ Forking PR 🙅‍♂️", @@ -130,7 +160,9 @@ "url": "https://github.com/jetersen/release-drafter-test-repo/pull/1", "body": "No thanks to forking PRs", "author": { - "login": "jetersen" + "login": "jetersen", + "__typename": "User", + "url": "https://github.com/jetersen" }, "baseRepository": { "nameWithOwner": "jetersen/release-drafter-test-repo" @@ -139,18 +171,24 @@ "isCrossRepository": true, "labels": { "nodes": [] - } + }, + "merged": true, + "baseRefName": "forking", + "headRefName": "forking" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MjY3MTQyMTYxOjIyOWY2MWY0ODNmNDEzODFjODUwMThkNmZmMGIwZjMyYzk0NWNhMTI=", "committedDate": "2020-05-26T20:19:59Z", "message": "Fixed another bug", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -162,7 +200,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/25", "body": "🐛 squashing", "author": { - "login": "jetersen" + "login": "jetersen", + "__typename": "User", + "url": "https://github.com/jetersen" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -178,7 +218,10 @@ "name": "patch" } ] - } + }, + "merged": true, + "baseRefName": "forking", + "headRefName": "fix/bug-fixes" }, { "title": "🙅‍♂️ Forking PR 🙅‍♂️", @@ -186,7 +229,9 @@ "url": "https://github.com/jetersen/release-drafter-test-repo/pull/1", "body": "No thanks to forking PRs", "author": { - "login": "jetersen" + "login": "jetersen", + "__typename": "User", + "url": "https://github.com/jetersen" }, "baseRepository": { "nameWithOwner": "jetersen/release-drafter-test-repo" @@ -195,18 +240,24 @@ "isCrossRepository": true, "labels": { "nodes": [] - } + }, + "merged": true, + "baseRefName": "forking", + "headRefName": "forking" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MjY3MTQyMTYxOmMzYThhNDY4NmFlMDkwNzdmNDk0YzcyOGJhMzc5ZTdhMDU4ZWJiYzY=", "committedDate": "2020-05-26T20:19:59Z", "message": "Fixed a bug", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -218,7 +269,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/25", "body": "🐛 squashing", "author": { - "login": "jetersen" + "login": "jetersen", + "__typename": "User", + "url": "https://github.com/jetersen" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -234,7 +287,10 @@ "name": "patch" } ] - } + }, + "merged": true, + "baseRefName": "forking", + "headRefName": "fix/bug-fixes" }, { "title": "🙅‍♂️ Forking PR 🙅‍♂️", @@ -242,7 +298,9 @@ "url": "https://github.com/jetersen/release-drafter-test-repo/pull/1", "body": "No thanks to forking PRs", "author": { - "login": "jetersen" + "login": "jetersen", + "__typename": "User", + "url": "https://github.com/jetersen" }, "baseRepository": { "nameWithOwner": "jetersen/release-drafter-test-repo" @@ -251,18 +309,24 @@ "isCrossRepository": true, "labels": { "nodes": [] - } + }, + "merged": true, + "baseRefName": "forking", + "headRefName": "forking" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MjY3MTQyMTYxOmMwOWM2NmZkNWU2YjU2NTE3MzRlMTI1NGY4YjkzZTZkOWI1ZjI5ZmQ=", "committedDate": "2020-05-26T20:18:34Z", "message": "Add big feature (#24)\n\nCo-authored-by: Timon van Spronsen ", "author": { + "__typename": "GitActor", "name": "Joseph Petersen", "user": { + "__typename": "User", "login": "jetersen" } }, @@ -274,7 +338,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/24", "body": "![I'm kind of a big deal](https://media.giphy.com/media/9LFBOD8a1Ip2M/giphy.gif)", "author": { - "login": "jetersen" + "login": "jetersen", + "__typename": "User", + "url": "https://github.com/jetersen" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -290,7 +356,10 @@ "name": "minor" } ] - } + }, + "merged": true, + "baseRefName": "forking", + "headRefName": "feature/big-feature" }, { "title": "🙅‍♂️ Forking PR 🙅‍♂️", @@ -298,7 +367,9 @@ "url": "https://github.com/jetersen/release-drafter-test-repo/pull/1", "body": "No thanks to forking PRs", "author": { - "login": "jetersen" + "login": "jetersen", + "__typename": "User", + "url": "https://github.com/jetersen" }, "baseRepository": { "nameWithOwner": "jetersen/release-drafter-test-repo" @@ -307,18 +378,24 @@ "isCrossRepository": true, "labels": { "nodes": [] - } + }, + "merged": true, + "baseRefName": "forking", + "headRefName": "forking" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MjY3MTQyMTYxOmNjNTliYjUxN2EwOGYxNDhkZThlM2JiMDIxOTkyNjQ1YTQwMGNlNzY=", "committedDate": "2020-05-26T20:16:43Z", "message": "Merge pull request #23 from TimonVS/feature/alien-technology", "author": { + "__typename": "GitActor", "name": "Joseph Petersen", "user": { + "__typename": "User", "login": "jetersen" } }, @@ -330,7 +407,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/23", "body": "Space invasion 👾", "author": { - "login": "jetersen" + "login": "jetersen", + "__typename": "User", + "url": "https://github.com/jetersen" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -346,7 +425,10 @@ "name": "major" } ] - } + }, + "merged": true, + "baseRefName": "forking", + "headRefName": "feature/alien-technology" }, { "title": "🙅‍♂️ Forking PR 🙅‍♂️", @@ -354,7 +436,9 @@ "url": "https://github.com/jetersen/release-drafter-test-repo/pull/1", "body": "No thanks to forking PRs", "author": { - "login": "jetersen" + "login": "jetersen", + "__typename": "User", + "url": "https://github.com/jetersen" }, "baseRepository": { "nameWithOwner": "jetersen/release-drafter-test-repo" @@ -363,18 +447,24 @@ "isCrossRepository": true, "labels": { "nodes": [] - } + }, + "merged": true, + "baseRefName": "forking", + "headRefName": "forking" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MjY3MTQyMTYxOjg1ODExZmJkYmE2OTViYjk3ZDc3YTVlMmE5NjNiMzFmNTM4YTgyMjM=", "committedDate": "2019-04-27T13:05:51Z", "message": "Fix typo", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -386,7 +476,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/5", "body": "✍️ writing docs all day", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -399,18 +491,24 @@ "name": "skip-changelog" } ] - } + }, + "merged": true, + "baseRefName": "merge-commit", + "headRefName": "add-documentation" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MjY3MTQyMTYxOmVkM2FmY2QzNmM4MDhlZWIxNWFhZjc0Zjg3MTNiODVkZTA2ZjBhMTk=", "committedDate": "2019-04-27T13:05:41Z", "message": "Add documentation", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -422,7 +520,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/5", "body": "✍️ writing docs all day", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -435,18 +535,24 @@ "name": "skip-changelog" } ] - } + }, + "merged": true, + "baseRefName": "merge-commit", + "headRefName": "add-documentation" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MjY3MTQyMTYxOmQyMTM0MjNmOTBjZjU5Y2YxMmRiNmUwMTIxYjgwNzE1NTE4NGRiNmQ=", "committedDate": "2019-04-27T13:03:56Z", "message": "Update Mongoose to 5.5.4", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -458,7 +564,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/4", "body": "📦 Package time! 📦", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -467,18 +575,24 @@ "isCrossRepository": false, "labels": { "nodes": [] - } + }, + "merged": true, + "baseRefName": "merge-commit", + "headRefName": "chore/update-dependencies" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MjY3MTQyMTYxOmQ5NTJjMjU1MDI5N2E4NTE1MmJlMWM4MjYwYjI1ZmI4OWYyODFkNDk=", "committedDate": "2019-04-27T13:03:27Z", "message": "Update Express to 4.16.4", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -490,7 +604,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/4", "body": "📦 Package time! 📦", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -499,18 +615,24 @@ "isCrossRepository": false, "labels": { "nodes": [] - } + }, + "merged": true, + "baseRefName": "merge-commit", + "headRefName": "chore/update-dependencies" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MjY3MTQyMTYxOjk0ZGViYjMwZTAyYjExNTc1YWQ0YzBmMDNmMWE1ZjdmOTJiMTFiMjg=", "committedDate": "2019-04-27T12:59:03Z", "message": "Add alien technology", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -522,7 +644,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/1", "body": "Space invasion 👾", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -535,16 +659,21 @@ "name": "feature" } ] - } + }, + "merged": true, + "baseRefName": "merge-commit", + "headRefName": "feature/alien-technology" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MjY3MTQyMTYxOjNhYmJhNTkxOGZmN2QxMmZhYjMyMjA1N2ZiZGMyM2I1MzVlNzZkMDE=", "committedDate": "2019-04-27T12:12:59Z", "message": "Add project description to README", "author": { + "__typename": "GitActor", "name": "Ada Lovelace", "user": null }, @@ -553,12 +682,15 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MjY3MTQyMTYxOmI5YWI2MGVlNGI4ZjNmYTg0Mjg2NDQ0NjViZGI1YTg4ZDcyZTY1M2E=", "committedDate": "2019-04-27T12:03:19Z", "message": "Initial commit", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, diff --git a/test/fixtures/__generated__/graphql-commits-merge-commit.json b/src/tests/fixtures/graphql/graphql-comparison-merge-commit.json similarity index 75% rename from test/fixtures/__generated__/graphql-commits-merge-commit.json rename to src/tests/fixtures/graphql/graphql-comparison-merge-commit.json index 698dfa804d..71d859690b 100644 --- a/test/fixtures/__generated__/graphql-commits-merge-commit.json +++ b/src/tests/fixtures/graphql/graphql-comparison-merge-commit.json @@ -2,21 +2,25 @@ "data": { "repository": { "ref": { - "target": { - "history": { - "totalCount": 16, + "compare": { + "commits": { + "__typename": "ComparisonCommitConnection", "pageInfo": { + "__typename": "PageInfo", "hasNextPage": false, "endCursor": "00de634cb5f3c0b854eb30b0afe5b1816e372ed3 15" }, "nodes": [ { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjAwZGU2MzRjYjVmM2MwYjg1NGViMzBiMGFmZTViMTgxNmUzNzJlZDM=", "committedDate": "2019-04-27T13:12:07Z", "message": "Merge pull request #5 from TimonVS/add-documentation\n\nAdd documentation", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -28,7 +32,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/5", "body": "✍️ writing docs all day", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -41,18 +47,24 @@ "name": "skip-changelog" } ] - } + }, + "merged": true, + "baseRefName": "merge-commit", + "headRefName": "add-documentation" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmQ4NTg3ZmRiMjYzNjM5Zjc2NTYxMjQ2ZDUzN2UwNDE4YTJmODZhZWU=", "committedDate": "2019-04-27T13:11:55Z", "message": "Merge pull request #4 from TimonVS/chore/update-dependencies\n\nUpdate dependencies", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -64,7 +76,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/4", "body": "📦 Package time! 📦", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -73,18 +87,24 @@ "isCrossRepository": false, "labels": { "nodes": [] - } + }, + "merged": true, + "baseRefName": "merge-commit", + "headRefName": "chore/update-dependencies" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjI1MzQ0MDhmZWI4MzZkYWNjNTFlYzdjMGFmNGYyNDdlN2JlMzlkNzQ=", "committedDate": "2019-04-27T13:11:47Z", "message": "Merge pull request #3 from TimonVS/fix/bug-fixes\n\nBug fixes", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -96,7 +116,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/3", "body": "🐛 squashing", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -112,18 +134,24 @@ "name": "patch" } ] - } + }, + "merged": true, + "baseRefName": "merge-commit", + "headRefName": "fix/bug-fixes" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjQ4ZTkwNTYwNWQ4ODkyNWI1MjJjMmIwMGY2NDZmNzQ0ZmQ5NzZkMjE=", "committedDate": "2019-04-27T13:11:38Z", "message": "Merge pull request #2 from TimonVS/feature/big-feature\n\nAdd big feature", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -135,7 +163,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/2", "body": "![I'm kind of a big deal](https://media.giphy.com/media/9LFBOD8a1Ip2M/giphy.gif)", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -151,18 +181,24 @@ "name": "minor" } ] - } + }, + "merged": true, + "baseRefName": "merge-commit", + "headRefName": "feature/big-feature" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjVmNTViZThiOTA2M2IzOWQ5Y2VkYzllNWRkOGZkMDIyMjFiZDMxZjQ=", "committedDate": "2019-04-27T13:11:29Z", "message": "Merge pull request #1 from TimonVS/feature/alien-technology\n\n👽 Add alien technology", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -174,7 +210,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/1", "body": "Space invasion 👾", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -187,18 +225,24 @@ "name": "feature" } ] - } + }, + "merged": true, + "baseRefName": "merge-commit", + "headRefName": "feature/alien-technology" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjg1ODExZmJkYmE2OTViYjk3ZDc3YTVlMmE5NjNiMzFmNTM4YTgyMjM=", "committedDate": "2019-04-27T13:05:51Z", "message": "Fix typo", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -210,7 +254,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/5", "body": "✍️ writing docs all day", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -223,18 +269,24 @@ "name": "skip-changelog" } ] - } + }, + "merged": true, + "baseRefName": "merge-commit", + "headRefName": "add-documentation" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmVkM2FmY2QzNmM4MDhlZWIxNWFhZjc0Zjg3MTNiODVkZTA2ZjBhMTk=", "committedDate": "2019-04-27T13:05:41Z", "message": "Add documentation", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -246,7 +298,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/5", "body": "✍️ writing docs all day", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -259,18 +313,24 @@ "name": "skip-changelog" } ] - } + }, + "merged": true, + "baseRefName": "merge-commit", + "headRefName": "add-documentation" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmQyMTM0MjNmOTBjZjU5Y2YxMmRiNmUwMTIxYjgwNzE1NTE4NGRiNmQ=", "committedDate": "2019-04-27T13:03:56Z", "message": "Update Mongoose to 5.5.4", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -282,7 +342,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/4", "body": "📦 Package time! 📦", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -291,18 +353,24 @@ "isCrossRepository": false, "labels": { "nodes": [] - } + }, + "merged": true, + "baseRefName": "merge-commit", + "headRefName": "chore/update-dependencies" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmQ5NTJjMjU1MDI5N2E4NTE1MmJlMWM4MjYwYjI1ZmI4OWYyODFkNDk=", "committedDate": "2019-04-27T13:03:27Z", "message": "Update Express to 4.16.4", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -314,7 +382,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/4", "body": "📦 Package time! 📦", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -323,18 +393,24 @@ "isCrossRepository": false, "labels": { "nodes": [] - } + }, + "merged": true, + "baseRefName": "merge-commit", + "headRefName": "chore/update-dependencies" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjFiYmJiM2FhYWFmMTk3MDYyYmRmMjg2ODhiMDk4NTI0NjcxM2NkMTI=", "committedDate": "2019-04-27T13:02:04Z", "message": "Fixed another bug", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -346,7 +422,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/3", "body": "🐛 squashing", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -362,18 +440,24 @@ "name": "patch" } ] - } + }, + "merged": true, + "baseRefName": "merge-commit", + "headRefName": "fix/bug-fixes" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjNlOGE5YTc4ODA3MmRiZDcyMmMxZTkzYTU0NzEzYzI5NzEyNjI5MDY=", "committedDate": "2019-04-27T13:01:55Z", "message": "Fixed a bug", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -385,7 +469,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/3", "body": "🐛 squashing", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -401,18 +487,24 @@ "name": "patch" } ] - } + }, + "merged": true, + "baseRefName": "merge-commit", + "headRefName": "fix/bug-fixes" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmI0YWZlNDc3YTZlMmU0OWFmYzdhYTU1ZWYwODMyOTU4MWJmODRiNDM=", "committedDate": "2019-04-27T13:01:14Z", "message": "Adjust parameters", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -424,7 +516,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/2", "body": "![I'm kind of a big deal](https://media.giphy.com/media/9LFBOD8a1Ip2M/giphy.gif)", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -440,18 +534,24 @@ "name": "minor" } ] - } + }, + "merged": true, + "baseRefName": "merge-commit", + "headRefName": "feature/big-feature" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmQwMWJjZmNjNzczMWRlYzNiOTg2OWY5ZTFkYTYwMjlhYTY2NzcyNDA=", "committedDate": "2019-04-27T13:00:49Z", "message": "Add big feature", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -463,7 +563,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/2", "body": "![I'm kind of a big deal](https://media.giphy.com/media/9LFBOD8a1Ip2M/giphy.gif)", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -479,18 +581,24 @@ "name": "minor" } ] - } + }, + "merged": true, + "baseRefName": "merge-commit", + "headRefName": "feature/big-feature" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjk0ZGViYjMwZTAyYjExNTc1YWQ0YzBmMDNmMWE1ZjdmOTJiMTFiMjg=", "committedDate": "2019-04-27T12:59:03Z", "message": "Add alien technology", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -502,7 +610,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/1", "body": "Space invasion 👾", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -515,16 +625,21 @@ "name": "feature" } ] - } + }, + "merged": true, + "baseRefName": "merge-commit", + "headRefName": "feature/alien-technology" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjNhYmJhNTkxOGZmN2QxMmZhYjMyMjA1N2ZiZGMyM2I1MzVlNzZkMDE=", "committedDate": "2019-04-27T12:12:59Z", "message": "Add project description to README", "author": { + "__typename": "GitActor", "name": "Ada Lovelace", "user": null }, @@ -533,12 +648,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmI5YWI2MGVlNGI4ZjNmYTg0Mjg2NDQ0NjViZGI1YTg4ZDcyZTY1M2E=", "committedDate": "2019-04-27T12:03:19Z", "message": "Initial commit", "author": { "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, diff --git a/src/tests/fixtures/graphql/graphql-comparison-missing-pr-with-paths.json b/src/tests/fixtures/graphql/graphql-comparison-missing-pr-with-paths.json new file mode 100644 index 0000000000..0551717d0c --- /dev/null +++ b/src/tests/fixtures/graphql/graphql-comparison-missing-pr-with-paths.json @@ -0,0 +1,46 @@ +{ + "data": { + "repository": { + "ref": { + "compare": { + "commits": { + "__typename": "ComparisonCommitConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "endCursor": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 1" + }, + "nodes": [ + { + "__typename": "Commit", + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE=", + "oid": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "committedDate": "2019-04-27T14:00:00Z", + "message": "Merge pull request #100 from TimonVS/touches-src\n\nDocs change", + "author": { + "__typename": "GitActor", + "name": "Timon van Spronsen", + "user": { "__typename": "User", "login": "TimonVS" } + }, + "associatedPullRequests": { "nodes": [] } + }, + { + "__typename": "Commit", + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI=", + "oid": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "committedDate": "2019-04-27T15:00:00Z", + "message": "Merge pull request #101 from TimonVS/touches-other\n\nUnrelated change", + "author": { + "__typename": "GitActor", + "name": "Timon van Spronsen", + "user": { "__typename": "User", "login": "TimonVS" } + }, + "associatedPullRequests": { "nodes": [] } + } + ] + } + } + } + } + } +} diff --git a/src/tests/fixtures/graphql/graphql-comparison-missing-pr.json b/src/tests/fixtures/graphql/graphql-comparison-missing-pr.json new file mode 100644 index 0000000000..f36348c69b --- /dev/null +++ b/src/tests/fixtures/graphql/graphql-comparison-missing-pr.json @@ -0,0 +1,38 @@ +{ + "data": { + "repository": { + "ref": { + "compare": { + "commits": { + "__typename": "ComparisonCommitConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "endCursor": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2 1" + }, + "nodes": [ + { + "__typename": "Commit", + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmExYjJjM2Q0ZTVmNmExYjJjM2Q0ZTVmNmExYjJjM2Q0ZTVmNmExYjI=", + "oid": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", + "committedDate": "2019-04-27T14:00:00Z", + "message": "Merge pull request #6 from TimonVS/new-feature\n\nAdd new feature", + "author": { + "__typename": "GitActor", + "name": "Timon van Spronsen", + "user": { + "__typename": "User", + "login": "TimonVS" + } + }, + "associatedPullRequests": { + "nodes": [] + } + } + ] + } + } + } + } + } +} diff --git a/test/fixtures/graphql-commits-no-prs.json b/src/tests/fixtures/graphql/graphql-comparison-no-prs.json similarity index 76% rename from test/fixtures/graphql-commits-no-prs.json rename to src/tests/fixtures/graphql/graphql-comparison-no-prs.json index e86d77650c..37797c847a 100644 --- a/test/fixtures/graphql-commits-no-prs.json +++ b/src/tests/fixtures/graphql/graphql-comparison-no-prs.json @@ -2,20 +2,24 @@ "data": { "repository": { "ref": { - "target": { - "history": { - "totalCount": 5, + "compare": { + "commits": { + "__typename": "ComparisonCommitConnection", "pageInfo": { + "__typename": "PageInfo", "hasNextPage": false, "endCursor": "83041a4962107d6bd0282e518ae402dd97ca8daf 24" }, "nodes": [ { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTA5MTM2ODE4OjgzMDQxYTQ5NjIxMDdkNmJkMDI4MmU1MThhZTQwMmRkOTdjYThkYWY=", "message": "Commit 5", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -24,11 +28,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTA5MTM2ODE4OjM5MTgyNDQ5MGUyYmRjNmI5ZTJiZTMzNzQwYjgzYTdjZGFkNDRmZWM=", "message": "Commit 4", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -37,11 +44,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTA5MTM2ODE4OjA0NTE5YTk0ODg1MWUyOWQ2MjVmZDM1Y2RkNTg4ODEzNWEwY2ZmMmM=", "message": "Commit 3", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -50,11 +60,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTA5MTM2ODE4OmM5MDBjOWJiYzg4Yzk4Y2MzZDczNjZlNzQwMjMzNmUyNjBmOTAyMDc=", "message": "Commit 2", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -63,11 +76,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTA5MTM2ODE4OjJiYTg2NzczNjgwYTRjMjFlOWUzYjA4YzkxZmIxZTdhNjliZjIwOWI=", "message": "Commit 1", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, diff --git a/test/fixtures/__generated__/graphql-commits-overlapping-label.json b/src/tests/fixtures/graphql/graphql-comparison-overlapping-label.json similarity index 75% rename from test/fixtures/__generated__/graphql-commits-overlapping-label.json rename to src/tests/fixtures/graphql/graphql-comparison-overlapping-label.json index 04adc971f2..f0b3ecc11d 100644 --- a/test/fixtures/__generated__/graphql-commits-overlapping-label.json +++ b/src/tests/fixtures/graphql/graphql-comparison-overlapping-label.json @@ -2,33 +2,40 @@ "data": { "repository": { "ref": { - "target": { - "history": { - "totalCount": 7, + "compare": { + "commits": { + "__typename": "ComparisonCommitConnection", "pageInfo": { + "__typename": "PageInfo", "hasNextPage": false, "endCursor": "b2ed3f089aa1022e91e0a9d109ab876fde688c34 6" }, "nodes": [ { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmIyZWQzZjA4OWFhMTAyMmU5MWUwYTlkMTA5YWI4NzZmZGU2ODhjMzQ=", "committedDate": "2020-02-02T13:55:49Z", "message": "Add documentation (#22)\n\n* Add documentation\r\n\r\n* Fix typo\r\n\r\nCo-authored-by: Timon van Spronsen ", "author": { + "__typename": "GitActor", "name": "Joseph Petersen", "user": { + "__typename": "User", "login": "jetersen" } }, "associatedPullRequests": { "nodes": [ { + "__typename": "PullRequest", "title": "Add documentation", "number": 22, "url": "https://github.com/toolmantim/release-drafter-test-project/pull/22", "body": "✍️ writing docs all day", "author": { - "login": "jetersen" + "__typename": "User", + "login": "jetersen", + "url": "https://github.com/jetersen" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -41,30 +48,39 @@ "name": "skip-changelog" } ] - } + }, + "merged": true, + "baseRefName": "overlapping-label", + "headRefName": "add-documentation" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmI2NzdkYjY0MGQwYjljMjViYjVmYjE3NjhiMzQ3NGEyNTI5MzQ5NzM=", "committedDate": "2020-02-02T13:55:28Z", "message": "Update dependencies (#21)\n\n* Update Express to 4.16.4\r\n\r\n* Update Mongoose to 5.5.4\r\n\r\nCo-authored-by: Timon van Spronsen ", "author": { + "__typename": "GitActor", "name": "Joseph Petersen", "user": { + "__typename": "User", "login": "jetersen" } }, "associatedPullRequests": { "nodes": [ { + "__typename": "PullRequest", "title": "Update dependencies", "number": 21, "url": "https://github.com/toolmantim/release-drafter-test-project/pull/21", "body": "📦 Package time! 📦", "author": { - "login": "jetersen" + "__typename": "User", + "login": "jetersen", + "url": "https://github.com/jetersen" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -73,30 +89,39 @@ "isCrossRepository": false, "labels": { "nodes": [] - } + }, + "merged": true, + "baseRefName": "overlapping-label", + "headRefName": "chore/update-dependencies" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjQ3N2FjNTBlNGNkMWMzOTRhYmJkNjcwZjFkNTkwY2I3OWJjOThhYzk=", "committedDate": "2020-02-02T13:55:11Z", "message": "Bug fixes (#20)\n\n* Fixed a bug\r\n\r\n* Fixed another bug\r\n\r\nCo-authored-by: Timon van Spronsen ", "author": { + "__typename": "GitActor", "name": "Joseph Petersen", "user": { + "__typename": "User", "login": "jetersen" } }, "associatedPullRequests": { "nodes": [ { + "__typename": "PullRequest", "title": "Bug fixes", "number": 20, "url": "https://github.com/toolmantim/release-drafter-test-project/pull/20", "body": "🐛 squashing", "author": { - "login": "jetersen" + "__typename": "User", + "login": "jetersen", + "url": "https://github.com/jetersen" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -109,36 +134,45 @@ "name": "bug" }, { - "name": "patch" + "name": "sentry" }, { - "name": "sentry" + "name": "patch" } ] - } + }, + "merged": true, + "baseRefName": "overlapping-label", + "headRefName": "fix/bug-fixes" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjliYTBlNTQ4MmMwY2I1OWU0MDBmYzhmNWUyNGU1OWMyMDE2N2U5MGE=", "committedDate": "2020-02-02T13:54:53Z", "message": "Add big feature (#19)\n\n* Add big feature\r\n\r\n* Adjust parameters\r\n\r\nCo-authored-by: Timon van Spronsen ", "author": { + "__typename": "GitActor", "name": "Joseph Petersen", "user": { + "__typename": "User", "login": "jetersen" } }, "associatedPullRequests": { "nodes": [ { + "__typename": "PullRequest", "title": "Add big feature", "number": 19, "url": "https://github.com/toolmantim/release-drafter-test-project/pull/19", "body": "![I'm kind of a big deal](https://media.giphy.com/media/9LFBOD8a1Ip2M/giphy.gif)", "author": { - "login": "jetersen" + "__typename": "User", + "login": "jetersen", + "url": "https://github.com/jetersen" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -154,30 +188,39 @@ "name": "minor" } ] - } + }, + "merged": true, + "baseRefName": "overlapping-label", + "headRefName": "feature/big-feature" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmY5YmYyMmMzNTI0MTcyODg0NTJhYmZkMzlkMGIxY2JhMmViYjk2MWE=", "committedDate": "2020-02-02T13:54:22Z", "message": "Add alien technology (#18)\n\nCo-authored-by: Timon van Spronsen ", "author": { + "__typename": "GitActor", "name": "Joseph Petersen", "user": { + "__typename": "User", "login": "jetersen" } }, "associatedPullRequests": { "nodes": [ { + "__typename": "PullRequest", "title": "Add alien technology", "number": 18, "url": "https://github.com/toolmantim/release-drafter-test-project/pull/18", "body": "", "author": { - "login": "jetersen" + "__typename": "User", + "login": "jetersen", + "url": "https://github.com/jetersen" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -193,16 +236,21 @@ "name": "major" } ] - } + }, + "merged": true, + "baseRefName": "overlapping-label", + "headRefName": "feature/alien-technology" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjNhYmJhNTkxOGZmN2QxMmZhYjMyMjA1N2ZiZGMyM2I1MzVlNzZkMDE=", "committedDate": "2019-04-27T12:12:59Z", "message": "Add project description to README", "author": { + "__typename": "GitActor", "name": "Ada Lovelace", "user": null }, @@ -211,12 +259,15 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmI5YWI2MGVlNGI4ZjNmYTg0Mjg2NDQ0NjViZGI1YTg4ZDcyZTY1M2E=", "committedDate": "2019-04-27T12:03:19Z", "message": "Initial commit", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, diff --git a/test/fixtures/graphql-commits-paginated-1.json b/src/tests/fixtures/graphql/graphql-comparison-paginated-1.json similarity index 76% rename from test/fixtures/graphql-commits-paginated-1.json rename to src/tests/fixtures/graphql/graphql-comparison-paginated-1.json index 33f77acc77..2cc2d59515 100644 --- a/test/fixtures/graphql-commits-paginated-1.json +++ b/src/tests/fixtures/graphql/graphql-comparison-paginated-1.json @@ -2,20 +2,24 @@ "data": { "repository": { "ref": { - "target": { - "history": { - "totalCount": 120, + "compare": { + "commits": { + "__typename": "ComparisonCommitConnection", "pageInfo": { + "__typename": "PageInfo", "hasNextPage": true, "endCursor": "d1d78f7d126c56c7165213cd2dffddcbc8cee8b6 99" }, "nodes": [ { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmQxZDc4ZjdkMTI2YzU2YzcxNjUyMTNjZDJkZmZkZGNiYzhjZWU4YjY=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -24,11 +28,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmRkYTVjOGUyMzUzNjkzNzQ5ZWJjYTkyMjA0ZTVlMjU4MDUzZWFjZTI=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -37,11 +44,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmU4YjYwOGI0MmM3NGY1NTkwOTQwNzFmOTZmNzQ1ZWM0OGI1YjQ3NjE=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -50,11 +60,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmQ0ZTgxNWRlM2U3NjhjNjRiZTQxNzU4NmI2YjY3Yzc1NjI2MTVhMGE=", "message": "Fixed some inexistent PR (#1111111111)", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -63,11 +76,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjNlMGQzMDNjNmM2ZmNhOGQwNjg1MjFmMjIyZTNmNGYwMTIwOTE5ZTI=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -76,11 +92,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmE0NjFiMzVmYjU1MTcyMzk1OWFmMjRjN2Q1YzNhOWU3NjU2NGRmOGE=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -89,11 +108,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjNmZjNhM2JmNjUyMTk3NWVjNGI4ZTAzM2VkYTRmYzI3MWE2NDY0ZjQ=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -102,11 +124,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmE1M2MyZDRiMzNiYjc5MTQ4YmE0MzY3YjVmNjdiOWYxMjE5OTdhNDY=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -115,11 +140,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjEzMjU5NGQzNTAxMjhkYjM2NWMxZTg4MzYxNDhmOWI5MjUyNDgxOWM=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -128,11 +156,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmU4NTU4MzA5NzdjOWU5ODE2OTRjNWZjMzdhNjNjYjMyZTg1NzZmMTk=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -141,11 +172,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmE1OGVmYTZjZDg4NWU3MTk2MGIxZmZiMzI3YzM5MDVmYzQxMTM4ZjQ=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -154,11 +188,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmE2ZWRlNzA2NGFkMWUwNTM0NWFkZmUzOGRhNDFhZTE4ZDJjZjkyZDU=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -167,11 +204,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjU5OTFjNWNiYmNhNzk0Y2IyZDZkNmM1NjA4YzBiNTY2YWU5OTJkMGU=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -180,11 +220,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjQ5NDQ1NmQ2NDQxNzc1ODc2NjU5OWUzMjU5MTNiMjIxOTdjNjllZGM=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -193,11 +236,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjEwZWY3MGZkMDJiMzRiMjgzMjdlOGM1YzIwZmE4NjM4MTVkNTliMzg=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -206,11 +252,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmI1OGQ5MGI2MWEwZWVlZmI3NzNhNDUzMDU4OWNhY2NkYzQ3MGRiN2U=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -219,11 +268,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmYzYzJhMWMxMTY4NWNjY2M4NTY4NGM4YjgzNjdhOGFkNTE1NWNmNDc=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -232,11 +284,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjg3YzU0OWY4ZjE4ZDFhMGMyMzI3NGFlZTZiMGUxNzNhOGY4ZTQyMzY=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -245,11 +300,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjhiZTk5NDYwZGVjMWU2YTQyOTViOGY0N2Y2ZjI5ZmM3MGU0YzZkODc=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -258,11 +316,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmQ5ZDgyZWY5MDMzMDIxNzk5NmM1NDhkNjMyNWFhNjRhNjg3M2VjMGQ=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -271,11 +332,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmFhZDEyMThhZTY5Nzg4MTliMGY4ODk4ZDgzZTUwMmU0Zjg5ZDcyZGI=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -284,11 +348,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmRhNzc2N2U4M2Y4Nzc4OTY3YjlkOTgzYjU1NjYxOGJjOWFhNTYwOTg=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -297,11 +364,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjAyMzJhNzFkYjkzZjYxZWIzNzU0MTBkYWZlZjUyNGUxMTNmMDlkYTQ=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -310,11 +380,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjkwYjNkNzdlM2NmZjFiMDc2NDllOTJlOWJmNTQ3ZWYyYWZlZDUzMDM=", "message": "Merge pull request #16 from toolmantim/another-one\n\nAdded great distance", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -325,6 +398,7 @@ "number": 16, "mergedAt": "2018-07-13T11:50:38Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -332,17 +406,21 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjdmOGY4NjI3NDY3ZTQwMDhmM2Q0NmE3ZWUwMmJmYWRlZGFmZjM4ZWU=", "message": "Update README.md", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -353,6 +431,7 @@ "number": 16, "mergedAt": "2018-07-13T11:50:38Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -360,17 +439,21 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjRlZmEwNWM2MjRjNjMwNzQxYjM2MDczN2MzYTFlZmVhODBiNDZhOGU=", "message": "Update new-feature.md (#15)", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -381,6 +464,7 @@ "number": 15, "mergedAt": "2018-07-13T11:40:10Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -388,27 +472,32 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjhjNThjMjliZjdmNDI4NWZiODIxMmY3MWFkY2FhYzMyM2IwOGU0ZTI=", - "message": "Merge pull request #14 from toolmantim/hello-there\n\n❤️ Add MOAR THINGS", + "message": "Merge pull request #14 from toolmantim/hello-there\n\n\u2764\ufe0f Add MOAR THINGS", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "❤️ Add MOAR THINGS", + "title": "\u2764\ufe0f Add MOAR THINGS", "number": 14, "mergedAt": "2018-07-13T11:38:34Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -416,27 +505,32 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjI4ZTEyMjZmOTkwYjU3MjVhNGU0Nzc0MzY2OThhMmViOTg1Nzk0ODg=", "message": "Update README.md", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "❤️ Add MOAR THINGS", + "title": "\u2764\ufe0f Add MOAR THINGS", "number": 14, "mergedAt": "2018-07-13T11:38:34Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -444,17 +538,21 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjhiZjM4MTI4NjA4MjQ1YjU2NmZlNzNkN2FkY2E5OWZlNDYwYmU0NGU=", "message": "Update README.md", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -463,11 +561,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjc3N2NiYjE4ZGExN2VhMjA5ZjU0ZDc4YTcwN2VkZjJmYThiZDNmYjI=", "message": "Merge pull request #13 from toolmantim/new-tech\n\nAdd all the tests", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -478,6 +579,7 @@ "number": 13, "mergedAt": "2018-07-13T11:17:50Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -485,17 +587,21 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmRhYWI1ZGRhZTA1Y2U2ZDExMDM5ZTU5YjFmOGRiZDAyMzdlNmJiZjE=", "message": "Update README.md", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -506,6 +612,7 @@ "number": 13, "mergedAt": "2018-07-13T11:17:50Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -513,17 +620,21 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjQ3Y2Y0ZjAyMGJkNGZkYmJiYThkNzJlMGZmMmY5ZDQyOGZhOGI5NWM=", "message": "Update README.md", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -532,11 +643,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjIxNzM5N2ZlNzFlZTc1ZGNiMWRjYjc2Njc5ZDVmMGYzOWI2MDU1ZWU=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -545,11 +659,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmFjYmMxM2FkYTU1MmU3YmNiZjlkOWRlMzM3MzY0Njk0Y2Q1YmQxYmY=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -558,11 +675,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmU3ZmIwM2UxMzkyM2M3NzcyYTYxMGFiYWMzNGMxY2Q4NTg3YmI1ZDQ=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -571,11 +691,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmVkNmExMzNkNjA1Y2M5OTEzOTU2ZTYxZTg4YzkwNWZmMTZmMGU3ZGM=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -584,11 +707,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmYyY2MxMjUzZDllNWZhZDA0MGUwNjIzNWQ0NTYxNTQ1OGY2YWU4OGQ=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -597,11 +723,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjQzZGIxODE4OWRjMzU5Y2JmZGZmYTAzM2IxYzYwMWIyOTA4NThhZGU=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -610,11 +739,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmViMjRhMTRlN2U0MGFlMjAwMmFiODZjMzRlNGY3ZGE1NTc2YzkzNTY=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -623,11 +755,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjM4ODc2NmJlNzgzYzc4YWY4MjAwODFjNThhYzVlYjdhNmU0OGYwMmI=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -636,11 +771,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjYwZDcxMGQ3ZTEzYTAwMjM5ZDA0MDBkYTVjMjI0ZWM2YzA3ZDI3NjQ=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -649,11 +787,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjY4MWMzNWNkMjhmMmNkMzEzMmI4ZDQyZmNiZDhhZTdlZDFkZDU2NGM=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -662,11 +803,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjdhODQwN2MxYzAxYWIyMThjNjA0Y2U4YWQ4MTQxNmM2MGRmNzg2OTM=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -675,11 +819,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjY1MjM0NWU4ZDI4YmRiNDYwY2M2OGNlMTJjYzFiYjVkNGQxMjdjZTI=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -688,11 +835,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmEyNjY0Y2U3OTUyNmQ4MzBiNWUzNDc3YTExNGVmY2QyMzk1OWMwMzM=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -701,11 +851,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjY0MGUwNWRiNWYwNWNhMzBlNzIwNDdjNGFhY2VmMDUzZmM4YjM1M2I=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -714,11 +867,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmE4ZjFlYzgxYjY5NmJhNzg0MjBiOTBkNjg1N2JhNTVmODE3ZjY1YjU=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -727,21 +883,25 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjY4ZTUzNWEyNTE0NjEzYWYxMjVlM2Y3ZDQxMmUxYmE3YmMyMzc3ODY=", - "message": "Merge pull request #12 from toolmantim/toolmantim-patch-1\n\n🤖 Add robots", + "message": "Merge pull request #12 from toolmantim/toolmantim-patch-1\n\n\ud83e\udd16 Add robots", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "🤖 Add robots", + "title": "\ud83e\udd16 Add robots", "number": 12, "mergedAt": "2018-07-04T12:26:18Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -749,27 +909,32 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjgxN2UyZDU3MTUzOTFhMTA0MDM2MTM0Mzg0YTAzNjY5MGYyNDNiN2Y=", "message": "Test", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "🤖 Add robots", + "title": "\ud83e\udd16 Add robots", "number": 12, "mergedAt": "2018-07-04T12:26:18Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -777,27 +942,32 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmI3ODEyMGVjZGYyMjk2NzViZWY1MmEwZTk4MDM1MmNkMWUwOTI2MjY=", - "message": "Merge pull request #11 from toolmantim/toolmantim-patch-1\n\n🎃 More pumpkins", + "message": "Merge pull request #11 from toolmantim/toolmantim-patch-1\n\n\ud83c\udf83 More pumpkins", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "🎃 More pumpkins", + "title": "\ud83c\udf83 More pumpkins", "number": 11, "mergedAt": "2018-07-04T12:24:27Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -805,27 +975,32 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjQwMGI2NWI0M2NmMDczZjE1OWFjZDFkZjIxMWY0YWZlNDJjMDljZmQ=", "message": "Test", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "🎃 More pumpkins", + "title": "\ud83c\udf83 More pumpkins", "number": 11, "mergedAt": "2018-07-04T12:24:27Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -833,17 +1008,21 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjA1Yzg5MTExNTZmYmQwNDcxYTcxZmQ5NTMyNDM1YzNhNGJlODgxMzI=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -852,11 +1031,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmQwYTAzNjJjZmUyNDcxMmIzODY1MTFjNTYxODNlNzYwMmYyOTAwMmU=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -865,11 +1047,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjMxY2NjMzk4ZTg5M2VkMWZjM2Q0YmUzOTAwMjViNjAxMzQ4Yzc0YWY=", "message": "Update release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -878,11 +1063,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmQ5ZjBlNzE5MzM1MjdlYjdiZTBmMTljNThmN2JjMDY5NDg1Y2JlMmI=", "message": "Moo", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -891,11 +1079,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmI4YWFhYzNiY2M2ZTM4YTIwYjQ1Y2I0YzMxODJkYmU5ZjhmYjU1MjU=", "message": "Moo", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -904,11 +1095,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjY0YjgwNTkzOTA4NmY5YjA0ZjFkY2NkMzZlYTI1OTA0ODJmMmRjOTQ=", "message": "Update and rename draftah.yml to release-drafter.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -917,11 +1111,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmQ4MzFlMmU1ZDU3OTgzZDljZjYzOTM4MjJhOGE5YTI4NWMyNzc4ZTE=", "message": "Update the template", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -930,21 +1127,25 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjhjM2RjOTNiN2VmZDc1YmQ5MjExMDE1OTBiNmQwNDNlNjdjNTg0OGU=", - "message": "Merge pull request #10 from toolmantim/more-cowbell\n\n🐄 Moar cowbell", + "message": "Merge pull request #10 from toolmantim/more-cowbell\n\n\ud83d\udc04 Moar cowbell", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "🐄 Moar cowbell", + "title": "\ud83d\udc04 Moar cowbell", "number": 10, "mergedAt": "2018-06-30T05:07:38Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -952,27 +1153,32 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmI1MDM5NGZjOGUzMzQwODMxNjJlZmIwODU3MmQ0NTRhNWUzYWRkOWE=", - "message": "🐄 Moar cowbell", + "message": "\ud83d\udc04 Moar cowbell", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "🐄 Moar cowbell", + "title": "\ud83d\udc04 Moar cowbell", "number": 10, "mergedAt": "2018-06-30T05:07:38Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -980,27 +1186,32 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjU3M2RjODEyZTU2NzgyNmM5NzNlMmM3YzA2NDBjMGJjN2JkODdmMGM=", - "message": "Merge pull request #9 from toolmantim/switch-to-a-monorepo\n\n1️⃣ Switch to a monorepo", + "message": "Merge pull request #9 from toolmantim/switch-to-a-monorepo\n\n1\ufe0f\u20e3 Switch to a monorepo", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "1️⃣ Switch to a monorepo", + "title": "1\ufe0f\u20e3 Switch to a monorepo", "number": 9, "mergedAt": "2018-06-30T05:06:27Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -1008,27 +1219,32 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjE1OWEwNDNlZjI4ZDcwMjNhZGM3ODAwMjk4ZWFkNDQ0YThhNTUwODQ=", - "message": "1️⃣ Switch to a monorepo", + "message": "1\ufe0f\u20e3 Switch to a monorepo", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "1️⃣ Switch to a monorepo", + "title": "1\ufe0f\u20e3 Switch to a monorepo", "number": 9, "mergedAt": "2018-06-30T05:06:27Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -1036,17 +1252,21 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjU2MTI0ZTg0NzA0NjY3ZTJlNDVlNzRmYWM0NjYxYjhiZmNkZmFjNzk=", "message": "Update draftah.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1055,11 +1275,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjVhMjIyNGFhMGZlYmRlOWJjNjZhMDgyY2NmYmY0MzVlZjViMTY1OTg=", "message": "Update draftah.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1068,21 +1291,25 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjMwODg3MzM0NzhlMTZkMTFhMTg1ZDQ4ODMxZDkyM2RkZmRiMDkzNmI=", - "message": "Merge pull request #8 from toolmantim/great-stuff\n\n👽 Integrate Alien technology", + "message": "Merge pull request #8 from toolmantim/great-stuff\n\n\ud83d\udc7d Integrate Alien technology", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "👽 Integrate Alien technology", + "title": "\ud83d\udc7d Integrate Alien technology", "number": 8, "mergedAt": "2018-06-30T04:30:04Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -1090,27 +1317,32 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmUxMzUzMzAyMDQwNzMyOTY5NDM5NGQ0MTA3N2E1Y2UwZGMzMDMxODc=", - "message": "👽 Integrate Alien technology", + "message": "\ud83d\udc7d Integrate Alien technology", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "👽 Integrate Alien technology", + "title": "\ud83d\udc7d Integrate Alien technology", "number": 8, "mergedAt": "2018-06-30T04:30:04Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -1118,17 +1350,21 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjg4NjI3NWNkM2ExMTY3ZmQxNjVkN2Q0ZDFkYzYzZDZlYjM5MmJlNzQ=", "message": "Update draftah.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1137,21 +1373,25 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmYyMjA5OTE5ZTg3Yzg5MTJhNTFlZDM4YzU1NTNkMTY4Nzc4MGQxNTE=", - "message": "Merge pull request #7 from toolmantim/add-bouldering-technology\n\nAdd ⛰ technology", + "message": "Merge pull request #7 from toolmantim/add-bouldering-technology\n\nAdd \u26f0 technology", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "Add ⛰ technology", + "title": "Add \u26f0 technology", "number": 7, "mergedAt": "2018-06-29T05:45:15Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -1159,27 +1399,32 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmQ5Yzc0ZjZmZWYyYmFkYTNlYTg0NWZjMmYyZGM0NGRjZTk2NzVjY2E=", "message": "MOAR STUFF", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "Add ⛰ technology", + "title": "Add \u26f0 technology", "number": 7, "mergedAt": "2018-06-29T05:45:15Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -1187,27 +1432,32 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmEwZGM3NWM0YmZlYjkyNTEwMmRhNTViY2M2NWEwODRjNjVmYzIyMTc=", - "message": "Merge pull request #6 from toolmantim/add-alien-tech\n\n👽 Added alien technology", + "message": "Merge pull request #6 from toolmantim/add-alien-tech\n\n\ud83d\udc7d Added alien technology", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "👽 Added alien technology", + "title": "\ud83d\udc7d Added alien technology", "number": 6, "mergedAt": "2018-06-28T13:25:07Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -1215,27 +1465,32 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjNhODEzNjE3YzA3ZjlmMGJjMzhmNDJmNjMxNjI3ZTIzZDk4OGZmMTA=", - "message": "👽 Added alien technology", + "message": "\ud83d\udc7d Added alien technology", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "👽 Added alien technology", + "title": "\ud83d\udc7d Added alien technology", "number": 6, "mergedAt": "2018-06-28T13:25:07Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -1243,27 +1498,32 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjZkM2M1MzVjNWYxYjk1MWJlNjM4MDJlOTE1YjJkOTkyNjNhZDc2M2U=", - "message": "Merge pull request #5 from toolmantim/less-cowbell\n\n🙅🏼‍♂️ 🐄", + "message": "Merge pull request #5 from toolmantim/less-cowbell\n\n\ud83d\ude45\ud83c\udffc\u200d\u2642\ufe0f \ud83d\udc04", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "🙅🏼‍♂️ 🐄", + "title": "\ud83d\ude45\ud83c\udffc\u200d\u2642\ufe0f \ud83d\udc04", "number": 5, "mergedAt": "2018-06-28T13:22:15Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -1271,27 +1531,32 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmU1MTNkMTI5ZDc1YjgwYjViZGU5MzgzYTVhOWU4N2RiNjEzNDcyM2Q=", - "message": "🙅🏼‍♂️ 🐄", + "message": "\ud83d\ude45\ud83c\udffc\u200d\u2642\ufe0f \ud83d\udc04", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "🙅🏼‍♂️ 🐄", + "title": "\ud83d\ude45\ud83c\udffc\u200d\u2642\ufe0f \ud83d\udc04", "number": 5, "mergedAt": "2018-06-28T13:22:15Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -1299,27 +1564,32 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjA5NzBmNDBmZTdjN2NlM2IyYWE2YzhkM2Y4MzdjNmYyNzFiZWMwOTA=", - "message": "Merge pull request #4 from toolmantim/add-more-cow-bells\n\n🐄 More cowbell", + "message": "Merge pull request #4 from toolmantim/add-more-cow-bells\n\n\ud83d\udc04 More cowbell", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "🐄 More cowbell", + "title": "\ud83d\udc04 More cowbell", "number": 4, "mergedAt": "2018-06-28T13:19:17Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -1327,27 +1597,32 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmFkZTM1M2U3MGQwM2FlNDZkYTk3NDk5MWRlMGRmYzQyNzMyYmYyYzc=", "message": "Merge branch 'master' into add-more-cow-bells", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "🐄 More cowbell", + "title": "\ud83d\udc04 More cowbell", "number": 4, "mergedAt": "2018-06-28T13:19:17Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -1355,27 +1630,32 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjQ1MGQwZTQxMGY2MmNjMjRmMTY2ZjBhNWQxNTg0MzBmZjU4N2I0NjA=", - "message": "🐄 More cowbell", + "message": "\ud83d\udc04 More cowbell", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "🐄 More cowbell", + "title": "\ud83d\udc04 More cowbell", "number": 4, "mergedAt": "2018-06-28T13:19:17Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -1383,27 +1663,32 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjI5NWY5YmE4OTBiZjIyZWQ4OTI5ZGU3N2U5YTAwZGQ3YjQ2MTc3NGQ=", - "message": "Merge pull request #3 from toolmantim/add-monkey-api\n\n🐒 Add monkeys technology", + "message": "Merge pull request #3 from toolmantim/add-monkey-api\n\n\ud83d\udc12 Add monkeys technology", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "🐒 Add monkeys technology", + "title": "\ud83d\udc12 Add monkeys technology", "number": 3, "mergedAt": "2018-06-28T13:05:13Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -1411,27 +1696,32 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjI1MjMwMzFmNzk5MzY3NzY4MDBjMGM5NWUxZGU4ODI3MzYyMTljYmU=", "message": "Merge branch 'master' into add-monkey-api", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "🐒 Add monkeys technology", + "title": "\ud83d\udc12 Add monkeys technology", "number": 3, "mergedAt": "2018-06-28T13:05:13Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -1439,17 +1729,21 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjE1YWY0NjljZGZiYTM2MmRkZjA4OWMwM2I2MjllMmRiYWNjY2MxMDk=", "message": "Oh hai", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1458,11 +1752,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmYyODFjNGNjMWJiNDI4Y2ViYjE0ZWUzNzcxNWZiMmQzMDFhZGVlZTg=", "message": "Oh hai", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1471,11 +1768,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjIwNGUxNjM1M2ZmODdkMTE3YjgxNDQ1MTgxZDJjNjk5N2I0NjcxZGE=", "message": "Oh hai", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1484,11 +1784,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmI5ODYyZjA3ZDUyOTdmMjU1ZjhjYjFkMTU4ZjNlZDMxY2JlNDlkMjM=", "message": "Oh hai", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1497,11 +1800,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmNkNGQ2N2U2MWMxYWE1ZjZjOGMxOGNiZWFkZGYxNDJhMzE3NzNhYjE=", "message": "Oh hai", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1510,11 +1816,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmZkZDM5NDI0NGVkOTQ5ZmJhMTA4NDI3MjA0NGIwZjk4ZGMwNDIwMGM=", "message": "Oh hai", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1523,11 +1832,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjM5NDA5YzE2MzYwMzJlNjM4OTFiMjNjNWQyZTQzOGVjZGIzMTc0OTI=", "message": "Oh hai", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1536,11 +1848,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjE3YjdhYjdkYjcyMWM0MDRiNmVjNWQ3YjM5MTcxYzAzMjg3NmM2MDc=", "message": "Oh hai", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1549,11 +1864,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmE5Mjk5ZmMyYjE4YzI0MjEyNmFlZWIxMzg4NDU3ZjFlYTVkODBkNzA=", "message": "Oh hai", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1562,21 +1880,25 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjIxNzM5NzE2YTNlMDhiNDk1MDE3NzVmM2ZhOTIxNWFlMTI0OTk2YjE=", "message": "More monkeys", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, "associatedPullRequests": { "nodes": [ { - "title": "🐒 Add monkeys technology", + "title": "\ud83d\udc12 Add monkeys technology", "number": 3, "mergedAt": "2018-06-28T13:05:13Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -1584,17 +1906,21 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjBkYzAzNDI0ZDc0ZWZlYThmMzQzYWY3Zjc3M2VkMzcyZjc1YTFiZTk=", "message": "Merge pull request #2 from toolmantim/add-more-widgets\n\nAdds a new Widgets API", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1605,6 +1931,7 @@ "number": 2, "mergedAt": "2018-06-28T11:43:24Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -1612,17 +1939,21 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjYwNzQ2YmZhYzYzOTdhY2Y0OGQ4Zjc1NGNlMWYyNTY0OTBkMjgxMDE=", "message": "Adds a new Widgets API", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1633,6 +1964,7 @@ "number": 2, "mergedAt": "2018-06-28T11:43:24Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -1640,17 +1972,21 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjI5MTU4YzRmZjEwZjcwMzI2Zjc1OGQyNTFmYmMxZjhmM2ZlZDFjOGY=", "message": "Oh hai", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1659,11 +1995,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmY2MDhjM2VlMThlNzA1NzRjNDMxYTQ3ODNkZWNiNDNkM2MxMzIyNTE=", "message": "Oh hai", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1672,11 +2011,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmQ0N2E2YmVmZjk1ZTljYjVhM2E1ZTk3OWJlYTZjNWFhOWI5NDg1ODU=", "message": "Oh hai", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1685,11 +2027,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjk4Y2YyMTBhZTk4YjllNmMyYWNjNzU2MDZkMzNmMWIxNzk3N2U0ZWI=", "message": "Oh hai", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1698,11 +2043,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjk3NzUzODE1NzJlNDYzZTUxMmFlYjQ0MjZkNTBjZjBjMDAxNzI2Mjk=", "message": "Oh hai", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1711,11 +2059,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjg0MjAyZDE3Y2JkYmQyZTEyMDE5MWZhZjVkOTkzMTEwNTBhNmE3NWI=", "message": "Oh hai", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1724,11 +2075,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjJjNjllMTRkNzc3ZDNmZGYzMzg4YmE2ZWU0MWJjOWNkM2ZmNjA2NzI=", "message": "Oh hai", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1737,11 +2091,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjYwZjA5YzU0MzY0ZmNjNzI0YjgyZDlhOWRjZmY2MWM0YjlmMzU2NzA=", "message": "Oh hai", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1750,11 +2107,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmM3YzNjMDEwMTJiNGNjYjMwMDNiZjFjMzkwMzc1MTZmNDUxNTRkNTk=", "message": "Oh hai", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -1763,11 +2123,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjFiYWZhZjg3NTUxYjIzM2Q2ZGFlNDViNDMwNzA0N2FmMTJjZmM5Y2U=", "message": "Oh hai", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, diff --git a/test/fixtures/graphql-commits-paginated-2.json b/src/tests/fixtures/graphql/graphql-comparison-paginated-2.json similarity index 78% rename from test/fixtures/graphql-commits-paginated-2.json rename to src/tests/fixtures/graphql/graphql-comparison-paginated-2.json index 59db1e6c36..78b74a89fc 100644 --- a/test/fixtures/graphql-commits-paginated-2.json +++ b/src/tests/fixtures/graphql/graphql-comparison-paginated-2.json @@ -2,20 +2,24 @@ "data": { "repository": { "ref": { - "target": { - "history": { - "totalCount": 120, + "compare": { + "commits": { + "__typename": "ComparisonCommitConnection", "pageInfo": { + "__typename": "PageInfo", "hasNextPage": false, "endCursor": "d1d78f7d126c56c7165213cd2dffddcbc8cee8b6 119" }, "nodes": [ { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmU0MDNlZDc3MDMxN2Q3OTdlYzU4YzE3ZTlhNTJlMWU4NWI3NjBjMWU=", "message": "Bump again", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -24,11 +28,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjMwZWM0NGYwNTg2YTI2ZTY3NTNmMmFiOTAxMzNlNzM5MWM0Y2M1MDk=", "message": "Bump", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -37,11 +44,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjlkMWRlN2I5Y2Y1NDhiN2ExMWRkODdkYWU5M2NlMGI2ZjYwYThhYjE=", "message": "Moo", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -50,11 +60,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmZhYTFkYWQzNjJmZDMwNDQyMGViMDExYmFhNjdmOTNkNWQwZWY5ZGQ=", "message": "Yep", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -63,11 +76,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjg1ZDhhOTRjNTljYWFhYmU0MzI2ZGEzYjE5ZjZjNDllYjIxMzk1NDM=", "message": "Remove wat", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -76,11 +92,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmQ2MWM3MzhmZTM2ZTcwNzM0ZDUzMjY5NDA4ZWVjYTJjNjY3NWRkMTY=", "message": "Hello", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -89,11 +108,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjQ0YjNjOGE3ZTdiZjIwNjc3OGVlODJhYWQxYzVmMjYxMTUxNzc5ZWM=", "message": "Whoops", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -102,11 +124,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmYxN2U3YmQ2YWVjNjUwZjQ2OGNiOGYxNTgxNmQ2MzQyZDI2M2JmNDc=", "message": "Moo", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -115,11 +140,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmU4MGZkNzUwN2Y3Njc5ODFlMDg4NGMxN2E5NTI4YTlmNWM5YWVmYWY=", "message": "Change", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -128,11 +156,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjMzNDg0NWVjMGMyYmUzNGU3NTBhMWM1NzJhN2FkNjI1NTk0NTBjNzA=", "message": "Oh hai", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -141,11 +172,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmRlNjllNTQ0ZDI3NzZkYzY5NjNhOTBjNjUyZGRlY2Y1MDBmOTU5ODA=", "message": "Bump", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -154,11 +188,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmM1MDE1NDdiZmQxOGZjNjNlZTE4YTQzYjg4OTdjNmRmMGNkNzA1NWI=", "message": "Update draftah.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -167,11 +204,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjU2NTExOTY1ODBlOTg0YmNjN2I1YjY5NDc1YWMzNzRmMmFkNTFjYjU=", "message": "Update draftah.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -180,11 +220,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjIxZDcyYzAxODY1MWE3NWRiMWEwMDEyZGUyMzQ0MGIyZGU5NGZiYTA=", "message": "Update draftah.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -193,11 +236,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjQ1YzFlODM0ZmQ1YzJjMGZjZjAzYmViM2JjMTUzMzE4MDk1MjU5OTE=", "message": "Update draftah.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -206,11 +252,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjJkOTZhZmRhZWZhM2NhMjY3NTBmZGVkODM3ZTlmYWI5MDg0ZjkwNmM=", "message": "Update draftah.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -219,11 +268,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjFjN2FkMDkzNDQ0MTk1YzRiMmM1NzQyZDNkYTlhMTgzNWZiMDExYTM=", "message": "Create draftah.yml", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -232,11 +284,14 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOmRjZjNlYmQyNzA4YjY2OTgxZjA5OGY4N2Y4ZjU0NjUzNGM2MzVjNjY=", "message": "Merge pull request #1 from toolmantim/new-feature-1\n\nCreate new-feature.md", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -247,6 +302,7 @@ "number": 1, "mergedAt": "2018-06-26T12:09:55Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -254,17 +310,21 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjY0MTYzM2U4OTIwM2Q1ZTdkYWY0NjU2MDVjMzUyMzc5ZjlmMGE4MjM=", "message": "Create new-feature.md", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, @@ -275,6 +335,7 @@ "number": 1, "mergedAt": "2018-06-26T12:09:55Z", "author": { + "__typename": "User", "login": "toolmantim" }, "labels": { @@ -282,17 +343,21 @@ }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" - } + }, + "merged": true } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTM4NzMyNDgxOjNlNzk1YTUyYzA4OTVhMDc3NjczMGM5NzEzNTU2NTM3NGY3OTg2MzQ=", "message": "Initial commit", "author": { + "__typename": "GitActor", "name": "Tim Lucas", "user": { + "__typename": "User", "login": "toolmantim" } }, diff --git a/test/fixtures/__generated__/graphql-commits-rebase-merging.json b/src/tests/fixtures/graphql/graphql-comparison-rebase-merging.json similarity index 75% rename from test/fixtures/__generated__/graphql-commits-rebase-merging.json rename to src/tests/fixtures/graphql/graphql-comparison-rebase-merging.json index 2dc590bdac..d48e3b4bec 100644 --- a/test/fixtures/__generated__/graphql-commits-rebase-merging.json +++ b/src/tests/fixtures/graphql/graphql-comparison-rebase-merging.json @@ -2,21 +2,25 @@ "data": { "repository": { "ref": { - "target": { - "history": { - "totalCount": 11, + "compare": { + "commits": { + "__typename": "ComparisonCommitConnection", "pageInfo": { + "__typename": "PageInfo", "hasNextPage": false, "endCursor": "ed0a2bec8c895bb908638795c9da64763be75120 10" }, "nodes": [ { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmVkMGEyYmVjOGM4OTViYjkwODYzODc5NWM5ZGE2NDc2M2JlNzUxMjA=", "committedDate": "2019-04-27T13:38:31Z", "message": "Fix typo", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -28,7 +32,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/10", "body": "✍️ writing docs all day", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -41,18 +47,24 @@ "name": "skip-changelog" } ] - } + }, + "merged": true, + "baseRefName": "rebase-merging", + "headRefName": "add-documentation" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmZhNDQ2NGQ3MTIyNjg1OTY3MDNjNmI0OGNiNTI4NjkzNzE3NzRlYmY=", "committedDate": "2019-04-27T13:38:31Z", "message": "Add documentation", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -64,7 +76,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/10", "body": "✍️ writing docs all day", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -77,18 +91,24 @@ "name": "skip-changelog" } ] - } + }, + "merged": true, + "baseRefName": "rebase-merging", + "headRefName": "add-documentation" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjkxZjkzZTk1YTBhZGRlNzE0MTk4OTc2OWJlMWJhZTVmOWU2ZDQ4MDA=", "committedDate": "2019-04-27T13:38:25Z", "message": "Update Mongoose to 5.5.4", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -100,7 +120,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/9", "body": "📦 Package time! 📦", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -109,18 +131,24 @@ "isCrossRepository": false, "labels": { "nodes": [] - } + }, + "merged": true, + "baseRefName": "rebase-merging", + "headRefName": "chore/update-dependencies" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjk0NjY1ZWQyMWVjN2YwMTU1MzBkMmRhMWM0ODQzMTVlNTg2NmM2NzY=", "committedDate": "2019-04-27T13:38:25Z", "message": "Update Express to 4.16.4", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -132,7 +160,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/9", "body": "📦 Package time! 📦", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -141,18 +171,24 @@ "isCrossRepository": false, "labels": { "nodes": [] - } + }, + "merged": true, + "baseRefName": "rebase-merging", + "headRefName": "chore/update-dependencies" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmMyMmY2MjI0OGYzZDAzMWRmMDAxYWRiZWRkNmY0NGZkOGM3YzQyZDY=", "committedDate": "2019-04-27T13:38:19Z", "message": "Fixed another bug", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -164,7 +200,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/8", "body": "🐛 squashing", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -180,18 +218,24 @@ "name": "patch" } ] - } + }, + "merged": true, + "baseRefName": "rebase-merging", + "headRefName": "fix/bug-fixes" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmQ5MDRkYWFmMzFmOGZkN2M4NjI3MzlkYWNlMjM0ZGViNGQ5MTA0ZmE=", "committedDate": "2019-04-27T13:38:19Z", "message": "Fixed a bug", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -203,7 +247,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/8", "body": "🐛 squashing", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -219,18 +265,24 @@ "name": "patch" } ] - } + }, + "merged": true, + "baseRefName": "rebase-merging", + "headRefName": "fix/bug-fixes" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjRmMmEwNjkxOTRlYWY5MDAwMWM2ZDg3Y2EwN2RhNjA0ZDM1Zjc0YTA=", "committedDate": "2019-04-27T13:38:12Z", "message": "Adjust parameters", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -242,7 +294,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/7", "body": "![I'm kind of a big deal](https://media.giphy.com/media/9LFBOD8a1Ip2M/giphy.gif)", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -258,18 +312,24 @@ "name": "minor" } ] - } + }, + "merged": true, + "baseRefName": "rebase-merging", + "headRefName": "feature/big-feature" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjJjZjU4MTc2OTZiNTExODRlNzc5MmI4ODczYWI3MTk4ODIwYmU0ZGE=", "committedDate": "2019-04-27T13:38:12Z", "message": "Add big feature", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -281,7 +341,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/7", "body": "![I'm kind of a big deal](https://media.giphy.com/media/9LFBOD8a1Ip2M/giphy.gif)", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -297,18 +359,24 @@ "name": "minor" } ] - } + }, + "merged": true, + "baseRefName": "rebase-merging", + "headRefName": "feature/big-feature" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjIzMmM2MjQ5MTRmMzMyZmI2MmMwZDdiYmViYmYwYWE4NGIyYjJhZWU=", "committedDate": "2019-04-27T13:37:58Z", "message": "Add alien technology", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -320,7 +388,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/6", "body": "Space invasion 👾", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -336,16 +406,21 @@ "name": "major" } ] - } + }, + "merged": true, + "baseRefName": "rebase-merging", + "headRefName": "feature/alien-technology" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjNhYmJhNTkxOGZmN2QxMmZhYjMyMjA1N2ZiZGMyM2I1MzVlNzZkMDE=", "committedDate": "2019-04-27T12:12:59Z", "message": "Add project description to README", "author": { + "__typename": "GitActor", "name": "Ada Lovelace", "user": null }, @@ -354,12 +429,15 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmI5YWI2MGVlNGI4ZjNmYTg0Mjg2NDQ0NjViZGI1YTg4ZDcyZTY1M2E=", "committedDate": "2019-04-27T12:03:19Z", "message": "Initial commit", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, diff --git a/test/fixtures/__generated__/graphql-commits-squash-merging.json b/src/tests/fixtures/graphql/graphql-comparison-squash-merging.json similarity index 76% rename from test/fixtures/__generated__/graphql-commits-squash-merging.json rename to src/tests/fixtures/graphql/graphql-comparison-squash-merging.json index bd8b8f9795..320f4c9c29 100644 --- a/test/fixtures/__generated__/graphql-commits-squash-merging.json +++ b/src/tests/fixtures/graphql/graphql-comparison-squash-merging.json @@ -2,21 +2,25 @@ "data": { "repository": { "ref": { - "target": { - "history": { - "totalCount": 7, + "compare": { + "commits": { + "__typename": "ComparisonCommitConnection", "pageInfo": { + "__typename": "PageInfo", "hasNextPage": false, "endCursor": "3f37d85d6f8bc9e456a93600d1648fe81316ac7c 6" }, "nodes": [ { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjNmMzdkODVkNmY4YmM5ZTQ1NmE5MzYwMGQxNjQ4ZmU4MTMxNmFjN2M=", "committedDate": "2019-04-27T13:39:23Z", "message": "Add documentation (#15)\n\n* Add documentation\r\n\r\n* Fix typo", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -28,7 +32,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/15", "body": "✍️ writing docs all day", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -41,18 +47,24 @@ "name": "skip-changelog" } ] - } + }, + "merged": true, + "baseRefName": "squash-merging", + "headRefName": "add-documentation" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmViMmVjZjc4YjNjZGI0Y2VlMzc2MmZkZmQ0MjY2MmMwNWM3MzQzZTM=", "committedDate": "2019-04-27T13:39:17Z", "message": "Update dependencies (#14)\n\n* Update Express to 4.16.4\r\n\r\n* Update Mongoose to 5.5.4", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -64,7 +76,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/14", "body": "📦 Package time! 📦", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -73,18 +87,24 @@ "isCrossRepository": false, "labels": { "nodes": [] - } + }, + "merged": true, + "baseRefName": "squash-merging", + "headRefName": "chore/update-dependencies" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjc5Zjc4ODBmMTU1ZWU3ZGZkZGUxNmVlMTFmYTMwZmFhMDljODhlMjU=", "committedDate": "2019-04-27T13:39:10Z", "message": "Bug fixes (#13)\n\n* Fixed a bug\r\n\r\n* Fixed another bug", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -96,7 +116,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/13", "body": "🐛 squashing", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -112,18 +134,24 @@ "name": "patch" } ] - } + }, + "merged": true, + "baseRefName": "squash-merging", + "headRefName": "fix/bug-fixes" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmUyMTZhODViOGNlMzU2MzRkN2IyMGNlMjU5YmRmZTA4ZjFhMGJiZGI=", "committedDate": "2019-04-27T13:39:03Z", "message": "Add big feature (#12)\n\n* Add big feature\r\n\r\n* Adjust parameters", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -135,7 +163,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/12", "body": "![I'm kind of a big deal](https://media.giphy.com/media/9LFBOD8a1Ip2M/giphy.gif)", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -151,18 +181,24 @@ "name": "minor" } ] - } + }, + "merged": true, + "baseRefName": "squash-merging", + "headRefName": "feature/big-feature" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmQzNmM3YmU4OTBlOGEwYzY3NWYyNzUzMDk0YzYyNmQyN2RiOWI5NmM=", "committedDate": "2019-04-27T13:38:46Z", "message": "Add alien technology (#11)", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, @@ -174,7 +210,9 @@ "url": "https://github.com/toolmantim/release-drafter-test-project/pull/11", "body": "Space invasion 👾", "author": { - "login": "TimonVS" + "login": "TimonVS", + "__typename": "User", + "url": "https://github.com/TimonVS" }, "baseRepository": { "nameWithOwner": "toolmantim/release-drafter-test-project" @@ -190,16 +228,21 @@ "name": "major" } ] - } + }, + "merged": true, + "baseRefName": "squash-merging", + "headRefName": "feature/alien-technology" } ] } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjNhYmJhNTkxOGZmN2QxMmZhYjMyMjA1N2ZiZGMyM2I1MzVlNzZkMDE=", "committedDate": "2019-04-27T12:12:59Z", "message": "Add project description to README", "author": { + "__typename": "GitActor", "name": "Ada Lovelace", "user": null }, @@ -208,12 +251,15 @@ } }, { + "__typename": "Commit", "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmI5YWI2MGVlNGI4ZjNmYTg0Mjg2NDQ0NjViZGI1YTg4ZDcyZTY1M2E=", "committedDate": "2019-04-27T12:03:19Z", "message": "Initial commit", "author": { + "__typename": "GitActor", "name": "Timon van Spronsen", "user": { + "__typename": "User", "login": "TimonVS" } }, diff --git a/src/tests/fixtures/graphql/graphql-include-null-path-forking.json b/src/tests/fixtures/graphql/graphql-include-null-path-forking.json new file mode 100644 index 0000000000..155474ee3e --- /dev/null +++ b/src/tests/fixtures/graphql/graphql-include-null-path-forking.json @@ -0,0 +1,60 @@ +{ + "data": { + "repository": { + "object": { + "__typename": "Commit", + "history": { + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "endCursor": "96cf76bb3470913a449d505f2011544b661652f4 13" + }, + "nodes": [ + { + "id": "MDY6Q29tbWl0MjY3MTQyMTYxOjk2Y2Y3NmJiMzQ3MDkxM2E0NDlkNTA1ZjIwMTE1NDRiNjYxNjUyZjQ=" + }, + { + "id": "MDY6Q29tbWl0MjY3MTQyMTYxOjlhZjJlZWMyYjY4NDA0MWY5ZDE3YmFlM2IwNTAwMjYwNGU5NGE4MzM=" + }, + { + "id": "MDY6Q29tbWl0MjY3MTQyMTYxOjYxMTAwMDY2OWJmZTRlYjQ2NjUzYTQ0YjU2MGUzNjFkYjRhZDc5MjQ=" + }, + { + "id": "MDY6Q29tbWl0MjY3MTQyMTYxOjIyOWY2MWY0ODNmNDEzODFjODUwMThkNmZmMGIwZjMyYzk0NWNhMTI=" + }, + { + "id": "MDY6Q29tbWl0MjY3MTQyMTYxOmMzYThhNDY4NmFlMDkwNzdmNDk0YzcyOGJhMzc5ZTdhMDU4ZWJiYzY=" + }, + { + "id": "MDY6Q29tbWl0MjY3MTQyMTYxOmMwOWM2NmZkNWU2YjU2NTE3MzRlMTI1NGY4YjkzZTZkOWI1ZjI5ZmQ=" + }, + { + "id": "MDY6Q29tbWl0MjY3MTQyMTYxOmNjNTliYjUxN2EwOGYxNDhkZThlM2JiMDIxOTkyNjQ1YTQwMGNlNzY=" + }, + { + "id": "MDY6Q29tbWl0MjY3MTQyMTYxOjg1ODExZmJkYmE2OTViYjk3ZDc3YTVlMmE5NjNiMzFmNTM4YTgyMjM=" + }, + { + "id": "MDY6Q29tbWl0MjY3MTQyMTYxOmVkM2FmY2QzNmM4MDhlZWIxNWFhZjc0Zjg3MTNiODVkZTA2ZjBhMTk=" + }, + { + "id": "MDY6Q29tbWl0MjY3MTQyMTYxOmQyMTM0MjNmOTBjZjU5Y2YxMmRiNmUwMTIxYjgwNzE1NTE4NGRiNmQ=" + }, + { + "id": "MDY6Q29tbWl0MjY3MTQyMTYxOmQ5NTJjMjU1MDI5N2E4NTE1MmJlMWM4MjYwYjI1ZmI4OWYyODFkNDk=" + }, + { + "id": "MDY6Q29tbWl0MjY3MTQyMTYxOjk0ZGViYjMwZTAyYjExNTc1YWQ0YzBmMDNmMWE1ZjdmOTJiMTFiMjg=" + }, + { + "id": "MDY6Q29tbWl0MjY3MTQyMTYxOjNhYmJhNTkxOGZmN2QxMmZhYjMyMjA1N2ZiZGMyM2I1MzVlNzZkMDE=" + }, + { + "id": "MDY6Q29tbWl0MjY3MTQyMTYxOmI5YWI2MGVlNGI4ZjNmYTg0Mjg2NDQ0NjViZGI1YTg4ZDcyZTY1M2E=" + } + ] + } + } + } + } +} diff --git a/src/tests/fixtures/graphql/graphql-include-null-path-overlapping-label.json b/src/tests/fixtures/graphql/graphql-include-null-path-overlapping-label.json new file mode 100644 index 0000000000..54b3c48347 --- /dev/null +++ b/src/tests/fixtures/graphql/graphql-include-null-path-overlapping-label.json @@ -0,0 +1,39 @@ +{ + "data": { + "repository": { + "object": { + "__typename": "Commit", + "history": { + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "endCursor": "b2ed3f089aa1022e91e0a9d109ab876fde688c34 6" + }, + "nodes": [ + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmIyZWQzZjA4OWFhMTAyMmU5MWUwYTlkMTA5YWI4NzZmZGU2ODhjMzQ=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmI2NzdkYjY0MGQwYjljMjViYjVmYjE3NjhiMzQ3NGEyNTI5MzQ5NzM=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjQ3N2FjNTBlNGNkMWMzOTRhYmJkNjcwZjFkNTkwY2I3OWJjOThhYzk=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjliYTBlNTQ4MmMwY2I1OWU0MDBmYzhmNWUyNGU1OWMyMDE2N2U5MGE=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmY5YmYyMmMzNTI0MTcyODg0NTJhYmZkMzlkMGIxY2JhMmViYjk2MWE=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjNhYmJhNTkxOGZmN2QxMmZhYjMyMjA1N2ZiZGMyM2I1MzVlNzZkMDE=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmI5YWI2MGVlNGI4ZjNmYTg0Mjg2NDQ0NjViZGI1YTg4ZDcyZTY1M2E=" + } + ] + } + } + } + } +} diff --git a/src/tests/fixtures/graphql/graphql-include-null-path-rebase-merging.json b/src/tests/fixtures/graphql/graphql-include-null-path-rebase-merging.json new file mode 100644 index 0000000000..7184b65c47 --- /dev/null +++ b/src/tests/fixtures/graphql/graphql-include-null-path-rebase-merging.json @@ -0,0 +1,51 @@ +{ + "data": { + "repository": { + "object": { + "__typename": "Commit", + "history": { + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "endCursor": "ed0a2bec8c895bb908638795c9da64763be75120 10" + }, + "nodes": [ + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmVkMGEyYmVjOGM4OTViYjkwODYzODc5NWM5ZGE2NDc2M2JlNzUxMjA=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmZhNDQ2NGQ3MTIyNjg1OTY3MDNjNmI0OGNiNTI4NjkzNzE3NzRlYmY=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjkxZjkzZTk1YTBhZGRlNzE0MTk4OTc2OWJlMWJhZTVmOWU2ZDQ4MDA=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjk0NjY1ZWQyMWVjN2YwMTU1MzBkMmRhMWM0ODQzMTVlNTg2NmM2NzY=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmMyMmY2MjI0OGYzZDAzMWRmMDAxYWRiZWRkNmY0NGZkOGM3YzQyZDY=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmQ5MDRkYWFmMzFmOGZkN2M4NjI3MzlkYWNlMjM0ZGViNGQ5MTA0ZmE=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjRmMmEwNjkxOTRlYWY5MDAwMWM2ZDg3Y2EwN2RhNjA0ZDM1Zjc0YTA=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjJjZjU4MTc2OTZiNTExODRlNzc5MmI4ODczYWI3MTk4ODIwYmU0ZGE=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjIzMmM2MjQ5MTRmMzMyZmI2MmMwZDdiYmViYmYwYWE4NGIyYjJhZWU=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjNhYmJhNTkxOGZmN2QxMmZhYjMyMjA1N2ZiZGMyM2I1MzVlNzZkMDE=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmI5YWI2MGVlNGI4ZjNmYTg0Mjg2NDQ0NjViZGI1YTg4ZDcyZTY1M2E=" + } + ] + } + } + } + } +} diff --git a/src/tests/fixtures/graphql/graphql-include-null-path-squash-merging.json b/src/tests/fixtures/graphql/graphql-include-null-path-squash-merging.json new file mode 100644 index 0000000000..ea1d1f210e --- /dev/null +++ b/src/tests/fixtures/graphql/graphql-include-null-path-squash-merging.json @@ -0,0 +1,39 @@ +{ + "data": { + "repository": { + "object": { + "__typename": "Commit", + "history": { + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "endCursor": "3f37d85d6f8bc9e456a93600d1648fe81316ac7c 6" + }, + "nodes": [ + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjNmMzdkODVkNmY4YmM5ZTQ1NmE5MzYwMGQxNjQ4ZmU4MTMxNmFjN2M=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmViMmVjZjc4YjNjZGI0Y2VlMzc2MmZkZmQ0MjY2MmMwNWM3MzQzZTM=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjc5Zjc4ODBmMTU1ZWU3ZGZkZGUxNmVlMTFmYTMwZmFhMDljODhlMjU=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmUyMTZhODViOGNlMzU2MzRkN2IyMGNlMjU5YmRmZTA4ZjFhMGJiZGI=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmQzNmM3YmU4OTBlOGEwYzY3NWYyNzUzMDk0YzYyNmQyN2RiOWI5NmM=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjNhYmJhNTkxOGZmN2QxMmZhYjMyMjA1N2ZiZGMyM2I1MzVlNzZkMDE=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmI5YWI2MGVlNGI4ZjNmYTg0Mjg2NDQ0NjViZGI1YTg4ZDcyZTY1M2E=" + } + ] + } + } + } + } +} diff --git a/src/tests/fixtures/graphql/graphql-include-path-src-5.md-forking.json b/src/tests/fixtures/graphql/graphql-include-path-src-5.md-forking.json new file mode 100644 index 0000000000..dfc76dd78e --- /dev/null +++ b/src/tests/fixtures/graphql/graphql-include-path-src-5.md-forking.json @@ -0,0 +1,24 @@ +{ + "data": { + "repository": { + "object": { + "__typename": "Commit", + "history": { + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "endCursor": "96cf76bb3470913a449d505f2011544b661652f4 1" + }, + "nodes": [ + { + "id": "MDY6Q29tbWl0MjY3MTQyMTYxOjg1ODExZmJkYmE2OTViYjk3ZDc3YTVlMmE5NjNiMzFmNTM4YTgyMjM=" + }, + { + "id": "MDY6Q29tbWl0MjY3MTQyMTYxOmVkM2FmY2QzNmM4MDhlZWIxNWFhZjc0Zjg3MTNiODVkZTA2ZjBhMTk=" + } + ] + } + } + } + } +} diff --git a/src/tests/fixtures/graphql/graphql-include-path-src-5.md-overlapping-label.json b/src/tests/fixtures/graphql/graphql-include-path-src-5.md-overlapping-label.json new file mode 100644 index 0000000000..8f3de832f4 --- /dev/null +++ b/src/tests/fixtures/graphql/graphql-include-path-src-5.md-overlapping-label.json @@ -0,0 +1,21 @@ +{ + "data": { + "repository": { + "object": { + "__typename": "Commit", + "history": { + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "endCursor": "b2ed3f089aa1022e91e0a9d109ab876fde688c34 0" + }, + "nodes": [ + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmIyZWQzZjA4OWFhMTAyMmU5MWUwYTlkMTA5YWI4NzZmZGU2ODhjMzQ=" + } + ] + } + } + } + } +} diff --git a/src/tests/fixtures/graphql/graphql-include-path-src-5.md-rebase-merging.json b/src/tests/fixtures/graphql/graphql-include-path-src-5.md-rebase-merging.json new file mode 100644 index 0000000000..7172973024 --- /dev/null +++ b/src/tests/fixtures/graphql/graphql-include-path-src-5.md-rebase-merging.json @@ -0,0 +1,24 @@ +{ + "data": { + "repository": { + "object": { + "__typename": "Commit", + "history": { + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "endCursor": "ed0a2bec8c895bb908638795c9da64763be75120 1" + }, + "nodes": [ + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmVkMGEyYmVjOGM4OTViYjkwODYzODc5NWM5ZGE2NDc2M2JlNzUxMjA=" + }, + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOmZhNDQ2NGQ3MTIyNjg1OTY3MDNjNmI0OGNiNTI4NjkzNzE3NzRlYmY=" + } + ] + } + } + } + } +} diff --git a/src/tests/fixtures/graphql/graphql-include-path-src-5.md-squash-merging.json b/src/tests/fixtures/graphql/graphql-include-path-src-5.md-squash-merging.json new file mode 100644 index 0000000000..83d9dc8e21 --- /dev/null +++ b/src/tests/fixtures/graphql/graphql-include-path-src-5.md-squash-merging.json @@ -0,0 +1,21 @@ +{ + "data": { + "repository": { + "object": { + "__typename": "Commit", + "history": { + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "endCursor": "3f37d85d6f8bc9e456a93600d1648fe81316ac7c 0" + }, + "nodes": [ + { + "id": "MDY6Q29tbWl0MTgzNzY2OTUzOjNmMzdkODVkNmY4YmM5ZTQ1NmE5MzYwMGQxNjQ4ZmU4MTMxNmFjN2M=" + } + ] + } + } + } + } +} diff --git a/src/tests/fixtures/graphql/graphql-recent-merged-prs-with-paths.json b/src/tests/fixtures/graphql/graphql-recent-merged-prs-with-paths.json new file mode 100644 index 0000000000..33e2414504 --- /dev/null +++ b/src/tests/fixtures/graphql/graphql-recent-merged-prs-with-paths.json @@ -0,0 +1,63 @@ +{ + "data": { + "repository": { + "pullRequests": { + "__typename": "PullRequestConnection", + "nodes": [ + { + "__typename": "PullRequest", + "title": "Touches src", + "number": 100, + "url": "https://github.com/toolmantim/release-drafter-test-project/pull/100", + "body": "Updates src/5.md", + "author": { + "__typename": "User", + "login": "TimonVS", + "url": "https://github.com/TimonVS" + }, + "baseRepository": { + "__typename": "Repository", + "nameWithOwner": "toolmantim/release-drafter-test-project" + }, + "mergedAt": "2019-04-27T14:00:00Z", + "isCrossRepository": false, + "labels": { "__typename": "LabelConnection", "nodes": [] }, + "merged": true, + "baseRefName": "master", + "headRefName": "touches-src", + "mergeCommit": { + "__typename": "Commit", + "oid": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + }, + { + "__typename": "PullRequest", + "title": "Touches other", + "number": 101, + "url": "https://github.com/toolmantim/release-drafter-test-project/pull/101", + "body": "Unrelated change", + "author": { + "__typename": "User", + "login": "TimonVS", + "url": "https://github.com/TimonVS" + }, + "baseRepository": { + "__typename": "Repository", + "nameWithOwner": "toolmantim/release-drafter-test-project" + }, + "mergedAt": "2019-04-27T15:00:00Z", + "isCrossRepository": false, + "labels": { "__typename": "LabelConnection", "nodes": [] }, + "merged": true, + "baseRefName": "master", + "headRefName": "touches-other", + "mergeCommit": { + "__typename": "Commit", + "oid": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + } + } + ] + } + } + } +} diff --git a/src/tests/fixtures/graphql/graphql-recent-merged-prs.json b/src/tests/fixtures/graphql/graphql-recent-merged-prs.json new file mode 100644 index 0000000000..b79e429a4d --- /dev/null +++ b/src/tests/fixtures/graphql/graphql-recent-merged-prs.json @@ -0,0 +1,40 @@ +{ + "data": { + "repository": { + "pullRequests": { + "__typename": "PullRequestConnection", + "nodes": [ + { + "__typename": "PullRequest", + "title": "Add new feature", + "number": 6, + "url": "https://github.com/toolmantim/release-drafter-test-project/pull/6", + "body": "New feature description", + "author": { + "__typename": "User", + "login": "TimonVS", + "url": "https://github.com/TimonVS" + }, + "baseRepository": { + "__typename": "Repository", + "nameWithOwner": "toolmantim/release-drafter-test-project" + }, + "mergedAt": "2019-04-27T14:00:00Z", + "isCrossRepository": false, + "labels": { + "__typename": "LabelConnection", + "nodes": [] + }, + "merged": true, + "baseRefName": "master", + "headRefName": "new-feature", + "mergeCommit": { + "__typename": "Commit", + "oid": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2" + } + } + ] + } + } + } +} diff --git a/src/tests/fixtures/pull_requests/files.json b/src/tests/fixtures/pull_requests/files.json new file mode 100644 index 0000000000..96d8992326 --- /dev/null +++ b/src/tests/fixtures/pull_requests/files.json @@ -0,0 +1,362 @@ +[ + { + "sha": "db421a0903d781a6bee2ae83a28f851c531da509", + "filename": ".devcontainer/Dockerfile", + "status": "removed", + "additions": 0, + "deletions": 16, + "changes": 16, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/40bb31b945af999c14bc1339eae4c0371d97cac7/.devcontainer%2FDockerfile", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/40bb31b945af999c14bc1339eae4c0371d97cac7/.devcontainer%2FDockerfile", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.devcontainer%2FDockerfile?ref=40bb31b945af999c14bc1339eae4c0371d97cac7", + "patch": "@@ -1,16 +0,0 @@\n-# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/javascript-node/.devcontainer/base.Dockerfile\n-\n-# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 16, 14, 12, 16-bullseye, 14-bullseye, 12-bullseye, 16-buster, 14-buster, 12-buster\n-ARG VARIANT=\"16-bullseye\"\n-FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}\n-\n-# [Optional] Uncomment this section to install additional OS packages.\n-# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \\\n-# && apt-get -y install --no-install-recommends \n-\n-# [Optional] Uncomment if you want to install an additional version of node using nvm\n-# ARG EXTRA_NODE_VERSION=10\n-# RUN su node -c \"source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}\"\n-\n-# [Optional] Uncomment if you want to install more global node modules\n-# RUN su node -c \"npm install -g \"" + }, + { + "sha": "d379a4224d0de22e4d1c2494688467409bb8bbb9", + "filename": ".devcontainer/devcontainer.json", + "status": "modified", + "additions": 45, + "deletions": 24, + "changes": 69, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.devcontainer%2Fdevcontainer.json", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.devcontainer%2Fdevcontainer.json", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.devcontainer%2Fdevcontainer.json?ref=e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "patch": "@@ -1,27 +1,48 @@\n-// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:\n-// https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/javascript-node\n {\n- \"name\": \"Node.js\",\n- \"build\": {\n- \"dockerfile\": \"Dockerfile\",\n- // Update 'VARIANT' to pick a Node version: 16, 14, 12.\n- // Append -bullseye or -buster to pin to an OS version.\n- // Use -bullseye variants on local arm64/Apple Silicon.\n- \"args\": { \"VARIANT\": \"16\" }\n- },\n-\n- // Set *default* container specific settings.json values on container create.\n- \"settings\": {},\n-\n- // Add the IDs of extensions you want installed when the container is created.\n- \"extensions\": [\"dbaeumer.vscode-eslint\"],\n-\n- // Use 'forwardPorts' to make a list of ports inside the container available locally.\n- // \"forwardPorts\": [],\n-\n- // Use 'postCreateCommand' to run commands after the container is created.\n+ \"name\": \"Release Drafter\",\n+ \"image\": \"mcr.microsoft.com/devcontainers/typescript-node:24\",\n \"postCreateCommand\": \"npm install\",\n-\n- // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.\n- \"remoteUser\": \"node\"\n+ \"customizations\": {\n+ \"codespaces\": {\n+ \"openFiles\": [\"README.md\"]\n+ },\n+ \"vscode\": {\n+ \"extensions\": [\n+ \"bierner.markdown-preview-github-styles\",\n+ \"davidanson.vscode-markdownlint\",\n+ \"dbaeumer.vscode-eslint\",\n+ \"esbenp.prettier-vscode\",\n+ \"github.copilot\",\n+ \"github.copilot-chat\",\n+ \"github.vscode-github-actions\",\n+ \"github.vscode-pull-request-github\",\n+ \"me-dutour-mathieu.vscode-github-actions\",\n+ \"redhat.vscode-yaml\",\n+ \"rvest.vs-code-prettier-eslint\",\n+ \"yzhang.markdown-all-in-one\",\n+ \"vivaxy.vscode-conventional-commits\"\n+ ],\n+ \"settings\": {\n+ \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n+ \"editor.tabSize\": 2,\n+ \"editor.formatOnSave\": true,\n+ \"markdown.extension.list.indentationSize\": \"adaptive\",\n+ \"markdown.extension.italic.indicator\": \"_\",\n+ \"markdown.extension.orderedList.marker\": \"one\"\n+ }\n+ }\n+ },\n+ \"remoteEnv\": {\n+ \"GITHUB_TOKEN\": \"${localEnv:GITHUB_TOKEN}\"\n+ },\n+ \"features\": {\n+ \"ghcr.io/devcontainers/features/github-cli:1\": {},\n+ \"ghcr.io/devcontainers-community/npm-features/prettier:1\": {}\n+ },\n+ \"mounts\": [\n+ /**\n+ * Mount local ssh keys such as container's git may use ssh commit signing\n+ */\n+ \"source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh,target=/home/node/.ssh,type=bind,consistency=cached\"\n+ ]\n }" + }, + { + "sha": "3c64dc4246002140cad460a885911fe9b91640d6", + "filename": ".dockerignore", + "status": "removed", + "additions": 0, + "deletions": 9, + "changes": 9, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/40bb31b945af999c14bc1339eae4c0371d97cac7/.dockerignore", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/40bb31b945af999c14bc1339eae4c0371d97cac7/.dockerignore", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.dockerignore?ref=40bb31b945af999c14bc1339eae4c0371d97cac7", + "patch": "@@ -1,9 +0,0 @@\n-design\n-node_modules\n-.env\n-.env.example\n-coverage\n-.buildkite\n-*.pem\n-.git\n-dist/" + }, + { + "sha": "9a480def3617efe3f4f3c72fac691b046d42f055", + "filename": ".env.example", + "status": "removed", + "additions": 0, + "deletions": 9, + "changes": 9, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/40bb31b945af999c14bc1339eae4c0371d97cac7/.env.example", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/40bb31b945af999c14bc1339eae4c0371d97cac7/.env.example", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.env.example?ref=40bb31b945af999c14bc1339eae4c0371d97cac7", + "patch": "@@ -1,9 +0,0 @@\n-# The ID of your GitHub App\n-APP_ID=13956\n-WEBHOOK_SECRET=development\n-\n-# Use `trace` to get verbose logging or `info` to show less\n-LOG_LEVEL=debug\n-\n-# Go to https://smee.io/new set this to the URL that you are redirected to.\n-WEBHOOK_PROXY_URL=" + }, + { + "sha": "20bf94d26929223d6381e18d0a986d4ead716f3a", + "filename": ".eslintignore", + "status": "removed", + "additions": 0, + "deletions": 3, + "changes": 3, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/40bb31b945af999c14bc1339eae4c0371d97cac7/.eslintignore", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/40bb31b945af999c14bc1339eae4c0371d97cac7/.eslintignore", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.eslintignore?ref=40bb31b945af999c14bc1339eae4c0371d97cac7", + "patch": "@@ -1,3 +0,0 @@\n-coverage\n-dist/\n-node_modules/" + }, + { + "sha": "54438a8884125fe938b7c277f53ecf14af7e2916", + "filename": ".eslintrc", + "status": "removed", + "additions": 0, + "deletions": 17, + "changes": 17, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/40bb31b945af999c14bc1339eae4c0371d97cac7/.eslintrc", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/40bb31b945af999c14bc1339eae4c0371d97cac7/.eslintrc", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.eslintrc?ref=40bb31b945af999c14bc1339eae4c0371d97cac7", + "patch": "@@ -1,17 +0,0 @@\n-{\n- \"extends\": [\"eslint:recommended\", \"prettier\", \"plugin:unicorn/recommended\"],\n- \"plugins\": [\"prettier\"],\n- \"parserOptions\": {\n- \"ecmaVersion\": 12\n- },\n- \"env\": { \"node\": true, \"es6\": true },\n- \"rules\": {\n- \"prettier/prettier\": \"warn\",\n- \"no-console\": \"off\",\n- \"no-unused-vars\": \"warn\",\n- \"unicorn/no-null\": \"off\",\n- \"unicorn/prefer-module\": \"off\",\n- \"unicorn/prevent-abbreviations\": \"off\",\n- \"unicorn/prefer-top-level-await\": \"off\"\n- }\n-}" + }, + { + "sha": "6ba54568bf5e6ba01650b3512f9f3f0e07d022e3", + "filename": ".gitattributes", + "status": "modified", + "additions": 2, + "deletions": 2, + "changes": 4, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.gitattributes", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.gitattributes", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.gitattributes?ref=e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "patch": "@@ -1,3 +1,3 @@\n * text=auto eol=lf\n-*.png binary\n-*.sketch binary\n+\n+dist/** -diff linguist-generated=true" + }, + { + "sha": "ffb47fabe6d522ec8fc75873c429cd1ea3d13696", + "filename": ".github/.codeql-config.yml", + "status": "added", + "additions": 5, + "deletions": 0, + "changes": 5, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2F.codeql-config.yml", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2F.codeql-config.yml", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.github%2F.codeql-config.yml?ref=e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "patch": "@@ -0,0 +1,5 @@\n+name: JavaScript CodeQL Configuration\n+\n+paths-ignore:\n+ - node_modules\n+ - dist" + }, + { + "sha": "91239edd96e4658a36be42a86921cd5febbd4aee", + "filename": ".github/.licensed.yml", + "status": "added", + "additions": 18, + "deletions": 0, + "changes": 18, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2F.licensed.yml", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2F.licensed.yml", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.github%2F.licensed.yml?ref=e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "patch": "@@ -0,0 +1,18 @@\n+# See: https://github.com/licensee/licensed/blob/main/docs/configuration.md\n+\n+sources:\n+ npm: true\n+\n+allowed:\n+ - apache-2.0\n+ - bsd-2-clause\n+ - bsd-3-clause\n+ - isc\n+ - mit\n+ - cc0-1.0\n+ - other\n+\n+ignored:\n+ npm:\n+ # Used by Rollup.js when building in GitHub Actions\n+ - '@rollup/rollup-linux-x64-gnu'" + }, + { + "sha": "07c1b5ef0c3dd0c89507dca30616a583d8fa1727", + "filename": ".github/actions/common-setup/action.yml", + "status": "modified", + "additions": 2, + "deletions": 5, + "changes": 7, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2Factions%2Fcommon-setup%2Faction.yml", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2Factions%2Fcommon-setup%2Faction.yml", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.github%2Factions%2Fcommon-setup%2Faction.yml?ref=e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "patch": "@@ -8,11 +8,8 @@ runs:\n - name: Setup Node.js\n uses: actions/setup-node@v6\n with:\n- node-version: 20\n-\n- - name: Install npm\n- shell: bash\n- run: npm install -g npm@11.5.2\n+ node-version-file: .node-version\n+ cache: npm\n \n - name: Install dependencies\n shell: bash" + }, + { + "sha": "2ea71de604d7d4d57674caa998c8f16459054164", + "filename": ".github/boomper.yml", + "status": "removed", + "additions": 0, + "deletions": 3, + "changes": 3, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/40bb31b945af999c14bc1339eae4c0371d97cac7/.github%2Fboomper.yml", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/40bb31b945af999c14bc1339eae4c0371d97cac7/.github%2Fboomper.yml", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.github%2Fboomper.yml?ref=40bb31b945af999c14bc1339eae4c0371d97cac7", + "patch": "@@ -1,3 +0,0 @@\n-updates:\n- - path: README.md\n- pattern: 'uses = \"release-drafter/release-drafter@(.*)\"'" + }, + { + "sha": "c651991817da22ec08216452f04b12d026c2c9ab", + "filename": ".github/copilot-instructions.md", + "status": "added", + "additions": 125, + "deletions": 0, + "changes": 125, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2Fcopilot-instructions.md", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2Fcopilot-instructions.md", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.github%2Fcopilot-instructions.md?ref=e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "patch": "@@ -0,0 +1,125 @@\n+# Copilot Instructions\n+\n+This GitHub Action is written in TypeScript and transpiled to JavaScript. Both\n+the TypeScript sources and the **generated** JavaScript code are contained in\n+this repository. The TypeScript sources are contained in the `src` directory and\n+the JavaScript code is contained in the `dist` directory. A GitHub Actions\n+workflow checks that the JavaScript code in `dist` is up-to-date. Therefore, you\n+should not review any changes to the contents of the `dist` folder and it is\n+expected that the JavaScript code in `dist` closely mirrors the TypeScript code\n+it is generated from.\n+\n+## Repository Structure\n+\n+| Path | Description |\n+| ------------------- | --------------------------------------------------------------- |\n+| `.devcontainer/` | Development Container Configuration |\n+| `.github/` | GitHub Configuration |\n+| `.licenses/` | License Information |\n+| `.vscode/` | Visual Studio Code Configuration |\n+| `autolabeler/` | Entrypoint for the Autolabeler action |\n+| `badges/` | Badges for readme |\n+| `coverage/` | Non-versionned files about coverage. |\n+| `dist/` | Generated JavaScript Code |\n+| `docs/` | Complementary documentation to README.md |\n+| `drafter/` | Entrypoint for the Drafter action |\n+| `src/` | TypeScript Source Code |\n+| `.node-version` | Node.js Version Configuration |\n+| `.prettierrc.yml` | Prettier Formatter Configuration |\n+| `action.yml` | Entrypoint to the Drafter action. Also available under drafter/ |\n+| `eslint.config.mjs` | ESLint Configuration |\n+| `vite.config.ts` | Vite configuration for bundling and testing |\n+| `LICENSE` | License File |\n+| `package.json` | NPM Package Configuration |\n+| `README.md` | Project Documentation |\n+| `tsconfig.json` | TypeScript Configuration |\n+\n+## Environment Setup\n+\n+Install dependencies by running:\n+\n+```bash\n+npm install\n+```\n+\n+## Testing\n+\n+Ensure all unit tests pass by running:\n+\n+```bash\n+npm run test\n+```\n+\n+Tests should exist in the `src/tests` directory. They are powered by `vitest`.\n+\n+## Bundling\n+\n+Any time files in the `src` directory are changed, you should run the following\n+command to bundle the TypeScript code into JavaScript:\n+\n+```bash\n+npm run build\n+```\n+\n+## General Coding Guidelines\n+\n+- Follow standard TypeScript and JavaScript coding conventions and best\n+ practices\n+- Changes should maintain consistency with existing patterns and style\n+- Document changes clearly and thoroughly, including updates to existing\n+ comments when appropriate\n+- Do not include basic, unnecessary comments that simply restate what the code\n+ is doing (focus on explaining _why_, not _what_)\n+- Use consistent error handling patterns throughout the codebase\n+- Use TypeScript's type system to ensure type safety and clarity\n+- Keep functions focused and manageable\n+- Use descriptive variable and function names that clearly convey their purpose\n+- Use JSDoc comments to document functions, classes, and complex logic\n+- After doing any refactoring, ensure to run `npm run test` to ensure that all\n+ tests still pass and coverage requirements are met\n+- When suggesting code changes, always opt for the most maintainable approach.\n+ Try your best to keep the code clean and follow \"Don't Repeat Yourself\" (DRY)\n+ principles\n+- Avoid unnecessary complexity and always consider the long-term maintainability\n+ of the code\n+- When writing unit tests, try to consider edge cases as well as the main path\n+ of success. This will help ensure that the code is robust and can handle\n+ unexpected inputs or situations\n+- Use the `@actions/core` package for logging over `console` to ensure\n+ compatibility with GitHub Actions logging features\n+\n+### Versioning\n+\n+GitHub Actions are versioned using branch and tag names. Please ensure the\n+version in the project's `package.json` is updated to reflect the changes made\n+in the codebase. The version should follow\n+[Semantic Versioning](https://semver.org/) principles.\n+\n+## Pull Request Guidelines\n+\n+When creating a pull request (PR), please ensure that:\n+\n+- Keep changes focused and minimal (avoid large changes, or consider breaking\n+ them into separate, smaller PRs)\n+- Formatting checks pass\n+- Linting checks pass\n+- Unit tests pass and coverage requirements are met\n+- The action has been transpiled to JavaScript and the `dist` directory is\n+ up-to-date with the latest changes in the `src` directory\n+- If necessary, the `README.md` file is updated to reflect any changes in\n+ functionality or usage\n+\n+The body of the PR should include:\n+\n+- A summary of the changes\n+- A special note of any changes to dependencies\n+- A link to any relevant issues or discussions\n+- Any additional context that may be helpful for reviewers\n+\n+## Code Review Guidelines\n+\n+When performing a code review, please follow these guidelines:\n+\n+- If there are changes that modify the functionality/usage of the action,\n+ validate that there are changes in the `README.md` file that document the new\n+ or modified functionality" + }, + { + "sha": "742ec57df9e5459bf70f4f13f9c5befae0d8a9da", + "filename": ".github/dependabot.yml", + "status": "modified", + "additions": 21, + "deletions": 13, + "changes": 34, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2Fdependabot.yml", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2Fdependabot.yml", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.github%2Fdependabot.yml?ref=e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "patch": "@@ -1,18 +1,26 @@\n version: 2\n updates:\n- - package-ecosystem: docker\n- directory: '/'\n+ - package-ecosystem: github-actions\n+ directory: /\n schedule:\n- interval: daily\n- open-pull-requests-limit: 10\n+ interval: weekly\n+ groups:\n+ actions-minor:\n+ update-types:\n+ - minor\n+ - patch\n+\n - package-ecosystem: npm\n- directory: '/'\n+ directory: /\n schedule:\n- interval: daily\n- open-pull-requests-limit: 10\n- labels:\n- - 'type: dependencies'\n- - package-ecosystem: 'github-actions'\n- directory: '/'\n- schedule:\n- interval: 'daily'\n+ interval: weekly\n+ groups:\n+ npm-development:\n+ dependency-type: development\n+ update-types:\n+ - minor\n+ - patch\n+ npm-production:\n+ dependency-type: production\n+ update-types:\n+ - patch" + }, + { + "sha": "8ae24104a9d175fbb9c7c1ec6c20d43f3483a521", + "filename": ".github/no-unstaged-files.sh", + "status": "removed", + "additions": 0, + "deletions": 9, + "changes": 9, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/40bb31b945af999c14bc1339eae4c0371d97cac7/.github%2Fno-unstaged-files.sh", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/40bb31b945af999c14bc1339eae4c0371d97cac7/.github%2Fno-unstaged-files.sh", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.github%2Fno-unstaged-files.sh?ref=40bb31b945af999c14bc1339eae4c0371d97cac7", + "patch": "@@ -1,9 +0,0 @@\n-#!/bin/bash\n-\n-git diff\n-\n-if [[ \"$(git status --porcelain)\" != \"\" ]]; then\n- git status\n- echo \"::error::💥 Unstaged changes detected. Locally try running: npm run prettier && npm run lint --fix && npm run build\"\n- exit 1\n-fi" + }, + { + "sha": "a537b0a9a9a7b0371f01d474b7d84d08a293e70d", + "filename": ".github/workflows/action-build.yml", + "status": "removed", + "additions": 0, + "deletions": 42, + "changes": 42, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/40bb31b945af999c14bc1339eae4c0371d97cac7/.github%2Fworkflows%2Faction-build.yml", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/40bb31b945af999c14bc1339eae4c0371d97cac7/.github%2Fworkflows%2Faction-build.yml", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.github%2Fworkflows%2Faction-build.yml?ref=40bb31b945af999c14bc1339eae4c0371d97cac7", + "patch": "@@ -1,42 +0,0 @@\n-name: Dependabot post-update\n-on:\n- pull_request:\n- types: [opened, synchronize, reopened]\n- branches:\n- - 'master'\n-\n-env:\n- HUSKY: '0'\n-\n-permissions:\n- contents: write\n- pull-requests: write\n-\n-concurrency:\n- group: ${{ github.workflow }}-${{ github.event.pull_request.number }}\n- cancel-in-progress: true\n-\n-jobs:\n- post-update:\n- if: github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == 'release-drafter/release-drafter'\n- runs-on: ubuntu-latest\n- steps:\n- - uses: actions/checkout@v6\n- with:\n- ref: ${{ github.event.pull_request.head.ref }}\n- - uses: ./.github/actions/common-setup\n-\n- - run: npm run build\n-\n- - name: Commit and push if needed\n- run: |\n- git add .\n- if [ -z \"$(git status --porcelain)\" ]; then\n- echo \"💾 no changes to dist/index.js\"\n- exit 0\n- fi\n- git config --local user.email \"github-actions[bot]@users.noreply.github.com\"\n- git config --local user.name \"github-actions[bot]\"\n- git commit -m \"Apply dist/index.js changes\"\n- git push\n- echo \"💾 pushed dist/index.js changes\"" + }, + { + "sha": "5fcf1c3077a1bd866f2338708125d4f85500de45", + "filename": ".github/workflows/capture-pr-sync-event.yml", + "status": "added", + "additions": 27, + "deletions": 0, + "changes": 27, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2Fworkflows%2Fcapture-pr-sync-event.yml", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2Fworkflows%2Fcapture-pr-sync-event.yml", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.github%2Fworkflows%2Fcapture-pr-sync-event.yml?ref=e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "patch": "@@ -0,0 +1,27 @@\n+name: Capture PR Synchronize Event\n+\n+on:\n+ pull_request:\n+ types:\n+ - synchronize\n+\n+jobs:\n+ capture-event:\n+ runs-on: ubuntu-latest\n+ steps:\n+ - name: Capture PR synchronize event payload\n+ run: |\n+ mkdir -p event-payload\n+ cat > event-payload/pull_request-synchronize.json << 'EOF'\n+ ${{ toJson(github.event) }}\n+ EOF\n+\n+ - name: Upload event payload as artifact\n+ uses: actions/upload-artifact@v4\n+ with:\n+ name: pr-synchronize-event\n+ path: event-payload/pull_request-synchronize.json\n+ retention-days: 7\n+\n+ - name: Display event payload\n+ run: cat event-payload/pull_request-synchronize.json" + }, + { + "sha": "3321cf3004a1d089a8258b100fb77236a6928e03", + "filename": ".github/workflows/check-dist.yml", + "status": "added", + "additions": 67, + "deletions": 0, + "changes": 67, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2Fworkflows%2Fcheck-dist.yml", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2Fworkflows%2Fcheck-dist.yml", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.github%2Fworkflows%2Fcheck-dist.yml?ref=e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "patch": "@@ -0,0 +1,67 @@\n+# In TypeScript actions, `dist/` is a special directory. When you reference\n+# an action with the `uses:` property, `dist/index.js` is the code that will be\n+# run. For this project, the `dist/index.js` file is transpiled from other\n+# source files. This workflow ensures the `dist/` directory contains the\n+# expected transpiled code.\n+#\n+# If this workflow is run from a feature branch, it will act as an additional CI\n+# check and fail if the checked-in `dist/` directory does not match what is\n+# expected from the build.\n+name: Check Transpiled JavaScript\n+\n+on:\n+ pull_request:\n+ branches:\n+ - master\n+ push:\n+ branches:\n+ - master\n+\n+permissions:\n+ contents: read\n+\n+jobs:\n+ check-dist:\n+ name: Check dist/\n+ runs-on: ubuntu-latest\n+\n+ steps:\n+ - name: Checkout\n+ id: checkout\n+ uses: actions/checkout@v6\n+\n+ - uses: ./.github/actions/common-setup\n+\n+ - name: Remove dist/ Directory\n+ id: remove-dist\n+ run: rm -rf ./dist\n+\n+ - name: Build dist/ Directory\n+ id: build\n+ run: npm run build\n+\n+ # This will fail the workflow if the `dist/` directory is different than\n+ # expected.\n+ - name: Compare Directories\n+ id: diff\n+ run: |\n+ if [ ! -d dist/ ]; then\n+ echo \"::error::💥 Expected dist/ directory does not exist. See status below:\"\n+ ls -la ./\n+ exit 1\n+ fi\n+ if [ \"$(git diff --ignore-space-at-eol --text dist/ | wc -l)\" -gt \"0\" ]; then\n+ echo \"::error::💥 Detected uncommitted changes after build. See status below:\"\n+ git diff --ignore-space-at-eol --text dist/\n+ exit 1\n+ fi\n+\n+ # If `dist/` was different than expected, upload the expected version as a\n+ # workflow artifact.\n+ - if: ${{ failure() && steps.diff.outcome == 'failure' }}\n+ name: Upload Artifact\n+ id: upload\n+ uses: actions/upload-artifact@v5\n+ with:\n+ name: dist\n+ path: dist/" + }, + { + "sha": "cc16ff321a235f085cc1bc119809d997960d4824", + "filename": ".github/workflows/ci.yml", + "status": "added", + "additions": 40, + "deletions": 0, + "changes": 40, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2Fworkflows%2Fci.yml", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2Fworkflows%2Fci.yml", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.github%2Fworkflows%2Fci.yml?ref=e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "patch": "@@ -0,0 +1,40 @@\n+name: Continuous Integration\n+\n+on:\n+ pull_request:\n+ branches:\n+ - master\n+ push:\n+ branches:\n+ - master\n+\n+permissions:\n+ contents: read\n+\n+jobs:\n+ test-typescript:\n+ name: TypeScript Tests\n+ runs-on: ubuntu-latest\n+\n+ steps:\n+ - name: Checkout\n+ id: checkout\n+ uses: actions/checkout@v6\n+\n+ - uses: ./.github/actions/common-setup\n+\n+ - name: Check Format\n+ id: npm-format-check\n+ run: npm run format:check\n+\n+ - name: Lint\n+ id: npm-lint\n+ run: npm run lint\n+\n+ - name: Type checks\n+ id: npm-ts-checks\n+ run: npm run tsc:check\n+\n+ - name: Tests\n+ id: npm-ci-test\n+ run: npm run test:run" + }, + { + "sha": "c1ab3f3de7b0a4d99eb7fd521815ed0f0f4fc235", + "filename": ".github/workflows/codeql-analysis.yml", + "status": "added", + "additions": 47, + "deletions": 0, + "changes": 47, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2Fworkflows%2Fcodeql-analysis.yml", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2Fworkflows%2Fcodeql-analysis.yml", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.github%2Fworkflows%2Fcodeql-analysis.yml?ref=e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "patch": "@@ -0,0 +1,47 @@\n+name: CodeQL\n+\n+on:\n+ pull_request:\n+ branches:\n+ - master\n+ push:\n+ branches:\n+ - master\n+\n+permissions:\n+ actions: read\n+ checks: write\n+ contents: read\n+ security-events: write\n+\n+jobs:\n+ analyze:\n+ name: Analyze\n+ runs-on: ubuntu-latest\n+\n+ strategy:\n+ fail-fast: false\n+ matrix:\n+ language:\n+ - typescript\n+\n+ steps:\n+ - name: Checkout\n+ id: checkout\n+ uses: actions/checkout@v6\n+\n+ - name: Initialize CodeQL\n+ id: initialize\n+ uses: github/codeql-action/init@v4\n+ with:\n+ config-file: .github/.codeql-config.yml\n+ languages: ${{ matrix.language }}\n+ source-root: src\n+\n+ - name: Autobuild\n+ id: autobuild\n+ uses: github/codeql-action/autobuild@v4\n+\n+ - name: Perform CodeQL Analysis\n+ id: analyze\n+ uses: github/codeql-action/analyze@v4" + }, + { + "sha": "d6149c256a48b3079f1325719703111c4dd20f96", + "filename": ".github/workflows/licensed.yml", + "status": "added", + "additions": 65, + "deletions": 0, + "changes": 65, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2Fworkflows%2Flicensed.yml", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2Fworkflows%2Flicensed.yml", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.github%2Fworkflows%2Flicensed.yml?ref=e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "patch": "@@ -0,0 +1,65 @@\n+# This workflow checks the statuses of cached dependencies used in this action\n+# with the help of the Licensed tool. If any licenses are invalid or missing,\n+# this workflow will fail. See: https://github.com/licensee/licensed\n+\n+name: Licensed\n+\n+on:\n+ # Uncomment the below lines to run this workflow on pull requests and pushes\n+ # to the default branch. This is useful for checking licenses before merging\n+ # changes into the default branch.\n+ # pull_request:\n+ # branches:\n+ # - main\n+ # push:\n+ # branches:\n+ # - main\n+ workflow_dispatch:\n+\n+permissions:\n+ contents: write\n+\n+jobs:\n+ licensed:\n+ name: Check Licenses\n+ runs-on: ubuntu-latest\n+\n+ steps:\n+ - name: Checkout\n+ id: checkout\n+ uses: actions/checkout@v6\n+\n+ - uses: ./.github/actions/common-setup\n+\n+ - name: Setup Ruby\n+ id: setup-ruby\n+ uses: ruby/setup-ruby@v1\n+ with:\n+ ruby-version: ruby\n+\n+ - uses: licensee/setup-licensed@v1.3.2\n+ with:\n+ version: 4.x\n+ github_token: ${{ secrets.GITHUB_TOKEN }}\n+\n+ # If this is a workflow_dispatch event, update the cached licenses.\n+ - if: ${{ github.event_name == 'workflow_dispatch' }}\n+ name: Update Licenses\n+ id: update-licenses\n+ run: licensed cache -c ./.github/.licensed.yml\n+\n+ # Then, commit the updated licenses to the repository.\n+ - if: ${{ github.event_name == 'workflow_dispatch' }}\n+ name: Commit Licenses\n+ id: commit-licenses\n+ run: |\n+ git config --local user.email \"licensed-ci@users.noreply.github.com\"\n+ git config --local user.name \"licensed-ci\"\n+ git add .\n+ git commit -m \"Auto-update license files\"\n+ git push\n+\n+ # Last, check the status of the cached licenses.\n+ - name: Check Licenses\n+ id: check-licenses\n+ run: licensed status -c ./.github/.licensed.yml" + }, + { + "sha": "9501304333a496e4da0d3c312f9dd2e686636d6c", + "filename": ".github/workflows/release.yml", + "status": "modified", + "additions": 1, + "deletions": 3, + "changes": 4, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2Fworkflows%2Frelease.yml", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.github%2Fworkflows%2Frelease.yml", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.github%2Fworkflows%2Frelease.yml?ref=e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "patch": "@@ -9,9 +9,6 @@ permissions:\n id-token: write\n contents: write\n \n-env:\n- HUSKY: '0'\n-\n jobs:\n build:\n if: github.repository == 'release-drafter/release-drafter'\n@@ -20,6 +17,7 @@ jobs:\n - uses: actions/checkout@v6\n with:\n fetch-depth: 0\n+\n - uses: ./.github/actions/common-setup\n \n - name: test" + }, + { + "sha": "5070abb80a54c67ff7c8812a1d60f20266ba687a", + "filename": ".github/workflows/tests.yml", + "status": "removed", + "additions": 0, + "deletions": 30, + "changes": 30, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/40bb31b945af999c14bc1339eae4c0371d97cac7/.github%2Fworkflows%2Ftests.yml", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/40bb31b945af999c14bc1339eae4c0371d97cac7/.github%2Fworkflows%2Ftests.yml", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.github%2Fworkflows%2Ftests.yml?ref=40bb31b945af999c14bc1339eae4c0371d97cac7", + "patch": "@@ -1,30 +0,0 @@\n-name: Tests\n-\n-on:\n- push:\n- branches:\n- - master\n- pull_request:\n-\n-permissions:\n- id-token: write\n- contents: write\n-\n-env:\n- HUSKY: '0'\n-\n-jobs:\n- build:\n- runs-on: ubuntu-latest\n- steps:\n- - uses: actions/checkout@v6\n- - uses: ./.github/actions/common-setup\n- - run: npm run test\n- - run: npm run lint --fix\n- - run: npm run prettier\n- - run: npm run build\n- - name: correct vercel/ncc crlf output\n- run: sed -i 's/\\x0D$//' ./dist/index.js\n- - name: check for unstaged files\n- if: ${{ github.actor != 'dependabot[bot]' }}\n- run: .github/no-unstaged-files.sh" + }, + { + "sha": "80a899159813dc46afdb78d0e90506fbeeaa2983", + "filename": ".gitignore", + "status": "modified", + "additions": 98, + "deletions": 26, + "changes": 124, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.gitignore", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.gitignore", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.gitignore?ref=e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "patch": "@@ -1,30 +1,102 @@\n+# Dependency directory\n node_modules\n-npm-debug.log\n-*.pem\n-.env\n+\n+# Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore\n+# Logs\n+logs\n+*.log\n+npm-debug.log*\n+yarn-debug.log*\n+yarn-error.log*\n+lerna-debug.log*\n+\n+# Diagnostic reports (https://nodejs.org/api/report.html)\n+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n+\n+# Runtime data\n+pids\n+*.pid\n+*.seed\n+*.pid.lock\n+\n+# Directory for instrumented libs generated by jscoverage/JSCover\n+lib-cov\n+\n+# Coverage directory used by tools like istanbul\n coverage\n-now.json\n-docker-compose-logs\n-!package-lock.json\n-yarn.lock\n-dist/static/\n-dist/views/\n-\n-# OS\n+*.lcov\n+\n+# nyc test coverage\n+.nyc_output\n+\n+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n+.grunt\n+\n+# Bower dependency directory (https://bower.io/)\n+bower_components\n+\n+# node-waf configuration\n+.lock-wscript\n+\n+# Compiled binary addons (https://nodejs.org/api/addons.html)\n+build/Release\n+\n+# Dependency directories\n+jspm_packages/\n+\n+# TypeScript v1 declaration files\n+typings/\n+\n+# TypeScript cache\n+*.tsbuildinfo\n+\n+# Optional npm cache directory\n+.npm\n+\n+# Optional eslint cache\n+.eslintcache\n+\n+# Optional REPL history\n+.node_repl_history\n+\n+# Output of 'npm pack'\n+*.tgz\n+\n+# Yarn Integrity file\n+.yarn-integrity\n+\n+# dotenv environment variables file\n+.env\n+.env.test\n+\n+# parcel-bundler cache (https://parceljs.org/)\n+.cache\n+\n+# next.js build output\n+.next\n+\n+# nuxt.js build output\n+.nuxt\n+\n+# vuepress build output\n+.vuepress/dist\n+\n+# Serverless directories\n+.serverless/\n+\n+# FuseBox cache\n+.fusebox/\n+\n+# DynamoDB Local files\n+.dynamodb/\n+\n+# OS metadata\n .DS_Store\n+Thumbs.db\n+\n+# Ignore built ts files\n+__tests__/runner/*\n \n-# IDE - VSCode\n-.vscode/*\n-!.vscode/settings.json\n-!.vscode/tasks.json\n-!.vscode/launch.json\n-!.vscode/extensions.json\n-\n-# IDEs and editors\n-/.idea\n-.project\n-.classpath\n-.c9/\n-*.launch\n-.settings/\n-*.sublime-workspace\n+# IDE files\n+.idea\n+*.code-workspace" + }, + { + "sha": "19019aa413056948ef84a2267434c56fe20f68fc", + "filename": ".husky/pre-commit", + "status": "removed", + "additions": 0, + "deletions": 4, + "changes": 4, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/40bb31b945af999c14bc1339eae4c0371d97cac7/.husky%2Fpre-commit", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/40bb31b945af999c14bc1339eae4c0371d97cac7/.husky%2Fpre-commit", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.husky%2Fpre-commit?ref=40bb31b945af999c14bc1339eae4c0371d97cac7", + "patch": "@@ -1,4 +0,0 @@\n-#!/bin/sh\n-. \"$(dirname \"$0\")/_/husky.sh\"\n-\n-[ ! -x \"$(command -v npm)\" ] || npx lint-staged" + }, + { + "sha": "09e099f71078ec77f194a04fa7006553d3ce1b14", + "filename": ".licenses/npm/@actions/core.dep.yml", + "status": "added", + "additions": 20, + "deletions": 0, + "changes": 20, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.licenses%2Fnpm%2F%40actions%2Fcore.dep.yml", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.licenses%2Fnpm%2F%40actions%2Fcore.dep.yml", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.licenses%2Fnpm%2F%40actions%2Fcore.dep.yml?ref=e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "patch": "@@ -0,0 +1,20 @@\n+---\n+name: \"@actions/core\"\n+version: 1.11.1\n+type: npm\n+summary: Actions core lib\n+homepage: https://github.com/actions/toolkit/tree/main/packages/core\n+license: mit\n+licenses:\n+- sources: LICENSE.md\n+ text: |-\n+ The MIT License (MIT)\n+\n+ Copyright 2019 GitHub\n+\n+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n+\n+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n+\n+ THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n+notices: []" + }, + { + "sha": "cbc5abd39e6391ec6e526e6536f1a3efd706ec05", + "filename": ".licenses/npm/@actions/exec.dep.yml", + "status": "added", + "additions": 20, + "deletions": 0, + "changes": 20, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.licenses%2Fnpm%2F%40actions%2Fexec.dep.yml", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.licenses%2Fnpm%2F%40actions%2Fexec.dep.yml", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.licenses%2Fnpm%2F%40actions%2Fexec.dep.yml?ref=e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "patch": "@@ -0,0 +1,20 @@\n+---\n+name: \"@actions/exec\"\n+version: 1.1.1\n+type: npm\n+summary: Actions exec lib\n+homepage: https://github.com/actions/toolkit/tree/main/packages/exec\n+license: mit\n+licenses:\n+- sources: LICENSE.md\n+ text: |-\n+ The MIT License (MIT)\n+\n+ Copyright 2019 GitHub\n+\n+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n+\n+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n+\n+ THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n+notices: []" + }, + { + "sha": "1ae400a62c500f4eb0766ec7dd39626ff43bb344", + "filename": ".licenses/npm/@actions/http-client.dep.yml", + "status": "added", + "additions": 32, + "deletions": 0, + "changes": 32, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.licenses%2Fnpm%2F%40actions%2Fhttp-client.dep.yml", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.licenses%2Fnpm%2F%40actions%2Fhttp-client.dep.yml", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.licenses%2Fnpm%2F%40actions%2Fhttp-client.dep.yml?ref=e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "patch": "@@ -0,0 +1,32 @@\n+---\n+name: \"@actions/http-client\"\n+version: 2.2.3\n+type: npm\n+summary: Actions Http Client\n+homepage: https://github.com/actions/toolkit/tree/main/packages/http-client\n+license: other\n+licenses:\n+- sources: LICENSE\n+ text: |\n+ Actions Http Client for Node.js\n+\n+ Copyright (c) GitHub, Inc.\n+\n+ All rights reserved.\n+\n+ MIT License\n+\n+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and\n+ associated documentation files (the \"Software\"), to deal in the Software without restriction,\n+ including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,\n+ and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,\n+ subject to the following conditions:\n+\n+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n+\n+ THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT\n+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n+ NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n+notices: []" + }, + { + "sha": "d28465403a243506f508f965c1c0641dfd5e0673", + "filename": ".licenses/npm/@actions/io.dep.yml", + "status": "added", + "additions": 20, + "deletions": 0, + "changes": 20, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.licenses%2Fnpm%2F%40actions%2Fio.dep.yml", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.licenses%2Fnpm%2F%40actions%2Fio.dep.yml", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.licenses%2Fnpm%2F%40actions%2Fio.dep.yml?ref=e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "patch": "@@ -0,0 +1,20 @@\n+---\n+name: \"@actions/io\"\n+version: 1.1.3\n+type: npm\n+summary: Actions io lib\n+homepage: https://github.com/actions/toolkit/tree/main/packages/io\n+license: mit\n+licenses:\n+- sources: LICENSE.md\n+ text: |-\n+ The MIT License (MIT)\n+\n+ Copyright 2019 GitHub\n+\n+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n+\n+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n+\n+ THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n+notices: []" + }, + { + "sha": "817d644a05ca715155fb1c76ec28814252f22b15", + "filename": ".licenses/npm/@fastify/busboy.dep.yml", + "status": "added", + "additions": 30, + "deletions": 0, + "changes": 30, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.licenses%2Fnpm%2F%40fastify%2Fbusboy.dep.yml", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.licenses%2Fnpm%2F%40fastify%2Fbusboy.dep.yml", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.licenses%2Fnpm%2F%40fastify%2Fbusboy.dep.yml?ref=e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "patch": "@@ -0,0 +1,30 @@\n+---\n+name: \"@fastify/busboy\"\n+version: 2.1.1\n+type: npm\n+summary: A streaming parser for HTML form data for node.js\n+homepage:\n+license: mit\n+licenses:\n+- sources: LICENSE\n+ text: |-\n+ Copyright Brian White. All rights reserved.\n+\n+ Permission is hereby granted, free of charge, to any person obtaining a copy\n+ of this software and associated documentation files (the \"Software\"), to\n+ deal in the Software without restriction, including without limitation the\n+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n+ sell copies of the Software, and to permit persons to whom the Software is\n+ furnished to do so, subject to the following conditions:\n+\n+ The above copyright notice and this permission notice shall be included in\n+ all copies or substantial portions of the Software.\n+\n+ THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n+ IN THE SOFTWARE.\n+notices: []" + }, + { + "sha": "9a7111da96a36c81dd1160453ab32caa08c8707f", + "filename": ".licenses/npm/tunnel.dep.yml", + "status": "added", + "additions": 35, + "deletions": 0, + "changes": 35, + "blob_url": "https://github.com/release-drafter/release-drafter/blob/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.licenses%2Fnpm%2Ftunnel.dep.yml", + "raw_url": "https://github.com/release-drafter/release-drafter/raw/e3c3ac3e28b6b32e849895782416a8fc9df47a11/.licenses%2Fnpm%2Ftunnel.dep.yml", + "contents_url": "https://api.github.com/repos/release-drafter/release-drafter/contents/.licenses%2Fnpm%2Ftunnel.dep.yml?ref=e3c3ac3e28b6b32e849895782416a8fc9df47a11", + "patch": "@@ -0,0 +1,35 @@\n+---\n+name: tunnel\n+version: 0.0.6\n+type: npm\n+summary: Node HTTP/HTTPS Agents for tunneling proxies\n+homepage: https://github.com/koichik/node-tunnel/\n+license: mit\n+licenses:\n+- sources: LICENSE\n+ text: |\n+ The MIT License (MIT)\n+\n+ Copyright (c) 2012 Koichi Kobayashi\n+\n+ Permission is hereby granted, free of charge, to any person obtaining a copy\n+ of this software and associated documentation files (the \"Software\"), to deal\n+ in the Software without restriction, including without limitation the rights\n+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n+ copies of the Software, and to permit persons to whom the Software is\n+ furnished to do so, subject to the following conditions:\n+\n+ The above copyright notice and this permission notice shall be included in\n+ all copies or substantial portions of the Software.\n+\n+ THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n+ THE SOFTWARE.\n+- sources: README.md\n+ text: Licensed under the [MIT](https://github.com/koichik/node-tunnel/blob/master/LICENSE)\n+ license.\n+notices: []" + } +] diff --git a/src/tests/fixtures/releases/pre-release.json b/src/tests/fixtures/releases/pre-release.json new file mode 100644 index 0000000000..9f4e329e41 --- /dev/null +++ b/src/tests/fixtures/releases/pre-release.json @@ -0,0 +1,39 @@ +{ + "url": "https://api.github.com/repos/toolmantim/release-drafter-test-project/releases/11691725", + "assets_url": "https://api.github.com/repos/toolmantim/release-drafter-test-project/releases/11691725/assets", + "upload_url": "https://uploads.github.com/repos/toolmantim/release-drafter-test-project/releases/11691725/assets{?name,label}", + "html_url": "https://github.com/toolmantim/release-drafter-test-project/releases/tag/v1.5.0-alpha", + "id": 11691725, + "node_id": "MDc6UmVsZWFzZTExNjkxNzI2", + "tag_name": "v1.5.0-alpha", + "target_commitish": "master", + "name": "v1.5.0-alpha", + "draft": false, + "author": { + "login": "release-drafter[bot]", + "id": 40600115, + "node_id": "MDM6Qm90NDA2MDAxMTU=", + "avatar_url": "https://avatars2.githubusercontent.com/in/14050?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/release-drafter%5Bbot%5D", + "html_url": "https://github.com/apps/release-drafter", + "followers_url": "https://api.github.com/users/release-drafter%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/release-drafter%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/release-drafter%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/release-drafter%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/release-drafter%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/release-drafter%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/release-drafter%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/release-drafter%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/release-drafter%5Bbot%5D/received_events", + "type": "Bot", + "site_admin": false + }, + "prerelease": true, + "created_at": "2018-06-28T05:45:15Z", + "published_at": "2018-06-28T05:47:08Z", + "assets": [], + "tarball_url": "https://api.github.com/repos/toolmantim/release-drafter-test-project/tarball/v1.5.0-alpha", + "zipball_url": "https://api.github.com/repos/toolmantim/release-drafter-test-project/zipball/v1.5.0-alpha", + "body": "A pre release" +} diff --git a/test/fixtures/release-2.json b/src/tests/fixtures/releases/release-2.json similarity index 100% rename from test/fixtures/release-2.json rename to src/tests/fixtures/releases/release-2.json diff --git a/test/fixtures/release-3.json b/src/tests/fixtures/releases/release-3.json similarity index 100% rename from test/fixtures/release-3.json rename to src/tests/fixtures/releases/release-3.json diff --git a/test/fixtures/release-draft.json b/src/tests/fixtures/releases/release-draft.json similarity index 100% rename from test/fixtures/release-draft.json rename to src/tests/fixtures/releases/release-draft.json diff --git a/test/fixtures/release-shared-commit-date.json b/src/tests/fixtures/releases/release-shared-commit-date.json similarity index 100% rename from test/fixtures/release-shared-commit-date.json rename to src/tests/fixtures/releases/release-shared-commit-date.json diff --git a/test/fixtures/release.json b/src/tests/fixtures/releases/release.json similarity index 100% rename from test/fixtures/release.json rename to src/tests/fixtures/releases/release.json diff --git a/src/tests/get-octokit-proxy.test.ts b/src/tests/get-octokit-proxy.test.ts new file mode 100644 index 0000000000..0da2b35645 --- /dev/null +++ b/src/tests/get-octokit-proxy.test.ts @@ -0,0 +1,41 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { getOctokit } from '#src/common/get-octokit.ts' + +// The suite-wide setup swaps in Node's global fetch so nock can intercept. +// These tests assert the real production wiring, so opt out of that. +vi.unmock('@actions/github') + +/** + * `@octokit/core` shallow-merges the `request` option, so passing it at all + * replaces the proxy-aware `fetch` and agent that `@actions/github` installs + * from `http_proxy`/`https_proxy`. That silently breaks GitHub Enterprise + * Server and corporate proxies, so pin the production configuration here. + */ +const requestDefaults = (octokit: ReturnType) => + ( + octokit.request as unknown as { + endpoint: { + DEFAULTS: { request: { fetch?: unknown; agent?: unknown } } + } + } + ).endpoint.DEFAULTS.request + +describe('getOctokit proxy support', () => { + beforeEach(() => { + vi.stubEnv('GITHUB_TOKEN', 'test') + }) + + it("keeps @actions/github's proxy-aware fetch rather than replacing it", () => { + const { fetch } = requestDefaults(getOctokit()) + + expect(fetch).toBeTypeOf('function') + // The proxy-aware wrapper, not a bare fetch handed straight through. + expect(fetch).not.toBe(globalThis.fetch) + }) + + it('keeps the proxy agent when a proxy is configured', () => { + vi.stubEnv('https_proxy', 'http://proxy.invalid:8080') + + expect(requestDefaults(getOctokit()).agent).toBeDefined() + }) +}) diff --git a/src/tests/get-octokit.test.ts b/src/tests/get-octokit.test.ts new file mode 100644 index 0000000000..bc6a2c7364 --- /dev/null +++ b/src/tests/get-octokit.test.ts @@ -0,0 +1,40 @@ +import nock from 'nock' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { getOctokit } from '#src/common/get-octokit.ts' + +describe('getOctokit', () => { + beforeEach(() => { + vi.stubEnv('GITHUB_TOKEN', 'test') + }) + + it('retries a transient server failure', async () => { + const scope = nock('https://api.github.com') + .get('/repos/release-drafter/release-drafter') + .reply(500, { message: 'Server Error' }) + .get('/repos/release-drafter/release-drafter') + .reply(200, { id: 1 }) + + const response = await getOctokit().request('GET /repos/{owner}/{repo}', { + owner: 'release-drafter', + repo: 'release-drafter', + }) + + expect(response.status).toBe(200) + expect(scope.isDone()).toBe(true) + }) + + it('does not retry an exempt client failure', async () => { + const scope = nock('https://api.github.com') + .get('/repos/release-drafter/missing') + .once() + .reply(404, { message: 'Not Found' }) + + await expect( + getOctokit().request('GET /repos/{owner}/{repo}', { + owner: 'release-drafter', + repo: 'missing', + }), + ).rejects.toMatchObject({ status: 404 }) + expect(scope.isDone()).toBe(true) + }) +}) diff --git a/src/tests/helpers/index.ts b/src/tests/helpers/index.ts new file mode 100644 index 0000000000..84ee5c5ce9 --- /dev/null +++ b/src/tests/helpers/index.ts @@ -0,0 +1 @@ +export { runAutolabeler, runDrafter } from './runners.ts' diff --git a/src/tests/helpers/runners.ts b/src/tests/helpers/runners.ts new file mode 100644 index 0000000000..cd5e5ed5c0 --- /dev/null +++ b/src/tests/helpers/runners.ts @@ -0,0 +1,7 @@ +import type { run as actionRun } from '#src/actions/drafter/runner.ts' + +export const runDrafter = async (...args: Parameters) => + await (await import(`#src/actions/drafter/runner.ts`)).run(...args) + +export const runAutolabeler = async (...args: Parameters) => + await (await import(`#src/actions/autolabeler/runner.ts`)).run(...args) diff --git a/src/tests/mocks/config.ts b/src/tests/mocks/config.ts new file mode 100644 index 0000000000..b4d2d7920c --- /dev/null +++ b/src/tests/mocks/config.ts @@ -0,0 +1,102 @@ +import { readFileSync } from 'node:fs' +import path from 'node:path' +import { parse } from 'yaml' +import type { composeConfigGet } from '#src/common/config/index.ts' +import { mocks } from './hoisted.ts' + +export type AvailableConfigs = + | 'config-autolabeler' + | 'config-name-input' + | 'config-non-master-branch' + | 'config-previous-tag' + | 'config-with-categories-2' + | 'config-with-categories-3' + | 'config-with-categories-4' + | 'config-with-categories-with-collapse-after' + | 'config-with-categories-with-other-category' + | 'config-with-category-pre-include-paths' + | 'config-with-category-version-resolver-major' + | 'config-with-categories' + | 'config-with-changes-templates-and-body' + | 'config-with-changes-templates-and-url' + | 'config-with-changes-templates' + | 'config-with-commitish' + | 'config-with-compare-link' + | 'config-with-component-helpers-custom' + | 'config-with-component-helpers-default' + | 'config-with-component-helpers-major' + | 'config-with-component-helpers-major-minor' + | 'config-with-component-helpers-prerelease' + | 'config-with-component-helpers-prerelease-identifier' + | 'config-with-contributors' + | 'config-with-custom-version-resolver-major' + | 'config-with-custom-version-resolver-minor' + | 'config-with-custom-version-resolver-none' + | 'config-with-custom-version-resolver-partial' + | 'config-with-custom-version-resolver-patch' + | 'config-with-exclude-contributors' + | 'config-with-exclude-labels' + | 'config-with-exclude-paths' + | 'config-with-footer-template' + | 'config-with-header-and-footer-no-nl-no-space-template' + | 'config-with-header-and-footer-template' + | 'config-with-header-template' + | 'config-with-history-limit' + | 'config-with-include-labels' + | 'config-with-include-paths' + | 'config-with-include-exclude-paths' + | 'config-with-include-pre-releases-true' + | 'config-with-include-pre-releases-false' + | 'config-with-input-version-template' + | 'config-with-major-minor-patch-version-template' + | 'config-with-major-minor-version-template' + | 'config-with-major-version-template' + | 'config-with-name-and-tag-template' + | 'config-with-next-versioning' + | 'config-without-prerelease' + | 'config-with-pre-release-identifier' + | 'config-with-prerelease' + | 'config-with-pull-request-limit' + | 'config-with-replacers' + | 'config-with-resolved-version-template' + | 'config-with-schema-error' + | 'config-with-smoke-test-categories-legacy' + | 'config-with-smoke-test-categories-migrated' + | 'config-with-sort-by-title' + | 'config-with-sort-direction-ascending' + | 'config-with-tag-prefix' + | 'config-with-yaml-exception' + | 'config' + | 'config-filter-range' + +export const mockedConfigModule = async ( + iom: () => Promise<{ composeConfigGet: typeof composeConfigGet }>, +) => { + const om = await iom() + + const mockedComposeConfigGet: typeof composeConfigGet = async () => { + const mockedConfig = mocks.config() + if (mockedConfig) { + const p = path.resolve( + import.meta.dirname, + '../fixtures', + 'config', + `${mockedConfig}.yml`, + ) + return { + config: parse(readFileSync(p, 'utf-8')), + contexts: mocks.getContextsConfigWasFetchedFrom(), + } + } else { + // will throw inside test-suites + throw new Error( + "composeConfigGet was called without an associated mocked config. Please use mocks.config.mockReturnValue('config')", + ) + } + } + + return { + ...om, + composeConfigGet: mockedComposeConfigGet, + } +} diff --git a/src/tests/mocks/context.ts b/src/tests/mocks/context.ts new file mode 100644 index 0000000000..d601a5b732 --- /dev/null +++ b/src/tests/mocks/context.ts @@ -0,0 +1,169 @@ +import { existsSync, readFileSync } from 'node:fs' +import path from 'node:path' +import * as github from '@actions/github' +import { expect, vi } from 'vitest' +import type { GithubActionEnvironment } from '#src/types/index.ts' + +type WebhookPayload = typeof github.context.payload + +type AllowedPayload = + | 'push' + | 'push-non-master-branch' + | 'push-tag' + | 'pull_request-synchronize' + +const getEventPayloadPath = (type: AllowedPayload) => { + const baseDir = path.join( + path.dirname(import.meta.filename), + '../fixtures', + 'events', + ) + return path.join(baseDir, `${type}.json`) +} + +/** + * Mocking GitHub Action environment variables for testing. + * + * The defined environments variabesl will determine the behavior of `@actions/github` + * when it is imported, so this function should be called before importing + * modules that depend on it. + */ +export const mockContext = async (desiredPayload: AllowedPayload) => { + let payload: WebhookPayload + const defaultEnv: GithubActionEnvironment = { + CI: 'true', + GITHUB_ACTION: 'release-drafter', + GITHUB_ACTION_PATH: '', + GITHUB_ACTION_REPOSITORY: 'toolmantim/release-drafter-test-project', + GITHUB_ACTIONS: 'false', + GITHUB_ACTOR: 'toolmantim', + GITHUB_ACTOR_ID: '153', + GITHUB_API_URL: 'https://api.github.com', + GITHUB_BASE_REF: 'master', + GITHUB_ENV: '', + GITHUB_EVENT_NAME: 'push', + GITHUB_EVENT_PATH: '__fixtures__/events/push.json', + GITHUB_GRAPHQL_URL: 'https://api.github.com/graphql', + GITHUB_HEAD_REF: '', + GITHUB_JOB: 'release_drafter_job', + GITHUB_OUTPUT: '', + GITHUB_PATH: '', + GITHUB_REF: 'refs/heads/master', + GITHUB_REF_NAME: 'master', + GITHUB_REF_PROTECTED: 'true', + GITHUB_REF_TYPE: 'branch', + GITHUB_REPOSITORY: 'toolmantim/release-drafter-test-project', + GITHUB_REPOSITORY_ID: '133810100', + GITHUB_REPOSITORY_OWNER: 'toolmantim', + GITHUB_REPOSITORY_OWNER_ID: '153', + GITHUB_RETENTION_DAYS: '', + GITHUB_RUN_ATTEMPT: '1', + GITHUB_RUN_ID: '1658821493', + GITHUB_RUN_NUMBER: '1', + GITHUB_SERVER_URL: 'https://github.com', + GITHUB_SHA: '1496a1f82f32f240f7cbe1a42eb0b0c7a06a5093', + GITHUB_STEP_SUMMARY: '', + GITHUB_TRIGGERING_ACTOR: 'toolmantim', + GITHUB_WORKFLOW: 'Release Drafter Workflow', + GITHUB_WORKFLOW_REF: + 'toolmantim/release-drafter-test-project/.github/workflows/my-workflow.yml@refs/heads/my_branch', + GITHUB_WORKFLOW_SHA: 'aa80724a5d394e0cadaeb0488c488fe21922d6ff', + GITHUB_WORKSPACE: '.', + RUNNER_ARCH: '', + RUNNER_DEBUG: '1', + RUNNER_NAME: '', + RUNNER_OS: 'Linux', + RUNNER_TEMP: '', + RUNNER_TOOL_CACHE: '', + ACTIONS_STEP_DEBUG: 'true', + INPUT_MILLISECONDS: '2400', + } + + const pathToPayload = getEventPayloadPath(desiredPayload) + + if (existsSync(pathToPayload)) { + payload = JSON.parse(readFileSync(pathToPayload, { encoding: 'utf8' })) + } else { + throw new Error('Event payload path does not exist') + } + + /** + * Some environment variables are related to the payload's content. + */ + const envFromPayload: Partial = { + GITHUB_ACTION_REPOSITORY: payload.repository?.full_name, + GITHUB_ACTOR: payload.pusher?.login, + GITHUB_ACTOR_ID: payload.sender?.id?.toString(), + GITHUB_BASE_REF: desiredPayload.startsWith('pull_request') + ? payload.pull_request?.base?.ref + : undefined, + GITHUB_EVENT_NAME: desiredPayload.startsWith('pull_request') + ? 'pull_request' + : 'push', + GITHUB_EVENT_PATH: pathToPayload, + GITHUB_REF: payload.ref, + GITHUB_REF_NAME: desiredPayload.startsWith('pull_request') + ? `refs/pull/${payload.number}/merge` + : payload.ref.replace(/^refs\/heads\//, ''), + GITHUB_REF_TYPE: desiredPayload === 'push-tag' ? 'tag' : 'branch', + GITHUB_REPOSITORY: payload.repository?.full_name, + GITHUB_REPOSITORY_ID: payload.repository?.id?.toString(), + GITHUB_REPOSITORY_OWNER: payload.repository?.owner?.login, + GITHUB_REPOSITORY_OWNER_ID: payload.repository?.owner?.id?.toString(), + GITHUB_SHA: payload.after, + GITHUB_TRIGGERING_ACTOR: payload.pusher?.login, + GITHUB_WORKSPACE: path.resolve( + path.dirname(import.meta.filename), + '../../..', + ), + } + + Object.entries({ + ...defaultEnv, + ...envFromPayload, + }) + .filter(([key, value]) => value !== undefined && value !== null && !!key) + .forEach(([key, value]) => { + vi.stubEnv(key, value) + }) + + // Inline Context shape (mirrors @actions/github/lib/context constructor) + // to avoid importing the unexported internal Context class. Field + // semantics — including undefined when env vars are unset — must match + // the upstream constructor exactly so tests see the same behavior. + const newContext = { + payload, + eventName: process.env.GITHUB_EVENT_NAME, + sha: process.env.GITHUB_SHA, + ref: process.env.GITHUB_REF, + workflow: process.env.GITHUB_WORKFLOW, + action: process.env.GITHUB_ACTION, + actor: process.env.GITHUB_ACTOR, + job: process.env.GITHUB_JOB, + runAttempt: parseInt(process.env.GITHUB_RUN_ATTEMPT as string, 10), + runNumber: parseInt(process.env.GITHUB_RUN_NUMBER as string, 10), + runId: parseInt(process.env.GITHUB_RUN_ID as string, 10), + apiUrl: process.env.GITHUB_API_URL ?? 'https://api.github.com', + serverUrl: process.env.GITHUB_SERVER_URL ?? 'https://github.com', + graphqlUrl: + process.env.GITHUB_GRAPHQL_URL ?? 'https://api.github.com/graphql', + } as unknown as typeof github.context + + // Hack to change github.context despite being read-only - @actions/github has side effects on import that need to be re-initialized + Object.keys(github.context).forEach((contextKey) => { + const k = contextKey as keyof typeof github.context + const newValue = newContext[k] + + if (k in github.context) { + // @ts-expect-error - please let me do my thing typescript thank you + github.context[k] = newValue + } else { + delete github.context[k] + } + }) + + // Verify the context has been set up correctly + const dynamicContext = (await import('@actions/github')).context + expect(dynamicContext.ref).toEqual(process.env.GITHUB_REF) + expect(dynamicContext.payload).toEqual(payload) +} diff --git a/src/tests/mocks/graphql.ts b/src/tests/mocks/graphql.ts new file mode 100644 index 0000000000..68f56a0a36 --- /dev/null +++ b/src/tests/mocks/graphql.ts @@ -0,0 +1,79 @@ +import { readFileSync } from 'node:fs' +import path from 'node:path' +import nock from 'nock' + +type Query = + | 'query findCommitsInComparison' + | 'query findRecentMergedPullRequests' + +/** + * Available files in fixtures/graphql + */ +type Payload = + | 'graphql-include-path-src-5.md-forking' + | 'graphql-include-path-src-5.md-rebase-merging' + | 'graphql-include-null-path-overlapping-label' + | 'graphql-include-null-path-forking' + | 'graphql-include-null-path-rebase-merging' + | 'graphql-include-null-path-squash-merging' + | 'graphql-include-path-src-5.md-overlapping-label' + | 'graphql-include-path-src-5.md-squash-merging' + | 'graphql-comparison-merge-commit' + | 'graphql-comparison-no-prs' + | 'graphql-comparison-empty' + | 'graphql-comparison-overlapping-label' + | 'graphql-comparison-forking' + | 'graphql-comparison-rebase-merging' + | 'graphql-comparison-squash-merging' + | 'graphql-comparison-paginated-1' + | 'graphql-comparison-paginated-2' + | 'graphql-comparison-missing-pr' + | 'graphql-comparison-missing-pr-with-paths' + | 'graphql-recent-merged-prs' + | 'graphql-recent-merged-prs-with-paths' + +export const getGqlPayload = (payload: Payload) => + JSON.parse( + readFileSync( + path.join( + path.dirname(import.meta.filename), + '../fixtures/graphql', + `${payload}.json`, + ), + { encoding: 'utf8' }, + ), + ) + +export const mockGraphqlQuery = ( + params: + | { + query?: Query + payload: Payload | Payload[] + } + | Array<{ + query?: Query + payload: Payload | Payload[] + }>, +) => { + const paramsList = Array.isArray(params) ? params : [params] + + let scope = nock('https://api.github.com') + + for (const param of paramsList) { + const payloads = Array.isArray(param.payload) + ? param.payload + : [param.payload] + + const defaultQuery: Query = 'query findCommitsInComparison' + + for (const payload of payloads) { + scope = scope + .post('/graphql', (body) => + body.query.includes(param.query || defaultQuery), + ) + .reply(200, getGqlPayload(payload)) + } + } + + return scope +} diff --git a/src/tests/mocks/hoisted.ts b/src/tests/mocks/hoisted.ts new file mode 100644 index 0000000000..f21ce00f10 --- /dev/null +++ b/src/tests/mocks/hoisted.ts @@ -0,0 +1,54 @@ +import type * as core from '@actions/core' +import { vi } from 'vitest' +import type { composeConfigGet } from '#src/common/index.ts' +import type { AvailableConfigs } from './config.ts' + +const mocks = vi.hoisted(() => { + const DEBUG_TESTS = false + + return { + /** + * Use this mock as a nock().post() body matcher, such as expectations + * can be defined as : + * @example expect(mocks.postReleaseBody.mock.lastCall).toMatchInlineSnapshot('...') + */ + postReleaseBody: vi.fn(() => true), + /** + * Use this mock as a nock().patch() body matcher, such as expectations + * can be defined as : + * @example expect(mocks.patchReleaseBody.mock.lastCall).toMatchInlineSnapshot('...') + */ + patchReleaseBody: vi.fn(() => true), + /** + * Use this mock as a nock().patch() body matcher, such as expectations + * can be defined as : + * @example expect(mocks.postPrLabelsBody.mock.lastCall).toMatchInlineSnapshot('...') + */ + postPrLabelsBody: vi.fn(() => true), + config: vi.fn<() => AvailableConfigs | undefined>(() => undefined), + getContextsConfigWasFetchedFrom: vi.fn< + () => Awaited>['contexts'] + >(() => [ + { + filepath: 'oui', + scheme: 'github', + ref: 'main', + repo: { owner: 'cchanche', repo: 'proj' }, + }, + ]), + core: { + debug: vi.fn(DEBUG_TESTS ? console.debug : undefined), + error: vi.fn(DEBUG_TESTS ? console.error : undefined), + info: vi.fn(DEBUG_TESTS ? console.info : undefined), + warning: vi.fn( + DEBUG_TESTS ? console.warn : undefined, + ), + setOutput: vi.fn(), + setFailed: vi.fn( + DEBUG_TESTS ? console.error : undefined, + ), + }, + } +}) + +export { mocks } diff --git a/src/tests/mocks/index.ts b/src/tests/mocks/index.ts new file mode 100644 index 0000000000..ea7a02d10e --- /dev/null +++ b/src/tests/mocks/index.ts @@ -0,0 +1,13 @@ +export { mockedConfigModule } from './config.ts' +export * from './context.ts' +export { getGqlPayload, mockGraphqlQuery } from './graphql.ts' +export { mocks } from './hoisted.ts' +export { mockInput } from './input.ts' +export { nockGetPrFiles } from './pull_requests.ts' +export { + getReleasePayload, + nockGetAndPatchReleases, + nockGetAndPostReleases, + nockGetReleases, + nockPostRelease, +} from './releases.ts' diff --git a/src/tests/mocks/input.ts b/src/tests/mocks/input.ts new file mode 100644 index 0000000000..489eff477f --- /dev/null +++ b/src/tests/mocks/input.ts @@ -0,0 +1,22 @@ +import { expect, vi } from 'vitest' +import type * as z from 'zod' +import type { actionInputSchema } from '#src/actions/drafter/config/index.ts' + +/** + * Mocking GitHub Action inputs for testing. + * + * The defined environments variables will determine the behavior of `@actions/core.getInput()` + * when it is executed. + */ +export const mockInput = async ( + key: keyof z.input, + value: string, +) => { + const envKey = `INPUT_${key.replace(/ /g, '_').toUpperCase()}` + + vi.stubEnv(envKey, value) + + // Verify the context has been set up correctly + const dynamicGetInput = (await import('@actions/core')).getInput + expect(dynamicGetInput(key)).toEqual(value) +} diff --git a/src/tests/mocks/pull_requests.ts b/src/tests/mocks/pull_requests.ts new file mode 100644 index 0000000000..e7a6f00d67 --- /dev/null +++ b/src/tests/mocks/pull_requests.ts @@ -0,0 +1,104 @@ +import { readFileSync } from 'node:fs' +import path from 'node:path' +import nock from 'nock' +import { mocks } from './hoisted.ts' + +/** + * Available files in fixtures/pull_requests + */ +type Files = 'files' + +const getPrFilesPayload = (f: Files) => + JSON.parse( + readFileSync( + path.join( + path.dirname(import.meta.filename), + '../fixtures/pull_requests', + `${f}.json`, + ), + { + encoding: 'utf8', + }, + ), + ) + +const nockSinglePrFiles = (params: { + files?: Files + filenames?: string[] + pr?: { owner: string; repo: string; number: number } +}) => { + const { pr, files, filenames } = params + const payload = filenames + ? filenames.map((filename) => ({ filename })) + : getPrFilesPayload(files || 'files') + + return nock('https://api.github.com') + .get( + `/repos/${pr?.owner || 'release-drafter'}/${pr?.repo || 'release-drafter'}/pulls/${pr?.number || 1475}/files`, + ) + .query(true) + .reply(200, payload) +} + +export function nockGetPrFiles(params: { + files?: Files + filenames?: string[] + pr?: { owner: string; repo: string; number: number } +}): nock.Scope +export function nockGetPrFiles(params: { + prs: Array<{ + pr: { owner: string; repo: string; number: number } + filenames: string[] + }> +}): nock.Scope[] +export function nockGetPrFiles(params: { + repo?: { owner: string; repo: string } + entries: Array<[number, string[]]> +}): nock.Scope[] +export function nockGetPrFiles(params: { + files?: Files + filenames?: string[] + pr?: { owner: string; repo: string; number: number } + prs?: Array<{ + pr: { owner: string; repo: string; number: number } + filenames: string[] + }> + repo?: { owner: string; repo: string } + entries?: Array<[number, string[]]> +}): nock.Scope | nock.Scope[] { + if (params.entries) { + const { repo } = params + + return params.entries.map(([number, filenames]) => + nockSinglePrFiles({ + pr: { + owner: repo?.owner || 'release-drafter', + repo: repo?.repo || 'release-drafter', + number, + }, + filenames, + }), + ) + } + + if (params.prs) { + return params.prs.map(({ pr, filenames }) => + nockSinglePrFiles({ pr, filenames }), + ) + } + + return nockSinglePrFiles(params) +} + +export const nockPostPrLabels = (params: { + pr?: { owner: string; repo: string; number: number } +}) => { + const { pr } = params || {} + + return nock('https://api.github.com') + .post( + `/repos/${pr?.owner || 'release-drafter'}/${pr?.repo || 'release-drafter'}/issues/${pr?.number || 1475}/labels`, + mocks.postPrLabelsBody, + ) + .reply(201) +} diff --git a/src/tests/mocks/releases.ts b/src/tests/mocks/releases.ts new file mode 100644 index 0000000000..f7b8b3612b --- /dev/null +++ b/src/tests/mocks/releases.ts @@ -0,0 +1,118 @@ +import { readFileSync } from 'node:fs' +import path from 'node:path' +import nock from 'nock' +import { mocks } from './hoisted.ts' + +/** + * Available files in fixtures/releases + */ +type Release = + | 'release' + | 'release-2' + | 'release-3' + | 'release-draft' + | 'pre-release' + | 'release-shared-commit-date' + +export const getReleasePayload = (f: Release) => + JSON.parse( + readFileSync( + path.join( + path.dirname(import.meta.filename), + '../fixtures/releases', + `${f}.json`, + ), + { + encoding: 'utf8', + }, + ), + ) + +export const nockGetReleases = (params: { + releaseFiles: Release[] + repo?: { owner: string; repo: string } +}) => { + const { repo, releaseFiles } = params || {} + + return nock('https://api.github.com') + .get( + `/repos/${repo?.owner || 'toolmantim'}/${repo?.repo || 'release-drafter-test-project'}/releases`, + ) + .query(true) + .reply( + 200, + releaseFiles.map((f) => getReleasePayload(f)), + ) +} + +export const nockPostRelease = (params?: { + replyRelease?: Release + repo?: { owner: string; repo: string } +}) => { + const { repo, replyRelease } = params || {} + + const postScope = nock('https://api.github.com') + .post( + `/repos/${repo?.owner || 'toolmantim'}/${repo?.repo || 'release-drafter-test-project'}/releases`, + mocks.postReleaseBody, + ) + .reply(200, getReleasePayload(replyRelease || 'release')) + + return postScope +} + +export const nockGetAndPostReleases = (params: { + fetchedReleases: Release[] + replyRelease?: Release + repo?: { owner: string; repo: string } + fetchedReleasesOverrides?: object[] +}) => { + const { repo, replyRelease, fetchedReleases } = params || {} + + return nock('https://api.github.com') + .get( + `/repos/${repo?.owner || 'toolmantim'}/${repo?.repo || 'release-drafter-test-project'}/releases`, + ) + .query(true) + .reply( + 200, + fetchedReleases.map((f, i) => ({ + ...getReleasePayload(f), + ...params.fetchedReleasesOverrides?.[i], + })), + ) + .post( + `/repos/${repo?.owner || 'toolmantim'}/${repo?.repo || 'release-drafter-test-project'}/releases`, + mocks.postReleaseBody, + ) + .reply(200, getReleasePayload(replyRelease || 'release')) +} + +export const nockGetAndPatchReleases = (params: { + fetchedReleases: Release[] + replyRelease?: Release + repo?: { owner: string; repo: string } + fetchedReleasesOverrides?: object[] +}) => { + const { repo, replyRelease, fetchedReleases } = params || {} + + return nock('https://api.github.com') + .get( + `/repos/${repo?.owner || 'toolmantim'}/${repo?.repo || 'release-drafter-test-project'}/releases`, + ) + .query(true) + .reply( + 200, + fetchedReleases.map((f, i) => ({ + ...getReleasePayload(f), + ...params.fetchedReleasesOverrides?.[i], + })), + ) + .patch( + new RegExp( + `/repos/${repo?.owner || 'toolmantim'}/${repo?.repo || 'release-drafter-test-project'}/releases/\\d+`, + ), + mocks.patchReleaseBody, + ) + .reply(200, getReleasePayload(replyRelease || 'release')) +} diff --git a/src/tests/setup.ts b/src/tests/setup.ts new file mode 100644 index 0000000000..70037c5d01 --- /dev/null +++ b/src/tests/setup.ts @@ -0,0 +1,81 @@ +/** + * Vitest setup file. + * + * Run before each test file in the same process + * + * @see https://vitest.dev/config/setupfiles.html + */ + +import nock from 'nock' +import { afterAll, afterEach, beforeAll, beforeEach, vi } from 'vitest' +import type * as z from 'zod' +import type { sharedInputSchema } from '#src/common/shared-input.schema.ts' +import { mocks } from '#tests/mocks/index.ts' + +/** + * The call to vi.mock is hoisted, so it doesn't matter where you call it. + * @see https://vitest.dev/api/vi.html#vi-mock + */ +vi.mock( + import('#src/common/config/index.ts'), + (await import('#tests/mocks/index.ts')).mockedConfigModule, +) +/** + * `@actions/github` hands octokit an `undici`-backed `fetch` that nock cannot + * intercept. Swap in Node's global `fetch`, which nock understands. + * + * Tests only — production keeps `@actions/github`'s `fetch` so its + * `http_proxy`/`https_proxy` dispatcher survives. + */ +vi.mock(import('@actions/github'), async (iom) => { + const om = await iom() + return { + ...om, + getOctokit: ((token, options, ...plugins) => + om.getOctokit( + token, + { + ...options, + request: { ...options?.request, fetch: globalThis.fetch }, + }, + ...plugins, + )) as typeof om.getOctokit, + } +}) + +vi.mock(import('@actions/core'), async (iom) => { + const om = await iom() + return { + ...om, + ...mocks.core, + getInput: (name: string) => { + switch (name as keyof z.infer) { + case 'token': + return 'test' + default: + return om.getInput(name) // will read from INPUT_* variables + } + }, + } +}) + +beforeAll(() => { + // Disable actual network requests. + nock.disableNetConnect() +}) + +afterAll(() => { + nock.restore() +}) + +beforeEach(() => { + nock('https://api.github.com') + .post('/app/installations/179208/access_tokens') + .reply(200, { token: 'test' }) + vi.resetAllMocks() + vi.unstubAllEnvs() +}) + +afterEach(() => { + nock.cleanAll() +}) diff --git a/src/types/env.ts b/src/types/env.ts new file mode 100644 index 0000000000..18a80d25ac --- /dev/null +++ b/src/types/env.ts @@ -0,0 +1,272 @@ +/** + * All values are strings exposed by the GitHub Actions runtime. + * + * @note AI Generated, prone to errors + */ +export interface GithubActionEnvironment extends Record { + /** + * Always set to `true` in GitHub Actions to indicate a CI environment. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + CI: string + + /** + * Identifier for the currently executing action. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_ACTION: string + + /** + * Filesystem path to the action being executed. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_ACTION_PATH: string + + /** + * `owner/repo` of the action being executed. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_ACTION_REPOSITORY: string + + /** + * Set to `true` when the workflow runs on GitHub Actions. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_ACTIONS: string + + /** + * Username of the actor that triggered the workflow. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_ACTOR: string + + /** + * Numeric ID of the actor that triggered the workflow. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_ACTOR_ID: string + + /** + * Base URL for the GitHub REST API. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_API_URL: string + + /** + * Base branch or tag ref for the event, such as the target branch of a pull + * request. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_BASE_REF: string + + /** + * Path to the file where environment variables for subsequent steps can be + * written. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_ENV: string + + /** + * Name of the event that triggered the workflow run. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_EVENT_NAME: string + + /** + * Path to the file containing the complete webhook event payload. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_EVENT_PATH: string + + /** + * Base URL for the GitHub GraphQL API. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_GRAPHQL_URL: string + + /** + * Head branch or tag ref for the event, such as the source branch of a pull + * request. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_HEAD_REF: string + + /** + * Job ID as defined in the workflow. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_JOB: string + + /** + * Path to the file where step outputs should be written for subsequent steps + * to read. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_OUTPUT: string + + /** + * Path to the file that can be populated with directories to prepend to the + * system `PATH`. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_PATH: string + + /** + * Full Git ref that triggered the workflow, such as `refs/heads/main` or + * `refs/tags/v1.0.0`. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_REF: string + + /** + * Short ref name that triggered the workflow, such as `main` or `v1.0.0`. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_REF_NAME: string + + /** + * Indicates whether the ref that triggered the workflow is protected. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_REF_PROTECTED: string + + /** + * Type of ref that triggered the workflow, either `branch` or `tag`. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_REF_TYPE: string + + /** + * `owner/repo` of the repository that triggered the workflow. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_REPOSITORY: string + + /** + * Numeric repository ID of the repository that triggered the workflow. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_REPOSITORY_ID: string + + /** + * Repository owner's name of the repository that triggered the workflow. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_REPOSITORY_OWNER: string + + /** + * Numeric repository owner ID of the repository that triggered the workflow. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_REPOSITORY_OWNER_ID: string + + /** + * Retention period (in days) for artifacts and logs generated by the + * workflow run. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_RETENTION_DAYS: string + + /** + * Number that increments with each attempt of a given workflow run. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_RUN_ATTEMPT: string + + /** + * Unique identifier for the current workflow run. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_RUN_ID: string + + /** + * Run number for the workflow, unique per workflow definition. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_RUN_NUMBER: string + + /** + * Base URL for the GitHub server instance (for example `https://github.com`). + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_SERVER_URL: string + + /** + * Commit SHA that triggered the workflow run. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_SHA: string + + /** + * Path to the file where step summaries can be written in Markdown. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_STEP_SUMMARY: string + + /** + * Actor that initiated the original workflow run (useful for re-runs). + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_TRIGGERING_ACTOR: string + + /** + * Name of the workflow as defined in the workflow file. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_WORKFLOW: string + + /** + * Fully qualified reference to the workflow file path and ref that was used + * to run the workflow. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_WORKFLOW_REF: string + + /** + * Commit SHA of the workflow file that is being executed. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_WORKFLOW_SHA: string + + /** + * Filesystem path to the GitHub workspace directory on the runner. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + GITHUB_WORKSPACE: string + + /** + * Processor architecture of the runner executing the job. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + RUNNER_ARCH: string + + /** + * Indicates whether runner diagnostic logging is enabled. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + RUNNER_DEBUG: string + + /** + * Name of the runner executing the job. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + RUNNER_NAME: string + + /** + * Operating system of the runner executing the job. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + RUNNER_OS: string + + /** + * Directory path on the runner for temporary files. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + RUNNER_TEMP: string + + /** + * Directory on the runner that stores cached tool installations. + * @see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + */ + RUNNER_TOOL_CACHE: string +} diff --git a/src/types/github.graphql.generated.ts b/src/types/github.graphql.generated.ts new file mode 100644 index 0000000000..522abcf8cb --- /dev/null +++ b/src/types/github.graphql.generated.ts @@ -0,0 +1,38138 @@ +/** Internal type. DO NOT USE DIRECTLY. */ +type Exact = { [K in keyof T]: T[K] }; +/** Internal type. DO NOT USE DIRECTLY. */ +export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; +import type { DocumentTypeDecoration } from '@graphql-typed-document-node/core'; +export type Maybe = T | null; +export type InputMaybe = Maybe; +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: { input: string; output: string; } + String: { input: string; output: string; } + Boolean: { input: boolean; output: boolean; } + Int: { input: number; output: number; } + Float: { input: number; output: number; } + /** A (potentially binary) string encoded using base64. */ + Base64String: { input: unknown; output: unknown; } + /** + * Represents non-fractional signed whole numeric values. Since the value may + * exceed the size of a 32-bit integer, it's encoded as a string. + */ + BigInt: { input: unknown; output: unknown; } + /** + * A custom property value can be either a string or an array of strings. All + * property types support only a single string value, except for the multi-select + * type, which supports only a string array. + */ + CustomPropertyValue: { input: unknown; output: unknown; } + /** An ISO-8601 encoded date string. */ + Date: { input: unknown; output: unknown; } + /** An ISO-8601 encoded UTC date string. */ + DateTime: { input: string; output: string; } + /** A Git object ID. */ + GitObjectID: { input: string; output: string; } + /** A fully qualified reference name (e.g. `refs/heads/master`). */ + GitRefname: { input: unknown; output: unknown; } + /** Git SSH string */ + GitSSHRemote: { input: unknown; output: unknown; } + /** An ISO-8601 encoded date string. Unlike the DateTime type, GitTimestamp is not converted in UTC. */ + GitTimestamp: { input: unknown; output: unknown; } + /** A string containing HTML code. */ + HTML: { input: unknown; output: unknown; } + /** An ISO-8601 encoded UTC date string with millisecond precision. */ + PreciseDateTime: { input: unknown; output: unknown; } + /** An RFC 3986, RFC 3987, and RFC 6570 (level 4) compliant URI string. */ + URI: { input: string; output: string; } + /** A valid x509 certificate string */ + X509Certificate: { input: unknown; output: unknown; } +}; + +/** Autogenerated input type of AbortQueuedMigrations */ +export type AbortQueuedMigrationsInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The ID of the organization that is running the migrations. */ + ownerId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of AbortQueuedMigrations. */ +export type AbortQueuedMigrationsPayload = { + __typename?: 'AbortQueuedMigrationsPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** Did the operation succeed? */ + success?: Maybe; +}; + +/** Autogenerated input type of AbortRepositoryMigration */ +export type AbortRepositoryMigrationInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The ID of the migration to be aborted. */ + migrationId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of AbortRepositoryMigration. */ +export type AbortRepositoryMigrationPayload = { + __typename?: 'AbortRepositoryMigrationPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** Did the operation succeed? */ + success?: Maybe; +}; + +/** Autogenerated input type of AcceptEnterpriseAdministratorInvitation */ +export type AcceptEnterpriseAdministratorInvitationInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The id of the invitation being accepted */ + invitationId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of AcceptEnterpriseAdministratorInvitation. */ +export type AcceptEnterpriseAdministratorInvitationPayload = { + __typename?: 'AcceptEnterpriseAdministratorInvitationPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The invitation that was accepted. */ + invitation?: Maybe; + /** A message confirming the result of accepting an administrator invitation. */ + message?: Maybe; +}; + +/** Autogenerated input type of AcceptEnterpriseMemberInvitation */ +export type AcceptEnterpriseMemberInvitationInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The id of the invitation being accepted */ + invitationId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of AcceptEnterpriseMemberInvitation. */ +export type AcceptEnterpriseMemberInvitationPayload = { + __typename?: 'AcceptEnterpriseMemberInvitationPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The invitation that was accepted. */ + invitation?: Maybe; + /** A message confirming the result of accepting an unaffiliated member invitation. */ + message?: Maybe; +}; + +/** Autogenerated input type of AcceptTopicSuggestion */ +export type AcceptTopicSuggestionInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** + * The name of the suggested topic. + * + * **Upcoming Change on 2024-04-01 UTC** + * **Description:** `name` will be removed. + * **Reason:** Suggested topics are no longer supported + */ + name?: InputMaybe; + /** + * The Node ID of the repository. + * + * **Upcoming Change on 2024-04-01 UTC** + * **Description:** `repositoryId` will be removed. + * **Reason:** Suggested topics are no longer supported + */ + repositoryId?: InputMaybe; +}; + +/** Autogenerated return type of AcceptTopicSuggestion. */ +export type AcceptTopicSuggestionPayload = { + __typename?: 'AcceptTopicSuggestionPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** + * The accepted topic. + * @deprecated Suggested topics are no longer supported Removal on 2024-04-01 UTC. + */ + topic?: Maybe; +}; + +/** Autogenerated input type of AccessUserNamespaceRepository */ +export type AccessUserNamespaceRepositoryInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The ID of the enterprise owning the user namespace repository. */ + enterpriseId: Scalars['ID']['input']; + /** The ID of the user namespace repository to access. */ + repositoryId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of AccessUserNamespaceRepository. */ +export type AccessUserNamespaceRepositoryPayload = { + __typename?: 'AccessUserNamespaceRepositoryPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The time that repository access expires at */ + expiresAt?: Maybe; + /** The repository that is temporarily accessible. */ + repository?: Maybe; +}; + +/** Represents an object which can take actions on GitHub. Typically a User or Bot. */ +export type Actor = { + /** A URL pointing to the actor's public avatar. */ + avatarUrl: Scalars['URI']['output']; + /** The username of the actor. */ + login: Scalars['String']['output']; + /** The HTTP path for this actor. */ + resourcePath: Scalars['URI']['output']; + /** The HTTP URL for this actor. */ + url: Scalars['URI']['output']; +}; + + +/** Represents an object which can take actions on GitHub. Typically a User or Bot. */ +export type ActorAvatarUrlArgs = { + size?: InputMaybe; +}; + +/** The connection type for Actor. */ +export type ActorConnection = { + __typename?: 'ActorConnection'; + /** A list of edges. */ + edges?: Maybe>>; + /** A list of nodes. */ + nodes?: Maybe>>; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** Identifies the total count of items in the connection. */ + totalCount: Scalars['Int']['output']; +}; + +/** An edge in a connection. */ +export type ActorEdge = { + __typename?: 'ActorEdge'; + /** A cursor for use in pagination. */ + cursor: Scalars['String']['output']; + /** The item at the end of the edge. */ + node?: Maybe; +}; + +/** Location information for an actor */ +export type ActorLocation = { + __typename?: 'ActorLocation'; + /** City */ + city?: Maybe; + /** Country name */ + country?: Maybe; + /** Country code */ + countryCode?: Maybe; + /** Region name */ + region?: Maybe; + /** Region or state code */ + regionCode?: Maybe; +}; + +/** The actor's type. */ +export type ActorType = + /** Indicates a team actor. */ + | 'TEAM' + /** Indicates a user actor. */ + | 'USER'; + +/** Autogenerated input type of AddAssigneesToAssignable */ +export type AddAssigneesToAssignableInput = { + /** Configuration for assigning Copilot to this issue. */ + agentAssignment?: InputMaybe; + /** The id of the assignable object to add assignees to. */ + assignableId: Scalars['ID']['input']; + /** The ids of actors (users or bots) to add as assignees. Mutually exclusive with `assignees`. */ + assigneeIds?: InputMaybe>; + /** + * An array of actors to add as assignees, each with optional rationale or + * suggest flag. Mutually exclusive with `assigneeIds`. + */ + assignees?: InputMaybe>; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; +}; + +/** Autogenerated return type of AddAssigneesToAssignable. */ +export type AddAssigneesToAssignablePayload = { + __typename?: 'AddAssigneesToAssignablePayload'; + /** The item that was assigned. */ + assignable?: Maybe; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; +}; + +/** Autogenerated input type of AddBlockedBy */ +export type AddBlockedByInput = { + /** The ID of the issue that blocks the given issue. */ + blockingIssueId: Scalars['ID']['input']; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The ID of the issue to be blocked. */ + issueId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of AddBlockedBy. */ +export type AddBlockedByPayload = { + __typename?: 'AddBlockedByPayload'; + /** The issue that is blocking the given issue. */ + blockingIssue?: Maybe; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The issue that is blocked. */ + issue?: Maybe; +}; + +/** Autogenerated input type of AddComment */ +export type AddCommentInput = { + /** The contents of the comment. */ + body: Scalars['String']['input']; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The Node ID of the subject to modify. */ + subjectId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of AddComment. */ +export type AddCommentPayload = { + __typename?: 'AddCommentPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The edge from the subject's comment connection. */ + commentEdge?: Maybe; + /** The subject */ + subject?: Maybe; + /** The edge from the subject's timeline connection. */ + timelineEdge?: Maybe; +}; + +/** Autogenerated input type of AddDiscussionComment */ +export type AddDiscussionCommentInput = { + /** The contents of the comment. */ + body: Scalars['String']['input']; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The Node ID of the discussion to comment on. */ + discussionId: Scalars['ID']['input']; + /** The Node ID of the discussion comment within this discussion to reply to. */ + replyToId?: InputMaybe; +}; + +/** Autogenerated return type of AddDiscussionComment. */ +export type AddDiscussionCommentPayload = { + __typename?: 'AddDiscussionCommentPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The newly created discussion comment. */ + comment?: Maybe; +}; + +/** Autogenerated input type of AddDiscussionPollVote */ +export type AddDiscussionPollVoteInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The Node ID of the discussion poll option to vote for. */ + pollOptionId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of AddDiscussionPollVote. */ +export type AddDiscussionPollVotePayload = { + __typename?: 'AddDiscussionPollVotePayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The poll option that a vote was added to. */ + pollOption?: Maybe; +}; + +/** Autogenerated input type of AddEnterpriseOrganizationMember */ +export type AddEnterpriseOrganizationMemberInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The ID of the enterprise which owns the organization. */ + enterpriseId: Scalars['ID']['input']; + /** The ID of the organization the users will be added to. */ + organizationId: Scalars['ID']['input']; + /** The role to assign the users in the organization */ + role?: InputMaybe; + /** The IDs of the enterprise members to add. */ + userIds: Array; +}; + +/** Autogenerated return type of AddEnterpriseOrganizationMember. */ +export type AddEnterpriseOrganizationMemberPayload = { + __typename?: 'AddEnterpriseOrganizationMemberPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The users who were added to the organization. */ + users?: Maybe>; +}; + +/** Autogenerated input type of AddEnterpriseSupportEntitlement */ +export type AddEnterpriseSupportEntitlementInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The ID of the Enterprise which the admin belongs to. */ + enterpriseId: Scalars['ID']['input']; + /** The login of a member who will receive the support entitlement. */ + login: Scalars['String']['input']; +}; + +/** Autogenerated return type of AddEnterpriseSupportEntitlement. */ +export type AddEnterpriseSupportEntitlementPayload = { + __typename?: 'AddEnterpriseSupportEntitlementPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** A message confirming the result of adding the support entitlement. */ + message?: Maybe; +}; + +/** Autogenerated input type of AddLabelsToLabelable */ +export type AddLabelsToLabelableInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The ids of the labels to add. */ + labelIds?: InputMaybe>; + /** The id of the labelable object to add labels to. */ + labelableId: Scalars['ID']['input']; + /** + * The labels to add, each with optional `rationale` or a `suggest` flag that + * stores the addition as a pending suggestion. Mutually exclusive with `labelIds`. + */ + labels?: InputMaybe>; +}; + +/** Autogenerated return type of AddLabelsToLabelable. */ +export type AddLabelsToLabelablePayload = { + __typename?: 'AddLabelsToLabelablePayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The item that was labeled. */ + labelable?: Maybe; +}; + +/** Autogenerated input type of AddProjectCard */ +export type AddProjectCardInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The content of the card. Must be a member of the ProjectCardItem union */ + contentId?: InputMaybe; + /** The note on the card. */ + note?: InputMaybe; + /** The Node ID of the ProjectColumn. */ + projectColumnId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of AddProjectCard. */ +export type AddProjectCardPayload = { + __typename?: 'AddProjectCardPayload'; + /** The edge from the ProjectColumn's card connection. */ + cardEdge?: Maybe; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The ProjectColumn */ + projectColumn?: Maybe; +}; + +/** Autogenerated input type of AddProjectColumn */ +export type AddProjectColumnInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The name of the column. */ + name: Scalars['String']['input']; + /** The Node ID of the project. */ + projectId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of AddProjectColumn. */ +export type AddProjectColumnPayload = { + __typename?: 'AddProjectColumnPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The edge from the project's column connection. */ + columnEdge?: Maybe; + /** The project */ + project?: Maybe; +}; + +/** Autogenerated input type of AddProjectV2DraftIssue */ +export type AddProjectV2DraftIssueInput = { + /** The IDs of the assignees of the draft issue. */ + assigneeIds?: InputMaybe>; + /** The body of the draft issue. */ + body?: InputMaybe; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The ID of the Project to add the draft issue to. */ + projectId: Scalars['ID']['input']; + /** + * The title of the draft issue. A project item can also be created by providing + * the URL of an Issue or Pull Request if you have access. + */ + title: Scalars['String']['input']; +}; + +/** Autogenerated return type of AddProjectV2DraftIssue. */ +export type AddProjectV2DraftIssuePayload = { + __typename?: 'AddProjectV2DraftIssuePayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The draft issue added to the project. */ + projectItem?: Maybe; +}; + +/** Autogenerated input type of AddProjectV2ItemById */ +export type AddProjectV2ItemByIdInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The id of the Issue or Pull Request to add. */ + contentId: Scalars['ID']['input']; + /** The ID of the Project to add the item to. */ + projectId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of AddProjectV2ItemById. */ +export type AddProjectV2ItemByIdPayload = { + __typename?: 'AddProjectV2ItemByIdPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The item added to the project. */ + item?: Maybe; +}; + +/** Autogenerated input type of AddPullRequestCreationCapBypassUsers */ +export type AddPullRequestCreationCapBypassUsersInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The Node ID of the repository. */ + repositoryId: Scalars['ID']['input']; + /** The Node IDs of the users to add to the bypass list. */ + userIds: Array; +}; + +/** Autogenerated return type of AddPullRequestCreationCapBypassUsers. */ +export type AddPullRequestCreationCapBypassUsersPayload = { + __typename?: 'AddPullRequestCreationCapBypassUsersPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The repository with the updated bypass list. */ + repository?: Maybe; +}; + +/** Autogenerated input type of AddPullRequestReviewComment */ +export type AddPullRequestReviewCommentInput = { + /** + * The text of the comment. This field is required + * + * **Upcoming Change on 2023-10-01 UTC** + * **Description:** `body` will be removed. use addPullRequestReviewThread or addPullRequestReviewThreadReply instead + * **Reason:** We are deprecating the addPullRequestReviewComment mutation + */ + body?: InputMaybe; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** + * The SHA of the commit to comment on. + * + * **Upcoming Change on 2023-10-01 UTC** + * **Description:** `commitOID` will be removed. use addPullRequestReviewThread or addPullRequestReviewThreadReply instead + * **Reason:** We are deprecating the addPullRequestReviewComment mutation + */ + commitOID?: InputMaybe; + /** + * The comment id to reply to. + * + * **Upcoming Change on 2023-10-01 UTC** + * **Description:** `inReplyTo` will be removed. use addPullRequestReviewThread or addPullRequestReviewThreadReply instead + * **Reason:** We are deprecating the addPullRequestReviewComment mutation + */ + inReplyTo?: InputMaybe; + /** + * The relative path of the file to comment on. + * + * **Upcoming Change on 2023-10-01 UTC** + * **Description:** `path` will be removed. use addPullRequestReviewThread or addPullRequestReviewThreadReply instead + * **Reason:** We are deprecating the addPullRequestReviewComment mutation + */ + path?: InputMaybe; + /** + * The line index in the diff to comment on. + * + * **Upcoming Change on 2023-10-01 UTC** + * **Description:** `position` will be removed. use addPullRequestReviewThread or addPullRequestReviewThreadReply instead + * **Reason:** We are deprecating the addPullRequestReviewComment mutation + */ + position?: InputMaybe; + /** + * The node ID of the pull request reviewing + * + * **Upcoming Change on 2023-10-01 UTC** + * **Description:** `pullRequestId` will be removed. use + * addPullRequestReviewThread or addPullRequestReviewThreadReply instead + * **Reason:** We are deprecating the addPullRequestReviewComment mutation + */ + pullRequestId?: InputMaybe; + /** + * The Node ID of the review to modify. + * + * **Upcoming Change on 2023-10-01 UTC** + * **Description:** `pullRequestReviewId` will be removed. use + * addPullRequestReviewThread or addPullRequestReviewThreadReply instead + * **Reason:** We are deprecating the addPullRequestReviewComment mutation + */ + pullRequestReviewId?: InputMaybe; +}; + +/** Autogenerated return type of AddPullRequestReviewComment. */ +export type AddPullRequestReviewCommentPayload = { + __typename?: 'AddPullRequestReviewCommentPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The newly created comment. */ + comment?: Maybe; + /** The edge from the review's comment connection. */ + commentEdge?: Maybe; +}; + +/** Autogenerated input type of AddPullRequestReview */ +export type AddPullRequestReviewInput = { + /** The contents of the review body comment. */ + body?: InputMaybe; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** + * The review line comments. + * + * **Upcoming Change on 2023-10-01 UTC** + * **Description:** `comments` will be removed. use the `threads` argument instead + * **Reason:** We are deprecating comment fields that use diff-relative positioning + */ + comments?: InputMaybe>>; + /** The commit OID the review pertains to. */ + commitOID?: InputMaybe; + /** The event to perform on the pull request review. */ + event?: InputMaybe; + /** The Node ID of the pull request to modify. */ + pullRequestId: Scalars['ID']['input']; + /** The review line comment threads. */ + threads?: InputMaybe>>; +}; + +/** Autogenerated return type of AddPullRequestReview. */ +export type AddPullRequestReviewPayload = { + __typename?: 'AddPullRequestReviewPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The newly created pull request review. */ + pullRequestReview?: Maybe; + /** The edge from the pull request's review connection. */ + reviewEdge?: Maybe; +}; + +/** Autogenerated input type of AddPullRequestReviewThread */ +export type AddPullRequestReviewThreadInput = { + /** Body of the thread's first comment. */ + body: Scalars['String']['input']; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** + * The line of the blob to which the thread refers, required for line-level + * threads. The end of the line range for multi-line comments. + */ + line?: InputMaybe; + /** Path to the file being commented on. */ + path?: InputMaybe; + /** The node ID of the pull request reviewing */ + pullRequestId?: InputMaybe; + /** The Node ID of the review to modify. */ + pullRequestReviewId?: InputMaybe; + /** The side of the diff on which the line resides. For multi-line comments, this is the side for the end of the line range. */ + side?: InputMaybe; + /** The first line of the range to which the comment refers. */ + startLine?: InputMaybe; + /** The side of the diff on which the start line resides. */ + startSide?: InputMaybe; + /** The level at which the comments in the corresponding thread are targeted, can be a diff line or a file */ + subjectType?: InputMaybe; +}; + +/** Autogenerated return type of AddPullRequestReviewThread. */ +export type AddPullRequestReviewThreadPayload = { + __typename?: 'AddPullRequestReviewThreadPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The newly created thread. */ + thread?: Maybe; +}; + +/** Autogenerated input type of AddPullRequestReviewThreadReply */ +export type AddPullRequestReviewThreadReplyInput = { + /** The text of the reply. */ + body: Scalars['String']['input']; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The Node ID of the pending review to which the reply will belong. */ + pullRequestReviewId?: InputMaybe; + /** The Node ID of the thread to which this reply is being written. */ + pullRequestReviewThreadId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of AddPullRequestReviewThreadReply. */ +export type AddPullRequestReviewThreadReplyPayload = { + __typename?: 'AddPullRequestReviewThreadReplyPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The newly created reply. */ + comment?: Maybe; +}; + +/** Autogenerated input type of AddReaction */ +export type AddReactionInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The name of the emoji to react with. */ + content: ReactionContent; + /** The Node ID of the subject to modify. */ + subjectId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of AddReaction. */ +export type AddReactionPayload = { + __typename?: 'AddReactionPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The reaction object. */ + reaction?: Maybe; + /** The reaction groups for the subject. */ + reactionGroups?: Maybe>; + /** The reactable subject. */ + subject?: Maybe; +}; + +/** Autogenerated input type of AddStar */ +export type AddStarInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The Starrable ID to star. */ + starrableId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of AddStar. */ +export type AddStarPayload = { + __typename?: 'AddStarPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The starrable. */ + starrable?: Maybe; +}; + +/** Autogenerated input type of AddSubIssue */ +export type AddSubIssueInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The id of the issue. */ + issueId: Scalars['ID']['input']; + /** Option to replace parent issue if one already exists */ + replaceParent?: InputMaybe; + /** The id of the sub-issue. */ + subIssueId?: InputMaybe; + /** The url of the sub-issue. */ + subIssueUrl?: InputMaybe; +}; + +/** Autogenerated return type of AddSubIssue. */ +export type AddSubIssuePayload = { + __typename?: 'AddSubIssuePayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The parent issue that the sub-issue was added to. */ + issue?: Maybe; + /** The sub-issue of the parent. */ + subIssue?: Maybe; +}; + +/** Autogenerated input type of AddUpvote */ +export type AddUpvoteInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The Node ID of the discussion or comment to upvote. */ + subjectId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of AddUpvote. */ +export type AddUpvotePayload = { + __typename?: 'AddUpvotePayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The votable subject. */ + subject?: Maybe; +}; + +/** Autogenerated input type of AddVerifiableDomain */ +export type AddVerifiableDomainInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The URL of the domain */ + domain: Scalars['URI']['input']; + /** The ID of the owner to add the domain to */ + ownerId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of AddVerifiableDomain. */ +export type AddVerifiableDomainPayload = { + __typename?: 'AddVerifiableDomainPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The verifiable domain that was added. */ + domain?: Maybe; +}; + +/** Represents an 'added_to_merge_queue' event on a given pull request. */ +export type AddedToMergeQueueEvent = Node & { + __typename?: 'AddedToMergeQueueEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** The user who added this Pull Request to the merge queue */ + enqueuer?: Maybe; + /** The Node ID of the AddedToMergeQueueEvent object */ + id: Scalars['ID']['output']; + /** The merge queue where this pull request was added to. */ + mergeQueue?: Maybe; + /** PullRequest referenced by event. */ + pullRequest?: Maybe; +}; + +/** Represents a 'added_to_project' event on a given issue or pull request. */ +export type AddedToProjectEvent = Node & { + __typename?: 'AddedToProjectEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** + * Identifies the primary key from the database. + * @deprecated Projects (classic) is being deprecated in favor of the new Projects experience, see: https://github.blog/changelog/2024-05-23-sunset-notice-projects-classic/. Removal on 2025-04-01 UTC. + */ + databaseId?: Maybe; + /** The Node ID of the AddedToProjectEvent object */ + id: Scalars['ID']['output']; + /** + * Project referenced by event. + * @deprecated Projects (classic) is being deprecated in favor of the new Projects experience, see: https://github.blog/changelog/2024-05-23-sunset-notice-projects-classic/. Removal on 2025-04-01 UTC. + */ + project?: Maybe; + /** + * Project card referenced by this project event. + * @deprecated Projects (classic) is being deprecated in favor of the new Projects experience, see: https://github.blog/changelog/2024-05-23-sunset-notice-projects-classic/. Removal on 2025-04-01 UTC. + */ + projectCard?: Maybe; + /** + * Column name referenced by this project event. + * @deprecated Projects (classic) is being deprecated in favor of the new Projects experience, see: https://github.blog/changelog/2024-05-23-sunset-notice-projects-classic/. Removal on 2025-04-01 UTC. + */ + projectColumnName: Scalars['String']['output']; +}; + +/** Represents a 'added_to_project_v2' event on a given issue or pull request. */ +export type AddedToProjectV2Event = Node & ProjectV2Event & { + __typename?: 'AddedToProjectV2Event'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** The Node ID of the AddedToProjectV2Event object */ + id: Scalars['ID']['output']; + /** Project referenced by event. */ + project?: Maybe; + /** Did this event result from workflow automation? */ + wasAutomated: Scalars['Boolean']['output']; +}; + +/** Represents configuration for assigning Copilot to an issue (public variant) */ +export type AgentAssignmentInput = { + /** The base ref/branch for the repository. Defaults to the default branch if not provided. */ + baseRef?: InputMaybe; + /** Custom agent for Copilot. */ + customAgent?: InputMaybe; + /** Custom instructions for Copilot. */ + customInstructions?: InputMaybe; + /** The Node ID of the target repository where Copilot should work. Defaults to the issue's repository if not provided. */ + targetRepositoryId?: InputMaybe; +}; + +/** Copilot Agentic fields in context of the current viewer. */ +export type Agentic = { + /** Channel value for subscribing to live updates for session creations. */ + viewerCopilotAgentCreatesChannel?: Maybe; + /** Channel value for subscribing to live updates for session log updates. */ + viewerCopilotAgentLogUpdatesChannel?: Maybe; + /** Channel value for subscribing to live updates for task updates. */ + viewerCopilotAgentTaskUpdatesChannel?: Maybe; + /** Channel value for subscribing to live updates for session updates. */ + viewerCopilotAgentUpdatesChannel?: Maybe; +}; + +/** An announcement banner for an enterprise or organization. */ +export type AnnouncementBanner = { + __typename?: 'AnnouncementBanner'; + /** The date the announcement was created */ + createdAt: Scalars['DateTime']['output']; + /** The expiration date of the announcement, if any */ + expiresAt?: Maybe; + /** Whether the announcement can be dismissed by the user */ + isUserDismissible: Scalars['Boolean']['output']; + /** The text of the announcement */ + message?: Maybe; +}; + +/** A GitHub App. */ +export type App = Node & { + __typename?: 'App'; + /** The client ID of the app. */ + clientId?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** Identifies the primary key from the database. */ + databaseId?: Maybe; + /** The description of the app. */ + description?: Maybe; + /** The Node ID of the App object */ + id: Scalars['ID']['output']; + /** The IP addresses of the app. */ + ipAllowListEntries: IpAllowListEntryConnection; + /** The hex color code, without the leading '#', for the logo background. */ + logoBackgroundColor: Scalars['String']['output']; + /** A URL pointing to the app's logo. */ + logoUrl: Scalars['URI']['output']; + /** The name of the app. */ + name: Scalars['String']['output']; + /** A slug based on the name of the app for use in URLs. */ + slug: Scalars['String']['output']; + /** Identifies the date and time when the object was last updated. */ + updatedAt: Scalars['DateTime']['output']; + /** The URL to the app's homepage. */ + url: Scalars['URI']['output']; +}; + + +/** A GitHub App. */ +export type AppIpAllowListEntriesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + orderBy?: InputMaybe; +}; + + +/** A GitHub App. */ +export type AppLogoUrlArgs = { + size?: InputMaybe; +}; + +/** Autogenerated input type of ApproveDeployments */ +export type ApproveDeploymentsInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** Optional comment for approving deployments */ + comment?: InputMaybe; + /** The ids of environments to reject deployments */ + environmentIds: Array; + /** The node ID of the workflow run containing the pending deployments. */ + workflowRunId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of ApproveDeployments. */ +export type ApproveDeploymentsPayload = { + __typename?: 'ApproveDeploymentsPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The affected deployments. */ + deployments?: Maybe>; +}; + +/** Autogenerated input type of ApproveVerifiableDomain */ +export type ApproveVerifiableDomainInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The ID of the verifiable domain to approve. */ + id: Scalars['ID']['input']; +}; + +/** Autogenerated return type of ApproveVerifiableDomain. */ +export type ApproveVerifiableDomainPayload = { + __typename?: 'ApproveVerifiableDomainPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The verifiable domain that was approved. */ + domain?: Maybe; +}; + +/** Autogenerated input type of ArchiveProjectV2Item */ +export type ArchiveProjectV2ItemInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The ID of the ProjectV2Item to archive. */ + itemId: Scalars['ID']['input']; + /** The ID of the Project to archive the item from. */ + projectId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of ArchiveProjectV2Item. */ +export type ArchiveProjectV2ItemPayload = { + __typename?: 'ArchiveProjectV2ItemPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The item archived from the project. */ + item?: Maybe; +}; + +/** Autogenerated input type of ArchivePullRequest */ +export type ArchivePullRequestInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The Node ID of the pull request to archive. */ + pullRequestId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of ArchivePullRequest. */ +export type ArchivePullRequestPayload = { + __typename?: 'ArchivePullRequestPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The pull request that was archived. */ + pullRequest?: Maybe; +}; + +/** Autogenerated input type of ArchiveRepository */ +export type ArchiveRepositoryInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The ID of the repository to mark as archived. */ + repositoryId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of ArchiveRepository. */ +export type ArchiveRepositoryPayload = { + __typename?: 'ArchiveRepositoryPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The repository that was marked as archived. */ + repository?: Maybe; +}; + +/** An object that can have users assigned to it. */ +export type Assignable = { + /** A list of actors assigned to this object. */ + assignedActors: AssigneeConnection; + /** A list of Users assigned to this object. */ + assignees: UserConnection; + /** A list of suggested actors to assign to this object */ + suggestedActors: AssigneeConnection; + /** Indicates if the viewer can edit assignees for this object. */ + viewerCanAssign: Scalars['Boolean']['output']; +}; + + +/** An object that can have users assigned to it. */ +export type AssignableAssignedActorsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** An object that can have users assigned to it. */ +export type AssignableAssigneesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** An object that can have users assigned to it. */ +export type AssignableSuggestedActorsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + query?: InputMaybe; +}; + +/** Represents an 'assigned' event on any assignable object. */ +export type AssignedEvent = Node & { + __typename?: 'AssignedEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** Identifies the assignable associated with the event. */ + assignable: Assignable; + /** Identifies the user or mannequin that was assigned. */ + assignee?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** The Node ID of the AssignedEvent object */ + id: Scalars['ID']['output']; + /** + * Identifies the user who was assigned. + * @deprecated Assignees can now be mannequins. Use the `assignee` field instead. Removal on 2020-01-01 UTC. + */ + user?: Maybe; +}; + +/** Types that can be assigned to issues. */ +export type Assignee = Bot | Mannequin | Organization | User; + +/** The connection type for Assignee. */ +export type AssigneeConnection = { + __typename?: 'AssigneeConnection'; + /** A list of edges. */ + edges?: Maybe>>; + /** A list of nodes. */ + nodes?: Maybe>>; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** Identifies the total count of items in the connection. */ + totalCount: Scalars['Int']['output']; +}; + +/** An edge in a connection. */ +export type AssigneeEdge = { + __typename?: 'AssigneeEdge'; + /** A cursor for use in pagination. */ + cursor: Scalars['String']['output']; + /** The item at the end of the edge. */ + node?: Maybe; +}; + +/** Specifies an actor (user or bot) to assign to an issue, with optional metadata such as a rationale. */ +export type AssigneeUpdateInput = { + /** The ID of the actor (user or bot) to assign. */ + actorId: Scalars['ID']['input']; + /** The confidence level the agent had when selecting this value. */ + confidence?: InputMaybe; + /** Optional rationale describing why this value was selected. Max 280 characters. */ + rationale?: InputMaybe; + /** If true, the value is stored as a pending suggestion for human review rather than being applied directly. */ + suggest?: InputMaybe; +}; + +/** An entry in the audit log. */ +export type AuditEntry = { + /** + * The action name + * @deprecated The GraphQL audit-log is deprecated. Please use the REST API instead. Removal on 2026-04-01 UTC. + */ + action: Scalars['String']['output']; + /** + * The user who initiated the action + * @deprecated The GraphQL audit-log is deprecated. Please use the REST API instead. Removal on 2026-04-01 UTC. + */ + actor?: Maybe; + /** + * The IP address of the actor + * @deprecated The GraphQL audit-log is deprecated. Please use the REST API instead. Removal on 2026-04-01 UTC. + */ + actorIp?: Maybe; + /** + * A readable representation of the actor's location + * @deprecated The GraphQL audit-log is deprecated. Please use the REST API instead. Removal on 2026-04-01 UTC. + */ + actorLocation?: Maybe; + /** + * The username of the user who initiated the action + * @deprecated The GraphQL audit-log is deprecated. Please use the REST API instead. Removal on 2026-04-01 UTC. + */ + actorLogin?: Maybe; + /** + * The HTTP path for the actor. + * @deprecated The GraphQL audit-log is deprecated. Please use the REST API instead. Removal on 2026-04-01 UTC. + */ + actorResourcePath?: Maybe; + /** + * The HTTP URL for the actor. + * @deprecated The GraphQL audit-log is deprecated. Please use the REST API instead. Removal on 2026-04-01 UTC. + */ + actorUrl?: Maybe; + /** + * The time the action was initiated + * @deprecated The GraphQL audit-log is deprecated. Please use the REST API instead. Removal on 2026-04-01 UTC. + */ + createdAt: Scalars['PreciseDateTime']['output']; + /** + * The corresponding operation type for the action + * @deprecated The GraphQL audit-log is deprecated. Please use the REST API instead. Removal on 2026-04-01 UTC. + */ + operationType?: Maybe; + /** + * The user affected by the action + * @deprecated The GraphQL audit-log is deprecated. Please use the REST API instead. Removal on 2026-04-01 UTC. + */ + user?: Maybe; + /** + * For actions involving two users, the actor is the initiator and the user is the affected user. + * @deprecated The GraphQL audit-log is deprecated. Please use the REST API instead. Removal on 2026-04-01 UTC. + */ + userLogin?: Maybe; + /** + * The HTTP path for the user. + * @deprecated The GraphQL audit-log is deprecated. Please use the REST API instead. Removal on 2026-04-01 UTC. + */ + userResourcePath?: Maybe; + /** + * The HTTP URL for the user. + * @deprecated The GraphQL audit-log is deprecated. Please use the REST API instead. Removal on 2026-04-01 UTC. + */ + userUrl?: Maybe; +}; + +/** Types that can initiate an audit log event. */ +export type AuditEntryActor = Bot | Organization | User; + +/** Ordering options for Audit Log connections. */ +export type AuditLogOrder = { + /** The ordering direction. */ + direction?: InputMaybe; + /** The field to order Audit Logs by. */ + field?: InputMaybe; +}; + +/** Properties by which Audit Log connections can be ordered. */ +export type AuditLogOrderField = + /** Order audit log entries by timestamp */ + | 'CREATED_AT'; + +/** Represents a 'auto_merge_disabled' event on a given pull request. */ +export type AutoMergeDisabledEvent = Node & { + __typename?: 'AutoMergeDisabledEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** The user who disabled auto-merge for this Pull Request */ + disabler?: Maybe; + /** The Node ID of the AutoMergeDisabledEvent object */ + id: Scalars['ID']['output']; + /** PullRequest referenced by event */ + pullRequest?: Maybe; + /** The reason auto-merge was disabled */ + reason?: Maybe; + /** The reason_code relating to why auto-merge was disabled */ + reasonCode?: Maybe; +}; + +/** Represents a 'auto_merge_enabled' event on a given pull request. */ +export type AutoMergeEnabledEvent = Node & { + __typename?: 'AutoMergeEnabledEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** The user who enabled auto-merge for this Pull Request */ + enabler?: Maybe; + /** The Node ID of the AutoMergeEnabledEvent object */ + id: Scalars['ID']['output']; + /** PullRequest referenced by event. */ + pullRequest?: Maybe; +}; + +/** Represents an auto-merge request for a pull request */ +export type AutoMergeRequest = { + __typename?: 'AutoMergeRequest'; + /** The email address of the author of this auto-merge request. */ + authorEmail?: Maybe; + /** + * The commit message of the auto-merge request. If a merge queue is required by + * the base branch, this value will be set by the merge queue when merging. + */ + commitBody?: Maybe; + /** + * The commit title of the auto-merge request. If a merge queue is required by + * the base branch, this value will be set by the merge queue when merging + */ + commitHeadline?: Maybe; + /** When was this auto-merge request was enabled. */ + enabledAt?: Maybe; + /** The actor who created the auto-merge request. */ + enabledBy?: Maybe; + /** + * The merge method of the auto-merge request. If a merge queue is required by + * the base branch, this value will be set by the merge queue when merging. + */ + mergeMethod: PullRequestMergeMethod; + /** The pull request that this auto-merge request is set against. */ + pullRequest: PullRequest; +}; + +/** Represents a 'auto_rebase_enabled' event on a given pull request. */ +export type AutoRebaseEnabledEvent = Node & { + __typename?: 'AutoRebaseEnabledEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** The user who enabled auto-merge (rebase) for this Pull Request */ + enabler?: Maybe; + /** The Node ID of the AutoRebaseEnabledEvent object */ + id: Scalars['ID']['output']; + /** PullRequest referenced by event. */ + pullRequest?: Maybe; +}; + +/** Represents a 'auto_squash_enabled' event on a given pull request. */ +export type AutoSquashEnabledEvent = Node & { + __typename?: 'AutoSquashEnabledEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** The user who enabled auto-merge (squash) for this Pull Request */ + enabler?: Maybe; + /** The Node ID of the AutoSquashEnabledEvent object */ + id: Scalars['ID']['output']; + /** PullRequest referenced by event. */ + pullRequest?: Maybe; +}; + +/** Represents a 'automatic_base_change_failed' event on a given pull request. */ +export type AutomaticBaseChangeFailedEvent = Node & { + __typename?: 'AutomaticBaseChangeFailedEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** The Node ID of the AutomaticBaseChangeFailedEvent object */ + id: Scalars['ID']['output']; + /** The new base for this PR */ + newBase: Scalars['String']['output']; + /** The old base for this PR */ + oldBase: Scalars['String']['output']; + /** PullRequest referenced by event. */ + pullRequest: PullRequest; +}; + +/** Represents a 'automatic_base_change_succeeded' event on a given pull request. */ +export type AutomaticBaseChangeSucceededEvent = Node & { + __typename?: 'AutomaticBaseChangeSucceededEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** The Node ID of the AutomaticBaseChangeSucceededEvent object */ + id: Scalars['ID']['output']; + /** The new base for this PR */ + newBase: Scalars['String']['output']; + /** The old base for this PR */ + oldBase: Scalars['String']['output']; + /** PullRequest referenced by event. */ + pullRequest: PullRequest; +}; + +/** Represents a 'base_ref_changed' event on a given issue or pull request. */ +export type BaseRefChangedEvent = Node & { + __typename?: 'BaseRefChangedEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** Identifies the name of the base ref for the pull request after it was changed. */ + currentRefName: Scalars['String']['output']; + /** Identifies the primary key from the database. */ + databaseId?: Maybe; + /** The Node ID of the BaseRefChangedEvent object */ + id: Scalars['ID']['output']; + /** Identifies the name of the base ref for the pull request before it was changed. */ + previousRefName: Scalars['String']['output']; + /** PullRequest referenced by event. */ + pullRequest: PullRequest; +}; + +/** Represents a 'base_ref_deleted' event on a given pull request. */ +export type BaseRefDeletedEvent = Node & { + __typename?: 'BaseRefDeletedEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** Identifies the name of the Ref associated with the `base_ref_deleted` event. */ + baseRefName?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** The Node ID of the BaseRefDeletedEvent object */ + id: Scalars['ID']['output']; + /** PullRequest referenced by event. */ + pullRequest?: Maybe; +}; + +/** Represents a 'base_ref_force_pushed' event on a given pull request. */ +export type BaseRefForcePushedEvent = Node & { + __typename?: 'BaseRefForcePushedEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** Identifies the after commit SHA for the 'base_ref_force_pushed' event. */ + afterCommit?: Maybe; + /** Identifies the before commit SHA for the 'base_ref_force_pushed' event. */ + beforeCommit?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** The Node ID of the BaseRefForcePushedEvent object */ + id: Scalars['ID']['output']; + /** PullRequest referenced by event. */ + pullRequest: PullRequest; + /** Identifies the fully qualified ref name for the 'base_ref_force_pushed' event. */ + ref?: Maybe; +}; + +/** Represents a Git blame. */ +export type Blame = { + __typename?: 'Blame'; + /** The list of ranges from a Git blame. */ + ranges: Array; +}; + +/** Represents a range of information from a Git blame. */ +export type BlameRange = { + __typename?: 'BlameRange'; + /** + * Identifies the recency of the change, from 1 (new) to 10 (old). This is + * calculated as a 2-quantile and determines the length of distance between the + * median age of all the changes in the file and the recency of the current + * range's change. + */ + age: Scalars['Int']['output']; + /** Identifies the line author */ + commit: Commit; + /** The ending line for the range */ + endingLine: Scalars['Int']['output']; + /** The starting line for the range */ + startingLine: Scalars['Int']['output']; +}; + +/** Represents a Git blob. */ +export type Blob = GitObject & Node & { + __typename?: 'Blob'; + /** An abbreviated version of the Git object ID */ + abbreviatedOid: Scalars['String']['output']; + /** Byte size of Blob object */ + byteSize: Scalars['Int']['output']; + /** The HTTP path for this Git object */ + commitResourcePath: Scalars['URI']['output']; + /** The HTTP URL for this Git object */ + commitUrl: Scalars['URI']['output']; + /** The Node ID of the Blob object */ + id: Scalars['ID']['output']; + /** Indicates whether the Blob is binary or text. Returns null if unable to determine the encoding. */ + isBinary?: Maybe; + /** Indicates whether the contents is truncated */ + isTruncated: Scalars['Boolean']['output']; + /** The Git object ID */ + oid: Scalars['GitObjectID']['output']; + /** The Repository the Git object belongs to */ + repository: Repository; + /** UTF8 text data or null if the Blob is binary */ + text?: Maybe; +}; + +/** Represents a 'blocked_by_added' event on a given issue. */ +export type BlockedByAddedEvent = Node & { + __typename?: 'BlockedByAddedEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** The blocking issue that was added. */ + blockingIssue?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** The Node ID of the BlockedByAddedEvent object */ + id: Scalars['ID']['output']; +}; + +/** Represents a 'blocked_by_removed' event on a given issue. */ +export type BlockedByRemovedEvent = Node & { + __typename?: 'BlockedByRemovedEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** The blocking issue that was removed. */ + blockingIssue?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** The Node ID of the BlockedByRemovedEvent object */ + id: Scalars['ID']['output']; +}; + +/** Represents a 'blocking_added' event on a given issue. */ +export type BlockingAddedEvent = Node & { + __typename?: 'BlockingAddedEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** The blocked issue that was added. */ + blockedIssue?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** The Node ID of the BlockingAddedEvent object */ + id: Scalars['ID']['output']; +}; + +/** Represents a 'blocking_removed' event on a given issue. */ +export type BlockingRemovedEvent = Node & { + __typename?: 'BlockingRemovedEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** The blocked issue that was removed. */ + blockedIssue?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** The Node ID of the BlockingRemovedEvent object */ + id: Scalars['ID']['output']; +}; + +/** A special type of user which takes actions on behalf of GitHub Apps. */ +export type Bot = Actor & Node & UniformResourceLocatable & { + __typename?: 'Bot'; + /** A URL pointing to the GitHub App's public avatar. */ + avatarUrl: Scalars['URI']['output']; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** Identifies the primary key from the database. */ + databaseId?: Maybe; + /** The Node ID of the Bot object */ + id: Scalars['ID']['output']; + /** The username of the actor. */ + login: Scalars['String']['output']; + /** The HTTP path for this bot */ + resourcePath: Scalars['URI']['output']; + /** Identifies the date and time when the object was last updated. */ + updatedAt: Scalars['DateTime']['output']; + /** The HTTP URL for this bot */ + url: Scalars['URI']['output']; +}; + + +/** A special type of user which takes actions on behalf of GitHub Apps. */ +export type BotAvatarUrlArgs = { + size?: InputMaybe; +}; + +/** Types which can be actors for `BranchActorAllowance` objects. */ +export type BranchActorAllowanceActor = App | Team | User; + +/** Parameters to be used for the branch_name_pattern rule */ +export type BranchNamePatternParameters = { + __typename?: 'BranchNamePatternParameters'; + /** How this rule appears when configuring it. */ + name?: Maybe; + /** If true, the rule will fail if the pattern matches. */ + negate: Scalars['Boolean']['output']; + /** The operator to use for matching. */ + operator: Scalars['String']['output']; + /** The pattern to match with. */ + pattern: Scalars['String']['output']; +}; + +/** Parameters to be used for the branch_name_pattern rule */ +export type BranchNamePatternParametersInput = { + /** How this rule appears when configuring it. */ + name?: InputMaybe; + /** If true, the rule will fail if the pattern matches. */ + negate?: InputMaybe; + /** The operator to use for matching. */ + operator: Scalars['String']['input']; + /** The pattern to match with. */ + pattern: Scalars['String']['input']; +}; + +/** A branch protection rule. */ +export type BranchProtectionRule = Node & { + __typename?: 'BranchProtectionRule'; + /** Can this branch be deleted. */ + allowsDeletions: Scalars['Boolean']['output']; + /** Are force pushes allowed on this branch. */ + allowsForcePushes: Scalars['Boolean']['output']; + /** Is branch creation a protected operation. */ + blocksCreations: Scalars['Boolean']['output']; + /** A list of conflicts matching branches protection rule and other branch protection rules */ + branchProtectionRuleConflicts: BranchProtectionRuleConflictConnection; + /** A list of actors able to force push for this branch protection rule. */ + bypassForcePushAllowances: BypassForcePushAllowanceConnection; + /** A list of actors able to bypass PRs for this branch protection rule. */ + bypassPullRequestAllowances: BypassPullRequestAllowanceConnection; + /** The actor who created this branch protection rule. */ + creator?: Maybe; + /** Identifies the primary key from the database. */ + databaseId?: Maybe; + /** Will new commits pushed to matching branches dismiss pull request review approvals. */ + dismissesStaleReviews: Scalars['Boolean']['output']; + /** The Node ID of the BranchProtectionRule object */ + id: Scalars['ID']['output']; + /** Can admins override branch protection. */ + isAdminEnforced: Scalars['Boolean']['output']; + /** + * Whether users can pull changes from upstream when the branch is locked. Set to + * `true` to allow fork syncing. Set to `false` to prevent fork syncing. + */ + lockAllowsFetchAndMerge: Scalars['Boolean']['output']; + /** Whether to set the branch as read-only. If this is true, users will not be able to push to the branch. */ + lockBranch: Scalars['Boolean']['output']; + /** Repository refs that are protected by this rule */ + matchingRefs: RefConnection; + /** Identifies the protection rule pattern. */ + pattern: Scalars['String']['output']; + /** A list push allowances for this branch protection rule. */ + pushAllowances: PushAllowanceConnection; + /** The repository associated with this branch protection rule. */ + repository?: Maybe; + /** Whether the most recent push must be approved by someone other than the person who pushed it */ + requireLastPushApproval: Scalars['Boolean']['output']; + /** Number of approving reviews required to update matching branches. */ + requiredApprovingReviewCount?: Maybe; + /** List of required deployment environments that must be deployed successfully to update matching branches */ + requiredDeploymentEnvironments?: Maybe>>; + /** List of required status check contexts that must pass for commits to be accepted to matching branches. */ + requiredStatusCheckContexts?: Maybe>>; + /** List of required status checks that must pass for commits to be accepted to matching branches. */ + requiredStatusChecks?: Maybe>; + /** Are approving reviews required to update matching branches. */ + requiresApprovingReviews: Scalars['Boolean']['output']; + /** Are reviews from code owners required to update matching branches. */ + requiresCodeOwnerReviews: Scalars['Boolean']['output']; + /** Are commits required to be signed. */ + requiresCommitSignatures: Scalars['Boolean']['output']; + /** Are conversations required to be resolved before merging. */ + requiresConversationResolution: Scalars['Boolean']['output']; + /** Does this branch require deployment to specific environments before merging */ + requiresDeployments: Scalars['Boolean']['output']; + /** Are merge commits prohibited from being pushed to this branch. */ + requiresLinearHistory: Scalars['Boolean']['output']; + /** Are status checks required to update matching branches. */ + requiresStatusChecks: Scalars['Boolean']['output']; + /** Are branches required to be up to date before merging. */ + requiresStrictStatusChecks: Scalars['Boolean']['output']; + /** Is pushing to matching branches restricted. */ + restrictsPushes: Scalars['Boolean']['output']; + /** Is dismissal of pull request reviews restricted. */ + restrictsReviewDismissals: Scalars['Boolean']['output']; + /** A list review dismissal allowances for this branch protection rule. */ + reviewDismissalAllowances: ReviewDismissalAllowanceConnection; +}; + + +/** A branch protection rule. */ +export type BranchProtectionRuleBranchProtectionRuleConflictsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A branch protection rule. */ +export type BranchProtectionRuleBypassForcePushAllowancesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A branch protection rule. */ +export type BranchProtectionRuleBypassPullRequestAllowancesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A branch protection rule. */ +export type BranchProtectionRuleMatchingRefsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + query?: InputMaybe; +}; + + +/** A branch protection rule. */ +export type BranchProtectionRulePushAllowancesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A branch protection rule. */ +export type BranchProtectionRuleReviewDismissalAllowancesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +/** A conflict between two branch protection rules. */ +export type BranchProtectionRuleConflict = { + __typename?: 'BranchProtectionRuleConflict'; + /** Identifies the branch protection rule. */ + branchProtectionRule?: Maybe; + /** Identifies the conflicting branch protection rule. */ + conflictingBranchProtectionRule?: Maybe; + /** Identifies the branch ref that has conflicting rules */ + ref?: Maybe; +}; + +/** The connection type for BranchProtectionRuleConflict. */ +export type BranchProtectionRuleConflictConnection = { + __typename?: 'BranchProtectionRuleConflictConnection'; + /** A list of edges. */ + edges?: Maybe>>; + /** A list of nodes. */ + nodes?: Maybe>>; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** Identifies the total count of items in the connection. */ + totalCount: Scalars['Int']['output']; +}; + +/** An edge in a connection. */ +export type BranchProtectionRuleConflictEdge = { + __typename?: 'BranchProtectionRuleConflictEdge'; + /** A cursor for use in pagination. */ + cursor: Scalars['String']['output']; + /** The item at the end of the edge. */ + node?: Maybe; +}; + +/** The connection type for BranchProtectionRule. */ +export type BranchProtectionRuleConnection = { + __typename?: 'BranchProtectionRuleConnection'; + /** A list of edges. */ + edges?: Maybe>>; + /** A list of nodes. */ + nodes?: Maybe>>; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** Identifies the total count of items in the connection. */ + totalCount: Scalars['Int']['output']; +}; + +/** An edge in a connection. */ +export type BranchProtectionRuleEdge = { + __typename?: 'BranchProtectionRuleEdge'; + /** A cursor for use in pagination. */ + cursor: Scalars['String']['output']; + /** The item at the end of the edge. */ + node?: Maybe; +}; + +/** + * Information about a sponsorship to make for a user or organization with a GitHub + * Sponsors profile, as part of sponsoring many users or organizations at once. + */ +export type BulkSponsorship = { + /** The amount to pay to the sponsorable in US dollars. Valid values: 1-12000. */ + amount: Scalars['Int']['input']; + /** The ID of the user or organization who is receiving the sponsorship. Required if sponsorableLogin is not given. */ + sponsorableId?: InputMaybe; + /** The username of the user or organization who is receiving the sponsorship. Required if sponsorableId is not given. */ + sponsorableLogin?: InputMaybe; +}; + +/** Types that can represent a repository ruleset bypass actor. */ +export type BypassActor = App | EnterpriseTeam | Team | User; + +/** A user, team, or app who has the ability to bypass a force push requirement on a protected branch. */ +export type BypassForcePushAllowance = Node & { + __typename?: 'BypassForcePushAllowance'; + /** The actor that can force push. */ + actor?: Maybe; + /** Identifies the branch protection rule associated with the allowed user, team, or app. */ + branchProtectionRule?: Maybe; + /** The Node ID of the BypassForcePushAllowance object */ + id: Scalars['ID']['output']; +}; + +/** The connection type for BypassForcePushAllowance. */ +export type BypassForcePushAllowanceConnection = { + __typename?: 'BypassForcePushAllowanceConnection'; + /** A list of edges. */ + edges?: Maybe>>; + /** A list of nodes. */ + nodes?: Maybe>>; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** Identifies the total count of items in the connection. */ + totalCount: Scalars['Int']['output']; +}; + +/** An edge in a connection. */ +export type BypassForcePushAllowanceEdge = { + __typename?: 'BypassForcePushAllowanceEdge'; + /** A cursor for use in pagination. */ + cursor: Scalars['String']['output']; + /** The item at the end of the edge. */ + node?: Maybe; +}; + +/** A user, team, or app who has the ability to bypass a pull request requirement on a protected branch. */ +export type BypassPullRequestAllowance = Node & { + __typename?: 'BypassPullRequestAllowance'; + /** The actor that can bypass. */ + actor?: Maybe; + /** Identifies the branch protection rule associated with the allowed user, team, or app. */ + branchProtectionRule?: Maybe; + /** The Node ID of the BypassPullRequestAllowance object */ + id: Scalars['ID']['output']; +}; + +/** The connection type for BypassPullRequestAllowance. */ +export type BypassPullRequestAllowanceConnection = { + __typename?: 'BypassPullRequestAllowanceConnection'; + /** A list of edges. */ + edges?: Maybe>>; + /** A list of nodes. */ + nodes?: Maybe>>; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** Identifies the total count of items in the connection. */ + totalCount: Scalars['Int']['output']; +}; + +/** An edge in a connection. */ +export type BypassPullRequestAllowanceEdge = { + __typename?: 'BypassPullRequestAllowanceEdge'; + /** A cursor for use in pagination. */ + cursor: Scalars['String']['output']; + /** The item at the end of the edge. */ + node?: Maybe; +}; + +/** The Common Vulnerability Scoring System */ +export type Cvss = { + __typename?: 'CVSS'; + /** The CVSS score associated with this advisory */ + score: Scalars['Float']['output']; + /** The CVSS vector string associated with this advisory */ + vectorString?: Maybe; +}; + +/** A common weakness enumeration */ +export type Cwe = Node & { + __typename?: 'CWE'; + /** The id of the CWE */ + cweId: Scalars['String']['output']; + /** A detailed description of this CWE */ + description: Scalars['String']['output']; + /** The Node ID of the CWE object */ + id: Scalars['ID']['output']; + /** The name of this CWE */ + name: Scalars['String']['output']; +}; + +/** The connection type for CWE. */ +export type CweConnection = { + __typename?: 'CWEConnection'; + /** A list of edges. */ + edges?: Maybe>>; + /** A list of nodes. */ + nodes?: Maybe>>; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** Identifies the total count of items in the connection. */ + totalCount: Scalars['Int']['output']; +}; + +/** An edge in a connection. */ +export type CweEdge = { + __typename?: 'CWEEdge'; + /** A cursor for use in pagination. */ + cursor: Scalars['String']['output']; + /** The item at the end of the edge. */ + node?: Maybe; +}; + +/** Autogenerated input type of CancelEnterpriseAdminInvitation */ +export type CancelEnterpriseAdminInvitationInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The Node ID of the pending enterprise administrator invitation. */ + invitationId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of CancelEnterpriseAdminInvitation. */ +export type CancelEnterpriseAdminInvitationPayload = { + __typename?: 'CancelEnterpriseAdminInvitationPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The invitation that was canceled. */ + invitation?: Maybe; + /** A message confirming the result of canceling an administrator invitation. */ + message?: Maybe; +}; + +/** Autogenerated input type of CancelEnterpriseMemberInvitation */ +export type CancelEnterpriseMemberInvitationInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The Node ID of the pending enterprise member invitation. */ + invitationId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of CancelEnterpriseMemberInvitation. */ +export type CancelEnterpriseMemberInvitationPayload = { + __typename?: 'CancelEnterpriseMemberInvitationPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The invitation that was canceled. */ + invitation?: Maybe; + /** A message confirming the result of canceling an member invitation. */ + message?: Maybe; +}; + +/** Autogenerated input type of CancelSponsorship */ +export type CancelSponsorshipInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** + * The ID of the user or organization who is acting as the sponsor, paying for + * the sponsorship. Required if sponsorLogin is not given. + */ + sponsorId?: InputMaybe; + /** + * The username of the user or organization who is acting as the sponsor, paying + * for the sponsorship. Required if sponsorId is not given. + */ + sponsorLogin?: InputMaybe; + /** The ID of the user or organization who is receiving the sponsorship. Required if sponsorableLogin is not given. */ + sponsorableId?: InputMaybe; + /** The username of the user or organization who is receiving the sponsorship. Required if sponsorableId is not given. */ + sponsorableLogin?: InputMaybe; +}; + +/** Autogenerated return type of CancelSponsorship. */ +export type CancelSponsorshipPayload = { + __typename?: 'CancelSponsorshipPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The tier that was being used at the time of cancellation. */ + sponsorsTier?: Maybe; +}; + +/** Autogenerated input type of ChangeUserStatus */ +export type ChangeUserStatusInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The emoji to represent your status. Can either be a native Unicode emoji or an emoji name with colons, e.g., :grinning:. */ + emoji?: InputMaybe; + /** If set, the user status will not be shown after this date. */ + expiresAt?: InputMaybe; + /** Whether this status should indicate you are not fully available on GitHub, e.g., you are away. */ + limitedAvailability?: InputMaybe; + /** A short description of your current status. */ + message?: InputMaybe; + /** + * The ID of the organization whose members will be allowed to see the status. If + * omitted, the status will be publicly visible. + */ + organizationId?: InputMaybe; +}; + +/** Autogenerated return type of ChangeUserStatus. */ +export type ChangeUserStatusPayload = { + __typename?: 'ChangeUserStatusPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** Your updated status. */ + status?: Maybe; +}; + +/** A single check annotation. */ +export type CheckAnnotation = { + __typename?: 'CheckAnnotation'; + /** The annotation's severity level. */ + annotationLevel?: Maybe; + /** The path to the file that this annotation was made on. */ + blobUrl: Scalars['URI']['output']; + /** + * Identifies the primary key from the database. + * @deprecated `databaseId` will be removed because it does not support 64-bit signed integer identifiers. Use `fullDatabaseId` instead. Removal on 2027-01-01 UTC. + */ + databaseId?: Maybe; + /** Identifies the primary key from the database as a BigInt. */ + fullDatabaseId?: Maybe; + /** The position of this annotation. */ + location: CheckAnnotationSpan; + /** The annotation's message. */ + message: Scalars['String']['output']; + /** The path that this annotation was made on. */ + path: Scalars['String']['output']; + /** Additional information about the annotation. */ + rawDetails?: Maybe; + /** The annotation's title */ + title?: Maybe; +}; + +/** The connection type for CheckAnnotation. */ +export type CheckAnnotationConnection = { + __typename?: 'CheckAnnotationConnection'; + /** A list of edges. */ + edges?: Maybe>>; + /** A list of nodes. */ + nodes?: Maybe>>; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** Identifies the total count of items in the connection. */ + totalCount: Scalars['Int']['output']; +}; + +/** Information from a check run analysis to specific lines of code. */ +export type CheckAnnotationData = { + /** Represents an annotation's information level */ + annotationLevel: CheckAnnotationLevel; + /** The location of the annotation */ + location: CheckAnnotationRange; + /** A short description of the feedback for these lines of code. */ + message: Scalars['String']['input']; + /** The path of the file to add an annotation to. */ + path: Scalars['String']['input']; + /** Details about this annotation. */ + rawDetails?: InputMaybe; + /** The title that represents the annotation. */ + title?: InputMaybe; +}; + +/** An edge in a connection. */ +export type CheckAnnotationEdge = { + __typename?: 'CheckAnnotationEdge'; + /** A cursor for use in pagination. */ + cursor: Scalars['String']['output']; + /** The item at the end of the edge. */ + node?: Maybe; +}; + +/** Represents an annotation's information level. */ +export type CheckAnnotationLevel = + /** An annotation indicating an inescapable error. */ + | 'FAILURE' + /** An annotation indicating some information. */ + | 'NOTICE' + /** An annotation indicating an ignorable error. */ + | 'WARNING'; + +/** A character position in a check annotation. */ +export type CheckAnnotationPosition = { + __typename?: 'CheckAnnotationPosition'; + /** Column number (1 indexed). */ + column?: Maybe; + /** Line number (1 indexed). */ + line: Scalars['Int']['output']; +}; + +/** Information from a check run analysis to specific lines of code. */ +export type CheckAnnotationRange = { + /** The ending column of the range. */ + endColumn?: InputMaybe; + /** The ending line of the range. */ + endLine: Scalars['Int']['input']; + /** The starting column of the range. */ + startColumn?: InputMaybe; + /** The starting line of the range. */ + startLine: Scalars['Int']['input']; +}; + +/** An inclusive pair of positions for a check annotation. */ +export type CheckAnnotationSpan = { + __typename?: 'CheckAnnotationSpan'; + /** End position (inclusive). */ + end: CheckAnnotationPosition; + /** Start position (inclusive). */ + start: CheckAnnotationPosition; +}; + +/** The possible states for a check suite or run conclusion. */ +export type CheckConclusionState = + /** The check suite or run requires action. */ + | 'ACTION_REQUIRED' + /** The check suite or run has been cancelled. */ + | 'CANCELLED' + /** The check suite or run has failed. */ + | 'FAILURE' + /** The check suite or run was neutral. */ + | 'NEUTRAL' + /** The check suite or run was skipped. */ + | 'SKIPPED' + /** The check suite or run was marked stale by GitHub. Only GitHub can use this conclusion. */ + | 'STALE' + /** The check suite or run has failed at startup. */ + | 'STARTUP_FAILURE' + /** The check suite or run has succeeded. */ + | 'SUCCESS' + /** The check suite or run has timed out. */ + | 'TIMED_OUT'; + +/** A check run. */ +export type CheckRun = Node & RequirableByPullRequest & UniformResourceLocatable & { + __typename?: 'CheckRun'; + /** The check run's annotations */ + annotations?: Maybe; + /** The check suite that this run is a part of. */ + checkSuite: CheckSuite; + /** Identifies the date and time when the check run was completed. */ + completedAt?: Maybe; + /** The conclusion of the check run. */ + conclusion?: Maybe; + /** Identifies the primary key from the database. */ + databaseId?: Maybe; + /** The corresponding deployment for this job, if any */ + deployment?: Maybe; + /** The URL from which to find full details of the check run on the integrator's site. */ + detailsUrl?: Maybe; + /** A reference for the check run on the integrator's system. */ + externalId?: Maybe; + /** The Node ID of the CheckRun object */ + id: Scalars['ID']['output']; + /** Whether this is required to pass before merging for a specific pull request. */ + isRequired: Scalars['Boolean']['output']; + /** The name of the check for this check run. */ + name: Scalars['String']['output']; + /** Information about a pending deployment, if any, in this check run */ + pendingDeploymentRequest?: Maybe; + /** The permalink to the check run summary. */ + permalink: Scalars['URI']['output']; + /** The repository associated with this check run. */ + repository: Repository; + /** The HTTP path for this check run. */ + resourcePath: Scalars['URI']['output']; + /** Identifies the date and time when the check run was started. */ + startedAt?: Maybe; + /** The current status of the check run. */ + status: CheckStatusState; + /** The check run's steps */ + steps?: Maybe; + /** A string representing the check run's summary */ + summary?: Maybe; + /** A string representing the check run's text */ + text?: Maybe; + /** A string representing the check run */ + title?: Maybe; + /** The HTTP URL for this check run. */ + url: Scalars['URI']['output']; +}; + + +/** A check run. */ +export type CheckRunAnnotationsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A check run. */ +export type CheckRunIsRequiredArgs = { + pullRequestId?: InputMaybe; + pullRequestNumber?: InputMaybe; +}; + + +/** A check run. */ +export type CheckRunStepsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + number?: InputMaybe; +}; + +/** Possible further actions the integrator can perform. */ +export type CheckRunAction = { + /** A short explanation of what this action would do. */ + description: Scalars['String']['input']; + /** A reference for the action on the integrator's system. */ + identifier: Scalars['String']['input']; + /** The text to be displayed on a button in the web UI. */ + label: Scalars['String']['input']; +}; + +/** The connection type for CheckRun. */ +export type CheckRunConnection = { + __typename?: 'CheckRunConnection'; + /** A list of edges. */ + edges?: Maybe>>; + /** A list of nodes. */ + nodes?: Maybe>>; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** Identifies the total count of items in the connection. */ + totalCount: Scalars['Int']['output']; +}; + +/** An edge in a connection. */ +export type CheckRunEdge = { + __typename?: 'CheckRunEdge'; + /** A cursor for use in pagination. */ + cursor: Scalars['String']['output']; + /** The item at the end of the edge. */ + node?: Maybe; +}; + +/** The filters that are available when fetching check runs. */ +export type CheckRunFilter = { + /** Filters the check runs created by this application ID. */ + appId?: InputMaybe; + /** Filters the check runs by this name. */ + checkName?: InputMaybe; + /** Filters the check runs by this type. */ + checkType?: InputMaybe; + /** Filters the check runs by these conclusions. */ + conclusions?: InputMaybe>; + /** Filters the check runs by this status. Superceded by statuses. */ + status?: InputMaybe; + /** Filters the check runs by this status. Overrides status. */ + statuses?: InputMaybe>; +}; + +/** Descriptive details about the check run. */ +export type CheckRunOutput = { + /** The annotations that are made as part of the check run. */ + annotations?: InputMaybe>; + /** Images attached to the check run output displayed in the GitHub pull request UI. */ + images?: InputMaybe>; + /** The summary of the check run (supports Commonmark). */ + summary: Scalars['String']['input']; + /** The details of the check run (supports Commonmark). */ + text?: InputMaybe; + /** A title to provide for this check run. */ + title: Scalars['String']['input']; +}; + +/** Images attached to the check run output displayed in the GitHub pull request UI. */ +export type CheckRunOutputImage = { + /** The alternative text for the image. */ + alt: Scalars['String']['input']; + /** A short image description. */ + caption?: InputMaybe; + /** The full URL of the image. */ + imageUrl: Scalars['URI']['input']; +}; + +/** The possible states of a check run in a status rollup. */ +export type CheckRunState = + /** The check run requires action. */ + | 'ACTION_REQUIRED' + /** The check run has been cancelled. */ + | 'CANCELLED' + /** The check run has been completed. */ + | 'COMPLETED' + /** The check run has failed. */ + | 'FAILURE' + /** The check run is in progress. */ + | 'IN_PROGRESS' + /** The check run was neutral. */ + | 'NEUTRAL' + /** The check run is in pending state. */ + | 'PENDING' + /** The check run has been queued. */ + | 'QUEUED' + /** The check run was skipped. */ + | 'SKIPPED' + /** The check run was marked stale by GitHub. Only GitHub can use this conclusion. */ + | 'STALE' + /** The check run has failed at startup. */ + | 'STARTUP_FAILURE' + /** The check run has succeeded. */ + | 'SUCCESS' + /** The check run has timed out. */ + | 'TIMED_OUT' + /** The check run is in waiting state. */ + | 'WAITING'; + +/** Represents a count of the state of a check run. */ +export type CheckRunStateCount = { + __typename?: 'CheckRunStateCount'; + /** The number of check runs with this state. */ + count: Scalars['Int']['output']; + /** The state of a check run. */ + state: CheckRunState; +}; + +/** The possible types of check runs. */ +export type CheckRunType = + /** Every check run available. */ + | 'ALL' + /** The latest check run. */ + | 'LATEST'; + +/** The possible states for a check suite or run status. */ +export type CheckStatusState = + /** The check suite or run has been completed. */ + | 'COMPLETED' + /** The check suite or run is in progress. */ + | 'IN_PROGRESS' + /** The check suite or run is in pending state. */ + | 'PENDING' + /** The check suite or run has been queued. */ + | 'QUEUED' + /** The check suite or run has been requested. */ + | 'REQUESTED' + /** The check suite or run is in waiting state. */ + | 'WAITING'; + +/** A single check step. */ +export type CheckStep = { + __typename?: 'CheckStep'; + /** Identifies the date and time when the check step was completed. */ + completedAt?: Maybe; + /** The conclusion of the check step. */ + conclusion?: Maybe; + /** A reference for the check step on the integrator's system. */ + externalId?: Maybe; + /** The step's name. */ + name: Scalars['String']['output']; + /** The index of the step in the list of steps of the parent check run. */ + number: Scalars['Int']['output']; + /** Number of seconds to completion. */ + secondsToCompletion?: Maybe; + /** Identifies the date and time when the check step was started. */ + startedAt?: Maybe; + /** The current status of the check step. */ + status: CheckStatusState; +}; + +/** The connection type for CheckStep. */ +export type CheckStepConnection = { + __typename?: 'CheckStepConnection'; + /** A list of edges. */ + edges?: Maybe>>; + /** A list of nodes. */ + nodes?: Maybe>>; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** Identifies the total count of items in the connection. */ + totalCount: Scalars['Int']['output']; +}; + +/** An edge in a connection. */ +export type CheckStepEdge = { + __typename?: 'CheckStepEdge'; + /** A cursor for use in pagination. */ + cursor: Scalars['String']['output']; + /** The item at the end of the edge. */ + node?: Maybe; +}; + +/** A check suite. */ +export type CheckSuite = Node & { + __typename?: 'CheckSuite'; + /** The GitHub App which created this check suite. */ + app?: Maybe; + /** The name of the branch for this check suite. */ + branch?: Maybe; + /** The check runs associated with a check suite. */ + checkRuns?: Maybe; + /** The commit for this check suite */ + commit: Commit; + /** The conclusion of this check suite. */ + conclusion?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** The user who triggered the check suite. */ + creator?: Maybe; + /** Identifies the primary key from the database. */ + databaseId?: Maybe; + /** The Node ID of the CheckSuite object */ + id: Scalars['ID']['output']; + /** A list of open pull requests matching the check suite. */ + matchingPullRequests?: Maybe; + /** The push that triggered this check suite. */ + push?: Maybe; + /** The repository associated with this check suite. */ + repository: Repository; + /** The HTTP path for this check suite */ + resourcePath: Scalars['URI']['output']; + /** The status of this check suite. */ + status: CheckStatusState; + /** Identifies the date and time when the object was last updated. */ + updatedAt: Scalars['DateTime']['output']; + /** The HTTP URL for this check suite */ + url: Scalars['URI']['output']; + /** The workflow run associated with this check suite. */ + workflowRun?: Maybe; +}; + + +/** A check suite. */ +export type CheckSuiteCheckRunsArgs = { + after?: InputMaybe; + before?: InputMaybe; + filterBy?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A check suite. */ +export type CheckSuiteMatchingPullRequestsArgs = { + after?: InputMaybe; + baseRefName?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + headRefName?: InputMaybe; + labels?: InputMaybe>; + last?: InputMaybe; + orderBy?: InputMaybe; + states?: InputMaybe>; +}; + +/** The auto-trigger preferences that are available for check suites. */ +export type CheckSuiteAutoTriggerPreference = { + /** The node ID of the application that owns the check suite. */ + appId: Scalars['ID']['input']; + /** Set to `true` to enable automatic creation of CheckSuite events upon pushes to the repository. */ + setting: Scalars['Boolean']['input']; +}; + +/** The connection type for CheckSuite. */ +export type CheckSuiteConnection = { + __typename?: 'CheckSuiteConnection'; + /** A list of edges. */ + edges?: Maybe>>; + /** A list of nodes. */ + nodes?: Maybe>>; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** Identifies the total count of items in the connection. */ + totalCount: Scalars['Int']['output']; +}; + +/** An edge in a connection. */ +export type CheckSuiteEdge = { + __typename?: 'CheckSuiteEdge'; + /** A cursor for use in pagination. */ + cursor: Scalars['String']['output']; + /** The item at the end of the edge. */ + node?: Maybe; +}; + +/** The filters that are available when fetching check suites. */ +export type CheckSuiteFilter = { + /** Filters the check suites created by this application ID. */ + appId?: InputMaybe; + /** Filters the check suites by this name. */ + checkName?: InputMaybe; +}; + +/** An object which can have its data claimed or claim data from another. */ +export type Claimable = Mannequin | User; + +/** Autogenerated input type of ClearLabelsFromLabelable */ +export type ClearLabelsFromLabelableInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The id of the labelable object to clear the labels from. */ + labelableId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of ClearLabelsFromLabelable. */ +export type ClearLabelsFromLabelablePayload = { + __typename?: 'ClearLabelsFromLabelablePayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The item that was unlabeled. */ + labelable?: Maybe; +}; + +/** Autogenerated input type of ClearProjectV2ItemFieldValue */ +export type ClearProjectV2ItemFieldValueInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The ID of the field to be cleared. */ + fieldId: Scalars['ID']['input']; + /** The ID of the item to be cleared. */ + itemId: Scalars['ID']['input']; + /** The ID of the Project. */ + projectId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of ClearProjectV2ItemFieldValue. */ +export type ClearProjectV2ItemFieldValuePayload = { + __typename?: 'ClearProjectV2ItemFieldValuePayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The updated item. */ + projectV2Item?: Maybe; +}; + +/** Autogenerated input type of CloneProject */ +export type CloneProjectInput = { + /** The description of the project. */ + body?: InputMaybe; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** Whether or not to clone the source project's workflows. */ + includeWorkflows: Scalars['Boolean']['input']; + /** The name of the project. */ + name: Scalars['String']['input']; + /** The visibility of the project, defaults to false (private). */ + public?: InputMaybe; + /** The source project to clone. */ + sourceId: Scalars['ID']['input']; + /** The owner ID to create the project under. */ + targetOwnerId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of CloneProject. */ +export type CloneProjectPayload = { + __typename?: 'CloneProjectPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The id of the JobStatus for populating cloned fields. */ + jobStatusId?: Maybe; + /** The new cloned project. */ + project?: Maybe; +}; + +/** Autogenerated input type of CloneTemplateRepository */ +export type CloneTemplateRepositoryInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** A short description of the new repository. */ + description?: InputMaybe; + /** + * Whether to copy all branches from the template to the new repository. Defaults + * to copying only the default branch of the template. + */ + includeAllBranches?: InputMaybe; + /** The name of the new repository. */ + name: Scalars['String']['input']; + /** The ID of the owner for the new repository. */ + ownerId: Scalars['ID']['input']; + /** The Node ID of the template repository. */ + repositoryId: Scalars['ID']['input']; + /** Indicates the repository's visibility level. */ + visibility: RepositoryVisibility; +}; + +/** Autogenerated return type of CloneTemplateRepository. */ +export type CloneTemplateRepositoryPayload = { + __typename?: 'CloneTemplateRepositoryPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The new repository. */ + repository?: Maybe; +}; + +/** An object that can be closed */ +export type Closable = { + /** Indicates if the object is closed (definition of closed may depend on type) */ + closed: Scalars['Boolean']['output']; + /** Identifies the date and time when the object was closed. */ + closedAt?: Maybe; + /** Indicates if the object can be closed by the viewer. */ + viewerCanClose: Scalars['Boolean']['output']; + /** Indicates if the object can be reopened by the viewer. */ + viewerCanReopen: Scalars['Boolean']['output']; +}; + +/** Autogenerated input type of CloseDiscussion */ +export type CloseDiscussionInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** ID of the discussion to be closed. */ + discussionId: Scalars['ID']['input']; + /** The reason why the discussion is being closed. */ + reason?: InputMaybe; +}; + +/** Autogenerated return type of CloseDiscussion. */ +export type CloseDiscussionPayload = { + __typename?: 'CloseDiscussionPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The discussion that was closed. */ + discussion?: Maybe; +}; + +/** Autogenerated input type of CloseIssue */ +export type CloseIssueInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The confidence level the agent had when suggesting this close. Only meaningful when isSuggestion is true. */ + confidence?: InputMaybe; + /** ID of the issue that this is a duplicate of. */ + duplicateIssueId?: InputMaybe; + /** If true, the close action is stored as a pending suggestion for human review rather than being applied directly. */ + isSuggestion?: InputMaybe; + /** ID of the issue to be closed. */ + issueId: Scalars['ID']['input']; + /** Optional rationale describing why the issue should be closed. Max 280 characters. */ + rationale?: InputMaybe; + /** The reason the issue is to be closed. */ + stateReason?: InputMaybe; +}; + +/** Autogenerated return type of CloseIssue. */ +export type CloseIssuePayload = { + __typename?: 'CloseIssuePayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The issue that was closed. */ + issue?: Maybe; +}; + +/** Autogenerated input type of ClosePullRequest */ +export type ClosePullRequestInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** ID of the pull request to be closed. */ + pullRequestId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of ClosePullRequest. */ +export type ClosePullRequestPayload = { + __typename?: 'ClosePullRequestPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The pull request that was closed. */ + pullRequest?: Maybe; +}; + +/** Represents a 'closed' event on any `Closable`. */ +export type ClosedEvent = Node & UniformResourceLocatable & { + __typename?: 'ClosedEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** Object that was closed. */ + closable: Closable; + /** Object which triggered the creation of this event. */ + closer?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** The issue or pull request that this issue was marked as a duplicate of. */ + duplicateOf?: Maybe; + /** The Node ID of the ClosedEvent object */ + id: Scalars['ID']['output']; + /** The HTTP path for this closed event. */ + resourcePath: Scalars['URI']['output']; + /** The reason the issue state was changed to closed. */ + stateReason?: Maybe; + /** The HTTP URL for this closed event. */ + url: Scalars['URI']['output']; +}; + +/** The object which triggered a `ClosedEvent`. */ +export type Closer = Commit | ProjectV2 | PullRequest; + +/** The Code of Conduct for a repository */ +export type CodeOfConduct = Node & { + __typename?: 'CodeOfConduct'; + /** The body of the Code of Conduct */ + body?: Maybe; + /** The Node ID of the CodeOfConduct object */ + id: Scalars['ID']['output']; + /** The key for the Code of Conduct */ + key: Scalars['String']['output']; + /** The formal name of the Code of Conduct */ + name: Scalars['String']['output']; + /** The HTTP path for this Code of Conduct */ + resourcePath?: Maybe; + /** The HTTP URL for this Code of Conduct */ + url?: Maybe; +}; + +/** + * Choose which tools must provide code scanning results before the reference is + * updated. When configured, code scanning must be enabled and have results for + * both the commit and the reference being updated. + */ +export type CodeScanningParameters = { + __typename?: 'CodeScanningParameters'; + /** Tools that must provide code scanning results for this rule to pass. */ + codeScanningTools: Array; +}; + +/** + * Choose which tools must provide code scanning results before the reference is + * updated. When configured, code scanning must be enabled and have results for + * both the commit and the reference being updated. + */ +export type CodeScanningParametersInput = { + /** Tools that must provide code scanning results for this rule to pass. */ + codeScanningTools: Array; +}; + +/** A tool that must provide code scanning results for this rule to pass. */ +export type CodeScanningTool = { + __typename?: 'CodeScanningTool'; + /** + * The severity level at which code scanning results that raise alerts block a + * reference update. For more information on alert severity levels, see "[About code scanning alerts](${externalDocsUrl}/code-security/code-scanning/managing-code-scanning-alerts/about-code-scanning-alerts#about-alert-severity-and-security-severity-levels)." + */ + alertsThreshold: Scalars['String']['output']; + /** + * The severity level at which code scanning results that raise security alerts + * block a reference update. For more information on security severity levels, + * see "[About code scanning alerts](${externalDocsUrl}/code-security/code-scanning/managing-code-scanning-alerts/about-code-scanning-alerts#about-alert-severity-and-security-severity-levels)." + */ + securityAlertsThreshold: Scalars['String']['output']; + /** The name of a code scanning tool */ + tool: Scalars['String']['output']; +}; + +/** A tool that must provide code scanning results for this rule to pass. */ +export type CodeScanningToolInput = { + /** + * The severity level at which code scanning results that raise alerts block a + * reference update. For more information on alert severity levels, see "[About code scanning alerts](${externalDocsUrl}/code-security/code-scanning/managing-code-scanning-alerts/about-code-scanning-alerts#about-alert-severity-and-security-severity-levels)." + */ + alertsThreshold: Scalars['String']['input']; + /** + * The severity level at which code scanning results that raise security alerts + * block a reference update. For more information on security severity levels, + * see "[About code scanning alerts](${externalDocsUrl}/code-security/code-scanning/managing-code-scanning-alerts/about-code-scanning-alerts#about-alert-severity-and-security-severity-levels)." + */ + securityAlertsThreshold: Scalars['String']['input']; + /** The name of a code scanning tool */ + tool: Scalars['String']['input']; +}; + +/** Collaborators affiliation level with a subject. */ +export type CollaboratorAffiliation = + /** All collaborators the authenticated user can see. */ + | 'ALL' + /** All collaborators with permissions to an organization-owned subject, regardless of organization membership status. */ + | 'DIRECT' + /** All outside collaborators of an organization-owned subject. */ + | 'OUTSIDE'; + +/** Represents a comment. */ +export type Comment = { + /** The actor who authored the comment. */ + author?: Maybe; + /** Author's association with the subject of the comment. */ + authorAssociation: CommentAuthorAssociation; + /** The body as Markdown. */ + body: Scalars['String']['output']; + /** The body rendered to HTML. */ + bodyHTML: Scalars['HTML']['output']; + /** The body rendered to text. */ + bodyText: Scalars['String']['output']; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** Check if this comment was created via an email reply. */ + createdViaEmail: Scalars['Boolean']['output']; + /** The actor who edited the comment. */ + editor?: Maybe; + /** The Node ID of the Comment object */ + id: Scalars['ID']['output']; + /** Check if this comment was edited and includes an edit with the creation data */ + includesCreatedEdit: Scalars['Boolean']['output']; + /** The moment the editor made the last edit */ + lastEditedAt?: Maybe; + /** Identifies when the comment was published at. */ + publishedAt?: Maybe; + /** Identifies the date and time when the object was last updated. */ + updatedAt: Scalars['DateTime']['output']; + /** A list of edits to this content. */ + userContentEdits?: Maybe; + /** Did the viewer author this comment. */ + viewerDidAuthor: Scalars['Boolean']['output']; +}; + + +/** Represents a comment. */ +export type CommentUserContentEditsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +/** A comment author association with repository. */ +export type CommentAuthorAssociation = + /** Author has been invited to collaborate on the repository. */ + | 'COLLABORATOR' + /** Author has previously committed to the repository. */ + | 'CONTRIBUTOR' + /** Author has not previously committed to GitHub. */ + | 'FIRST_TIMER' + /** Author has not previously committed to the repository. */ + | 'FIRST_TIME_CONTRIBUTOR' + /** Author is a placeholder for an unclaimed user. */ + | 'MANNEQUIN' + /** Author is a member of the organization that owns the repository. */ + | 'MEMBER' + /** Author has no association with the repository. */ + | 'NONE' + /** Author is the owner of the repository. */ + | 'OWNER'; + +/** The possible errors that will prevent a user from updating a comment. */ +export type CommentCannotUpdateReason = + /** Unable to create comment because repository is archived. */ + | 'ARCHIVED' + /** You cannot update this comment */ + | 'DENIED' + /** You must be the author or have write access to this repository to update this comment. */ + | 'INSUFFICIENT_ACCESS' + /** Unable to create comment because issue is locked. */ + | 'LOCKED' + /** You must be logged in to update this comment. */ + | 'LOGIN_REQUIRED' + /** Repository is under maintenance. */ + | 'MAINTENANCE' + /** At least one email address must be verified to update this comment. */ + | 'VERIFIED_EMAIL_REQUIRED'; + +/** Represents a 'comment_deleted' event on a given issue or pull request. */ +export type CommentDeletedEvent = Node & { + __typename?: 'CommentDeletedEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** Identifies the primary key from the database. */ + databaseId?: Maybe; + /** The user who authored the deleted comment. */ + deletedCommentAuthor?: Maybe; + /** The Node ID of the CommentDeletedEvent object */ + id: Scalars['ID']['output']; +}; + +/** Represents a Git commit. */ +export type Commit = GitObject & Node & Subscribable & UniformResourceLocatable & { + __typename?: 'Commit'; + /** An abbreviated version of the Git object ID */ + abbreviatedOid: Scalars['String']['output']; + /** The number of additions in this commit. */ + additions: Scalars['Int']['output']; + /** + * The merged Pull Request that introduced the commit to the repository. If the + * commit is not present in the default branch, additionally returns open Pull + * Requests associated with the commit + */ + associatedPullRequests?: Maybe; + /** Authorship details of the commit. */ + author?: Maybe; + /** Check if the committer and the author match. */ + authoredByCommitter: Scalars['Boolean']['output']; + /** The datetime when this commit was authored. */ + authoredDate: Scalars['DateTime']['output']; + /** + * The list of authors for this commit based on the git author and the Co-authored-by + * message trailer. The git author will always be first. + */ + authors: GitActorConnection; + /** Fetches `git blame` information. */ + blame: Blame; + /** + * We recommend using the `changedFilesIfAvailable` field instead of + * `changedFiles`, as `changedFiles` will cause your request to return an error + * if GitHub is unable to calculate the number of changed files. + * @deprecated `changedFiles` will be removed. Use `changedFilesIfAvailable` instead. Removal on 2023-01-01 UTC. + */ + changedFiles: Scalars['Int']['output']; + /** + * The number of changed files in this commit. If GitHub is unable to calculate + * the number of changed files (for example due to a timeout), this will return + * `null`. We recommend using this field instead of `changedFiles`. + */ + changedFilesIfAvailable?: Maybe; + /** The check suites associated with a commit. */ + checkSuites?: Maybe; + /** Comments made on the commit. */ + comments: CommitCommentConnection; + /** The HTTP path for this Git object */ + commitResourcePath: Scalars['URI']['output']; + /** The HTTP URL for this Git object */ + commitUrl: Scalars['URI']['output']; + /** The datetime when this commit was committed. */ + committedDate: Scalars['DateTime']['output']; + /** Check if committed via GitHub web UI. */ + committedViaWeb: Scalars['Boolean']['output']; + /** Committer details of the commit. */ + committer?: Maybe; + /** The number of deletions in this commit. */ + deletions: Scalars['Int']['output']; + /** The deployments associated with a commit. */ + deployments?: Maybe; + /** The tree entry representing the file located at the given path. */ + file?: Maybe; + /** The linear commit history starting from (and including) this commit, in the same order as `git log`. */ + history: CommitHistoryConnection; + /** The Node ID of the Commit object */ + id: Scalars['ID']['output']; + /** The Git commit message */ + message: Scalars['String']['output']; + /** The Git commit message body */ + messageBody: Scalars['String']['output']; + /** The commit message body rendered to HTML. */ + messageBodyHTML: Scalars['HTML']['output']; + /** The Git commit message headline */ + messageHeadline: Scalars['String']['output']; + /** The commit message headline rendered to HTML. */ + messageHeadlineHTML: Scalars['HTML']['output']; + /** The Git object ID */ + oid: Scalars['GitObjectID']['output']; + /** The organization this commit was made on behalf of. */ + onBehalfOf?: Maybe; + /** The parents of a commit. */ + parents: CommitConnection; + /** + * The datetime when this commit was pushed. + * @deprecated `pushedDate` is no longer supported. Removal on 2023-07-01 UTC. + */ + pushedDate?: Maybe; + /** The Repository this commit belongs to */ + repository: Repository; + /** The HTTP path for this commit */ + resourcePath: Scalars['URI']['output']; + /** Commit signing information, if present. */ + signature?: Maybe; + /** Status information for this commit */ + status?: Maybe; + /** Check and Status rollup information for this commit. */ + statusCheckRollup?: Maybe; + /** Returns a list of all submodules in this repository as of this Commit parsed from the .gitmodules file. */ + submodules: SubmoduleConnection; + /** + * Returns a URL to download a tarball archive for a repository. + * Note: For private repositories, these links are temporary and expire after five minutes. + */ + tarballUrl: Scalars['URI']['output']; + /** Commit's root Tree */ + tree: Tree; + /** The HTTP path for the tree of this commit */ + treeResourcePath: Scalars['URI']['output']; + /** The HTTP URL for the tree of this commit */ + treeUrl: Scalars['URI']['output']; + /** The HTTP URL for this commit */ + url: Scalars['URI']['output']; + /** Check if the viewer is able to change their subscription status for the repository. */ + viewerCanSubscribe: Scalars['Boolean']['output']; + /** Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. */ + viewerSubscription?: Maybe; + /** + * Returns a URL to download a zipball archive for a repository. + * Note: For private repositories, these links are temporary and expire after five minutes. + */ + zipballUrl: Scalars['URI']['output']; +}; + + +/** Represents a Git commit. */ +export type CommitAssociatedPullRequestsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + orderBy?: InputMaybe; +}; + + +/** Represents a Git commit. */ +export type CommitAuthorsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Represents a Git commit. */ +export type CommitBlameArgs = { + path: Scalars['String']['input']; +}; + + +/** Represents a Git commit. */ +export type CommitCheckSuitesArgs = { + after?: InputMaybe; + before?: InputMaybe; + filterBy?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Represents a Git commit. */ +export type CommitCommentsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Represents a Git commit. */ +export type CommitDeploymentsArgs = { + after?: InputMaybe; + before?: InputMaybe; + environments?: InputMaybe>; + first?: InputMaybe; + last?: InputMaybe; + orderBy?: InputMaybe; +}; + + +/** Represents a Git commit. */ +export type CommitFileArgs = { + path: Scalars['String']['input']; +}; + + +/** Represents a Git commit. */ +export type CommitHistoryArgs = { + after?: InputMaybe; + author?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + path?: InputMaybe; + since?: InputMaybe; + until?: InputMaybe; +}; + + +/** Represents a Git commit. */ +export type CommitParentsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Represents a Git commit. */ +export type CommitSubmodulesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +/** Specifies an author for filtering Git commits. */ +export type CommitAuthor = { + /** Email addresses to filter by. Commits authored by any of the specified email addresses will be returned. */ + emails?: InputMaybe>; + /** + * ID of a User to filter by. If non-null, only commits authored by this user + * will be returned. This field takes precedence over emails. + */ + id?: InputMaybe; +}; + +/** Parameters to be used for the commit_author_email_pattern rule */ +export type CommitAuthorEmailPatternParameters = { + __typename?: 'CommitAuthorEmailPatternParameters'; + /** How this rule appears when configuring it. */ + name?: Maybe; + /** If true, the rule will fail if the pattern matches. */ + negate: Scalars['Boolean']['output']; + /** The operator to use for matching. */ + operator: Scalars['String']['output']; + /** The pattern to match with. */ + pattern: Scalars['String']['output']; +}; + +/** Parameters to be used for the commit_author_email_pattern rule */ +export type CommitAuthorEmailPatternParametersInput = { + /** How this rule appears when configuring it. */ + name?: InputMaybe; + /** If true, the rule will fail if the pattern matches. */ + negate?: InputMaybe; + /** The operator to use for matching. */ + operator: Scalars['String']['input']; + /** The pattern to match with. */ + pattern: Scalars['String']['input']; +}; + +/** Represents a comment on a given Commit. */ +export type CommitComment = Comment & Deletable & Minimizable & Node & Reactable & RepositoryNode & Updatable & UpdatableComment & { + __typename?: 'CommitComment'; + /** The actor who authored the comment. */ + author?: Maybe; + /** Author's association with the subject of the comment. */ + authorAssociation: CommentAuthorAssociation; + /** Identifies the comment body. */ + body: Scalars['String']['output']; + /** The body rendered to HTML. */ + bodyHTML: Scalars['HTML']['output']; + /** The body rendered to text. */ + bodyText: Scalars['String']['output']; + /** Identifies the commit associated with the comment, if the commit exists. */ + commit?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** Check if this comment was created via an email reply. */ + createdViaEmail: Scalars['Boolean']['output']; + /** Identifies the primary key from the database. */ + databaseId?: Maybe; + /** The actor who edited the comment. */ + editor?: Maybe; + /** The Node ID of the CommitComment object */ + id: Scalars['ID']['output']; + /** Check if this comment was edited and includes an edit with the creation data */ + includesCreatedEdit: Scalars['Boolean']['output']; + /** Returns whether or not a comment has been minimized. */ + isMinimized: Scalars['Boolean']['output']; + /** The moment the editor made the last edit */ + lastEditedAt?: Maybe; + /** + * Returns why the comment was minimized. One of `abuse`, `off-topic`, + * `outdated`, `resolved`, `duplicate`, `spam`, and `low-quality`. Note that the + * case and formatting of these values differs from the inputs to the + * `MinimizeComment` mutation. + */ + minimizedReason?: Maybe; + /** Identifies the file path associated with the comment. */ + path?: Maybe; + /** Identifies the line position associated with the comment. */ + position?: Maybe; + /** Identifies when the comment was published at. */ + publishedAt?: Maybe; + /** A list of reactions grouped by content left on the subject. */ + reactionGroups?: Maybe>; + /** A list of Reactions left on the Issue. */ + reactions: ReactionConnection; + /** The repository associated with this node. */ + repository: Repository; + /** The HTTP path permalink for this commit comment. */ + resourcePath: Scalars['URI']['output']; + /** Identifies the date and time when the object was last updated. */ + updatedAt: Scalars['DateTime']['output']; + /** The HTTP URL permalink for this commit comment. */ + url: Scalars['URI']['output']; + /** A list of edits to this content. */ + userContentEdits?: Maybe; + /** Check if the current viewer can delete this object. */ + viewerCanDelete: Scalars['Boolean']['output']; + /** Check if the current viewer can minimize this object. */ + viewerCanMinimize: Scalars['Boolean']['output']; + /** Can user react to this subject */ + viewerCanReact: Scalars['Boolean']['output']; + /** Check if the current viewer can unminimize this object. */ + viewerCanUnminimize: Scalars['Boolean']['output']; + /** Check if the current viewer can update this object. */ + viewerCanUpdate: Scalars['Boolean']['output']; + /** Reasons why the current viewer can not update this comment. */ + viewerCannotUpdateReasons: Array; + /** Did the viewer author this comment. */ + viewerDidAuthor: Scalars['Boolean']['output']; +}; + + +/** Represents a comment on a given Commit. */ +export type CommitCommentReactionsArgs = { + after?: InputMaybe; + before?: InputMaybe; + content?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + orderBy?: InputMaybe; +}; + + +/** Represents a comment on a given Commit. */ +export type CommitCommentUserContentEditsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +/** The connection type for CommitComment. */ +export type CommitCommentConnection = { + __typename?: 'CommitCommentConnection'; + /** A list of edges. */ + edges?: Maybe>>; + /** A list of nodes. */ + nodes?: Maybe>>; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** Identifies the total count of items in the connection. */ + totalCount: Scalars['Int']['output']; +}; + +/** An edge in a connection. */ +export type CommitCommentEdge = { + __typename?: 'CommitCommentEdge'; + /** A cursor for use in pagination. */ + cursor: Scalars['String']['output']; + /** The item at the end of the edge. */ + node?: Maybe; +}; + +/** A thread of comments on a commit. */ +export type CommitCommentThread = Node & RepositoryNode & { + __typename?: 'CommitCommentThread'; + /** The comments that exist in this thread. */ + comments: CommitCommentConnection; + /** The commit the comments were made on. */ + commit?: Maybe; + /** The Node ID of the CommitCommentThread object */ + id: Scalars['ID']['output']; + /** The file the comments were made on. */ + path?: Maybe; + /** The position in the diff for the commit that the comment was made on. */ + position?: Maybe; + /** The repository associated with this node. */ + repository: Repository; +}; + + +/** A thread of comments on a commit. */ +export type CommitCommentThreadCommentsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +/** The connection type for Commit. */ +export type CommitConnection = { + __typename?: 'CommitConnection'; + /** A list of edges. */ + edges?: Maybe>>; + /** A list of nodes. */ + nodes?: Maybe>>; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** Identifies the total count of items in the connection. */ + totalCount: Scalars['Int']['output']; +}; + +/** Ordering options for commit contribution connections. */ +export type CommitContributionOrder = { + /** The ordering direction. */ + direction: OrderDirection; + /** The field by which to order commit contributions. */ + field: CommitContributionOrderField; +}; + +/** Properties by which commit contribution connections can be ordered. */ +export type CommitContributionOrderField = + /** Order commit contributions by how many commits they represent. */ + | 'COMMIT_COUNT' + /** Order commit contributions by when they were made. */ + | 'OCCURRED_AT'; + +/** This aggregates commits made by a user within one repository. */ +export type CommitContributionsByRepository = { + __typename?: 'CommitContributionsByRepository'; + /** The commit contributions, each representing a day. */ + contributions: CreatedCommitContributionConnection; + /** The repository in which the commits were made. */ + repository: Repository; + /** The HTTP path for the user's commits to the repository in this time range. */ + resourcePath: Scalars['URI']['output']; + /** The HTTP URL for the user's commits to the repository in this time range. */ + url: Scalars['URI']['output']; +}; + + +/** This aggregates commits made by a user within one repository. */ +export type CommitContributionsByRepositoryContributionsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + orderBy?: InputMaybe; +}; + +/** An edge in a connection. */ +export type CommitEdge = { + __typename?: 'CommitEdge'; + /** A cursor for use in pagination. */ + cursor: Scalars['String']['output']; + /** The item at the end of the edge. */ + node?: Maybe; +}; + +/** The connection type for Commit. */ +export type CommitHistoryConnection = { + __typename?: 'CommitHistoryConnection'; + /** A list of edges. */ + edges?: Maybe>>; + /** A list of nodes. */ + nodes?: Maybe>>; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** Identifies the total count of items in the connection. */ + totalCount: Scalars['Int']['output']; +}; + +/** A message to include with a new commit */ +export type CommitMessage = { + /** The body of the message. */ + body?: InputMaybe; + /** The headline of the message. */ + headline: Scalars['String']['input']; +}; + +/** Parameters to be used for the commit_message_pattern rule */ +export type CommitMessagePatternParameters = { + __typename?: 'CommitMessagePatternParameters'; + /** How this rule appears when configuring it. */ + name?: Maybe; + /** If true, the rule will fail if the pattern matches. */ + negate: Scalars['Boolean']['output']; + /** The operator to use for matching. */ + operator: Scalars['String']['output']; + /** The pattern to match with. */ + pattern: Scalars['String']['output']; +}; + +/** Parameters to be used for the commit_message_pattern rule */ +export type CommitMessagePatternParametersInput = { + /** How this rule appears when configuring it. */ + name?: InputMaybe; + /** If true, the rule will fail if the pattern matches. */ + negate?: InputMaybe; + /** The operator to use for matching. */ + operator: Scalars['String']['input']; + /** The pattern to match with. */ + pattern: Scalars['String']['input']; +}; + +/** + * A git ref for a commit to be appended to. + * + * The ref must be a branch, i.e. its fully qualified name must start + * with `refs/heads/` (although the input is not required to be fully + * qualified). + * + * The Ref may be specified by its global node ID or by the + * `repositoryNameWithOwner` and `branchName`. + * + * ### Examples + * + * Specify a branch using a global node ID: + * + * { "id": "MDM6UmVmMTpyZWZzL2hlYWRzL21haW4=" } + * + * Specify a branch using `repositoryNameWithOwner` and `branchName`: + * + * { + * "repositoryNameWithOwner": "github/graphql-client", + * "branchName": "main" + * } + */ +export type CommittableBranch = { + /** The unqualified name of the branch to append the commit to. */ + branchName?: InputMaybe; + /** The Node ID of the Ref to be updated. */ + id?: InputMaybe; + /** The nameWithOwner of the repository to commit to. */ + repositoryNameWithOwner?: InputMaybe; +}; + +/** Parameters to be used for the committer_email_pattern rule */ +export type CommitterEmailPatternParameters = { + __typename?: 'CommitterEmailPatternParameters'; + /** How this rule appears when configuring it. */ + name?: Maybe; + /** If true, the rule will fail if the pattern matches. */ + negate: Scalars['Boolean']['output']; + /** The operator to use for matching. */ + operator: Scalars['String']['output']; + /** The pattern to match with. */ + pattern: Scalars['String']['output']; +}; + +/** Parameters to be used for the committer_email_pattern rule */ +export type CommitterEmailPatternParametersInput = { + /** How this rule appears when configuring it. */ + name?: InputMaybe; + /** If true, the rule will fail if the pattern matches. */ + negate?: InputMaybe; + /** The operator to use for matching. */ + operator: Scalars['String']['input']; + /** The pattern to match with. */ + pattern: Scalars['String']['input']; +}; + +/** Represents a comparison between two commit revisions. */ +export type Comparison = Node & { + __typename?: 'Comparison'; + /** The number of commits ahead of the base branch. */ + aheadBy: Scalars['Int']['output']; + /** The base revision of this comparison. */ + baseTarget: GitObject; + /** The number of commits behind the base branch. */ + behindBy: Scalars['Int']['output']; + /** The commits which compose this comparison. */ + commits: ComparisonCommitConnection; + /** The head revision of this comparison. */ + headTarget: GitObject; + /** The Node ID of the Comparison object */ + id: Scalars['ID']['output']; + /** The status of this comparison. */ + status: ComparisonStatus; +}; + + +/** Represents a comparison between two commit revisions. */ +export type ComparisonCommitsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +/** The connection type for Commit. */ +export type ComparisonCommitConnection = { + __typename?: 'ComparisonCommitConnection'; + /** The total count of authors and co-authors across all commits. */ + authorCount: Scalars['Int']['output']; + /** A list of edges. */ + edges?: Maybe>>; + /** A list of nodes. */ + nodes?: Maybe>>; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** Identifies the total count of items in the connection. */ + totalCount: Scalars['Int']['output']; +}; + +/** The status of a git comparison between two refs. */ +export type ComparisonStatus = + /** The head ref is ahead of the base ref. */ + | 'AHEAD' + /** The head ref is behind the base ref. */ + | 'BEHIND' + /** The head ref is both ahead and behind of the base ref, indicating git history has diverged. */ + | 'DIVERGED' + /** The head ref and base ref are identical. */ + | 'IDENTICAL'; + +/** Represents a 'connected' event on a given issue or pull request. */ +export type ConnectedEvent = Node & { + __typename?: 'ConnectedEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** The Node ID of the ConnectedEvent object */ + id: Scalars['ID']['output']; + /** Reference originated in a different repository. */ + isCrossRepository: Scalars['Boolean']['output']; + /** Issue or pull request that made the reference. */ + source: ReferencedSubject; + /** Issue or pull request which was connected. */ + subject: ReferencedSubject; +}; + +/** The content warning for a repository */ +export type ContentWarning = { + __typename?: 'ContentWarning'; + /** The content warning' category. E.g. 'mis_dis_information' */ + category: Scalars['String']['output']; + /** The content warning's custom sub category text. E.g. 'dangerous stuff.' */ + customSubCategory?: Maybe; + /** The content warning's sub category. E.g. 'medical_scientific' */ + subCategory?: Maybe; + /** The content warning's sub title. E.g. 'The information contained in this page has not been verified.' */ + subTitle?: Maybe; + /** The content warning's title. E.g. 'This page may contain false or misleading information.' */ + title: Scalars['String']['output']; + /** The type of content warning. E.g. 'interstitial' */ + type: Scalars['String']['output']; +}; + +/** The Contributing Guidelines for a repository. */ +export type ContributingGuidelines = { + __typename?: 'ContributingGuidelines'; + /** The body of the Contributing Guidelines. */ + body?: Maybe; + /** The HTTP path for the Contributing Guidelines. */ + resourcePath?: Maybe; + /** The HTTP URL for the Contributing Guidelines. */ + url?: Maybe; +}; + +/** Represents a contribution a user made on GitHub, such as opening an issue. */ +export type Contribution = { + /** + * Whether this contribution is associated with a record you do not have access to. For + * example, your own 'first issue' contribution may have been made on a repository you can no + * longer access. + */ + isRestricted: Scalars['Boolean']['output']; + /** When this contribution was made. */ + occurredAt: Scalars['DateTime']['output']; + /** The HTTP path for this contribution. */ + resourcePath: Scalars['URI']['output']; + /** The HTTP URL for this contribution. */ + url: Scalars['URI']['output']; + /** The user who made this contribution. */ + user: User; +}; + +/** A calendar of contributions made on GitHub by a user. */ +export type ContributionCalendar = { + __typename?: 'ContributionCalendar'; + /** A list of hex color codes used in this calendar. The darker the color, the more contributions it represents. */ + colors: Array; + /** Determine if the color set was chosen because it's currently Halloween. */ + isHalloween: Scalars['Boolean']['output']; + /** A list of the months of contributions in this calendar. */ + months: Array; + /** The count of total contributions in the calendar. */ + totalContributions: Scalars['Int']['output']; + /** A list of the weeks of contributions in this calendar. */ + weeks: Array; +}; + +/** Represents a single day of contributions on GitHub by a user. */ +export type ContributionCalendarDay = { + __typename?: 'ContributionCalendarDay'; + /** The hex color code that represents how many contributions were made on this day compared to others in the calendar. */ + color: Scalars['String']['output']; + /** How many contributions were made by the user on this day. */ + contributionCount: Scalars['Int']['output']; + /** + * Indication of contributions, relative to other days. Can be used to indicate + * which color to represent this day on a calendar. + */ + contributionLevel: ContributionLevel; + /** The day this square represents. */ + date: Scalars['Date']['output']; + /** A number representing which day of the week this square represents, e.g., 1 is Monday. */ + weekday: Scalars['Int']['output']; +}; + +/** A month of contributions in a user's contribution graph. */ +export type ContributionCalendarMonth = { + __typename?: 'ContributionCalendarMonth'; + /** The date of the first day of this month. */ + firstDay: Scalars['Date']['output']; + /** The name of the month. */ + name: Scalars['String']['output']; + /** How many weeks started in this month. */ + totalWeeks: Scalars['Int']['output']; + /** The year the month occurred in. */ + year: Scalars['Int']['output']; +}; + +/** A week of contributions in a user's contribution graph. */ +export type ContributionCalendarWeek = { + __typename?: 'ContributionCalendarWeek'; + /** The days of contributions in this week. */ + contributionDays: Array; + /** The date of the earliest square in this week. */ + firstDay: Scalars['Date']['output']; +}; + +/** Varying levels of contributions from none to many. */ +export type ContributionLevel = + /** Lowest 25% of days of contributions. */ + | 'FIRST_QUARTILE' + /** Highest 25% of days of contributions. More contributions than the third quartile. */ + | 'FOURTH_QUARTILE' + /** No contributions occurred. */ + | 'NONE' + /** Second lowest 25% of days of contributions. More contributions than the first quartile. */ + | 'SECOND_QUARTILE' + /** Second highest 25% of days of contributions. More contributions than second quartile, less than the fourth quartile. */ + | 'THIRD_QUARTILE'; + +/** Ordering options for contribution connections. */ +export type ContributionOrder = { + /** The ordering direction. */ + direction: OrderDirection; +}; + +/** + * A collection of contributions made by a user, including opened issues, commits, and pull requests. + * Contributions in private and internal repositories are only included with the optional read:user scope. + */ +export type ContributionsCollection = { + __typename?: 'ContributionsCollection'; + /** Commit contributions made by the user, grouped by repository. */ + commitContributionsByRepository: Array; + /** A calendar of this user's contributions on GitHub. */ + contributionCalendar: ContributionCalendar; + /** The years the user has been making contributions with the most recent year first. */ + contributionYears: Array; + /** Determine if this collection's time span ends in the current month. */ + doesEndInCurrentMonth: Scalars['Boolean']['output']; + /** + * The date of the first restricted contribution the user made in this time + * period. Can only be non-null when the user has enabled private contribution counts. + */ + earliestRestrictedContributionDate?: Maybe; + /** The ending date and time of this collection. */ + endedAt: Scalars['DateTime']['output']; + /** + * The first issue the user opened on GitHub. This will be null if that issue was + * opened outside the collection's time range and ignoreTimeRange is false. If + * the issue is not visible but the user has opted to show private contributions, + * a RestrictedContribution will be returned. + */ + firstIssueContribution?: Maybe; + /** + * The first pull request the user opened on GitHub. This will be null if that + * pull request was opened outside the collection's time range and + * ignoreTimeRange is not true. If the pull request is not visible but the user + * has opted to show private contributions, a RestrictedContribution will be returned. + */ + firstPullRequestContribution?: Maybe; + /** + * The first repository the user created on GitHub. This will be null if that + * first repository was created outside the collection's time range and + * ignoreTimeRange is false. If the repository is not visible, then a + * RestrictedContribution is returned. + */ + firstRepositoryContribution?: Maybe; + /** Does the user have any more activity in the timeline that occurred prior to the collection's time range? */ + hasActivityInThePast: Scalars['Boolean']['output']; + /** Determine if there are any contributions in this collection. */ + hasAnyContributions: Scalars['Boolean']['output']; + /** + * Determine if the user made any contributions in this time frame whose details + * are not visible because they were made in a private repository. Can only be + * true if the user enabled private contribution counts. + */ + hasAnyRestrictedContributions: Scalars['Boolean']['output']; + /** Whether or not the collector's time span is all within the same day. */ + isSingleDay: Scalars['Boolean']['output']; + /** A list of issues the user opened. */ + issueContributions: CreatedIssueContributionConnection; + /** Issue contributions made by the user, grouped by repository. */ + issueContributionsByRepository: Array; + /** + * When the user signed up for GitHub. This will be null if that sign up date + * falls outside the collection's time range and ignoreTimeRange is false. + */ + joinedGitHubContribution?: Maybe; + /** + * The date of the most recent restricted contribution the user made in this time + * period. Can only be non-null when the user has enabled private contribution counts. + */ + latestRestrictedContributionDate?: Maybe; + /** + * When this collection's time range does not include any activity from the user, use this + * to get a different collection from an earlier time range that does have activity. + */ + mostRecentCollectionWithActivity?: Maybe; + /** + * Returns a different contributions collection from an earlier time range than this one + * that does not have any contributions. + */ + mostRecentCollectionWithoutActivity?: Maybe; + /** + * The issue the user opened on GitHub that received the most comments in the specified + * time frame. + */ + popularIssueContribution?: Maybe; + /** + * The pull request the user opened on GitHub that received the most comments in the + * specified time frame. + */ + popularPullRequestContribution?: Maybe; + /** Pull request contributions made by the user. */ + pullRequestContributions: CreatedPullRequestContributionConnection; + /** Pull request contributions made by the user, grouped by repository. */ + pullRequestContributionsByRepository: Array; + /** + * Pull request review contributions made by the user. Returns the most recently + * submitted review for each PR reviewed by the user. + */ + pullRequestReviewContributions: CreatedPullRequestReviewContributionConnection; + /** Pull request review contributions made by the user, grouped by repository. */ + pullRequestReviewContributionsByRepository: Array; + /** A list of repositories owned by the user that the user created in this time range. */ + repositoryContributions: CreatedRepositoryContributionConnection; + /** + * A count of contributions made by the user that the viewer cannot access. Only + * non-zero when the user has chosen to share their private contribution counts. + */ + restrictedContributionsCount: Scalars['Int']['output']; + /** The beginning date and time of this collection. */ + startedAt: Scalars['DateTime']['output']; + /** How many commits were made by the user in this time span. */ + totalCommitContributions: Scalars['Int']['output']; + /** How many issues the user opened. */ + totalIssueContributions: Scalars['Int']['output']; + /** How many pull requests the user opened. */ + totalPullRequestContributions: Scalars['Int']['output']; + /** How many pull request reviews the user left. */ + totalPullRequestReviewContributions: Scalars['Int']['output']; + /** How many different repositories the user committed to. */ + totalRepositoriesWithContributedCommits: Scalars['Int']['output']; + /** How many different repositories the user opened issues in. */ + totalRepositoriesWithContributedIssues: Scalars['Int']['output']; + /** How many different repositories the user left pull request reviews in. */ + totalRepositoriesWithContributedPullRequestReviews: Scalars['Int']['output']; + /** How many different repositories the user opened pull requests in. */ + totalRepositoriesWithContributedPullRequests: Scalars['Int']['output']; + /** How many repositories the user created. */ + totalRepositoryContributions: Scalars['Int']['output']; + /** The user who made the contributions in this collection. */ + user: User; +}; + + +/** + * A collection of contributions made by a user, including opened issues, commits, and pull requests. + * Contributions in private and internal repositories are only included with the optional read:user scope. + */ +export type ContributionsCollectionCommitContributionsByRepositoryArgs = { + maxRepositories?: InputMaybe; +}; + + +/** + * A collection of contributions made by a user, including opened issues, commits, and pull requests. + * Contributions in private and internal repositories are only included with the optional read:user scope. + */ +export type ContributionsCollectionIssueContributionsArgs = { + after?: InputMaybe; + before?: InputMaybe; + excludeFirst?: InputMaybe; + excludePopular?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + orderBy?: InputMaybe; +}; + + +/** + * A collection of contributions made by a user, including opened issues, commits, and pull requests. + * Contributions in private and internal repositories are only included with the optional read:user scope. + */ +export type ContributionsCollectionIssueContributionsByRepositoryArgs = { + excludeFirst?: InputMaybe; + excludePopular?: InputMaybe; + maxRepositories?: InputMaybe; +}; + + +/** + * A collection of contributions made by a user, including opened issues, commits, and pull requests. + * Contributions in private and internal repositories are only included with the optional read:user scope. + */ +export type ContributionsCollectionPullRequestContributionsArgs = { + after?: InputMaybe; + before?: InputMaybe; + excludeFirst?: InputMaybe; + excludePopular?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + orderBy?: InputMaybe; +}; + + +/** + * A collection of contributions made by a user, including opened issues, commits, and pull requests. + * Contributions in private and internal repositories are only included with the optional read:user scope. + */ +export type ContributionsCollectionPullRequestContributionsByRepositoryArgs = { + excludeFirst?: InputMaybe; + excludePopular?: InputMaybe; + maxRepositories?: InputMaybe; +}; + + +/** + * A collection of contributions made by a user, including opened issues, commits, and pull requests. + * Contributions in private and internal repositories are only included with the optional read:user scope. + */ +export type ContributionsCollectionPullRequestReviewContributionsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + orderBy?: InputMaybe; +}; + + +/** + * A collection of contributions made by a user, including opened issues, commits, and pull requests. + * Contributions in private and internal repositories are only included with the optional read:user scope. + */ +export type ContributionsCollectionPullRequestReviewContributionsByRepositoryArgs = { + maxRepositories?: InputMaybe; +}; + + +/** + * A collection of contributions made by a user, including opened issues, commits, and pull requests. + * Contributions in private and internal repositories are only included with the optional read:user scope. + */ +export type ContributionsCollectionRepositoryContributionsArgs = { + after?: InputMaybe; + before?: InputMaybe; + excludeFirst?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + orderBy?: InputMaybe; +}; + + +/** + * A collection of contributions made by a user, including opened issues, commits, and pull requests. + * Contributions in private and internal repositories are only included with the optional read:user scope. + */ +export type ContributionsCollectionTotalIssueContributionsArgs = { + excludeFirst?: InputMaybe; + excludePopular?: InputMaybe; +}; + + +/** + * A collection of contributions made by a user, including opened issues, commits, and pull requests. + * Contributions in private and internal repositories are only included with the optional read:user scope. + */ +export type ContributionsCollectionTotalPullRequestContributionsArgs = { + excludeFirst?: InputMaybe; + excludePopular?: InputMaybe; +}; + + +/** + * A collection of contributions made by a user, including opened issues, commits, and pull requests. + * Contributions in private and internal repositories are only included with the optional read:user scope. + */ +export type ContributionsCollectionTotalRepositoriesWithContributedIssuesArgs = { + excludeFirst?: InputMaybe; + excludePopular?: InputMaybe; +}; + + +/** + * A collection of contributions made by a user, including opened issues, commits, and pull requests. + * Contributions in private and internal repositories are only included with the optional read:user scope. + */ +export type ContributionsCollectionTotalRepositoriesWithContributedPullRequestsArgs = { + excludeFirst?: InputMaybe; + excludePopular?: InputMaybe; +}; + + +/** + * A collection of contributions made by a user, including opened issues, commits, and pull requests. + * Contributions in private and internal repositories are only included with the optional read:user scope. + */ +export type ContributionsCollectionTotalRepositoryContributionsArgs = { + excludeFirst?: InputMaybe; +}; + +/** Autogenerated input type of ConvertProjectCardNoteToIssue */ +export type ConvertProjectCardNoteToIssueInput = { + /** The body of the newly created issue. */ + body?: InputMaybe; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The ProjectCard ID to convert. */ + projectCardId: Scalars['ID']['input']; + /** The ID of the repository to create the issue in. */ + repositoryId: Scalars['ID']['input']; + /** The title of the newly created issue. Defaults to the card's note text. */ + title?: InputMaybe; +}; + +/** Autogenerated return type of ConvertProjectCardNoteToIssue. */ +export type ConvertProjectCardNoteToIssuePayload = { + __typename?: 'ConvertProjectCardNoteToIssuePayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The updated ProjectCard. */ + projectCard?: Maybe; +}; + +/** Autogenerated input type of ConvertProjectV2DraftIssueItemToIssue */ +export type ConvertProjectV2DraftIssueItemToIssueInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The ID of the draft issue ProjectV2Item to convert. */ + itemId: Scalars['ID']['input']; + /** The ID of the repository to create the issue in. */ + repositoryId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of ConvertProjectV2DraftIssueItemToIssue. */ +export type ConvertProjectV2DraftIssueItemToIssuePayload = { + __typename?: 'ConvertProjectV2DraftIssueItemToIssuePayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The updated project item. */ + item?: Maybe; +}; + +/** Autogenerated input type of ConvertPullRequestToDraft */ +export type ConvertPullRequestToDraftInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** ID of the pull request to convert to draft */ + pullRequestId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of ConvertPullRequestToDraft. */ +export type ConvertPullRequestToDraftPayload = { + __typename?: 'ConvertPullRequestToDraftPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The pull request that is now a draft. */ + pullRequest?: Maybe; +}; + +/** Represents a 'convert_to_draft' event on a given pull request. */ +export type ConvertToDraftEvent = Node & UniformResourceLocatable & { + __typename?: 'ConvertToDraftEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** The Node ID of the ConvertToDraftEvent object */ + id: Scalars['ID']['output']; + /** PullRequest referenced by event. */ + pullRequest: PullRequest; + /** The HTTP path for this convert to draft event. */ + resourcePath: Scalars['URI']['output']; + /** The HTTP URL for this convert to draft event. */ + url: Scalars['URI']['output']; +}; + +/** Represents a 'converted_from_draft' event on a given issue or pull request. */ +export type ConvertedFromDraftEvent = Node & ProjectV2Event & { + __typename?: 'ConvertedFromDraftEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** The Node ID of the ConvertedFromDraftEvent object */ + id: Scalars['ID']['output']; + /** Project referenced by event. */ + project?: Maybe; + /** Did this event result from workflow automation? */ + wasAutomated: Scalars['Boolean']['output']; +}; + +/** Represents a 'converted_note_to_issue' event on a given issue or pull request. */ +export type ConvertedNoteToIssueEvent = Node & { + __typename?: 'ConvertedNoteToIssueEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** Identifies the primary key from the database. */ + databaseId?: Maybe; + /** The Node ID of the ConvertedNoteToIssueEvent object */ + id: Scalars['ID']['output']; + /** + * Project referenced by event. + * @deprecated Projects (classic) is being deprecated in favor of the new Projects experience, see: https://github.blog/changelog/2024-05-23-sunset-notice-projects-classic/. Removal on 2025-04-01 UTC. + */ + project?: Maybe; + /** + * Project card referenced by this project event. + * @deprecated Projects (classic) is being deprecated in favor of the new Projects experience, see: https://github.blog/changelog/2024-05-23-sunset-notice-projects-classic/. Removal on 2025-04-01 UTC. + */ + projectCard?: Maybe; + /** Column name referenced by this project event. */ + projectColumnName: Scalars['String']['output']; +}; + +/** Represents a 'converted_to_discussion' event on a given issue. */ +export type ConvertedToDiscussionEvent = Node & { + __typename?: 'ConvertedToDiscussionEvent'; + /** Identifies the actor who performed the event. */ + actor?: Maybe; + /** Identifies the date and time when the object was created. */ + createdAt: Scalars['DateTime']['output']; + /** The discussion that the issue was converted into. */ + discussion?: Maybe; + /** The Node ID of the ConvertedToDiscussionEvent object */ + id: Scalars['ID']['output']; +}; + +/** + * Request Copilot code review for new pull requests automatically if the author + * has access to Copilot code review and their premium requests quota has not + * reached the limit. + */ +export type CopilotCodeReviewParameters = { + __typename?: 'CopilotCodeReviewParameters'; + /** Copilot automatically reviews draft pull requests before they are marked as ready for review. */ + reviewDraftPullRequests: Scalars['Boolean']['output']; + /** Copilot automatically reviews each new push to the pull request. */ + reviewOnPush: Scalars['Boolean']['output']; +}; + +/** + * Request Copilot code review for new pull requests automatically if the author + * has access to Copilot code review and their premium requests quota has not + * reached the limit. + */ +export type CopilotCodeReviewParametersInput = { + /** Copilot automatically reviews draft pull requests before they are marked as ready for review. */ + reviewDraftPullRequests?: InputMaybe; + /** Copilot automatically reviews each new push to the pull request. */ + reviewOnPush?: InputMaybe; +}; + +/** Copilot endpoint information */ +export type CopilotEndpoints = { + __typename?: 'CopilotEndpoints'; + /** Copilot API endpoint */ + api: Scalars['String']['output']; + /** Copilot origin tracker endpoint */ + originTracker: Scalars['String']['output']; + /** Copilot proxy endpoint */ + proxy: Scalars['String']['output']; + /** Copilot telemetry endpoint */ + telemetry: Scalars['String']['output']; +}; + +/** Autogenerated input type of CopyProjectV2 */ +export type CopyProjectV2Input = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** Include draft issues in the new project */ + includeDraftIssues?: InputMaybe; + /** The owner ID of the new project. */ + ownerId: Scalars['ID']['input']; + /** The ID of the source Project to copy. */ + projectId: Scalars['ID']['input']; + /** The title of the project. */ + title: Scalars['String']['input']; +}; + +/** Autogenerated return type of CopyProjectV2. */ +export type CopyProjectV2Payload = { + __typename?: 'CopyProjectV2Payload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The copied project. */ + projectV2?: Maybe; +}; + +/** Autogenerated input type of CreateAttributionInvitation */ +export type CreateAttributionInvitationInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The Node ID of the owner scoping the reattributable data. */ + ownerId: Scalars['ID']['input']; + /** The Node ID of the account owning the data to reattribute. */ + sourceId: Scalars['ID']['input']; + /** The Node ID of the account which may claim the data. */ + targetId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of CreateAttributionInvitation. */ +export type CreateAttributionInvitationPayload = { + __typename?: 'CreateAttributionInvitationPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The owner scoping the reattributable data. */ + owner?: Maybe; + /** The account owning the data to reattribute. */ + source?: Maybe; + /** The account which may claim the data. */ + target?: Maybe; +}; + +/** Autogenerated input type of CreateBranchProtectionRule */ +export type CreateBranchProtectionRuleInput = { + /** Can this branch be deleted. */ + allowsDeletions?: InputMaybe; + /** Are force pushes allowed on this branch. */ + allowsForcePushes?: InputMaybe; + /** Is branch creation a protected operation. */ + blocksCreations?: InputMaybe; + /** A list of User, Team, or App IDs allowed to bypass force push targeting matching branches. */ + bypassForcePushActorIds?: InputMaybe>; + /** A list of User, Team, or App IDs allowed to bypass pull requests targeting matching branches. */ + bypassPullRequestActorIds?: InputMaybe>; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** Will new commits pushed to matching branches dismiss pull request review approvals. */ + dismissesStaleReviews?: InputMaybe; + /** Can admins override branch protection. */ + isAdminEnforced?: InputMaybe; + /** + * Whether users can pull changes from upstream when the branch is locked. Set to + * `true` to allow fork syncing. Set to `false` to prevent fork syncing. + */ + lockAllowsFetchAndMerge?: InputMaybe; + /** Whether to set the branch as read-only. If this is true, users will not be able to push to the branch. */ + lockBranch?: InputMaybe; + /** The glob-like pattern used to determine matching branches. */ + pattern: Scalars['String']['input']; + /** A list of User, Team, or App IDs allowed to push to matching branches. */ + pushActorIds?: InputMaybe>; + /** The global relay id of the repository in which a new branch protection rule should be created in. */ + repositoryId: Scalars['ID']['input']; + /** Whether the most recent push must be approved by someone other than the person who pushed it */ + requireLastPushApproval?: InputMaybe; + /** Number of approving reviews required to update matching branches. */ + requiredApprovingReviewCount?: InputMaybe; + /** The list of required deployment environments */ + requiredDeploymentEnvironments?: InputMaybe>; + /** List of required status check contexts that must pass for commits to be accepted to matching branches. */ + requiredStatusCheckContexts?: InputMaybe>; + /** The list of required status checks */ + requiredStatusChecks?: InputMaybe>; + /** Are approving reviews required to update matching branches. */ + requiresApprovingReviews?: InputMaybe; + /** Are reviews from code owners required to update matching branches. */ + requiresCodeOwnerReviews?: InputMaybe; + /** Are commits required to be signed. */ + requiresCommitSignatures?: InputMaybe; + /** Are conversations required to be resolved before merging. */ + requiresConversationResolution?: InputMaybe; + /** Are successful deployments required before merging. */ + requiresDeployments?: InputMaybe; + /** Are merge commits prohibited from being pushed to this branch. */ + requiresLinearHistory?: InputMaybe; + /** Are status checks required to update matching branches. */ + requiresStatusChecks?: InputMaybe; + /** Are branches required to be up to date before merging. */ + requiresStrictStatusChecks?: InputMaybe; + /** Is pushing to matching branches restricted. */ + restrictsPushes?: InputMaybe; + /** Is dismissal of pull request reviews restricted. */ + restrictsReviewDismissals?: InputMaybe; + /** A list of User, Team, or App IDs allowed to dismiss reviews on pull requests targeting matching branches. */ + reviewDismissalActorIds?: InputMaybe>; +}; + +/** Autogenerated return type of CreateBranchProtectionRule. */ +export type CreateBranchProtectionRulePayload = { + __typename?: 'CreateBranchProtectionRulePayload'; + /** The newly created BranchProtectionRule. */ + branchProtectionRule?: Maybe; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; +}; + +/** Autogenerated input type of CreateCheckRun */ +export type CreateCheckRunInput = { + /** Possible further actions the integrator can perform, which a user may trigger. */ + actions?: InputMaybe>; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The time that the check run finished. */ + completedAt?: InputMaybe; + /** The final conclusion of the check. */ + conclusion?: InputMaybe; + /** The URL of the integrator's site that has the full details of the check. */ + detailsUrl?: InputMaybe; + /** A reference for the run on the integrator's system. */ + externalId?: InputMaybe; + /** The SHA of the head commit. */ + headSha: Scalars['GitObjectID']['input']; + /** The name of the check. */ + name: Scalars['String']['input']; + /** Descriptive details about the run. */ + output?: InputMaybe; + /** The node ID of the repository. */ + repositoryId: Scalars['ID']['input']; + /** The time that the check run began. */ + startedAt?: InputMaybe; + /** The current status. */ + status?: InputMaybe; +}; + +/** Autogenerated return type of CreateCheckRun. */ +export type CreateCheckRunPayload = { + __typename?: 'CreateCheckRunPayload'; + /** The newly created check run. */ + checkRun?: Maybe; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; +}; + +/** Autogenerated input type of CreateCheckSuite */ +export type CreateCheckSuiteInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The SHA of the head commit. */ + headSha: Scalars['GitObjectID']['input']; + /** The Node ID of the repository. */ + repositoryId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of CreateCheckSuite. */ +export type CreateCheckSuitePayload = { + __typename?: 'CreateCheckSuitePayload'; + /** The newly created check suite. */ + checkSuite?: Maybe; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; +}; + +/** Autogenerated input type of CreateCommitOnBranch */ +export type CreateCommitOnBranchInput = { + /** The Ref to be updated. Must be a branch. */ + branch: CommittableBranch; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The git commit oid expected at the head of the branch prior to the commit */ + expectedHeadOid: Scalars['GitObjectID']['input']; + /** A description of changes to files in this commit. */ + fileChanges?: InputMaybe; + /** The commit message the be included with the commit. */ + message: CommitMessage; +}; + +/** Autogenerated return type of CreateCommitOnBranch. */ +export type CreateCommitOnBranchPayload = { + __typename?: 'CreateCommitOnBranchPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The new commit. */ + commit?: Maybe; + /** The ref which has been updated to point to the new commit. */ + ref?: Maybe; +}; + +/** Autogenerated input type of CreateDeployment */ +export type CreateDeploymentInput = { + /** Attempt to automatically merge the default branch into the requested ref, defaults to true. */ + autoMerge?: InputMaybe; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** Short description of the deployment. */ + description?: InputMaybe; + /** Name for the target deployment environment. */ + environment?: InputMaybe; + /** JSON payload with extra information about the deployment. */ + payload?: InputMaybe; + /** The node ID of the ref to be deployed. */ + refId: Scalars['ID']['input']; + /** The node ID of the repository. */ + repositoryId: Scalars['ID']['input']; + /** + * The status contexts to verify against commit status checks. To bypass required + * contexts, pass an empty array. Defaults to all unique contexts. + */ + requiredContexts?: InputMaybe>; + /** Specifies a task to execute. */ + task?: InputMaybe; +}; + +/** Autogenerated return type of CreateDeployment. */ +export type CreateDeploymentPayload = { + __typename?: 'CreateDeploymentPayload'; + /** True if the default branch has been auto-merged into the deployment ref. */ + autoMerged?: Maybe; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The new deployment. */ + deployment?: Maybe; +}; + +/** Autogenerated input type of CreateDeploymentStatus */ +export type CreateDeploymentStatusInput = { + /** + * Adds a new inactive status to all non-transient, non-production environment + * deployments with the same repository and environment name as the created + * status's deployment. + */ + autoInactive?: InputMaybe; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The node ID of the deployment. */ + deploymentId: Scalars['ID']['input']; + /** A short description of the status. Maximum length of 140 characters. */ + description?: InputMaybe; + /** If provided, updates the environment of the deploy. Otherwise, does not modify the environment. */ + environment?: InputMaybe; + /** Sets the URL for accessing your environment. */ + environmentUrl?: InputMaybe; + /** + * The log URL to associate with this status. This URL should contain + * output to keep the user updated while the task is running or serve as + * historical information for what happened in the deployment. + */ + logUrl?: InputMaybe; + /** The state of the deployment. */ + state: DeploymentStatusState; +}; + +/** Autogenerated return type of CreateDeploymentStatus. */ +export type CreateDeploymentStatusPayload = { + __typename?: 'CreateDeploymentStatusPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The new deployment status. */ + deploymentStatus?: Maybe; +}; + +/** Autogenerated input type of CreateDiscussion */ +export type CreateDiscussionInput = { + /** The body of the discussion. */ + body: Scalars['String']['input']; + /** The id of the discussion category to associate with this discussion. */ + categoryId: Scalars['ID']['input']; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The id of the repository on which to create the discussion. */ + repositoryId: Scalars['ID']['input']; + /** The title of the discussion. */ + title: Scalars['String']['input']; +}; + +/** Autogenerated return type of CreateDiscussion. */ +export type CreateDiscussionPayload = { + __typename?: 'CreateDiscussionPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The discussion that was just created. */ + discussion?: Maybe; +}; + +/** Autogenerated input type of CreateEnterpriseOrganization */ +export type CreateEnterpriseOrganizationInput = { + /** The logins for the administrators of the new organization. */ + adminLogins: Array; + /** The email used for sending billing receipts. */ + billingEmail: Scalars['String']['input']; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The ID of the enterprise owning the new organization. */ + enterpriseId: Scalars['ID']['input']; + /** The login of the new organization. */ + login: Scalars['String']['input']; + /** The profile name of the new organization. */ + profileName: Scalars['String']['input']; +}; + +/** Autogenerated return type of CreateEnterpriseOrganization. */ +export type CreateEnterpriseOrganizationPayload = { + __typename?: 'CreateEnterpriseOrganizationPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The enterprise that owns the created organization. */ + enterprise?: Maybe; + /** The organization that was created. */ + organization?: Maybe; +}; + +/** Autogenerated input type of CreateEnvironment */ +export type CreateEnvironmentInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The name of the environment. */ + name: Scalars['String']['input']; + /** The node ID of the repository. */ + repositoryId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of CreateEnvironment. */ +export type CreateEnvironmentPayload = { + __typename?: 'CreateEnvironmentPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The new or existing environment. */ + environment?: Maybe; +}; + +/** Autogenerated input type of CreateIpAllowListEntry */ +export type CreateIpAllowListEntryInput = { + /** An IP address or range of addresses in CIDR notation. */ + allowListValue: Scalars['String']['input']; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** Whether the IP allow list entry is active when an IP allow list is enabled. */ + isActive: Scalars['Boolean']['input']; + /** An optional name for the IP allow list entry. */ + name?: InputMaybe; + /** The ID of the owner for which to create the new IP allow list entry. */ + ownerId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of CreateIpAllowListEntry. */ +export type CreateIpAllowListEntryPayload = { + __typename?: 'CreateIpAllowListEntryPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The IP allow list entry that was created. */ + ipAllowListEntry?: Maybe; +}; + +/** Autogenerated input type of CreateIssueField */ +export type CreateIssueFieldInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The data type of the issue field. */ + dataType: IssueFieldDataType; + /** A description of the issue field. */ + description?: InputMaybe; + /** The name of the issue field. */ + name: Scalars['String']['input']; + /** The options for the issue field if applicable. */ + options?: InputMaybe>; + /** The ID of the organization where the issue field will be created. */ + ownerId: Scalars['ID']['input']; + /** The visibility of the issue field. */ + visibility?: InputMaybe; +}; + +/** Autogenerated return type of CreateIssueField. */ +export type CreateIssueFieldPayload = { + __typename?: 'CreateIssueFieldPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The newly created issue field. */ + issueField?: Maybe; +}; + +/** Autogenerated input type of CreateIssueFieldValue */ +export type CreateIssueFieldValueInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** The field value to create. */ + issueField: IssueFieldCreateOrUpdateInput; + /** The ID of the issue. */ + issueId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of CreateIssueFieldValue. */ +export type CreateIssueFieldValuePayload = { + __typename?: 'CreateIssueFieldValuePayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The issue object. */ + issue?: Maybe; + /** The newly created issue field value. */ + issueFieldValue?: Maybe; +}; + +/** Autogenerated input type of CreateIssue */ +export type CreateIssueInput = { + /** Configuration for assigning Copilot to this issue. */ + agentAssignment?: InputMaybe; + /** The Node ID of assignees for this issue. */ + assigneeIds?: InputMaybe>; + /** The body for the issue description. */ + body?: InputMaybe; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** An array of issue fields to set on the issue during creation */ + issueFields?: InputMaybe>; + /** The name of an issue template in the repository, assigns labels and assignees from the template to the issue */ + issueTemplate?: InputMaybe; + /** The Node ID of the issue type for this issue */ + issueTypeId?: InputMaybe; + /** An array of Node IDs of labels for this issue. */ + labelIds?: InputMaybe>; + /** The Node ID of the milestone for this issue. */ + milestoneId?: InputMaybe; + /** The Node ID of the parent issue to add this new issue to */ + parentIssueId?: InputMaybe; + /** An array of Node IDs for projects associated with this issue. */ + projectIds?: InputMaybe>; + /** An array of Node IDs for Projects V2 associated with this issue. */ + projectV2Ids?: InputMaybe>; + /** The Node ID of the repository. */ + repositoryId: Scalars['ID']['input']; + /** The title for the issue. */ + title: Scalars['String']['input']; +}; + +/** Autogenerated return type of CreateIssue. */ +export type CreateIssuePayload = { + __typename?: 'CreateIssuePayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The new issue. */ + issue?: Maybe; +}; + +/** Autogenerated input type of CreateIssueType */ +export type CreateIssueTypeInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** Color for the issue type */ + color?: InputMaybe; + /** Description of the new issue type */ + description?: InputMaybe; + /** Whether or not the issue type is enabled on the org level */ + isEnabled: Scalars['Boolean']['input']; + /** Name of the new issue type */ + name: Scalars['String']['input']; + /** The ID for the organization on which the issue type is created */ + ownerId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of CreateIssueType. */ +export type CreateIssueTypePayload = { + __typename?: 'CreateIssueTypePayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The newly created issue type */ + issueType?: Maybe; +}; + +/** Autogenerated input type of CreateLabel */ +export type CreateLabelInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + /** A 6 character hex code, without the leading #, identifying the color of the label. */ + color: Scalars['String']['input']; + /** A brief description of the label, such as its purpose. */ + description?: InputMaybe; + /** The name of the label. */ + name: Scalars['String']['input']; + /** The Node ID of the repository. */ + repositoryId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of CreateLabel. */ +export type CreateLabelPayload = { + __typename?: 'CreateLabelPayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + /** The new label. */ + label?: Maybe