feat(cli): port config push#5489
Open
Coly010 wants to merge 7 commits into
Open
Conversation
Supabase CLI previewnpx --yes https://pkg.pr.new/supabase@5489Preview package for commit |
Replace the Phase-0 Go proxy for `supabase config push` with a native
Effect implementation in the legacy shell (CLI-1305).
Pushes the local `supabase/config.toml` to the linked project's Management
API across all five services — api, db (settings / network_restrictions /
ssl_enforcement), auth, storage, experimental.webhooks — in Go order, with
per-service GET → diff → confirm → PATCH/PUT/POST flow.
Parity-critical engine (byte-exact against the Go binary, locked by goldens):
- config-sync.diff.ts: port of Go `pkg/diff` anchored/patience unified diff
- config-sync.toml.ts: BurntSushi-parity TOML encoder driven by ordered
per-service field descriptors
- config-sync.units.ts / .duration.ts / .secret.ts: ramInBytes/bytesSize,
time.ParseDuration/Duration.String, and Secret HMAC-SHA256 hashing
- {api,db,storage,experimental,auth}.sync.ts: push-subset projections,
FromRemote, diff, and update-body construction
Orchestration:
- push.handler.ts native orchestrator with cost-aware confirm prompts, the
auth MFA addon cost filter, telemetry + linked-project cache flush on
success and failure, and json / stream-json structured summaries
- push.cost-matrix.ts and push.raw-presence.ts use raw HTTP / raw TOML where
the generated client / decoded config can't express Go's behaviour
(string addon types; nil-pointer ssl_enforcement / image_transformation /
s3_protocol skipped when absent from config.toml)
Secrets are sent to the API as their raw plaintext value (Go `Secret.Value`)
while the diff serialises the `hash:` form, mirroring Go exactly.
Hoist `requestWithAuth` to legacy/shared/legacy-raw-http.ts (shared with
postgres-config). Flip docs/go-cli-porting-status.md to `ported`.
- abort with exit 1 when a [remotes.*] block targets the project ref instead of silently resetting non-overridden fields to schema defaults - treat dotenvx encrypted: secrets as unresolved (hash to ''), so the ciphertext is gated out of the diff and update body rather than overwriting the remote secret - compute sms_test_otp_valid_until with calendar-exact 10-year offset (setUTCFullYear) to match Go's time.Now().UTC().AddDate(10, 0, 0) - document the CLI-1489 loadProjectConfig tradeoff; drop now-unreachable remote-merge branch from raw-presence - add secret hasher unit tests + remotes-guard and encrypted-secret regression coverage
…PI fields The config-push e2e (reconciles every section / parity) failed once decoding got past the first service. Two classes of bug, both pre-existing and masked: 1. Over-strict generated @supabase/api schemas crashed on real responses: - NetworkRestrictionsResponse marked `status` required; the API omits it. - AuthConfigResponse marked `nimbus_oauth_email_optional` required; omitted. - `sms_test_otp_valid_until` / `smtp_admin_email` were emitted as single-element unions missing `Schema.Null` despite `nullable: true`. Relax the two over-strict `required` arrays via openapi-overrides.json (mirroring the existing saml override) and sync openapi.json + contracts.ts; add the dropped `Schema.Null` to the two nullable fields. These are real production decode crashes, not just test issues. 2. The auth update body diverged from Go's ToUpdateAuthConfigBody: - captcha / hooks / smtp / external providers were always emitted because @supabase/config defaults those pointer sections present. Gate them on raw-config presence (extends push.raw-presence to the [auth] subtree), and emit only Go's external set (the apple template default + configured providers). - external_url / jwt_issuer were hard-coded to ""; derive them from the api endpoint like Go's config load (api.external_url -> +/auth/v1 -> jwt_issuer).
b69940b to
ceca515
Compare
…tures The two config-push replay scenarios embedded a `GET /network-restrictions` response missing the `status` field, while the live API and the per-endpoint recorded fixtures both include it. The native config push port decodes the response against the (correct, strict) generated schema, which requires `status`, so the stale scenario bodies caused a `Missing key` decode error and exit 1. Add `"status": "applied"` to match real API shape; no schema override needed.
…ig push
The auth sync used the Go oapi-codegen enum *constant names* (alphanumerics
with separators stripped, e.g. "…XYZ0123456789", "…01234567891") as the string
values for `password_required_characters`. The real API values contain `:`
separators between character-class groups ("…XYZ:0123456789", etc.).
Consequences: a real remote value never matched, so `FromRemote` always mapped
back to "" → wrong diffs; and `authToUpdateBody` emitted a value the generated
client rejects on encode → the auth PATCH failed before being sent.
Replace the two duplicated switch tables with a single source-of-truth
`PASSWORD_REQUIREMENTS_TO_CHAR` map (values matching the @supabase/api
`V1{Get,Update}AuthServiceConfig` literals) plus its inverse, so the two
directions cannot diverge again. Add regression tests: round-trip per enum,
schema-acceptance of the emitted update-body value, and the unknown-value → ""
fallback. The existing tests repeated the wrong value and are corrected.
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 for
supabase config pushwith a native Effect implementation in the legacy shell (CLI-1305). It pushes the localsupabase/config.tomlto the linked project's Management API across all five services — api, db (settings / network_restrictions / ssl_enforcement), auth, storage, experimental.webhooks — in Go's order, with the per-serviceGET → diff → confirm → PATCH/PUT/POSTflow frompkg/config/updater.go.Why
config pushwas the lastconfigsubcommand still proxying to the bundled Go binary. It rests on essentially the whole Gopkg/configpackage (schema + per-service diff + update-body machinery).How it stays byte-exact with Go
The output contract is strict 1:1 — same stderr text, same unified-diff bytes, same API routes, same exit codes. The parity-critical engine lives in
config-sync/and is locked by golden fixtures generated from the Go binary:config-sync.diff.ts— port of Gopkg/diff(anchored/patience unified diff).config-sync.toml.ts— BurntSushi-parity TOML encoder driven by explicit per-service ordered field descriptors (ordering,omitempty, depth-1 blank lines, map sorting).config-sync.units.ts/.duration.ts/.secret.ts— ports ofdocker/go-units(RAMInBytes/BytesSize),time.ParseDuration/Duration.String, and theSecretHMAC-SHA256 hashing.{api,db,storage,experimental,auth}.sync.ts— push-subset projections +FromRemote+ diff + update-body, one module per service.Orchestration
push.handler.ts— native orchestrator: cost-aware confirmation prompts, the auth MFA addon cost filter, telemetry + linked-project-cache flush on success and failure, and--output-format json/stream-jsonstructured summaries.push.cost-matrix.ts/push.raw-presence.ts— use raw HTTP / raw TOML where the typed client or decoded config can't express Go's behaviour: the generated addontypeis a closed enum (Go uses a plain string), and@supabase/configdefaults the nil-pointer sectionsdb.ssl_enforcement/storage.image_transformation/storage.s3_protocolto present, so raw-key detection recovers Go's skip-when-absent semantics.@supabase/apischema correctionsconfig pushis the first native command to decode the auth and network-restrictionsGETresponses, which surfaced spots where the generated schema is stricter than the real API. Corrected via the test-guardedopenapi-overrides.json+ syncedcontracts.ts:AuthConfigResponse.nimbus_oauth_email_optional→ optional. The spec marks itrequired, but the live API omits the key.AuthConfigResponse.smtp_admin_email/sms_test_otp_valid_until→ nullable. The spec marks themnullable: true, but the generated schema droppedSchema.Nullfor theseformat-annotated fields; the live API returns them asnull.Two
config-pushcli-e2e replay scenarios also had a recordedGET /network-restrictionsbody missingstatus; it's added to match the live API and the per-endpoint recorded fixtures.Reviewer notes
hash:<sha256>form (GoSecret.MarshalText), but the update body sends the raw plaintext value (GoSecret.Value), gated by hash presence. Covered by a regression test.requestWithAuthis hoisted tolegacy/shared/legacy-raw-http.tsand shared withpostgres-config.SIDE_EFFECTS.md): a matched[remotes.*]block aborts with exit 1 rather than overlaying a defaulted subtree;encrypted:dotenvx secret decryption is not reproduced (such secrets are skipped, not pushed as ciphertext).docs/go-cli-porting-status.mdflipsconfig push→ported.