diff --git a/.github/workflows/apply-release-notes.yml b/.github/workflows/apply-release-notes.yml new file mode 100644 index 000000000..2daa0befe --- /dev/null +++ b/.github/workflows/apply-release-notes.yml @@ -0,0 +1,58 @@ +name: Apply release notes + +# When a PR that touches release-notes/v.md is merged to main, push +# the contents of each touched file to the corresponding GitHub Release body. +# The proposer (propose-release-notes.yml) creates these PRs; reviewers approve +# by merging. + +on: + pull_request: + types: [closed] + branches: [main] + paths: + - "release-notes/**" + +permissions: + contents: read + +jobs: + apply: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1 + with: + ref: main + # Fetch only the merge commit; release-notes files are checked in by + # the PR and we don't need history here. + fetch-depth: 1 + persist-credentials: false + + - uses: ./.github/actions/setup + + - name: Apply each touched release-notes file + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + # `gh pr view --json files` returns every file the PR touched. Filter + # to release-notes/v.md, derive the tag from the basename, + # and invoke the apply script per file. Tolerates PRs that bundle + # multiple version files, though in practice the proposer creates one + # file per PR. + run: | + set -euo pipefail + files=$(gh pr view "$PR_NUMBER" --repo "${{ github.repository }}" \ + --json files --jq '.files[].path' | \ + grep -E '^release-notes/v[0-9]+\.[0-9]+\.[0-9]+\.md$' || true) + if [ -z "$files" ]; then + echo "No release-notes/ files in PR #$PR_NUMBER; nothing to apply." >&2 + exit 0 + fi + while IFS= read -r file; do + base=$(basename "$file" .md) + tag="${base}" + echo "==> Applying $file to release $tag" + pnpm exec bun apps/cli/scripts/apply-release-notes.ts --tag "$tag" + done <<< "$files" diff --git a/.github/workflows/propose-release-notes.yml b/.github/workflows/propose-release-notes.yml new file mode 100644 index 000000000..899544e38 --- /dev/null +++ b/.github/workflows/propose-release-notes.yml @@ -0,0 +1,73 @@ +name: Propose release notes + +# Runs after backfill-release-notes lands the raw semantic-release block in the +# GitHub Release body. Re-derives that block, asks Claude to rewrite it into +# user-centric notes per tools/release/release-notes-prompt.md, and opens a PR +# adding release-notes/v.md. Merging the PR triggers +# apply-release-notes.yml, which pushes the file's contents to the GH Release. +# +# Stable releases only — prerelease tags (-beta./-alpha.) keep the raw body. + +on: + workflow_call: + inputs: + tag: + description: Release tag to propose notes for (e.g. v2.101.0) + required: true + type: string + non_blocking: + description: Do not fail the workflow run when proposing fails (release pipeline) + required: false + type: boolean + default: false + workflow_dispatch: + inputs: + tag: + description: Release tag to propose notes for (e.g. v2.101.0) + required: true + type: string + +permissions: + contents: read + +jobs: + propose: + # Belt-and-suspenders: the script also refuses prerelease tags, but cheap + # to short-circuit here so we never pay for an LLM call we'd discard. + if: ${{ !contains(inputs.tag, '-beta.') && !contains(inputs.tag, '-alpha.') }} + runs-on: ubuntu-latest + continue-on-error: ${{ inputs.non_blocking }} + permissions: + contents: write + pull-requests: write + env: + TAG: ${{ inputs.tag }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + steps: + # App token gets us push to a protected default branch *and* PR creation + # under the App identity, matching the rest of release.yml. + - id: app-token + uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} + + - uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1 + with: + # Full history + tags so backfill-release-notes.ts can reach the + # commit graph it needs (semantic-release walks notes back to the + # last release on the channel). + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} + + - uses: ./.github/actions/setup + + - name: Configure git identity + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Propose release notes + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: pnpm exec bun apps/cli/scripts/propose-release-notes.ts --tag "${TAG}" --apply diff --git a/.github/workflows/release-shared.yml b/.github/workflows/release-shared.yml index 1a6f841e7..66cfaaa2c 100644 --- a/.github/workflows/release-shared.yml +++ b/.github/workflows/release-shared.yml @@ -343,6 +343,20 @@ jobs: apply: true non_blocking: true + # Once the raw semantic-release block is in the release body, ask Claude to + # rewrite it into user-centric notes and open a PR for human approval. Stable + # releases only — prereleases keep the raw body. Non-blocking so an LLM + # hiccup never gates a published release; reviewers can always rerun the + # workflow by hand from the Actions tab. + propose-release-notes: + uses: ./.github/workflows/propose-release-notes.yml + needs: backfill-release-notes + if: ${{ !inputs.dry_run && !inputs.prerelease && needs.backfill-release-notes.result == 'success' }} + with: + tag: v${{ inputs.version }} + non_blocking: true + secrets: inherit + publish-homebrew: needs: publish if: ${{ !inputs.dry_run && inputs.publish_brew_scoop }} diff --git a/apps/cli/package.json b/apps/cli/package.json index 67ab24316..ae4757112 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -37,10 +37,13 @@ "fix:all": "nx run-many -t lint:fix fmt:fix knip:fix --projects=$npm_package_name" }, "devDependencies": { + "@anthropic-ai/claude-agent-sdk": "^0.3.146", + "@anthropic-ai/sdk": "^0.97.1", "@clack/prompts": "^1.4.0", "@effect/atom-react": "catalog:", "@effect/platform-bun": "catalog:", "@effect/vitest": "catalog:", + "@modelcontextprotocol/sdk": "^1.29.0", "@napi-rs/keyring": "^1.3.0", "@parcel/watcher": "^2.5.6", "@supabase/api": "workspace:*", @@ -116,7 +119,10 @@ "oxfmt", "oxlint", "oxlint-tsgolint", - "semantic-release" + "semantic-release", + "@anthropic-ai/claude-agent-sdk", + "@anthropic-ai/sdk", + "@modelcontextprotocol/sdk" ] }, "nx": { diff --git a/apps/cli/scripts/apply-release-notes.ts b/apps/cli/scripts/apply-release-notes.ts new file mode 100644 index 000000000..902720823 --- /dev/null +++ b/apps/cli/scripts/apply-release-notes.ts @@ -0,0 +1,37 @@ +#!/usr/bin/env bun +// Push the contents of release-notes/v.md to the GitHub Release body +// for tag v. Invoked from apply-release-notes.yml after a +// release-notes PR is merged to main. +// +// Usage: +// bun apps/cli/scripts/apply-release-notes.ts --tag v2.101.0 +import { $ } from "bun"; +import { existsSync } from "node:fs"; +import path from "node:path"; +import process from "node:process"; +import { parseArgs } from "node:util"; + +const { values } = parseArgs({ + options: { + tag: { type: "string" }, + }, + strict: true, +}); + +const tag = values.tag; +if (!tag) { + console.error("--tag is required (e.g. --tag v2.101.0)"); + process.exit(2); +} +const version = tag.replace(/^v/, ""); + +const repoRoot = (await $`git rev-parse --show-toplevel`.text()).trim(); +const notesPath = path.join(repoRoot, "release-notes", `v${version}.md`); +if (!existsSync(notesPath)) { + console.error(`No notes file at ${path.relative(repoRoot, notesPath)}`); + process.exit(1); +} + +console.error(`==> Updating GitHub Release body for ${tag}`); +await $`gh release edit ${tag} --notes-file ${notesPath}`.cwd(repoRoot); +console.error(`==> Done`); diff --git a/apps/cli/scripts/propose-release-notes.ts b/apps/cli/scripts/propose-release-notes.ts new file mode 100644 index 000000000..e97b738ce --- /dev/null +++ b/apps/cli/scripts/propose-release-notes.ts @@ -0,0 +1,195 @@ +#!/usr/bin/env bun +// Generate a user-centric GitHub Release body for a stable Supabase CLI tag +// by running the Claude Agent SDK against tools/release/release-notes-prompt.md +// with the raw semantic-release block substituted in. +// +// Pipeline shape: +// 1. `backfill-release-notes.ts --tag ` produces the raw semantic-release +// markdown (without writing anything to the GH release). We always +// re-derive this so the proposer is decoupled from whatever happens to +// sit in the release body at the moment. +// 2. The raw block is inlined into tools/release/release-notes-prompt.md in +// place of the {{PASTE_SEMANTIC_RELEASE_BLOCK_HERE}} placeholder. +// 3. The Claude Agent SDK runs the rendered prompt with WebFetch + Bash so +// it can investigate PR bodies, linked issues, and changed files (the +// prompt's investigation step is real work, not boilerplate). +// 4. The agent's final assistant message is written to +// release-notes/v.md. +// 5. Unless --dry-run is passed, the script commits the file on a branch +// `release-notes/v` and opens a PR. Merging the PR triggers +// apply-release-notes.yml which pushes the file's contents to the GH +// release body. +// +// Usage: +// bun apps/cli/scripts/propose-release-notes.ts --tag v2.101.0 --dry-run +// bun apps/cli/scripts/propose-release-notes.ts --tag v2.101.0 --apply +// +// --tag Required. Stable release tag (e.g. v2.101.0). Prerelease tags +// (-beta./-alpha.) are rejected per the prompt's scope rules. +// --dry-run Print the proposed notes to stdout. Does not write any files, +// does not touch git. +// --apply Write release-notes/v.md, commit on a branch, push, +// and open a PR. Default behavior when neither flag is passed +// is `--dry-run`. +// --render-only Print the rendered prompt (template + raw notes block) +// and exit before any LLM call. Useful for prompt iteration +// and for verifying the pipeline shape without spending tokens. +// --model Optional. Override the Claude model (default: claude-opus-4-7). +import { query, type Options } from "@anthropic-ai/claude-agent-sdk"; +import { $ } from "bun"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { existsSync } from "node:fs"; +import path from "node:path"; +import process from "node:process"; +import { parseArgs } from "node:util"; + +const { values } = parseArgs({ + options: { + tag: { type: "string" }, + "dry-run": { type: "boolean", default: false }, + apply: { type: "boolean", default: false }, + "render-only": { type: "boolean", default: false }, + model: { type: "string", default: "claude-opus-4-7" }, + }, + strict: true, +}); + +const tag = values.tag; +if (!tag) { + console.error("--tag is required (e.g. --tag v2.101.0)"); + process.exit(2); +} +if (tag.includes("-beta.") || tag.includes("-alpha.")) { + console.error( + `Refusing to propose notes for ${tag}: prereleases keep the raw ` + + `semantic-release body (see tools/release/release-notes-prompt.md).`, + ); + process.exit(0); +} +const version = tag.replace(/^v/, ""); +const apply = values.apply === true && values["dry-run"] !== true; + +const repoRoot = (await $`git rev-parse --show-toplevel`.text()).trim(); +const promptPath = path.join(repoRoot, "tools/release/release-notes-prompt.md"); +const backfillScript = path.join(repoRoot, "apps/cli/scripts/backfill-release-notes.ts"); +const notesDir = path.join(repoRoot, "release-notes"); +const notesPath = path.join(notesDir, `v${version}.md`); + +console.error(`==> Re-deriving raw semantic-release notes for ${tag}`); +const rawNotes = (await $`bun ${backfillScript} --tag ${tag}`.cwd(repoRoot).text()).trim(); +if (!rawNotes) { + console.error(`backfill-release-notes produced no output for ${tag}`); + process.exit(1); +} + +const promptTemplate = await readFile(promptPath, "utf8"); +const placeholder = "{{PASTE_SEMANTIC_RELEASE_BLOCK_HERE}}"; +if (!promptTemplate.includes(placeholder)) { + console.error(`Prompt template at ${promptPath} is missing ${placeholder}`); + process.exit(1); +} +const rendered = promptTemplate.replace(placeholder, rawNotes); + +if (values["render-only"]) { + process.stdout.write(rendered); + process.exit(0); +} + +console.error(`==> Running Claude Agent SDK (model=${values.model})`); +const options: Options = { + model: values.model, + // The agent needs WebFetch / WebSearch to investigate PR bodies and linked + // issues per the prompt's step 3, and Bash so it can use `gh` for + // authenticated GitHub queries instead of HTML scraping. Edit/Write are + // intentionally excluded — the script owns the final file output. + allowedTools: ["WebFetch", "WebSearch", "Bash"], + permissionMode: "bypassPermissions", + // Don't load the repo's CLAUDE.md or settings.json — the prompt is + // self-contained and we don't want unrelated agent context bleeding in. + settingSources: [], + cwd: repoRoot, +}; + +let finalText = ""; +let cost = 0; +const stream = query({ prompt: rendered, options }); +for await (const msg of stream) { + if (msg.type === "result") { + if (msg.subtype === "success") { + finalText = msg.result; + cost = msg.total_cost_usd; + } else { + console.error(`Agent failed: ${msg.subtype}`); + if (msg.errors?.length) console.error(msg.errors.join("\n")); + process.exit(1); + } + } +} + +if (!finalText.trim()) { + console.error("Agent returned no result text"); + process.exit(1); +} + +const normalized = finalText.endsWith("\n") ? finalText : `${finalText}\n`; +console.error(`==> Agent finished (cost ~$${cost.toFixed(4)})`); + +if (!apply) { + process.stdout.write(normalized); + process.exit(0); +} + +await mkdir(notesDir, { recursive: true }); +if (existsSync(notesPath)) { + console.error( + `Refusing to overwrite existing ${path.relative(repoRoot, notesPath)}. ` + + `Delete it or rerun with --dry-run to preview.`, + ); + process.exit(1); +} +await writeFile(notesPath, normalized); +console.error(`==> Wrote ${path.relative(repoRoot, notesPath)}`); + +const branch = `release-notes/v${version}`; +const currentBranch = (await $`git rev-parse --abbrev-ref HEAD`.cwd(repoRoot).text()).trim(); +if (currentBranch !== branch) { + await $`git checkout -B ${branch}`.cwd(repoRoot); +} +await $`git add ${notesPath}`.cwd(repoRoot); +const commitMessage = `docs(release): propose user-facing notes for ${tag}`; +await $`git commit -m ${commitMessage}`.cwd(repoRoot); + +console.error(`==> Pushing ${branch}`); +let pushed = false; +for (let attempt = 0; attempt < 4; attempt++) { + const result = await $`git push -u origin ${branch}`.cwd(repoRoot).nothrow(); + if (result.exitCode === 0) { + pushed = true; + break; + } + const wait = 2 ** (attempt + 1) * 1000; + console.error(`Push failed (attempt ${attempt + 1}/4); retrying in ${wait / 1000}s`); + await new Promise((r) => setTimeout(r, wait)); +} +if (!pushed) { + console.error("git push failed after 4 attempts"); + process.exit(1); +} + +const prBody = [ + `Proposed user-facing release notes for ${tag}, generated by`, + "`apps/cli/scripts/propose-release-notes.ts` against", + "`tools/release/release-notes-prompt.md`.", + "", + "**Merging this PR will overwrite the GitHub Release body for**", + `**[${tag}](https://github.com/supabase/cli/releases/tag/${tag})**`, + "via `.github/workflows/apply-release-notes.yml`.", + "", + "Reviewers: edit the file directly to refine wording or fix omissions.", + "Close without merging if the auto-generated body should stand instead.", +].join(" "); + +await $`gh pr create --title ${`docs(release): notes for ${tag}`} --body ${prBody} --base main --head ${branch}`.cwd( + repoRoot, +); +console.error(`==> PR opened for ${branch}`); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da3dd252e..872f50fcd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -80,6 +80,12 @@ importers: apps/cli: devDependencies: + '@anthropic-ai/claude-agent-sdk': + specifier: ^0.3.146 + version: 0.3.146(@anthropic-ai/sdk@0.97.1(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3) + '@anthropic-ai/sdk': + specifier: ^0.97.1 + version: 0.97.1(zod@4.4.3) '@clack/prompts': specifier: ^1.4.0 version: 1.4.0 @@ -92,6 +98,9 @@ importers: '@effect/vitest': specifier: 'catalog:' version: 4.0.0-beta.43(effect@4.0.0-beta.67)(vitest@4.1.6) + '@modelcontextprotocol/sdk': + specifier: ^1.29.0 + version: 1.29.0(zod@4.4.3) '@napi-rs/keyring': specifier: ^1.3.0 version: 1.3.0 @@ -503,6 +512,67 @@ packages: resolution: {integrity: sha512-p+CMKJ93HFmLkjXKlXiVGlMQEuRb6H0MokBSwUsX+S6BRX8eV5naFZpQJFfJHjRZY0Hmnqy1/r6UWl3x+19zYA==} engines: {node: '>=18'} + '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.146': + resolution: {integrity: sha512-0IIvlEaenq2CRSVx5Bo5BaCtHQXS87GancM35WKEYveGVLn6DI+5G7ikYuTE4AKRPkMnogFtY4BJt6LulWGj+A==} + cpu: [arm64] + os: [darwin] + + '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.146': + resolution: {integrity: sha512-Dk5xJ03Ff1JXbMRP1t2wc/TyfY6xF/2Ysp31wMhFPjoNiKSPHMWaIg242+T3CHdxLWmJ8plWHL1HL5cyZ/LCkw==} + cpu: [x64] + os: [darwin] + + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.146': + resolution: {integrity: sha512-QlCid0ucdrmhUAOewfQjaofN2wlokWcfFTxSFePTSj1umk35JO7TDFP700F7jU49r1fPWIdvJpPwWGyB0DeFPA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.146': + resolution: {integrity: sha512-mzBXDDWWBAC/vDtAYpO1G/dq5QvJtYSPXsqcb+sNdcDhiuf4IYnYp7ytRncYlsUNDkLmX6Gk2jkWAHUUA2Lozg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.146': + resolution: {integrity: sha512-E3coK1ThQT08KIX80RLcsq7DWXFllCKOzoOe32it/bdtY56TBgPY9xemwXhIJ+cVBHTI9/MpBSIlKBcFCt+yQA==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.146': + resolution: {integrity: sha512-B2baXU1tCBT5CVlD7jJMKjpC4xdO45NUIWpqImmwuOfKvlM/PITjyTXyTY662mGZf1dBmdqBBsqirwFH/jhi8Q==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.146': + resolution: {integrity: sha512-CIwQxGX2r/yWpjCJ6ahB3smKXhghWgGTxL98+LGW52TUwqTiBnlNrH9DPqqgv1/+Hyquw6xfLrKU+StyfMgiLw==} + cpu: [arm64] + os: [win32] + + '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.146': + resolution: {integrity: sha512-qmxrsyaqA8s4HShqJls7ZCRjdoqN66Jo/hbjQNB3uHepD8tEO1iD19aPV4+osdLT7feMkhDBfLT07Q30R2NB5w==} + cpu: [x64] + os: [win32] + + '@anthropic-ai/claude-agent-sdk@0.3.146': + resolution: {integrity: sha512-hK9/Ng+hOyexUemTxdIUsSWJ9o2LFi2YNWzHwz8/YMCohUYOnFMZkBiENvUAb0WIc5hieOyBZrOIlg5OewuJMg==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@anthropic-ai/sdk': '>=0.93.0' + '@modelcontextprotocol/sdk': ^1.29.0 + zod: ^4.0.0 + + '@anthropic-ai/sdk@0.97.1': + resolution: {integrity: sha512-wOf7AUeJPitcVpvKO4UMu63mWH5SaVipkGd7OOQJt/G6VYGlV8D2Gp9dLxOrttDJh/9gqPqdaBwDGcBevumeAg==} + hasBin: true + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -558,6 +628,10 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} @@ -973,6 +1047,12 @@ packages: tailwindcss: optional: true + '@hono/node-server@1.19.14': + resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@img/colour@1.1.0': resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} @@ -1156,6 +1236,16 @@ packages: '@mdx-js/mdx@3.1.1': resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + '@modelcontextprotocol/sdk@1.29.0': + resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} cpu: [arm64] @@ -2621,6 +2711,9 @@ packages: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} + '@stablelib/base64@1.0.1': + resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -3023,6 +3116,10 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -3045,6 +3142,14 @@ packages: resolution: {integrity: sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==} engines: {node: '>=18'} + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} @@ -3198,6 +3303,10 @@ packages: resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + bottleneck@2.19.5: resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} @@ -3425,6 +3534,10 @@ packages: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} @@ -3465,6 +3578,10 @@ packages: cookie-signature@1.0.7: resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + cookie@0.7.2: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} @@ -3766,6 +3883,14 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + eventsource-parser@3.0.8: + resolution: {integrity: sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} @@ -3781,10 +3906,20 @@ packages: express-rate-limit@5.5.1: resolution: {integrity: sha512-MTjE2eIbHv5DyfuFz4zLYWxpqVhEhkTiwFGuB74Q9CSou2WHO52nlE5y3Zlg6SIsiYUIPj6ifFxnkPz6O3sIUg==} + express-rate-limit@8.5.2: + resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + express@4.22.1: resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} engines: {node: '>= 0.10.0'} + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -3809,6 +3944,9 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-sha256@1.3.0: + resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==} + fast-string-truncated-width@3.0.3: resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} @@ -3856,6 +3994,10 @@ packages: resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} engines: {node: '>= 0.8'} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + find-my-way-ts@0.1.6: resolution: {integrity: sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==} @@ -3921,6 +4063,10 @@ packages: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -4187,6 +4333,10 @@ packages: highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + hono@4.12.21: + resolution: {integrity: sha512-uV63apnb0kyPtAUwoWgaGh9HyIFcv8lgmzPZSiTBQAFOFGIzka5EZ1dZocmGnn0XdX0+XTqJ6Tqv7selMuGLRQ==} + engines: {node: '>=16.9.0'} + hook-std@4.0.0: resolution: {integrity: sha512-IHI4bEVOt3vRUDJ+bFA9VUJlo7SzvFARPNLw75pqSmAOP2HmTWfFJtPvLBrDrlgjEYXY9zs7SFdHPQaJShkSCQ==} engines: {node: '>=20'} @@ -4255,6 +4405,10 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -4322,6 +4476,10 @@ packages: resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==} engines: {node: '>=12.22.0'} + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} + engines: {node: '>= 12'} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -4393,6 +4551,9 @@ packages: is-promise@2.2.2: resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -4449,6 +4610,9 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -4473,9 +4637,16 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema-to-ts@3.1.1: + resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} + engines: {node: '>=16'} + json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} @@ -4777,6 +4948,10 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + meow@13.2.0: resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} engines: {node: '>=18'} @@ -4784,6 +4959,10 @@ packages: merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -4916,6 +5095,10 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -5024,6 +5207,10 @@ packages: resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} engines: {node: '>= 0.6'} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} @@ -5370,6 +5557,9 @@ packages: path-to-regexp@0.1.13: resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==} + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -5415,6 +5605,10 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + pkg-conf@2.1.0: resolution: {integrity: sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==} engines: {node: '>=4'} @@ -5501,6 +5695,10 @@ packages: resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} engines: {node: '>= 0.8'} + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -5685,6 +5883,10 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -5747,10 +5949,18 @@ packages: resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} engines: {node: '>= 0.8.0'} + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + serve-static@1.16.3: resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} engines: {node: '>= 0.8.0'} + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -5884,6 +6094,9 @@ packages: standard-as-callback@2.1.0: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + standardwebhooks@1.0.0: + resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==} + statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -6106,6 +6319,9 @@ packages: truncate-utf8-bytes@1.0.2: resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} + ts-algebra@2.0.0: + resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} + tsconfig-paths@4.2.0: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} @@ -6142,6 +6358,10 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + typescript@6.0.3: resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} @@ -6502,6 +6722,11 @@ packages: yoga-layout@3.2.1: resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} @@ -6518,6 +6743,52 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 + '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.146': + optional: true + + '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.146': + optional: true + + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.146': + optional: true + + '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.146': + optional: true + + '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.146': + optional: true + + '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.146': + optional: true + + '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.146': + optional: true + + '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.146': + optional: true + + '@anthropic-ai/claude-agent-sdk@0.3.146(@anthropic-ai/sdk@0.97.1(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)': + dependencies: + '@anthropic-ai/sdk': 0.97.1(zod@4.4.3) + '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) + zod: 4.4.3 + optionalDependencies: + '@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.146 + '@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.146 + '@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.146 + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.146 + '@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.146 + '@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.146 + '@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.146 + '@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.146 + + '@anthropic-ai/sdk@0.97.1(zod@4.4.3)': + dependencies: + json-schema-to-ts: 3.1.1 + standardwebhooks: 1.0.0 + optionalDependencies: + zod: 4.4.3 + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -6595,6 +6866,8 @@ snapshots: dependencies: '@babel/types': 7.29.0 + '@babel/runtime@7.29.2': {} + '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 @@ -6894,6 +7167,10 @@ snapshots: '@fumadocs/tailwind@0.0.5': {} + '@hono/node-server@1.19.14(hono@4.12.21)': + dependencies: + hono: 4.12.21 + '@img/colour@1.1.0': optional: true @@ -7046,6 +7323,28 @@ snapshots: transitivePeerDependencies: - supports-color + '@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)': + dependencies: + '@hono/node-server': 1.19.14(hono@4.12.21) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.8 + express: 5.2.1 + express-rate-limit: 8.5.2(express@5.2.1) + hono: 4.12.21 + jose: 6.2.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.4.3 + zod-to-json-schema: 3.25.2(zod@4.4.3) + transitivePeerDependencies: + - supports-color + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': optional: true @@ -8146,6 +8445,8 @@ snapshots: '@sindresorhus/merge-streams@4.0.0': {} + '@stablelib/base64@1.0.1': {} + '@standard-schema/spec@1.1.0': {} '@supabase/auth-js@2.105.4': @@ -8627,6 +8928,11 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: acorn: 8.16.0 @@ -8646,6 +8952,10 @@ snapshots: clean-stack: 5.3.0 indent-string: 5.0.0 + ajv-formats@3.0.1(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + ajv@8.17.1: dependencies: fast-deep-equal: 3.1.3 @@ -8775,6 +9085,20 @@ snapshots: transitivePeerDependencies: - supports-color + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.14.2 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + bottleneck@2.19.5: {} brace-expansion@2.1.0: @@ -9008,6 +9332,8 @@ snapshots: dependencies: safe-buffer: 5.2.1 + content-disposition@1.1.0: {} + content-type@1.0.5: {} content-type@2.0.0: {} @@ -9039,6 +9365,8 @@ snapshots: cookie-signature@1.0.7: {} + cookie-signature@1.2.2: {} + cookie@0.7.2: {} core-util-is@1.0.2: {} @@ -9364,6 +9692,12 @@ snapshots: events@3.3.0: {} + eventsource-parser@3.0.8: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.8 + execa@8.0.1: dependencies: cross-spawn: 7.0.6 @@ -9395,6 +9729,11 @@ snapshots: express-rate-limit@5.5.1: {} + express-rate-limit@8.5.2(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.2.0 + express@4.22.1: dependencies: accepts: 1.3.8 @@ -9431,6 +9770,39 @@ snapshots: transitivePeerDependencies: - supports-color + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.14.2 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + extend@3.0.2: {} extsprintf@1.3.0: {} @@ -9453,6 +9825,8 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-sha256@1.3.0: {} + fast-string-truncated-width@3.0.3: {} fast-string-width@3.0.2: @@ -9505,6 +9879,17 @@ snapshots: transitivePeerDependencies: - supports-color + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + find-my-way-ts@0.1.6: {} find-up-simple@1.0.1: {} @@ -9551,6 +9936,8 @@ snapshots: fresh@0.5.2: {} + fresh@2.0.0: {} + fs-constants@1.0.0: {} fs-extra@11.3.5: @@ -9902,6 +10289,8 @@ snapshots: highlight.js@10.7.3: {} + hono@4.12.21: {} + hook-std@4.0.0: {} hosted-git-info@7.0.2: @@ -9978,6 +10367,10 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + ieee754@1.2.1: {} ignore@5.3.2: {} @@ -10065,6 +10458,8 @@ snapshots: transitivePeerDependencies: - supports-color + ip-address@10.2.0: {} + ipaddr.js@1.9.1: {} is-alphabetical@2.0.1: {} @@ -10110,6 +10505,8 @@ snapshots: is-promise@2.2.2: {} + is-promise@4.0.0: {} + is-stream@3.0.0: {} is-stream@4.0.1: {} @@ -10155,6 +10552,8 @@ snapshots: jiti@2.7.0: {} + jose@6.2.3: {} + js-tokens@4.0.0: {} js-yaml@4.1.1: @@ -10171,8 +10570,15 @@ snapshots: json-parse-even-better-errors@2.3.1: {} + json-schema-to-ts@3.1.1: + dependencies: + '@babel/runtime': 7.29.2 + ts-algebra: 2.0.0 + json-schema-traverse@1.0.0: {} + json-schema-typed@8.0.2: {} + json-schema@0.4.0: {} json-stringify-safe@5.0.1: {} @@ -10578,10 +10984,14 @@ snapshots: media-typer@0.3.0: {} + media-typer@1.1.0: {} + meow@13.2.0: {} merge-descriptors@1.0.3: {} + merge-descriptors@2.0.0: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -10865,6 +11275,10 @@ snapshots: dependencies: mime-db: 1.52.0 + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + mime@1.6.0: {} mime@3.0.0: {} @@ -10945,6 +11359,8 @@ snapshots: negotiator@0.6.4: {} + negotiator@1.0.0: {} + neo-async@2.6.2: {} nerf-dart@1.0.0: {} @@ -11397,6 +11813,8 @@ snapshots: path-to-regexp@0.1.13: {} + path-to-regexp@8.4.2: {} + path-type@4.0.0: {} pathe@2.0.3: {} @@ -11444,6 +11862,8 @@ snapshots: pirates@4.0.7: {} + pkce-challenge@5.0.1: {} + pkg-conf@2.1.0: dependencies: find-up: 2.1.0 @@ -11525,6 +11945,13 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -11788,6 +12215,16 @@ snapshots: - '@emnapi/core' - '@emnapi/runtime' + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -11877,6 +12314,22 @@ snapshots: transitivePeerDependencies: - supports-color + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + serve-static@1.16.3: dependencies: encodeurl: 2.0.0 @@ -11886,6 +12339,15 @@ snapshots: transitivePeerDependencies: - supports-color + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + setprototypeof@1.2.0: {} sharp@0.34.5: @@ -12057,6 +12519,11 @@ snapshots: standard-as-callback@2.1.0: {} + standardwebhooks@1.0.0: + dependencies: + '@stablelib/base64': 1.0.1 + fast-sha256: 1.3.0 + statuses@2.0.1: {} statuses@2.0.2: {} @@ -12267,6 +12734,8 @@ snapshots: dependencies: utf8-byte-length: 1.0.5 + ts-algebra@2.0.0: {} + tsconfig-paths@4.2.0: dependencies: json5: 2.2.3 @@ -12298,6 +12767,12 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 + type-is@2.1.0: + dependencies: + content-type: 2.0.0 + media-typer: 1.1.0 + mime-types: 3.0.2 + typescript@6.0.3: {} uglify-js@3.19.3: @@ -12628,6 +13103,10 @@ snapshots: yoga-layout@3.2.1: {} + zod-to-json-schema@3.25.2(zod@4.4.3): + dependencies: + zod: 4.4.3 + zod@4.3.6: {} zod@4.4.3: {} diff --git a/tools/release/release-notes-prompt.md b/tools/release/release-notes-prompt.md new file mode 100644 index 000000000..c71175467 --- /dev/null +++ b/tools/release/release-notes-prompt.md @@ -0,0 +1,453 @@ +# Release Notes Generator — Prompt + +Use this prompt to generate user-centric release notes for a Supabase CLI release. +Paste the raw semantic-release block into the **Inputs** block below, then run the +rest of the file verbatim against an LLM. The output is meant to replace the +auto-generated GitHub Release body (see +`apps/cli/scripts/backfill-release-notes.ts`). + +--- + +## Inputs + +``` +REPO: supabase/cli +PRODUCT_NAME: Supabase CLI +AUDIENCE: developers using the Supabase CLI locally and in CI +TONE: clear, direct, lightly informal — no marketing fluff +``` + +**Semantic-release changelog block** (paste between the fences — this is the raw +output from semantic-release that you want to rewrite): + +``` +{{PASTE_SEMANTIC_RELEASE_BLOCK_HERE}} +``` + +Example shape: + +``` +# [2.101.0](https://github.com/supabase/cli/compare/v2.100.1...v2.101.0) (2026-05-21) +### Bug Fixes +* alias and telemetry docs link ([#5301](...)) ([efb0949](...)) +* **ci:** drop co-incident tags in backfill-release-notes ([#5321](...)) ... +### Features +* **cli:** Add --no-apply flag for db schema declarative sync ([#5220](...)) ... +``` + +--- + +## Role + +You are a senior developer-relations engineer writing release notes for **Supabase CLI**. +Your audience is **developers using the Supabase CLI locally and in CI**. Your job +is to translate a list of merged PRs into notes that answer three questions for +the reader: + +1. **Should I upgrade?** (What do I gain? What might break?) +2. **What can I do now that I couldn't before?** +3. **What gotchas should I know about?** + +You are not summarizing PR titles. You are explaining changes in terms of the +user's workflow. + +--- + +## Supabase CLI scope rules + +These rules are specific to this repo. Apply them before the generic process +below — they decide what is even eligible to appear in the notes. + +### `next/` shell is invisible to users + +The repo has two CLI shells under `apps/cli/src/`: + +- `legacy/` — the strict 1:1 TypeScript port of the Go CLI. This is what users + invoke today as `supabase`. **All user-facing behavior lives here.** +- `next/` — the new CLI experience (v3 / alpha channel). Not yet a user-facing + product. + +**Exclude anything that only touches `next/` from the changelog entirely.** That +includes new `next/` commands, `next/` flags, `next/` refactors, `next/` tests, +and `next/`-only fixes. These changes do not count toward the tail-line total +either — drop them silently. The same applies to PRs scoped purely to alpha-channel +plumbing for `next/` distribution. + +If a PR touches both `legacy/` (or `shared/`) and `next/`, write the entry only +about the user-facing `legacy/`/`shared/` impact. Do not mention `next/` at all. + +### Go-to-TypeScript port: keep it minimal + +This release cycle includes ongoing work to port the Go CLI to TypeScript +(`apps/cli-go/` → `apps/cli/src/legacy/`). Most port PRs are not user-visible — +the contract is "behave exactly like the Go CLI". Do not describe these as +features, improvements, or fixes. + +If one or more PRs port Go commands to the TypeScript shell, collapse them into +a **single short line** under a dedicated section, listing only the commands +that were ported: + +> **TypeScript port progress** — `db diff`, `orgs list`, and `projects list` are +> now served by the native TypeScript implementation. Behavior and output match +> the Go CLI. (#5301, #5302, #5303) + +Rules for that line: + +- List only the leaf commands (`db diff`, not "the db group"). +- Do not list internal infrastructure PRs that support the port (service layers, + shared helpers, test scaffolding, parity scripts) — those are tail-line + internal work. +- If a port PR *also* fixes a real Go-CLI bug or adds a flag that wasn't in Go, + promote that part to a proper Bug fixes / New features entry; the port line + still mentions the command. +- If no commands were ported in this release, omit the section entirely. + +### Where the real user-facing changes live + +User-facing changes for Supabase CLI typically show up as: + +- PRs that change `apps/cli/src/legacy/commands/**` in a way that alters + behavior, output, flags, or error messages (not just porting). +- PRs that change `apps/cli/src/shared/**` in a way that legacy commands inherit + (e.g. output format, telemetry, global flags). +- PRs against `apps/cli-go/**` while that tree is still the production binary. +- PRs that change packaging, install paths, or platform support in + `packages/cli-*` or `apps/cli/scripts/` (homebrew, scoop, build). + +Anything else is almost always internal. + +--- + +## Process + +Follow these steps in order. Do not skip the investigation steps — release notes +built from titles alone are exactly what we're trying to avoid. + +### 1. Parse the input + +Parse the pasted semantic-release block to extract: + +- The **version** and **compare URL** from the header line + (e.g. `v2.100.1...v2.101.0`). +- The **list of PRs**, each with: title, conventional-commit prefix and scope + (e.g. `fix(cli):`, `feat:`, `chore:`), PR number, and PR URL. + +Treat the semantic-release section headers (`### Bug Fixes`, `### Features`) as +**hints, not authority**. They reflect the commit type, not user impact. Many +`fix:` commits are internal, and the eventual grouping is your decision based on +investigation. + +### 2. Apply the prefix triage (fast first pass) + +Before fetching anything, use the conventional-commit prefix to set an initial +expectation. This saves work — some PRs you can confidently skip without opening +them. + +| Prefix pattern | Default action | Notes | +| ------------------------------------ | ----------------------------------------- | ---------------------------------------------------------------- | +| `chore:`, `chore(*):` | **Skip**, compress to tail | Open only if title hints at user impact. | +| `ci:`, `*(ci):` | **Skip**, compress to tail | Internal pipeline work. | +| `test:`, `*(test):` | **Skip**, compress to tail | Unless it's a user-visible test helper. | +| `docs:` | Skip unless it's a docs site/page users read directly | README/in-CLI help changes can be worth noting. | +| `refactor:`, `style:`, `perf:` | Open and judge | `perf:` is often user-visible; the other two rarely are. | +| `fix:`, `fix():` | **Open and investigate** | Product scopes like `cli`, `db`, `auth`. Default to user-facing. | +| `feat:`, `feat():` | **Open and investigate** | Default to user-facing. | +| `feat!:`, `fix!:`, `BREAKING CHANGE` | **Open, investigate, flag as breaking** | Always called out separately. | + +Tail-compressed PRs (`chore`, `ci`, `test`, most `refactor`/`style`) still get +counted — they roll up into the "Plus N internal improvements…" line at the +bottom. **Exception:** PRs scoped purely to `next/` do not count toward the +tail line — drop them silently (see Supabase CLI scope rules above). + +### 3. Investigate each remaining PR + +For every PR that survived the triage, open its GitHub URL (from the input) and +gather context **before** writing about it: + +- Read the **PR body**, not just the title. Titles are written for reviewers; + bodies often have the actual rationale. +- Read **linked issues** (look for `Closes #`, `Fixes #`, `Refs #` in body, and + the `closes #...` suffix that semantic-release sometimes inlines). Issues tend + to have the user-side problem statement. +- Skim the **files changed** to sanity-check scope. Titles lie — "small fix" PRs + sometimes add real features and vice versa. A `fix(cli):` that only touches + `.github/workflows/` is internal, regardless of label. **A PR that only + touches `apps/cli/src/next/` is not user-facing — drop it.** +- Check **labels** on the PR (`breaking-change`, `bug`, `feature`, `chore`, + `internal`). +- Look for **`!` in the prefix** or `BREAKING CHANGE:` in body/footer — these + mark breaking changes. + +If a PR's purpose is unclear after reading the body and any linked issue, +**flag it** in your output rather than guessing. A +`` comment is better than fabricated +context. + +### 4. Apply the user-relevance gate + +For each investigated PR, ask: **would a user of Supabase CLI notice this change +in their workflow, output, errors, or available commands/flags?** + +- **Yes** → write an entry (see step 5). +- **No** → drop it into the tail-line count. Examples of "no" even when the + prefix looks user-facing: + - `fix(cli): inject Sentry DSN at build time` — release-engineering plumbing, + invisible to users. + - `fix(cli): stabilize smoke tests with docker caching` — CI hygiene, + invisible to users. + - `fix(cli): refactor command registration` — internal even though scoped + `cli`. + - Anything that only modifies `apps/cli/src/next/**` — `next/` is not a + user-facing product (drop silently, do not count in the tail). +- **Borderline** (e.g. a build-system fix that incidentally makes `--version` + report correctly) → write a one-liner, don't promote to highlights. + +### 5. Classify and group + +Bucket each PR into one of: + +- **Highlights** — 1–4 changes worth leading with. Either a meaningful new + capability, a fix for a widely-hit pain point, or a breaking change. +- **New features** — user-visible additions. +- **Improvements** — UX, performance, output quality, error messages. +- **Bug fixes** — user-visible fixes. Include enough context that someone hit by + the bug recognizes it. +- **Breaking changes** — anything that requires the user to do something. Always + called out separately, even if there's only one. +- **TypeScript port progress** — see scope rules above. One short line listing + the leaf commands ported in this release. Omit if none. +- **Internal / chore** — refactors, test-only changes, CI, dep bumps with no + user-visible effect. **Compress to a single line at the bottom** ("Plus N + internal improvements and dependency updates."). Do not list individually. + +**Group related PRs.** If three PRs together ship one feature (e.g. flag added, +docs updated, edge case fixed), write **one entry** that covers all three and +reference each PR. + +### 6. Write each entry + +Each entry follows this shape: + +> **** — (#1234) + +Voice rules: + +- **Second person, active voice.** "You can now…" not "Added support for…". +- **Lead with the user benefit, not the implementation.** "Schema diffs now work + offline" beats "Added pglite backend to db diff". +- **Be concrete.** Mention the actual command, flag, env var, or behavior the + user will touch. +- **Show, don't tell.** A one-line example (`supabase db diff --schema public,auth`) + is worth a paragraph of description. +- **Cut hedges and filler.** No "We're excited to announce…", no "improved + various aspects of…". +- **One PR link per entry minimum.** Multiple if grouped. +- **Never mention `next/`, the v3 shell, or alpha-channel work.** Those are + not part of the user-facing product yet. + +For **bug fixes**, describe the symptom the user would have seen, not the +internal cause: + +- ❌ "Fixed nil pointer in `resolveProjectRef` when config.toml is missing `[db.pooler]`" +- ✅ "`supabase start` no longer crashes when `config.toml` is missing a `[db.pooler]` block (#5012)" + +For **breaking changes**, always include: + +- What's breaking +- Who's affected (everyone? only users of flag X?) +- The exact migration step + +### 7. Write the intro + +A 1–3 sentence opener that names the headline of the release. If the release is +mostly fixes, say so. If it ships one big thing, lead with that. If it's a +grab-bag, say "This release brings X, Y, and Z" — don't pretend there's a theme +when there isn't. Do not advertise port progress in the intro unless an entire +command surface meaningfully changed for users. + +--- + +## Output format + +The version number and compare URL come from the header line of the pasted +block (e.g. `# [2.101.0](https://github.com/supabase/cli/compare/v2.100.1...v2.101.0) (2026-05-21)`). +Extract `VERSION`, `COMPARE_URL`, and `DATE` from there. + +```markdown +## Supabase CLI v + +<1–3 sentence intro that gives the reader the gist before they scroll.> + +### ⚠️ Breaking changes + + + +- **** — . (#1234) + +### Highlights + +- **** — . (#1234) + +### New features + +- **** — . (#1234) + +### Improvements + +- . (#1234) + +### Bug fixes + +- . (#1234) + +### TypeScript port progress + +- **Now served by the TypeScript shell:** ``, ``. Behavior matches the Go CLI. (#1234, #1235) + +--- + +Plus N internal improvements and dependency updates. + +**Full changelog:** +``` + +Omit empty sections. If there are no breaking changes, no breaking-changes +section. If there are no new features, no new-features section. If no Go +commands were ported, no TypeScript-port-progress section. + +--- + +## Worked examples (PR → entry) + +These show the transformation from raw PR data to a good entry. + +**Example 1 — feature PR** + +PR title: `feat(db): add --linked flag to db diff` +PR body: "Allows running `db diff` against the linked remote project without +spinning up a local Docker stack." + +❌ Bad: *Added `--linked` flag to `db diff`. (#4567)* + +✅ Good: ***`db diff` against your linked project, no Docker required*** — *Pass +`--linked` to diff your local migrations against the remote project directly. +Useful in CI where Docker isn't available. (#4567)* + +**Example 2 — bug fix PR** + +PR title: `fix: handle empty config.toml sections` +Linked issue: "`supabase start` panics with `nil pointer dereference` if +`[db.pooler]` is missing." + +❌ Bad: *Fixed nil pointer in config parser. (#5012)* + +✅ Good: *`supabase start` no longer crashes when `config.toml` is missing +optional sections like `[db.pooler]`. (#5012)* + +**Example 3 — grouped PRs** + +Three PRs: `feat: add --json to db lint`, `docs: document --json on db lint`, +`fix: --json output for db lint when no issues found` + +❌ Bad: three separate bullets repeating "db lint --json". + +✅ Good: ***`db lint` now supports machine-readable output*** — *Pass `--json` +to get lint results as JSON, suitable for piping into CI checks or other tools. +Returns an empty array when there are no issues. (#4801, #4815, #4823)* + +**Example 4 — chore PR (do NOT write an entry)** + +PR title: `chore: bump golang.org/x/net to 0.27.0` + +This goes into the "Plus N internal improvements and dependency updates" tail +line. No bullet. + +**Example 5 — breaking change** + +PR title: `feat!: remove deprecated --legacy-postgres flag` + +✅ ***Removed `--legacy-postgres` flag*** — *If you were still passing +`--legacy-postgres` to `supabase start` or `supabase db reset`, remove it; the +flag has been a no-op since v1.180 and now errors. (#4990)* + +**Example 6 — `fix(cli):` that's actually internal** + +PR title: `fix(cli): inject Sentry DSN and PostHog credentials into Go binary` +PR body: "We were reading these from env at runtime, which meant they were +empty in distributed binaries. Inject at build time via ldflags instead." + +This passes the prefix triage (`fix(cli):` → investigate) but **fails the +user-relevance gate** — it's release-engineering plumbing. No bullet. Counts +toward the tail line. The lesson: scope `cli` is not a guarantee of user impact. + +**Example 7 — borderline build-system fix worth a one-liner** + +PR title: `fix(cli): inject version into Go binary via ldflags` +PR body: "Closes #5308. `supabase --version` was reporting `dev` in published +binaries." + +Borderline: the cause is build-system, but the user-visible effect is real. +One-liner under Bug fixes, no promotion to highlights. + +✅ *`supabase --version` now reports the actual release version instead of +`dev`. (#5313)* + +**Example 8 — Go-to-TS port PR** + +PR title: `feat(cli): port db diff to TypeScript` +PR body: "Replaces the Go-binary proxy for `db diff` with a native TS +implementation. Output and flags unchanged." + +This is not a feature or a fix from the user's point of view — behavior is +identical to before. Do **not** describe it as new functionality. Roll it into +the dedicated port-progress line: + +✅ *Under **TypeScript port progress**: `db diff` is now served by the native +TypeScript implementation. Behavior and output match the Go CLI. (#5314)* + +**Example 9 — `next/`-only PR (drop entirely)** + +PR title: `feat(next): add new branches list command to the v3 shell` +Files changed: only under `apps/cli/src/next/`. + +`next/` is not a user-facing product yet. **Do not mention this PR at all** — +no bullet, no tail-line count, no port-progress entry. Silently drop. + +**Example 10 — port PR that also fixes a real bug** + +PR title: `feat(cli): port orgs list to TypeScript, fix pagination when >100 orgs` +PR body: Ports the command and, while doing so, fixes a long-standing +pagination bug where users with more than 100 orgs only saw the first page. + +Promote the bug-fix half to its own entry under Bug fixes, and still list the +command under TypeScript port progress: + +✅ Under **Bug fixes**: *`supabase orgs list` now returns every org you belong +to, not just the first 100. (#5318)* + +✅ Under **TypeScript port progress**: `orgs list`. (#5318) + +--- + +## What to avoid + +- Listing PR titles verbatim. +- Writing for the developer who wrote the code instead of the developer who + uses the tool. +- Burying breaking changes in a fixes section. +- "Various improvements and bug fixes." Either name them or cut them. +- Marketing language ("excited", "thrilled", "powerful", "robust"). +- Speculating about intent. If a PR is unclear, flag it for human review rather + than guessing. +- Describing port PRs as new features or improvements. Behavior is unchanged + by definition — list the command under TypeScript port progress and move on. +- Mentioning `next/`, the v3 shell, or alpha-channel work anywhere in the + user-facing notes. + +--- + +## Output + +Now generate the release notes for **supabase/cli** based on the pasted +semantic-release block, following all of the above. The result replaces the +pasted block — it does not extend it.