From a8422740fadce8d68aef0927f4f2a672b61074b3 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Fri, 12 Jun 2026 12:07:38 +0200 Subject: [PATCH 1/6] ci: drive releases with release-please and Communique notes Replace the Unreleased-changelog workflow, the release/* changelog workflow, and the local release:prep/release:finalize scripts (release-it) with a single release-please flow: a runner executes release-please as a library with Communique registered as the changelog generator, maintaining one release PR whose merge creates the tag + GitHub Release and dispatches the existing tag-driven publish pipeline. - src/tools/release-please-runner.ts: registerChangelogNotes('communique') shells out to `communique generate HEAD --concise`; outputs drive workflow dispatches (CI onto the release branch, release.yml by tag) since workflow-token pushes/tags never trigger other workflows. - Heading style for new CHANGELOG sections is `## [] - ` (no v: release-please's PR-body parser needs a digit after the bracket); unit tests pin that contract and the insertion point against the installed release-please internals. - CHANGELOG.md drops the [Unreleased] section: the release PR is the unreleased view now. - pnpm-workspace.yaml: aube supportedArchitectures matrix (keeps the lockfile host-independent) and a documented trustPolicyExclude for @octokit/endpoint@9.0.6 (last-CJS octokit line ships no provenance). - ADR 0009 records the decision; ADR 0002 (release-it) is superseded. Change-Id: If33cccf3ae5d1054992a619d2dab4c1539a01fbc Co-Authored-By: Claude Fable 5 Signed-off-by: Thomas Kosiewski --- .github/workflows/release-changelog.yml | 169 -- .github/workflows/release-please.yml | 86 + .../workflows/update-unreleased-changelog.yml | 137 - .release-it.json | 20 - .release-please-manifest.json | 3 + CHANGELOG.md | 2 - aube-lock.yaml | 2229 ++++++++--------- docs/RELEASE-PROCESS.md | 319 +-- ...02-use-release-it-only-for-release-prep.md | 7 +- ...09-release-please-with-communique-notes.md | 35 + package.json | 4 +- pnpm-workspace.yaml | 23 + release-please-config.json | 16 + scripts/release-finalize.mjs | 63 - scripts/release-helpers.mjs | 883 ------- scripts/release-prep.mjs | 115 - src/tools/release-please-runner.ts | 288 +++ test/integration/release-scripts.test.ts | 799 ------ test/unit/tools/release-please-runner.test.ts | 284 +++ 19 files changed, 1923 insertions(+), 3559 deletions(-) delete mode 100644 .github/workflows/release-changelog.yml create mode 100644 .github/workflows/release-please.yml delete mode 100644 .github/workflows/update-unreleased-changelog.yml delete mode 100644 .release-it.json create mode 100644 .release-please-manifest.json create mode 100644 docs/adr/0009-release-please-with-communique-notes.md create mode 100644 pnpm-workspace.yaml create mode 100644 release-please-config.json delete mode 100755 scripts/release-finalize.mjs delete mode 100644 scripts/release-helpers.mjs delete mode 100755 scripts/release-prep.mjs create mode 100644 src/tools/release-please-runner.ts delete mode 100644 test/integration/release-scripts.test.ts create mode 100644 test/unit/tools/release-please-runner.test.ts diff --git a/.github/workflows/release-changelog.yml b/.github/workflows/release-changelog.yml deleted file mode 100644 index 3f178897..00000000 --- a/.github/workflows/release-changelog.yml +++ /dev/null @@ -1,169 +0,0 @@ -name: Release Changelog - -on: - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - branches: - - main - paths: - - package.json - - package-lock.json - - CHANGELOG.md - - communique.toml - - mise.lock - - mise.toml - - .github/workflows/release-changelog.yml - -permissions: - actions: write - contents: write - issues: read - pull-requests: read - -jobs: - update-changelog: - if: >- - github.event.pull_request.head.repo.full_name == github.repository && - startsWith(github.head_ref, 'release/') - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - name: Check out release branch - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - ref: ${{ github.head_ref }} - fetch-depth: 0 - persist-credentials: false - - - name: Set up mise - uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3 - with: - install_args: --locked - - - name: Resolve release metadata - id: release-metadata - shell: bash - env: - BASE_REF: ${{ github.base_ref }} - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - auth_header="$(printf 'x-access-token:%s' "$GH_TOKEN" | base64 | tr -d '\n')" - git -c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \ - fetch --no-tags origin \ - "refs/heads/$BASE_REF:refs/remotes/origin/$BASE_REF" - - node --input-type=module <<'EOF' - import assert from 'node:assert/strict'; - import { appendFileSync, readFileSync } from 'node:fs'; - import { execFileSync } from 'node:child_process'; - - const packageJson = JSON.parse(readFileSync('package.json', 'utf8')); - assert(packageJson !== null && typeof packageJson === 'object', 'package.json must parse to an object'); - assert(typeof packageJson.version === 'string' && packageJson.version.length > 0, 'package.json version must not be empty'); - - const basePackageJsonText = execFileSync( - 'git', - ['show', `refs/remotes/origin/${process.env.BASE_REF}:package.json`], - { encoding: 'utf8' }, - ); - const basePackageJson = JSON.parse(basePackageJsonText); - assert(basePackageJson !== null && typeof basePackageJson === 'object', 'base package.json must parse to an object'); - - const packageVersion = packageJson.version; - const baseVersion = typeof basePackageJson.version === 'string' ? basePackageJson.version : ''; - const releaseTag = `v${packageVersion}`; - const shouldRun = packageVersion !== baseVersion; - - for (const [key, value] of Object.entries({ - package_version: packageVersion, - release_tag: releaseTag, - should_run: String(shouldRun), - })) { - assert(typeof value === 'string' && value.length > 0, `${key} must not be empty`); - appendFileSync(process.env.GITHUB_OUTPUT, `${key}=${value}\n`); - } - EOF - - - name: Check existing changelog entry - id: changelog-entry - if: steps.release-metadata.outputs.should_run == 'true' - shell: bash - env: - PACKAGE_VERSION: ${{ steps.release-metadata.outputs.package_version }} - RELEASE_TAG: ${{ steps.release-metadata.outputs.release_tag }} - run: | - set -euo pipefail - if [[ -f CHANGELOG.md ]] && { grep -Fq "## [${RELEASE_TAG}]" CHANGELOG.md || grep -Fq "## [${PACKAGE_VERSION}]" CHANGELOG.md; }; then - echo 'exists=true' >> "$GITHUB_OUTPUT" - else - echo 'exists=false' >> "$GITHUB_OUTPUT" - fi - - - name: Update CHANGELOG.md with Communique - if: >- - steps.release-metadata.outputs.should_run == 'true' && - steps.changelog-entry.outputs.exists == 'false' - shell: bash - env: - GITHUB_TOKEN: ${{ github.token }} - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - COMMUNIQUE_MODEL: ${{ vars.COMMUNIQUE_MODEL }} - RELEASE_TAG: ${{ steps.release-metadata.outputs.release_tag }} - run: | - set -euo pipefail - if [[ -z "${ANTHROPIC_API_KEY:-}" && -z "${OPENAI_API_KEY:-}" ]]; then - echo 'ANTHROPIC_API_KEY or OPENAI_API_KEY is required to generate release changelog entries with Communique.' >&2 - exit 1 - fi - if [[ -z "${ANTHROPIC_API_KEY:-}" && -z "${COMMUNIQUE_MODEL:-}" ]]; then - echo 'Set COMMUNIQUE_MODEL when using OPENAI_API_KEY so Communique can select an OpenAI-compatible model.' >&2 - exit 1 - fi - - communique_args=() - if [[ -n "${COMMUNIQUE_MODEL:-}" ]]; then - communique_args+=(--model "$COMMUNIQUE_MODEL") - fi - - communique generate "$RELEASE_TAG" \ - --changelog \ - --repo "$GITHUB_REPOSITORY" \ - "${communique_args[@]}" - - - name: Commit changelog update - id: commit_changelog - if: >- - steps.release-metadata.outputs.should_run == 'true' && - steps.changelog-entry.outputs.exists == 'false' - shell: bash - env: - GH_TOKEN: ${{ github.token }} - RELEASE_TAG: ${{ steps.release-metadata.outputs.release_tag }} - run: | - set -euo pipefail - if git diff --quiet -- CHANGELOG.md; then - echo 'CHANGELOG.md is already up to date.' - echo 'pushed=false' >> "$GITHUB_OUTPUT" - exit 0 - fi - - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git add CHANGELOG.md - git commit -m "docs: update changelog for ${RELEASE_TAG}" - auth_header="$(printf 'x-access-token:%s' "$GH_TOKEN" | base64 | tr -d '\n')" - git -c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \ - push "https://github.com/${GITHUB_REPOSITORY}.git" "HEAD:${GITHUB_HEAD_REF}" - echo 'pushed=true' >> "$GITHUB_OUTPUT" - - - name: Dispatch follow-up checks - if: steps.commit_changelog.outputs.pushed == 'true' - shell: bash - env: - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - gh workflow run ci.yml --ref "$GITHUB_HEAD_REF" - gh workflow run validate-skills.yml --ref "$GITHUB_HEAD_REF" diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 00000000..f15f4fb4 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,86 @@ +name: Release Please + +# Maintains the single release PR (version bump + Communique-written +# CHANGELOG section) and, when that PR merges, creates the tag + GitHub +# Release and dispatches the tag-driven Release pipeline. +# +# Runs release-please as a library (src/tools/release-please-runner.ts) +# instead of googleapis/release-please-action because the stock action cannot +# use a custom changelog generator, and this repo's changelog entries come +# from Communique. +on: + workflow_dispatch: + push: + branches: + - main + +concurrency: + group: release-please-${{ github.ref }} + cancel-in-progress: false + +permissions: + actions: write + contents: write + issues: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Check out main + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + ref: main + fetch-depth: 0 + persist-credentials: false + + - name: Set up mise + uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3 + with: + install_args: --locked + + - name: Install CI dependencies + run: mise run bootstrap-ci + + - name: Run release-please with Communique notes + id: release_please + env: + GITHUB_TOKEN: ${{ github.token }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + COMMUNIQUE_MODEL: ${{ vars.COMMUNIQUE_MODEL }} + run: npx tsx src/tools/release-please-runner.ts + + # Branch pushes made with the workflow token never trigger + # `pull_request` workflows, so dispatch the checks explicitly — the + # dispatched runs report against the branch head SHA and satisfy the + # release PR's required status checks. + - name: Dispatch checks onto the release PR branch + if: steps.release_please.outputs.prs_created == 'true' + env: + GH_TOKEN: ${{ github.token }} + PR_BRANCHES: ${{ steps.release_please.outputs.pr_branches }} + run: | + set -euo pipefail + read -ra branches <<< "$PR_BRANCHES" + for branch in "${branches[@]}"; do + gh workflow run ci.yml --ref "$branch" + gh workflow run validate-skills.yml --ref "$branch" + done + + # Tags created with the workflow token never trigger `push: tags` + # workflows, so start the Release pipeline (quality gates, verified + # tarball, GitHub Release assets, npm publish) explicitly. + - name: Dispatch the Release pipeline for created releases + if: steps.release_please.outputs.releases_created == 'true' + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAGS: ${{ steps.release_please.outputs.release_tags }} + run: | + set -euo pipefail + read -ra tags <<< "$RELEASE_TAGS" + for tag in "${tags[@]}"; do + gh workflow run release.yml --field "tag=$tag" + done diff --git a/.github/workflows/update-unreleased-changelog.yml b/.github/workflows/update-unreleased-changelog.yml deleted file mode 100644 index 17000fb2..00000000 --- a/.github/workflows/update-unreleased-changelog.yml +++ /dev/null @@ -1,137 +0,0 @@ -name: Update Unreleased Changelog - -on: - workflow_dispatch: - push: - branches: - - main - paths-ignore: - - CHANGELOG.md - -concurrency: - group: update-unreleased-changelog-${{ github.ref }} - cancel-in-progress: true - -permissions: - actions: write - contents: write - issues: read - pull-requests: write - -jobs: - update-unreleased-changelog: - runs-on: ubuntu-latest - timeout-minutes: 20 - env: - BASE_BRANCH: main - CHANGELOG_BRANCH: automation/update-unreleased-changelog - steps: - - name: Check out main - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - ref: main - fetch-depth: 0 - persist-credentials: false - - - name: Set up mise - uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3 - with: - install_args: --locked - - - name: Prepare changelog branch - run: git switch -c "$CHANGELOG_BRANCH" - - - name: Generate unreleased changelog - env: - GITHUB_TOKEN: ${{ github.token }} - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - COMMUNIQUE_MODEL: ${{ vars.COMMUNIQUE_MODEL }} - run: | - set -euo pipefail - if [[ -z "${ANTHROPIC_API_KEY:-}" && -z "${OPENAI_API_KEY:-}" ]]; then - echo 'ANTHROPIC_API_KEY or OPENAI_API_KEY is required to generate changelog entries with Communique.' >&2 - exit 1 - fi - if [[ -z "${ANTHROPIC_API_KEY:-}" && -z "${COMMUNIQUE_MODEL:-}" ]]; then - echo 'Set COMMUNIQUE_MODEL when using OPENAI_API_KEY so Communique can select an OpenAI-compatible model.' >&2 - exit 1 - fi - - communique_args=() - if [[ -n "${COMMUNIQUE_MODEL:-}" ]]; then - communique_args+=(--model "$COMMUNIQUE_MODEL") - fi - - communique generate HEAD \ - --changelog \ - --repo "$GITHUB_REPOSITORY" \ - "${communique_args[@]}" - - - name: Open or update changelog PR - id: changelog_pr - env: - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - if git diff --quiet -- CHANGELOG.md; then - echo 'CHANGELOG.md is already up to date.' - echo 'pushed=false' >> "$GITHUB_OUTPUT" - exit 0 - fi - - changed_files="$(git diff --name-only)" - if [[ "$changed_files" != 'CHANGELOG.md' ]]; then - printf 'Communique changed unexpected files:\n%s\n' "$changed_files" >&2 - exit 1 - fi - - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git add CHANGELOG.md - git commit -m 'docs: update unreleased changelog' - - auth_header="$(printf 'x-access-token:%s' "$GH_TOKEN" | base64 | tr -d '\n')" - git -c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \ - fetch --no-tags origin \ - "+refs/heads/${CHANGELOG_BRANCH}:refs/remotes/origin/${CHANGELOG_BRANCH}" || true - git -c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \ - push --force-with-lease origin "HEAD:${CHANGELOG_BRANCH}" - - body_file="$(mktemp)" - cat > "$body_file" <<'EOF' - ## Summary - - Updates the `[Unreleased]` section of `CHANGELOG.md` after changes merged to `main`. - - Generated by: - - ```sh - communique generate HEAD --changelog - ``` - EOF - - pr_number="$(gh pr list --head "$CHANGELOG_BRANCH" --state open --json number --jq '.[0].number // empty')" - if [[ -n "$pr_number" ]]; then - gh pr edit "$pr_number" \ - --title 'docs(CHANGELOG.md): update unreleased section' \ - --body-file "$body_file" - echo "Updated existing changelog PR #${pr_number}." - else - gh pr create \ - --head "$CHANGELOG_BRANCH" \ - --base "$BASE_BRANCH" \ - --title 'docs(CHANGELOG.md): update unreleased section' \ - --body-file "$body_file" - fi - - echo 'pushed=true' >> "$GITHUB_OUTPUT" - - - name: Dispatch follow-up checks - if: steps.changelog_pr.outputs.pushed == 'true' - env: - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - gh workflow run ci.yml --ref "$CHANGELOG_BRANCH" - gh workflow run validate-skills.yml --ref "$CHANGELOG_BRANCH" diff --git a/.release-it.json b/.release-it.json deleted file mode 100644 index 49c5e92e..00000000 --- a/.release-it.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "git": { - "commit": false, - "tag": false, - "push": false, - "requireBranch": false, - "requireCleanWorkingDir": false, - "requireCommits": false, - "requireUpstream": false - }, - "npm": { - "publish": false - }, - "github": { - "release": false - }, - "gitlab": { - "release": false - } -} diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 00000000..218393f3 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.4.1" +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 488f34b4..546e91d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,5 @@ # Changelog -## [Unreleased] - ## [v0.4.1] - 2026-06-12 ### Added diff --git a/aube-lock.yaml b/aube-lock.yaml index 6ed29f06..ddb445af 100644 --- a/aube-lock.yaml +++ b/aube-lock.yaml @@ -6,41 +6,32 @@ settings: time: '@alcalzone/ansi-tokenize@0.3.0': 2026-02-20T13:12:49.245Z + '@babel/code-frame@7.29.7': 2026-05-25T11:15:38.208Z + '@babel/helper-validator-identifier@7.29.7': 2026-05-25T11:15:31.539Z '@coder/libghostty-vt-node@0.1.0-beta.0': 2026-04-24T16:17:20.231Z + '@conventional-commits/parser@0.4.1': 2021-01-07T02:41:52.208Z '@esbuild/darwin-arm64@0.27.7': 2026-04-02T16:41:35.638Z '@esbuild/linux-arm64@0.27.7': 2026-04-02T16:42:03.486Z '@esbuild/linux-x64@0.27.7': 2026-04-02T16:42:42.543Z '@esbuild/win32-arm64@0.27.7': 2026-04-02T16:43:29.208Z '@esbuild/win32-x64@0.27.7': 2026-04-02T16:43:41.245Z - '@inquirer/ansi@2.0.5': 2026-04-06T19:34:41.750Z - '@inquirer/checkbox@5.1.4': 2026-04-19T16:52:11.002Z - '@inquirer/confirm@6.0.12': 2026-04-19T16:52:10.489Z - '@inquirer/core@11.1.9': 2026-04-19T16:52:08.934Z - '@inquirer/editor@5.1.1': 2026-04-19T16:52:10.876Z - '@inquirer/expand@5.0.13': 2026-04-19T16:52:10.602Z - '@inquirer/external-editor@3.0.0': 2026-04-06T19:34:41.740Z - '@inquirer/figures@2.0.5': 2026-04-06T19:34:41.706Z - '@inquirer/input@5.0.12': 2026-04-19T16:52:10.998Z - '@inquirer/number@4.0.12': 2026-04-19T16:52:10.781Z - '@inquirer/password@5.0.12': 2026-04-19T16:52:10.821Z - '@inquirer/prompts@8.4.2': 2026-04-19T16:52:12.930Z - '@inquirer/rawlist@5.2.8': 2026-04-19T16:52:10.791Z - '@inquirer/search@4.1.8': 2026-04-19T16:52:10.778Z - '@inquirer/select@5.1.4': 2026-04-19T16:52:11.134Z - '@inquirer/type@4.0.5': 2026-04-06T19:34:41.698Z + '@google-automations/git-file-utils@3.0.1': 2026-04-01T16:30:19.463Z + '@iarna/toml@3.0.0': 2020-04-23T20:50:05.483Z '@jridgewell/sourcemap-codec@1.5.5': 2025-08-12T06:43:59.139Z - '@octokit/auth-token@6.0.0': 2025-05-20T06:25:24.426Z - '@octokit/core@7.0.6': 2025-10-31T00:26:10.155Z - '@octokit/endpoint@11.0.3': 2026-02-18T21:20:20.117Z - '@octokit/graphql@9.0.3': 2025-10-31T00:04:07.797Z - '@octokit/openapi-types@27.0.0': 2025-10-30T21:46:34.069Z - '@octokit/plugin-paginate-rest@14.0.0': 2025-10-31T01:55:14.414Z - '@octokit/plugin-request-log@6.0.0': 2025-05-20T17:01:35.869Z - '@octokit/plugin-rest-endpoint-methods@17.0.0': 2025-10-31T01:16:01.283Z - '@octokit/request-error@7.1.0': 2025-11-13T18:32:02.043Z - '@octokit/request@10.0.8': 2026-02-20T20:21:37.663Z - '@octokit/rest@22.0.1': 2025-10-31T20:59:36.519Z - '@octokit/types@16.0.0': 2025-10-30T21:58:24.702Z + '@jsep-plugin/assignment@1.3.0': 2024-11-05T14:51:18.851Z + '@jsep-plugin/regex@1.0.4': 2024-11-05T14:55:29.008Z + '@octokit/auth-token@4.0.0': 2023-06-12T20:40:41.179Z + '@octokit/core@5.2.2': 2025-07-11T00:26:25.656Z + '@octokit/endpoint@9.0.6': 2025-02-14T21:30:48.886Z + '@octokit/graphql@7.1.1': 2025-02-20T20:36:37.468Z + '@octokit/openapi-types@24.2.0': 2025-03-18T23:18:11.768Z + '@octokit/plugin-paginate-rest@11.4.4-cjs.2': 2025-02-26T22:00:43.096Z + '@octokit/plugin-request-log@4.0.1': 2024-03-01T00:34:38.291Z + '@octokit/plugin-rest-endpoint-methods@13.3.2-cjs.1': 2025-02-26T21:50:02.895Z + '@octokit/request-error@5.1.1': 2025-02-14T22:27:01.469Z + '@octokit/request@8.4.1': 2025-02-15T00:08:47.891Z + '@octokit/rest@20.1.2': 2025-02-26T22:20:28.740Z + '@octokit/types@13.10.0': 2025-03-18T23:28:55.056Z '@oxc-project/types@0.128.0': 2026-04-27T11:33:26.853Z '@oxfmt/binding-darwin-arm64@0.47.0': 2026-04-27T12:22:26.779Z '@oxfmt/binding-linux-arm64-gnu@0.47.0': 2026-04-27T12:22:40.334Z @@ -61,7 +52,6 @@ time: '@oxlint/binding-linux-x64-musl@1.62.0': 2026-04-27T12:07:28.786Z '@oxlint/binding-win32-arm64-msvc@1.62.0': 2026-04-27T12:05:47.226Z '@oxlint/binding-win32-x64-msvc@1.62.0': 2026-04-27T12:07:08.230Z - '@phun-ky/typeof@2.0.3': 2025-09-15T18:39:08.703Z '@rolldown/binding-darwin-arm64@1.0.0-rc.18': 2026-04-29T13:43:27.149Z '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.18': 2026-04-29T13:43:21.315Z '@rolldown/binding-linux-arm64-musl@1.0.0-rc.18': 2026-04-29T13:43:32.890Z @@ -74,9 +64,12 @@ time: '@types/chai@5.2.3': 2025-10-20T23:32:43.277Z '@types/deep-eql@4.0.2': 2023-11-07T01:34:12.026Z '@types/estree@1.0.9': 2026-05-06T21:01:00.975Z + '@types/minimist@1.2.5': 2023-11-07T11:11:21.661Z '@types/node@25.5.0': 2026-03-12T15:48:00.014Z - '@types/parse-path@7.1.0': 2025-05-05T20:03:45.729Z + '@types/normalize-package-data@2.4.4': 2023-11-07T12:04:03.711Z + '@types/npm-package-arg@6.1.4': 2023-11-07T12:06:21.829Z '@types/react@19.2.16': 2026-06-01T18:00:27.791Z + '@types/unist@2.0.11': 2024-08-15T02:19:24.090Z '@vitest/expect@4.1.2': 2026-03-26T14:36:39.253Z '@vitest/mocker@4.1.2': 2026-03-26T14:36:29.326Z '@vitest/pretty-format@4.1.2': 2026-03-26T14:36:18.500Z @@ -84,96 +77,109 @@ time: '@vitest/snapshot@4.1.2': 2026-03-26T14:36:35.845Z '@vitest/spy@4.1.2': 2026-03-26T14:36:22.490Z '@vitest/utils@4.1.2': 2026-03-26T14:36:25.794Z - agent-base@8.0.0: 2026-03-11T16:58:24.478Z + '@xmldom/xmldom@0.8.13': 2026-04-18T11:27:55.806Z + agent-base@7.1.4: 2025-07-07T17:44:01.356Z ansi-escapes@7.3.0: 2026-02-04T16:21:10.230Z + ansi-regex@5.0.1: 2021-09-14T15:55:19.540Z ansi-regex@6.2.2: 2025-09-08T14:48:14.264Z + ansi-styles@4.3.0: 2020-10-04T19:18:25.986Z ansi-styles@6.2.3: 2025-09-08T14:52:15.705Z + argparse@2.0.1: 2020-08-28T21:14:26.713Z + array-ify@1.0.0: 2015-06-14T23:27:57.755Z + arrify@1.0.1: 2015-12-09T17:48:38.769Z assertion-error@2.0.1: 2023-10-18T18:04:13.507Z - ast-types@0.13.4: 2020-08-23T22:36:07.885Z async-retry@1.3.3: 2021-08-17T02:10:33.288Z auto-bind@5.0.1: 2021-10-18T05:54:17.480Z balanced-match@4.0.4: 2026-02-22T11:38:25.951Z - basic-ftp@5.3.1: 2026-04-28T03:37:53.895Z - before-after-hook@4.0.0: 2025-05-12T22:37:33.860Z + before-after-hook@2.2.3: 2022-10-04T00:19:26.570Z + boolbase@1.0.0: 2014-02-15T14:44:50.620Z brace-expansion@5.0.5: 2026-03-24T17:58:07.554Z - bundle-name@4.1.0: 2023-12-18T23:18:40.946Z - c12@3.3.3: 2025-12-16T18:53:21.708Z + camelcase-keys@6.2.2: 2020-04-03T03:51:03.816Z + camelcase@5.3.1: 2019-04-03T13:34:32.701Z chai@6.2.2: 2025-12-22T21:26:03.989Z + chalk@4.1.2: 2021-07-30T12:02:52.839Z chalk@5.6.2: 2025-09-08T14:47:54.486Z - chardet@2.1.1: 2025-10-29T22:15:03.874Z - chokidar@5.0.0: 2025-11-25T23:28:06.854Z - ci-info@4.4.0: 2026-01-29T16:23:16.342Z - citty@0.1.6: 2024-02-14T13:09:33.620Z - citty@0.2.2: 2026-04-01T18:24:39.653Z cli-boxes@4.0.1: 2024-08-04T17:57:54.988Z cli-cursor@4.0.0: 2021-08-23T19:35:19.940Z - cli-cursor@5.0.0: 2024-07-26T14:02:26.233Z - cli-spinners@3.4.0: 2026-01-13T15:09:46.171Z cli-truncate@6.0.0: 2026-04-04T13:38:05.476Z - cli-width@4.1.0: 2023-08-05T16:14:01.241Z + cliui@8.0.1: 2022-10-01T01:16:14.782Z code-excerpt@4.0.0: 2022-02-06T10:11:50.660Z + color-convert@2.0.1: 2019-08-19T21:05:36.605Z + color-name@1.1.4: 2018-09-21T10:48:56.546Z commander@14.0.3: 2026-01-31T01:47:17.592Z - confbox@0.2.4: 2026-02-06T12:11:42.581Z - consola@3.4.2: 2025-03-18T10:17:43.596Z + compare-func@2.0.0: 2020-05-15T13:35:21.878Z + conventional-changelog-conventionalcommits@6.1.0: 2023-06-17T21:31:22.730Z + conventional-changelog-writer@6.0.1: 2023-07-09T14:50:44.698Z + conventional-commits-filter@3.0.0: 2023-06-06T14:33:57.369Z convert-source-map@2.0.0: 2022-10-17T22:06:48.628Z convert-to-spaces@2.0.1: 2022-02-06T10:05:09.499Z + css-select@5.2.2: 2025-06-28T15:49:53.402Z + css-what@6.2.2: 2025-06-28T15:45:55.599Z csstype@3.2.3: 2025-11-17T11:42:27.045Z - data-uri-to-buffer@7.0.0: 2026-03-11T16:58:27.424Z + dateformat@3.0.3: 2018-02-09T20:05:42.054Z debug@4.4.3: 2025-09-13T17:25:19.732Z - default-browser-id@5.0.1: 2025-11-14T07:11:37.981Z - default-browser@5.5.0: 2026-02-02T19:40:52.509Z - define-lazy-prop@3.0.0: 2021-04-14T07:54:05.644Z - defu@6.1.7: 2026-04-07T09:28:09.388Z - degenerator@6.0.0: 2026-03-11T16:58:30.708Z - destr@2.0.5: 2025-04-03T16:41:15.624Z + decamelize-keys@1.1.1: 2022-10-30T10:36:49.464Z + decamelize@1.2.0: 2016-03-05T08:49:10.462Z + deprecation@2.3.1: 2019-06-13T17:45:59.689Z + detect-indent@6.1.0: 2021-05-28T06:44:53.545Z detect-libc@2.1.2: 2025-10-05T12:46:33.077Z - dotenv@17.4.2: 2026-04-12T16:41:11.574Z + diff@8.0.4: 2026-03-23T11:24:30.551Z + dom-serializer@2.0.0: 2022-04-09T17:21:48.457Z + domelementtype@2.3.0: 2022-04-07T11:41:20.402Z + domhandler@5.0.3: 2022-04-30T11:00:14.978Z + domutils@3.2.2: 2025-01-06T20:05:27.541Z + dot-prop@5.3.0: 2020-09-06T14:14:50.395Z + emoji-regex@8.0.0: 2019-03-05T18:58:23.040Z + entities@4.5.0: 2023-04-13T17:57:56.308Z environment@1.1.0: 2024-05-14T07:02:20.386Z + error-ex@1.3.4: 2025-09-15T14:58:41.266Z + es-errors@1.3.0: 2024-02-05T08:05:51.479Z es-module-lexer@2.1.0: 2026-04-25T22:50:31.041Z es-toolkit@1.47.0: 2026-05-25T08:00:38.148Z esbuild@0.27.7: 2026-04-02T16:43:44.831Z + escalade@3.2.0: 2024-08-29T22:59:36.690Z + escape-string-regexp@1.0.5: 2016-02-21T12:55:17.074Z escape-string-regexp@2.0.0: 2019-04-17T07:49:09.559Z - escodegen@2.1.0: 2023-06-29T20:18:37.341Z - esprima@4.0.1: 2018-07-13T08:39:14.711Z - estraverse@5.3.0: 2021-10-25T12:18:17.722Z estree-walker@3.0.3: 2023-01-20T02:27:12.467Z - esutils@2.0.3: 2019-07-31T01:10:54.228Z - eta@4.5.1: 2026-02-05T21:51:59.381Z expect-type@1.3.0: 2025-12-08T13:04:58.798Z - exsolve@1.0.8: 2025-11-10T09:03:08.662Z - fast-content-type-parse@3.0.0: 2025-03-08T16:33:03.786Z - fast-string-truncated-width@3.0.3: 2025-09-30T19:48:51.801Z - fast-string-width@3.0.2: 2025-09-30T10:31:05.462Z - fast-wrap-ansi@0.2.0: 2025-09-30T17:51:16.514Z fdir@6.5.0: 2025-08-14T16:56:03.948Z + figures@3.2.0: 2020-02-16T14:55:52.820Z + find-up@4.1.0: 2019-06-17T06:20:34.221Z fsevents@2.3.2: 2021-02-05T14:46:33.925Z fsevents@2.3.3: 2023-08-21T16:24:22.854Z + function-bind@1.1.2: 2023-10-12T19:08:19.687Z + get-caller-file@2.0.5: 2019-03-09T21:48:30.527Z get-east-asian-width@1.5.0: 2026-02-18T16:34:54.366Z get-tsconfig@4.14.0: 2026-04-15T02:51:47.332Z - get-uri@7.0.0: 2026-03-11T16:58:39.170Z ghostty-web@0.4.0: 2025-12-09T03:11:18.721Z - giget@2.0.0: 2025-02-25T20:22:48.094Z - git-up@8.1.1: 2025-04-11T10:59:36.994Z - git-url-parse@16.1.0: 2025-04-15T07:02:58.203Z glob@13.0.6: 2026-02-19T17:26:33.269Z - http-proxy-agent@8.0.0: 2026-03-11T16:58:43.756Z - https-proxy-agent@8.0.0: 2026-03-11T16:58:46.683Z - iconv-lite@0.7.2: 2026-01-08T16:49:11.167Z + handlebars@4.7.9: 2026-03-26T20:46:39.280Z + hard-rejection@2.1.0: 2019-04-02T02:45:26.609Z + has-flag@4.0.0: 2019-04-06T15:49:21.907Z + hasown@2.0.4: 2026-05-28T18:11:39.940Z + he@1.2.0: 2018-09-23T17:18:22.163Z + hosted-git-info@2.8.9: 2021-04-07T20:04:34.547Z + hosted-git-info@4.1.0: 2022-01-06T16:16:49.728Z + http-proxy-agent@7.0.2: 2024-02-15T19:14:20.532Z + https-proxy-agent@7.0.6: 2024-12-07T03:32:09.302Z + indent-string@4.0.0: 2019-04-17T07:31:13.363Z indent-string@5.0.0: 2021-04-17T17:10:20.469Z ink@7.0.5: 2026-05-29T21:15:43.919Z - ip-address@10.2.0: 2026-05-01T06:34:05.804Z - is-docker@3.0.0: 2021-08-31T23:02:35.148Z + is-arrayish@0.2.1: 2015-08-31T23:02:50.373Z + is-core-module@2.16.2: 2026-05-05T17:34:49.209Z + is-fullwidth-code-point@3.0.0: 2019-03-18T08:09:05.035Z is-fullwidth-code-point@5.1.0: 2025-08-31T07:37:04.805Z is-in-ci@2.0.0: 2025-08-17T11:37:36.590Z - is-in-ssh@1.0.0: 2025-11-12T15:31:49.173Z - is-inside-container@1.0.0: 2023-02-15T06:50:49.428Z - is-interactive@2.0.0: 2021-05-03T20:19:09.981Z - is-ssh@1.4.1: 2025-02-13T20:16:53.384Z - is-unicode-supported@2.1.0: 2024-09-09T06:26:03.073Z - is-wsl@3.1.1: 2026-02-15T08:45:04.136Z - issue-parser@7.0.1: 2024-05-31T23:18:58.778Z + is-obj@2.0.0: 2019-04-19T15:37:37.870Z + is-plain-obj@1.1.0: 2015-11-05T09:31:58.189Z jiti@2.7.0: 2026-05-05T21:02:47.918Z - json-with-bigint@3.5.8: 2026-03-19T19:58:42.290Z + js-tokens@4.0.0: 2018-01-28T11:58:58.170Z + js-yaml@4.2.0: 2026-05-31T22:17:13.783Z + jsep@1.4.0: 2024-11-05T14:49:55.640Z + json-parse-even-better-errors@2.3.1: 2020-09-02T16:37:58.371Z + json-stringify-safe@5.0.1: 2015-05-19T01:42:09.719Z + jsonpath-plus@10.4.0: 2026-02-16T03:04:22.106Z + kind-of@6.0.3: 2020-01-16T16:59:19.808Z lightningcss-darwin-arm64@1.32.0: 2026-03-09T04:23:07.357Z lightningcss-linux-arm64-gnu@1.32.0: 2026-03-09T04:23:18.594Z lightningcss-linux-arm64-musl@1.32.0: 2026-03-09T04:23:21.203Z @@ -182,125 +188,134 @@ time: lightningcss-win32-arm64-msvc@1.32.0: 2026-03-09T04:23:30.176Z lightningcss-win32-x64-msvc@1.32.0: 2026-03-09T04:23:33.073Z lightningcss@1.32.0: 2026-03-09T04:23:43.754Z - lodash.capitalize@4.2.1: 2016-08-13T17:37:07.150Z - lodash.escaperegexp@4.1.2: 2016-07-25T14:43:16.424Z - lodash.isplainobject@4.0.6: 2016-08-13T17:41:07.730Z - lodash.isstring@4.0.1: 2016-02-03T07:28:59.695Z - lodash.merge@4.6.2: 2019-07-10T00:19:41.667Z - lodash.uniqby@4.7.0: 2016-08-13T17:46:46.042Z - log-symbols@7.0.1: 2025-05-21T13:47:39.089Z + lines-and-columns@1.2.4: 2021-11-21T03:31:07.871Z + locate-path@5.0.0: 2019-05-04T13:00:07.428Z + lodash.ismatch@4.4.0: 2016-08-13T17:40:50.466Z lru-cache@11.3.6: 2026-05-04T15:35:22.273Z - lru-cache@7.18.3: 2023-03-05T18:04:26.750Z - macos-release@3.4.0: 2025-06-11T18:30:18.212Z + lru-cache@6.0.0: 2020-07-11T00:59:07.352Z magic-string@0.30.21: 2025-10-24T00:59:47.122Z - mime-db@1.54.0: 2025-03-18T15:06:44.354Z - mime-types@3.0.2: 2025-11-20T11:12:29.693Z + map-obj@1.0.1: 2015-05-02T18:02:46.655Z + map-obj@4.3.0: 2021-09-19T10:14:33.997Z + meow@8.1.2: 2021-01-07T11:21:04.505Z mimic-fn@2.1.0: 2019-03-31T17:53:34.125Z - mimic-function@5.0.1: 2024-03-14T08:37:42.297Z + min-indent@1.0.1: 2020-05-28T19:20:06.032Z minimatch@10.2.5: 2026-03-30T18:08:07.247Z + minimist-options@4.1.0: 2020-05-16T13:05:47.807Z + minimist@1.2.8: 2023-02-09T20:59:49.233Z minipass@7.1.3: 2026-02-19T00:34:33.886Z + modify-values@1.0.1: 2018-03-23T07:35:47.388Z ms@2.1.3: 2020-12-08T13:54:35.223Z - mute-stream@3.0.0: 2025-10-22T15:27:56.606Z nanoid@3.3.12: 2026-04-30T22:04:14.515Z - netmask@2.1.1: 2026-04-08T16:59:06.620Z - new-github-release-url@2.0.0: 2021-10-18T15:50:05.060Z + neo-async@2.6.2: 2020-07-09T18:23:53.065Z node-addon-api@7.1.1: 2024-07-12T10:15:07.595Z node-addon-api@8.7.0: 2026-03-26T01:10:33.995Z - node-fetch-native@1.6.7: 2025-08-02T10:15:36.735Z node-gyp-build@4.8.4: 2024-11-19T14:43:46.572Z + node-html-parser@6.1.13: 2024-03-29T01:09:52.774Z node-pty@1.1.0: 2025-12-22T13:51:43.876Z - nypm@0.6.6: 2026-04-24T17:28:57.939Z + normalize-package-data@2.5.0: 2019-02-05T20:25:23.279Z + normalize-package-data@3.0.3: 2021-08-18T15:50:52.754Z + nth-check@2.1.1: 2022-05-23T10:05:21.450Z obug@2.1.1: 2025-11-22T09:31:55.146Z - ohash@2.0.11: 2025-03-04T17:39:01.858Z + once@1.4.0: 2016-09-06T21:11:09.367Z onetime@5.1.2: 2020-08-09T20:29:04.963Z - onetime@7.0.0: 2023-11-05T20:42:03.405Z - open@11.0.0: 2025-11-15T08:22:54.225Z - ora@9.3.0: 2026-02-05T04:33:37.616Z - os-name@7.0.0: 2026-02-02T15:03:15.070Z oxfmt@0.47.0: 2026-04-27T12:23:53.802Z oxlint-tsgolint@0.22.1: 2026-04-27T14:59:54.947Z oxlint@1.62.0: 2026-04-27T12:07:33.195Z - pac-proxy-agent@8.0.0: 2026-03-11T16:58:56.163Z - pac-resolver@8.0.0: 2026-03-11T16:58:49.790Z + p-limit@2.3.0: 2020-04-05T15:40:45.137Z + p-locate@4.1.0: 2019-04-04T05:02:46.111Z + p-try@2.2.0: 2019-03-31T12:34:35.198Z package-json-from-dist@1.0.1: 2024-09-26T18:59:08.941Z - parse-path@7.1.0: 2025-04-15T07:02:16.169Z - parse-url@9.2.0: 2024-04-03T05:25:19.419Z + parse-github-repo-url@1.4.1: 2017-08-31T03:52:10.755Z + parse-json@5.2.0: 2021-01-18T11:15:13.459Z patch-console@2.0.0: 2022-02-04T09:24:24.525Z + path-exists@4.0.0: 2019-04-04T03:29:16.887Z + path-parse@1.0.7: 2021-05-25T12:57:37.333Z path-scurry@2.0.2: 2026-02-19T17:20:00.211Z pathe@2.0.3: 2025-02-11T19:47:35.862Z - perfect-debounce@2.1.0: 2026-01-21T23:47:33.457Z picocolors@1.1.1: 2024-10-16T18:20:03.921Z picomatch@4.0.4: 2026-03-23T20:39:47.960Z - pkg-types@2.3.1: 2026-04-27T10:24:15.497Z playwright-core@1.60.0: 2026-05-11T19:09:40.047Z playwright@1.60.0: 2026-05-11T19:09:33.114Z postcss@8.5.14: 2026-05-04T16:43:35.284Z - powershell-utils@0.1.0: 2025-11-15T08:10:24.411Z - powershell-utils@0.2.0: 2026-01-25T03:39:53.218Z - protocols@2.0.2: 2025-02-14T01:18:58.734Z - proxy-agent@7.0.0: 2026-03-11T16:58:59.093Z - proxy-from-env@1.1.0: 2020-03-04T03:35:19.815Z - quickjs-wasi@0.0.1: 2026-03-06T21:20:04.435Z - rc9@2.1.2: 2024-04-09T17:15:22.489Z + quick-lru@4.0.1: 2019-05-29T17:21:30.565Z react-reconciler@0.33.0: 2025-10-01T21:39:00.081Z react@19.2.7: 2026-06-01T18:00:48.323Z - readdirp@5.0.0: 2025-11-25T23:11:27.476Z - release-it@20.0.1: 2026-04-24T09:06:54.177Z + read-pkg-up@7.0.1: 2019-12-06T08:19:28.254Z + read-pkg@5.2.0: 2019-07-11T08:30:48.441Z + redent@3.0.0: 2019-04-26T06:38:25.407Z + release-please@17.9.0: 2026-06-09T22:21:18.184Z + require-directory@2.1.1: 2015-05-28T08:31:04.702Z resolve-pkg-maps@1.0.0: 2022-12-14T15:37:46.218Z + resolve@1.22.12: 2026-04-11T17:41:35.049Z restore-cursor@4.0.0: 2021-08-23T19:27:21.792Z - restore-cursor@5.1.0: 2024-07-26T13:56:42.169Z retry@0.13.1: 2021-06-21T07:45:32.286Z rimraf@6.1.3: 2026-02-16T00:59:39.538Z rolldown@1.0.0-rc.18: 2026-04-29T13:44:09.738Z - run-applescript@7.1.0: 2025-09-09T13:42:36.477Z - safer-buffer@2.1.2: 2018-04-08T10:42:42.130Z scheduler@0.27.0: 2025-10-01T21:39:15.208Z + semver@5.7.2: 2023-07-10T19:57:47.111Z semver@7.7.4: 2026-02-05T17:23:11.131Z siginfo@2.0.0: 2020-06-16T20:30:23.515Z signal-exit@3.0.7: 2022-02-03T21:05:34.544Z - signal-exit@4.1.0: 2023-07-29T05:44:56.721Z slice-ansi@9.0.0: 2026-04-04T13:19:33.198Z - smart-buffer@4.2.0: 2021-08-07T06:18:54.397Z - socks-proxy-agent@9.0.0: 2026-03-11T16:58:53.061Z - socks@2.8.8: 2026-04-28T06:19:04.302Z source-map-js@1.2.1: 2024-09-08T16:22:55.645Z source-map@0.6.1: 2017-09-29T14:42:30.948Z + spdx-correct@3.2.0: 2023-03-07T01:53:18.381Z + spdx-exceptions@2.5.0: 2024-02-15T03:06:38.111Z + spdx-expression-parse@3.0.1: 2020-05-13T16:12:46.317Z + spdx-license-ids@3.0.23: 2026-02-20T23:02:57.930Z + split@1.0.1: 2017-08-03T08:03:58.517Z stack-utils@2.0.6: 2022-11-08T15:39:54.449Z stackback@0.0.2: 2012-10-20T00:56:54.591Z std-env@4.1.0: 2026-04-15T08:02:30.338Z - stdin-discarder@0.3.2: 2026-04-14T07:35:35.513Z + string-width@4.2.3: 2021-09-23T17:17:21.341Z string-width@8.2.1: 2026-04-27T05:27:21.188Z + strip-ansi@6.0.1: 2021-09-23T16:34:41.798Z strip-ansi@7.2.0: 2026-02-26T13:51:11.099Z + strip-indent@3.0.0: 2019-04-17T02:21:49.701Z + supports-color@7.2.0: 2020-08-28T11:17:34.870Z + supports-preserve-symlinks-flag@1.0.0: 2022-01-03T07:22:56.640Z tagged-tag@1.0.0: 2025-05-12T05:23:55.484Z terminal-size@4.0.1: 2026-02-02T05:23:26.211Z + through@2.3.8: 2015-07-03T13:38:39.650Z tinybench@2.9.0: 2024-08-02T15:09:44.961Z tinyexec@1.1.2: 2026-04-29T07:40:28.138Z tinyglobby@0.2.15: 2025-09-06T18:52:04.151Z tinyglobby@0.2.16: 2026-04-07T23:37:03.457Z tinypool@2.1.0: 2026-01-03T07:37:52.918Z tinyrainbow@3.1.0: 2026-03-12T09:21:01.296Z - tslib@2.8.1: 2024-10-31T22:42:48.624Z + trim-newlines@3.0.1: 2021-05-28T16:46:12.397Z tsx@4.21.0: 2025-11-30T15:56:09.488Z - type-fest@2.19.0: 2022-08-22T17:20:40.104Z + type-fest@0.18.1: 2020-11-12T14:31:51.682Z + type-fest@0.6.0: 2019-07-05T09:46:43.673Z + type-fest@0.8.1: 2019-09-25T09:33:46.409Z + type-fest@3.13.1: 2023-07-16T09:50:11.856Z type-fest@5.7.0: 2026-05-31T19:28:39.290Z + typescript@4.9.5: 2023-01-30T21:05:37.231Z typescript@5.9.3: 2025-09-30T21:19:38.784Z + uglify-js@3.19.3: 2024-08-29T13:49:01.316Z ulid@3.0.2: 2025-11-30T20:02:50.707Z undici-types@7.18.2: 2026-01-06T15:57:40.133Z - undici@7.24.5: 2026-03-19T22:16:47.238Z - universal-user-agent@7.0.3: 2025-05-12T03:35:01.466Z - url-join@5.0.0: 2022-03-23T21:03:24.801Z + unist-util-is@4.1.0: 2021-03-07T14:27:40.795Z + unist-util-visit-parents@3.1.1: 2020-10-19T11:50:34.056Z + unist-util-visit@2.0.3: 2020-07-11T09:47:14.798Z + universal-user-agent@6.0.1: 2023-11-04T22:29:03.740Z + validate-npm-package-license@3.0.4: 2018-08-05T16:59:03.230Z vite@8.0.11: 2026-05-07T06:05:59.982Z vitest@4.1.2: 2026-03-26T14:36:51.447Z why-is-node-running@2.3.0: 2024-07-08T12:57:23.951Z widest-line@6.0.0: 2026-01-24T03:25:03.834Z - wildcard-match@5.1.4: 2024-12-17T17:21:21.445Z - windows-release@7.1.1: 2026-02-24T15:25:50.430Z + wordwrap@1.0.0: 2015-05-07T17:07:25.416Z wrap-ansi@10.0.0: 2026-02-20T10:03:54.703Z + wrap-ansi@7.0.0: 2020-04-22T16:53:23.889Z + wrappy@1.0.2: 2016-05-17T23:30:52.415Z ws@8.20.0: 2026-03-21T17:31:08.578Z - wsl-utils@0.3.1: 2026-01-02T13:12:04.090Z + xpath@0.0.34: 2023-12-16T14:31:54.702Z + y18n@5.0.8: 2021-04-07T18:57:33.969Z + yallist@4.0.0: 2019-09-30T20:08:08.970Z yaml@2.8.4: 2026-05-02T09:09:40.093Z - yargs-parser@22.0.0: 2025-05-26T20:12:00.864Z - yoctocolors@2.1.2: 2025-08-19T15:19:23.680Z + yargs-parser@20.2.9: 2021-06-20T23:54:31.941Z + yargs-parser@21.1.1: 2022-08-04T21:13:43.411Z + yargs@17.7.2: 2023-04-27T19:59:02.861Z yoga-layout@3.2.1: 2024-12-13T01:45:49.754Z zod@4.3.6: 2026-01-22T19:14:35.382Z @@ -351,9 +366,9 @@ importers: oxlint-tsgolint: specifier: 0.22.1 version: 0.22.1 - release-it: - specifier: 20.0.1 - version: 20.0.1(@octokit/core@7.0.6)(@types/node@25.5.0)(picomatch@4.0.4)(quickjs-wasi@0.0.1) + release-please: + specifier: 17.9.0 + version: 17.9.0(@octokit/core@5.2.2)(jsep@1.4.0) rimraf: specifier: 6.1.3 version: 6.1.3 @@ -377,10 +392,21 @@ packages: resolution: {integrity: sha512-p+CMKJ93HFmLkjXKlXiVGlMQEuRb6H0MokBSwUsX+S6BRX8eV5naFZpQJFfJHjRZY0Hmnqy1/r6UWl3x+19zYA==} engines: {node: '>=18'} + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + '@coder/libghostty-vt-node@0.1.0-beta.0': resolution: {integrity: sha512-m0KqgBfEzD7G6DIFWe3OSGoa0muDHneXDKHDVz8sJdSYl2N8HN9W642H3LSw2YDkw9VuxxkbHqXdN98akkqwxQ==} engines: {node: '>=20.19'} + '@conventional-commits/parser@0.4.1': + resolution: {integrity: sha512-H2ZmUVt6q+KBccXfMBhbBF14NlANeqHTXL4qCL6QGbMzrc4HDXyzWuxPxPNbz71f/5UkR5DrycP5VO9u7crahg==} + '@esbuild/darwin-arm64@0.27.7': resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} engines: {node: '>=18'} @@ -421,194 +447,79 @@ packages: cpu: - x64 - '@inquirer/ansi@2.0.5': - resolution: {integrity: sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - - '@inquirer/checkbox@5.1.4': - resolution: {integrity: sha512-w6KF8ZYRvqHhROkOTHXYC3qIV/KYEu5o12oLqQySvch61vrYtRxNSHTONSdJqWiFJPlCUQAHT5OgOIyuTr+MHQ==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/confirm@6.0.12': - resolution: {integrity: sha512-h9FgGun3QwVYNj5TWIZZ+slii73bMoBFjPfVIGtnFuL4t8gBiNDV9PcSfIzkuxvgquJKt9nr1QzszpBzTbH8Og==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/core@11.1.9': - resolution: {integrity: sha512-BDE4fG22uYh1bGSifcj7JSx119TVYNViMhMu85usp4Fswrzh6M0DV3yld64jA98uOAa2GSQ4Bg4bZRm2d2cwSg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/editor@5.1.1': - resolution: {integrity: sha512-6y11LgmNpmn5D2aB5FgnCfBUBK8ZstwLCalyJmORcJZ/WrhOjm16mu6eSqIx8DnErxDqSLr+Jkp+GP8/Nwd5tA==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/expand@5.0.13': - resolution: {integrity: sha512-dF2zvrFo9LshkcB23/O1il13kBkBltWIXzut1evfbuBLXMiGIuC45c+ZQ0uukjCDsvI8OWqun4FRYMnzFCQa3g==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/external-editor@3.0.0': - resolution: {integrity: sha512-lDSwMgg+M5rq6JKBYaJwSX6T9e/HK2qqZ1oxmOwn4AQoJE5D+7TumsxLGC02PWS//rkIVqbZv3XA3ejsc9FYvg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/figures@2.0.5': - resolution: {integrity: sha512-NsSs4kzfm12lNetHwAn3GEuH317IzpwrMCbOuMIVytpjnJ90YYHNwdRgYGuKmVxwuIqSgqk3M5qqQt1cDk0tGQ==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - - '@inquirer/input@5.0.12': - resolution: {integrity: sha512-uiMFBl4LqFzJClh80Q3f9hbOFJ6kgkDWI4LjAeBuyO6EanVVMF69AgOvpi1qdqjDSjDN6578B6nky9ceEpI+1Q==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/number@4.0.12': - resolution: {integrity: sha512-/vrwhEf7Xsuh+YlHF4IjSy3g1cyrQuPaSiHIxCEbLu8qnfvrcvJyCkoktOOF+xV9gSb77/G0n3h04RbMDW2sIg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/password@5.0.12': - resolution: {integrity: sha512-CBh7YHju623lxJRcAOo498ZUwIuMy63bqW/vVq0tQAZVv+lkWlHkP9ealYE1utWSisEShY5VMdzIXRmyEODzcQ==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/prompts@8.4.2': - resolution: {integrity: sha512-XJmn/wY4AX56l1BRU+ZjDrFtg9+2uBEi4JvJQj82kwJDQKiPgSn4CEsbfGGygS4Gw6rkL4W18oATjfVfaqub2Q==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true + '@google-automations/git-file-utils@3.0.1': + resolution: {integrity: sha512-vlQZ8DlBcippB5zTY0M5Rib8tKT4yQ7oBKbs6kcWAzp70oyillKinXLZwlIgNTmfzzZx1J6cez3M0EmrpXFRcw==} + engines: {node: '>= 20'} - '@inquirer/rawlist@5.2.8': - resolution: {integrity: sha512-Su7FQvp5buZmCymN3PPoYv31ZQQX4ve2j02k7piGgKAWgE+AQRB5YoYVveGXcl3TZ9ldgRMSxj56YfDFmmaqLg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true + '@iarna/toml@3.0.0': + resolution: {integrity: sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q==} - '@inquirer/search@4.1.8': - resolution: {integrity: sha512-fGiHKGD6DyPIYUWxoXnQTeXeyYqSOUrasDMABBmMHUalH/LxkuzY0xVRtimXAt1sUeeyYkVuKQx1bebMuN11Kw==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@inquirer/select@5.1.4': - resolution: {integrity: sha512-2kWcGKPMLAXAWRp1AH1SLsQmX+j0QjeljyXMUji9WMZC8nRDO0b7qquIGr6143E7KMLt3VAIGNXzwa/6PXQs4Q==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@jsep-plugin/assignment@1.3.0': + resolution: {integrity: sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==} + engines: {node: '>= 10.16.0'} peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true + jsep: ^0.4.0||^1.0.0 - '@inquirer/type@4.0.5': - resolution: {integrity: sha512-aetVUNeKNc/VriqXlw1NRSW0zhMBB0W4bNbWRJgzRl/3d0QNDQFfk0GO5SDdtjMZVg6o8ZKEiadd7SCCzoOn5Q==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@jsep-plugin/regex@1.0.4': + resolution: {integrity: sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==} + engines: {node: '>= 10.16.0'} peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + jsep: ^0.4.0||^1.0.0 - '@octokit/auth-token@6.0.0': - resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==} - engines: {node: '>= 20'} + '@octokit/auth-token@4.0.0': + resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==} + engines: {node: '>= 18'} - '@octokit/core@7.0.6': - resolution: {integrity: sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==} - engines: {node: '>= 20'} + '@octokit/core@5.2.2': + resolution: {integrity: sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==} + engines: {node: '>= 18'} - '@octokit/endpoint@11.0.3': - resolution: {integrity: sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==} - engines: {node: '>= 20'} + '@octokit/endpoint@9.0.6': + resolution: {integrity: sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==} + engines: {node: '>= 18'} - '@octokit/graphql@9.0.3': - resolution: {integrity: sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==} - engines: {node: '>= 20'} + '@octokit/graphql@7.1.1': + resolution: {integrity: sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==} + engines: {node: '>= 18'} - '@octokit/openapi-types@27.0.0': - resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==} + '@octokit/openapi-types@24.2.0': + resolution: {integrity: sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==} - '@octokit/plugin-paginate-rest@14.0.0': - resolution: {integrity: sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==} - engines: {node: '>= 20'} + '@octokit/plugin-paginate-rest@11.4.4-cjs.2': + resolution: {integrity: sha512-2dK6z8fhs8lla5PaOTgqfCGBxgAv/le+EhPs27KklPhm1bKObpu6lXzwfUEQ16ajXzqNrKMujsFyo9K2eaoISw==} + engines: {node: '>= 18'} peerDependencies: - '@octokit/core': '>=6' + '@octokit/core': '5' - '@octokit/plugin-request-log@6.0.0': - resolution: {integrity: sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q==} - engines: {node: '>= 20'} + '@octokit/plugin-request-log@4.0.1': + resolution: {integrity: sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==} + engines: {node: '>= 18'} peerDependencies: - '@octokit/core': '>=6' + '@octokit/core': '5' - '@octokit/plugin-rest-endpoint-methods@17.0.0': - resolution: {integrity: sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==} - engines: {node: '>= 20'} + '@octokit/plugin-rest-endpoint-methods@13.3.2-cjs.1': + resolution: {integrity: sha512-VUjIjOOvF2oELQmiFpWA1aOPdawpyaCUqcEBc/UOUnj3Xp6DJGrJ1+bjUIIDzdHjnFNO6q57ODMfdEZnoBkCwQ==} + engines: {node: '>= 18'} peerDependencies: - '@octokit/core': '>=6' + '@octokit/core': ^5 - '@octokit/request-error@7.1.0': - resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==} - engines: {node: '>= 20'} + '@octokit/request-error@5.1.1': + resolution: {integrity: sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==} + engines: {node: '>= 18'} - '@octokit/request@10.0.8': - resolution: {integrity: sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw==} - engines: {node: '>= 20'} + '@octokit/request@8.4.1': + resolution: {integrity: sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==} + engines: {node: '>= 18'} - '@octokit/rest@22.0.1': - resolution: {integrity: sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw==} - engines: {node: '>= 20'} + '@octokit/rest@20.1.2': + resolution: {integrity: sha512-GmYiltypkHHtihFwPRxlaorG5R9VAHuk/vbszVoRTGXnAsY60wYLkh/E2XiFmdZmqrisw+9FaazS1i5SbdWYgA==} + engines: {node: '>= 18'} - '@octokit/types@16.0.0': - resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} + '@octokit/types@13.10.0': + resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==} '@oxc-project/types@0.128.0': resolution: {integrity: sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==} @@ -776,10 +687,6 @@ packages: cpu: - x64 - '@phun-ky/typeof@2.0.3': - resolution: {integrity: sha512-oeQJs1aa8Ghke8JIK9yuq/+KjMiaYeDZ38jx7MhkXncXlUKjqQ3wEm2X3qCKyjo+ZZofZj+WsEEiqkTtRuE2xQ==} - engines: {node: ^20.9.0 || >=22.0.0, npm: '>=10.8.2'} - '@rolldown/binding-darwin-arm64@1.0.0-rc.18': resolution: {integrity: sha512-apJq2ktnGp27nSInMR5Vcj8kY6xJzDAvfdIFlpDcAK/w4cDO58qVoi1YQsES/SKiFNge/6e4CUzgjfHduYqWpQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -859,15 +766,24 @@ packages: '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/minimist@1.2.5': + resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + '@types/node@25.5.0': resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} - '@types/parse-path@7.1.0': - resolution: {integrity: sha512-EULJ8LApcVEPbrfND0cRQqutIOdiIgJ1Mgrhpy755r14xMohPTEpkV/k28SJvuOs9bHRFW8x+KeDAEPiGQPB9Q==} + '@types/normalize-package-data@2.4.4': + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + + '@types/npm-package-arg@6.1.4': + resolution: {integrity: sha512-vDgdbMy2QXHnAruzlv68pUtXCjmqUk3WrBAsRboRovsOmxbfn/WiYCjmecyKjGztnMps5dWp4Uq2prp+Ilo17Q==} '@types/react@19.2.16': resolution: {integrity: sha512-esJiCAnl0kfpNdE69f3So4WJUXy95dLZydX0KwK46riIHDzHM7O9Vtf9xCHW0PXIqvgqNrswl522kA/5yx+F4w==} + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + '@vitest/expect@4.1.2': resolution: {integrity: sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==} @@ -897,30 +813,48 @@ packages: '@vitest/utils@4.1.2': resolution: {integrity: sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==} - agent-base@8.0.0: - resolution: {integrity: sha512-QT8i0hCz6C/KQ+KTAbSNwCHDGdmUJl2tp2ZpNlGSWCfhUNVbYG2WLE3MdZGBAgXPV4GAvjGMxo+C1hroyxmZEg==} + '@xmldom/xmldom@0.8.13': + resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==} + engines: {node: '>=10.0.0'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} ansi-escapes@7.3.0: resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} engines: {node: '>=18'} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + ansi-regex@6.2.2: resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + ansi-styles@6.2.3: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-ify@1.0.0: + resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} + + arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - ast-types@0.13.4: - resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} - engines: {node: '>=4'} - async-retry@1.3.3: resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} @@ -932,54 +866,36 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - basic-ftp@5.3.1: - resolution: {integrity: sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==} - engines: {node: '>=10.0.0'} + before-after-hook@2.2.3: + resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} - before-after-hook@4.0.0: - resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} brace-expansion@5.0.5: resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} engines: {node: 18 || 20 || >=22} - bundle-name@4.1.0: - resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} - engines: {node: '>=18'} + camelcase-keys@6.2.2: + resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} + engines: {node: '>=8'} - c12@3.3.3: - resolution: {integrity: sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q==} - peerDependencies: - magicast: '*' - peerDependenciesMeta: - magicast: - optional: true + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - chardet@2.1.1: - resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} - - chokidar@5.0.0: - resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} - engines: {node: '>= 20.19.0'} - - ci-info@4.4.0: - resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} - engines: {node: '>=8'} - - citty@0.1.6: - resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} - - citty@0.2.2: - resolution: {integrity: sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==} - cli-boxes@4.0.1: resolution: {integrity: sha512-5IOn+jcCEHEraYolBPs/sT4BxYCe2nHg374OPiItB1O96KZFseS2gthU4twyYzeDcFew4DaUM/xwc5BQf08JJw==} engines: {node: '>=18.20 <19 || >=20.10'} @@ -988,36 +904,44 @@ packages: resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - cli-cursor@5.0.0: - resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} - engines: {node: '>=18'} - - cli-spinners@3.4.0: - resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==} - engines: {node: '>=18.20'} - cli-truncate@6.0.0: resolution: {integrity: sha512-3+YKIUFsohD9MIoOFPFBldjAlnfCmCDcqe6aYGFqlDTRKg80p4wg35L+j83QQ63iOlKRccEkbn8IuM++HsgEjA==} engines: {node: '>=22'} - cli-width@4.1.0: - resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} - engines: {node: '>= 12'} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} code-excerpt@4.0.0: resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + commander@14.0.3: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} - confbox@0.2.4: - resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + compare-func@2.0.0: + resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + + conventional-changelog-conventionalcommits@6.1.0: + resolution: {integrity: sha512-3cS3GEtR78zTfMzk0AizXKKIdN4OvSh7ibNz6/DPbhWWQu7LqE/8+/GqSodV+sywUR2gpJAdP/1JFf4XtN7Zpw==} + engines: {node: '>=14'} + + conventional-changelog-writer@6.0.1: + resolution: {integrity: sha512-359t9aHorPw+U+nHzUXHS5ZnPBOizRxfQsWT5ZDHBfvfxQOAik+yfuhKXG66CN5LEWPpMNnIMHUTCKeYNprvHQ==} + engines: {node: '>=14'} + hasBin: true - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} - engines: {node: ^14.18.0 || >=16.10.0} + conventional-commits-filter@3.0.0: + resolution: {integrity: sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q==} + engines: {node: '>=14'} convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -1026,12 +950,19 @@ packages: resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - data-uri-to-buffer@7.0.0: - resolution: {integrity: sha512-CuRUx0TXGSbbWdEci3VK/XOZGP3n0P4pIKpsqpVtBqaIIuj3GKK8H45oAqA4Rg8FHipc+CzRdUzmD4YQXxv66Q==} - engines: {node: '>= 14'} + dateformat@3.0.3: + resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} + engines: {node: '*'} debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} @@ -1040,42 +971,64 @@ packages: supports-color: optional: true - default-browser-id@5.0.1: - resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} - engines: {node: '>=18'} - - default-browser@5.5.0: - resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} - engines: {node: '>=18'} - - define-lazy-prop@3.0.0: - resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} - engines: {node: '>=12'} + decamelize-keys@1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} - defu@6.1.7: - resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} - degenerator@6.0.0: - resolution: {integrity: sha512-j5MdXdefrecJeSqTpUrgZd4fBsD2IxZx0JlJD+n1Q7+aTf7/HcyXSfHsicPW6ekPurX159v1ZYla6OJgSPh2Dw==} - engines: {node: '>= 14'} - peerDependencies: - quickjs-wasi: ^0.0.1 + deprecation@2.3.1: + resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} - destr@2.0.5: - resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} - dotenv@17.4.2: - resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} - engines: {node: '>=12'} + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + dot-prop@5.3.0: + resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} + engines: {node: '>=8'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} environment@1.1.0: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + es-module-lexer@2.1.0: resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} @@ -1087,54 +1040,25 @@ packages: engines: {node: '>=18'} hasBin: true + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + escape-string-regexp@2.0.0: resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} engines: {node: '>=8'} - escodegen@2.1.0: - resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} - engines: {node: '>=6.0'} - hasBin: true - - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - - eta@4.5.1: - resolution: {integrity: sha512-EaNCGm+8XEIU7YNcc+THptWAO5NfKBHHARxt+wxZljj9bTr/+arRoOm9/MpGt4n6xn9fLnPFRSoLD0WFYGFUxQ==} - engines: {node: '>=20'} - expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} - exsolve@1.0.8: - resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} - - fast-content-type-parse@3.0.0: - resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} - - fast-string-truncated-width@3.0.3: - resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} - - fast-string-width@3.0.2: - resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} - - fast-wrap-ansi@0.2.0: - resolution: {integrity: sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==} - fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1144,6 +1068,14 @@ packages: picomatch: optional: true + figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1156,6 +1088,13 @@ packages: os: - darwin + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.5.0: resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} engines: {node: '>=18'} @@ -1163,38 +1102,52 @@ packages: get-tsconfig@4.14.0: resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} - get-uri@7.0.0: - resolution: {integrity: sha512-ZsC7KQxm1Hra8yO0RvMZ4lGJT7vnBtSNpEHKq39MPN7vjuvCiu1aQ8rkXUaIXG1y/TSDez97Gmv04ibnYqCp/A==} - engines: {node: '>= 14'} - ghostty-web@0.4.0: resolution: {integrity: sha512-0puDBik2qapbD/QQBW9o5ZHfXnZBqZWx/ctBiVtKZ6ZLds4NYb+wZuw1cRLXZk9zYovIQ908z3rvFhexAvc5Hg==} - giget@2.0.0: - resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + + handlebars@4.7.9: + resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==} + engines: {node: '>=0.4.7'} hasBin: true - git-up@8.1.1: - resolution: {integrity: sha512-FDenSF3fVqBYSaJoYy1KSc2wosx0gCvKP+c+PRBht7cAaiCeQlBtfBDX9vgnNOHmdePlSFITVcn4pFfcgNvx3g==} + hard-rejection@2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} - git-url-parse@16.1.0: - resolution: {integrity: sha512-cPLz4HuK86wClEW7iDdeAKcCVlWXmrLpb2L+G9goW0Z1dtpNS6BXXSOckUTlJT/LDQViE1QZKstNORzHsLnobw==} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} - glob@13.0.6: - resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} - engines: {node: 18 || 20 || >=22} + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + + hosted-git-info@4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} - http-proxy-agent@8.0.0: - resolution: {integrity: sha512-7pose0uGgrCJeH2Qh4JcNhWZp3u/oNrWjNYDK4ydOLxOpTw8V8ogHFAmkz0VWq96JBFj4umVJpvmQi287rSYLg==} + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} - https-proxy-agent@8.0.0: - resolution: {integrity: sha512-YYeW+iCnAS3xhvj2dvVoWgsbca3RfQy/IlaNHHOtDmU0jMqPI9euIq3Y9BJETdxk16h9NHHCKqp/KB9nIMStCQ==} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} - iconv-lite@0.7.2: - resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} - engines: {node: '>=0.10.0'} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} indent-string@5.0.0: resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} @@ -1213,14 +1166,16 @@ packages: react-devtools-core: optional: true - ip-address@10.2.0: - resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} - engines: {node: '>= 12'} + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-docker@3.0.0: - resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - hasBin: true + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} is-fullwidth-code-point@5.1.0: resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} @@ -1231,40 +1186,43 @@ packages: engines: {node: '>=20'} hasBin: true - is-in-ssh@1.0.0: - resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} - engines: {node: '>=20'} + is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} - is-inside-container@1.0.0: - resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} - engines: {node: '>=14.16'} + is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true - is-interactive@2.0.0: - resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} - engines: {node: '>=12'} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - is-ssh@1.4.1: - resolution: {integrity: sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==} + js-yaml@4.2.0: + resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} + hasBin: true - is-unicode-supported@2.1.0: - resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} - engines: {node: '>=18'} + jsep@1.4.0: + resolution: {integrity: sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==} + engines: {node: '>= 10.16.0'} - is-wsl@3.1.1: - resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} - engines: {node: '>=16'} + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - issue-parser@7.0.1: - resolution: {integrity: sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==} - engines: {node: ^18.17 || >=20.6.1} + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - jiti@2.7.0: - resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + jsonpath-plus@10.4.0: + resolution: {integrity: sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA==} + engines: {node: '>=18.0.0'} hasBin: true - json-with-bigint@3.5.8: - resolution: {integrity: sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==} + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} lightningcss-darwin-arm64@1.32.0: resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} @@ -1334,86 +1292,76 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} - lodash.capitalize@4.2.1: - resolution: {integrity: sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==} - - lodash.escaperegexp@4.1.2: - resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} - - lodash.isplainobject@4.0.6: - resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - - lodash.isstring@4.0.1: - resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - - lodash.uniqby@4.7.0: - resolution: {integrity: sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==} + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} - log-symbols@7.0.1: - resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==} - engines: {node: '>=18'} + lodash.ismatch@4.4.0: + resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==} lru-cache@11.3.6: resolution: {integrity: sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==} engines: {node: 20 || >=22} - lru-cache@7.18.3: - resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} - engines: {node: '>=12'} - - macos-release@3.4.0: - resolution: {integrity: sha512-wpGPwyg/xrSp4H4Db4xYSeAr6+cFQGHfspHzDUdYxswDnUW0L5Ov63UuJiSr8NMSpyaChO4u1n0MXUvVPtrN6A==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - mime-db@1.54.0: - resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} - engines: {node: '>= 0.6'} + map-obj@1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} + + map-obj@4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} - mime-types@3.0.2: - resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} - engines: {node: '>=18'} + meow@8.1.2: + resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} + engines: {node: '>=10'} mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - mimic-function@5.0.1: - resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} - engines: {node: '>=18'} + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} minimatch@10.2.5: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} + minimist-options@4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} + modify-values@1.0.1: + resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} + engines: {node: '>=0.10.0'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - mute-stream@3.0.0: - resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} - engines: {node: ^20.17.0 || >=22.9.0} - nanoid@3.3.12: resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - netmask@2.1.1: - resolution: {integrity: sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==} - engines: {node: '>= 0.4.0'} - - new-github-release-url@2.0.0: - resolution: {integrity: sha512-NHDDGYudnvRutt/VhKFlX26IotXe1w0cmkDm6JGquh5bz/bDTw0LufSmH/GxTjEdpHEO+bVKFTwdrcGa/9XlKQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} @@ -1422,47 +1370,36 @@ packages: resolution: {integrity: sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==} engines: {node: ^18 || ^20 || >= 21} - node-fetch-native@1.6.7: - resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} - node-gyp-build@4.8.4: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true + node-html-parser@6.1.13: + resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==} + node-pty@1.1.0: resolution: {integrity: sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==} - nypm@0.6.6: - resolution: {integrity: sha512-vRyr0r4cbBapw07Xw8xrj9Teq3o7MUD35rSaTcanDbW+aK2XHDgJFiU6ZTj2GBw7Q12ysdsyFss+Vdz4hQ0Y6Q==} - engines: {node: '>=18'} - hasBin: true + normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + + normalize-package-data@3.0.3: + resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} + engines: {node: '>=10'} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} - ohash@2.0.11: - resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} - onetime@7.0.0: - resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} - engines: {node: '>=18'} - - open@11.0.0: - resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} - engines: {node: '>=20'} - - ora@9.3.0: - resolution: {integrity: sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==} - engines: {node: '>=20'} - - os-name@7.0.0: - resolution: {integrity: sha512-/HfRU/lPPr4T2VigM+cvM3cU77es+XF4OEAa4aE5zpdvrxHGD2NmH0AFIWpMNAb+CsZL45rlcIO49Re0ZcRseg==} - engines: {node: '>=20'} - oxfmt@0.47.0: resolution: {integrity: sha512-OFbkbzxKCpooQEnRmpTDnuwTX8KHXzZTQ4Df/hz85fpS67Pl+lxPEFvUtin56HIIS0B1k4X8oIzTXRZPufA2CA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1482,30 +1419,39 @@ packages: oxlint-tsgolint: optional: true - pac-proxy-agent@8.0.0: - resolution: {integrity: sha512-HyCoVbyQ/nbVlQ/R6wBu0YXhbG2oAnEK5BQ3xMyj1OffQmU5NoOnpLzgPlKHaobUzz5NK0+AZHby4TdydAEBUA==} - engines: {node: '>= 14'} + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} - pac-resolver@8.0.0: - resolution: {integrity: sha512-SVNzOxVq2zuTew3WAt7U8UghwzJzuWYuJryd3y8FxyLTZdjVoCzY8kLP39PpEqQCDvlMWdQXwViu0sYT3eiU2w==} - engines: {node: '>= 14'} - peerDependencies: - quickjs-wasi: ^0.0.1 + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - parse-path@7.1.0: - resolution: {integrity: sha512-EuCycjZtfPcjWk7KTksnJ5xPMvWGA/6i4zrLYhRG0hGvC3GPU/jGUj3Cy+ZR0v30duV3e23R95T1lE2+lsndSw==} + parse-github-repo-url@1.4.1: + resolution: {integrity: sha512-bSWyzBKqcSL4RrncTpGsEKoJ7H8a4L3++ifTAbTFeMHyq2wRV+42DGmQcHIrJIvdcacjIOxEuKH/w4tthF17gg==} - parse-url@9.2.0: - resolution: {integrity: sha512-bCgsFI+GeGWPAvAiUv63ZorMeif3/U0zaXABGJbOWt5OH2KCaPHF6S+0ok4aqM9RuIPGyZdx9tR9l13PsW4AYQ==} - engines: {node: '>=14.13.0'} + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} patch-console@2.0.0: resolution: {integrity: sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@2.0.2: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} @@ -1513,9 +1459,6 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - perfect-debounce@2.1.0: - resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1523,9 +1466,6 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} - pkg-types@2.3.1: - resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} - playwright-core@1.60.0: resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} engines: {node: '>=18'} @@ -1540,29 +1480,9 @@ packages: resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} engines: {node: ^10 || ^12 || >=14} - powershell-utils@0.1.0: - resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} - engines: {node: '>=20'} - - powershell-utils@0.2.0: - resolution: {integrity: sha512-ZlsFlG7MtSFCoc5xreOvBAozCJ6Pf06opgJjh9ONEv418xpZSAzNjstD36C6+JwOnfSqOW/9uDkqKjezTdxZhw==} - engines: {node: '>=20'} - - protocols@2.0.2: - resolution: {integrity: sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==} - - proxy-agent@7.0.0: - resolution: {integrity: sha512-okTgt79rHTvMHkr/Ney5rZpgCHh3g1g3tI5uhkgN5b7OeI3n0Q/ui1uv9OdrnZNJM9WIZJqZPh/UJs+YtO/TMQ==} - engines: {node: '>= 14'} - - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - - quickjs-wasi@0.0.1: - resolution: {integrity: sha512-fBWNLTBkxkLAhe1AzF1hyXEvuA+N+vV1WMP2D6iiMUblvmOt8Pp5t8zUcgvz7aYA1ldUdxDlgUse15dmcKjkNg==} - - rc9@2.1.2: - resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + quick-lru@4.0.1: + resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} + engines: {node: '>=8'} react-reconciler@0.33.0: resolution: {integrity: sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==} @@ -1574,26 +1494,39 @@ packages: resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} engines: {node: '>=0.10.0'} - readdirp@5.0.0: - resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} - engines: {node: '>= 20.19.0'} + read-pkg-up@7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} + + read-pkg@5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} - release-it@20.0.1: - resolution: {integrity: sha512-3ob1P1aV+3+ZOoR7qgobfYyMlQbpitzOK09iKTtQ145vFi4rWxlRTgHwtVl8kokCvqiF/cJPxRlfcmZmF5aDJA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} + release-please@17.9.0: + resolution: {integrity: sha512-3/dVnZpsR2qfZMcYXKsiE8MiZPNYntff2csLFR0m2Hil0QN74cuF+nllubqD2qxdhtg81SmK+zzriQUZuI5wpw==} + engines: {node: '>=20.0.0'} hasBin: true + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + restore-cursor@4.0.0: resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - restore-cursor@5.1.0: - resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} - engines: {node: '>=18'} - retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} @@ -1608,16 +1541,13 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - run-applescript@7.1.0: - resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} - engines: {node: '>=18'} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + semver@7.7.4: resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} @@ -1629,26 +1559,10 @@ packages: signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - slice-ansi@9.0.0: resolution: {integrity: sha512-SO/3iYL5S3W57LLEniscOGPZgOqZUPCx6d3dB+52B80yJ0XstzsC/eV8gnA4tM3MHDrKz+OCFSLNjswdSC+/bA==} engines: {node: '>=22'} - smart-buffer@4.2.0: - resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} - engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - - socks-proxy-agent@9.0.0: - resolution: {integrity: sha512-fFlbMlfsXhK02ZB8aZY7Hwxh/IHBV9b1Oq9bvBk6tkFWXvdAxUgA0wbw/NYR5liU3Y5+KI6U4FH3kYJt9QYv0w==} - engines: {node: '>= 14'} - - socks@2.8.8: - resolution: {integrity: sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog==} - engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -1657,6 +1571,22 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-license-ids@3.0.23: + resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} + + split@1.0.1: + resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} + engines: {node: '*'} + stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -1667,18 +1597,34 @@ packages: std-env@4.1.0: resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} - stdin-discarder@0.3.2: - resolution: {integrity: sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==} - engines: {node: '>=18'} + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} string-width@8.2.1: resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==} engines: {node: '>=20'} + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + strip-ansi@7.2.0: resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + tagged-tag@1.0.0: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} @@ -1687,6 +1633,9 @@ packages: resolution: {integrity: sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==} engines: {node: '>=18'} + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -1710,27 +1659,50 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + trim-newlines@3.0.1: + resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} + engines: {node: '>=8'} tsx@4.21.0: resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} engines: {node: '>=18.0.0'} hasBin: true - type-fest@2.19.0: - resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} - engines: {node: '>=12.20'} + type-fest@0.18.1: + resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} + engines: {node: '>=10'} + + type-fest@0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} + + type-fest@0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + + type-fest@3.13.1: + resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} + engines: {node: '>=14.16'} type-fest@5.7.0: resolution: {integrity: sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==} engines: {node: '>=20'} + typescript@4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + ulid@3.0.2: resolution: {integrity: sha512-yu26mwteFYzBAot7KVMqFGCVpsF6g8wXfJzQUHvu1no3+rRRSFcSV2nKeYvNPLD2J4b08jYBDhHUjeH0ygIl9w==} hasBin: true @@ -1738,16 +1710,20 @@ packages: undici-types@7.18.2: resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} - undici@7.24.5: - resolution: {integrity: sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==} - engines: {node: '>=20.18.1'} + unist-util-is@4.1.0: + resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} - universal-user-agent@7.0.3: - resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} + unist-util-visit-parents@3.1.1: + resolution: {integrity: sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==} - url-join@5.0.0: - resolution: {integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + unist-util-visit@2.0.3: + resolution: {integrity: sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==} + + universal-user-agent@6.0.1: + resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==} + + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} vite@8.0.11: resolution: {integrity: sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow==} @@ -1837,17 +1813,20 @@ packages: resolution: {integrity: sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA==} engines: {node: '>=20'} - wildcard-match@5.1.4: - resolution: {integrity: sha512-wldeCaczs8XXq7hj+5d/F38JE2r7EXgb6WQDM84RVwxy81T/sxB5e9+uZLK9Q9oNz1mlvjut+QtvgaOQFPVq/g==} - - windows-release@7.1.1: - resolution: {integrity: sha512-0GBwC9WmR8Bm3WYiz3FC391054BsFHZ2gzBVdYj9uj5eIVYzbn/YPYCYW9SWdh9vwnLuzpn1UGwJKiMG4F236w==} - engines: {node: '>=20'} + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} wrap-ansi@10.0.0: resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==} engines: {node: '>=20'} + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.20.0: resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} engines: {node: '>=10.0.0'} @@ -1860,22 +1839,33 @@ packages: utf-8-validate: optional: true - wsl-utils@0.3.1: - resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} - engines: {node: '>=20'} + xpath@0.0.34: + resolution: {integrity: sha512-FxF6+rkr1rNSQrhUNYrAFJpRXNzlDoMxeXN5qI84939ylEv3qqPFKa85Oxr6tDaJKqwW6KKyo2v26TSv3k6LeA==} + engines: {node: '>=0.6.0'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} yaml@2.8.4: resolution: {integrity: sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==} engines: {node: '>= 14.6'} hasBin: true - yargs-parser@22.0.0: - resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} - engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} - yoctocolors@2.1.2: - resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} - engines: {node: '>=18'} + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} yoga-layout@3.2.1: resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} @@ -1890,11 +1880,24 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-validator-identifier@7.29.7': {} + '@coder/libghostty-vt-node@0.1.0-beta.0': dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 + '@conventional-commits/parser@0.4.1': + dependencies: + unist-util-visit: 2.0.3 + unist-util-visit-parents: 3.1.1 + '@esbuild/darwin-arm64@0.27.7': {} '@esbuild/linux-arm64@0.27.7': {} @@ -1905,176 +1908,87 @@ snapshots: '@esbuild/win32-x64@0.27.7': {} - '@inquirer/ansi@2.0.5': {} - - '@inquirer/checkbox@5.1.4(@types/node@25.5.0)': - dependencies: - '@inquirer/ansi': 2.0.5 - '@inquirer/core': 11.1.9(@types/node@25.5.0) - '@inquirer/figures': 2.0.5 - '@inquirer/type': 4.0.5(@types/node@25.5.0) - '@types/node': 25.5.0 - - '@inquirer/confirm@6.0.12(@types/node@25.5.0)': - dependencies: - '@inquirer/core': 11.1.9(@types/node@25.5.0) - '@inquirer/type': 4.0.5(@types/node@25.5.0) - '@types/node': 25.5.0 - - '@inquirer/core@11.1.9(@types/node@25.5.0)': - dependencies: - '@inquirer/ansi': 2.0.5 - '@inquirer/figures': 2.0.5 - '@inquirer/type': 4.0.5(@types/node@25.5.0) - '@types/node': 25.5.0 - cli-width: 4.1.0 - fast-wrap-ansi: 0.2.0 - mute-stream: 3.0.0 - signal-exit: 4.1.0 - - '@inquirer/editor@5.1.1(@types/node@25.5.0)': - dependencies: - '@inquirer/core': 11.1.9(@types/node@25.5.0) - '@inquirer/external-editor': 3.0.0(@types/node@25.5.0) - '@inquirer/type': 4.0.5(@types/node@25.5.0) - '@types/node': 25.5.0 - - '@inquirer/expand@5.0.13(@types/node@25.5.0)': - dependencies: - '@inquirer/core': 11.1.9(@types/node@25.5.0) - '@inquirer/type': 4.0.5(@types/node@25.5.0) - '@types/node': 25.5.0 - - '@inquirer/external-editor@3.0.0(@types/node@25.5.0)': + '@google-automations/git-file-utils@3.0.1(@octokit/core@5.2.2)': dependencies: - '@types/node': 25.5.0 - chardet: 2.1.1 - iconv-lite: 0.7.2 + '@octokit/rest': 20.1.2(@octokit/core@5.2.2) + '@octokit/types': 13.10.0 + js-yaml: 4.2.0 + minimatch: 10.2.5 - '@inquirer/figures@2.0.5': {} + '@iarna/toml@3.0.0': {} - '@inquirer/input@5.0.12(@types/node@25.5.0)': - dependencies: - '@inquirer/core': 11.1.9(@types/node@25.5.0) - '@inquirer/type': 4.0.5(@types/node@25.5.0) - '@types/node': 25.5.0 + '@jridgewell/sourcemap-codec@1.5.5': {} - '@inquirer/number@4.0.12(@types/node@25.5.0)': + '@jsep-plugin/assignment@1.3.0(jsep@1.4.0)': dependencies: - '@inquirer/core': 11.1.9(@types/node@25.5.0) - '@inquirer/type': 4.0.5(@types/node@25.5.0) - '@types/node': 25.5.0 + jsep: 1.4.0 - '@inquirer/password@5.0.12(@types/node@25.5.0)': + '@jsep-plugin/regex@1.0.4(jsep@1.4.0)': dependencies: - '@inquirer/ansi': 2.0.5 - '@inquirer/core': 11.1.9(@types/node@25.5.0) - '@inquirer/type': 4.0.5(@types/node@25.5.0) - '@types/node': 25.5.0 + jsep: 1.4.0 - '@inquirer/prompts@8.4.2(@types/node@25.5.0)': - dependencies: - '@inquirer/checkbox': 5.1.4(@types/node@25.5.0) - '@inquirer/confirm': 6.0.12(@types/node@25.5.0) - '@inquirer/editor': 5.1.1(@types/node@25.5.0) - '@inquirer/expand': 5.0.13(@types/node@25.5.0) - '@inquirer/input': 5.0.12(@types/node@25.5.0) - '@inquirer/number': 4.0.12(@types/node@25.5.0) - '@inquirer/password': 5.0.12(@types/node@25.5.0) - '@inquirer/rawlist': 5.2.8(@types/node@25.5.0) - '@inquirer/search': 4.1.8(@types/node@25.5.0) - '@inquirer/select': 5.1.4(@types/node@25.5.0) - '@types/node': 25.5.0 + '@octokit/auth-token@4.0.0': {} - '@inquirer/rawlist@5.2.8(@types/node@25.5.0)': + '@octokit/core@5.2.2': dependencies: - '@inquirer/core': 11.1.9(@types/node@25.5.0) - '@inquirer/type': 4.0.5(@types/node@25.5.0) - '@types/node': 25.5.0 + '@octokit/auth-token': 4.0.0 + '@octokit/graphql': 7.1.1 + '@octokit/request': 8.4.1 + '@octokit/request-error': 5.1.1 + '@octokit/types': 13.10.0 + before-after-hook: 2.2.3 + universal-user-agent: 6.0.1 - '@inquirer/search@4.1.8(@types/node@25.5.0)': + '@octokit/endpoint@9.0.6': dependencies: - '@inquirer/core': 11.1.9(@types/node@25.5.0) - '@inquirer/figures': 2.0.5 - '@inquirer/type': 4.0.5(@types/node@25.5.0) - '@types/node': 25.5.0 + '@octokit/types': 13.10.0 + universal-user-agent: 6.0.1 - '@inquirer/select@5.1.4(@types/node@25.5.0)': + '@octokit/graphql@7.1.1': dependencies: - '@inquirer/ansi': 2.0.5 - '@inquirer/core': 11.1.9(@types/node@25.5.0) - '@inquirer/figures': 2.0.5 - '@inquirer/type': 4.0.5(@types/node@25.5.0) - '@types/node': 25.5.0 - - '@inquirer/type@4.0.5(@types/node@25.5.0)': - dependencies: - '@types/node': 25.5.0 + '@octokit/request': 8.4.1 + '@octokit/types': 13.10.0 + universal-user-agent: 6.0.1 - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@octokit/auth-token@6.0.0': {} + '@octokit/openapi-types@24.2.0': {} - '@octokit/core@7.0.6': + '@octokit/plugin-paginate-rest@11.4.4-cjs.2(@octokit/core@5.2.2)': dependencies: - '@octokit/auth-token': 6.0.0 - '@octokit/graphql': 9.0.3 - '@octokit/request': 10.0.8 - '@octokit/request-error': 7.1.0 - '@octokit/types': 16.0.0 - before-after-hook: 4.0.0 - universal-user-agent: 7.0.3 + '@octokit/core': 5.2.2 + '@octokit/types': 13.10.0 - '@octokit/endpoint@11.0.3': + '@octokit/plugin-request-log@4.0.1(@octokit/core@5.2.2)': dependencies: - '@octokit/types': 16.0.0 - universal-user-agent: 7.0.3 - - '@octokit/graphql@9.0.3': - dependencies: - '@octokit/request': 10.0.8 - '@octokit/types': 16.0.0 - universal-user-agent: 7.0.3 - - '@octokit/openapi-types@27.0.0': {} + '@octokit/core': 5.2.2 - '@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.6)': + '@octokit/plugin-rest-endpoint-methods@13.3.2-cjs.1(@octokit/core@5.2.2)': dependencies: - '@octokit/core': 7.0.6 - '@octokit/types': 16.0.0 + '@octokit/core': 5.2.2 + '@octokit/types': 13.10.0 - '@octokit/plugin-request-log@6.0.0(@octokit/core@7.0.6)': + '@octokit/request-error@5.1.1': dependencies: - '@octokit/core': 7.0.6 + '@octokit/types': 13.10.0 + deprecation: 2.3.1 + once: 1.4.0 - '@octokit/plugin-rest-endpoint-methods@17.0.0(@octokit/core@7.0.6)': + '@octokit/request@8.4.1': dependencies: - '@octokit/core': 7.0.6 - '@octokit/types': 16.0.0 + '@octokit/endpoint': 9.0.6 + '@octokit/request-error': 5.1.1 + '@octokit/types': 13.10.0 + universal-user-agent: 6.0.1 - '@octokit/request-error@7.1.0': + '@octokit/rest@20.1.2(@octokit/core@5.2.2)': dependencies: - '@octokit/types': 16.0.0 + '@octokit/core': 5.2.2 + '@octokit/plugin-paginate-rest': 11.4.4-cjs.2(@octokit/core@5.2.2) + '@octokit/plugin-request-log': 4.0.1(@octokit/core@5.2.2) + '@octokit/plugin-rest-endpoint-methods': 13.3.2-cjs.1(@octokit/core@5.2.2) - '@octokit/request@10.0.8': + '@octokit/types@13.10.0': dependencies: - '@octokit/endpoint': 11.0.3 - '@octokit/request-error': 7.1.0 - '@octokit/types': 16.0.0 - fast-content-type-parse: 3.0.0 - json-with-bigint: 3.5.8 - universal-user-agent: 7.0.3 - - '@octokit/rest@22.0.1(@octokit/core@7.0.6)': - dependencies: - '@octokit/core': 7.0.6 - '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.6) - '@octokit/plugin-request-log': 6.0.0(@octokit/core@7.0.6) - '@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.6) - - '@octokit/types@16.0.0': - dependencies: - '@octokit/openapi-types': 27.0.0 + '@octokit/openapi-types': 24.2.0 '@oxc-project/types@0.128.0': {} @@ -2116,8 +2030,6 @@ snapshots: '@oxlint/binding-win32-x64-msvc@1.62.0': {} - '@phun-ky/typeof@2.0.3': {} - '@rolldown/binding-darwin-arm64@1.0.0-rc.18': {} '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.18': {} @@ -2145,18 +2057,22 @@ snapshots: '@types/estree@1.0.9': {} + '@types/minimist@1.2.5': {} + '@types/node@25.5.0': dependencies: undici-types: 7.18.2 - '@types/parse-path@7.1.0': - dependencies: - parse-path: 7.1.0 + '@types/normalize-package-data@2.4.4': {} + + '@types/npm-package-arg@6.1.4': {} '@types/react@19.2.16': dependencies: csstype: 3.2.3 + '@types/unist@2.0.11': {} + '@vitest/expect@4.1.2': dependencies: '@standard-schema/spec': 1.1.0 @@ -2197,21 +2113,31 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 - agent-base@8.0.0: {} + '@xmldom/xmldom@0.8.13': {} + + agent-base@7.1.4: {} ansi-escapes@7.3.0: dependencies: environment: 1.1.0 + ansi-regex@5.0.1: {} + ansi-regex@6.2.2: {} + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + ansi-styles@6.2.3: {} - assertion-error@2.0.1: {} + argparse@2.0.1: {} - ast-types@0.13.4: - dependencies: - tslib: 2.8.1 + array-ify@1.0.0: {} + + arrify@1.0.1: {} + + assertion-error@2.0.1: {} async-retry@1.3.3: dependencies: @@ -2221,50 +2147,30 @@ snapshots: balanced-match@4.0.4: {} - basic-ftp@5.3.1: {} + before-after-hook@2.2.3: {} - before-after-hook@4.0.0: {} + boolbase@1.0.0: {} brace-expansion@5.0.5: dependencies: balanced-match: 4.0.4 - bundle-name@4.1.0: + camelcase-keys@6.2.2: dependencies: - run-applescript: 7.1.0 + camelcase: 5.3.1 + map-obj: 4.3.0 + quick-lru: 4.0.1 - c12@3.3.3: - dependencies: - chokidar: 5.0.0 - confbox: 0.2.4 - defu: 6.1.7 - dotenv: 17.4.2 - exsolve: 1.0.8 - giget: 2.0.0 - jiti: 2.7.0 - ohash: 2.0.11 - pathe: 2.0.3 - perfect-debounce: 2.1.0 - pkg-types: 2.3.1 - rc9: 2.1.2 + camelcase@5.3.1: {} chai@6.2.2: {} - chalk@5.6.2: {} - - chardet@2.1.1: {} - - chokidar@5.0.0: - dependencies: - readdirp: 5.0.0 - - ci-info@4.4.0: {} - - citty@0.1.6: + chalk@4.1.2: dependencies: - consola: 3.4.2 + ansi-styles: 4.3.0 + supports-color: 7.2.0 - citty@0.2.2: {} + chalk@5.6.2: {} cli-boxes@4.0.1: {} @@ -2272,67 +2178,124 @@ snapshots: dependencies: restore-cursor: 4.0.0 - cli-cursor@5.0.0: - dependencies: - restore-cursor: 5.1.0 - - cli-spinners@3.4.0: {} - cli-truncate@6.0.0: dependencies: slice-ansi: 9.0.0 string-width: 8.2.1 - cli-width@4.1.0: {} + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 code-excerpt@4.0.0: dependencies: convert-to-spaces: 2.0.1 + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + commander@14.0.3: {} - confbox@0.2.4: {} + compare-func@2.0.0: + dependencies: + array-ify: 1.0.0 + dot-prop: 5.3.0 + + conventional-changelog-conventionalcommits@6.1.0: + dependencies: + compare-func: 2.0.0 - consola@3.4.2: {} + conventional-changelog-writer@6.0.1: + dependencies: + conventional-commits-filter: 3.0.0 + dateformat: 3.0.3 + handlebars: 4.7.9 + json-stringify-safe: 5.0.1 + meow: 8.1.2 + semver: 7.7.4 + split: 1.0.1 + + conventional-commits-filter@3.0.0: + dependencies: + lodash.ismatch: 4.4.0 + modify-values: 1.0.1 convert-source-map@2.0.0: {} convert-to-spaces@2.0.1: {} + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-what@6.2.2: {} + csstype@3.2.3: {} - data-uri-to-buffer@7.0.0: {} + dateformat@3.0.3: {} debug@4.4.3: dependencies: ms: 2.1.3 - default-browser-id@5.0.1: {} + decamelize-keys@1.1.1: + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + + decamelize@1.2.0: {} + + deprecation@2.3.1: {} + + detect-indent@6.1.0: {} + + detect-libc@2.1.2: {} + + diff@8.0.4: {} - default-browser@5.5.0: + dom-serializer@2.0.0: dependencies: - bundle-name: 4.1.0 - default-browser-id: 5.0.1 + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 - define-lazy-prop@3.0.0: {} + domelementtype@2.3.0: {} - defu@6.1.7: {} + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 - degenerator@6.0.0(quickjs-wasi@0.0.1): + domutils@3.2.2: dependencies: - ast-types: 0.13.4 - escodegen: 2.1.0 - esprima: 4.0.1 - quickjs-wasi: 0.0.1 + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 - destr@2.0.5: {} + dot-prop@5.3.0: + dependencies: + is-obj: 2.0.0 - detect-libc@2.1.2: {} + emoji-regex@8.0.0: {} - dotenv@17.4.2: {} + entities@4.5.0: {} environment@1.1.0: {} + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-errors@1.3.0: {} + es-module-lexer@2.1.0: {} es-toolkit@1.47.0: {} @@ -2345,103 +2308,89 @@ snapshots: '@esbuild/win32-arm64': 0.27.7 '@esbuild/win32-x64': 0.27.7 - escape-string-regexp@2.0.0: {} - - escodegen@2.1.0: - dependencies: - esprima: 4.0.1 - estraverse: 5.3.0 - esutils: 2.0.3 - optionalDependencies: - source-map: 0.6.1 + escalade@3.2.0: {} - esprima@4.0.1: {} + escape-string-regexp@1.0.5: {} - estraverse@5.3.0: {} + escape-string-regexp@2.0.0: {} estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 - esutils@2.0.3: {} - - eta@4.5.1: {} - expect-type@1.3.0: {} - exsolve@1.0.8: {} - - fast-content-type-parse@3.0.0: {} - - fast-string-truncated-width@3.0.3: {} - - fast-string-width@3.0.2: + fdir@6.5.0(picomatch@4.0.4): dependencies: - fast-string-truncated-width: 3.0.3 + picomatch: 4.0.4 - fast-wrap-ansi@0.2.0: + figures@3.2.0: dependencies: - fast-string-width: 3.0.2 + escape-string-regexp: 1.0.5 - fdir@6.5.0(picomatch@4.0.4): + find-up@4.1.0: dependencies: - picomatch: 4.0.4 + locate-path: 5.0.0 + path-exists: 4.0.0 fsevents@2.3.2: {} fsevents@2.3.3: {} + function-bind@1.1.2: {} + + get-caller-file@2.0.5: {} + get-east-asian-width@1.5.0: {} get-tsconfig@4.14.0: dependencies: resolve-pkg-maps: 1.0.0 - get-uri@7.0.0: - dependencies: - basic-ftp: 5.3.1 - data-uri-to-buffer: 7.0.0 - debug: 4.4.3 - ghostty-web@0.4.0: {} - giget@2.0.0: + glob@13.0.6: dependencies: - citty: 0.1.6 - consola: 3.4.2 - defu: 6.1.7 - node-fetch-native: 1.6.7 - nypm: 0.6.6 - pathe: 2.0.3 + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 - git-up@8.1.1: + handlebars@4.7.9: dependencies: - is-ssh: 1.4.1 - parse-url: 9.2.0 + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + + hard-rejection@2.1.0: {} - git-url-parse@16.1.0: + has-flag@4.0.0: {} + + hasown@2.0.4: dependencies: - git-up: 8.1.1 + function-bind: 1.1.2 - glob@13.0.6: + he@1.2.0: {} + + hosted-git-info@2.8.9: {} + + hosted-git-info@4.1.0: dependencies: - minimatch: 10.2.5 - minipass: 7.1.3 - path-scurry: 2.0.2 + lru-cache: 6.0.0 - http-proxy-agent@8.0.0: + http-proxy-agent@7.0.2: dependencies: - agent-base: 8.0.0 + agent-base: 7.1.4 debug: 4.4.3 - https-proxy-agent@8.0.0: + https-proxy-agent@7.0.6: dependencies: - agent-base: 8.0.0 + agent-base: 7.1.4 debug: 4.4.3 - iconv-lite@0.7.2: - dependencies: - safer-buffer: 2.1.2 + indent-string@4.0.0: {} indent-string@5.0.0: {} @@ -2475,9 +2424,13 @@ snapshots: ws: 8.20.0 yoga-layout: 3.2.1 - ip-address@10.2.0: {} + is-arrayish@0.2.1: {} - is-docker@3.0.0: {} + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + + is-fullwidth-code-point@3.0.0: {} is-fullwidth-code-point@5.1.0: dependencies: @@ -2485,35 +2438,31 @@ snapshots: is-in-ci@2.0.0: {} - is-in-ssh@1.0.0: {} + is-obj@2.0.0: {} - is-inside-container@1.0.0: - dependencies: - is-docker: 3.0.0 + is-plain-obj@1.1.0: {} + + jiti@2.7.0: {} - is-interactive@2.0.0: {} + js-tokens@4.0.0: {} - is-ssh@1.4.1: + js-yaml@4.2.0: dependencies: - protocols: 2.0.2 + argparse: 2.0.1 - is-unicode-supported@2.1.0: {} + jsep@1.4.0: {} - is-wsl@3.1.1: - dependencies: - is-inside-container: 1.0.0 + json-parse-even-better-errors@2.3.1: {} - issue-parser@7.0.1: - dependencies: - lodash.capitalize: 4.2.1 - lodash.escaperegexp: 4.1.2 - lodash.isplainobject: 4.0.6 - lodash.isstring: 4.0.1 - lodash.uniqby: 4.7.0 + json-stringify-safe@5.0.1: {} - jiti@2.7.0: {} + jsonpath-plus@10.4.0(jsep@1.4.0): + dependencies: + '@jsep-plugin/assignment': 1.3.0(jsep@1.4.0) + '@jsep-plugin/regex': 1.0.4(jsep@1.4.0) + jsep: 1.4.0 - json-with-bigint@3.5.8: {} + kind-of@6.0.3: {} lightningcss-darwin-arm64@1.32.0: {} @@ -2541,115 +2490,110 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 - lodash.capitalize@4.2.1: {} - - lodash.escaperegexp@4.1.2: {} + lines-and-columns@1.2.4: {} - lodash.isplainobject@4.0.6: {} - - lodash.isstring@4.0.1: {} - - lodash.merge@4.6.2: {} - - lodash.uniqby@4.7.0: {} - - log-symbols@7.0.1: + locate-path@5.0.0: dependencies: - is-unicode-supported: 2.1.0 - yoctocolors: 2.1.2 + p-locate: 4.1.0 - lru-cache@11.3.6: {} + lodash.ismatch@4.4.0: {} - lru-cache@7.18.3: {} + lru-cache@11.3.6: {} - macos-release@3.4.0: {} + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - mime-db@1.54.0: {} + map-obj@1.0.1: {} + + map-obj@4.3.0: {} - mime-types@3.0.2: + meow@8.1.2: dependencies: - mime-db: 1.54.0 + '@types/minimist': 1.2.5 + camelcase-keys: 6.2.2 + decamelize-keys: 1.1.1 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 7.0.1 + redent: 3.0.0 + trim-newlines: 3.0.1 + type-fest: 0.18.1 + yargs-parser: 20.2.9 mimic-fn@2.1.0: {} - mimic-function@5.0.1: {} + min-indent@1.0.1: {} minimatch@10.2.5: dependencies: brace-expansion: 5.0.5 + minimist-options@4.1.0: + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 + + minimist@1.2.8: {} + minipass@7.1.3: {} - ms@2.1.3: {} + modify-values@1.0.1: {} - mute-stream@3.0.0: {} + ms@2.1.3: {} nanoid@3.3.12: {} - netmask@2.1.1: {} - - new-github-release-url@2.0.0: - dependencies: - type-fest: 2.19.0 + neo-async@2.6.2: {} node-addon-api@7.1.1: {} node-addon-api@8.7.0: {} - node-fetch-native@1.6.7: {} - node-gyp-build@4.8.4: {} + node-html-parser@6.1.13: + dependencies: + css-select: 5.2.2 + he: 1.2.0 + node-pty@1.1.0: dependencies: node-addon-api: 7.1.1 - nypm@0.6.6: + normalize-package-data@2.5.0: dependencies: - citty: 0.2.2 - pathe: 2.0.3 - tinyexec: 1.1.2 + hosted-git-info: 2.8.9 + resolve: 1.22.12 + semver: 5.7.2 + validate-npm-package-license: 3.0.4 - obug@2.1.1: {} - - ohash@2.0.11: {} - - onetime@5.1.2: + normalize-package-data@3.0.3: dependencies: - mimic-fn: 2.1.0 + hosted-git-info: 4.1.0 + is-core-module: 2.16.2 + semver: 7.7.4 + validate-npm-package-license: 3.0.4 - onetime@7.0.0: + nth-check@2.1.1: dependencies: - mimic-function: 5.0.1 + boolbase: 1.0.0 - open@11.0.0: - dependencies: - default-browser: 5.5.0 - define-lazy-prop: 3.0.0 - is-in-ssh: 1.0.0 - is-inside-container: 1.0.0 - powershell-utils: 0.1.0 - wsl-utils: 0.3.1 + obug@2.1.1: {} - ora@9.3.0: + once@1.4.0: dependencies: - chalk: 5.6.2 - cli-cursor: 5.0.0 - cli-spinners: 3.4.0 - is-interactive: 2.0.0 - is-unicode-supported: 2.1.0 - log-symbols: 7.0.1 - stdin-discarder: 0.3.2 - string-width: 8.2.1 + wrappy: 1.0.2 - os-name@7.0.0: + onetime@5.1.2: dependencies: - macos-release: 3.4.0 - windows-release: 7.1.1 + mimic-fn: 2.1.0 oxfmt@0.47.0: dependencies: @@ -2683,36 +2627,33 @@ snapshots: '@oxlint/binding-win32-arm64-msvc': 1.62.0 '@oxlint/binding-win32-x64-msvc': 1.62.0 - pac-proxy-agent@8.0.0(quickjs-wasi@0.0.1): + p-limit@2.3.0: dependencies: - agent-base: 8.0.0 - debug: 4.4.3 - get-uri: 7.0.0 - http-proxy-agent: 8.0.0 - https-proxy-agent: 8.0.0 - pac-resolver: 8.0.0(quickjs-wasi@0.0.1) - quickjs-wasi: 0.0.1 - socks-proxy-agent: 9.0.0 + p-try: 2.2.0 - pac-resolver@8.0.0(quickjs-wasi@0.0.1): + p-locate@4.1.0: dependencies: - degenerator: 6.0.0(quickjs-wasi@0.0.1) - netmask: 2.1.1 - quickjs-wasi: 0.0.1 + p-limit: 2.3.0 + + p-try@2.2.0: {} package-json-from-dist@1.0.1: {} - parse-path@7.1.0: - dependencies: - protocols: 2.0.2 + parse-github-repo-url@1.4.1: {} - parse-url@9.2.0: + parse-json@5.2.0: dependencies: - '@types/parse-path': 7.1.0 - parse-path: 7.1.0 + '@babel/code-frame': 7.29.7 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 patch-console@2.0.0: {} + path-exists@4.0.0: {} + + path-parse@1.0.7: {} + path-scurry@2.0.2: dependencies: lru-cache: 11.3.6 @@ -2720,18 +2661,10 @@ snapshots: pathe@2.0.3: {} - perfect-debounce@2.1.0: {} - picocolors@1.1.1: {} picomatch@4.0.4: {} - pkg-types@2.3.1: - dependencies: - confbox: 0.2.4 - exsolve: 1.0.8 - pathe: 2.0.3 - playwright-core@1.60.0: {} playwright@1.60.0: @@ -2746,31 +2679,7 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - powershell-utils@0.1.0: {} - - powershell-utils@0.2.0: {} - - protocols@2.0.2: {} - - proxy-agent@7.0.0(quickjs-wasi@0.0.1): - dependencies: - agent-base: 8.0.0 - debug: 4.4.3 - http-proxy-agent: 8.0.0 - https-proxy-agent: 8.0.0 - lru-cache: 7.18.3 - pac-proxy-agent: 8.0.0(quickjs-wasi@0.0.1) - proxy-from-env: 1.1.0 - socks-proxy-agent: 9.0.0 - - proxy-from-env@1.1.0: {} - - quickjs-wasi@0.0.1: {} - - rc9@2.1.2: - dependencies: - defu: 6.1.7 - destr: 2.0.5 + quick-lru@4.0.1: {} react-reconciler@0.33.0(react@19.2.7): dependencies: @@ -2779,46 +2688,74 @@ snapshots: react@19.2.7: {} - readdirp@5.0.0: {} + read-pkg-up@7.0.1: + dependencies: + find-up: 4.1.0 + read-pkg: 5.2.0 + type-fest: 0.8.1 - release-it@20.0.1(@octokit/core@7.0.6)(@types/node@25.5.0)(picomatch@4.0.4)(quickjs-wasi@0.0.1): + read-pkg@5.2.0: dependencies: - '@inquirer/prompts': 8.4.2(@types/node@25.5.0) - '@octokit/rest': 22.0.1(@octokit/core@7.0.6) - '@phun-ky/typeof': 2.0.3 + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 2.5.0 + parse-json: 5.2.0 + type-fest: 0.6.0 + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + release-please@17.9.0(@octokit/core@5.2.2)(jsep@1.4.0): + dependencies: + '@conventional-commits/parser': 0.4.1 + '@google-automations/git-file-utils': 3.0.1(@octokit/core@5.2.2) + '@iarna/toml': 3.0.0 + '@octokit/graphql': 7.1.1 + '@octokit/request': 8.4.1 + '@octokit/request-error': 5.1.1 + '@octokit/rest': 20.1.2(@octokit/core@5.2.2) + '@types/npm-package-arg': 6.1.4 + '@xmldom/xmldom': 0.8.13 async-retry: 1.3.3 - c12: 3.3.3 - ci-info: 4.4.0 - defu: 6.1.7 - eta: 4.5.1 - git-url-parse: 16.1.0 - issue-parser: 7.0.1 - lodash.merge: 4.6.2 - mime-types: 3.0.2 - new-github-release-url: 2.0.0 - open: 11.0.0 - ora: 9.3.0 - os-name: 7.0.0 - proxy-agent: 7.0.0(quickjs-wasi@0.0.1) + chalk: 4.1.2 + conventional-changelog-conventionalcommits: 6.1.0 + conventional-changelog-writer: 6.0.1 + conventional-commits-filter: 3.0.0 + detect-indent: 6.1.0 + diff: 8.0.4 + figures: 3.2.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + js-yaml: 4.2.0 + jsonpath-plus: 10.4.0(jsep@1.4.0) + node-html-parser: 6.1.13 + parse-github-repo-url: 1.4.1 semver: 7.7.4 - tinyglobby: 0.2.15(picomatch@4.0.4) - undici: 7.24.5 - url-join: 5.0.0 - wildcard-match: 5.1.4 - yargs-parser: 22.0.0 + type-fest: 3.13.1 + typescript: 4.9.5 + unist-util-visit: 2.0.3 + unist-util-visit-parents: 3.1.1 + xpath: 0.0.34 + yaml: 2.8.4 + yargs: 17.7.2 + + require-directory@2.1.1: {} resolve-pkg-maps@1.0.0: {} + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + restore-cursor@4.0.0: dependencies: onetime: 5.1.2 signal-exit: 3.0.7 - restore-cursor@5.1.0: - dependencies: - onetime: 7.0.0 - signal-exit: 4.1.0 - retry@0.13.1: {} rimraf@6.1.3: @@ -2839,41 +2776,42 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.18 '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.18 - run-applescript@7.1.0: {} - - safer-buffer@2.1.2: {} - scheduler@0.27.0: {} + semver@5.7.2: {} + semver@7.7.4: {} siginfo@2.0.0: {} signal-exit@3.0.7: {} - signal-exit@4.1.0: {} - slice-ansi@9.0.0: dependencies: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 - smart-buffer@4.2.0: {} + source-map-js@1.2.1: {} - socks-proxy-agent@9.0.0: + source-map@0.6.1: {} + + spdx-correct@3.2.0: dependencies: - agent-base: 8.0.0 - debug: 4.4.3 - socks: 2.8.8 + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.23 + + spdx-exceptions@2.5.0: {} - socks@2.8.8: + spdx-expression-parse@3.0.1: dependencies: - ip-address: 10.2.0 - smart-buffer: 4.2.0 + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.23 - source-map-js@1.2.1: {} + spdx-license-ids@3.0.23: {} - source-map@0.6.1: {} + split@1.0.1: + dependencies: + through: 2.3.8 stack-utils@2.0.6: dependencies: @@ -2883,21 +2821,41 @@ snapshots: std-env@4.1.0: {} - stdin-discarder@0.3.2: {} + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 string-width@8.2.1: dependencies: get-east-asian-width: 1.5.0 strip-ansi: 7.2.0 + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + strip-ansi@7.2.0: dependencies: ansi-regex: 6.2.2 + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + tagged-tag@1.0.0: {} terminal-size@4.0.1: {} + through@2.3.8: {} + tinybench@2.9.0: {} tinyexec@1.1.2: {} @@ -2916,7 +2874,7 @@ snapshots: tinyrainbow@3.1.0: {} - tslib@2.8.1: {} + trim-newlines@3.0.1: {} tsx@4.21.0: dependencies: @@ -2925,23 +2883,47 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - type-fest@2.19.0: {} + type-fest@0.18.1: {} + + type-fest@0.6.0: {} + + type-fest@0.8.1: {} + + type-fest@3.13.1: {} type-fest@5.7.0: dependencies: tagged-tag: 1.0.0 + typescript@4.9.5: {} + typescript@5.9.3: {} + uglify-js@3.19.3: {} + ulid@3.0.2: {} undici-types@7.18.2: {} - undici@7.24.5: {} + unist-util-is@4.1.0: {} - universal-user-agent@7.0.3: {} + unist-util-visit-parents@3.1.1: + dependencies: + '@types/unist': 2.0.11 + unist-util-is: 4.1.0 + + unist-util-visit@2.0.3: + dependencies: + '@types/unist': 2.0.11 + unist-util-is: 4.1.0 + unist-util-visit-parents: 3.1.1 - url-join@5.0.0: {} + universal-user-agent@6.0.1: {} + + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 vite@8.0.11(@types/node@25.5.0)(esbuild@0.27.7)(jiti@2.7.0)(tsx@4.21.0)(yaml@2.8.4): dependencies: @@ -2991,11 +2973,7 @@ snapshots: dependencies: string-width: 8.2.1 - wildcard-match@5.1.4: {} - - windows-release@7.1.1: - dependencies: - powershell-utils: 0.2.0 + wordwrap@1.0.0: {} wrap-ansi@10.0.0: dependencies: @@ -3003,18 +2981,37 @@ snapshots: string-width: 8.2.1 strip-ansi: 7.2.0 + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + ws@8.20.0: {} - wsl-utils@0.3.1: - dependencies: - is-wsl: 3.1.1 - powershell-utils: 0.1.0 + xpath@0.0.34: {} + + y18n@5.0.8: {} + + yallist@4.0.0: {} yaml@2.8.4: {} - yargs-parser@22.0.0: {} + yargs-parser@20.2.9: {} - yoctocolors@2.1.2: {} + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 yoga-layout@3.2.1: {} diff --git a/docs/RELEASE-PROCESS.md b/docs/RELEASE-PROCESS.md index 11d76d58..39f0c905 100644 --- a/docs/RELEASE-PROCESS.md +++ b/docs/RELEASE-PROCESS.md @@ -2,6 +2,8 @@ [`../RELEASE.md`](../RELEASE.md) defines the supported product contract. This document is the canonical maintainer process for validating, versioning, tagging, and publishing that contract on GitHub Releases and npm. +Releases are driven by [release-please](https://github.com/googleapis/release-please) running as a library with Communique-generated changelog notes (ADR 0009). The short version: a workflow maintains one **release PR** at all times; merging that PR **is** the release decision — the tag, GitHub Release, and publish pipeline follow automatically. + ## One-time npm trusted publishing setup 1. Ensure the intended npm package name remains `agent-tty`. @@ -17,20 +19,52 @@ - `package.json.publishConfig.registry` must stay `https://registry.npmjs.org/` 5. No GitHub Actions secret is required for npm publishing in this flow; the workflow uses GitHub-hosted runners plus OIDC trusted publishing. +## How the release automation works + +Three workflows cooperate: + +1. **Release Please** ([`.github/workflows/release-please.yml`](../.github/workflows/release-please.yml)) runs on every push to `main`. It executes [`src/tools/release-please-runner.ts`](../src/tools/release-please-runner.ts), which runs release-please as a library with Communique registered as the changelog generator (`"changelog-type": "communique"` in [`release-please-config.json`](../release-please-config.json)). Each run: + - tags and creates the GitHub Release for any release PR that merged since the last run, then dispatches the **Release** workflow for that tag; + - opens or updates the single release PR on branch `release-please--branches--main`, carrying the `package.json` version bump plus a new `CHANGELOG.md` section written by Communique (`communique generate HEAD --concise`); + - dispatches **CI** and **Validate skills** onto the release branch (pushes made with the workflow token never trigger `pull_request` workflows on their own). +2. **Release** ([`.github/workflows/release.yml`](../.github/workflows/release.yml)) is the publish pipeline (see below). It is dispatched by Release Please after the tag exists, and still also triggers on manually pushed `v*` tags. +3. **CI** runs on the release PR like on any other PR and gates the merge. + +Configuration lives in [`release-please-config.json`](../release-please-config.json) (release behavior) and [`.release-please-manifest.json`](../.release-please-manifest.json) (the version release-please considers current; release-please updates it through release PRs). + +### Versioning + +The next version is computed from Conventional Commit subjects on `main` since the last release. While the package is pre-1.0 the config maps: + +- breaking changes (`feat!:`, `BREAKING CHANGE:`) → **minor** bump (`bump-minor-pre-major`) +- `feat:` / `fix:` / everything else releasable → **patch** bump (`bump-patch-for-minor-pre-major`) + +To force a specific version (for example the eventual `1.0.0`, or a prerelease), land an empty commit with a `Release-As` footer and let the workflow rebuild the PR: + +```bash +git commit --allow-empty -m "chore: force release" -m "Release-As: 1.0.0" +``` + +### The changelog contract + +- `CHANGELOG.md` has no `[Unreleased]` section. The release PR **is** the unreleased view: its `CHANGELOG.md` diff and PR body always show the pending release's curated notes. +- New sections use the heading `## [] - ` (no `v` inside the brackets — release-please parses the version back out of the merged PR body, and its parser requires the bracket to be followed by a digit; entries before v0.4.2 keep the historical `[v]` style). +- Feature PRs must never touch `CHANGELOG.md`; the file is written only through release PRs. +- Notes are regenerated by Communique on every push to `main`, so manual edits to the release PR (branch or body) are overwritten by the next push. To fix wording, improve the source material instead: edit the merged feature PR descriptions/titles, or add a `BEGIN_COMMIT_OVERRIDE` block to a merged PR body, then let the next push to `main` rebuild the notes. (After the release, `gh release edit` can still fix the published notes.) + ## Release prerequisites 1. Re-read [`../RELEASE.md`](../RELEASE.md) and confirm it still matches the shipped surface. 2. Verify the primary docs route correctly from [`../README.md`](../README.md) to release, design, and dogfood materials. 3. Review [`../dogfood/CATALOG.md`](../dogfood/CATALOG.md) and make sure the release-signoff bundle is current and easy to find. 4. Confirm the published package metadata still points at `agent-tty` and the public GitHub repository. -5. Remember that `main` is protected: release changes must land through a pull request, and the release tag must be created only after that PR is merged. -6. Confirm release-note automation has an LLM provider secret available in GitHub Actions: +5. Confirm release-note automation has an LLM provider secret available in GitHub Actions: - default/recommended: `ANTHROPIC_API_KEY` - OpenAI-compatible fallback: `OPENAI_API_KEY` plus a repository variable named `COMMUNIQUE_MODEL` ## GitHub CLI readiness -This flow assumes `gh` can create PRs, inspect checks, merge, and create releases. `gh auth status` is useful for a quick summary, but in some environments it can report a stale or misleading state even when real GitHub API calls still work. +This flow assumes `gh` can inspect checks, approve, and merge PRs. `gh auth status` is useful for a quick summary, but in some environments it can report a stale or misleading state even when real GitHub API calls still work. Before treating release automation as blocked, verify with a real API call such as: @@ -42,13 +76,13 @@ If that succeeds, prefer the result of the real `gh` operation over the status s ## Validation bar -Preferred local validation uses `mise`: +The release PR runs the same CI as any other PR, and the publish pipeline reruns the full quality bar against the tagged commit before any asset is built. For local validation use `mise`: ```bash mise run ci ``` -GitHub Actions installs mise-managed tools from the committed [`../mise.lock`](../mise.lock) with `--locked`. If `mise.toml` tool versions or supported CI platforms change, regenerate the lock before opening the release PR: +GitHub Actions installs mise-managed tools from the committed [`../mise.lock`](../mise.lock) with `--locked`. If `mise.toml` tool versions or supported CI platforms change, regenerate the lock before merging: ```bash mise lock @@ -66,9 +100,7 @@ If the public bootstrap under `skills/` or the bundled runtime skills under `ski npm run intent:validate ``` -`mise run ci` exercises formatting, GitHub Actions workflow linting, lint, typecheck, tests, build, and the install smoke. The install smoke validates the shared release tarball packer plus the required tarball install route before any publish step runs. - -## Prepare the release asset locally (optional but recommended) +## Prepare the release asset locally (optional) Use the same release packer that CI relies on: @@ -82,221 +114,27 @@ sha256sum -c "$RELEASE_DIR"/*.tgz.sha256 When skill packaging changes, also inspect `npm pack --dry-run` output to confirm the tarball still includes both `skills/` (bootstrap) and `skill-data/` (runtime skills). -That command produces the same tarball, checksum, and metadata shape that the GitHub release workflow uploads and later reuses for npm publishing. - -## Release flow overview - -Because `main` is pull-request-only, the release process has three named parts: - -1. **Release Prep Workflow**: prepare a reviewable release branch and one local release-prep commit. -2. **Release Finalization Step**: after the release PR merges, create and push the matching annotated release tag from clean, synced `main`. -3. **Publish Pipeline**: let the tag-triggered `Release` workflow publish GitHub assets and npm. - -Do **not** run `npm version ...` on `main` and then push `HEAD --follow-tags`; GitHub will reject the protected-branch push but still accept the tag, which can start a release from an unmerged commit. - -The primary commands are project-owned wrappers: - -```bash -npm run release:prep -- --version --changelog local|ci -npm run release:finalize -``` - -`release-it` is an implementation detail of the prep command only. Do not call raw `release-it` for agent-tty releases. - -## Prepare the version-bump PR - -Start from a clean, up-to-date `main` checkout: - -```bash -git checkout main -git pull origin main -``` - -Choose the exact release version. Increment aliases such as `patch`, `prepatch`, and `prerelease` are intentionally not part of the first scripted workflow; pass the exact semantic version instead. - -Stable release example: - -```bash -npm run release:prep -- --version 0.1.1 --changelog ci -``` - -Prerelease example: - -```bash -npm run release:prep -- --version 0.1.1-beta.0 --changelog ci -``` - -Versions containing a hyphen, such as `-beta.0` or `-rc.0`, are published by the workflow as GitHub prereleases and published to the matching npm dist-tag (`beta`, `rc`, and so on). - -### Changelog mode - -Use `--changelog ci` for the default maintainer path. The prep commit will contain only the version files — `package.json` plus `package-lock.json` when present (after PR #91 this repo uses `aube-lock.yaml` instead, so the prep commit on the default branch contains only `package.json`). The `Release Changelog` workflow will update `CHANGELOG.md` on the release branch when needed. - -```bash -npm run release:prep -- --version --changelog ci -``` - -Use `--changelog local` only when you want to inspect the Communique changelog before opening the PR and have the required local credentials/tooling available: - -```bash -npm run release:prep -- --version --changelog local -``` - -Local changelog generation requires either `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`; when using only `OPENAI_API_KEY`, set `COMMUNIQUE_MODEL`. It also requires `communique` on `PATH` and GitHub API auth through `GITHUB_TOKEN` or an authenticated `gh` session. If those prerequisites are unavailable, rerun with `--changelog ci`. - -Add `--verify` when you want the prep script to run the full local validation bar after creating the release-prep commit: - -```bash -npm run release:prep -- --version --changelog ci --verify -``` - -The prep script validates release-specific invariants, creates `release/` locally, updates the version files through pinned release-it configuration, optionally updates `CHANGELOG.md`, stages only allowlisted release-prep files, and creates exactly one commit: - -```text -chore(release): -``` - -It does not push the branch or open the pull request. After it succeeds, push and open the PR with the commands printed by the script: - -```bash -git push -u origin release/ -gh pr create --base main --head release/ --title "chore(release): " -``` - -Release branches named `release/*` are watched by the `Release Changelog` workflow. When `package.json` changes the package version and `CHANGELOG.md` does not already contain that version, the workflow runs: - -```bash -communique generate "v" --changelog --repo coder/agent-tty -``` +## Cut a release -and commits the resulting `CHANGELOG.md` update back to the release branch. When it pushes that bot commit, it dispatches the CI and skill-validation workflows for the updated release branch so protected-branch checks can run against the new head commit. +1. Open the current release PR (head branch `release-please--branches--main`, title `chore(release): `). If it is missing or stale, trigger the **Release Please** workflow manually (`gh workflow run release-please.yml`) or push to `main`. +2. Review the proposed version and the `CHANGELOG.md` section. If the version is wrong, land a `Release-As` commit (see above) rather than editing the PR. If notes are wrong, fix the source material (feature PR bodies / commit overrides) and let the next push rebuild them. +3. Wait for the dispatched checks on the release branch to pass. If they were never dispatched (for example after a manual branch poke), dispatch them yourself: -### `[Unreleased]` heading must stay on `main` + ```bash + gh workflow run ci.yml --ref release-please--branches--main + gh workflow run validate-skills.yml --ref release-please--branches--main + ``` -When preparing the release PR, **rename** the existing `## [Unreleased]` heading to `## [v] - ` and **insert a fresh empty `## [Unreleased]` heading immediately above it** before pushing. Do not remove `[Unreleased]` outright. - -Two workflows depend on this: - -- `Release Changelog` (release-changelog.yml) keys off `## [v]` to decide whether to run Communique on the release branch. Having `## [v] - ` present makes the workflow skip its Communique pass and preserve the curated `[Unreleased]` content that you just renamed. -- `Update Unreleased Changelog` (update-unreleased-changelog.yml) runs on every push to `main` and calls `communique generate HEAD --changelog`, which requires `## [Unreleased]` or `## Unreleased` to exist. If the heading is missing on `main`, every post-release merge fails this workflow until the heading is restored. - -So the right CHANGELOG.md state after the release-prep commit is roughly: - -```markdown -# Changelog - -## [Unreleased] - -## [v] - - -### Added - -... -``` - -`release:prep --changelog ci` does not edit `CHANGELOG.md` for you, so this rename + insert must be done manually (or via `release:prep --changelog local`, which lets Communique handle both steps). After running `release:prep --changelog ci`, edit `CHANGELOG.md`, then `git add CHANGELOG.md && git commit --amend --no-edit` to fold the change into the same release-prep commit. - -### Manual prep fallback - -If the scripted prep path is blocked, use the manual fallback only from a clean, up-to-date `main` checkout. Stage `package-lock.json` only if your checkout still has one (post-PR #91 the repo is aube-only and the file is absent): - -```bash -git switch -c release/ -npm version --no-git-tag-version -git add package.json -[[ -f package-lock.json ]] && git add package-lock.json -git commit -m "chore(release): " -git push -u origin release/ -gh pr create --base main --head release/ --title "chore(release): " -``` - -For the local changelog variant, run Communique after `npm version ... --no-git-tag-version` and include `CHANGELOG.md` in the same commit: - -```bash -communique generate "v" --changelog --repo coder/agent-tty -git add package.json CHANGELOG.md -[[ -f package-lock.json ]] && git add package-lock.json -git commit -m "chore(release): " -``` - -## Wait for CI and merge the release PR - -For interactive use, `gh pr checks --watch` is fine. For automation or agent-driven release work, prefer inspecting the workflow run directly so you can wait on structured `status` and `conclusion` fields instead of parsing live terminal refresh output. - -Typical sequence: - -```bash -gh pr checks -gh run list --branch --event pull_request --limit 5 -gh run view --json status,conclusion,jobs -``` - -If the PR still cannot be merged after every required check passes, inspect the base-branch policy first. When normal merge and `--auto` are unavailable but an authorized releaser is allowed to override the policy, use: - -```bash -gh pr merge --squash --admin --delete-branch -``` - -Use `--admin` sparingly and only after confirming the required release checks succeeded. - -## Tag the merged `main` commit - -After the release PR has merged, use the Release Finalization Step from clean, synced `main`: - -```bash -git checkout main -git pull origin main -npm run release:finalize -``` - -Add `--verify` when you want to run the full local validation bar immediately before tagging: - -```bash -npm run release:finalize -- --verify -``` - -The finalize script verifies that `package.json` and `package-lock.json` agree, derives the release tag as `v${package.json.version}`, rejects pre-existing local or remote tags, creates an annotated tag, and pushes only that tag. - -The tag must match `package.json` exactly: - -- `package.json`: `0.1.1` -- tag: `v0.1.1` - -or: - -- `package.json`: `0.1.1-beta.0` -- tag: `v0.1.1-beta.0` - -### Manual tag fallback - -If the scripted finalization path is blocked after the release PR has merged, run the equivalent manual commands from clean, synced `main`: - -```bash -VERSION=$(node --input-type=module -e "import pkg from './package.json' with { type: 'json' }; process.stdout.write(pkg.version)") -git tag -a "v${VERSION}" -m "v${VERSION}" -git push origin "v${VERSION}" -``` - -### GitHub CLI alternative: create the tag and release in one step - -If you already know the merged `main` commit SHA and want GitHub to create the tag and release together, this also works: - -```bash -MERGED_SHA= -gh release create v0.1.1-beta.0 \ - --target "$MERGED_SHA" \ - --prerelease \ - --latest=false \ - --title v0.1.1-beta.0 \ - --notes-file -``` - -`gh release create` creates the tag on the specified merged commit and still triggers the `Release` workflow via the pushed `v*` tag. Use the prerelease flags only for prerelease versions; omit them for stable releases. +4. Approve and merge the release PR (squash, like any other PR). The release PR is authored by the workflow bot, so a maintainer approval satisfies the required-review ruleset — no admin bypass is needed. +5. The merge push triggers **Release Please** again, which: + - creates the `v` tag on the merge commit and the GitHub Release (notes = the changelog section from the merged PR body), + - dispatches the **Release** workflow for the tag. +6. Watch the **Release** workflow to completion, then verify (sections below). The GitHub Release exists for a few minutes before assets attach and before the editorial notes replace the changelog section — the notes themselves tell readers npm install becomes available once the publish job completes. ## Publish the GitHub Release and npm package The hand-curated workflow lives at [`.github/workflows/release.yml`](../.github/workflows/release.yml). -It triggers automatically on pushed `v*` tags, and it can also be rerun manually for an already-existing remote tag via the **Release** workflow's `tag` input. +It is dispatched by Release Please for new tags, triggers automatically on manually pushed `v*` tags, and can be rerun for an existing tag via the **Release** workflow's `tag` input. The workflow will: @@ -307,8 +145,8 @@ The workflow will: - run `mise run ci`, - pack the verified tarball with `npm run pack:release`, - upload the tarball, checksum, and metadata JSON as workflow artifacts, -- generate Communique release notes for the tag, -- create or update the GitHub Release with Communique notes plus the deterministic install/checksum block and the `.tgz` / `.sha256` assets attached, +- generate editorial Communique release notes for the tag, +- update the GitHub Release with those notes plus the deterministic install/checksum block and the `.tgz` / `.sha256` assets attached, - and publish that same verified tarball to npm via trusted publishing on a GitHub-hosted runner. Stable releases publish with npm's default `latest` dist-tag. @@ -388,47 +226,32 @@ If you are testing a public release and the direct asset URL is reachable in you ## Failure and recovery -### Release prep fails after creating a branch +### The release PR is missing, stale, or has wrong notes -If `npm run release:prep` fails after creating `release/`, inspect the local branch before deleting anything: +Trigger the workflow again — it rebuilds the PR from scratch on every run: ```bash -git status -git log --oneline --decorate --max-count=5 +gh workflow run release-please.yml ``` -If there is no work worth preserving, return to `main`, delete the local release branch, and rerun from clean, synced `main`: +Wrong version → land a `Release-As` commit. Wrong notes → fix the merged feature PR bodies (or add `BEGIN_COMMIT_OVERRIDE` blocks) and rerun. Never edit the release PR directly; edits are overwritten. -```bash -git switch main -git branch -D release/ -git pull origin main -npm run release:prep -- --version --changelog ci -``` - -### Release finalization fails to push the tag +### The release PR merged but no tag or GitHub Release appeared -If `npm run release:finalize` creates the local tag but fails before pushing it, delete the local tag, fix the underlying issue, and retry from clean, synced `main`: +The Release Please run triggered by the merge failed before `createReleases()` finished. Rerun it — release creation is idempotent (it finds merged release PRs still labeled `autorelease: pending`): ```bash -git tag -d v -npm run release:finalize +gh workflow run release-please.yml ``` -### Release finalization pushes a tag but the Release workflow fails before publishing +### The tag and GitHub Release exist but the Release workflow failed before publishing -If `npm run release:finalize` pushes the tag but the workflow fails before any GitHub Release or npm publish, fix the underlying issue on `main`. Delete and recreate the failed tag only if maintainers explicitly decide it is safe, and document the action. - -Example tag cleanup, only after that explicit decision: +Fix the underlying issue, then rerun the publish pipeline for the existing tag: ```bash -gh run cancel -git push origin :refs/tags/vX.Y.Z -git tag -d vX.Y.Z +gh workflow run release.yml --field tag=v ``` -Then rerun the Release Finalization Step from clean, synced `main`. - ### npm published but GitHub Release or verification fails If npm publish succeeds, never reuse the same version, even if later GitHub Release asset creation or verification fails. Repair forward with a new version, or complete missing release assets manually according to maintainer policy. @@ -437,15 +260,9 @@ If npm publish succeeds, never reuse the same version, even if later GitHub Rele If the GitHub Release exists but npm publish fails, treat the release as partial. Verify which assets and npm state exist, then follow maintainer policy before deleting assets, deleting tags, or retrying publish automation. -### Accidental tag before merge +### A release was tagged manually (outside release-please) -If a release tag is accidentally pushed before the version-bump PR is merged, cancel the in-progress workflow, delete the remote tag, delete the local tag, and redo the release through the PR-first flow above. - -```bash -gh run cancel -git push origin :refs/tags/vX.Y.Z -git tag -d vX.Y.Z -``` +Manual tagging (`git tag` + push, or `gh release create`) still triggers the publish pipeline, but it bypasses release-please's bookkeeping: [`.release-please-manifest.json`](../.release-please-manifest.json) keeps the old version, so the next release PR computes its version and commit range from the wrong baseline. After any manual release, open a PR updating `.release-please-manifest.json` (and `package.json` if it was not bumped) to the manually released version. ## Proof expectations diff --git a/docs/adr/0002-use-release-it-only-for-release-prep.md b/docs/adr/0002-use-release-it-only-for-release-prep.md index aafb4c82..8e95d61e 100644 --- a/docs/adr/0002-use-release-it-only-for-release-prep.md +++ b/docs/adr/0002-use-release-it-only-for-release-prep.md @@ -1,9 +1,14 @@ --- -status: accepted +status: superseded by 0009 --- # Use release-it only for release prep +> **Superseded:** release prep is now owned by release-please with Communique +> changelog notes ([ADR 0009](0009-release-please-with-communique-notes.md)). +> The `release:prep` / `release:finalize` commands and the release-it +> dependency were removed. + ## Context agent-tty already has a tag-triggered publish pipeline in `.github/workflows/release.yml`. That pipeline validates the release tag against `package.json`, checks that the tagged commit is reachable from `main`, runs CI, creates one release tarball, uploads checksum assets, generates Communique release notes, creates or updates the GitHub Release, and publishes the same tarball to npm via trusted publishing/OIDC. diff --git a/docs/adr/0009-release-please-with-communique-notes.md b/docs/adr/0009-release-please-with-communique-notes.md new file mode 100644 index 00000000..d1c25f87 --- /dev/null +++ b/docs/adr/0009-release-please-with-communique-notes.md @@ -0,0 +1,35 @@ +--- +status: accepted +--- + +# Release-please as a library with Communique changelog notes + +## Context + +The release flow had four moving parts: a per-push workflow that kept an `[Unreleased]` section of `CHANGELOG.md` updated through bot PRs (`update-unreleased-changelog.yml`), local `release:prep` / `release:finalize` scripts (ADR 0002, release-it as the version-file engine), a workflow that wrote the release section onto `release/*` PRs (`release-changelog.yml`), and the tag-triggered Publish Pipeline (`release.yml`). It worked, but with friction: + +- Releasing required two local script runs on a literal, clean `main` checkout with `node_modules` installed, and an admin-merge — the maintainer authored the release PR, so they could not approve it themselves under the required-review ruleset. +- The changelog automation produced a continuous stream of `[Unreleased]` PRs whose merges raced other work, and the `[Unreleased]` / `## [v]` heading pair was a contract two workflows depended on. +- Communique ran in three flavors (unreleased section, release section, editorial release notes) across three workflows. + +[release-please](https://github.com/googleapis/release-please) provides the desired shape — one continuously maintained release PR whose merge produces the tag and GitHub Release — but its GitHub Action only supports the built-in changelog generators (conventional-commit bullets or GitHub auto-notes). The Communique-written changelog entries are a feature we keep. + +## Decision + +Run release-please **as a library** from a repo-owned runner (`src/tools/release-please-runner.ts`, executed by `.github/workflows/release-please.yml` on every push to `main`), with Communique registered as a custom changelog-notes type via `registerChangelogNotes('communique', ...)`. The notes hook shells out to `communique generate HEAD --concise`, passing release-please's previous-tag so both tools agree on the commit range, and wraps the output in a `## [] - ` heading. + +Decisions inside that frame: + +- **Heading drops the `v`** (`## [0.4.2] - ...`, not `## [v0.4.2] - ...`): release-please recovers version and notes by parsing the merged PR body with `/^#{2,} \[?(\d+\.\d+\.\d+...)/` — a digit must follow the bracket, or merging the PR creates no GitHub Release. Unit tests pin this contract and the CHANGELOG insertion point against the installed release-please internals. +- **No `[Unreleased]` section.** The release PR is the unreleased view. The two workflows that depended on the heading pair are retired with it. +- **Version bumps from Conventional Commits** with `bump-minor-pre-major` + `bump-patch-for-minor-pre-major`, matching the project's pre-1.0 history (breaking → minor, feat/fix → patch); `Release-As` footers override. +- **Tags and releases are created non-draft** by the runner, which then dispatches the existing `release.yml` by tag input — tags created with the workflow token never fire `push: tags` triggers, and the repo already uses explicit `gh workflow run` dispatch for exactly this class of problem (CI on bot branches). `release.yml` keeps quality gates, the verified tarball, editorial Communique notes, assets, and npm trusted publishing unchanged; it briefly leaves the Release with changelog-style notes and no assets until it completes. +- **release-please is a pinned devDependency** installed by the normal `aube ci` bootstrap. Its CJS-only octokit 9.x line carries no npm provenance attestations, so `@octokit/endpoint@9.0.6` is excluded from aube's trust policy in `pnpm-workspace.yaml` with justification. + +## Consequences + +- Releasing is: review the standing release PR, approve, merge. No local scripts, no admin bypass (the bot authors the PR, so maintainer review satisfies the ruleset), no manual tagging. +- `CHANGELOG.md` is written only through release PRs; entries from v0.4.2 onward use the bracketed no-`v` heading while older entries keep their style. Feature PRs still must not touch the file. +- Notes regenerate from scratch on every push to `main`: an LLM call per push (same cost profile as the retired unreleased workflow), and reviewed wording can drift between pushes — the fix-the-source loop (feature PR bodies, `BEGIN_COMMIT_OVERRIDE`) replaces hand-editing the release PR. +- The runner is ~250 lines we own, pinned to an exact release-please version; the PR-body parsing contract it relies on is undocumented upstream, so version bumps must keep the compatibility tests green. +- Manual tagging remains an emergency path but now requires re-syncing `.release-please-manifest.json` afterwards (documented in `docs/RELEASE-PROCESS.md`). diff --git a/package.json b/package.json index 7fec770a..01717566 100644 --- a/package.json +++ b/package.json @@ -51,8 +51,6 @@ "pack:release": "node ./scripts/pack-release.mjs", "prepare": "npm run build", "prepublishOnly": "npm run verify", - "release:prep": "node ./scripts/release-prep.mjs", - "release:finalize": "node ./scripts/release-finalize.mjs", "review-bundle": "tsx src/tools/review-bundle.ts", "smoke:install": "node ./scripts/smoke-install.mjs", "test": "vitest run --retry=2", @@ -72,7 +70,7 @@ "oxfmt": "0.47.0", "oxlint": "1.62.0", "oxlint-tsgolint": "0.22.1", - "release-it": "20.0.1", + "release-please": "17.9.0", "rimraf": "6.1.3", "tsx": "4.21.0", "typescript": "5.9.3", diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..40511507 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,23 @@ +# aube configuration (aube reads pnpm-workspace.yaml keys; npm ignores this +# file, so settings here don't trigger "Unknown project config" warnings the +# way .npmrc keys do). + +# Resolve optional platform dependencies for every platform devs and CI use, +# so the lockfile is identical no matter which host regenerates it. +supportedArchitectures: + os: + - darwin + - linux + - win32 + cpu: + - x64 + - arm64 + libc: + - glibc + - musl + +trustPolicyExclude: + # @octokit 9.x is the last CJS line (required by release-please, which is + # CJS-only); octokit only publishes provenance attestations for the newer + # pure-ESM majors, so this exact version has no trust evidence to verify. + - '@octokit/endpoint@9.0.6' diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 00000000..3dff5ca4 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,16 @@ +{ + "packages": { + ".": { + "release-type": "node", + "changelog-type": "communique", + "changelog-path": "CHANGELOG.md", + "include-component-in-tag": false, + "include-v-in-tag": true, + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": true, + "pull-request-title-pattern": "chore(release): ${version}", + "pull-request-header": "The next agent-tty release. Merging this PR creates the release tag and GitHub Release and starts the publish pipeline.", + "pull-request-footer": "Maintained by the release-please workflow. Notes are regenerated by Communique on every push to main, so manual edits to this PR are overwritten." + } + } +} diff --git a/scripts/release-finalize.mjs b/scripts/release-finalize.mjs deleted file mode 100755 index 723617f6..00000000 --- a/scripts/release-finalize.mjs +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env node -import { resolve } from 'node:path'; -import process from 'node:process'; -import { fileURLToPath } from 'node:url'; - -import { - assertCleanWorkingTree, - assertGitIdentity, - assertPackageVersionsMatch, - assertReleaseTagAvailable, - assertRepoRoot, - assertSyncedMain, - exitWithError, - parseFinalizeArgs, - parseSemver, - runGit, - runVerification, -} from './release-helpers.mjs'; - -// The env override is intentionally scoped to external verification. Git -// operations use process.env because the supported entrypoint is spawning this -// script with the desired env. -export function releaseFinalize( - argv = process.argv.slice(2), - env = process.env, -) { - const options = parseFinalizeArgs(argv); - const root = assertRepoRoot(process.cwd()); - - assertCleanWorkingTree(root); - assertSyncedMain(root); - const { packageVersion } = assertPackageVersionsMatch(root); - parseSemver(packageVersion); - assertGitIdentity(root, 'creating the release tag'); - const tagName = assertReleaseTagAvailable(root, packageVersion); - - if (options.verify) { - runVerification(root, env); - assertCleanWorkingTree(root); - } - - runGit(root, ['tag', '-a', tagName, '-m', tagName], { stdio: 'inherit' }); - runGit(root, ['push', 'origin', tagName], { stdio: 'inherit' }); - - process.stdout.write( - [ - `Release tag ${tagName} pushed.`, - 'The tag-triggered Release workflow should now publish GitHub assets and npm.', - 'Watch the workflow and verify npm/GitHub release assets using docs/RELEASE-PROCESS.md.', - '', - ].join('\n'), - ); -} - -const invokedPath = - process.argv[1] === undefined ? null : resolve(process.argv[1]); -if (invokedPath === fileURLToPath(import.meta.url)) { - try { - releaseFinalize(); - } catch (error) { - exitWithError(error); - } -} diff --git a/scripts/release-helpers.mjs b/scripts/release-helpers.mjs deleted file mode 100644 index 12259409..00000000 --- a/scripts/release-helpers.mjs +++ /dev/null @@ -1,883 +0,0 @@ -import assert from 'node:assert/strict'; -import { spawnSync } from 'node:child_process'; -import { - accessSync, - constants as fsConstants, - existsSync, - readFileSync, - realpathSync, -} from 'node:fs'; -import { delimiter, join, resolve } from 'node:path'; -import process from 'node:process'; -import { fileURLToPath } from 'node:url'; - -const projectRoot = resolve(fileURLToPath(new URL('..', import.meta.url))); - -const SEMVER_PATTERN = - /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*))*))?$/u; -const NUMERIC_IDENTIFIER_PATTERN = /^(0|[1-9]\d*)$/u; -const RELEASE_IT_BIN = join( - projectRoot, - 'node_modules', - 'release-it', - 'bin', - 'release-it.js', -); -const RELEASE_IT_CONFIG = join(projectRoot, '.release-it.json'); - -export function assertString(value, description) { - assert.equal(typeof value, 'string', `${description} must be a string`); - assert(value.length > 0, `${description} must not be empty`); - return value; -} - -export function parsePrepArgs(argv) { - assert(Array.isArray(argv), 'argv must be an array'); - - let version = null; - let changelog = null; - let verify = false; - - for (let index = 0; index < argv.length; index += 1) { - const argument = assertString(argv[index], 'CLI argument'); - - if (argument === '--verify') { - verify = true; - continue; - } - - if (argument === '--version' || argument.startsWith('--version=')) { - assert(version === null, '--version may only be provided once'); - if (argument === '--version') { - index += 1; - assert(index < argv.length, '--version requires a value'); - version = assertString(argv[index], '--version value'); - } else { - version = assertString( - argument.slice('--version='.length), - '--version value', - ); - } - continue; - } - - if (argument === '--changelog' || argument.startsWith('--changelog=')) { - assert(changelog === null, '--changelog may only be provided once'); - if (argument === '--changelog') { - index += 1; - assert(index < argv.length, '--changelog requires a value'); - changelog = assertString(argv[index], '--changelog value'); - } else { - changelog = assertString( - argument.slice('--changelog='.length), - '--changelog value', - ); - } - continue; - } - - throw new Error(`unsupported argument: ${argument}`); - } - - if (version === null) { - throw new Error('--version is required'); - } - if (changelog !== 'local' && changelog !== 'ci') { - throw new Error('--changelog local|ci is required'); - } - - return { version, changelog, verify }; -} - -export function parseFinalizeArgs(argv) { - assert(Array.isArray(argv), 'argv must be an array'); - - let verify = false; - for (const rawArgument of argv) { - const argument = assertString(rawArgument, 'CLI argument'); - if (argument === '--verify') { - verify = true; - continue; - } - - throw new Error(`unsupported argument: ${argument}`); - } - - return { verify }; -} - -export function parseSemver(version) { - assertString(version, 'version'); - if (version.includes('+')) { - throw new Error( - `version ${version} contains build metadata, which release automation does not support yet`, - ); - } - - const match = SEMVER_PATTERN.exec(version); - if (match === null) { - throw new Error(`version ${version} is not an exact semantic version`); - } - - const [, major, minor, patch, prerelease] = match; - assert(major !== undefined, 'major version match missing'); - assert(minor !== undefined, 'minor version match missing'); - assert(patch !== undefined, 'patch version match missing'); - - return { - major: Number.parseInt(major, 10), - minor: Number.parseInt(minor, 10), - patch: Number.parseInt(patch, 10), - prerelease: prerelease === undefined ? [] : prerelease.split('.'), - }; -} - -function compareIdentifiers(left, right) { - assertString(left, 'left prerelease identifier'); - assertString(right, 'right prerelease identifier'); - - const leftIsNumeric = NUMERIC_IDENTIFIER_PATTERN.test(left); - const rightIsNumeric = NUMERIC_IDENTIFIER_PATTERN.test(right); - - if (leftIsNumeric && rightIsNumeric) { - const leftValue = BigInt(left); - const rightValue = BigInt(right); - if (leftValue === rightValue) { - return 0; - } - return leftValue < rightValue ? -1 : 1; - } - - if (leftIsNumeric !== rightIsNumeric) { - return leftIsNumeric ? -1 : 1; - } - - if (left === right) { - return 0; - } - return left < right ? -1 : 1; -} - -export function compareSemver(leftVersion, rightVersion) { - const left = parseSemver(leftVersion); - const right = parseSemver(rightVersion); - - for (const key of ['major', 'minor', 'patch']) { - assert(key in left, `missing left ${key}`); - assert(key in right, `missing right ${key}`); - if (left[key] !== right[key]) { - return left[key] < right[key] ? -1 : 1; - } - } - - if (left.prerelease.length === 0 && right.prerelease.length === 0) { - return 0; - } - if (left.prerelease.length === 0) { - return 1; - } - if (right.prerelease.length === 0) { - return -1; - } - - const maxLength = Math.max(left.prerelease.length, right.prerelease.length); - for (let index = 0; index < maxLength; index += 1) { - const leftIdentifier = left.prerelease[index]; - const rightIdentifier = right.prerelease[index]; - if (leftIdentifier === undefined) { - return -1; - } - if (rightIdentifier === undefined) { - return 1; - } - - const compared = compareIdentifiers(leftIdentifier, rightIdentifier); - if (compared !== 0) { - return compared; - } - } - - return 0; -} - -export function assertTargetVersionIsGreater(currentVersion, targetVersion) { - parseSemver(currentVersion); - parseSemver(targetVersion); - - const compared = compareSemver(currentVersion, targetVersion); - if (compared === 0) { - throw new Error( - `target version ${targetVersion} matches current package version`, - ); - } - if (compared > 0) { - throw new Error( - `target version ${targetVersion} must be greater than current package version ${currentVersion}`, - ); - } -} - -function readJsonFile(path, description) { - const raw = readFileSync(path, 'utf8'); - assert(raw.length > 0, `${description} must not be empty`); - - try { - return JSON.parse(raw); - } catch (error) { - throw new Error(`${description} is not valid JSON`, { cause: error }); - } -} - -function assertPackageLike(value, description) { - assert( - typeof value === 'object' && value !== null && !Array.isArray(value), - `${description} must be an object`, - ); - return value; -} - -export function readPackageVersions(root = process.cwd()) { - const resolvedRoot = resolve(root); - const packageJson = assertPackageLike( - readJsonFile(join(resolvedRoot, 'package.json'), 'package.json'), - 'package.json', - ); - const packageVersion = assertString( - packageJson.version, - 'package.json version', - ); - - // `package-lock.json` is optional: after the aube migration (PR #91) this - // repo no longer carries one. When present, its version fields are asserted - // for coherence with `package.json`; when absent, only `package.json` is - // validated and dependency pinning is delegated to `aube-lock.yaml`. - const packageLockPath = join(resolvedRoot, 'package-lock.json'); - const hasPackageLock = existsSync(packageLockPath); - - let lockfileVersion = null; - let lockRootVersion = null; - - if (hasPackageLock) { - const packageLock = assertPackageLike( - readJsonFile(packageLockPath, 'package-lock.json'), - 'package-lock.json', - ); - lockfileVersion = assertString( - packageLock.version, - 'package-lock.json version', - ); - const packages = assertPackageLike( - packageLock.packages, - 'package-lock.json packages', - ); - const rootPackage = assertPackageLike( - packages[''], - 'package-lock.json packages[""]', - ); - lockRootVersion = assertString( - rootPackage.version, - 'package-lock.json packages[""].version', - ); - } - - return { - packageName: assertString(packageJson.name, 'package.json name'), - packageVersion, - hasPackageLock, - lockfileVersion, - lockRootVersion, - }; -} - -export function assertPackageVersionsMatch( - root = process.cwd(), - expectedVersion = null, -) { - assert( - expectedVersion === null || typeof expectedVersion === 'string', - 'expected version must be a string or null', - ); - const versions = readPackageVersions(root); - assert.equal( - versions.packageName, - 'agent-tty', - 'package.json name must be agent-tty', - ); - if (versions.hasPackageLock) { - assert.equal( - versions.lockfileVersion, - versions.packageVersion, - 'package-lock.json version must match package.json version', - ); - assert.equal( - versions.lockRootVersion, - versions.packageVersion, - 'package-lock.json packages[""].version must match package.json version', - ); - } - if (expectedVersion !== null) { - assert.equal( - versions.packageVersion, - expectedVersion, - 'package.json version must match requested release version', - ); - } - - return versions; -} - -function formatCommand(command, args) { - assertString(command, 'command'); - assert(Array.isArray(args), 'command args must be an array'); - return [command, ...args].join(' '); -} - -export function run(command, args, options = {}) { - assertString(command, 'command'); - assert(Array.isArray(args), 'command args must be an array'); - - const { - cwd = process.cwd(), - env = process.env, - expectedStatus = 0, - stdio = 'pipe', - timeoutMs = 30 * 60 * 1000, - } = options; - assertString(cwd, 'cwd'); - assert(env !== null && typeof env === 'object', 'env must be an object'); - assert( - Number.isInteger(expectedStatus), - 'expected status must be an integer', - ); - assert(stdio === 'pipe' || stdio === 'inherit', 'unsupported stdio mode'); - assert( - Number.isInteger(timeoutMs) && timeoutMs > 0, - 'timeout must be a positive integer', - ); - - const result = spawnSync(command, args, { - cwd, - env, - encoding: 'utf8', - stdio, - timeout: timeoutMs, - }); - - if (result.error !== undefined) { - if (result.error.code === 'ETIMEDOUT') { - throw new Error( - `command timed out after ${String(timeoutMs)}ms: ${formatCommand(command, args)}`, - { cause: result.error }, - ); - } - throw new Error(`failed to start ${command}: ${result.error.message}`, { - cause: result.error, - }); - } - - const stdout = typeof result.stdout === 'string' ? result.stdout : ''; - const stderr = typeof result.stderr === 'string' ? result.stderr : ''; - - if (result.status !== expectedStatus) { - const exitInfo = - result.status === null - ? `killed by signal: ${String(result.signal)}` - : `actual exit code: ${String(result.status)}`; - throw new Error( - [ - `command failed: ${formatCommand(command, args)}`, - `cwd: ${cwd}`, - `expected exit code: ${String(expectedStatus)}`, - exitInfo, - stdout.length === 0 ? '' : `stdout:\n${stdout}`, - stderr.length === 0 ? '' : `stderr:\n${stderr}`, - ] - .filter((line) => line.length > 0) - .join('\n\n'), - ); - } - - return { stdout, stderr, status: result.status ?? 0 }; -} - -function tryRun(command, args, options = {}) { - const { - cwd = process.cwd(), - env = process.env, - stdio = 'pipe', - timeoutMs = 30 * 60 * 1000, - } = options; - assert( - Number.isInteger(timeoutMs) && timeoutMs > 0, - 'timeout must be a positive integer', - ); - const result = spawnSync(command, args, { - cwd, - env, - encoding: 'utf8', - stdio, - timeout: timeoutMs, - }); - - return { - error: result.error, - status: result.status, - stdout: typeof result.stdout === 'string' ? result.stdout : '', - stderr: typeof result.stderr === 'string' ? result.stderr : '', - }; -} - -export function runGit(root, args, options = {}) { - return run('git', args, { ...options, cwd: root }); -} - -export function assertRepoRoot(root = process.cwd()) { - const resolvedRoot = resolve(root); - const topLevel = runGit(resolvedRoot, [ - 'rev-parse', - '--show-toplevel', - ]).stdout.trim(); - assertString(topLevel, 'git top-level'); - assert.equal( - realpathSync(resolve(topLevel)), - realpathSync(resolvedRoot), - 'release scripts must run at the repo root', - ); - return resolvedRoot; -} - -export function assertCleanWorkingTree(root) { - const status = runGit(root, [ - 'status', - '--porcelain=v1', - '--untracked-files=all', - ]).stdout.trim(); - if (status.length > 0) { - throw new Error( - `working tree must be clean before release automation:\n${status}`, - ); - } -} - -export function assertCurrentBranch(root, expectedBranch) { - const branch = runGit(root, ['branch', '--show-current']).stdout.trim(); - if (branch !== expectedBranch) { - throw new Error( - `release automation must run from ${expectedBranch}; current branch is ${branch || '(detached HEAD)'}`, - ); - } -} - -export function fetchOriginMain(root) { - runGit(root, [ - 'fetch', - '--no-tags', - 'origin', - 'main:refs/remotes/origin/main', - ]); -} - -export function assertHeadMatchesOriginMain(root) { - const head = runGit(root, ['rev-parse', 'HEAD']).stdout.trim(); - const originMain = runGit(root, ['rev-parse', 'origin/main']).stdout.trim(); - assertString(head, 'HEAD revision'); - assertString(originMain, 'origin/main revision'); - if (head !== originMain) { - throw new Error('local main must be up to date with origin/main'); - } -} - -export function assertSyncedMain(root) { - assertCurrentBranch(root, 'main'); - fetchOriginMain(root); - assertHeadMatchesOriginMain(root); -} - -export function assertGitIdentity(root, action) { - assertString(action, 'git identity action'); - const userNameResult = tryRun('git', ['config', '--get', 'user.name'], { - cwd: root, - }); - const userEmailResult = tryRun('git', ['config', '--get', 'user.email'], { - cwd: root, - }); - const userName = userNameResult.stdout.trim(); - const userEmail = userEmailResult.stdout.trim(); - if ( - userNameResult.status !== 0 || - userEmailResult.status !== 0 || - userName.length === 0 || - userEmail.length === 0 - ) { - throw new Error( - `git user.name and user.email must be configured before ${action}`, - ); - } -} - -export function localBranchExists(root, branchName) { - assertString(branchName, 'branch name'); - const result = tryRun( - 'git', - ['show-ref', '--verify', '--quiet', `refs/heads/${branchName}`], - { cwd: root }, - ); - if (result.error !== undefined) { - throw new Error( - `failed to inspect local branch ${branchName}: ${result.error.message}`, - { - cause: result.error, - }, - ); - } - if (result.status === 0) { - return true; - } - if (result.status === 1) { - return false; - } - throw new Error( - `failed to inspect local branch ${branchName} (exit ${String(result.status)})`, - ); -} - -export function remoteBranchExists(root, branchName) { - assertString(branchName, 'branch name'); - const result = tryRun( - 'git', - ['ls-remote', '--exit-code', '--heads', 'origin', branchName], - { cwd: root }, - ); - if (result.error !== undefined) { - throw new Error( - `failed to inspect remote branch ${branchName}: ${result.error.message}`, - { - cause: result.error, - }, - ); - } - if (result.status === 0) { - return true; - } - if (result.status === 2) { - return false; - } - throw new Error( - [ - `failed to inspect remote branch ${branchName}`, - result.stdout.length === 0 ? '' : `stdout:\n${result.stdout}`, - result.stderr.length === 0 ? '' : `stderr:\n${result.stderr}`, - ] - .filter((line) => line.length > 0) - .join('\n\n'), - ); -} - -export function localTagExists(root, tagName) { - assertString(tagName, 'tag name'); - const result = tryRun( - 'git', - ['show-ref', '--verify', '--quiet', `refs/tags/${tagName}`], - { cwd: root }, - ); - if (result.error !== undefined) { - throw new Error( - `failed to inspect local tag ${tagName}: ${result.error.message}`, - { - cause: result.error, - }, - ); - } - if (result.status === 0) { - return true; - } - if (result.status === 1) { - return false; - } - throw new Error( - `failed to inspect local tag ${tagName} (exit ${String(result.status)})`, - ); -} - -export function remoteTagExists(root, tagName) { - assertString(tagName, 'tag name'); - const result = tryRun( - 'git', - ['ls-remote', '--exit-code', '--tags', 'origin', `refs/tags/${tagName}`], - { cwd: root }, - ); - if (result.error !== undefined) { - throw new Error( - `failed to inspect remote tag ${tagName}: ${result.error.message}`, - { - cause: result.error, - }, - ); - } - if (result.status === 0) { - return true; - } - if (result.status === 2) { - return false; - } - throw new Error( - [ - `failed to inspect remote tag ${tagName}`, - result.stdout.length === 0 ? '' : `stdout:\n${result.stdout}`, - result.stderr.length === 0 ? '' : `stderr:\n${result.stderr}`, - ] - .filter((line) => line.length > 0) - .join('\n\n'), - ); -} - -export function assertReleaseBranchAvailable(root, version) { - const branchName = `release/${version}`; - if (localBranchExists(root, branchName)) { - throw new Error(`local release branch already exists: ${branchName}`); - } - if (remoteBranchExists(root, branchName)) { - throw new Error( - `remote release branch already exists: origin/${branchName}`, - ); - } - return branchName; -} - -export function assertReleaseTagAvailable(root, version) { - const tagName = `v${version}`; - if (localTagExists(root, tagName)) { - throw new Error(`local release tag already exists: ${tagName}`); - } - if (remoteTagExists(root, tagName)) { - throw new Error(`remote release tag already exists: origin/${tagName}`); - } - return tagName; -} - -export function getChangedFiles(root) { - const status = runGit(root, [ - 'status', - '--porcelain=v1', - '--untracked-files=all', - ]).stdout; - const files = new Set(); - - for (const line of status.split('\n')) { - if (line.trim().length === 0) { - continue; - } - assert(line.length >= 4, `unexpected git status line: ${line}`); - let file = line.slice(3).trim(); - if (file.includes(' -> ')) { - const [, destination] = file.split(' -> '); - file = assertString(destination, 'renamed destination'); - } - files.add(file); - } - - return [...files].sort(); -} - -export function assertAllowedChangedFiles(root, allowedFiles) { - assert(Array.isArray(allowedFiles), 'allowed files must be an array'); - const allowed = new Set( - allowedFiles.map((file) => assertString(file, 'allowed changed file')), - ); - const changedFiles = getChangedFiles(root); - const unexpectedFiles = changedFiles.filter((file) => !allowed.has(file)); - if (unexpectedFiles.length > 0) { - throw new Error( - `release automation produced unexpected file changes: ${unexpectedFiles.join(', ')}`, - ); - } - return changedFiles; -} - -export function assertExpectedFilesChanged(changedFiles, expectedFiles) { - assert(Array.isArray(changedFiles), 'changed files must be an array'); - assert(Array.isArray(expectedFiles), 'expected files must be an array'); - const changed = new Set(changedFiles); - const missingFiles = expectedFiles.filter((file) => !changed.has(file)); - if (missingFiles.length > 0) { - throw new Error( - `release automation did not change expected files: ${missingFiles.join(', ')}`, - ); - } -} - -export function stageFiles(root, files) { - assert(Array.isArray(files), 'files must be an array'); - runGit(root, ['add', '--', ...files]); -} - -export function createCommit(root, message) { - assertString(message, 'commit message'); - runGit(root, ['commit', '-m', message], { stdio: 'inherit' }); -} - -export function assertExactlyOneCommitSince(root, baseRevision) { - assertString(baseRevision, 'base revision'); - const rawCount = runGit(root, [ - 'rev-list', - '--count', - `${baseRevision}..HEAD`, - ]).stdout.trim(); - const commitCount = Number.parseInt(rawCount, 10); - assert(Number.isInteger(commitCount), 'commit count must be an integer'); - assert.equal(commitCount, 1, 'release prep must create exactly one commit'); -} - -function pathEntries(env) { - assert(env !== null && typeof env === 'object', 'env must be an object'); - const rawPath = typeof env.PATH === 'string' ? env.PATH : ''; - return rawPath.split(delimiter).filter((entry) => entry.length > 0); -} - -export function findExecutable(name, env = process.env) { - assertString(name, 'executable name'); - const extensions = - process.platform === 'win32' ? ['', '.exe', '.cmd', '.bat'] : ['']; - for (const directory of pathEntries(env)) { - for (const extension of extensions) { - const candidate = join(directory, `${name}${extension}`); - try { - accessSync(candidate, fsConstants.X_OK); - return candidate; - } catch { - // Keep searching PATH. - } - } - } - return null; -} - -export function commandIsAvailable(name, env = process.env) { - return findExecutable(name, env) !== null; -} - -function hasEnv(env, name) { - assert(env !== null && typeof env === 'object', 'env must be an object'); - assertString(name, 'env name'); - const value = env[name]; - return typeof value === 'string' && value.length > 0; -} - -export function assertLocalChangelogPrerequisites( - root, - version, - env = process.env, -) { - assertString(root, 'root'); - assertString(version, 'version'); - - const missing = []; - const hasAnthropicKey = hasEnv(env, 'ANTHROPIC_API_KEY'); - const hasOpenAiKey = hasEnv(env, 'OPENAI_API_KEY'); - if (!hasAnthropicKey && !hasOpenAiKey) { - missing.push('ANTHROPIC_API_KEY or OPENAI_API_KEY'); - } - if (!hasAnthropicKey && hasOpenAiKey && !hasEnv(env, 'COMMUNIQUE_MODEL')) { - missing.push( - 'COMMUNIQUE_MODEL when using OPENAI_API_KEY without ANTHROPIC_API_KEY', - ); - } - if (!commandIsAvailable('communique', env)) { - missing.push('communique on PATH'); - } - if (!hasEnv(env, 'GITHUB_TOKEN')) { - if (!commandIsAvailable('gh', env)) { - missing.push('GITHUB_TOKEN or an authenticated gh CLI session'); - } else { - const ghResult = tryRun( - 'gh', - ['api', 'graphql', '-f', 'query=query { viewer { login } }'], - { cwd: root, env }, - ); - if (ghResult.status !== 0) { - missing.push('GITHUB_TOKEN or an authenticated gh CLI session'); - } - } - } - - if (missing.length > 0) { - throw new Error( - [ - 'local changelog prerequisites are missing:', - ...missing.map((item) => `- ${item}`), - `Fallback: npm run release:prep -- --version ${version} --changelog ci`, - ].join('\n'), - ); - } -} - -export function runReleaseIt(root, version, env = process.env) { - assertString(root, 'root'); - assertString(version, 'version'); - parseSemver(version); - const packageJson = assertPackageLike( - readJsonFile(join(root, 'package.json'), 'package.json'), - 'package.json', - ); - if (Object.hasOwn(packageJson, 'release-it')) { - throw new Error('package.json must not contain a release-it key'); - } - if (!existsSync(RELEASE_IT_BIN)) { - throw new Error( - `release-it binary is missing at ${RELEASE_IT_BIN}; run npm install first`, - ); - } - if (!existsSync(RELEASE_IT_CONFIG)) { - throw new Error(`release-it config is missing at ${RELEASE_IT_CONFIG}`); - } - - run( - process.execPath, - [RELEASE_IT_BIN, version, '--ci', '--config', RELEASE_IT_CONFIG], - { - cwd: root, - env: { ...env, CI: 'true' }, - stdio: 'inherit', - }, - ); -} - -export function runCommunique(root, version, env = process.env) { - assertString(root, 'root'); - assertString(version, 'version'); - run( - 'communique', - ['generate', `v${version}`, '--changelog', '--repo', 'coder/agent-tty'], - { cwd: root, env, stdio: 'inherit' }, - ); -} - -function runNpm(root, args, env = process.env) { - assert(Array.isArray(args), 'npm args must be an array'); - const npmExecPath = env.npm_execpath; - if (typeof npmExecPath === 'string' && npmExecPath.length > 0) { - run(process.execPath, [npmExecPath, ...args], { - cwd: root, - env, - stdio: 'inherit', - }); - return; - } - - run('npm', args, { cwd: root, env, stdio: 'inherit' }); -} - -export function runVerification(root, env = process.env) { - if (commandIsAvailable('mise', env)) { - run('mise', ['run', 'ci'], { cwd: root, env, stdio: 'inherit' }); - return; - } - - runNpm(root, ['run', 'verify'], env); -} - -export function exitWithError(error) { - const message = error instanceof Error ? error.message : String(error); - process.stderr.write(`error: ${message}\n`); - process.exitCode = 1; -} diff --git a/scripts/release-prep.mjs b/scripts/release-prep.mjs deleted file mode 100755 index b2558825..00000000 --- a/scripts/release-prep.mjs +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env node -import { existsSync } from 'node:fs'; -import { join, resolve } from 'node:path'; -import process from 'node:process'; -import { fileURLToPath } from 'node:url'; - -import { - assertAllowedChangedFiles, - assertCleanWorkingTree, - assertExactlyOneCommitSince, - assertExpectedFilesChanged, - assertGitIdentity, - assertLocalChangelogPrerequisites, - assertPackageVersionsMatch, - assertReleaseBranchAvailable, - assertRepoRoot, - assertSyncedMain, - assertTargetVersionIsGreater, - createCommit, - exitWithError, - parsePrepArgs, - runCommunique, - runGit, - runReleaseIt, - runVerification, - stageFiles, -} from './release-helpers.mjs'; - -// `package-lock.json` is included in the version-file allowlist only when it -// actually exists. After the aube migration (PR #91) the repo no longer ships -// one, but the npm-lockfile path is still supported for downstream consumers -// that re-introduce `package-lock.json`. -function resolveVersionFilePaths(root) { - return existsSync(join(root, 'package-lock.json')) - ? Object.freeze(['package.json', 'package-lock.json']) - : Object.freeze(['package.json']); -} - -// The env override is intentionally scoped to external release tools -// (release-it, Communique, and verification). Git operations use process.env -// because the supported entrypoint is spawning this script with the desired env. -export function releasePrep(argv = process.argv.slice(2), env = process.env) { - const options = parsePrepArgs(argv); - const root = assertRepoRoot(process.cwd()); - const versionFilePaths = resolveVersionFilePaths(root); - const { packageVersion } = assertPackageVersionsMatch(root); - assertTargetVersionIsGreater(packageVersion, options.version); - - if (options.changelog === 'local') { - assertLocalChangelogPrerequisites(root, options.version, env); - } - - assertCleanWorkingTree(root); - assertSyncedMain(root); - assertGitIdentity(root, 'creating the release-prep commit'); - const releaseBranch = assertReleaseBranchAvailable(root, options.version); - const baseRevision = runGit(root, ['rev-parse', 'HEAD']).stdout.trim(); - - runGit(root, ['switch', '-c', releaseBranch], { stdio: 'inherit' }); - runReleaseIt(root, options.version, env); - assertPackageVersionsMatch(root, options.version); - - if (options.changelog === 'local') { - runCommunique(root, options.version, env); - const changedFiles = assertAllowedChangedFiles(root, [ - ...versionFilePaths, - 'CHANGELOG.md', - ]); - assertExpectedFilesChanged(changedFiles, [ - ...versionFilePaths, - 'CHANGELOG.md', - ]); - stageFiles(root, [...versionFilePaths, 'CHANGELOG.md']); - } else { - const changedFiles = assertAllowedChangedFiles(root, [ - ...versionFilePaths, - 'CHANGELOG.md', - ]); - if (changedFiles.includes('CHANGELOG.md')) { - throw new Error('CHANGELOG.md must not change when using --changelog ci'); - } - assertExpectedFilesChanged(changedFiles, versionFilePaths); - stageFiles(root, versionFilePaths); - } - - createCommit(root, `chore(release): ${options.version}`); - assertExactlyOneCommitSince(root, baseRevision); - assertCleanWorkingTree(root); - - if (options.verify) { - runVerification(root, env); - assertCleanWorkingTree(root); - } - - process.stdout.write( - [ - `Release prep commit created on ${releaseBranch}.`, - '', - 'Next commands:', - `git push -u origin ${releaseBranch}`, - `gh pr create --base main --head ${releaseBranch} --title "chore(release): ${options.version}"`, - '', - ].join('\n'), - ); -} - -const invokedPath = - process.argv[1] === undefined ? null : resolve(process.argv[1]); -if (invokedPath === fileURLToPath(import.meta.url)) { - try { - releasePrep(); - } catch (error) { - exitWithError(error); - } -} diff --git a/src/tools/release-please-runner.ts b/src/tools/release-please-runner.ts new file mode 100644 index 00000000..f3c328a8 --- /dev/null +++ b/src/tools/release-please-runner.ts @@ -0,0 +1,288 @@ +/** + * Release Please runner with Communique changelog notes. + * + * The stock `googleapis/release-please-action` only supports its built-in + * `default` (conventional-commit bullets) and `github` (auto-generated notes) + * changelog generators. This repo's changelog sections are written by + * Communique, so we run release-please as a library instead and register + * Communique as a custom changelog-notes type — `"changelog-type": + * "communique"` in `release-please-config.json` routes note building here. + * + * On every push to `main` (.github/workflows/release-please.yml): + * 1. `createReleases()` — if a release PR merged, create the `v` + * tag and the GitHub Release (notes parsed from the merged PR body). + * 2. `createPullRequests()` — open or update the single release PR carrying + * the version bump plus a Communique-written CHANGELOG section. + * + * The workflow reads this script's GITHUB_OUTPUT values to dispatch CI onto + * the release branch (pushes made with the workflow token never trigger + * `pull_request` events) and the tag-driven Release pipeline (tags created + * with the workflow token never trigger `push: tags` events). + * + * Run `npx tsx src/tools/release-please-runner.ts --dry-run` with + * GITHUB_TOKEN/GITHUB_REPOSITORY and an LLM key to preview the candidate + * release PR and releases without mutating anything on GitHub. + */ + +import { execFile } from 'node:child_process'; +import { appendFileSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import process from 'node:process'; +import { promisify } from 'node:util'; + +import { + GitHub, + Manifest, + registerChangelogNotes, + type ChangelogNotes, + type CreatedRelease, + type PullRequest, +} from 'release-please'; + +import { isDirectExecution } from '../util/isDirectExecution.js'; + +const execFileAsync = promisify(execFile); + +/** + * Mirrors the credential contract of the retired changelog workflows: + * Anthropic works standalone, OpenAI-compatible endpoints additionally need + * an explicit model selection. + */ +export function assertLlmCredentials(env: NodeJS.ProcessEnv): void { + const anthropicKey = env.ANTHROPIC_API_KEY ?? ''; + const openaiKey = env.OPENAI_API_KEY ?? ''; + const model = env.COMMUNIQUE_MODEL ?? ''; + if (anthropicKey === '' && openaiKey === '') { + throw new Error( + 'ANTHROPIC_API_KEY or OPENAI_API_KEY is required to generate changelog entries with Communique.', + ); + } + if (anthropicKey === '' && model === '') { + throw new Error( + 'Set COMMUNIQUE_MODEL when using OPENAI_API_KEY so Communique can select an OpenAI-compatible model.', + ); + } +} + +export interface CommuniqueInvocation { + readonly repo: string; + readonly outputFile: string; + readonly previousTag?: string | undefined; + readonly model?: string | undefined; +} + +/** + * `communique generate HEAD [PREV_TAG] --concise` emits the changelog-entry + * flavor (the same content `--changelog` would insert into CHANGELOG.md) + * without touching any files. The previous tag is passed explicitly so + * Communique and release-please always agree on the commit range. + */ +export function buildCommuniqueArgs( + invocation: CommuniqueInvocation, +): string[] { + const args = ['generate', 'HEAD']; + if (invocation.previousTag !== undefined && invocation.previousTag !== '') { + args.push(invocation.previousTag); + } + args.push('--concise', '--repo', invocation.repo); + args.push('--output', invocation.outputFile); + if (invocation.model !== undefined && invocation.model !== '') { + args.push('--model', invocation.model); + } + return args; +} + +/** + * Formats the section that becomes the CHANGELOG.md entry, the release PR + * body, and (after merge) the GitHub Release notes. + * + * The heading is `## [] - ` — close to this repo's historical + * `## [v] - ` style, but without the `v`: release-please + * parses the merged PR body with `/^#{2,} \[?(?\d+\.\d+\.\d+...)/`, + * which requires a digit immediately after the optional bracket. A `v` there + * would make the merged release PR unparseable and no release would be + * created. + */ +export function formatChangelogSection( + version: string, + isoDate: string, + body: string, +): string { + const trimmed = body.trim(); + const content = + trimmed === '' + ? '- Maintenance release with no user-facing changes.' + : trimmed; + // No trailing newline: release-please's Changelog updater joins the entry + // with `\n` on both sides, so a trailing newline here would leave a double + // blank line above the previous section. + return `## [${version}] - ${isoDate}\n\n${content}`; +} + +export function todayIsoDate(now = new Date()): string { + return now.toISOString().slice(0, 10); +} + +export type CommuniqueRunner = ( + args: string[], + env: NodeJS.ProcessEnv, +) => Promise; + +async function runCommuniqueBinary( + args: string[], + env: NodeJS.ProcessEnv, +): Promise { + try { + const { stderr } = await execFileAsync('communique', args, { + env, + maxBuffer: 16 * 1024 * 1024, + }); + if (stderr.trim() !== '') { + process.stderr.write(stderr); + } + } catch (error) { + const stderr = + error instanceof Object && 'stderr' in error + ? String((error as { stderr: unknown }).stderr) + : ''; + throw new Error( + `communique ${args.join(' ')} failed${stderr === '' ? '' : `:\n${stderr}`}`, + { cause: error }, + ); + } +} + +export function createCommuniqueChangelogNotes( + runCommunique: CommuniqueRunner = runCommuniqueBinary, + env: NodeJS.ProcessEnv = process.env, +): ChangelogNotes { + return { + async buildNotes(_commits, options) { + assertLlmCredentials(env); + const scratchDir = mkdtempSync(join(tmpdir(), 'communique-notes-')); + const outputFile = join(scratchDir, 'notes.md'); + try { + const args = buildCommuniqueArgs({ + repo: `${options.owner}/${options.repository}`, + outputFile, + previousTag: options.previousTag, + model: env.COMMUNIQUE_MODEL, + }); + await runCommunique(args, env); + const body = readFileSync(outputFile, 'utf8'); + return formatChangelogSection(options.version, todayIsoDate(), body); + } finally { + rmSync(scratchDir, { recursive: true, force: true }); + } + }, + }; +} + +export interface RunnerOutputs { + readonly prs_created: string; + readonly pr_branches: string; + readonly releases_created: string; + readonly release_tags: string; +} + +export function formatOutputs( + releases: readonly (CreatedRelease | undefined)[], + pullRequests: readonly (PullRequest | undefined)[], +): RunnerOutputs { + const tags = releases + .filter((release): release is CreatedRelease => release !== undefined) + .map((release) => release.tagName); + const branches = pullRequests + .filter((pr): pr is PullRequest => pr !== undefined) + .map((pr) => pr.headBranchName); + return { + prs_created: branches.length > 0 ? 'true' : 'false', + pr_branches: branches.join(' '), + releases_created: tags.length > 0 ? 'true' : 'false', + release_tags: tags.join(' '), + }; +} + +function writeGithubOutputs(outputs: RunnerOutputs): void { + const outputPath = process.env.GITHUB_OUTPUT; + const lines = Object.entries(outputs) + .map(([key, value]) => `${key}=${value}`) + .join('\n'); + if (outputPath === undefined || outputPath === '') { + process.stdout.write(`${lines}\n`); + return; + } + appendFileSync(outputPath, `${lines}\n`); +} + +function requireEnv(name: string): string { + const value = process.env[name]; + if (value === undefined || value === '') { + throw new Error(`${name} must be set`); + } + return value; +} + +async function main(): Promise { + const dryRun = process.argv.includes('--dry-run'); + const token = requireEnv('GITHUB_TOKEN'); + const repository = requireEnv('GITHUB_REPOSITORY'); + const [owner, repo] = repository.split('/'); + if (owner === undefined || repo === undefined || repo === '') { + throw new Error(`GITHUB_REPOSITORY must be owner/repo, got: ${repository}`); + } + + registerChangelogNotes('communique', () => createCommuniqueChangelogNotes()); + + const github = await GitHub.create({ owner, repo, token }); + // The override exists for --dry-run debugging against a feature branch + // (release-please reads release-please-config.json and the manifest from + // the *remote* target branch, not the local checkout). + const targetBranch = + process.env.RELEASE_PLEASE_TARGET_BRANCH ?? github.repository.defaultBranch; + const manifest = await Manifest.fromManifest(github, targetBranch); + + if (dryRun) { + const candidateReleases = await manifest.buildReleases(); + const candidatePullRequests = await manifest.buildPullRequests(); + process.stdout.write( + `${JSON.stringify( + { + releases: candidateReleases.map((release) => ({ + tag: release.tag.toString(), + sha: release.sha, + notes: release.notes, + })), + pullRequests: candidatePullRequests.map((pullRequest) => ({ + title: pullRequest.title.toString(), + headBranchName: pullRequest.headRefName, + version: pullRequest.version?.toString(), + body: pullRequest.body.toString(), + })), + }, + null, + 2, + )}\n`, + ); + return; + } + + // Releases first, then PRs — same order as release-please-action: the run + // triggered by a release PR merge tags that release before considering a + // new PR for any commits that landed since. + const releases = await manifest.createReleases(); + const pullRequests = await manifest.createPullRequests(); + const outputs = formatOutputs(releases, pullRequests); + writeGithubOutputs(outputs); + process.stdout.write( + `release-please: releases=[${outputs.release_tags}] prs=[${outputs.pr_branches}]\n`, + ); +} + +if (isDirectExecution(import.meta.url)) { + main().catch((error: unknown) => { + console.error(error); + process.exitCode = 1; + }); +} diff --git a/test/integration/release-scripts.test.ts b/test/integration/release-scripts.test.ts deleted file mode 100644 index 3b1253cc..00000000 --- a/test/integration/release-scripts.test.ts +++ /dev/null @@ -1,799 +0,0 @@ -import { spawnSync } from 'node:child_process'; -import { - chmodSync, - mkdirSync, - mkdtempSync, - readFileSync, - rmSync, - symlinkSync, - writeFileSync, -} from 'node:fs'; -import { tmpdir } from 'node:os'; -import { delimiter, join, resolve } from 'node:path'; -import { pathToFileURL } from 'node:url'; - -import { afterEach, describe, expect, it } from 'vitest'; - -const releasePrepScript = resolve('scripts/release-prep.mjs'); -const releaseFinalizeScript = resolve('scripts/release-finalize.mjs'); -const releaseHelpersUrl = pathToFileURL( - resolve('scripts/release-helpers.mjs'), -).href; - -interface CommandResult { - status: number; - stdout: string; - stderr: string; -} - -interface TempRepo { - root: string; - origin: string; - repo: string; -} - -const tempRoots: string[] = []; - -function run( - command: string, - args: string[], - options: { - cwd?: string; - env?: NodeJS.ProcessEnv; - expectedStatus?: number; - } = {}, -): CommandResult { - const result = spawnSync(command, args, { - cwd: options.cwd, - env: options.env, - encoding: 'utf8', - }); - - expect(result.error).toBeUndefined(); - const status = result.status ?? 1; - const commandResult = { - status, - stdout: result.stdout, - stderr: result.stderr, - }; - - if (options.expectedStatus !== undefined) { - expect(commandResult).toMatchObject({ status: options.expectedStatus }); - } - - return commandResult; -} - -function runGit(repo: string, args: string[]): string { - return run('git', args, { cwd: repo, expectedStatus: 0 }).stdout.trim(); -} - -function writePackageFiles( - repo: string, - version: string, - { includePackageLock = true }: { includePackageLock?: boolean } = {}, -): void { - const packageJson = { - name: 'agent-tty', - version, - type: 'module', - private: true, - }; - - writeFileSync(join(repo, 'package.json'), `${JSON.stringify(packageJson)}\n`); - - if (includePackageLock) { - const packageLock = { - name: 'agent-tty', - version, - lockfileVersion: 3, - requires: true, - packages: { - '': { - name: 'agent-tty', - version, - license: 'Apache-2.0', - }, - }, - }; - writeFileSync( - join(repo, 'package-lock.json'), - `${JSON.stringify(packageLock)}\n`, - ); - } - - writeFileSync(join(repo, 'CHANGELOG.md'), '# Changelog\n'); -} - -function createTempRepo( - version = '0.1.1-beta.4', - { includePackageLock = true }: { includePackageLock?: boolean } = {}, -): TempRepo { - const root = mkdtempSync(join(tmpdir(), 'agent-tty-release-scripts-')); - tempRoots.push(root); - const origin = join(root, 'origin.git'); - const repo = join(root, 'repo'); - - run('git', ['init', '-q', '--bare', origin], { expectedStatus: 0 }); - run('git', ['init', '-q', '-b', 'main', repo], { expectedStatus: 0 }); - runGit(repo, ['remote', 'add', 'origin', origin]); - runGit(repo, ['config', 'user.name', 'Agent TTY Test']); - runGit(repo, ['config', 'user.email', 'agent-tty-test@example.invalid']); - writePackageFiles(repo, version, { includePackageLock }); - const initialFiles = includePackageLock - ? ['package.json', 'package-lock.json', 'CHANGELOG.md'] - : ['package.json', 'CHANGELOG.md']; - runGit(repo, ['add', ...initialFiles]); - runGit(repo, ['commit', '-q', '-m', 'init']); - runGit(repo, ['push', '-q', '-u', 'origin', 'main']); - - return { root, origin, repo }; -} - -function runReleasePrep( - repo: string, - args: string[], - env = process.env, -): CommandResult { - return run(process.execPath, [releasePrepScript, ...args], { - cwd: repo, - env, - }); -} - -function runReleaseFinalize( - repo: string, - args: string[] = [], - env = process.env, -): CommandResult { - return run(process.execPath, [releaseFinalizeScript, ...args], { - cwd: repo, - env, - }); -} - -function readVersions(repo: string): [string, string, string] { - const packageJson = JSON.parse( - readFileSync(join(repo, 'package.json'), 'utf8'), - ) as { version: string }; - const packageLock = JSON.parse( - readFileSync(join(repo, 'package-lock.json'), 'utf8'), - ) as { version: string; packages: { '': { version: string } } }; - - return [ - packageJson.version, - packageLock.version, - packageLock.packages[''].version, - ]; -} - -function withoutLocalChangelogCredentials(): NodeJS.ProcessEnv { - const env = { ...process.env }; - delete env.ANTHROPIC_API_KEY; - delete env.OPENAI_API_KEY; - delete env.GITHUB_TOKEN; - return env; -} - -function withFakeMise( - root: string, - exitCode = 0, -): { - env: NodeJS.ProcessEnv; - marker: string; -} { - const bin = join(root, 'bin'); - const marker = join(root, 'mise-called.txt'); - mkdirSync(bin); - const misePath = join(bin, 'mise'); - writeFileSync( - misePath, - [ - '#!/usr/bin/env sh', - `printf '%s\n' "$*" >> '${marker}'`, - `exit ${String(exitCode)}`, - '', - ].join('\n'), - ); - chmodSync(misePath, 0o755); - - return { - env: { - ...process.env, - PATH: `${bin}${delimiter}${process.env.PATH ?? ''}`, - }, - marker, - }; -} - -function withoutGitIdentity(root: string): NodeJS.ProcessEnv { - const home = join(root, 'empty-home'); - const xdgConfigHome = join(root, 'empty-xdg-config'); - mkdirSync(home); - mkdirSync(xdgConfigHome); - - return { - ...process.env, - GIT_CONFIG_NOSYSTEM: '1', - GIT_CONFIG_GLOBAL: join(root, 'missing-gitconfig'), - HOME: home, - XDG_CONFIG_HOME: xdgConfigHome, - }; -} - -describe('release scripts', () => { - afterEach(() => { - while (tempRoots.length > 0) { - const root = tempRoots.pop(); - if (root !== undefined) { - rmSync(root, { recursive: true, force: true }); - } - } - }); - - it('prepares a ci-changelog release branch with one version commit', () => { - const { repo } = createTempRepo(); - - const result = runReleasePrep(repo, [ - '--version', - '0.1.1-beta.5', - '--changelog', - 'ci', - ]); - - expect(result.status).toBe(0); - expect(result.stdout).toContain( - 'Release prep commit created on release/0.1.1-beta.5.', - ); - expect(result.stdout).toContain('git push -u origin release/0.1.1-beta.5'); - expect(runGit(repo, ['branch', '--show-current'])).toBe( - 'release/0.1.1-beta.5', - ); - expect(runGit(repo, ['status', '--short'])).toBe(''); - expect(runGit(repo, ['rev-list', '--count', 'origin/main..HEAD'])).toBe( - '1', - ); - expect(runGit(repo, ['show', '-s', '--format=%s', 'HEAD'])).toBe( - 'chore(release): 0.1.1-beta.5', - ); - expect( - runGit(repo, ['diff', '--name-only', 'HEAD^..HEAD']).split('\n'), - ).toEqual(['package-lock.json', 'package.json']); - expect(readVersions(repo)).toEqual([ - '0.1.1-beta.5', - '0.1.1-beta.5', - '0.1.1-beta.5', - ]); - }); - - it('prepares a release on an aube-only checkout (no package-lock.json)', () => { - const { repo } = createTempRepo(undefined, { includePackageLock: false }); - - const result = runReleasePrep(repo, [ - '--version', - '0.1.1-beta.5', - '--changelog', - 'ci', - ]); - - expect(result.status).toBe(0); - expect(result.stdout).toContain( - 'Release prep commit created on release/0.1.1-beta.5.', - ); - expect(runGit(repo, ['branch', '--show-current'])).toBe( - 'release/0.1.1-beta.5', - ); - expect(runGit(repo, ['status', '--short'])).toBe(''); - expect(runGit(repo, ['rev-list', '--count', 'origin/main..HEAD'])).toBe( - '1', - ); - expect(runGit(repo, ['show', '-s', '--format=%s', 'HEAD'])).toBe( - 'chore(release): 0.1.1-beta.5', - ); - expect( - runGit(repo, ['diff', '--name-only', 'HEAD^..HEAD']).split('\n'), - ).toEqual(['package.json']); - const packageJson = JSON.parse( - readFileSync(join(repo, 'package.json'), 'utf8'), - ) as { version: string }; - expect(packageJson.version).toBe('0.1.1-beta.5'); - }); - - it('prepares a release from a repo root reached through a symlink', () => { - const { root, repo } = createTempRepo(); - const linkedRepo = join(root, 'repo-link'); - symlinkSync(repo, linkedRepo, 'dir'); - - const result = runReleasePrep(linkedRepo, [ - '--version', - '0.1.1-beta.5', - '--changelog', - 'ci', - ]); - - expect(result.status).toBe(0); - expect(runGit(repo, ['branch', '--show-current'])).toBe( - 'release/0.1.1-beta.5', - ); - expect(runGit(repo, ['status', '--short'])).toBe(''); - }); - - it('fails local changelog prep before creating a branch when credentials are missing', () => { - const { repo } = createTempRepo(); - - const result = runReleasePrep( - repo, - ['--version', '0.1.1-beta.5', '--changelog', 'local'], - withoutLocalChangelogCredentials(), - ); - - expect(result.status).toBe(1); - expect(result.stderr).toContain( - 'local changelog prerequisites are missing', - ); - expect(result.stderr).toContain( - 'Fallback: npm run release:prep -- --version 0.1.1-beta.5 --changelog ci', - ); - expect(runGit(repo, ['branch', '--show-current'])).toBe('main'); - expect(runGit(repo, ['status', '--short'])).toBe(''); - }); - - it('rejects missing and invalid release prep arguments before side effects', () => { - const missingVersionRepo = createTempRepo(); - const missingVersion = runReleasePrep(missingVersionRepo.repo, [ - '--changelog', - 'ci', - ]); - expect(missingVersion.status).toBe(1); - expect(missingVersion.stderr).toContain( - '--version is required', - ); - expect(runGit(missingVersionRepo.repo, ['branch', '--show-current'])).toBe( - 'main', - ); - - const missingChangelogRepo = createTempRepo(); - const missingChangelog = runReleasePrep(missingChangelogRepo.repo, [ - '--version', - '0.1.1-beta.5', - ]); - expect(missingChangelog.status).toBe(1); - expect(missingChangelog.stderr).toContain( - '--changelog local|ci is required', - ); - expect( - runGit(missingChangelogRepo.repo, ['branch', '--show-current']), - ).toBe('main'); - - const invalidVersionRepo = createTempRepo(); - const invalidVersion = runReleasePrep(invalidVersionRepo.repo, [ - '--version', - 'not-semver', - '--changelog', - 'ci', - ]); - expect(invalidVersion.status).toBe(1); - expect(invalidVersion.stderr).toContain( - 'version not-semver is not an exact semantic version', - ); - expect(runGit(invalidVersionRepo.repo, ['branch', '--show-current'])).toBe( - 'main', - ); - }); - - it('refuses to prepare a release from a dirty tree', () => { - const { repo } = createTempRepo(); - writeFileSync(join(repo, 'CHANGELOG.md'), '# Changelog\n\nDirty work\n'); - - const result = runReleasePrep(repo, [ - '--version', - '0.1.1-beta.5', - '--changelog', - 'ci', - ]); - - expect(result.status).toBe(1); - expect(result.stderr).toContain('working tree must be clean'); - expect(runGit(repo, ['branch', '--show-current'])).toBe('main'); - }); - - it('refuses to prepare a release from a non-main branch', () => { - const { repo } = createTempRepo(); - runGit(repo, ['switch', '-c', 'feature/not-main']); - - const result = runReleasePrep(repo, [ - '--version', - '0.1.1-beta.5', - '--changelog', - 'ci', - ]); - - expect(result.status).toBe(1); - expect(result.stderr).toContain('release automation must run from main'); - expect(runGit(repo, ['branch', '--show-current'])).toBe('feature/not-main'); - }); - - it('refuses to prepare a release when local main is stale', () => { - const { repo } = createTempRepo(); - writeFileSync(join(repo, 'remote-only.txt'), 'remote change\n'); - runGit(repo, ['add', 'remote-only.txt']); - runGit(repo, ['commit', '-q', '-m', 'advance remote']); - runGit(repo, ['push', '-q', 'origin', 'main']); - runGit(repo, ['reset', '--hard', 'HEAD~1']); - - const result = runReleasePrep(repo, [ - '--version', - '0.1.1-beta.5', - '--changelog', - 'ci', - ]); - - expect(result.status).toBe(1); - expect(result.stderr).toContain( - 'local main must be up to date with origin/main', - ); - expect(runGit(repo, ['branch', '--show-current'])).toBe('main'); - }); - - it('refuses release prep when git identity is missing', () => { - const { root, repo } = createTempRepo(); - runGit(repo, ['config', '--unset', 'user.name']); - runGit(repo, ['config', '--unset', 'user.email']); - - const result = runReleasePrep( - repo, - ['--version', '0.1.1-beta.5', '--changelog', 'ci'], - withoutGitIdentity(root), - ); - - expect(result.status).toBe(1); - expect(result.stderr).toContain( - 'git user.name and user.email must be configured before creating the release-prep commit', - ); - expect(runGit(repo, ['branch', '--show-current'])).toBe('main'); - }); - - it('refuses to prepare a release when the local release branch already exists', () => { - const { repo } = createTempRepo(); - runGit(repo, ['branch', 'release/0.1.1-beta.5']); - - const result = runReleasePrep(repo, [ - '--version', - '0.1.1-beta.5', - '--changelog', - 'ci', - ]); - - expect(result.status).toBe(1); - expect(result.stderr).toContain( - 'local release branch already exists: release/0.1.1-beta.5', - ); - expect(runGit(repo, ['branch', '--show-current'])).toBe('main'); - }); - - it('refuses to prepare a release when the remote release branch already exists', () => { - const { repo } = createTempRepo(); - runGit(repo, ['branch', 'release/0.1.1-beta.5']); - runGit(repo, ['push', '-q', 'origin', 'release/0.1.1-beta.5']); - runGit(repo, ['branch', '-D', 'release/0.1.1-beta.5']); - - const result = runReleasePrep(repo, [ - '--version', - '0.1.1-beta.5', - '--changelog', - 'ci', - ]); - - expect(result.status).toBe(1); - expect(result.stderr).toContain( - 'remote release branch already exists: origin/release/0.1.1-beta.5', - ); - expect(runGit(repo, ['branch', '--show-current'])).toBe('main'); - }); - - it('refuses same or lower release prep target versions', () => { - const sameVersionRepo = createTempRepo(); - const sameResult = runReleasePrep(sameVersionRepo.repo, [ - '--version', - '0.1.1-beta.4', - '--changelog', - 'ci', - ]); - - expect(sameResult.status).toBe(1); - expect(sameResult.stderr).toContain( - 'target version 0.1.1-beta.4 matches current package version', - ); - expect(runGit(sameVersionRepo.repo, ['branch', '--show-current'])).toBe( - 'main', - ); - - const lowerVersionRepo = createTempRepo(); - const lowerResult = runReleasePrep(lowerVersionRepo.repo, [ - '--version', - '0.1.1-beta.3', - '--changelog', - 'ci', - ]); - - expect(lowerResult.status).toBe(1); - expect(lowerResult.stderr).toContain( - 'target version 0.1.1-beta.3 must be greater than current package version 0.1.1-beta.4', - ); - expect(runGit(lowerVersionRepo.repo, ['branch', '--show-current'])).toBe( - 'main', - ); - }); - - it('handles semver prerelease edge precedence before release prep side effects', () => { - const alphaRepo = createTempRepo('1.0.0-alpha.1'); - const alphaResult = runReleasePrep(alphaRepo.repo, [ - '--version', - '1.0.0-alpha', - '--changelog', - 'ci', - ]); - - expect(alphaResult.status).toBe(1); - expect(alphaResult.stderr).toContain( - 'target version 1.0.0-alpha must be greater than current package version 1.0.0-alpha.1', - ); - expect(runGit(alphaRepo.repo, ['branch', '--show-current'])).toBe('main'); - - const numericRepo = createTempRepo('1.0.0-alpha.10'); - const numericResult = runReleasePrep(numericRepo.repo, [ - '--version', - '1.0.0-alpha.2', - '--changelog', - 'ci', - ]); - - expect(numericResult.status).toBe(1); - expect(numericResult.stderr).toContain( - 'target version 1.0.0-alpha.2 must be greater than current package version 1.0.0-alpha.10', - ); - expect(runGit(numericRepo.repo, ['branch', '--show-current'])).toBe('main'); - - const stableRepo = createTempRepo('1.0.0'); - const stableResult = runReleasePrep(stableRepo.repo, [ - '--version', - '1.0.0-rc.1', - '--changelog', - 'ci', - ]); - - expect(stableResult.status).toBe(1); - expect(stableResult.stderr).toContain( - 'target version 1.0.0-rc.1 must be greater than current package version 1.0.0', - ); - expect(runGit(stableRepo.repo, ['branch', '--show-current'])).toBe('main'); - - const buildMetadataRepo = createTempRepo(); - const buildMetadataResult = runReleasePrep(buildMetadataRepo.repo, [ - '--version', - '0.1.1-beta.5+build.1', - '--changelog', - 'ci', - ]); - - expect(buildMetadataResult.status).toBe(1); - expect(buildMetadataResult.stderr).toContain('contains build metadata'); - expect(runGit(buildMetadataRepo.repo, ['branch', '--show-current'])).toBe( - 'main', - ); - }); - - it('keeps the ci changelog-mode guard reachable', () => { - const { repo } = createTempRepo(); - writePackageFiles(repo, '0.1.1-beta.5'); - writeFileSync(join(repo, 'CHANGELOG.md'), '# Changelog\n\nUnexpected\n'); - - const result = run(process.execPath, [ - '--input-type=module', - '-e', - `import { assertAllowedChangedFiles } from ${JSON.stringify(releaseHelpersUrl)}; -const changedFiles = assertAllowedChangedFiles(process.argv[1], ['package.json', 'package-lock.json', 'CHANGELOG.md']); -if (changedFiles.includes('CHANGELOG.md')) { - throw new Error('CHANGELOG.md must not change when using --changelog ci'); -}`, - repo, - ]); - - expect(result.status).toBe(1); - expect(result.stderr).toContain( - 'CHANGELOG.md must not change when using --changelog ci', - ); - }); - - it('refuses release-it package.json config injection', () => { - const { repo } = createTempRepo(); - const packageJson = JSON.parse( - readFileSync(join(repo, 'package.json'), 'utf8'), - ) as Record; - packageJson['release-it'] = { hooks: { 'before:init': 'echo unsafe' } }; - writeFileSync( - join(repo, 'package.json'), - `${JSON.stringify(packageJson)}\n`, - ); - runGit(repo, ['add', 'package.json']); - runGit(repo, ['commit', '-q', '-m', 'add release-it config']); - runGit(repo, ['push', '-q', 'origin', 'main']); - - const result = runReleasePrep(repo, [ - '--version', - '0.1.1-beta.5', - '--changelog', - 'ci', - ]); - - expect(result.status).toBe(1); - expect(result.stderr).toContain( - 'package.json must not contain a release-it key', - ); - expect(runGit(repo, ['branch', '--show-current'])).toBe( - 'release/0.1.1-beta.5', - ); - }); - - it('runs verification during release prep when requested', () => { - const { root, repo } = createTempRepo(); - const { env, marker } = withFakeMise(root); - - const result = runReleasePrep( - repo, - ['--version', '0.1.1-beta.5', '--changelog', 'ci', '--verify'], - env, - ); - - expect(result.status).toBe(0); - expect(readFileSync(marker, 'utf8')).toBe('run ci\n'); - expect(runGit(repo, ['status', '--short'])).toBe(''); - }); - - it('finalizes by creating and pushing the exact release tag', () => { - const { repo } = createTempRepo('0.1.1-beta.5'); - - const result = runReleaseFinalize(repo); - - expect(result.status).toBe(0); - expect(result.stdout).toContain('Release tag v0.1.1-beta.5 pushed.'); - expect(runGit(repo, ['tag', '--list'])).toBe('v0.1.1-beta.5'); - expect(runGit(repo, ['cat-file', '-t', 'v0.1.1-beta.5'])).toBe('tag'); - expect( - runGit(repo, [ - 'ls-remote', - '--tags', - 'origin', - 'refs/tags/v0.1.1-beta.5', - ]), - ).toContain('refs/tags/v0.1.1-beta.5'); - }); - - it('finalizes on an aube-only checkout (no package-lock.json)', () => { - const { repo } = createTempRepo('0.1.1-beta.5', { - includePackageLock: false, - }); - - const result = runReleaseFinalize(repo); - - expect(result.status).toBe(0); - expect(result.stdout).toContain('Release tag v0.1.1-beta.5 pushed.'); - expect(runGit(repo, ['tag', '--list'])).toBe('v0.1.1-beta.5'); - }); - - it('refuses to finalize from a dirty tree', () => { - const { repo } = createTempRepo('0.1.1-beta.5'); - writeFileSync(join(repo, 'CHANGELOG.md'), '# Changelog\n\nDirty work\n'); - - const result = runReleaseFinalize(repo); - - expect(result.status).toBe(1); - expect(result.stderr).toContain('working tree must be clean'); - expect(runGit(repo, ['tag', '--list'])).toBe(''); - }); - - it('refuses to finalize from a non-main branch', () => { - const { repo } = createTempRepo('0.1.1-beta.5'); - runGit(repo, ['switch', '-c', 'feature/not-main']); - - const result = runReleaseFinalize(repo); - - expect(result.status).toBe(1); - expect(result.stderr).toContain('release automation must run from main'); - expect(runGit(repo, ['tag', '--list'])).toBe(''); - }); - - it('refuses to finalize package versions with build metadata before tagging', () => { - const { repo } = createTempRepo('0.1.1-beta.5+build.1'); - - const result = runReleaseFinalize(repo); - - expect(result.status).toBe(1); - expect(result.stderr).toContain('contains build metadata'); - expect(runGit(repo, ['tag', '--list'])).toBe(''); - expect(runGit(repo, ['ls-remote', '--tags', 'origin'])).toBe(''); - }); - - it('refuses to finalize when git identity is missing', () => { - const { root, repo } = createTempRepo('0.1.1-beta.5'); - runGit(repo, ['config', '--unset', 'user.name']); - runGit(repo, ['config', '--unset', 'user.email']); - - const result = runReleaseFinalize(repo, [], withoutGitIdentity(root)); - - expect(result.status).toBe(1); - expect(result.stderr).toContain( - 'git user.name and user.email must be configured before creating the release tag', - ); - expect(runGit(repo, ['tag', '--list'])).toBe(''); - }); - - it('refuses to finalize when local main is stale', () => { - const { repo } = createTempRepo('0.1.1-beta.5'); - writeFileSync(join(repo, 'remote-only.txt'), 'remote change\n'); - runGit(repo, ['add', 'remote-only.txt']); - runGit(repo, ['commit', '-q', '-m', 'advance remote']); - runGit(repo, ['push', '-q', 'origin', 'main']); - runGit(repo, ['reset', '--hard', 'HEAD~1']); - - const result = runReleaseFinalize(repo); - - expect(result.status).toBe(1); - expect(result.stderr).toContain( - 'local main must be up to date with origin/main', - ); - expect(runGit(repo, ['tag', '--list'])).toBe(''); - }); - - it('refuses to finalize when the local release tag already exists', () => { - const { repo } = createTempRepo('0.1.1-beta.5'); - runGit(repo, ['tag', '-a', 'v0.1.1-beta.5', '-m', 'v0.1.1-beta.5']); - - const result = runReleaseFinalize(repo); - - expect(result.status).toBe(1); - expect(result.stderr).toContain( - 'local release tag already exists: v0.1.1-beta.5', - ); - expect(runGit(repo, ['ls-remote', '--tags', 'origin'])).toBe(''); - }); - - it('runs verification during release finalization when requested', () => { - const { root, repo } = createTempRepo('0.1.1-beta.5'); - const { env, marker } = withFakeMise(root); - - const result = runReleaseFinalize(repo, ['--verify'], env); - - expect(result.status).toBe(0); - expect(readFileSync(marker, 'utf8')).toBe('run ci\n'); - expect(runGit(repo, ['tag', '--list'])).toBe('v0.1.1-beta.5'); - }); - - it('does not create a release tag when finalization verification fails', () => { - const { root, repo } = createTempRepo('0.1.1-beta.5'); - const { env, marker } = withFakeMise(root, 1); - - const result = runReleaseFinalize(repo, ['--verify'], env); - - expect(result.status).toBe(1); - expect(readFileSync(marker, 'utf8')).toBe('run ci\n'); - expect(result.stderr).toContain('command failed: mise run ci'); - expect(runGit(repo, ['tag', '--list'])).toBe(''); - expect(runGit(repo, ['ls-remote', '--tags', 'origin'])).toBe(''); - }); - - it('refuses to finalize when the remote release tag already exists', () => { - const { repo } = createTempRepo('0.1.1-beta.5'); - runGit(repo, ['tag', '-a', 'v0.1.1-beta.5', '-m', 'v0.1.1-beta.5']); - runGit(repo, ['push', '-q', 'origin', 'v0.1.1-beta.5']); - runGit(repo, ['tag', '-d', 'v0.1.1-beta.5']); - - const result = runReleaseFinalize(repo); - - expect(result.status).toBe(1); - expect(result.stderr).toContain( - 'remote release tag already exists: origin/v0.1.1-beta.5', - ); - expect(runGit(repo, ['tag', '--list'])).toBe(''); - }); -}); diff --git a/test/unit/tools/release-please-runner.test.ts b/test/unit/tools/release-please-runner.test.ts new file mode 100644 index 00000000..cc0e0f13 --- /dev/null +++ b/test/unit/tools/release-please-runner.test.ts @@ -0,0 +1,284 @@ +import { describe, expect, it } from 'vitest'; + +// Deep imports into release-please internals: these tests pin the two +// compatibility contracts the runner depends on but release-please does not +// document — (a) the merged release PR body must parse back into a version + +// notes (otherwise no GitHub Release is created on merge), and (b) the +// Changelog updater must insert the new section above this repo's historical +// `## [v] - ` headings. +import { Changelog } from 'release-please/build/src/updaters/changelog.js'; +import { PullRequestBody } from 'release-please/build/src/util/pull-request-body.js'; +import { + PullRequestTitle, + generateMatchPattern, +} from 'release-please/build/src/util/pull-request-title.js'; +import { Version } from 'release-please/build/src/version.js'; + +import { + assertLlmCredentials, + buildCommuniqueArgs, + createCommuniqueChangelogNotes, + formatChangelogSection, + formatOutputs, + todayIsoDate, +} from '../../../src/tools/release-please-runner.js'; +import { writeFileSync } from 'node:fs'; + +const SAMPLE_BODY = [ + '### Added', + '', + '- `agent-tty ls` is now a short alias for `agent-tty list` ([#135](https://github.com/coder/agent-tty/pull/135)).', + '', + '### Changed', + '', + '- `record export --format webm` now defaults to `--timing recorded` ([#139](https://github.com/coder/agent-tty/pull/139)).', +].join('\n'); + +describe('assertLlmCredentials', () => { + it('accepts an Anthropic key alone', () => { + expect(() => + assertLlmCredentials({ ANTHROPIC_API_KEY: 'sk-ant' }), + ).not.toThrow(); + }); + + it('requires a model with an OpenAI-only key', () => { + expect(() => assertLlmCredentials({ OPENAI_API_KEY: 'sk-oai' })).toThrow( + 'COMMUNIQUE_MODEL', + ); + expect(() => + assertLlmCredentials({ OPENAI_API_KEY: 'sk-oai', COMMUNIQUE_MODEL: 'm' }), + ).not.toThrow(); + }); + + it('rejects when no key is present', () => { + expect(() => assertLlmCredentials({})).toThrow( + 'ANTHROPIC_API_KEY or OPENAI_API_KEY', + ); + }); +}); + +describe('buildCommuniqueArgs', () => { + it('passes the previous tag explicitly so ranges match release-please', () => { + expect( + buildCommuniqueArgs({ + repo: 'coder/agent-tty', + outputFile: '/tmp/notes.md', + previousTag: 'v0.4.1', + model: 'claude-opus-4-7', + }), + ).toEqual([ + 'generate', + 'HEAD', + 'v0.4.1', + '--concise', + '--repo', + 'coder/agent-tty', + '--output', + '/tmp/notes.md', + '--model', + 'claude-opus-4-7', + ]); + }); + + it('lets communique auto-detect the previous tag when unknown', () => { + expect( + buildCommuniqueArgs({ repo: 'coder/agent-tty', outputFile: '/n.md' }), + ).toEqual([ + 'generate', + 'HEAD', + '--concise', + '--repo', + 'coder/agent-tty', + '--output', + '/n.md', + ]); + }); +}); + +describe('formatChangelogSection', () => { + it('uses the bracketed no-v heading with the house date style', () => { + const section = formatChangelogSection('0.4.2', '2026-06-12', SAMPLE_BODY); + expect(section.startsWith('## [0.4.2] - 2026-06-12\n\n### Added')).toBe( + true, + ); + expect(section.endsWith('\n')).toBe(false); + }); + + it('falls back to a maintenance bullet for empty notes', () => { + // An empty section would trip release-please's changelogEmpty() check and + // silently skip the release PR even when releasable commits exist. + expect(formatChangelogSection('0.4.2', '2026-06-12', ' \n')).toBe( + '## [0.4.2] - 2026-06-12\n\n- Maintenance release with no user-facing changes.', + ); + }); +}); + +describe('todayIsoDate', () => { + it('formats as YYYY-MM-DD in UTC', () => { + expect(todayIsoDate(new Date('2026-06-12T23:59:59Z'))).toBe('2026-06-12'); + }); +}); + +describe('release PR body round-trip (release-please compatibility)', () => { + it('parses the version and notes back out of the generated body', () => { + const notes = formatChangelogSection('0.4.2', '2026-06-12', SAMPLE_BODY); + const body = new PullRequestBody([ + { version: Version.parse('0.4.2'), notes }, + ]); + + const parsed = PullRequestBody.parse(body.toString()); + + expect(parsed).toBeDefined(); + expect(parsed?.releaseData).toHaveLength(1); + expect(parsed?.releaseData[0]?.version?.toString()).toBe('0.4.2'); + expect(parsed?.releaseData[0]?.notes).toContain('### Added'); + }); + + it('documents why the heading must not carry a v prefix', () => { + // `extractSingleRelease` matches /^#{2,} \[?(\d+\.\d+\.\d+...)/ — a digit + // must follow the optional bracket. With `## [v0.4.2]` the body yields no + // release data, and buildRelease() would create a release with no notes. + const body = new PullRequestBody([ + { + version: Version.parse('0.4.2'), + notes: '## [v0.4.2] - 2026-06-12\n\n- entry', + }, + ]); + const parsed = PullRequestBody.parse(body.toString()); + expect(parsed?.releaseData).toHaveLength(0); + }); + + it('parses the release version from the configured PR title pattern', () => { + const pattern = 'chore(release): ${version}'; + const title = PullRequestTitle.ofVersion( + Version.parse('0.4.2'), + pattern, + ).toString(); + expect(title).toBe('chore(release): 0.4.2'); + + expect(generateMatchPattern(pattern).test(title)).toBe(true); + const parsed = PullRequestTitle.parse(title, pattern); + expect(parsed?.getVersion()?.toString()).toBe('0.4.2'); + }); +}); + +describe('CHANGELOG.md insertion (release-please compatibility)', () => { + const existing = [ + '# Changelog', + '', + '## [v0.4.1] - 2026-06-12', + '', + '### Added', + '', + '- Older entry ([#135](https://github.com/coder/agent-tty/pull/135)).', + '', + '## [v0.4.0] - 2026-06-08', + '', + '### Added', + '', + '- Oldest entry.', + '', + ].join('\n'); + + it('inserts the new section above the previous v-prefixed heading', () => { + const updater = new Changelog({ + version: Version.parse('0.4.2'), + changelogEntry: formatChangelogSection( + '0.4.2', + '2026-06-13', + SAMPLE_BODY, + ), + }); + + const updated = updater.updateContent(existing); + + const newIndex = updated.indexOf('## [0.4.2] - 2026-06-13'); + const previousIndex = updated.indexOf('## [v0.4.1] - 2026-06-12'); + expect(newIndex).toBeGreaterThan(updated.indexOf('# Changelog')); + expect(newIndex).toBeGreaterThan(-1); + expect(previousIndex).toBeGreaterThan(newIndex); + // Exactly one blank line between the new section and the previous one. + expect(updated).toContain( + '--timing recorded` ([#139](https://github.com/coder/agent-tty/pull/139)).\n\n## [v0.4.1]', + ); + }); +}); + +describe('createCommuniqueChangelogNotes', () => { + it('invokes communique with the release range and wraps its output', async () => { + const invocations: string[][] = []; + const notes = createCommuniqueChangelogNotes( + (args) => { + invocations.push(args); + const outputFlag = args.indexOf('--output'); + writeFileSync(args[outputFlag + 1] as string, `${SAMPLE_BODY}\n`); + return Promise.resolve(); + }, + { ANTHROPIC_API_KEY: 'sk-ant' }, + ); + + const section = await notes.buildNotes([], { + owner: 'coder', + repository: 'agent-tty', + version: '0.4.2', + previousTag: 'v0.4.1', + currentTag: 'v0.4.2', + targetBranch: 'main', + }); + + expect(invocations).toHaveLength(1); + expect(invocations[0]?.slice(0, 3)).toEqual(['generate', 'HEAD', 'v0.4.1']); + expect(invocations[0]).toContain('--concise'); + expect( + section.startsWith(`## [0.4.2] - ${todayIsoDate()}\n\n### Added`), + ).toBe(true); + }); + + it('fails fast without LLM credentials', async () => { + const notes = createCommuniqueChangelogNotes(() => Promise.resolve(), {}); + await expect( + notes.buildNotes([], { + owner: 'coder', + repository: 'agent-tty', + version: '0.4.2', + currentTag: 'v0.4.2', + targetBranch: 'main', + }), + ).rejects.toThrow('ANTHROPIC_API_KEY or OPENAI_API_KEY'); + }); +}); + +describe('formatOutputs', () => { + it('maps releases and pull requests into workflow outputs', () => { + const outputs = formatOutputs( + [ + undefined, + { + tagName: 'v0.4.2', + headBranchName: 'x', + } as unknown as Parameters[0][number], + ], + [ + { + headBranchName: 'release-please--branches--main', + } as unknown as Parameters[1][number], + undefined, + ], + ); + expect(outputs).toEqual({ + prs_created: 'true', + pr_branches: 'release-please--branches--main', + releases_created: 'true', + release_tags: 'v0.4.2', + }); + }); + + it('reports false when nothing was created', () => { + expect(formatOutputs([], [undefined])).toEqual({ + prs_created: 'false', + pr_branches: '', + releases_created: 'false', + release_tags: '', + }); + }); +}); From 95b484b74ef4dea352fa680cef53bf372a774c90 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Fri, 12 Jun 2026 12:09:26 +0200 Subject: [PATCH 2/6] docs: reference the release PR by title, not hardcoded branch name The release branch carries a component suffix (release-please--branches--main--components--agent-tty) because getBranchComponent() ignores include-component-in-tag; resolve the branch from the open PR instead of hardcoding it. Change-Id: Ibf4b9480f96860317aa0bec4838f6d9d8715c4e0 Co-Authored-By: Claude Fable 5 Signed-off-by: Thomas Kosiewski --- docs/RELEASE-PROCESS.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/RELEASE-PROCESS.md b/docs/RELEASE-PROCESS.md index 39f0c905..0f287010 100644 --- a/docs/RELEASE-PROCESS.md +++ b/docs/RELEASE-PROCESS.md @@ -25,7 +25,7 @@ Three workflows cooperate: 1. **Release Please** ([`.github/workflows/release-please.yml`](../.github/workflows/release-please.yml)) runs on every push to `main`. It executes [`src/tools/release-please-runner.ts`](../src/tools/release-please-runner.ts), which runs release-please as a library with Communique registered as the changelog generator (`"changelog-type": "communique"` in [`release-please-config.json`](../release-please-config.json)). Each run: - tags and creates the GitHub Release for any release PR that merged since the last run, then dispatches the **Release** workflow for that tag; - - opens or updates the single release PR on branch `release-please--branches--main`, carrying the `package.json` version bump plus a new `CHANGELOG.md` section written by Communique (`communique generate HEAD --concise`); + - opens or updates the single release PR (title `chore(release): `, head branch `release-please--branches--main--components--agent-tty`), carrying the `package.json` version bump plus a new `CHANGELOG.md` section written by Communique (`communique generate HEAD --concise`); - dispatches **CI** and **Validate skills** onto the release branch (pushes made with the workflow token never trigger `pull_request` workflows on their own). 2. **Release** ([`.github/workflows/release.yml`](../.github/workflows/release.yml)) is the publish pipeline (see below). It is dispatched by Release Please after the tag exists, and still also triggers on manually pushed `v*` tags. 3. **CI** runs on the release PR like on any other PR and gates the merge. @@ -116,13 +116,14 @@ When skill packaging changes, also inspect `npm pack --dry-run` output to confir ## Cut a release -1. Open the current release PR (head branch `release-please--branches--main`, title `chore(release): `). If it is missing or stale, trigger the **Release Please** workflow manually (`gh workflow run release-please.yml`) or push to `main`. +1. Open the current release PR (title `chore(release): `). If it is missing or stale, trigger the **Release Please** workflow manually (`gh workflow run release-please.yml`) or push to `main`. 2. Review the proposed version and the `CHANGELOG.md` section. If the version is wrong, land a `Release-As` commit (see above) rather than editing the PR. If notes are wrong, fix the source material (feature PR bodies / commit overrides) and let the next push rebuild them. 3. Wait for the dispatched checks on the release branch to pass. If they were never dispatched (for example after a manual branch poke), dispatch them yourself: ```bash - gh workflow run ci.yml --ref release-please--branches--main - gh workflow run validate-skills.yml --ref release-please--branches--main + BRANCH=$(gh pr list --search 'chore(release) in:title' --state open --json headRefName --jq '.[0].headRefName') + gh workflow run ci.yml --ref "$BRANCH" + gh workflow run validate-skills.yml --ref "$BRANCH" ``` 4. Approve and merge the release PR (squash, like any other PR). The release PR is authored by the workflow bot, so a maintainer approval satisfies the required-review ruleset — no admin bypass is needed. From d1af3b9dd8e3146a53c7b16f7aa9c8a4c58b0d7f Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Fri, 12 Jun 2026 13:02:17 +0200 Subject: [PATCH 3/6] fix: keep the [Unreleased] anchor in CHANGELOG.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Communique hard-fails when CHANGELOG.md lacks an Unreleased heading, and it feeds that section's body to the LLM as draft material to reconcile into generated notes. Restore the section and replace release-please's stock Changelog updater (via a node-strategy subclass registered over the builtin type) with one that inserts release sections below the anchor — the stock insertion regex matches '## [Unreleased]' and would insert above it — and clears the reconciled draft body so it cannot leak into later releases. Also add a changelogPreview field to --dry-run output so the resulting CHANGELOG.md can be inspected without pushing. Change-Id: I634dd707083c5d53163e624760c9099b8dd1ff9d Co-Authored-By: Claude Fable 5 Signed-off-by: Thomas Kosiewski --- CHANGELOG.md | 2 + docs/RELEASE-PROCESS.md | 12 +- ...09-release-please-with-communique-notes.md | 8 +- src/tools/release-please-runner.ts | 118 ++++++++++++++++++ test/unit/tools/release-please-runner.test.ts | 99 ++++++++++----- 5 files changed, 203 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 546e91d4..488f34b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [v0.4.1] - 2026-06-12 ### Added diff --git a/docs/RELEASE-PROCESS.md b/docs/RELEASE-PROCESS.md index 0f287010..f53e2f36 100644 --- a/docs/RELEASE-PROCESS.md +++ b/docs/RELEASE-PROCESS.md @@ -47,10 +47,16 @@ git commit --allow-empty -m "chore: force release" -m "Release-As: 1.0.0" ### The changelog contract -- `CHANGELOG.md` has no `[Unreleased]` section. The release PR **is** the unreleased view: its `CHANGELOG.md` diff and PR body always show the pending release's curated notes. +- `CHANGELOG.md` keeps a `## [Unreleased]` section at the top, normally empty. Communique refuses to run when the heading is missing, and it feeds the section's body to the LLM as draft material to reconcile into the notes it generates. The release PR's living `CHANGELOG.md` diff and body are still the authoritative view of the pending release. +- New release sections are inserted **below** the `[Unreleased]` anchor (a custom updater in the runner replaces release-please's stock one, which would insert above it), and the release PR clears any reconciled draft body so it cannot leak into later releases. - New sections use the heading `## [] - ` (no `v` inside the brackets — release-please parses the version back out of the merged PR body, and its parser requires the bracket to be followed by a digit; entries before v0.4.2 keep the historical `[v]` style). -- Feature PRs must never touch `CHANGELOG.md`; the file is written only through release PRs. -- Notes are regenerated by Communique on every push to `main`, so manual edits to the release PR (branch or body) are overwritten by the next push. To fix wording, improve the source material instead: edit the merged feature PR descriptions/titles, or add a `BEGIN_COMMIT_OVERRIDE` block to a merged PR body, then let the next push to `main` rebuild the notes. (After the release, `gh release edit` can still fix the published notes.) +- Notes are regenerated by Communique on every push to `main`, so manual edits to the release PR (branch or body) are overwritten by the next push. To shape wording, improve the source material instead: + - stage draft notes under `## [Unreleased]` in a small dedicated PR — Communique reconciles them into the generated section, and the release PR clears them on merge; + - or edit the merged feature PR descriptions/titles, or add a `BEGIN_COMMIT_OVERRIDE` block to a merged PR body. + + (After the release, `gh release edit` can still fix the published notes.) + +- Feature PRs must not add release entries to `CHANGELOG.md`; the versioned sections are written only through release PRs. ## Release prerequisites diff --git a/docs/adr/0009-release-please-with-communique-notes.md b/docs/adr/0009-release-please-with-communique-notes.md index d1c25f87..9d7bb6ff 100644 --- a/docs/adr/0009-release-please-with-communique-notes.md +++ b/docs/adr/0009-release-please-with-communique-notes.md @@ -20,8 +20,8 @@ Run release-please **as a library** from a repo-owned runner (`src/tools/release Decisions inside that frame: -- **Heading drops the `v`** (`## [0.4.2] - ...`, not `## [v0.4.2] - ...`): release-please recovers version and notes by parsing the merged PR body with `/^#{2,} \[?(\d+\.\d+\.\d+...)/` — a digit must follow the bracket, or merging the PR creates no GitHub Release. Unit tests pin this contract and the CHANGELOG insertion point against the installed release-please internals. -- **No `[Unreleased]` section.** The release PR is the unreleased view. The two workflows that depended on the heading pair are retired with it. +- **Heading drops the `v`** (`## [0.4.2] - ...`, not `## [v0.4.2] - ...`): release-please recovers version and notes by parsing the merged PR body with `/^#{2,} \[?(\d+\.\d+\.\d+...)/` — a digit must follow the bracket, or merging the PR creates no GitHub Release. Unit tests pin this contract against the installed release-please internals. +- **`[Unreleased]` stays, as Communique's anchor and draft inbox.** Communique hard-requires the heading and reconciles any hand-staged draft body into the notes it generates. Release-please's stock Changelog updater would insert new sections _above_ that heading (its insertion regex matches `## [Unreleased]`), so a custom updater — wired in by re-registering the `node` release type with a small strategy subclass — inserts the release section below the anchor and clears the reconciled draft so it cannot leak into later releases. The two workflows that maintained/consumed the old heading pair are still retired; the release PR remains the authoritative unreleased view. - **Version bumps from Conventional Commits** with `bump-minor-pre-major` + `bump-patch-for-minor-pre-major`, matching the project's pre-1.0 history (breaking → minor, feat/fix → patch); `Release-As` footers override. - **Tags and releases are created non-draft** by the runner, which then dispatches the existing `release.yml` by tag input — tags created with the workflow token never fire `push: tags` triggers, and the repo already uses explicit `gh workflow run` dispatch for exactly this class of problem (CI on bot branches). `release.yml` keeps quality gates, the verified tarball, editorial Communique notes, assets, and npm trusted publishing unchanged; it briefly leaves the Release with changelog-style notes and no assets until it completes. - **release-please is a pinned devDependency** installed by the normal `aube ci` bootstrap. Its CJS-only octokit 9.x line carries no npm provenance attestations, so `@octokit/endpoint@9.0.6` is excluded from aube's trust policy in `pnpm-workspace.yaml` with justification. @@ -29,7 +29,7 @@ Decisions inside that frame: ## Consequences - Releasing is: review the standing release PR, approve, merge. No local scripts, no admin bypass (the bot authors the PR, so maintainer review satisfies the ruleset), no manual tagging. -- `CHANGELOG.md` is written only through release PRs; entries from v0.4.2 onward use the bracketed no-`v` heading while older entries keep their style. Feature PRs still must not touch the file. -- Notes regenerate from scratch on every push to `main`: an LLM call per push (same cost profile as the retired unreleased workflow), and reviewed wording can drift between pushes — the fix-the-source loop (feature PR bodies, `BEGIN_COMMIT_OVERRIDE`) replaces hand-editing the release PR. +- Versioned `CHANGELOG.md` sections are written only through release PRs; entries from v0.4.2 onward use the bracketed no-`v` heading while older entries keep their style. `[Unreleased]` stays at the top as an optionally hand-staged draft area that each release PR empties. +- Notes regenerate from scratch on every push to `main`: an LLM call per push (same cost profile as the retired unreleased workflow), and reviewed wording can drift between pushes — staging draft material under `[Unreleased]` or fixing the source (feature PR bodies, `BEGIN_COMMIT_OVERRIDE`) replaces hand-editing the release PR. - The runner is ~250 lines we own, pinned to an exact release-please version; the PR-body parsing contract it relies on is undocumented upstream, so version bumps must keep the compatibility tests green. - Manual tagging remains an emergency path but now requires re-syncing `.release-please-manifest.json` afterwards (documented in `docs/RELEASE-PROCESS.md`). diff --git a/src/tools/release-please-runner.ts b/src/tools/release-please-runner.ts index f3c328a8..be011c1b 100644 --- a/src/tools/release-please-runner.ts +++ b/src/tools/release-please-runner.ts @@ -19,6 +19,12 @@ * `pull_request` events) and the tag-driven Release pipeline (tags created * with the workflow token never trigger `push: tags` events). * + * CHANGELOG.md keeps a `## [Unreleased]` section at the top: Communique + * requires the heading to exist and reconciles any hand-staged draft body + * into the notes it generates. A custom updater (see + * {@link UnreleasedAwareChangelog}) therefore inserts release sections below + * that heading and clears the reconciled draft. + * * Run `npx tsx src/tools/release-please-runner.ts --dry-run` with * GITHUB_TOKEN/GITHUB_REPOSITORY and an LLM key to preview the candidate * release PR and releases without mutating anything on GitHub. @@ -35,10 +41,18 @@ import { GitHub, Manifest, registerChangelogNotes, + registerReleaseType, + type BuildUpdatesOptions, type ChangelogNotes, type CreatedRelease, type PullRequest, } from 'release-please'; +// Deep imports: neither the Node strategy class nor the stock Changelog +// updater is re-exported from the package root, and release-please ships no +// `exports` map restricting subpath access. +import { Node } from 'release-please/build/src/strategies/node.js'; +import { Changelog } from 'release-please/build/src/updaters/changelog.js'; +import type { Update } from 'release-please/build/src/update.js'; import { isDirectExecution } from '../util/isDirectExecution.js'; @@ -179,6 +193,88 @@ export function createCommuniqueChangelogNotes( }; } +const UNRELEASED_HEADING_PATTERN = /^#{2,3} \[?Unreleased\]?[ \t]*$/m; +// An H1 or H2 heading ends the Unreleased section; H3 stays inside it because +// hand-staged drafts use Keep-a-Changelog `### Added`-style subsections. +const NEXT_SECTION_PATTERN = /^##? /m; +const TITLE_PATTERN = /^# .+$/m; + +/** + * Replaces release-please's stock Changelog updater, which would insert the + * new section at the first `\n###? v?[0-9[]` match — and `## [Unreleased]` + * matches that pattern, so the release section would land *above* it. + * + * The `[Unreleased]` heading must stay at the top of CHANGELOG.md: Communique + * refuses to run without it, and it feeds the section's body to the LLM as + * draft material to reconcile into the generated notes. This updater keeps + * the heading, drops the draft body (Communique has already folded it into + * `changelogEntry` by the time this runs), and inserts the new release + * section directly below. + */ +export class UnreleasedAwareChangelog { + readonly changelogEntry: string; + + constructor(options: { changelogEntry: string }) { + this.changelogEntry = options.changelogEntry; + } + + updateContent(content: string | undefined): string { + const existing = (content ?? '').replace(/\r\n/g, '\n'); + const entry = this.changelogEntry.trim(); + const heading = UNRELEASED_HEADING_PATTERN.exec(existing); + + if (heading !== null) { + const headingEnd = heading.index + heading[0].length; + const head = existing.slice(0, headingEnd); + const tail = existing.slice(headingEnd); + const next = NEXT_SECTION_PATTERN.exec(tail); + const rest = next === null ? '' : tail.slice(next.index); + return joinSections(head, entry, rest); + } + + // Self-heal a missing Unreleased anchor so Communique keeps working on + // the next run. + const title = TITLE_PATTERN.exec(existing); + if (title !== null) { + const titleEnd = title.index + title[0].length; + const head = `${existing.slice(0, titleEnd)}\n\n## [Unreleased]`; + return joinSections(head, entry, existing.slice(titleEnd)); + } + return joinSections('# Changelog\n\n## [Unreleased]', entry, existing); + } +} + +function joinSections(head: string, entry: string, rest: string): string { + const sections = [head.trimEnd(), entry]; + if (rest.trim() !== '') { + sections.push(rest.trim()); + } + return `${sections.join('\n\n')}\n`; +} + +/** + * The stock `node` strategy with the CHANGELOG.md updater swapped for + * {@link UnreleasedAwareChangelog}. Registered over the builtin `node` type so + * `release-please-config.json` keeps the standard `"release-type": "node"`. + */ +export class CommuniqueNodeStrategy extends Node { + protected override async buildUpdates( + options: BuildUpdatesOptions, + ): Promise { + const updates = await super.buildUpdates(options); + return updates.map((update) => + update.updater instanceof Changelog + ? { + ...update, + updater: new UnreleasedAwareChangelog({ + changelogEntry: update.updater.changelogEntry, + }), + } + : update, + ); + } +} + export interface RunnerOutputs { readonly prs_created: string; readonly pr_branches: string; @@ -216,6 +312,26 @@ function writeGithubOutputs(outputs: RunnerOutputs): void { appendFileSync(outputPath, `${lines}\n`); } +/** + * Dry-run aid: applies the candidate PR's CHANGELOG.md update to the local + * working-tree copy so the resulting file can be inspected without pushing. + */ +function previewChangelog(updates: readonly Update[]): string | undefined { + const changelogUpdate = updates.find( + (update) => update.updater instanceof UnreleasedAwareChangelog, + ); + if (changelogUpdate === undefined) { + return undefined; + } + let current: string | undefined; + try { + current = readFileSync(changelogUpdate.path, 'utf8'); + } catch { + current = undefined; + } + return changelogUpdate.updater.updateContent(current); +} + function requireEnv(name: string): string { const value = process.env[name]; if (value === undefined || value === '') { @@ -234,6 +350,7 @@ async function main(): Promise { } registerChangelogNotes('communique', () => createCommuniqueChangelogNotes()); + registerReleaseType('node', (options) => new CommuniqueNodeStrategy(options)); const github = await GitHub.create({ owner, repo, token }); // The override exists for --dry-run debugging against a feature branch @@ -259,6 +376,7 @@ async function main(): Promise { headBranchName: pullRequest.headRefName, version: pullRequest.version?.toString(), body: pullRequest.body.toString(), + changelogPreview: previewChangelog(pullRequest.updates), })), }, null, diff --git a/test/unit/tools/release-please-runner.test.ts b/test/unit/tools/release-please-runner.test.ts index cc0e0f13..3d56e22e 100644 --- a/test/unit/tools/release-please-runner.test.ts +++ b/test/unit/tools/release-please-runner.test.ts @@ -1,12 +1,9 @@ import { describe, expect, it } from 'vitest'; -// Deep imports into release-please internals: these tests pin the two -// compatibility contracts the runner depends on but release-please does not -// document — (a) the merged release PR body must parse back into a version + -// notes (otherwise no GitHub Release is created on merge), and (b) the -// Changelog updater must insert the new section above this repo's historical -// `## [v] - ` headings. -import { Changelog } from 'release-please/build/src/updaters/changelog.js'; +// Deep imports into release-please internals: these tests pin the +// compatibility contract the runner depends on but release-please does not +// document — the merged release PR body must parse back into a version + +// notes, otherwise no GitHub Release is created on merge. import { PullRequestBody } from 'release-please/build/src/util/pull-request-body.js'; import { PullRequestTitle, @@ -15,6 +12,7 @@ import { import { Version } from 'release-please/build/src/version.js'; import { + UnreleasedAwareChangelog, assertLlmCredentials, buildCommuniqueArgs, createCommuniqueChangelogNotes, @@ -162,10 +160,9 @@ describe('release PR body round-trip (release-please compatibility)', () => { }); }); -describe('CHANGELOG.md insertion (release-please compatibility)', () => { - const existing = [ - '# Changelog', - '', +describe('UnreleasedAwareChangelog', () => { + const entry = formatChangelogSection('0.4.2', '2026-06-13', SAMPLE_BODY); + const previousSections = [ '## [v0.4.1] - 2026-06-12', '', '### Added', @@ -180,28 +177,72 @@ describe('CHANGELOG.md insertion (release-please compatibility)', () => { '', ].join('\n'); - it('inserts the new section above the previous v-prefixed heading', () => { - const updater = new Changelog({ - version: Version.parse('0.4.2'), - changelogEntry: formatChangelogSection( - '0.4.2', - '2026-06-13', - SAMPLE_BODY, - ), - }); + it('keeps the empty Unreleased anchor and inserts the section below it', () => { + const existing = `# Changelog\n\n## [Unreleased]\n\n${previousSections}`; + const updated = new UnreleasedAwareChangelog({ + changelogEntry: entry, + }).updateContent(existing); + + expect(updated).toBe( + `# Changelog\n\n## [Unreleased]\n\n${entry}\n\n${previousSections.trim()}\n`, + ); + }); + + it('clears a hand-staged draft body, including H3 subsections', () => { + // Communique reconciles the draft into the generated notes before this + // updater runs; leaving it in place would leak it into every later + // release. + const existing = [ + '# Changelog', + '', + '## [Unreleased]', + '', + '### Added', + '', + '- Hand-staged draft bullet.', + '', + previousSections, + ].join('\n'); + const updated = new UnreleasedAwareChangelog({ + changelogEntry: entry, + }).updateContent(existing); + + expect(updated).not.toContain('Hand-staged draft bullet'); + expect(updated.indexOf('## [Unreleased]')).toBeLessThan( + updated.indexOf('## [0.4.2] - 2026-06-13'), + ); + expect(updated.indexOf('## [0.4.2] - 2026-06-13')).toBeLessThan( + updated.indexOf('## [v0.4.1] - 2026-06-12'), + ); + }); + + it('accepts the bracketless Unreleased variant communique also allows', () => { + const existing = `# Changelog\n\n## Unreleased\n\n${previousSections}`; + const updated = new UnreleasedAwareChangelog({ + changelogEntry: entry, + }).updateContent(existing); + + expect(updated).toContain(`## Unreleased\n\n${entry}\n\n## [v0.4.1]`); + }); - const updated = updater.updateContent(existing); + it('self-heals a missing Unreleased anchor below the title', () => { + const existing = `# Changelog\n\n${previousSections}`; + const updated = new UnreleasedAwareChangelog({ + changelogEntry: entry, + }).updateContent(existing); - const newIndex = updated.indexOf('## [0.4.2] - 2026-06-13'); - const previousIndex = updated.indexOf('## [v0.4.1] - 2026-06-12'); - expect(newIndex).toBeGreaterThan(updated.indexOf('# Changelog')); - expect(newIndex).toBeGreaterThan(-1); - expect(previousIndex).toBeGreaterThan(newIndex); - // Exactly one blank line between the new section and the previous one. - expect(updated).toContain( - '--timing recorded` ([#139](https://github.com/coder/agent-tty/pull/139)).\n\n## [v0.4.1]', + expect(updated).toBe( + `# Changelog\n\n## [Unreleased]\n\n${entry}\n\n${previousSections.trim()}\n`, ); }); + + it('scaffolds a fresh changelog when the file is missing', () => { + const updated = new UnreleasedAwareChangelog({ + changelogEntry: entry, + }).updateContent(undefined); + + expect(updated).toBe(`# Changelog\n\n## [Unreleased]\n\n${entry}\n`); + }); }); describe('createCommuniqueChangelogNotes', () => { From e692945aed8d39a2ff8e01244e80a81c66ee3130 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Fri, 12 Jun 2026 13:04:50 +0200 Subject: [PATCH 4/6] fix: normalize communique body headings against LLM drift A live run produced '## Changed' (H2) where the historical changelog nests '### Changed' (H3) under each version heading; demote stray H2s and drop a duplicate leading version heading if the LLM emits one. Change-Id: I3f8ff8223d8482bffac556d90b32fd348597fab6 Co-Authored-By: Claude Fable 5 Signed-off-by: Thomas Kosiewski --- src/tools/release-please-runner.ts | 26 ++++++++++++++----- test/unit/tools/release-please-runner.test.ts | 21 +++++++++++++++ 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/tools/release-please-runner.ts b/src/tools/release-please-runner.ts index be011c1b..caa0a9fb 100644 --- a/src/tools/release-please-runner.ts +++ b/src/tools/release-please-runner.ts @@ -118,19 +118,33 @@ export function buildCommuniqueArgs( * would make the merged release PR unparseable and no release would be * created. */ +/** + * Guards the generated section body against LLM drift: drops a leading + * version heading if Communique emitted one (the runner prepends its own + * canonical heading), and demotes stray H2 section headings to the H3 the + * historical CHANGELOG nests under each version (`### Added` / `### Changed`). + */ +export function normalizeCommuniqueBody(body: string): string { + return body + .trim() + .replace(/^#{2,3} \[?v?\d[^\n]*\n*/, '') + .replace(/^## (?!\[)/gm, '### ') + .trim(); +} + export function formatChangelogSection( version: string, isoDate: string, body: string, ): string { - const trimmed = body.trim(); + const normalized = normalizeCommuniqueBody(body); const content = - trimmed === '' + normalized === '' ? '- Maintenance release with no user-facing changes.' - : trimmed; - // No trailing newline: release-please's Changelog updater joins the entry - // with `\n` on both sides, so a trailing newline here would leave a double - // blank line above the previous section. + : normalized; + // No trailing newline: the changelog updater joins the entry with `\n\n` on + // both sides, so a trailing newline here would leave a double blank line + // above the previous section. return `## [${version}] - ${isoDate}\n\n${content}`; } diff --git a/test/unit/tools/release-please-runner.test.ts b/test/unit/tools/release-please-runner.test.ts index 3d56e22e..8acf3848 100644 --- a/test/unit/tools/release-please-runner.test.ts +++ b/test/unit/tools/release-please-runner.test.ts @@ -18,6 +18,7 @@ import { createCommuniqueChangelogNotes, formatChangelogSection, formatOutputs, + normalizeCommuniqueBody, todayIsoDate, } from '../../../src/tools/release-please-runner.js'; import { writeFileSync } from 'node:fs'; @@ -111,6 +112,26 @@ describe('formatChangelogSection', () => { }); }); +describe('normalizeCommuniqueBody', () => { + it('demotes stray H2 section headings to the house H3 style', () => { + // Observed in a live run: communique --concise emitted `## Changed` + // where the historical changelog nests `### Changed` under the version. + expect(normalizeCommuniqueBody('## Changed\n\n- A change.')).toBe( + '### Changed\n\n- A change.', + ); + }); + + it('drops a duplicate leading version heading', () => { + expect( + normalizeCommuniqueBody('## [0.4.2] - 2026-06-12\n\n### Added\n\n- X.'), + ).toBe('### Added\n\n- X.'); + }); + + it('leaves house-style bodies untouched', () => { + expect(normalizeCommuniqueBody(SAMPLE_BODY)).toBe(SAMPLE_BODY); + }); +}); + describe('todayIsoDate', () => { it('formats as YYYY-MM-DD in UTC', () => { expect(todayIsoDate(new Date('2026-06-12T23:59:59Z'))).toBe('2026-06-12'); From 068e2e293c8f663cd6d45105f9f1cc2704befeab Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Fri, 12 Jun 2026 13:34:38 +0200 Subject: [PATCH 5/6] fix: apply prerelease flags on the gh release edit path too Under the release-please flow the GitHub Release already exists when release.yml runs, so the edit path is the normal route; without the flags a prerelease (e.g. an rc rehearsal) would take over the Latest badge. Change-Id: I82bf91dbb61c8751e8c13578f83ff8b95e8fc8a8 Co-Authored-By: Claude Fable 5 Signed-off-by: Thomas Kosiewski --- .github/workflows/release.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2d13393d..98575b93 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -361,9 +361,13 @@ jobs: CHECKSUM_FILENAME: ${{ needs.prepare-release.outputs.checksum_filename }} run: | set -euo pipefail - create_flags=() + # Applied on both paths: under the release-please flow the release + # already exists (created at release-PR merge), so the edit path is + # the normal route and must keep prereleases off the Latest badge + # just like the create path does. + release_flags=() if [[ "$PACKAGE_VERSION" == *-* ]]; then - create_flags+=(--prerelease --latest=false) + release_flags+=(--prerelease --latest=false) fi if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then @@ -371,7 +375,8 @@ jobs: "$RELEASE_TAG" \ --repo "$GITHUB_REPOSITORY" \ --title "$RELEASE_TITLE" \ - --notes-file "$RELEASE_NOTES_FILE" + --notes-file "$RELEASE_NOTES_FILE" \ + "${release_flags[@]}" gh release upload \ "$RELEASE_TAG" \ "$RELEASE_DIR/$TARBALL_FILENAME" \ @@ -387,7 +392,7 @@ jobs: --verify-tag \ --title "$RELEASE_TITLE" \ --notes-file "$RELEASE_NOTES_FILE" \ - "${create_flags[@]}" + "${release_flags[@]}" fi publish-npm: From b47da04b45d31a6b40204f83df96d6355669c054 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Fri, 12 Jun 2026 13:55:01 +0200 Subject: [PATCH 6/6] fix: address max-effort code review findings - Write release outputs before rebuilding the release PR and guard the workflow dispatch steps with !cancelled(): a notes failure (LLM outage) after createReleases() tagged a release previously meant release.yml was never dispatched, and a rerun could not recover it (the merged PR label has already flipped to autorelease: tagged). - Make the changelog section-boundary scan and the H2 demotion fence-aware so '#'-prefixed lines inside fenced code blocks in staged drafts or LLM output are never mistaken for headings. - Fail with an error naming communique when it exits 0 without writing the --output file, instead of a bare readFileSync ENOENT. - Move formatChangelogSection's heading-contract JSDoc back onto the function it documents. Change-Id: Ie5f78cb5e52338a1f9b55c4b5d7df0818c900fcc Co-Authored-By: Claude Fable 5 Signed-off-by: Thomas Kosiewski --- .github/workflows/release-please.yml | 37 +++-- src/tools/release-please-runner.ts | 150 +++++++++++++----- test/unit/tools/release-please-runner.test.ts | 85 ++++++++-- 3 files changed, 199 insertions(+), 73 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index f15f4fb4..03fd3d13 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -53,12 +53,32 @@ jobs: COMMUNIQUE_MODEL: ${{ vars.COMMUNIQUE_MODEL }} run: npx tsx src/tools/release-please-runner.ts + # Tags created with the workflow token never trigger `push: tags` + # workflows, so start the Release pipeline (quality gates, verified + # tarball, GitHub Release assets, npm publish) explicitly. The + # `!cancelled()` guard matters: the runner writes the release outputs + # before it rebuilds the release PR notes, so a notes failure (e.g. an + # LLM outage) still dispatches the pipeline for the tag it just created + # — a rerun could not recover that, because the merged release PR is + # already labeled `autorelease: tagged`. + - name: Dispatch the Release pipeline for created releases + if: ${{ !cancelled() && steps.release_please.outputs.releases_created == 'true' }} + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAGS: ${{ steps.release_please.outputs.release_tags }} + run: | + set -euo pipefail + read -ra tags <<< "$RELEASE_TAGS" + for tag in "${tags[@]}"; do + gh workflow run release.yml --field "tag=$tag" + done + # Branch pushes made with the workflow token never trigger # `pull_request` workflows, so dispatch the checks explicitly — the # dispatched runs report against the branch head SHA and satisfy the # release PR's required status checks. - name: Dispatch checks onto the release PR branch - if: steps.release_please.outputs.prs_created == 'true' + if: ${{ !cancelled() && steps.release_please.outputs.prs_created == 'true' }} env: GH_TOKEN: ${{ github.token }} PR_BRANCHES: ${{ steps.release_please.outputs.pr_branches }} @@ -69,18 +89,3 @@ jobs: gh workflow run ci.yml --ref "$branch" gh workflow run validate-skills.yml --ref "$branch" done - - # Tags created with the workflow token never trigger `push: tags` - # workflows, so start the Release pipeline (quality gates, verified - # tarball, GitHub Release assets, npm publish) explicitly. - - name: Dispatch the Release pipeline for created releases - if: steps.release_please.outputs.releases_created == 'true' - env: - GH_TOKEN: ${{ github.token }} - RELEASE_TAGS: ${{ steps.release_please.outputs.release_tags }} - run: | - set -euo pipefail - read -ra tags <<< "$RELEASE_TAGS" - for tag in "${tags[@]}"; do - gh workflow run release.yml --field "tag=$tag" - done diff --git a/src/tools/release-please-runner.ts b/src/tools/release-please-runner.ts index caa0a9fb..f881367f 100644 --- a/src/tools/release-please-runner.ts +++ b/src/tools/release-please-runner.ts @@ -31,7 +31,13 @@ */ import { execFile } from 'node:child_process'; -import { appendFileSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'; +import { + appendFileSync, + existsSync, + mkdtempSync, + readFileSync, + rmSync, +} from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import process from 'node:process'; @@ -107,31 +113,45 @@ export function buildCommuniqueArgs( return args; } -/** - * Formats the section that becomes the CHANGELOG.md entry, the release PR - * body, and (after merge) the GitHub Release notes. - * - * The heading is `## [] - ` — close to this repo's historical - * `## [v] - ` style, but without the `v`: release-please - * parses the merged PR body with `/^#{2,} \[?(?\d+\.\d+\.\d+...)/`, - * which requires a digit immediately after the optional bracket. A `v` there - * would make the merged release PR unparseable and no release would be - * created. - */ /** * Guards the generated section body against LLM drift: drops a leading * version heading if Communique emitted one (the runner prepends its own * canonical heading), and demotes stray H2 section headings to the H3 the * historical CHANGELOG nests under each version (`### Added` / `### Changed`). + * Lines inside fenced code blocks are left untouched. */ export function normalizeCommuniqueBody(body: string): string { - return body + const withoutLeadingHeading = body .trim() - .replace(/^#{2,3} \[?v?\d[^\n]*\n*/, '') - .replace(/^## (?!\[)/gm, '### ') + .replace(/^#{2,3} \[?v?\d[^\n]*\n*/, ''); + let inFence = false; + return withoutLeadingHeading + .split('\n') + .map((line) => { + if (FENCE_PATTERN.test(line)) { + inFence = !inFence; + return line; + } + if (!inFence && /^## (?!\[)/.test(line)) { + return `### ${line.slice(3)}`; + } + return line; + }) + .join('\n') .trim(); } +/** + * Formats the section that becomes the CHANGELOG.md entry, the release PR + * body, and (after merge) the GitHub Release notes. + * + * The heading is `## [] - ` — close to this repo's historical + * `## [v] - ` style, but without the `v`: release-please + * parses the merged PR body with `/^#{2,} \[?(?\d+\.\d+\.\d+...)/`, + * which requires a digit immediately after the optional bracket. A `v` there + * would make the merged release PR unparseable and no release would be + * created. + */ export function formatChangelogSection( version: string, isoDate: string, @@ -198,6 +218,11 @@ export function createCommuniqueChangelogNotes( model: env.COMMUNIQUE_MODEL, }); await runCommunique(args, env); + if (!existsSync(outputFile)) { + throw new Error( + `communique exited successfully but wrote no output file at ${outputFile}`, + ); + } const body = readFileSync(outputFile, 'utf8'); return formatChangelogSection(options.version, todayIsoDate(), body); } finally { @@ -207,11 +232,37 @@ export function createCommuniqueChangelogNotes( }; } -const UNRELEASED_HEADING_PATTERN = /^#{2,3} \[?Unreleased\]?[ \t]*$/m; +const UNRELEASED_HEADING_PATTERN = /^#{2,3} \[?Unreleased\]?[ \t]*$/; // An H1 or H2 heading ends the Unreleased section; H3 stays inside it because // hand-staged drafts use Keep-a-Changelog `### Added`-style subsections. -const NEXT_SECTION_PATTERN = /^##? /m; -const TITLE_PATTERN = /^# .+$/m; +const NEXT_SECTION_PATTERN = /^##? /; +const TITLE_PATTERN = /^# .+$/; +const FENCE_PATTERN = /^ {0,3}(?:`{3,}|~{3,})/; + +/** + * Index of the first line at or after `start` matching `predicate` outside + * any fenced code block, or -1. Fence state is tracked from the first line so + * heading-like lines inside ``` fences (a `# comment` in a bash example, a + * `## heading` in a markdown sample) are never mistaken for real headings. + */ +function findLineOutsideFences( + lines: readonly string[], + start: number, + predicate: (line: string) => boolean, +): number { + let inFence = false; + for (let i = 0; i < lines.length; i += 1) { + const line = lines[i] ?? ''; + if (FENCE_PATTERN.test(line)) { + inFence = !inFence; + continue; + } + if (!inFence && i >= start && predicate(line)) { + return i; + } + } + return -1; +} /** * Replaces release-please's stock Changelog updater, which would insert the @@ -235,24 +286,30 @@ export class UnreleasedAwareChangelog { updateContent(content: string | undefined): string { const existing = (content ?? '').replace(/\r\n/g, '\n'); const entry = this.changelogEntry.trim(); - const heading = UNRELEASED_HEADING_PATTERN.exec(existing); - - if (heading !== null) { - const headingEnd = heading.index + heading[0].length; - const head = existing.slice(0, headingEnd); - const tail = existing.slice(headingEnd); - const next = NEXT_SECTION_PATTERN.exec(tail); - const rest = next === null ? '' : tail.slice(next.index); + const lines = existing.split('\n'); + + const unreleasedIndex = findLineOutsideFences(lines, 0, (line) => + UNRELEASED_HEADING_PATTERN.test(line), + ); + if (unreleasedIndex !== -1) { + const head = lines.slice(0, unreleasedIndex + 1).join('\n'); + const nextIndex = findLineOutsideFences( + lines, + unreleasedIndex + 1, + (line) => NEXT_SECTION_PATTERN.test(line), + ); + const rest = nextIndex === -1 ? '' : lines.slice(nextIndex).join('\n'); return joinSections(head, entry, rest); } // Self-heal a missing Unreleased anchor so Communique keeps working on // the next run. - const title = TITLE_PATTERN.exec(existing); - if (title !== null) { - const titleEnd = title.index + title[0].length; - const head = `${existing.slice(0, titleEnd)}\n\n## [Unreleased]`; - return joinSections(head, entry, existing.slice(titleEnd)); + const titleIndex = findLineOutsideFences(lines, 0, (line) => + TITLE_PATTERN.test(line), + ); + if (titleIndex !== -1) { + const head = `${lines.slice(0, titleIndex + 1).join('\n')}\n\n## [Unreleased]`; + return joinSections(head, entry, lines.slice(titleIndex + 1).join('\n')); } return joinSections('# Changelog\n\n## [Unreleased]', entry, existing); } @@ -296,25 +353,31 @@ export interface RunnerOutputs { readonly release_tags: string; } -export function formatOutputs( +export function formatReleaseOutputs( releases: readonly (CreatedRelease | undefined)[], - pullRequests: readonly (PullRequest | undefined)[], -): RunnerOutputs { +): Pick { const tags = releases .filter((release): release is CreatedRelease => release !== undefined) .map((release) => release.tagName); + return { + releases_created: tags.length > 0 ? 'true' : 'false', + release_tags: tags.join(' '), + }; +} + +export function formatPullRequestOutputs( + pullRequests: readonly (PullRequest | undefined)[], +): Pick { const branches = pullRequests .filter((pr): pr is PullRequest => pr !== undefined) .map((pr) => pr.headBranchName); return { prs_created: branches.length > 0 ? 'true' : 'false', pr_branches: branches.join(' '), - releases_created: tags.length > 0 ? 'true' : 'false', - release_tags: tags.join(' '), }; } -function writeGithubOutputs(outputs: RunnerOutputs): void { +function writeGithubOutputs(outputs: Partial): void { const outputPath = process.env.GITHUB_OUTPUT; const lines = Object.entries(outputs) .map(([key, value]) => `${key}=${value}`) @@ -404,11 +467,18 @@ async function main(): Promise { // triggered by a release PR merge tags that release before considering a // new PR for any commits that landed since. const releases = await manifest.createReleases(); + // Written before createPullRequests so the workflow's `!cancelled()` + // dispatch steps can still start the Release pipeline for an + // already-created tag when the notes rebuild below fails — a rerun could + // not recover it (the merged PR's label has already flipped to + // `autorelease: tagged`). + const releaseOutputs = formatReleaseOutputs(releases); + writeGithubOutputs(releaseOutputs); const pullRequests = await manifest.createPullRequests(); - const outputs = formatOutputs(releases, pullRequests); - writeGithubOutputs(outputs); + const pullRequestOutputs = formatPullRequestOutputs(pullRequests); + writeGithubOutputs(pullRequestOutputs); process.stdout.write( - `release-please: releases=[${outputs.release_tags}] prs=[${outputs.pr_branches}]\n`, + `release-please: releases=[${releaseOutputs.release_tags}] prs=[${pullRequestOutputs.pr_branches}]\n`, ); } diff --git a/test/unit/tools/release-please-runner.test.ts b/test/unit/tools/release-please-runner.test.ts index 8acf3848..b81e5a38 100644 --- a/test/unit/tools/release-please-runner.test.ts +++ b/test/unit/tools/release-please-runner.test.ts @@ -17,7 +17,8 @@ import { buildCommuniqueArgs, createCommuniqueChangelogNotes, formatChangelogSection, - formatOutputs, + formatPullRequestOutputs, + formatReleaseOutputs, normalizeCommuniqueBody, todayIsoDate, } from '../../../src/tools/release-please-runner.js'; @@ -130,6 +131,24 @@ describe('normalizeCommuniqueBody', () => { it('leaves house-style bodies untouched', () => { expect(normalizeCommuniqueBody(SAMPLE_BODY)).toBe(SAMPLE_BODY); }); + + it('never rewrites heading-like lines inside fenced code blocks', () => { + const body = [ + '## Changed', + '', + '- New install flow:', + '', + '```bash', + '## this is a markdown sample, not a heading', + '# comment', + 'npm install -g agent-tty', + '```', + ].join('\n'); + const normalized = normalizeCommuniqueBody(body); + expect(normalized).toContain('### Changed'); + expect(normalized).toContain('## this is a markdown sample, not a heading'); + expect(normalized).not.toContain('### this is a markdown sample'); + }); }); describe('todayIsoDate', () => { @@ -264,6 +283,35 @@ describe('UnreleasedAwareChangelog', () => { expect(updated).toBe(`# Changelog\n\n## [Unreleased]\n\n${entry}\n`); }); + + it('ignores heading-like lines inside fenced code blocks when clearing a draft', () => { + // Without fence tracking, the `# comment` line would be taken as the next + // section boundary, half-clearing the draft and corrupting the fence. + const existing = [ + '# Changelog', + '', + '## [Unreleased]', + '', + '- Draft bullet with an example:', + '', + '```bash', + '# comment inside a fence', + '## not a heading either', + 'agent-tty ls', + '```', + '', + '- Trailing draft bullet.', + '', + previousSections, + ].join('\n'); + const updated = new UnreleasedAwareChangelog({ + changelogEntry: entry, + }).updateContent(existing); + + expect(updated).not.toContain('comment inside a fence'); + expect(updated).not.toContain('Trailing draft bullet'); + expect(updated).toContain(`## [Unreleased]\n\n${entry}\n\n## [v0.4.1]`); + }); }); describe('createCommuniqueChangelogNotes', () => { @@ -310,37 +358,40 @@ describe('createCommuniqueChangelogNotes', () => { }); }); -describe('formatOutputs', () => { +describe('workflow outputs', () => { it('maps releases and pull requests into workflow outputs', () => { - const outputs = formatOutputs( - [ + expect( + formatReleaseOutputs([ undefined, { tagName: 'v0.4.2', - headBranchName: 'x', - } as unknown as Parameters[0][number], - ], - [ + } as unknown as Parameters[0][number], + ]), + ).toEqual({ + releases_created: 'true', + release_tags: 'v0.4.2', + }); + expect( + formatPullRequestOutputs([ { headBranchName: 'release-please--branches--main', - } as unknown as Parameters[1][number], + } as unknown as Parameters[0][number], undefined, - ], - ); - expect(outputs).toEqual({ + ]), + ).toEqual({ prs_created: 'true', pr_branches: 'release-please--branches--main', - releases_created: 'true', - release_tags: 'v0.4.2', }); }); it('reports false when nothing was created', () => { - expect(formatOutputs([], [undefined])).toEqual({ - prs_created: 'false', - pr_branches: '', + expect(formatReleaseOutputs([])).toEqual({ releases_created: 'false', release_tags: '', }); + expect(formatPullRequestOutputs([undefined])).toEqual({ + prs_created: 'false', + pr_branches: '', + }); }); });