From 510ec2d0053add135cec16cb55666d730f8fa1fe Mon Sep 17 00:00:00 2001 From: 7ttp <117663341+7ttp@users.noreply.github.com> Date: Thu, 30 Jul 2026 18:17:23 +0530 Subject: [PATCH 1/3] fix --- packages/stack/docs/architecture.md | 47 ++++++------ .../stack/src/StackLifecycleCoordinator.ts | 4 +- packages/stack/src/StackPreparation.ts | 71 ++++++++++--------- packages/stack/src/effect.ts | 1 + packages/stack/src/errors.ts | 25 +++++-- packages/stack/src/prefetch.ts | 10 +-- packages/stack/src/prefetch.unit.test.ts | 35 +++++++++ packages/stack/src/resolve.ts | 42 ++++++----- packages/stack/src/resolve.unit.test.ts | 65 +++++++++++++++++ packages/stack/tests/helpers/mocks.ts | 9 ++- 10 files changed, 225 insertions(+), 84 deletions(-) create mode 100644 packages/stack/src/resolve.unit.test.ts diff --git a/packages/stack/docs/architecture.md b/packages/stack/docs/architecture.md index 75d39fcf93..423c79459c 100644 --- a/packages/stack/docs/architecture.md +++ b/packages/stack/docs/architecture.md @@ -326,11 +326,11 @@ The download is written to a temporary file (`_download.tar` or `_download.zip`) --- -### resolveService — binary-first Docker fallback +### resolveService — binary-first, mode-aware Docker fallback **File:** `src/resolve.ts` -`resolveService` is a thin helper that wraps `BinaryResolver.resolve()` and implements the binary-first, Docker-fallback strategy used by `StackPreparation` and therefore shared by both `stack.start()` and `prefetch()`. +`resolveService` is a thin helper that wraps `BinaryResolver.resolve()` and implements the binary-first resolution strategy used by `StackPreparation` and therefore shared by both `stack.start()` and `prefetch()`. The Docker fallback is **mode-aware**: it only applies in `"auto"` mode — `"native"` mode propagates resolution failures instead of silently flipping the service onto Docker (supabase/cli#5787). #### ServiceResolution type @@ -344,34 +344,39 @@ This discriminated union is the canonical output of resolution: downstream code #### Resolution logic -`resolveService(resolver, service, version)` calls `resolver.resolve({ service, version })` and maps the result: +`resolveService(resolver, service, version, mode = "auto")` calls `resolver.resolve({ service, version })` and maps the result: - **Success** (binary found and extracted) → `{ type: "binary", path }`. -- **`BinaryNotFoundError`** (no native asset for this OS/arch) → `{ type: "docker", image }` using the default Docker image for the service and version. -- **`DownloadError`** (network or extraction failure) → `{ type: "docker", image }` — falls back to Docker rather than hard-failing. -- **`ChecksumMismatchError`** → propagates as a real error; a tampered or corrupted download is never silently replaced by Docker. +- **`BinaryNotFoundError`** (no native asset for this OS/arch) → in `"auto"` mode, `{ type: "docker", image }` using the default Docker image for the service and version; in `"native"` mode the error propagates. +- **`DownloadError`** (network or extraction failure) → in `"auto"` mode, `{ type: "docker", image }` rather than hard-failing; in `"native"` mode the error propagates. +- **`ChecksumMismatchError`** → propagates as a real error in every mode; a tampered or corrupted download is never silently replaced by Docker. ```ts export const resolveService = ( resolver: BinaryResolver["Service"], service: ServiceName, version: string, -): Effect.Effect => - resolver.resolve({ service, version }).pipe( - Effect.map((path): ServiceResolution => ({ type: "binary", path })), - Effect.catchTag("BinaryNotFoundError", () => - Effect.succeed({ - type: "docker", - image: dockerImageForService(service, version), - }), - ), - Effect.catchTag("DownloadError", () => - Effect.succeed({ - type: "docker", - image: dockerImageForService(service, version), - }), - ), + mode: "native" | "auto" = "auto", +): Effect.Effect< + ServiceResolution, + ChecksumMismatchError | BinaryNotFoundError | DownloadError +> => { + const nativeBinary = resolver + .resolve({ service, version }) + .pipe(Effect.map((path): ServiceResolution => ({ type: "binary", path }))); + if (mode === "native") { + return nativeBinary; + } + const dockerFallback = () => + Effect.succeed({ + type: "docker", + image: dockerImageForService(service, version), + }); + return nativeBinary.pipe( + Effect.catchTag("BinaryNotFoundError", dockerFallback), + Effect.catchTag("DownloadError", dockerFallback), ); +}; ``` --- diff --git a/packages/stack/src/StackLifecycleCoordinator.ts b/packages/stack/src/StackLifecycleCoordinator.ts index 0a8ba5bee1..03af2dba8e 100644 --- a/packages/stack/src/StackLifecycleCoordinator.ts +++ b/packages/stack/src/StackLifecycleCoordinator.ts @@ -252,7 +252,9 @@ export class StackLifecycleCoordinator extends Context.Service< Stream.mapError( (cause) => new StackBuildError({ - detail: "Failed to prepare stack assets", + // Carry the underlying failure text (e.g. which native binary + // is missing) — `.cause` alone never reaches CLI output. + detail: `Failed to prepare stack assets: ${cause.message}`, cause, }), ), diff --git a/packages/stack/src/StackPreparation.ts b/packages/stack/src/StackPreparation.ts index 1c3a856bee..678c71b9a9 100644 --- a/packages/stack/src/StackPreparation.ts +++ b/packages/stack/src/StackPreparation.ts @@ -1,7 +1,7 @@ import { Cause, Data, Effect, Exit, Layer, Queue, Context, Stream } from "effect"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import { BinaryResolver } from "./BinaryResolver.ts"; -import type { ChecksumMismatchError } from "./errors.ts"; +import type { BinaryNotFoundError, ChecksumMismatchError, DownloadError } from "./errors.ts"; import { DockerPullError } from "./errors.ts"; import type { ServiceResolution } from "./resolve.ts"; import { @@ -22,6 +22,13 @@ export interface StackPreparationInput { readonly mode?: "native" | "auto" | "docker"; } +/** `BinaryNotFoundError`/`DownloadError` surface only in `mode: "native"` (no Docker fallback there). */ +export type StackPreparationError = + | DockerPullError + | ChecksumMismatchError + | BinaryNotFoundError + | DownloadError; + export class ServiceDownloadStarted extends Data.TaggedClass("ServiceDownloadStarted")<{ readonly service: ServiceName; }> {} @@ -86,7 +93,7 @@ export const prepareAssetsWithDependencies = ( spawner: ChildProcessSpawner.ChildProcessSpawner["Service"], input?: StackPreparationInput, publishEvent?: (event: StackPreparationEvent) => Effect.Effect, -): Effect.Effect => +): Effect.Effect => Effect.gen(function* () { const versions = { ...DEFAULT_VERSIONS, ...input?.versions }; const services: ReadonlyArray = input?.services ?? SERVICE_NAMES; @@ -94,9 +101,7 @@ export const prepareAssetsWithDependencies = ( type Entry = readonly [ServiceName, ServiceResolution]; - const resolveService = ( - service: ServiceName, - ): Effect.Effect => { + const resolveService = (service: ServiceName): Effect.Effect => { let isDownloading = false; const markDownloadStart = () => Effect.sync(() => { @@ -120,6 +125,10 @@ export const prepareAssetsWithDependencies = ( ); } + // Docker-only services resolve to Docker in every mode, including "native". + // The native+docker-only-service CONFIG contract is enforced one layer up + // (`StackBuilder.validateResolvedConfig`), not here: `prefetch()` defaults + // to ALL services, so failing here would break `prefetch({ mode: "native" })`. if (dockerOnlyServices.has(service)) { return resolveDockerImageForService(spawner, service, versions[service], { onDownloadStart: markDownloadStart(), @@ -135,6 +144,7 @@ export const prepareAssetsWithDependencies = ( service, versions[service], markDownloadStart(), + mode, ).pipe( Effect.map((resolution): Entry => [service, resolution]), Effect.ensuring(markDownloadFinished()), @@ -157,10 +167,10 @@ export class StackPreparation extends Context.Service< { readonly prepare: ( input?: StackPreparationInput, - ) => Effect.Effect; + ) => Effect.Effect; readonly prepareEvents: ( input?: StackPreparationInput, - ) => Stream.Stream; + ) => Stream.Stream; } >()("stack/StackPreparation") { static layer: Layer.Layer< @@ -177,7 +187,7 @@ export class StackPreparation extends Context.Service< prepare: (input?: StackPreparationInput) => prepareAssetsWithDependencies(resolver, spawner, input), prepareEvents: (input?: StackPreparationInput) => - Stream.callback((queue) => + Stream.callback((queue) => prepareAssetsWithDependencies(resolver, spawner, input, (event) => Queue.offer(queue, event), ).pipe( @@ -268,34 +278,25 @@ const resolveServiceWithMetadata = ( service: ServiceName, version: string, onDownloadStart: Effect.Effect, -): Effect.Effect => - resolver.resolveWithMetadata({ service, version }, { onDownloadStart }).pipe( - Effect.map(({ path }): ServiceResolution => ({ type: "binary", path })), - Effect.catchTag("BinaryNotFoundError", () => - resolveDockerImageForService(spawner, service, version, { - onDownloadStart, - }).pipe( - Effect.map( - (image): ServiceResolution => ({ - type: "docker", - image, - }), - ), - ), - ), - Effect.catchTag("DownloadError", () => - resolveDockerImageForService(spawner, service, version, { - onDownloadStart, - }).pipe( - Effect.map( - (image): ServiceResolution => ({ - type: "docker", - image, - }), - ), - ), - ), + mode: "native" | "auto", +): Effect.Effect => { + const nativeBinary = resolver + .resolveWithMetadata({ service, version }, { onDownloadStart }) + .pipe(Effect.map(({ path }): ServiceResolution => ({ type: "binary", path }))); + // `mode: "native"` requires native binaries (README): resolution failures + // propagate instead of silently flipping the service onto Docker. + if (mode === "native") { + return nativeBinary; + } + const dockerFallback = () => + resolveDockerImageForService(spawner, service, version, { + onDownloadStart, + }).pipe(Effect.map((image): ServiceResolution => ({ type: "docker", image }))); + return nativeBinary.pipe( + Effect.catchTag("BinaryNotFoundError", dockerFallback), + Effect.catchTag("DownloadError", dockerFallback), ); +}; const runPullCommand = ( spawner: ChildProcessSpawner.ChildProcessSpawner["Service"], diff --git a/packages/stack/src/effect.ts b/packages/stack/src/effect.ts index 526813e741..72c94cb7f7 100644 --- a/packages/stack/src/effect.ts +++ b/packages/stack/src/effect.ts @@ -29,6 +29,7 @@ export { BinaryResolver } from "./BinaryResolver.ts"; export type { ServiceResolution } from "./resolve.ts"; export { resolveService } from "./resolve.ts"; +export type { StackPreparationError } from "./StackPreparation.ts"; export type { PrefetchOptions, PrefetchResult } from "./prefetch.ts"; export { prefetch } from "./prefetch.ts"; diff --git a/packages/stack/src/errors.ts b/packages/stack/src/errors.ts index 71bafcb46f..1d165f75bf 100644 --- a/packages/stack/src/errors.ts +++ b/packages/stack/src/errors.ts @@ -3,24 +3,41 @@ import { Data } from "effect"; export class BinaryNotFoundError extends Data.TaggedError("BinaryNotFoundError")<{ readonly service: string; readonly platform: string; -}> {} +}> { + override get message() { + return `No native ${this.service} binary is available for ${this.platform}. Native mode requires a native binary — use mode "auto" or "docker" to run this service with Docker.`; + } +} export class DownloadError extends Data.TaggedError("DownloadError")<{ readonly url: string; readonly cause: unknown; -}> {} +}> { + override get message() { + const cause = this.cause instanceof Error ? `: ${this.cause.message}` : ""; + return `Failed to download ${this.url}${cause}`; + } +} export class ChecksumMismatchError extends Data.TaggedError("ChecksumMismatchError")<{ readonly url: string; readonly expected: string; readonly actual: string; -}> {} +}> { + override get message() { + return `Checksum mismatch for ${this.url} (expected ${this.expected}, got ${this.actual})`; + } +} export class DockerPullError extends Data.TaggedError("DockerPullError")<{ readonly image: string; readonly detail: string; readonly cause: unknown; -}> {} +}> { + override get message() { + return this.detail; + } +} export class StackBuildError extends Data.TaggedError("StackBuildError")<{ readonly detail: string; diff --git a/packages/stack/src/prefetch.ts b/packages/stack/src/prefetch.ts index f086bd5102..9ab6fddc39 100644 --- a/packages/stack/src/prefetch.ts +++ b/packages/stack/src/prefetch.ts @@ -1,7 +1,9 @@ import { Effect } from "effect"; -import type { ChecksumMismatchError } from "./errors.ts"; -import type { DockerPullError } from "./errors.ts"; -import { type PreparedStackArtifacts, type StackPreparationInput } from "./StackPreparation.ts"; +import { + type PreparedStackArtifacts, + type StackPreparationError, + type StackPreparationInput, +} from "./StackPreparation.ts"; import { StackPreparation } from "./StackPreparation.ts"; import type { ServiceResolution } from "./resolve.ts"; @@ -14,7 +16,7 @@ const toPrefetchResult = (artifacts: PreparedStackArtifacts): PrefetchResult => export const prefetch = ( options?: PrefetchOptions, -): Effect.Effect => +): Effect.Effect => Effect.gen(function* () { const preparation = yield* StackPreparation; return yield* preparation.prepare(options).pipe(Effect.map(toPrefetchResult)); diff --git a/packages/stack/src/prefetch.unit.test.ts b/packages/stack/src/prefetch.unit.test.ts index 1cf509d074..5702c41561 100644 --- a/packages/stack/src/prefetch.unit.test.ts +++ b/packages/stack/src/prefetch.unit.test.ts @@ -279,6 +279,41 @@ describe("prefetch", () => { expect(events.at(-1)).toBe("PreparationCompleted"); }); + test("native mode fails on a missing binary instead of silently falling back to Docker", async () => { + const resolver = mockBinaryResolver({ failServices: ["auth"] }); + const spawner = mockSequenceSpawner([]); + + const layer = StackPreparation.layer.pipe( + Layer.provide(resolver.layer), + Layer.provide(spawner.layer), + ); + + const error = await Effect.runPromise( + prefetch({ mode: "native", services: ["auth"] }).pipe(Effect.provide(layer), Effect.flip), + ); + + expect(error._tag).toBe("BinaryNotFoundError"); + // The silent substrate flip is the bug: no Docker resolution may be attempted. + expect(spawner.spawned).toEqual([]); + }); + + test("native mode fails on a download error instead of silently falling back to Docker", async () => { + const resolver = mockBinaryResolver({ downloadErrorServices: ["postgres"] }); + const spawner = mockSequenceSpawner([]); + + const layer = StackPreparation.layer.pipe( + Layer.provide(resolver.layer), + Layer.provide(spawner.layer), + ); + + const error = await Effect.runPromise( + prefetch({ mode: "native", services: ["postgres"] }).pipe(Effect.provide(layer), Effect.flip), + ); + + expect(error._tag).toBe("DownloadError"); + expect(spawner.spawned).toEqual([]); + }); + test("uses docker for edge-runtime in auto mode even when a native binary exists", async () => { const resolver = mockBinaryResolver(); const spawner = mockSequenceSpawner([{ exitCode: 0 }]); diff --git a/packages/stack/src/resolve.ts b/packages/stack/src/resolve.ts index 813a7448f7..c18cce4740 100644 --- a/packages/stack/src/resolve.ts +++ b/packages/stack/src/resolve.ts @@ -1,6 +1,6 @@ import { Effect } from "effect"; import type { BinaryResolver } from "./BinaryResolver.ts"; -import type { ChecksumMismatchError } from "./errors.ts"; +import type { BinaryNotFoundError, ChecksumMismatchError, DownloadError } from "./errors.ts"; import type { ServiceName } from "./versions.ts"; import { dockerImageForService } from "./versions.ts"; @@ -10,26 +10,32 @@ export type ServiceResolution = /** * Resolve a service to either a native binary path or a Docker image. - * Tries BinaryResolver first; falls back to Docker on BinaryNotFoundError or DownloadError. - * ChecksumMismatchError is a real error and propagates. + * Tries BinaryResolver first; in `"auto"` mode BinaryNotFoundError/DownloadError + * fall back to Docker, while `"native"` mode propagates them — native requires + * native binaries. ChecksumMismatchError always propagates. */ export const resolveService = ( resolver: BinaryResolver["Service"], service: ServiceName, version: string, -): Effect.Effect => - resolver.resolve({ service, version }).pipe( - Effect.map((path): ServiceResolution => ({ type: "binary", path })), - Effect.catchTag("BinaryNotFoundError", () => - Effect.succeed({ - type: "docker", - image: dockerImageForService(service, version), - }), - ), - Effect.catchTag("DownloadError", () => - Effect.succeed({ - type: "docker", - image: dockerImageForService(service, version), - }), - ), + mode: "native" | "auto" = "auto", +): Effect.Effect< + ServiceResolution, + ChecksumMismatchError | BinaryNotFoundError | DownloadError +> => { + const nativeBinary = resolver + .resolve({ service, version }) + .pipe(Effect.map((path): ServiceResolution => ({ type: "binary", path }))); + if (mode === "native") { + return nativeBinary; + } + const dockerFallback = () => + Effect.succeed({ + type: "docker", + image: dockerImageForService(service, version), + }); + return nativeBinary.pipe( + Effect.catchTag("BinaryNotFoundError", dockerFallback), + Effect.catchTag("DownloadError", dockerFallback), ); +}; diff --git a/packages/stack/src/resolve.unit.test.ts b/packages/stack/src/resolve.unit.test.ts new file mode 100644 index 0000000000..1ac91b111d --- /dev/null +++ b/packages/stack/src/resolve.unit.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, test } from "vitest"; +import { Effect } from "effect"; +import { mockBinaryResolver } from "../tests/helpers/mocks.ts"; +import { BinaryResolver } from "./BinaryResolver.ts"; +import { toStackError } from "./errors.ts"; +import { resolveService } from "./resolve.ts"; +import { DEFAULT_VERSIONS } from "./versions.ts"; + +const run = ( + effect: Effect.Effect, + layer: ReturnType["layer"], +) => Effect.runPromise(effect.pipe(Effect.provide(layer))); + +describe("resolveService", () => { + test("native mode propagates BinaryNotFoundError instead of resolving to Docker", async () => { + const resolver = mockBinaryResolver({ failServices: ["auth"] }); + const error = await run( + Effect.gen(function* () { + const service = yield* BinaryResolver; + return yield* resolveService(service, "auth", DEFAULT_VERSIONS.auth, "native").pipe( + Effect.flip, + ); + }), + resolver.layer, + ); + expect(error._tag).toBe("BinaryNotFoundError"); + // The surfaced failure must be actionable, not a bare tag name — this is + // what the CLI renders (both `Error.message` and `toStackError` read it). + expect(error.message).toContain("No native auth binary is available"); + expect(error.message).toContain('use mode "auto" or "docker"'); + expect(toStackError(error).message).toBe(error.message); + }); + + test("native mode propagates DownloadError instead of resolving to Docker", async () => { + const resolver = mockBinaryResolver({ downloadErrorServices: ["postgres"] }); + const error = await run( + Effect.gen(function* () { + const service = yield* BinaryResolver; + return yield* resolveService(service, "postgres", DEFAULT_VERSIONS.postgres, "native").pipe( + Effect.flip, + ); + }), + resolver.layer, + ); + expect(error._tag).toBe("DownloadError"); + expect(error.message).toContain("Failed to download https://releases.invalid/postgres/"); + expect(error.message).toContain("404 Not Found"); + expect(toStackError(error).message).toBe(error.message); + }); + + test("auto mode (the default) still falls back to a Docker image", async () => { + const resolver = mockBinaryResolver({ failServices: ["auth"] }); + const resolution = await run( + Effect.gen(function* () { + const service = yield* BinaryResolver; + return yield* resolveService(service, "auth", DEFAULT_VERSIONS.auth); + }), + resolver.layer, + ); + expect(resolution).toEqual({ + type: "docker", + image: `public.ecr.aws/supabase/gotrue:v${DEFAULT_VERSIONS.auth}`, + }); + }); +}); diff --git a/packages/stack/tests/helpers/mocks.ts b/packages/stack/tests/helpers/mocks.ts index 6017124333..787a03587e 100644 --- a/packages/stack/tests/helpers/mocks.ts +++ b/packages/stack/tests/helpers/mocks.ts @@ -4,7 +4,7 @@ import { type BinarySpec, type ResolveBinaryOptions, } from "../../src/BinaryResolver.ts"; -import { BinaryNotFoundError } from "../../src/errors.ts"; +import { BinaryNotFoundError, DownloadError } from "../../src/errors.ts"; import { DEFAULT_VERSIONS } from "../../src/versions.ts"; export function mockBinaryResolver( @@ -14,6 +14,7 @@ export function mockBinaryResolver( downloadDelayMs?: number; downloadDelaysMs?: Partial>; failServices?: string[]; + downloadErrorServices?: string[]; } = {}, ) { const resolved: Array<{ service: string; version: string }> = []; @@ -31,6 +32,12 @@ export function mockBinaryResolver( platform: "darwin-arm64", }); } + if (opts.downloadErrorServices?.includes(spec.service)) { + return yield* new DownloadError({ + url: `https://releases.invalid/${spec.service}/${spec.version}`, + cause: new Error("404 Not Found"), + }); + } resolved.push({ service: spec.service, version: spec.version }); const path = binaries[spec.service]; if (!path) { From 04845507785d4e3881ff4f44767b603e9200673e Mon Sep 17 00:00:00 2001 From: 7ttp <117663341+7ttp@users.noreply.github.com> Date: Thu, 30 Jul 2026 19:19:50 +0530 Subject: [PATCH 2/3] fix: honor native mode for docker-only prefetch resolution --- packages/stack/src/StackPreparation.ts | 16 +++++++++++---- packages/stack/src/prefetch.unit.test.ts | 25 ++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/packages/stack/src/StackPreparation.ts b/packages/stack/src/StackPreparation.ts index 678c71b9a9..114bdf546f 100644 --- a/packages/stack/src/StackPreparation.ts +++ b/packages/stack/src/StackPreparation.ts @@ -8,6 +8,7 @@ import { DEFAULT_VERSIONS, SERVICE_NAMES, dockerImageCandidatesForService, + dockerImageForService, type ServiceName, type VersionManifest, } from "./versions.ts"; @@ -125,11 +126,18 @@ export const prepareAssetsWithDependencies = ( ); } - // Docker-only services resolve to Docker in every mode, including "native". - // The native+docker-only-service CONFIG contract is enforced one layer up - // (`StackBuilder.validateResolvedConfig`), not here: `prefetch()` defaults - // to ALL services, so failing here would break `prefetch({ mode: "native" })`. + // Docker-only services cannot run natively. The native+docker-only CONFIG + // contract is enforced one layer up (`StackBuilder.validateResolvedConfig`); + // this branch is only reachable in "native" mode via `prefetch()` (which + // defaults to ALL services), where warming native assets must not require + // a Docker daemon — resolve to the canonical image name without pulling. if (dockerOnlyServices.has(service)) { + if (mode === "native") { + return Effect.succeed([ + service, + { type: "docker", image: dockerImageForService(service, versions[service]) }, + ]); + } return resolveDockerImageForService(spawner, service, versions[service], { onDownloadStart: markDownloadStart(), }).pipe( diff --git a/packages/stack/src/prefetch.unit.test.ts b/packages/stack/src/prefetch.unit.test.ts index 5702c41561..7903a553a6 100644 --- a/packages/stack/src/prefetch.unit.test.ts +++ b/packages/stack/src/prefetch.unit.test.ts @@ -314,6 +314,31 @@ describe("prefetch", () => { expect(spawner.spawned).toEqual([]); }); + test("native mode resolves docker-only services to an image name without touching Docker", async () => { + // `prefetch({ mode: "native" })` may run on a machine with no Docker daemon + // to warm native assets — docker-only services must not trigger any pull. + const resolver = mockBinaryResolver(); + const spawner = mockSequenceSpawner([]); + + const layer = StackPreparation.layer.pipe( + Layer.provide(resolver.layer), + Layer.provide(spawner.layer), + ); + + const result = await Effect.runPromise( + prefetch({ mode: "native", services: ["postgres", "edge-runtime"] }).pipe( + Effect.provide(layer), + ), + ); + + expect(result["edge-runtime"]).toEqual({ + type: "docker", + image: `public.ecr.aws/supabase/edge-runtime:v${DEFAULT_VERSIONS["edge-runtime"]}`, + }); + expect(result["postgres"]?.type).toBe("binary"); + expect(spawner.spawned).toEqual([]); + }); + test("uses docker for edge-runtime in auto mode even when a native binary exists", async () => { const resolver = mockBinaryResolver(); const spawner = mockSequenceSpawner([{ exitCode: 0 }]); From ad78ca9f8442c67333cf0a06f8ba2be587ea843d Mon Sep 17 00:00:00 2001 From: 7ttp <117663341+7ttp@users.noreply.github.com> Date: Thu, 30 Jul 2026 19:19:50 +0530 Subject: [PATCH 3/3] docs: fix architecture toc anchor --- packages/stack/docs/architecture.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stack/docs/architecture.md b/packages/stack/docs/architecture.md index 423c79459c..ee7bf27281 100644 --- a/packages/stack/docs/architecture.md +++ b/packages/stack/docs/architecture.md @@ -10,7 +10,7 @@ Manages a local Supabase development stack — resolving native binaries, wiring - [errors — typed error hierarchy](#errors--typed-error-hierarchy) - [Platform — OS and architecture detection](#platform--os-and-architecture-detection) - [BinaryResolver — download and cache binaries](#binaryresolver--download-and-cache-binaries) - - [resolveService — binary-first Docker fallback](#resolveservice--binary-first-docker-fallback) + - [resolveService — binary-first, mode-aware Docker fallback](#resolveservice--binary-first-mode-aware-docker-fallback) - [JwtGenerator — JWT token generation and opaque keys](#jwtgenerator--jwt-token-generation-and-opaque-keys) - [PortAllocator — dynamic port assignment](#portallocator--dynamic-port-assignment) - [prefetch — pre-download binaries and images](#prefetch--pre-download-binaries-and-images)