feat(cli): port supabase start command to native TypeScript#5847
feat(cli): port supabase start command to native TypeScript#5847Coly010 wants to merge 55 commits into
Conversation
Ports `supabase start` from a Go-binary proxy to native TypeScript in the legacy CLI shell (CLI-1323), continuing the local-dev-stack shell migration alongside the already-ported `stop`/`status`/`db push`/`db reset`/`db start`. ## What changed Talks directly to Docker/Podman via subprocess, mirroring Go's sequential per-container `DockerStart` — no Docker Compose, and no `@supabase/stack/effect` orchestration (that runtime targets a deliberately different local-dev product and would silently manage the wrong set of containers). Brings up all 14 containers in Go's real start order, with per-service config-boolean + `--exclude` gating, Go-byte-exact env/image resolution, a bulk health-check phase (Docker healthcheck for most services, an HTTP-HEAD-through-Kong bypass for PostgREST/Edge Runtime), and full rollback on any bring-up failure. Also natively implements the fresh-volume DB schema/migration/seed pipeline, Edge Runtime container bring-up, and fresh-volume storage bucket seeding — previously tracked as out-of-scope follow-ups for this port, now closed. Only the linked-project version-check suggestion (a best-effort "update available" hint with zero Management API dependency otherwise) remains out of scope by design. ## Notable review-driven fixes - `legacyParseGoDuration` (the `config.toml` duration-string parser feeding Go-parity env vars like `GOTRUE_SESSIONS_TIMEBOX`) silently accepted a malformed duration with no digits (a bare unit like `"s"`, or a lone `"."` with no digits on either side) and returned `0` instead of erroring like Go's real `time.ParseDuration` — both cases now throw Go's exact `time: invalid duration "..."` message. - `start.services.ts`'s descriptive `enabledGate` metadata for Mailpit referenced the deprecated `inbucket` config section instead of its `local_smtp` rename. Fixed, and added a mechanical cross-check test that evaluates every service's `enabledGate` string against `start.gates.ts`'s real computed gate across a battery of synthetic configs, so a future drift between the two fails loudly instead of silently. - Confirmed and closed the PostgREST health-check's TLS/CA trust gap for `[api.tls] enabled = true` local stacks (self-signed Kong cert) — the local-Kong-CA-trust mechanism already used by `seed buckets`/`storage`/ `db reset` is now wired into `start`'s health-check HTTP client too. Closes CLI-1323
|
👋 Thanks for the contribution! This pull request isn't linked to a tracked issue, so it's being closed automatically. Please open an issue first, wait for a maintainer to add the |
…3-port-supabase-start
|
👋 Thanks for the contribution! This pull request isn't linked to a tracked issue, so it's being closed automatically. Please open an issue first, wait for a maintainer to add the |
|
👋 Thanks for the contribution! This pull request isn't linked to a tracked issue, so it's being closed automatically. Please open an issue first, wait for a maintainer to add the |
|
👋 Thanks for the contribution! This pull request isn't linked to a tracked issue, so it's being closed automatically. Please open an issue first, wait for a maintainer to add the |
) The contribution gate identified internal maintainers solely from the PR's `author_association`, which GitHub only reports as `MEMBER` when a user's organization membership is public. A private org member (e.g. a `supabase/cli` team member who keeps membership private) is reported as `CONTRIBUTOR`/`NONE`, so the gate wrongly closed their PRs as "no-linked-issue" (see supabase#5847). Resolve maintainer status from the author's effective repository permission (`admin`/`write`), which reflects team/org-granted access that `author_association` does not surface, falling back to it only when the cheap signals (bot, public internal association) are inconclusive. The permission endpoint needs just `Metadata: read`, already covered by the workflow's `contents: read`. Claude-Session: https://claude.ai/code/session_01D41gYiFBSU7adE4ppnUUKg --------- Co-authored-by: Claude <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 623dd8b18e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 623dd8b18e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
supabase start's TS port had five spots where Go's real behaviour was
silently dropped:
- supabase/.temp/storage-migration (the linked project's Storage
migration pin) was never read, so DB_MIGRATIONS_FREEZE_AT was always
empty on a fresh DB setup and the Storage container itself.
- supabase/.temp/{gotrue,rest,storage,realtime,studio,pgmeta,logflare,
pooler}-version pins were never applied to the images start
pulls/creates, unlike Go's Config.Load rewrite. Hoisted the existing
services command's reader into shared/legacy-service-version-overrides.ts
and reused services.shared.ts's tag-rewrite helpers instead of a third
copy.
- --network-id was read by several other native ports but never by
start itself, so the override never reached any container or the
Docker network start creates.
- SUPABASE_API_PORT's env override was computed for status URLs but
never exposed for Kong/Edge Runtime's own container specs, so they
kept publishing/binding the un-overridden config.api.port.
- Studio's function bind mounts were hardcoded to [], unlike Go's
unconditional (Edge-Runtime-independent) PopulatePerFunctionConfigs
call — extracted the existing per-function bind computation in
shared/functions/serve.ts into a reusable resolveFunctionBindMounts
so both callers share one calculation.
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…l in start Three more Go-parity gaps in supabase start found by review: - db.root_key (including encrypted: values) was never resolved or passed to the Postgres container, so every project booted with the hard-coded default pgsodium key instead of a customized one, breaking decryption of existing encrypted data. Resolved in legacyResolveLocalConfigValues off the raw config document (unmodeled in @supabase/config's schema), reusing the existing decrypt helpers. - DockerStart's Linux-only host.docker.internal:host-gateway mapping was already correctly ported for the one-shot migrate jobs and Edge Runtime bring-up, but never made it into the common legacyStartContainer path the other 13 services go through. - auth.external_url (also unmodeled in the schema, with its own Go regression test) was never read, so GoTrue's API_EXTERNAL_URL/JWT issuer default/mailer verify URL/OAuth redirect fallbacks always derived from apiUrl even when a project intentionally exposed auth at a different host.
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…es tag ordering Three more Go-parity gaps found by review: - [auth.email.smtp] present without an explicit enabled key should default to enabled (Go sets this at load time), but the schema always decodes enabled: false when the key is absent, so GoTrue silently fell back to Mailpit. Reuses the existing correct resolution already implemented for config validation, exposed as legacyResolveAuthEmailSmtp. - auth.external providers outside the schema's fixed ~19-provider set (e.g. a custom [auth.external.my_oidc] block) never reached GoTrue's env, even though Go's Auth.External is a genuine map iterated unconditionally and this port's own config validation already accepts arbitrary provider names. Reuses the same raw-document iteration validateAuthExternalProviders already established. - A registry override with a port (SUPABASE_INTERNAL_IMAGE_REGISTRY= localhost:5000) broke the Postgres version-tag comparison, since Go compares the pre-registry-rewrite image while this port was comparing the already-rewritten one. Threaded the original image through as a separate configImage field used only for that comparison.
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…r, gated on confirmed removal (review: #5847) Two related bugs in stop's staged-secret cleanup (Kong/Postgres/ Supavisor plaintext files under <workdir>/supabase/.temp/start-secrets): Go has no equivalent here (it never stages secrets on host disk), so neither is a parity question, but both are genuine TS-side correctness bugs. 1. Cleanup always used the invoking cwd's workdir, never the actual container's. Tearing down another project's containers via --all or --project-id orphaned that project's plaintext secret files on disk forever, since the removed containers can no longer be rediscovered via docker ps. Fixed by stamping each container with its own workdir label at creation time and reading it back through the existing single docker ps listing (no extra Engine API call), falling back to the caller's workdir only when the label is absent. 2. Cleanup ran via Effect.ensuring keyed off the pre-teardown listing, so it fired even when the stop stage itself failed and container prune never ran -- deleting secrets for containers that were never actually removed and might still be live. Fixed by moving the capture point to fire only once container prune has confirmed removal, while still firing on a later volume/network prune failure (preserving the existing 983eab9 fix for that case). Applied the same fix to start's own rollback path (start.rollback.ts), which had the identical pattern.
There was a problem hiding this comment.
💡 Codex Review
cli/apps/cli/src/shared/functions/serve.ts
Line 1512 in f799a9f
Checked the Go paths: supabase start calls ServeFunctions directly (apps/cli-go/internal/start/start.go:1101-1108), while only standalone functions serve's restartEdgeRuntime reloads Kong afterward (apps/cli-go/internal/functions/serve/serve.go:124-130). Because this shared helper is also used by legacyStartEdgeRuntimeContainer, supabase start --exclude kong still attempts docker exec <kong> kong reload and emits a warning for an intentionally excluded gateway; move the reload back to the standalone serve wrapper or gate it away for start.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
#5847) Go's Config.Load decodes and validates every Viper-bound field -- including realtime.ip_version, realtime.max_header_length, api.max_rows, api.enabled, api.tls.enabled, storage.vector.enabled, and the Mailpit/analytics ports -- in one atomic pass before any Docker work runs (pkg/config/config.go:749-756, called before ensureImagesCached at start.go:289). This port's pooler/Edge Runtime overrides already matched that by wrapping in Effect.try + LegacyStartInvalidConfigError, but these 10 sibling fields threw raw exceptions instead -- becoming untyped Effect defects that bypass withJsonErrorHandling's Effect.catch (breaking --output-format json/ stream-json error formatting) and wasting image pre-pull work on a config Go would have already rejected. Extracted the existing inline Effect.try pattern into a shared wrapConfigOverride helper, applied it to all 10 previously-unwrapped call sites plus the 4 existing pooler ones (removing the duplicated boilerplate), and added regression tests for the malformed-value path on 4 representative fields.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 99f0bedc22
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Go's Config.Validate gates the Kong TLS disk-read on len(CertPath) > 0 / len(KeyPath) > 0 (pkg/config/config.go:1006-1027), treating an empty string the same as absent and falling back to Kong's embedded default cert/key. This port's guard only checked !== undefined, so an explicit cert_path = ""/key_path = "" passed through and tried to read <workdir>/supabase/ as a file, aborting start. The correct check already existed in legacy-local-config-values.ts's readApiTlsFiles (used by status/stop) -- this just brings start's guard in line with it.
Go's DockerNetworkCreateIfNotExists checks isUserDefined(mode) first and skips creation entirely for default/bridge/host/none network modes (docker.go:63-77), since those already exist and can't be created -- verified live that docker network create host errors with "operation is not permitted on predefined host network". This port's legacyEnsureStartNetwork had no equivalent guard, so --network-id host (or bridge/none/default) failed the whole start run instead of reusing the existing network. Reused the existing isUserDefinedDockerNetwork check from shared/functions/deploy.ts (exported it) rather than reimplementing it.
… (review: #5847) Go's Run() checks ignoreHealthCheck && IsUnhealthyError(err) against whatever run() returns as a whole (start.go:73-81), and run() propagates StartDatabase's health-wait failure immediately (start.go:294-296), before any other service starts -- there is no Postgres-specific carve-out in Go's classification (db/start/ start.go:227-231 is service-agnostic). With --ignore-health-check set and Postgres itself timing out, Go skips rollback, prints a warning, and still reaches the same "Started..."/status tail every other path reaches. This port's Postgres wait sat inside bringUp's unconditional-rollback tapError, so a Postgres timeout always hard-failed regardless of the flag -- the existing ignore-health-check downgrade mechanism was only wired up for the later bulk (non-Postgres) health check. Routed Postgres's wait through the same classification/downgrade branch, short-circuiting the rest of bring-up (no other services, no seeding, no cli_stack_started) on the ignored path while still reaching the final tail, matching Go exactly.
…ck (review: #5847) hasLocalImage's docker image inspect call only awaited the exit code but left stdout/stderr at the default "pipe", and docker image inspect writes the full image JSON to stdout on a cache hit -- large enough to exceed the OS pipe buffer for images with a non-trivial layer history, deadlocking the child against an unread pipe. Every other exit-code- only containerCliExitCode call site in this codebase already passes stdin/stdout/stderr: "ignore" for exactly this reason (legacy-docker-remove-all.ts, legacy-pgdelta.seam.layer.ts); this call site was the one outlier still at the unsafe default.
…(review: #5847) An earlier fix (99f0bed) stamped every legacyStartContainer-created container with a com.supabase.cli.workdir label so a later stop from a different cwd could reclaim that container's own staged-secret directory instead of falling back to the caller's cwd. Edge Runtime's container is created via a separate docker run in shared/functions/ serve.ts rather than legacyStartContainer, so it never got the label -- even though it stages the same kind of secret-like artifacts (JWT/env/multiline-env/serve-main template) under the same <workdir>/supabase/.temp/start-secrets/<containerId> convention and carries the project label that puts it in scope for stop's cleanup scan. This extends the same label to Edge Runtime's docker run call.
|
@codex review |
…5847) apps/cli/AGENTS.md requires every exported token from legacy/ to carry the Legacy/legacy prefix at the START of the name. buildLegacyGotrueEnv, buildLegacyGotrueContainerSpec, buildLegacyPgMetaContainerSpec, buildLegacyStudioEnv, buildLegacyStudioContainerSpec, and buildLegacyStartContainerCreateArgs had it embedded mid-name instead, unlike the rest of start/services/*.ts in this same PR (legacyBuild*). Pure mechanical rename, no behavior change. buildLegacyDockerArgs in legacy-docker-run.args.ts has the same inversion but predates this PR and is shared by other already-merged commands, so it's left for a separate cleanup.
…x (review: #5847) Go's Config.Load passes .temp/*-version pin file contents straight into replaceImageTag, which only trims whitespace and never adds a v prefix (pkg/config/utils.go:81-84). legacyResolvePinnedImage instead routed the same raw pin through tagForServiceVersion, which does prepend v for several services -- so a storage-version pin of 1.2.3 produced :v1.2.3 instead of Go's :1.2.3. The services command already gets this right via normalizeVersionTags: false; start.gates.ts reinvented the same case and got it wrong. Also fixes the existing integration test that had locked in the wrong :v1.2.3 assertion.
…isabled (review: #5847) Go's Config.Validate only runs Auth.ThirdParty.validate() (the required-field/multi-provider checks) inside "if c.Auth.Enabled" (config.go:1087,1151-1153), and functions serve's own JWKS resolution discards ResolveJWKS's error unconditionally regardless of auth.enabled (serve.go:141). This port's resolveAuthArtifacts called the strict resolveThirdPartyIssuerUrl unconditionally, so a malformed or multi-enabled third-party provider config aborted functions serve even with auth disabled, where Go tolerates it. start's own JWKS resolution already had the correct pattern (legacyResolveLocalJwks in legacy-local-config-values.ts, gating on auth.enabled and falling back to the unchecked builder) -- this carries the same fix to standalone functions serve's resolveAuthArtifacts.
There was a problem hiding this comment.
💡 Codex Review
cli/apps/cli/src/shared/functions/serve.ts
Line 1507 in 9c3aae9
startEdgeRuntimeContainer is now also called by supabase start, but this banner belongs to the functions serve restart wrapper in Go: start.go calls serve.ServeFunctions directly, while only restartEdgeRuntime prints Setting up Edge Functions runtime.... With edge_runtime.enabled, supabase start now emits an extra stderr line (including non-text output modes), breaking the Go-compatible command transcript; move the print back to the functions serve wrapper.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…re needed (review: #5847) writeDockerMultilineEnvScript's env.length === 0 early return ran before the self-healing rm(dir) cleanup, so a run with no multiline secrets never reclaimed a stale multiline-env directory a previous run (or a crash) left behind -- leaving plaintext JWT/custom secret material on host disk until the next stop/failed-start rollback. Moved the cleanup to run unconditionally before the early return.
…iew: #5847) db.root_key is unmodeled by @supabase/config's schema, so a bare root_key = 12345 in config.toml parses to a JS number. The as string cast here let that number flow unguarded into legacyIsEncryptedSecret, which calls .startsWith(...) and throws an uncaught TypeError instead of a config-load failure. Go's DecryptSecretHookFunc passes non-string kinds through untouched and mapstructure then rejects the scalar with a clean "failed to parse config" error (verified via a live repro). Replaced the cast with a runtime type guard that throws LegacyConfigValidateError, matching this file's existing pattern for the same class of unmodeled-field decode failure, and removes a production `as` cast this repo's own conventions ban.
…ount path (review: #5847) Go's PopulatePerFunctionConfigs unconditionally prints "Skipped serving Function: <slug>" to stderr for every disabled function, called from two independent sites -- ServeFunctions (gated on Edge Runtime enabled) and, separately, Studio's bind-mount setup in start.go:1149-1159, which is not gated on Edge Runtime at all. So when Edge Runtime is disabled/excluded but Studio is enabled, Go still emits the message via the Studio path alone. resolveFunctionBindMounts (the shared helper backing Studio's bind mounts) silently dropped disabled functions with no output. Added the matching stderr warning, matching Go's message exactly, including the double-print quirk when both Edge Runtime and Studio are enabled (both call sites fire in Go, so this port reproduces that rather than deduping it).
|
@codex review |
…review: #5847) legacyStart resolved local config values once early on to build every container spec, but the success-path status print called legacyResolveStatusLocalState -> legacyResolveLocalConfigValues again after bring-up/health checks completed. For auth.signing_keys_path configured with unset anon/service-role keys, legacyGenerateAsymmetricGoJwt embeds a time-dependent exp claim, so the second call mints a different signed token than the one already baked into the running Kong/GoTrue containers -- and since Kong does literal string matching on these key values (including Realtime's apikey query param), a printed key that doesn't byte-match the running one can genuinely fail client requests. Go generates these keys exactly once per process (Config.Validate()) and reuses the same in-memory value for both container config and the final print. Threaded the already-resolved values through as an optional precomputedLocal param on legacyResolveStatusLocalState, reused only by the success path -- the already-running branch correctly still recomputes, since Go's own status.Run() genuinely calls LoadConfig a second time in that branch too.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 38c452030d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
) inferFunctionsManifest called findProjectPaths with no search option, defaulting to ancestor-directory climbing. The rest of start deliberately loads config with search: false for this exact same workdir (legacy-local-project-context.ts), since --workdir/ SUPABASE_WORKDIR pointing at a directory with no config.toml of its own is a legitimate, reachable state -- an absent config is not a failure. Letting function discovery climb ancestors let an unrelated ancestor project's supabase/functions silently win instead of respecting the exact requested workdir, breaking Edge Runtime/Studio function bind mounts. Go's own ChangeWorkDir only climbs ancestors when workdir is unset; once resolved, function discovery reads paths relative to the already-fixed cwd with no further ancestor search -- matching the design already enforced elsewhere in this command. Added an optional search option to inferFunctionsManifest (default unchanged for every other caller) and passed search: false from start's call site.
|
@codex review |
|
Codex Review: Didn't find any major issues. Keep it up! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
…3-port-supabase-start
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 998d261291
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| * non-blank. Any read error (including not-exist) resolves to "" for that | ||
| * file, matching Go's `err == nil && len(version) > 0` gate. | ||
| */ | ||
| export const readLegacyServiceVersionOverrides = Effect.fnUntraced(function* ( |
There was a problem hiding this comment.
Rename the legacy version override export
apps/cli/AGENTS.md says every export from src/legacy/** must start with Legacy or legacy to preserve legacy/next import isolation, but this new helper is exported as readLegacyServiceVersionOverrides. Because it is imported outside this file, it becomes another unprefixed legacy symbol offered to callers; please rename the export to start with legacy/Legacy.
Useful? React with 👍 / 👎.
| ...(spec.extraHosts ?? []).flatMap((host) => ["--add-host", host]), | ||
| "--network", | ||
| spec.networkId, | ||
| ...(spec.networkAliases ?? []).flatMap((alias) => ["--network-alias", alias]), |
There was a problem hiding this comment.
Suppress aliases for built-in network modes
When users set --network-id to a built-in network such as host, bridge, default, or none, the new create path now skips docker network create but still passes --network-alias for every service. Docker rejects network-scoped aliases on non-user-defined networks, so these otherwise-valid network overrides still fail at container creation; gate aliases with the same user-defined-network check.
Useful? React with 👍 / 👎.
| const { config, projectId, projectEnvValues } = context; | ||
| // Single source resolved once, fed to both Kong's template mounts and GoTrue's env builder — | ||
| // see {@link legacyResolveAuthEmail}'s doc comment. | ||
| const resolvedEmail = legacyResolveAuthEmail( |
There was a problem hiding this comment.
Wrap email override resolution before start
When auth is disabled and a malformed auth.email override is present, for example SUPABASE_AUTH_EMAIL_OTP_LENGTH=abc or an invalid email boolean override, the earlier legacyResolveLocalConfigValues call does not enter the auth-email validation block, so this unwrapped resolver becomes the first parser. Because legacyResolveAuthEmail can throw synchronously here, the command raises an Effect defect instead of the typed LegacyStartInvalidConfigError/JSON error path Go's config-load failure maps to; wrap this resolution like the other start-time overrides before using it for Kong/GoTrue.
Useful? React with 👍 / 👎.
…t bump (ci: types:check, unit) effect 4.0.0-beta.97 (pulled in via develop's dependency bump, #5900) removes Schedule.both in favor of n-ary Schedule.max/min. The health probe's schedule combinator (1s spacing intersected with a hard timeoutSeconds cap) now uses Schedule.max([...]), matching the changelog's stated replacement for "combining schedules by their slowest delay" with the same continue-while-both-still-recur semantics Go's backoff.WithMaxRetries(NewConstantBackOff, maxRetries) requires.
Only called internally within services.shared.ts; knip flagged it as an unused export after start.gates.ts stopped wrapping pinned versions through it.
…out (ci: unit) These two tests force every docker pull to fail, driving the real DOCKER_PULL_RETRY_DELAYS_MS backoff (4s + 8s) to exhaustion across all 3 registry candidates (~36s of real sleep) — comfortably exceeds Vitest's 5s default test timeout. Not a flake: deterministic given the real retry/registry-fallback loop these tests intentionally exercise.
What changed
Ports
supabase startfrom a Go-binary proxy to native TypeScript in the legacy CLI shell (CLI-1323), continuing the local-dev-stack shell migration alongside the already-portedstop/status/db push/db reset/db start.Talks directly to Docker/Podman via subprocess, mirroring Go's sequential per-container
DockerStart— no Docker Compose, and no@supabase/stack/effectorchestration (that runtime targets a deliberately different local-dev product and would silently manage the wrong set of containers). Brings up all 14 containers in Go's real start order, with per-service config-boolean +--excludegating, Go-byte-exact env/image resolution, a bulk health-check phase (Docker healthcheck for most services, an HTTP-HEAD-through-Kong bypass for PostgREST/Edge Runtime), and full rollback on any bring-up failure.Also natively implements the fresh-volume DB schema/migration/seed pipeline, Edge Runtime container bring-up, and fresh-volume storage bucket seeding — previously tracked as out-of-scope follow-ups for this port, now closed. Only the linked-project version-check suggestion (a best-effort "update available" hint with zero Management API dependency otherwise) remains out of scope by design.
Why
Continues the legacy-shell migration from Go-binary-proxy commands to native TypeScript, closing CLI-1323.
Reviewer-relevant context
legacyParseGoDuration(theconfig.tomlduration-string parser feeding Go-parity env vars likeGOTRUE_SESSIONS_TIMEBOX) silently accepted a malformed duration with no digits (a bare unit like"s", or a lone"."with no digits on either side) and returned0instead of erroring like Go's realtime.ParseDuration— both cases now throw Go's exacttime: invalid duration "..."message.start.services.ts's descriptiveenabledGatemetadata for Mailpit referenced the deprecatedinbucketconfig section instead of itslocal_smtprename. Fixed, and added a mechanical cross-check test that evaluates every service'senabledGatestring againststart.gates.ts's real computed gate across a battery of synthetic configs, so a future drift between the two fails loudly instead of silently.[api.tls] enabled = truelocal stacks (self-signed Kong cert) — the local-Kong-CA-trust mechanism already used byseed buckets/storage/db resetis now wired intostart's health-check HTTP client too.Note
This branch is currently 9 commits behind
develop(unrelateddeps/dockerbump commits) — opening as draft to get CI/review visibility; happy to rebase before this comes out of draft.