From 82aec4b2eeab00ab95ce5aeae9a12def24c6e67f Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Tue, 1 Sep 2026 14:11:58 +0100 Subject: [PATCH 1/3] feat(config): publish @supabase/config to npm (CLI-2169) Flips packages/config to private: false so the release pipeline's publish half activates, and drops the README's not-yet-published caveat. On merge, this commit is the first releasable commit since the config-v0.0.0 baseline tag, so the Release Config workflow will plan 0.1.0 as the package's first real release, pending config-release environment approval. --- packages/config/README.md | 3 +-- packages/config/package.json | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/config/README.md b/packages/config/README.md index 349e01abd3..17eacd3268 100644 --- a/packages/config/README.md +++ b/packages/config/README.md @@ -30,8 +30,7 @@ import { getDefaultCliConfig, toProjectConfig } from "@supabase/config"; const projectConfig = toProjectConfig({ cliConfig: getDefaultCliConfig() }); ``` -This package is not yet published (`private: true`; publishing is tracked separately). Once it -is, install it alongside the peers your runtime needs. +Install it alongside the peers your runtime needs. This package requires Effect 4.x, currently only published under the `rc` dist-tag — `effect@latest` still resolves to 3.x, which will not satisfy this package's peer range. diff --git a/packages/config/package.json b/packages/config/package.json index 0d05cf2f90..24464c26c2 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,7 +1,7 @@ { "name": "@supabase/config", "version": "0.1.0", - "private": true, + "private": false, "description": "Supabase project configuration schema, parsing, and validation, built on Effect Schema.", "keywords": [ "config", From 46885d1b8f0dbc2a5c1eb3f2b26dae30ab865665 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Tue, 1 Sep 2026 15:27:55 +0100 Subject: [PATCH 2/3] docs(config): describe the release setup as standing configuration (CLI-2169) The go-live setup (environment reviewers, trusted publisher, baseline tag, config-v* tag ruleset) is complete, so AGENTS.md's one-time-setup section now records the standing invariants to verify/restore instead of pending steps, the workspace-conventions doc lists packages/config as a published exception, and the workflow comments and private-blocked diagnostics treat private: true as a regression guard rather than a future flip. --- .github/workflows/release-config.yml | 11 +++--- AGENTS.md | 2 ++ packages/config/AGENTS.md | 47 ++++++++++++++----------- packages/config/scripts/release-plan.ts | 6 ++-- 4 files changed, 36 insertions(+), 30 deletions(-) diff --git a/.github/workflows/release-config.yml b/.github/workflows/release-config.yml index 5cb54b575b..0658837208 100644 --- a/.github/workflows/release-config.yml +++ b/.github/workflows/release-config.yml @@ -102,9 +102,9 @@ jobs: fi # The build, gate, and pack steps also run on private-blocked pushes - # (should_release=false, version set) — every config push rehearses the - # plan half of the release train while CLI-2169 hasn't flipped `private` - # yet. The publish half stays unexercised until then. + # (should_release=false, version set): if `private` were ever flipped + # back on, every config push would still rehearse the plan half of the + # release train while the publish half stays parked. - name: Build @supabase/config if: steps.plan.outputs.version != '' run: pnpm exec turbo run @supabase/config#build @@ -159,8 +159,7 @@ jobs: # would run straight through unreviewed. Fail closed here — before a # real (non-dry) release can reach the publish job — if the rule is # missing or unreadable. Private-blocked rehearsals (should_release - # false) are unaffected, so this only bites once CLI-2169 flips - # `private`, which is exactly when it must. + # false) are unaffected — this only gates real releases. - name: Assert the release approval gate is armed if: steps.plan.outputs.should_release == 'true' && steps.plan.outputs.dry_run != 'true' env: @@ -264,7 +263,7 @@ jobs: [[ "$(jq -r .name package/package.json)" == "@supabase/config" ]] [[ "$(jq -r .version package/package.json)" == "${VERSION}" ]] if [[ "$(jq -r .private package/package.json)" == "true" ]]; then - echo "packages/config is still private: true — flip it under CLI-2169 before publishing." >&2 + echo "packages/config is private: true — refusing to publish a private manifest (was it flipped back deliberately?)." >&2 exit 1 fi diff --git a/AGENTS.md b/AGENTS.md index 5b433a4fed..9e313dc366 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,6 +34,8 @@ Generic linting (`oxlint`), formatting (`oxfmt`), and unused-code analysis (`kni Expected exceptions: - `apps/cli` is published, so it is not `private` +- `packages/config` is published (on its own release train — see `packages/config/AGENTS.md`), so + it is not `private` - `apps/docs` is a Next.js app and does not follow the standard package template - `packages/cli-*` are binary wrapper packages and do not follow the standard TypeScript workspace template diff --git a/packages/config/AGENTS.md b/packages/config/AGENTS.md index d111880397..bf71cb72f6 100644 --- a/packages/config/AGENTS.md +++ b/packages/config/AGENTS.md @@ -156,24 +156,29 @@ elsewhere in the monorepo never releases `@supabase/config`, and vice versa. - **Local dry runs:** `scripts/release-plan.ts` runs the plan locally without publishing; `tools/config-release-gate.ts --tarball` rehearses the type-surface gate locally. -### One-time setup (tracked under CLI-2169) - -Four things must be settled before the first real publish: - -1. The `config-release` GitHub environment needs required reviewers configured in repo settings. An - environment referenced by a workflow is auto-created WITHOUT protection rules — the plan job - asserts the rule exists and refuses to plan a real release until it does, so the first release - attempt fails closed rather than publishing unreviewed. -2. npm trusted publishing must be configured for the package, which requires the package to exist - first. The very first publish is a manual bootstrap — use a granular, single-package, - short-expiry token and revoke it as soon as the trusted publisher is configured (repo - `supabase/cli`, workflow `release-config.yml`, environment `config-release`). -3. Push a baseline `config-v*` tag (e.g. `config-v0.1.0`) on a `develop` commit. This is required, - not optional: with no baseline, semantic-release would cut `1.0.0` with release notes generated - from the entire monorepo history — a whole-history changelog as both the approval artifact and - the public GH release body. `scripts/release-plan.ts` refuses to plan without a baseline tag - (escape hatch: `CONFIG_RELEASE_ALLOW_NO_BASELINE=1`). This is the single exception to the - "never hand-push a `config-v*` tag" rule above. -4. Add a repository tag ruleset protecting `config-v*` (alongside `v*`), restricted to the release - App. The last `config-v*` tag is the version oracle: a stray hand-pushed tag permanently skews - versioning, and a deleted tag makes the next plan re-cut an already-published version. +### Standing release configuration (set up under CLI-2169) + +The one-time go-live setup is complete. These are the standing invariants — verify them if a +release fails unexpectedly, and restore them if repo or npm settings are ever rebuilt: + +1. **The `config-release` GitHub environment has required reviewers.** An environment referenced + by a workflow is auto-created WITHOUT protection rules — the plan job asserts the rule exists + and refuses to plan a real release without it, so a stripped environment fails closed rather + than publishing unreviewed. +2. **npm trusted publishing is configured** for the package (repo `supabase/cli`, workflow + `release-config.yml`, environment `config-release`); no `NPM_TOKEN` exists anywhere. Trusted + publishing can only be configured on a package that already exists, so the package was seeded + with a manually published `0.0.0` placeholder (no `dist/`), and the bootstrap token was + revoked immediately after. +3. **The baseline tag `config-v0.0.0` matches that placeholder** — the tag oracle and the registry + must always agree on the last released version. With no baseline tag, semantic-release would + cut `1.0.0` with release notes generated from the entire monorepo history — a whole-history + changelog as both the approval artifact and the public GH release body — so + `scripts/release-plan.ts` refuses to plan in that state (escape hatch: + `CONFIG_RELEASE_ALLOW_NO_BASELINE=1`). Seeding it was the single exception to the "never + hand-push a `config-v*` tag" rule above. +4. **The "Protect config-v* release tags" ruleset** restricts creating, moving, and deleting + `config-v*` tags to the `supabase-cli-releaser` App (the same App the release workflows mint + tokens from). The last `config-v*` tag is the version oracle: a stray hand-pushed tag + permanently skews versioning, and a deleted tag wedges the next plan on an already-published + version. diff --git a/packages/config/scripts/release-plan.ts b/packages/config/scripts/release-plan.ts index bc9d4698f3..53ce9f7031 100644 --- a/packages/config/scripts/release-plan.ts +++ b/packages/config/scripts/release-plan.ts @@ -174,8 +174,8 @@ export function renderStepSummary(plan: ReleasePlan): string { if (plan.isPrivate) { lines.push( "> [!WARNING]", - "> `packages/config` is still `private: true`, so publishing is blocked — flip it under " + - "CLI-2169. This run validated the release pipeline only; nothing will be published.", + "> `packages/config` is `private: true`, so publishing is blocked. This run validated the " + + "release pipeline only; nothing will be published.", "", ); } @@ -204,7 +204,7 @@ function renderLocalPlan(plan: ReleasePlan): string { `${PACKAGE_PATH_PREFIX} since the last config-v* tag).` ); } - const privateNote = plan.isPrivate ? " (blocked: packages/config is still private: true)" : ""; + const privateNote = plan.isPrivate ? " (blocked: packages/config is private: true)" : ""; return `[release-plan] @supabase/config would release ${plan.version} (${plan.bumpType})${privateNote}.`; } From 9e0527a8a42bc494a0c4e4bcc2006b99de569a38 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Tue, 1 Sep 2026 15:36:05 +0100 Subject: [PATCH 3/3] docs(config): backtick the config-v* literal inside the ruleset bold span (ci: fmt:check) An unescaped * inside a bold span is an emphasis delimiter to CommonMark, so oxfmt --check rejected the committed text. --- packages/config/AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/config/AGENTS.md b/packages/config/AGENTS.md index bf71cb72f6..ec403bb1e4 100644 --- a/packages/config/AGENTS.md +++ b/packages/config/AGENTS.md @@ -177,7 +177,7 @@ release fails unexpectedly, and restore them if repo or npm settings are ever re `scripts/release-plan.ts` refuses to plan in that state (escape hatch: `CONFIG_RELEASE_ALLOW_NO_BASELINE=1`). Seeding it was the single exception to the "never hand-push a `config-v*` tag" rule above. -4. **The "Protect config-v* release tags" ruleset** restricts creating, moving, and deleting +4. **The "Protect `config-v*` release tags" ruleset** restricts creating, moving, and deleting `config-v*` tags to the `supabase-cli-releaser` App (the same App the release workflows mint tokens from). The last `config-v*` tag is the version oracle: a stray hand-pushed tag permanently skews versioning, and a deleted tag wedges the next plan on an already-published