ci(config): add independent release automation for @supabase/config (CLI-2233) - #6381
Conversation
45aef83 to
dfdfdeb
Compare
9872825 to
0aeac52
Compare
dfdfdeb to
d8fccba
Compare
|
/ai-review |
There was a problem hiding this comment.
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.
d8fccba to
e7473b1
Compare
|
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 ( The finding outside the diff (stale "planned release-time hard gate" wording in |
…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).
90f6728 to
f93cce0
Compare
Supabase CLI previewnpx --yes https://pkg.pr.new/supabase/cli/supabase@f93cce021abcd86d838a272e4a4eda30cb61b64aPreview package for commit |
Closes CLI-2233
Stacked on #6366 (compiled build + trimmed surface) — review only the top three commits until that merges.
What changed
@supabase/configgets its own release train, fully independent of the CLI's semantic-release flow:.github/workflows/release-config.yml, triggered only by pushes todeveloptouchingpackages/config/**.Version computation (plan job)
packages/config/scripts/release-plan.ts) withtagFormat: config-v${version}— semantic-release's tag matching is anchored, so theconfig-v*and CLIv*trains can't see each other's tags (verified in both directions against the installed v25 source).packages/config/**by an in-repo plugin (scripts/semantic-release-path-filter.ts): one batchedgit diff-tree --stdin -r --root --name-only -zcall, then delegation to the real@semantic-release/commit-analyzer/release-notes-generator.semantic-release-monorepowas 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 outsidepackages/config/must not produce a major — plus merge-commit, root-commit (--root), and non-ASCII-path (-zvscore.quotePath) edges.config-v*tag: with none, semantic-release would cut1.0.0with notes generated from the entire monorepo history — a whole-history changelog as the approval artifact. Escape hatch:CONFIG_RELEASE_ALLOW_NO_BASELINE=1.private: true(until CLI-2169 flips it), the plan job still runs everything — version, gate, pack — and reportsblocked_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.tsdiffs the freshly builtdist/**/*.d.tsagainst 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.tsfile 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 newtools/lib/dts-diff.ts(behavior-preserving extraction fromtools/config-api-compare.ts, verified byte-identical output). The gate itself never fails on a diff — the human approving theconfig-releaseenvironment, with the plan job's summary in front of them, IS the gate. Handled explicitly: first publish (E404, parsed fromnpm view --json, not substring-matched), a published tarball with nodist/(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'sdist.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.jspresent — the.gitignore/packlist trap, name/version match,privatenot true), and publishes its extracted content withpnpm publish --provenance --tag latest --no-git-checks. Nopnpm installand no build run in the job that holdsid-token: write— the only repo code it executes is the workflow file itself. The rest mirrorsrelease-shared.yml's hardened posture: github-hosted runner (npm provenance rejects self-hosted), OIDC trusted publishing (no NPM_TOKEN), idempotent registry probe and idempotentconfig-v*tag push, GitHub Release from the semantic-release notes withmake_latest: "false"so a config release can never hijack thereleases/latest/download/...URLs the CLI install scripts and setup-cli resolve.Approval-evidence integrity: the plan job asserts the
config-releaseenvironment 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'sversionis never committed; it's set at pack time from the computed version, same convention as the CLI's own train.Decisions (flagging for override)
develop, npm dist-taglatest, 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.versioninput 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":config-releaseenvironment (the plan job fails closed until this exists).supabase/cli, workflowrelease-config.yml, environmentconfig-release.config-v0.1.0) — required, see above.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,
-zpath quoting, dist-less tolerance, baseline guard, tarball integrity/origin checks, stop-commands bracketing, version-shape validation, notes trailing-newline). Noted but deliberately not done: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.privateflips — 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.~/.releasercleakage into localrelease-plan.tsruns (cosmiconfig searches up to$HOME): harmless in CI, where no such file exists.