Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions apps/cli/src/legacy/commands/functions/serve/SIDE_EFFECTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,13 @@ validation is performed on the discovered URLs, also matching the Go CLI.

## Exit Codes

| Code | Condition |
| ---- | ---------------------------------------------------------------------- |
| `0` | clean shutdown after `SIGINT`, `SIGTERM`, or stdin close |
| `1` | Docker unavailable / `docker info` fails |
| `1` | local DB container is not running |
| `1` | invalid inspect flag combination or invalid project/auth config |
| `1` | env file, signing key, import map, or function bind resolution failure |
| `1` | edge-runtime container startup, log streaming, or restart loop failure |
| Code | Condition |
| ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `0` | clean shutdown after `SIGINT`, `SIGTERM`, or stdin close |
| `1` | local DB container is not running, or the Docker daemon is unreachable (surfaces from the DB inspect as `failed to inspect service: …` plus the Docker Desktop install suggestion) |
| `1` | invalid inspect flag combination or invalid project/auth config |
| `1` | env file, signing key, import map, or function bind resolution failure |
| `1` | edge-runtime container startup, log streaming, or restart loop failure |

## Telemetry Events Fired

Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,11 @@ export interface LegacyEdgeRuntimeBringUpInput {
/** `config.edge_runtime.inspector_port` — only published when `inspectMode` is set, which `start` never does (Go's `serve.RuntimeOption{}` zero value). */
readonly edgeRuntimeInspectorPort: number;
/**
* `config.edge_runtime.secrets`, already unwrapped/uppercased — build via
* `shared/functions/serve.ts`'s exported `toPlainEdgeRuntimeConfig(config.edge_runtime).secrets`
* rather than re-deriving the `Redacted`-unwrap/uppercase logic here.
* `config.edge_runtime.secrets`, already unwrapped — keys verbatim, empty

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale doc comment: this says "keys verbatim", but the final behavior (restored in 817e60b) is that toPlainEdgeRuntimeConfig uppercases secret names — name.toUpperCase() in shared/functions/serve.ts, matching Go's strings.ToUpper viper workaround (pkg/config/config.go:766-771). This line was written for the earlier verbatim-keys revision of this PR and no longer describes what the referenced helper does. Should read something like "keys UPPERCASED (Go's strings.ToUpper config-load workaround), empty and unresolved-env() values filtered out (Go's SHA256 > 0 gate)".

* and unresolved-`env()` values filtered out (Go's `SHA256 > 0` gate).
* Build via `shared/functions/serve.ts`'s exported
* `toPlainEdgeRuntimeConfig(config.edge_runtime).secrets` rather than
* re-deriving the `Redacted`-unwrap/zero-hash-filter logic here.
*/
readonly edgeRuntimeSecrets: Readonly<Record<string, string>>;
/**
Expand Down
10 changes: 9 additions & 1 deletion apps/cli/src/legacy/commands/start/start.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4515,7 +4515,7 @@ content_path = "./templates/custom_notice.html"
() => {
const { layer, child } = setup({
configContents:
'project_id = "demo"\n[edge_runtime.secrets]\nMY_SECRET = "shh-do-not-tell"\n',
'project_id = "demo"\n[edge_runtime.secrets]\nMY_SECRET = "shh-do-not-tell"\nmy_lower_secret = "keep-me"\nEMPTY_SECRET = ""\n',
});
return Effect.gen(function* () {
yield* legacyStart(flags());
Expand All @@ -4528,6 +4528,14 @@ content_path = "./templates/custom_notice.html"
expect(envFilePath).toBeDefined();
const envFileContent = readFileSync(envFilePath ?? "", "utf-8");
expect(envFileContent).toContain("MY_SECRET=shh-do-not-tell");
// Names reach the container UPPERCASED — Go's config loader applies
// `strings.ToUpper` to every secret key (`pkg/config/config.go:766-771`)
// — and empty values are skipped — Go's `set.ListSecrets` SHA256>0
// gate (`internal/secrets/set/set.go:48-52`), shared with
// `functions serve` via `toPlainEdgeRuntimeConfig`.
expect(envFileContent).toContain("MY_LOWER_SECRET=keep-me");
expect(envFileContent).not.toContain("my_lower_secret=");
expect(envFileContent).not.toContain("EMPTY_SECRET=");
}).pipe(Effect.provide(layer));
},
);
Expand Down
28 changes: 26 additions & 2 deletions apps/cli/src/legacy/shared/legacy-docker-suggest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,34 @@ export const LEGACY_SUGGEST_DOCKER_INSTALL =
* subprocess-stderr equivalent of Go's `client.IsErrConnectionFailed` (which
* inspects the Docker API client error). The docker / podman CLIs print
* "Cannot connect to the Docker daemon …" / "Cannot connect to Podman …" (often
* followed by "Is the docker daemon running?") when the socket is down.
* followed by "Is the docker daemon running?") when the socket is down, and
* "permission denied while trying to connect to the Docker daemon socket …"
* when the socket exists but the user can't open it — the pinned Docker SDK
* classifies that permission error as a connection failure too
* (`client/request.go:144-152`, docker/docker v28.5.2: `os.IsPermission` →
* `errConnectionFailed`), so Go surfaces the install hint for it as well.
*
* Also matches `spawnContainerCli`'s runtime-not-found message
* ("docker: command not found …", `legacy-container-cli.ts`): a missing
* container-CLI binary is the shell-out equivalent of Go's missing daemon
* socket — on a machine with no Docker installed, Go's socket dial fails,
* `client.IsErrConnectionFailed` fires, and the install hint is exactly the
* guidance that case needs (`misc.go:155-166`).
*
* "error during connect" is the pinned SDK's uniform outer wrap for every
* transport-level failure (`client/request.go:175-185`, docker/docker
* v28.5.2) and therefore the closest 1:1 stderr equivalent of
* `errConnectionFailed`'s breadth. It notably covers Windows daemon-down,
* where a failed npipe open is wrapped as "error during connect: this error
* may indicate that the docker daemon is not running: …" (elevated,
* `request.go:181`) or "… the docker client must be run with elevated
* privileges …" (non-elevated, `request.go:178`) — word orders none of the
* Unix-socket phrases match. The inner OS text ("The system cannot find the
* file specified") is localized per the SDK's own comment
* (`request.go:172-174`), so it is deliberately not matched.
*/
export function legacyIsDockerDaemonUnreachable(stderr: string): boolean {
return /cannot connect to the docker daemon|cannot connect to podman|is the docker daemon running/iu.test(
return /cannot connect to the docker daemon|cannot connect to podman|is the docker daemon running|permission denied while trying to connect|docker: command not found|error during connect/iu.test(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fragile coupling: docker: command not found here is a hardcoded substring of RUNTIME_NOT_FOUND_MESSAGE in legacy-container-cli.ts (which is module-private). If that message is ever reworded, this matcher silently stops classifying the no-runtime case — and the unit test won't catch the drift because it also hardcodes the full string rather than importing it. Consider exporting the constant (or a legacyIsContainerRuntimeNotFound predicate) from legacy-container-cli.ts and matching/testing against it directly, so the producer and the classifier can't drift apart.

stderr,
);
}
35 changes: 31 additions & 4 deletions apps/cli/src/legacy/shared/legacy-docker-suggest.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,39 @@ describe("legacyIsDockerDaemonUnreachable", () => {
// Case-insensitive + the podman phrasing.
expect(legacyIsDockerDaemonUnreachable("cannot connect to podman")).toBe(true);
expect(legacyIsDockerDaemonUnreachable("Is the docker daemon running?")).toBe(true);
// Socket permission errors are connection failures in the pinned Docker
// SDK (`client/request.go:144-152`, v28.5.2: `os.IsPermission` →
// `errConnectionFailed`), so Go attaches the install hint for them too.
expect(
legacyIsDockerDaemonUnreachable(
"permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock",
),
).toBe(true);
// No container runtime installed at all (`spawnContainerCli`'s
// runtime-not-found message) — the shell-out equivalent of Go's missing
// daemon socket, which `IsErrConnectionFailed` also classifies as a
// connection failure, so the install hint applies.
expect(
legacyIsDockerDaemonUnreachable(
"docker: command not found (podman also not found) — install Docker Desktop or Podman and ensure it is on PATH",
),
).toBe(true);
// Windows daemon-down: a failed npipe open is wrapped "error during
// connect" by the pinned SDK (`client/request.go:175-185`, v28.5.2) —
// both the elevated and the non-elevated variants.
expect(
legacyIsDockerDaemonUnreachable(
'error during connect: this error may indicate that the docker daemon is not running: Get "http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.51/containers/supabase_db_test/json": open //./pipe/docker_engine: The system cannot find the file specified.',
),
).toBe(true);
expect(
legacyIsDockerDaemonUnreachable(
"error during connect: in the default daemon configuration on Windows, the docker client must be run with elevated privileges to connect: open //./pipe/docker_engine: Access is denied.",
),
).toBe(true);
});

it("does not flag an unrelated inspect failure (e.g. a permission error)", () => {
expect(legacyIsDockerDaemonUnreachable("permission denied while trying to connect")).toBe(
false,
);
it("does not flag an unrelated inspect failure", () => {
expect(legacyIsDockerDaemonUnreachable("Error: No such container: supabase_db_x")).toBe(false);
expect(legacyIsDockerDaemonUnreachable("")).toBe(false);
});
Expand Down
5 changes: 3 additions & 2 deletions apps/cli/src/legacy/shared/legacy-local-config-values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2984,8 +2984,9 @@ export function legacyResolveLocalConfigValues(
* JWKS fetch) there would tax two commands that never render a JWKS. This is a standalone sibling
* `start`-only callers invoke separately, alongside (not instead of) `legacyResolveLocalConfigValues`.
*
* Divergences from the structurally similar (but functionally unrelated) `resolveAuthArtifacts` in
* `shared/functions/serve.ts` (Go's equivalent call site for THAT function is
* Divergences from the structurally similar (but functionally unrelated)
* `resolveLocalAuthArtifacts`/`finalizeAuthArtifacts` pair in
* `shared/functions/serve.ts` (Go's equivalent call site for THAT pair is
* `internal/functions/serve/`, out of scope for this port) — deliberately NOT copied here:
* - a remote-JWKS fetch failure is a hard, propagating error here (matching `start.go:274-277`
* returning the error outright); `serve.ts` instead swallows the failure and continues with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3018,7 +3018,7 @@ describe("legacyResolveLocalJwks", () => {
});

// The key divergence from `shared/functions/serve.ts`'s own (unrelated)
// `resolveAuthArtifacts`: Go's `start` treats a remote-JWKS fetch failure as a hard,
// `finalizeAuthArtifacts`: Go's `start` treats a remote-JWKS fetch failure as a hard,
// command-failing error (`internal/start/start.go:274-277`) — `legacyResolveLocalJwks`
// must propagate it too, not swallow it and continue with zero remote keys.
it("fails the whole resolution when the remote JWKS fetch fails, unlike functions serve's leniency", async () => {
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/shared/functions/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1327,7 +1327,7 @@ export const runChildProcess = Effect.fnUntraced(function* (
return { exitCode, stdout, stderr };
});

export const isDockerRunning = Effect.fnUntraced(function* () {
const isDockerRunning = Effect.fnUntraced(function* () {
const result = yield* runChildProcess("docker", ["info"], {
stdout: "ignore",
stderr: "ignore",
Expand Down
Loading
Loading