Skip to content

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

Draft
Coly010 wants to merge 3 commits into
columferry/cli-2234-audit-and-trim-supabaseconfigs-public-export-surface-beforefrom
columferry/cli-2233-set-up-independent-release-automation-for-supabaseconfig
Draft

ci(config): add independent release automation for @supabase/config (CLI-2233)#6381
Coly010 wants to merge 3 commits into
columferry/cli-2234-audit-and-trim-supabaseconfigs-public-export-surface-beforefrom
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.

…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.
@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
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.

1 participant