feat(cli): port projects commands to native TypeScript#5392
Open
Coly010 wants to merge 6 commits into
Open
Conversation
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.
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).
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 changed
Replaces the four Phase-0 Go proxies in the legacy
projectscommand group with native TypeScript Effect implementations that call the Management API directly, and flips their rows ingo-cli-porting-status.mdfromwrappedtoported.list—GET /v1/projects; soft linked-ref marker via the newresolveOptional(never prompts/fails); two-axis output (Go--output pretty|json|yaml|toml|env× TS--output-format text|json|stream-json), with--output envunsupported, 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_sizeonly when--sizeset;--planaccepted-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-effortsupabase/.tempunlink 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 VALUEtable with******masking;SUPABASE_<NAME>_KEYenv/toml encoding; rawApiKeyResponse[]for json/yaml.Shared infrastructure
LegacyProjectRefResolvergainsresolveOptional(soft marker forlist) andpromptProjectRef(title)(per-command prompt label fordelete); existingresolveunchanged.apiKeysToEnvintolegacy/shared/legacy-api-keys.format.tsand reuses the existinglegacy-timestamp.format.tsinstead of duplicating a timestamp formatter (per the hoist-before-duplicate policy).Reviewer notes
^[a-z]{20}$before reaching any API path.SIDE_EFFECTS.md): terminal ANSI color is omitted (Go disables color on non-TTY output, so piped output matches byte-for-byte); the non-interactivecreateerror consolidates cobra's per-flag "required" errors into one message;DB_PASSWORDis not consumed (Go only mirrors--db-passwordinto viper for local-stack reuse).SIDE_EFFECTS.md.Closes CLI-1288.