Skip to content

feat(cli): port projects commands to native TypeScript - #5392

Merged
jgoux merged 7 commits into
developfrom
cli/port-projects-commands
Jun 1, 2026
Merged

feat(cli): port projects commands to native TypeScript#5392
jgoux merged 7 commits into
developfrom
cli/port-projects-commands

Conversation

@Coly010

@Coly010Coly010 commented May 29, 2026

Copy link
Copy Markdown
Contributor

What changed

Replaces the four Phase-0 Go proxies in the legacy projects command group with native TypeScript Effect implementations that call the Management API directly, and flips their rows in go-cli-porting-status.md from wrapped to ported.

  • listGET /v1/projects; soft linked-ref marker via the new resolveOptional (never prompts/fails); two-axis output (Go --output pretty|json|yaml|toml|env × TS --output-format text|json|stream-json), with --output env unsupported, matching Go.
  • create — interactive/non-interactive gating on --interactive + TTY + output mode; prompts for name/org/region/password when omitted; crypto-RNG 16-char password fallback; desired_instance_size only when --size set; --plan accepted-but-ignored (hidden, vestigial in Go); dashboard URL resolved per profile.
  • delete — arg-or-prompt ref resolution (never reads the linked file as a source); confirmation defaulting to No and honouring --yes; 404 → does-not-exist, other non-2xx → failed; best-effort supabase/.temp unlink when the deleted ref matches the linked one.
  • api-keys — ref resolution through the shared resolver (flag → env → linked file → prompt on a TTY → error); NAME | KEY VALUE table with ****** masking; SUPABASE_<NAME>_KEY env/toml encoding; raw ApiKeyResponse[] for json/yaml.

Shared infrastructure

  • LegacyProjectRefResolver gains resolveOptional (soft marker for list) and promptProjectRef(title) (per-command prompt label for delete); existing resolve unchanged.
  • Hoists apiKeysToEnv into legacy/shared/legacy-api-keys.format.ts and reuses the existing legacy-timestamp.format.ts instead of duplicating a timestamp formatter (per the hoist-before-duplicate policy).

Reviewer notes

  • Strict Go parity for stdout/stderr text, flags, exit codes, API routes, and error-message phrasing. Refs are validated against ^[a-z]{20}$ before reaching any API path.
  • Intentional divergences (documented in SIDE_EFFECTS.md): terminal ANSI color is omitted (Go disables color on non-TTY output, so piped output matches byte-for-byte); the non-interactive create error consolidates cobra's per-flag "required" errors into one message; DB_PASSWORD is not consumed (Go only mirrors --db-password into viper for local-stack reuse).
  • New unit + integration coverage across all four commands and the resolver; per-command and group-level SIDE_EFFECTS.md.

Closes CLI-1288.

Replace the four Phase-0 Go proxies in the `projects` group (`list`,
`create`, `delete`, `api-keys`) with native Effect implementations that hit
the Management API directly, and flip their porting-status rows to `ported`.
- list: GET /v1/projects + soft linked-ref marker (resolveOptional), two-axis
output (Go --output pretty|json|yaml|toml|env x TS --output-format), env
unsupported.
- create: interactive/non-interactive flag gating, org/region/name/password
prompts, crypto-RNG password fallback, dashboard URL by profile.
- delete: arg-or-prompt ref resolution, confirmation (default No, honours
--yes), 404/unexpected mapping, best-effort supabase/.temp unlink.
- api-keys: ref resolution via shared resolver, NAME/KEY VALUE table with
masking, SUPABASE_<NAME>_KEY env/toml encoding.
Shared infra:
- LegacyProjectRefResolver gains resolveOptional + promptProjectRef(title).
- Hoist apiKeysToEnv into legacy/shared/legacy-api-keys.format.ts and reuse the
existing legacy-timestamp.format.ts (drop duplicate formatter).
Adds unit + integration coverage and per-command + group SIDE_EFFECTS.md.
@Coly010
Coly010 requested a review from a team as a code ownerMay 29, 2026 12:15
@Coly010Coly010 self-assigned this May 29, 2026
Coly010and others added 6 commits May 29, 2026 13:44
The native port broke three `projects` parity dimensions the Go proxy passed:
- delete: reproduce Go's best-effort per-ref keyring delete
(`credentials.StoreProvider.Delete`) — add `LegacyCredentials.deleteProjectCredential`,
surfacing "Keyring is not supported on WSL" to stderr when the keyring is
unsupported (matches `assertKeyringSupported`), swallowing a missing entry.
- create: send the POST body with alphabetically-sorted keys via
`encodeGoStructJsonBody` over the raw HTTP client so it byte-matches Go's
`json.Marshal`.
- list/create: parse the `/v1/projects` response via the raw HTTP client — the
typed client's `ref: isMinLength(20)`/`^[a-z]+$` schema rejects the cli-e2e
`__PROJECT_REF__` placeholder fixtures. Same workaround as `legacySuggestUpgrade`.
- list: print Go's `ErrNotLinked` stderr line ("Cannot find project ref...")
when no project is linked, then render the table anyway.
Also caches the resolved ref on delete (PersistentPostRun parity), updates the
SIDE_EFFECTS notes, and adds integration coverage for the new branches.
… raw HTTP
Replace the hand-built `HttpClientRequest` calls with a typed escape hatch on
the platform API client so list/create reuse its URL/auth/header/body handling.
- packages/api: add `executeRaw(definition, input)` to the client — builds the
request exactly like `execute` but returns the raw response without output
decoding (callers parse leniently when the strict generated schema can't
represent a value, e.g. the cli-e2e `__PROJECT_REF__` ref placeholder).
- packages/api: serialize JSON request bodies with alphabetically-sorted keys
to match Go's `json.Marshal`, fixing multi-field body parity generally (e.g.
`projects create --size`); previously only single/already-sorted bodies matched.
- projects list/create: use `api.executeRaw(...)` instead of manual requests.
- delete: correctly surface "Keyring is not supported on WSL" — the headless CI
keyring failure is not the WSL osrelease path, so discriminate the
`@napi-rs/keyring` "No matching credential found" (entry absent → swallow)
from a missing backend (→ emit, matching Go's `ErrNotSupported`).
…e in e2e
Auditing Go's `delete.Run` against the port: Go best-effort deletes a per-ref
keyring credential, but Go only ever *stores* the profile-scoped access token in
the keyring (`StoreProvider.Set` is only ever called with `CurrentProfile.Name`,
never a project ref). So that delete always targets a non-existent entry — a
functional no-op for both CLIs. Its only observable output is Go's keyring
*availability* error ("Keyring is not supported on WSL"), emitted when the system
keyring backend is down (headless CI with no D-Bus). The TS `@napi-rs/keyring`
kernel-keyutils backend is always available and never hits this.
So the faithful port is to not perform the no-op delete, and to treat Go's
keyring-availability line as the environment noise it is:
- revert the per-ref `deleteProjectCredential` machinery (it could not match Go
without faking Go's message); the delete handler keeps the API call, status
mapping, `supabase/.temp` unlink, and output — all matching Go.
- normalize "Keyring is not supported on WSL" out of stderr in the cli-e2e
parity harness, consistent with how it already treats keyring divergence
(login/logout parity is deferred for the same reason).
@jgoux
jgoux enabled auto-merge (squash) June 1, 2026 07:37
@jgoux
jgoux merged commit cbf20cc into developJun 1, 2026
12 checks passed
@jgoux
jgoux deleted the cli/port-projects-commands branch June 1, 2026 07:41
Sign up for freeto 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

@Coly010@jgoux