feat(cli): port supabase start command to native TypeScript#5847
feat(cli): port supabase start command to native TypeScript#5847Coly010 wants to merge 50 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. |
|
@codex review |
…in-container behavior (review: #5847) Go writes Kong's kong.yml and Postgres's pgsodium_root.key inside the container via root-authored heredocs, landing at world-readable 0644. This port instead stages them on the host at 0600 and bind-mounts them read-only; on real Linux/Podman bind mounts that host uid/mode is preserved verbatim, so Kong (uid 100) and Postgres's post-privilege-drop postgres user (uid 100) get EACCES reading them. The enclosing 0700 per-container directory is unchanged and remains the actual host-side protection via ancestor-directory execute/search checks, so this does not reopen host-level access.
|
@codex review |
…5847) Go's DockerImagePullWithRetry(ctx, image, 2) retries any non-canceled pull failure unconditionally, twice, with escalating 4s/8s backoff. This port allowed only one retry at a flat 500ms and gated it on the failure text matching a local regex allowlist, so transient errors worded differently than expected (or a second consecutive failure) would give up where Go would retry and often recover. Removed the message-pattern gating and matched Go's retry count/backoff exactly, with a new unit test covering a non-matching error message. Also bumped one existing integration test's timeout (10s -> 45s, matching this file's own convention for multi-candidate scenarios) since exhausting all 3 registry candidates now takes ~36s of real backoff, same as Go.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f53bf09a5b
ℹ️ 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".
…review: #5847) Go's DockerStart sets both the CLI project label and the compose project label on config.Labels before creating anything, then passes that same map into DockerNetworkCreateIfNotExists, so Go's network carries both labels just like its containers and volumes. This port's containers/volumes already merge both labels correctly, but the network-creation call site only passed the CLI label, leaving fresh start networks outside compose-project-label-based grouping/tooling.
…th minutes/hours (review: #5847) Go's time.Duration.String() computes the fractional-seconds remainder from the whole nanosecond count before peeling off minutes/hours, so it always survives in the output (e.g. 1h0.5s -> 1h0m0.5s). This port built the seconds component as a bare integer whenever an hours/minutes prefix was present, silently truncating any sub-second remainder. auth.sessions.timebox, auth.sms.max_frequency, and similar config duration fields are formatted through this function before being passed as GoTrue env vars, so a configured value like "1h0.5s" was regressing to a shorter effective timeout/rate-limit than Go CLI sets.
|
@codex review |
…eview: #5847) Go's ServeFunctions (the actual Edge Runtime bring-up) never reloads Kong; only restartEdgeRuntime does, after ServeFunctions succeeds, and that wrapper is used exclusively by functions serve's own restart loop. start.go calls serve.ServeFunctions directly, bypassing restartEdgeRuntime entirely, so Go's start never reloads Kong. This port had the reload baked into startEdgeRuntimeContainer (the ServeFunctions port), so start's Edge Runtime bring-up inherited a Kong reload Go's start never performs -- which can race Kong's own startup on a cold start and emit a misleading warning. Moved the reload into startEdgeRuntime (the restartEdgeRuntime port), so only functions serve's restart path triggers it, matching Go exactly.
…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".
| Command.withShortDescription("Start local Supabase stack"), | ||
| Command.withHandler((flags) => legacyStart(flags)), | ||
| Command.withHandler((flags) => | ||
| legacyStart(flags).pipe(withLegacyCommandInstrumentation({ flags }), withJsonErrorHandling), |
There was a problem hiding this comment.
Reject unsupported --output for start
When users pass -o/--output to supabase start, this wrapper uses withLegacyCommandInstrumentation's default allowed output formats, so the global LegacyOutputFlag is accepted and then ignored by the handler. The Go command registers only exclude, ignore-health-check, and hidden preview in apps/cli-go/cmd/start.go:55-60, so supabase start -o json should fail during flag parsing; after this port it proceeds to Docker startup instead of rejecting the unsupported flag.
Useful? React with 👍 / 👎.
| const configFunctions = yield* inferFunctionsManifest({ | ||
| cwd: cliConfig.workdir, | ||
| config: { ...config, functions: configDeclaredFunctions }, |
There was a problem hiding this comment.
Keep function discovery within the selected workdir
When --workdir points at a directory without its own supabase/config.toml under another Supabase project, this call lets inferFunctionsManifest climb ancestors via findProjectPaths(options.cwd) and use the ancestor project as the functions discovery root. The rest of start deliberately loads config with search:false for exact-workdir parity, so matching function slugs in the requested workdir can inherit ancestor filesystem defaults such as deno.json/import-map settings and then fail or mount the wrong paths during Studio/Edge Runtime setup.
Useful? React with 👍 / 👎.
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.