feat(cli): port branches commands to native TypeScript#5374
Merged
Conversation
Replaces the Phase 0 Go-proxy shims for `supabase branches {list, create, get,
update, pause, unpause, delete, disable}` with native Effect handlers under
`apps/cli/src/legacy/commands/branches/`. Behavior, output formatting, exit
codes, and telemetry are 1:1 with `apps/cli-go/internal/branches/*`.
- Per-family helpers: `branches.errors.ts`, `branches.format.ts`,
`branches.resolver.ts`, `branches.prompt.ts`.
- Shared hoist: `shared/git/git-branch.ts` (also adopted by
`next/branches/create`), `legacy/shared/legacy-profile.ts` (project_host /
dashboard_url / billing URL lookups), and `legacy/shared/legacy-upgrade-suggest.ts`
(fires `cli_upgrade_suggested` PostHog event matching Go's `TrackUpgradeSuggested`).
- `create` and `update` use `Option<boolean>` for `--persistent` / `--with-data`
so explicit-false demote requests reach the API (mirrors Go's
`cmdFlags.Changed("persistent")`).
- Pooler warning sanitizes the parse error to avoid leaking the connection
string (host/user) to stderr.
- 982 unit + integration tests passing; types/lint/fmt/knip clean.
Fixes CLI-1289
The OpenAPI spec models `branch_id_or_ref` as `oneOf [project-ref, uuid]`, but OpenAPI 3.0's `format: "uuid"` is a hint, not validation. The generator pipeline left the UUID branch unconstrained (`Schema.String` with only a format annotation), so a 20-letter project ref matched BOTH branches and the union rejected with "Expected exactly one member to match" — breaking every `branches get/update/pause/unpause/delete` invocation against a real project ref returned from the resolver lookup (caught by branches e2e parity tests). Adds the canonical RFC 4122 pattern to every `format: "uuid"` string both in the generator (sanitizeOpenApiSchema) and inline in the 8 affected `branch_id_or_ref` schemas in contracts.ts. The branches become mutually exclusive and 20-letter project refs route deterministically. Also fixes a unit test that depended on the buggy permissive matching.
Two e2e parity failures observed against the bundled `dist/supabase-legacy`: 1. `branches get/create/update/...` panicked with "Service not found: supabase/legacy/CliConfig" — `legacy-upgrade-suggest.ts` and `get.handler.ts` both yield `LegacyCliConfig` directly, but `legacyManagementApiRuntimeLayer` only piped `legacyCliConfigLayer` into sub-layers via `Layer.provide`, which does not share to siblings inside `Layer.mergeAll`. Expose `legacyCliConfigLayer` at the top level of the merged layer so handler-scope `yield* LegacyCliConfig` resolves. Matches the CLAUDE.md parity-checklist item 5 footgun. 2. `branches get --output json` exit code 1 due to a schema-decode failure on the pooler fixture: the recorded scenario was missing the deprecated-but-still-required `connectionString` (camelCase) field. The Go proxy never re-decoded the response; the native TS handler does. Add the field to mirror the production API shape.
The schema fix for `branch_id_or_ref`'s `oneOf` union added a real RFC 4122
pattern check to the UUID branch, so the placeholder string "branch-ref"
no longer validates against either branch. Update every test fixture and
the `platform-examples` placeholder to use a 20-letter project ref
("abcdefghijklmnopqrst") that matches the project-ref branch:
- packages/api: api-client unit test that drove the unified execute path
- platform-examples.ts placeholder + its unit test + integration test
- platform-input.unit.test.ts prompt mock that asserted validate() accepts
the value
User-facing example commands now display a valid placeholder, so anyone
who copies the example past the placeholder substitution prompt gets a
schema-valid command.
9fe7172 to
c0c786d
Compare
Coly010
added a commit
that referenced
this pull request
May 28, 2026
…-HTTP fix After rebasing onto develop, the branches port (#5374) had already landed a shared `legacy/shared/legacy-upgrade-suggest.ts` helper covering the same `cli_upgrade_suggested` telemetry path my SSO commit duplicated. Reconcile to use the develop-side helper (`legacySuggestUpgrade` with an opts-object argument shape) and delete the SSO-only `legacy/telemetry/legacy-upgrade-suggested.ts` + test. Three pieces had to merge: 1. The shared helper's *behaviour* now writes the bold billing-URL suggestion to stderr in text mode (Go's `CmdSuggestion` parity) on top of firing the telemetry event. SSO inherits this for free — my prior helper only emitted the event and was missing this Go-parity side-effect. 2. The helper now uses raw `HttpClient` for the project + entitlements GETs (my fix from a few commits ago). Develop's version went through the typed `api.v1.getProject` which strict-decodes — the cli-e2e replay fixtures embed the literal 15-char `__PROJECT_REF__` placeholder in response bodies, failing `ref: isMinLength(20)`. With `Effect.option` swallowing the decode error, the entitlements GET would silently be skipped and the test would never observe the gated path. Same workaround used by `legacy-linked-project-cache.layer.ts`. 3. `branches/update`'s upgrade-gate integration test was built around `mockLegacyPlatformApiService` (typed-client mock) — its assertion pattern `apiMock.requests.find(r => r.method === "getProject")` no longer applies now that the helper bypasses the typed client. Rewrote that one test to use `mockLegacyPlatformApi` with a handler that routes the three URLs (`PATCH /v1/branches/<id>`, `GET /v1/projects/<ref>`, `GET /v1/organizations/<slug>/entitlements`) and assert against the recorded request URLs. As a bonus the rewrite verifies the stderr suggestion now emits ("Upgrade your plan:"), which the previous test couldn't reach. Six SSO handler call sites updated to the opts-object signature. SSO integration tests updated to use `mockAnalytics` from `tests/helpers/mocks.ts` (the existing shared helper) instead of the parallel `mockLegacyAnalytics` I had added — same shape, cleaner. Verified locally: - `nx run-many ...` (types + lint + fmt + knip) green. - `bun run test:core`: 1127 in-process tests pass. - Full `sso.e2e.test.ts` cli-e2e suite (52 tests) passes against `CLI_HARNESS_TARGET=ts-legacy`. - `legacy-upgrade-suggest.unit.test.ts` (8 tests, develop-originated) still passes.
jgoux
added a commit
that referenced
this pull request
May 28, 2026
## Summary Promotes `supabase orgs list` and `supabase orgs create` from Phase 0 Go-binary proxies to native TypeScript implementations in the legacy shell. The handlers reuse the shared infrastructure introduced by the [branches port](#5374) — HTTP error mapper, Go-compatible JSON/YAML/TOML/env encoders, and Glamour-rendered pretty tables — so no new shared helpers were needed. Go source mirrored 1:1: `apps/cli-go/internal/orgs/list/list.go` and `apps/cli-go/internal/orgs/create/create.go`. Error templates, output mode behaviour, and the `Created organization: <id>` preamble byte-match the Go CLI. ## Notable parity rules - **`orgs list --output env`** is rejected with the byte-exact Go message `--output env flag is not supported`. - **`orgs list --output toml`** wraps the array as `[[organizations]]` (Go uses a TOML envelope for arrays). - **`orgs create --output env`** _is_ supported here, unlike on list — matches Go's `EncodeOutput` flattening of a single object into `ID=… NAME=… SLUG=…`. - **`Created organization: <id>`** preamble fires before every Go-output mode (json / yaml / toml / env / pretty) but is omitted from `--output-format json` / `stream-json`, where the message is delivered as a structured event field instead. - **Telemetry** flushes in `Effect.ensuring` on success and failure (CLAUDE.md PersistentPostRun parity rule). No linked-project cache write — these are user-level commands without a `--project-ref`. - **Parent description** matches Go exactly: `Manage Supabase organizations` (no trailing period). ## Tests - 3 unit tests for `renderOrgsListTable` (header-only, multi-row, literal `|` passthrough). - 32 integration tests covering every output mode for both subcommands, the env-not-supported rejection, `--output`-over-`--output-format` precedence, network and HTTP-503 error paths (including the `format !== "text"` spinner-undefined branch), and telemetry flush on both success and failure. E2E coverage deliberately omitted — these are pure Management-API CRUD commands with no subprocess-specific behaviour to validate (per CLAUDE.md testing policy). ## Reviewer-relevant context - The orgs handlers never resolve a project ref but still use `legacyManagementApiRuntimeLayer`, which constructs the project-ref resolver lazily. The orgs handlers simply don't yield those services. This is intentional: keeping a single shared runtime layer is cheaper than maintaining a user-vs-project layer split. - `orgs.errors.ts` and `orgs.format.ts` are colocated at the family root, not hoisted to `legacy/shared/`. Per CLAUDE.md hoist-before-duplicate, the threshold is "used across ≥2 families" — branches has its own `branches.format.ts`/`branches.errors.ts`, and these are the second instance of the pattern, not yet a third. - The Glamour table renders API-supplied strings (`id`, `name`) without ANSI/control-character stripping. This is strict Go parity — both `SIDE_EFFECTS.md` files document the deliberate pass-through under "Security Notes" so a future renderer-level sanitization fix has a clear pointer. Refs CLI-1290. Co-authored-by: Julien Goux <hi@jgoux.dev>
Merged
1 task
pull Bot
pushed a commit
to limerencel/tldraw
that referenced
this pull request
May 29, 2026
…review (tldraw#8964) In order to stop the "Deploy .com to preview" workflow from failing on the "Create Supabase preview branch" step, this PR makes the branch-provisioning wait loop tolerant of the Supabase CLI's output while a branch is still being created. The Supabase CLI [v2.102.0](https://github.com/supabase/cli/releases/tag/v2.102.0) [ported the `branches` commands from Go to native TypeScript](supabase/cli#5374). Because our workflow installs the CLI with `version: latest`, CI picked this up automatically. The rewrite changed what `supabase branches get -o json` writes to **stdout** while a branch is still provisioning: the old (Go) CLI wrote empty stdout there, but the new (TS) CLI renders a non-JSON error/status line. The workflow piped that straight into an unguarded `jq`, which exited with code 5 on the parse error, and since the step runs under `set -e` the whole step aborted on the first loop iteration — before the retry loop could wait for the branch to become ready. This was a CLI behavior change, not a regression in our code: the script had been unchanged since the [Supabase preview-DB migration](tldraw#8066), and the same workflow passed on older CLI versions. The fix adds `2>/dev/null || true` to `supabase branches get` and to both `jq` calls so that, while the branch is provisioning, non-JSON / non-zero output is treated as "not ready yet" and the loop keeps retrying as it was always meant to. Once the branch is ready the CLI returns valid JSON, `jq` extracts `POSTGRES_URL`, and the loop breaks normally. Field names (`POSTGRES_URL`, `POSTGRES_URL_NON_POOLING`) are unchanged in the new CLI, so no other adjustments are needed, and the CLI stays on `latest`. ### Change type - [x] `other` (CI) ### Test plan 1. Open a PR that triggers the "Deploy .com to preview" workflow with a brand-new preview branch (one that has to provision from scratch). 2. Confirm the "Create Supabase preview branch" step logs "Waiting for branch to be ready (i/24)..." while provisioning and then succeeds, instead of failing with `jq: parse error` / exit code 5. ### Code changes | Section | LOC change | | -------------- | ---------- | | Config/tooling | +8 / -3 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Replaces the Phase 0 Go-proxy shims for all eight
supabase branchessubcommands (list,create,get,update,pause,unpause,delete,disable) with native Effect handlers underapps/cli/src/legacy/commands/branches/. Behavior, output formatting, exit codes, and telemetry are 1:1 withapps/cli-go/internal/branches/*.Why
Fifth batch in the Go-to-TS port (CLI-1289). Removing the subprocess fork-exec from the hot path of every
supabase branchesinvocation is the immediate motivation; the broader goal is shrinking the Go-binary surface so the TS shell can ship standalone.Highlights
Per-family colocated helpers
branches.errors.ts— tagged errors for every HTTP call (15 pairs) plus validation/cancellation errors.branches.format.ts— list-table and get-table renderers,toStandardEnvsenv-map projection (POSTGRES_URL/SUPABASE_URL/SUPABASE_<NAME>_KEY/SUPABASE_JWT_SECRET), Go-byte-exacttoPostgresUrlandparsePoolerConnectionString.branches.resolver.ts—GetBranchProjectRefequivalent (project-ref / UUID / named-lookup dispatch). Exports the sharedLEGACY_BRANCH_PROJECT_REF_PATTERNandLEGACY_BRANCH_UUID_PATTERN.branches.prompt.ts— TTY list-picker and non-TTY stdin prompt with git-branch default; emitsSelected branch ID: <ref>to stderr after a TTY pick. Cyan-styles the git-branch hint and surfaces theCreate your first branch with: supabase branches createsuggestion when branching is disabled.Hoisted shared infrastructure
shared/git/git-branch.ts—detectGitBranch(reads$GITHUB_HEAD_REF, then walks up CWD reading.git/HEAD).next/branches/createswitches to this helper in the same change.legacy/shared/legacy-profile.ts— consolidatesprofile → project_host,profile → dashboard_url, and(profile, org_slug) → billing_urllookups that were duplicated between formatters and the upgrade-suggest helper.legacy/shared/legacy-upgrade-suggest.ts— replays Go'sSuggestUpgradeOnError+TrackUpgradeSuggestedflow. Firescli_upgrade_suggestedwith{feature_key, org_slug}on 4xx-gated entitlement failures fromcreate(branching_limit) andupdate(branching_persistent). Writes the bold billing-URL suggestion to stderr in text mode only.Notable Go-parity decisions
create/updateuseOption<boolean>for--persistentand--with-dataso explicit-false reaches the API. Mirrors Go'scmdFlags.Changed("persistent")check; previously these would have been silently dropped.createconfirm prompt fires unconditionally (TTY or non-TTY) — Go'sPromptYesNodefaults to true on EOF, the non-interactiveOutputlayer matches that.updateupgrade-suggest passes the resolvedbranchRef(Go parity withupdate.go:26); the test path uses a newmockLegacyPlatformApiServicedirect-service mock that sidesteps the upstreambranch_id_or_refoneOfschema constraint.WARNING:prefix is yellow-styled to matchutils.Yellow; the git-branch default in the prompt label is cyan to matchutils.Aqua.Tests
mockLegacyPlatformApiService) andlegacyStatusCodeFailurehelper intests/helpers/legacy-mocks.tsfor handler tests that need to bypass schema validation.Updated
docs/go-cli-porting-status.md— eightbranches *rows flipped fromwrappedtoported. Each subcommand directory has a refreshedSIDE_EFFECTS.md.Fixes CLI-1289