Skip to content

ci(config): add independent release automation for @supabase/config (CLI-2233) - #6381

Merged
Coly010 merged 4 commits into
developfrom
columferry/cli-2233-set-up-independent-release-automation-for-supabaseconfig
Sep 1, 2026
Merged

ci(config): add independent release automation for @supabase/config (CLI-2233)#6381
Coly010 merged 4 commits into
developfrom
columferry/cli-2233-set-up-independent-release-automation-for-supabaseconfig

Conversation

@Coly010

@Coly010 Coly010 commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Closes CLI-2233

Stacked on #6366 (compiled build + trimmed surface) — review only the top three commits until that merges.

What changed

@supabase/config gets its own release train, fully independent of the CLI's semantic-release flow: .github/workflows/release-config.yml, triggered only by pushes to develop touching packages/config/**.

Version computation (plan job)

  • A second semantic-release instance (catalog-pinned devDep, driven through the JS API by packages/config/scripts/release-plan.ts) with tagFormat: config-v${version} — semantic-release's tag matching is anchored, so the config-v* and CLI v* trains can't see each other's tags (verified in both directions against the installed v25 source).
  • Commit analysis is path-filtered to packages/config/** by an in-repo plugin (scripts/semantic-release-path-filter.ts): one batched git diff-tree --stdin -r --root --name-only -z call, then delegation to the real @semantic-release/commit-analyzer/release-notes-generator. semantic-release-monorepo was considered and rejected (last published Feb 2024, unproven against v25's ESM internals); the wrapper is small and unit-tested, including the case that justifies its existence — a breaking change outside packages/config/ must not produce a major — plus merge-commit, root-commit (--root), and non-ASCII-path (-z vs core.quotePath) edges.
  • The plan refuses to run without a baseline config-v* tag: with none, semantic-release would cut 1.0.0 with notes generated from the entire monorepo history — a whole-history changelog as the approval artifact. Escape hatch: CONFIG_RELEASE_ALLOW_NO_BASELINE=1.
  • While private: true (until CLI-2169 flips it), the plan job still runs everything — version, gate, pack — and reports blocked_on_private; every config push to develop rehearses the plan half of the train while publishing nothing.

The release-time hard gate (deferred here from #6366)

tools/config-release-gate.ts diffs the freshly built dist/**/*.d.ts against the previously published npm tarball's declarations and writes the diff to the job summary, with semver-sanity warnings (surface changed on a patch bump; a .d.ts file removed, or declaration lines removed from an existing one, on a non-major bump). It shares its diff machinery with the PR-time advisory compare via a new tools/lib/dts-diff.ts (behavior-preserving extraction from tools/config-api-compare.ts, verified byte-identical output). The gate itself never fails on a diff — the human approving the config-release environment, with the plan job's summary in front of them, IS the gate. Handled explicitly: first publish (E404, parsed from npm view --json, not substring-matched), a published tarball with no dist/ (warns instead of blocking every subsequent release), an unparseable published version (bump class degrades to "unknown" instead of exit 2). Downloaded tarballs are verified against the registry's dist.integrity, refused if their URL points off-registry, and extracted with --no-same-owner --no-same-permissions.

Publish job — the approved bytes are the published bytes

The plan job packs the tarball the gate evidence describes and uploads it as a workflow artifact; the publish job downloads that exact tarball, verifies it (dist/index.js present — the .gitignore/packlist trap, name/version match, private not true), and publishes its extracted content with pnpm publish --provenance --tag latest --no-git-checks. No pnpm install and no build run in the job that holds id-token: write — the only repo code it executes is the workflow file itself. The rest mirrors release-shared.yml's hardened posture: github-hosted runner (npm provenance rejects self-hosted), OIDC trusted publishing (no NPM_TOKEN), idempotent registry probe and idempotent config-v* tag push, GitHub Release from the semantic-release notes with make_latest: "false" so a config release can never hijack the releases/latest/download/... URLs the CLI install scripts and setup-cli resolve.

Approval-evidence integrity: the plan job asserts the config-release environment actually has a required-reviewers rule before a real release can reach the publish job (auto-created environments have no protection rules); semantic-release's commit-derived stdout is bracketed with ::stop-commands:: so a crafted commit message can't issue workflow commands (e.g. ::add-mask:: over the gate output); and everything commit- or registry-derived that lands in the step summary is rendered inside dynamically-sized fences with HTML-escaped paths, so a fenced code block in a JSDoc comment or PR title can't forge the approver's evidence.

package.json's version is never committed; it's set at pack time from the computed version, same convention as the CLI's own train.

Decisions (flagging for override)

  • semantic-release, not changesets — one release philosophy per repo; the commit discipline (conventional commits, squash-merge) already carries the signal.
  • Single channel: stable-only from develop, npm dist-tag latest, no beta/alpha and no git-notes channel machinery. The CLI's three channels exist for binary distribution rings; a library doesn't need them, and adding a prerelease branch later is additive.
  • No release-notes rewrite stage — raw path-filtered commit-analyzer notes go straight to the GH release; the CLI's Claude-rewrite pipeline is product-flavored and stays CLI-only.
  • No brew/scoop/smoke/install-verify jobs — nothing here ships binaries.
  • No version input on workflow_dispatch — the registry probe makes re-runs idempotent, and with no binary artifacts plus a human approval in the loop, the CLI's cut-forward escape hatch isn't worth a second code path; recovery is "land a new releasable commit".

One-time setup before the first real publish (CLI-2169)

Documented in packages/config/AGENTS.md → "Releases":

  1. Required reviewers on the config-release environment (the plan job fails closed until this exists).
  2. npm trusted publishing needs the package to exist: first publish is a manual bootstrap with a granular single-package short-expiry token (revoked immediately after), then the trusted publisher is set to repo supabase/cli, workflow release-config.yml, environment config-release.
  3. Push a baseline tag (e.g. config-v0.1.0) — required, see above.
  4. A repository tag ruleset protecting config-v* (the last tag is the version oracle).

Also here: npm-page README pass (CLI-2169, second commit)

Quickstart moved to the top with a pure-entrypoint example, the remaining relative links made absolute, the semver section now describes the live release pipeline instead of "planned under CLI-2233", Development section labeled contributor-facing, License section added, no bare issue IDs left.

Review

Engineer and security reviews both ran before this PR; every blocking and medium finding is fixed in the third commit (artifact handoff, gate-armed assertion, markdown-fence injection, -z path quoting, dist-less tolerance, baseline guard, tarball integrity/origin checks, stop-commands bracketing, version-shape validation, notes trailing-newline). Noted but deliberately not done:

  • The CLI's own train still analyzes config commits (a feat(config): bumps the CLI version too, as it always has) — the "vice versa" half of the filtering would change the CLI's established release behavior and deserves its own issue.
  • The publish half stays unexercised until private flips — the first real publish is also the first execution of the OIDC path, which is why the bootstrap in AGENTS.md is manual and deliberate.
  • ~/.releaserc leakage into local release-plan.ts runs (cosmiconfig searches up to $HOME): harmless in CI, where no such file exists.

@Coly010
Coly010 force-pushed the columferry/cli-2233-set-up-independent-release-automation-for-supabaseconfig branch from 45aef83 to dfdfdeb Compare August 30, 2026 13:35
@Coly010
Coly010 force-pushed the columferry/cli-2234-audit-and-trim-supabaseconfigs-public-export-surface-before branch from 9872825 to 0aeac52 Compare September 1, 2026 09:01
@Coly010
Coly010 force-pushed the columferry/cli-2233-set-up-independent-release-automation-for-supabaseconfig branch from dfdfdeb to d8fccba Compare September 1, 2026 09:06
@Coly010

Coly010 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

/ai-review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superseded by a newer AI review

🤖 AI Review

All 16 raw findings were verified. One overlapping publish-boundary finding was merged, leaving 15 confirmed findings: 1 critical, 4 major, 1 minor, and 9 nits. The principal issues are lifecycle-code execution in the OIDC publish job, republishing bytes other than the reviewed tarball, an unreadable environment-gate API call, unsafe stale-publish recovery, and type-gate evidence generated from a different tree than the packed artifact.

Findings

Severity Location Category Sources Claim
🔴 CRITICAL .github/workflows/release-config.yml:253 supply-chain-security claude+codex Publishing from the extracted directory repacks the package and permits publish lifecycle scripts, violating both the reviewed-byte guarantee and the stated boundary that package code cannot execute in the OIDC-enabled job.
🟠 MAJOR .github/workflows/release-config.yml:156 ci-correctness claude The approval-gate assertion uses a contents-only GITHUB_TOKEN to query a repository-administration endpoint, so real releases will fail closed with a misleading “required_reviewers rule missing” message.
🟠 MAJOR .github/workflows/release-config.yml:250 release-recovery codex When the computed npm version already exists, the workflow can tag the current commit without verifying that the existing registry tarball matches the reviewed artifact.
🟠 MAJOR .github/workflows/release-config.yml:109 artifact-integrity codex The approval evidence is generated from the build directory before packaging, rather than from the tarball subsequently uploaded for publication.
🟠 MAJOR tools/config-release-gate.ts:449 release-gate codex If the published package contains no declarations, the gate returns success without presenting the next release’s declaration surface for approval.
🟡 MINOR tools/config-release-gate.ts:280 correctness claude Version classification does not detect equal or decreasing versions, so tag or registry skew can produce no version-order warning even when the declaration surface changed.
⚪ NIT packages/config/README.md:51 documentation claude The installation section refers to “Entrypoints above,” although the Entrypoints section now appears below it.
⚪ NIT packages/config/README.md:25 documentation claude The new quickstart is not self-contained: it references an undefined someCliConfig value and imports CliConfigSchema without demonstrating its use.
⚪ NIT packages/config/AGENTS.md:152 documentation claude The release documentation says package.json’s version field is never committed, but the package manifest commits version 0.1.0.
⚪ NIT .github/workflows/release-config.yml:90 ci-correctness claude If the release-plan command fails, set -e exits before workflow-command processing is resumed.
⚪ NIT tools/config-release-gate.ts:295 correctness claude Removed diff content beginning with two hyphens is mistaken for the unified-diff old-file header and omitted from removal detection.
⚪ NIT tools/lib/dts-diff.ts:157 robustness claude The summary-size protection caps each file independently but does not cap the complete GitHub step summary, so sufficiently many changed declaration files can still exceed the service limit.
⚪ NIT packages/config/scripts/semantic-release-path-filter.ts:110 robustness claude The commit parser silently fails for abbreviated hashes because its generic API does not require full object IDs while header recognition requires exact equality with git’s output.
⚪ NIT packages/config/scripts/release-plan.ts:52 type-safety codex The release planner trusts parsed package metadata through a TypeScript cast instead of validating the runtime value.
⚪ NIT tools/config-api-compare.ts:351 documentation claude The PR-time comparison tool and CI workflow still describe the release-time hard gate as merely planned under CLI-2233, despite this PR implementing it.

Findings outside the diff

  • ⚪ NIT tools/config-api-compare.ts:351 — The PR-time comparison tool and CI workflow still describe the release-time hard gate as merely planned under CLI-2233, despite this PR implementing it.

Stats

Claude findings: 11 · Codex findings: 5 · Confirmed: 15 · Refuted: 0 · Uncertain: 0


Models: claude-opus-5 + gpt-5.6-sol · Trigger: manual · Workflow run

This review runs once per PR. A maintainer can request another with a /ai-review comment.

Comment thread .github/workflows/release-config.yml Outdated
Comment thread .github/workflows/release-config.yml Outdated
Comment thread tools/config-release-gate.ts
Comment thread packages/config/README.md Outdated
Comment thread packages/config/README.md Outdated
Comment thread packages/config/scripts/semantic-release-path-filter.ts
Comment thread .github/workflows/release-config.yml
Comment thread .github/workflows/release-config.yml Outdated
Comment thread tools/config-release-gate.ts Outdated
Comment thread packages/config/scripts/release-plan.ts Outdated
@Coly010
Coly010 force-pushed the columferry/cli-2233-set-up-independent-release-automation-for-supabaseconfig branch from d8fccba to e7473b1 Compare September 1, 2026 09:33
@Coly010

Coly010 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

All 15 AI-review findings adjudicated and addressed in 90f6728 (all accepted; the approval-gate token finding accepted for its error-conflation half — the environments read endpoint needs only repo read access, but API failures were being misreported as a missing rule).

Highlights: the publish job now ships the reviewed tarball itself (npm publish <tgz> --ignore-scripts via pinned setup-node; no repack, no lifecycle scripts in the OIDC job), its recovery path integrity-checks the registry against the reviewed artifact before tagging, and the plan job packs before gating so the type-surface evidence comes from inside the packed tarball.

The finding outside the diff (stale "planned release-time hard gate" wording in tools/config-api-compare.ts and test.yml) is fixed in the same commit — both now point at tools/config-release-gate.ts/release-config.yml as the implemented gate. Per-thread details on each inline comment; all threads resolved.

Base automatically changed from columferry/cli-2234-audit-and-trim-supabaseconfigs-public-export-surface-before to develop September 1, 2026 12:16
…review (CLI-2233)

The publish job now publishes the exact tarball the approver reviewed
(artifact handoff; no install or build in the job holding id-token), the
plan job asserts the config-release environment has required reviewers
before a real release, commit-derived text is fenced/bracketed out of the
approval evidence, registry tarballs are integrity- and origin-checked,
the commit filter is -z clean for non-ASCII paths, a missing baseline
config-v* tag fails the plan loudly, and a dist-less published tarball
degrades to a warning instead of blocking every release.
…233)

The publish job now publishes the reviewed tarball ITSELF via npm publish
<tgz> --ignore-scripts (never a repack of the extracted tree, never with
lifecycle scripts while id-token: write is live; setup-node pins npm >= 11.5.1
for OIDC trusted publishing, replacing mise), and its already-published
recovery path verifies the registry's dist.integrity against the reviewed
artifact before skipping to the tag push. The plan job packs before gating so
the type-surface evidence is generated from the declarations inside the packed
tarball (--local-dist), the approval-gate assertion distinguishes an API/token
read failure from a genuinely missing required_reviewers rule, and the
stop-commands resume token is emitted from an EXIT trap.

Gate: a declaration-less published tarball now diffs against an empty tree
(full surface shown as additions instead of skipping), equal/lower next
versions get their own skew warnings, and removed-line detection is
hunk-aware. dts-diff caps the aggregate step-summary size, the path filter
refuses abbreviated hashes (diff-tree echoes full OIDs), release-plan
validates package.json instead of casting, and the README/AGENTS/api-compare
docs are corrected (self-contained quickstart, Entrypoints "below", version
placeholder wording, implemented-gate references).
@Coly010
Coly010 force-pushed the columferry/cli-2233-set-up-independent-release-automation-for-supabaseconfig branch from 90f6728 to f93cce0 Compare September 1, 2026 12:22
@Coly010
Coly010 marked this pull request as ready for review September 1, 2026 12:24
@Coly010
Coly010 requested a review from a team as a code owner September 1, 2026 12:24
@Coly010 Coly010 self-assigned this Sep 1, 2026
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Supabase CLI preview

npx --yes https://pkg.pr.new/supabase/cli/supabase@f93cce021abcd86d838a272e4a4eda30cb61b64a

Preview package for commit f93cce0.

@Coly010
Coly010 added this pull request to the merge queue Sep 1, 2026
Merged via the queue into develop with commit ed81a1c Sep 1, 2026
43 checks passed
@Coly010
Coly010 deleted the columferry/cli-2233-set-up-independent-release-automation-for-supabaseconfig branch September 1, 2026 12:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants