From fb1e23cdde11a459073dcf1fb6edd720af62b38e Mon Sep 17 00:00:00 2001 From: 7ttp <117663341+7ttp@users.noreply.github.com> Date: Tue, 4 Aug 2026 13:45:50 +0530 Subject: [PATCH 1/7] fix(stack): fall back to markerless cache when downloads fail --- packages/stack/src/BinaryResolver.ts | 18 ++++- .../stack/src/BinaryResolver.unit.test.ts | 73 ++++++++++++------- 2 files changed, 63 insertions(+), 28 deletions(-) diff --git a/packages/stack/src/BinaryResolver.ts b/packages/stack/src/BinaryResolver.ts index 15097c8d1a..a54e86dcbb 100644 --- a/packages/stack/src/BinaryResolver.ts +++ b/packages/stack/src/BinaryResolver.ts @@ -433,7 +433,23 @@ export class BinaryResolver extends Context.Service< ); return yield* attemptPublish(); - }).pipe(Effect.ensuring(cleanupTmpDir)); + }).pipe( + Effect.ensuring(cleanupTmpDir), + // A cache entry written by a pre-marker CLI release is non-empty + // but markerless, so it fails the completeness check above and + // lands here to be replaced. When the replacement cannot be + // fetched (offline, GitHub outage), that previously-working + // binary is strictly better than a hard failure — the same + // trade every pre-marker release already made on every resolve. + Effect.catchTag("DownloadError", (error) => + fs.readDirectory(cacheDir).pipe( + Effect.mapError(() => error), + Effect.flatMap((entries) => + entries.length > 0 ? Effect.succeed(false) : Effect.fail(error), + ), + ), + ), + ); return { path: cacheDir, diff --git a/packages/stack/src/BinaryResolver.unit.test.ts b/packages/stack/src/BinaryResolver.unit.test.ts index 4d3cebd91b..7753a90b56 100644 --- a/packages/stack/src/BinaryResolver.unit.test.ts +++ b/packages/stack/src/BinaryResolver.unit.test.ts @@ -711,36 +711,55 @@ describe("BinaryResolver.resolveWithMetadata cache completeness", () => { }, ); - it.live( - "does not destroy a markerless legacy cacheDir before a download attempt that then fails", - () => { - const fakeFs = createFakeCacheFs(); - const spawner = mockExtractingSpawner(fakeFs); - const httpLayer = mockOfflineHttpClient(); + it.live("falls back to a markerless legacy cacheDir when the replacement download fails", () => { + const fakeFs = createFakeCacheFs(); + const spawner = mockExtractingSpawner(fakeFs); + const httpLayer = mockOfflineHttpClient(); - const layer = BinaryResolver.make("/cache-root").pipe( - Layer.provide(fakeFs.layer), - Layer.provide(Path.layer), - Layer.provide(httpLayer), - Layer.provide(spawner.layer), - ); + const layer = BinaryResolver.make("/cache-root").pipe( + Layer.provide(fakeFs.layer), + Layer.provide(Path.layer), + Layer.provide(httpLayer), + Layer.provide(spawner.layer), + ); - return Effect.gen(function* () { - const resolver = yield* BinaryResolver; - const spec: BinarySpec = { service: "postgrest", version: postgrestVersion }; - const cacheDir = yield* resolvePostgrestCacheDir; + return Effect.gen(function* () { + const resolver = yield* BinaryResolver; + const spec: BinarySpec = { service: "postgrest", version: postgrestVersion }; + const cacheDir = yield* resolvePostgrestCacheDir; - // A markerless legacy cacheDir from before this resolver's staging - // model existed — still a perfectly usable binary on disk. - fakeFs.seedDirWithFile(cacheDir, "bin/postgrest"); + // A markerless legacy cacheDir from before this resolver's staging + // model existed — a binary that served every earlier release. When + // the replacement cannot be fetched, resolving to it beats failing. + fakeFs.seedDirWithFile(cacheDir, "bin/postgrest"); - const error = yield* resolver.resolveWithMetadata(spec).pipe(Effect.flip); + const result = yield* resolver.resolveWithMetadata(spec); - expect(error).toBeInstanceOf(DownloadError); - // The legacy binary must survive an offline/failed download attempt - // — it must not be deleted before we know we can replace it. - expect(fakeFs.files.has(`${cacheDir}/bin/postgrest`)).toBe(true); - }).pipe(Effect.provide(layer)); - }, - ); + expect(result.path).toBe(cacheDir); + expect(result.downloaded).toBe(false); + expect(fakeFs.files.has(`${cacheDir}/bin/postgrest`)).toBe(true); + }).pipe(Effect.provide(layer)); + }); + + it.live("still fails offline when no legacy cache entry exists to fall back to", () => { + const fakeFs = createFakeCacheFs(); + const spawner = mockExtractingSpawner(fakeFs); + const httpLayer = mockOfflineHttpClient(); + + const layer = BinaryResolver.make("/cache-root").pipe( + Layer.provide(fakeFs.layer), + Layer.provide(Path.layer), + Layer.provide(httpLayer), + Layer.provide(spawner.layer), + ); + + return Effect.gen(function* () { + const resolver = yield* BinaryResolver; + const spec: BinarySpec = { service: "postgrest", version: postgrestVersion }; + + const error = yield* resolver.resolveWithMetadata(spec).pipe(Effect.flip); + + expect(error).toBeInstanceOf(DownloadError); + }).pipe(Effect.provide(layer)); + }); }); From b61e2625e01779479afa151b5d36fa397635bee8 Mon Sep 17 00:00:00 2001 From: 7ttp <117663341+7ttp@users.noreply.github.com> Date: Tue, 4 Aug 2026 13:36:40 +0530 Subject: [PATCH 2/7] fix(cli): tighten sso test url assertions --- .../src/legacy/commands/sso/update/update.integration.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/cli/src/legacy/commands/sso/update/update.integration.test.ts b/apps/cli/src/legacy/commands/sso/update/update.integration.test.ts index 091645b63c..32de4a3237 100644 --- a/apps/cli/src/legacy/commands/sso/update/update.integration.test.ts +++ b/apps/cli/src/legacy/commands/sso/update/update.integration.test.ts @@ -1568,7 +1568,7 @@ describe("legacy sso update integration", () => { // The merge seeds from the reconciled host's GET response. const domains = (put?.body as { domains?: string[] })?.domains ?? []; expect([...domains].sort()).toEqual(["old1.com", "old2.com"]); - expect(api.requests.some((r) => r.url.startsWith("http://first.example"))).toBe(false); + expect(api.requests.some((r) => r.url.startsWith("http://first.example/"))).toBe(false); // The raw GET stitches identity through the shared per-command guard, // like Go's identityTransport on every Management API response. expect(testSetup.stitchedResponses).toBeGreaterThan(0); @@ -1879,7 +1879,7 @@ describe("legacy sso update integration", () => { const entitlements = api.requests.find((r) => r.url.includes("/entitlements")); expect(project?.url).toBe(`http://second.example/v1/projects/${LEGACY_VALID_REF}`); expect(entitlements?.url).toBe("http://second.example/v1/organizations/acme/entitlements"); - expect(api.requests.some((r) => r.url.startsWith("http://first.example"))).toBe(false); + expect(api.requests.some((r) => r.url.startsWith("http://first.example/"))).toBe(false); }).pipe(Effect.ensuring(restoreEnv), Effect.provide(layer)); }, ); From 1dba4b0f2c933159d087e6f7a945d319d1c04d85 Mon Sep 17 00:00:00 2001 From: 7ttp <117663341+7ttp@users.noreply.github.com> Date: Tue, 4 Aug 2026 14:02:04 +0530 Subject: [PATCH 3/7] fix: validate legacy cache entrypoint before fallback --- packages/stack/src/BinaryResolver.ts | 29 +++++++++++++---- .../stack/src/BinaryResolver.unit.test.ts | 32 +++++++++++++++++-- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/packages/stack/src/BinaryResolver.ts b/packages/stack/src/BinaryResolver.ts index a54e86dcbb..58df70f593 100644 --- a/packages/stack/src/BinaryResolver.ts +++ b/packages/stack/src/BinaryResolver.ts @@ -77,6 +77,21 @@ const cachePath = (baseDir: string, info: AssetInfo): string => */ const CACHE_COMPLETE_MARKER = ".supabase-cache-complete"; +/** + * The file each service's runner actually executes from a resolved directory + * (see `services/*.ts`). A markerless legacy cache entry is only trusted as a + * download-failure fallback when this file is present — mere non-emptiness + * would also accept a partial leftover from a killed pre-staging writer, and + * "resolving" one of those masks the DownloadError that lets the stack fall + * back to a Docker image instead of exec-ing a missing binary. + */ +const SERVICE_ENTRYPOINT: Partial> = { + postgres: "share/supabase-cli/bin/supabase-postgres-init.sh", + postgrest: "postgrest", + auth: "auth", + "edge-runtime": "bin/edge-runtime", +}; + /** * Age threshold for reaping abandoned `.tmp-*` staging siblings (see the * sweep in `resolveWithMetadata`). Generous on purpose: well beyond how long @@ -441,14 +456,14 @@ export class BinaryResolver extends Context.Service< // fetched (offline, GitHub outage), that previously-working // binary is strictly better than a hard failure — the same // trade every pre-marker release already made on every resolve. - Effect.catchTag("DownloadError", (error) => - fs.readDirectory(cacheDir).pipe( + Effect.catchTag("DownloadError", (error) => { + const entrypoint = SERVICE_ENTRYPOINT[spec.service]; + if (entrypoint === undefined) return Effect.fail(error); + return fs.exists(path.join(cacheDir, entrypoint)).pipe( Effect.mapError(() => error), - Effect.flatMap((entries) => - entries.length > 0 ? Effect.succeed(false) : Effect.fail(error), - ), - ), - ), + Effect.flatMap((usable) => (usable ? Effect.succeed(false) : Effect.fail(error))), + ); + }), ); return { diff --git a/packages/stack/src/BinaryResolver.unit.test.ts b/packages/stack/src/BinaryResolver.unit.test.ts index 7753a90b56..22f9e2acc5 100644 --- a/packages/stack/src/BinaryResolver.unit.test.ts +++ b/packages/stack/src/BinaryResolver.unit.test.ts @@ -731,13 +731,41 @@ describe("BinaryResolver.resolveWithMetadata cache completeness", () => { // A markerless legacy cacheDir from before this resolver's staging // model existed — a binary that served every earlier release. When // the replacement cannot be fetched, resolving to it beats failing. - fakeFs.seedDirWithFile(cacheDir, "bin/postgrest"); + fakeFs.seedDirWithFile(cacheDir, "postgrest"); const result = yield* resolver.resolveWithMetadata(spec); expect(result.path).toBe(cacheDir); expect(result.downloaded).toBe(false); - expect(fakeFs.files.has(`${cacheDir}/bin/postgrest`)).toBe(true); + expect(fakeFs.files.has(`${cacheDir}/postgrest`)).toBe(true); + }).pipe(Effect.provide(layer)); + }); + + it.live("rejects a partial markerless leftover that lacks the service entrypoint", () => { + // A pre-staging writer killed mid-extraction leaves a non-empty dir with + // no executable. Resolving it would mask the DownloadError that lets the + // stack fall back to a Docker image — so non-emptiness is not enough. + const fakeFs = createFakeCacheFs(); + const spawner = mockExtractingSpawner(fakeFs); + const httpLayer = mockOfflineHttpClient(); + + const layer = BinaryResolver.make("/cache-root").pipe( + Layer.provide(fakeFs.layer), + Layer.provide(Path.layer), + Layer.provide(httpLayer), + Layer.provide(spawner.layer), + ); + + return Effect.gen(function* () { + const resolver = yield* BinaryResolver; + const spec: BinarySpec = { service: "postgrest", version: postgrestVersion }; + const cacheDir = yield* resolvePostgrestCacheDir; + + fakeFs.seedDirWithFile(cacheDir, "_download-interrupted.tar"); + + const error = yield* resolver.resolveWithMetadata(spec).pipe(Effect.flip); + + expect(error).toBeInstanceOf(DownloadError); }).pipe(Effect.provide(layer)); }); From 857ed175166bdc5374422ed377f9f3919c9c3a1d Mon Sep 17 00:00:00 2001 From: 7ttp <117663341+7ttp@users.noreply.github.com> Date: Tue, 4 Aug 2026 14:12:06 +0530 Subject: [PATCH 4/7] fix: probe postgrest.exe in windows legacy caches --- packages/stack/src/BinaryResolver.ts | 24 ++++++++++------- .../stack/src/BinaryResolver.unit.test.ts | 26 +++++++++++++++++++ 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/packages/stack/src/BinaryResolver.ts b/packages/stack/src/BinaryResolver.ts index 58df70f593..862a13d143 100644 --- a/packages/stack/src/BinaryResolver.ts +++ b/packages/stack/src/BinaryResolver.ts @@ -85,11 +85,12 @@ const CACHE_COMPLETE_MARKER = ".supabase-cache-complete"; * "resolving" one of those masks the DownloadError that lets the stack fall * back to a Docker image instead of exec-ing a missing binary. */ -const SERVICE_ENTRYPOINT: Partial> = { - postgres: "share/supabase-cli/bin/supabase-postgres-init.sh", - postgrest: "postgrest", - auth: "auth", - "edge-runtime": "bin/edge-runtime", +const SERVICE_ENTRYPOINTS: Partial>> = { + postgres: ["share/supabase-cli/bin/supabase-postgres-init.sh"], + // The Windows asset is a .zip whose executable carries the .exe suffix. + postgrest: ["postgrest", "postgrest.exe"], + auth: ["auth"], + "edge-runtime": ["bin/edge-runtime"], }; /** @@ -457,11 +458,14 @@ export class BinaryResolver extends Context.Service< // binary is strictly better than a hard failure — the same // trade every pre-marker release already made on every resolve. Effect.catchTag("DownloadError", (error) => { - const entrypoint = SERVICE_ENTRYPOINT[spec.service]; - if (entrypoint === undefined) return Effect.fail(error); - return fs.exists(path.join(cacheDir, entrypoint)).pipe( - Effect.mapError(() => error), - Effect.flatMap((usable) => (usable ? Effect.succeed(false) : Effect.fail(error))), + const entrypoints = SERVICE_ENTRYPOINTS[spec.service]; + if (entrypoints === undefined) return Effect.fail(error); + return Effect.forEach(entrypoints, (entry) => + fs.exists(path.join(cacheDir, entry)).pipe(Effect.mapError(() => error)), + ).pipe( + Effect.flatMap((found) => + found.some(Boolean) ? Effect.succeed(false) : Effect.fail(error), + ), ); }), ); diff --git a/packages/stack/src/BinaryResolver.unit.test.ts b/packages/stack/src/BinaryResolver.unit.test.ts index 22f9e2acc5..15db92a556 100644 --- a/packages/stack/src/BinaryResolver.unit.test.ts +++ b/packages/stack/src/BinaryResolver.unit.test.ts @@ -741,6 +741,32 @@ describe("BinaryResolver.resolveWithMetadata cache completeness", () => { }).pipe(Effect.provide(layer)); }); + it.live("accepts a Windows legacy cache whose executable carries the .exe suffix", () => { + const fakeFs = createFakeCacheFs(); + const spawner = mockExtractingSpawner(fakeFs); + const httpLayer = mockOfflineHttpClient(); + + const layer = BinaryResolver.make("/cache-root").pipe( + Layer.provide(fakeFs.layer), + Layer.provide(Path.layer), + Layer.provide(httpLayer), + Layer.provide(spawner.layer), + ); + + return Effect.gen(function* () { + const resolver = yield* BinaryResolver; + const spec: BinarySpec = { service: "postgrest", version: postgrestVersion }; + const cacheDir = yield* resolvePostgrestCacheDir; + + fakeFs.seedDirWithFile(cacheDir, "postgrest.exe"); + + const result = yield* resolver.resolveWithMetadata(spec); + + expect(result.path).toBe(cacheDir); + expect(result.downloaded).toBe(false); + }).pipe(Effect.provide(layer)); + }); + it.live("rejects a partial markerless leftover that lacks the service entrypoint", () => { // A pre-staging writer killed mid-extraction leaves a non-empty dir with // no executable. Resolving it would mask the DownloadError that lets the From 2ff6a7b6df4f61efe7bacf4a45e080dd192cee4f Mon Sep 17 00:00:00 2001 From: 7ttp <117663341+7ttp@users.noreply.github.com> Date: Tue, 4 Aug 2026 14:22:40 +0530 Subject: [PATCH 5/7] fix: require the full postgres layout in legacy caches --- packages/stack/src/BinaryResolver.ts | 37 ++++++---- .../stack/src/BinaryResolver.unit.test.ts | 70 ++++++++++++++++++- 2 files changed, 94 insertions(+), 13 deletions(-) diff --git a/packages/stack/src/BinaryResolver.ts b/packages/stack/src/BinaryResolver.ts index 862a13d143..674640aff7 100644 --- a/packages/stack/src/BinaryResolver.ts +++ b/packages/stack/src/BinaryResolver.ts @@ -85,12 +85,23 @@ const CACHE_COMPLETE_MARKER = ".supabase-cache-complete"; * "resolving" one of those masks the DownloadError that lets the stack fall * back to a Docker image instead of exec-ing a missing binary. */ -const SERVICE_ENTRYPOINTS: Partial>> = { - postgres: ["share/supabase-cli/bin/supabase-postgres-init.sh"], - // The Windows asset is a .zip whose executable carries the .exe suffix. - postgrest: ["postgrest", "postgrest.exe"], - auth: ["auth"], - "edge-runtime": ["bin/edge-runtime"], +/** + * AND-of-ORs: every inner group must have at least one member present. The + * paths are exactly what each services/*.ts invokes from a resolved directory + * — postgres needs BOTH its init script and the bin/ payload the script and + * health check run, while postgrest's Windows .zip carries the .exe suffix. + */ +const SERVICE_ENTRYPOINTS: Partial< + Record>> +> = { + postgres: [ + ["share/supabase-cli/bin/supabase-postgres-init.sh"], + ["bin/pg_isready"], + ["bin/postgres", "bin/postgres.exe"], + ], + postgrest: [["postgrest", "postgrest.exe"]], + auth: [["auth"]], + "edge-runtime": [["bin/edge-runtime"]], }; /** @@ -458,13 +469,15 @@ export class BinaryResolver extends Context.Service< // binary is strictly better than a hard failure — the same // trade every pre-marker release already made on every resolve. Effect.catchTag("DownloadError", (error) => { - const entrypoints = SERVICE_ENTRYPOINTS[spec.service]; - if (entrypoints === undefined) return Effect.fail(error); - return Effect.forEach(entrypoints, (entry) => - fs.exists(path.join(cacheDir, entry)).pipe(Effect.mapError(() => error)), + const requirements = SERVICE_ENTRYPOINTS[spec.service]; + if (requirements === undefined) return Effect.fail(error); + return Effect.forEach(requirements, (alternatives) => + Effect.forEach(alternatives, (entry) => + fs.exists(path.join(cacheDir, entry)).pipe(Effect.mapError(() => error)), + ).pipe(Effect.map((found) => found.some(Boolean))), ).pipe( - Effect.flatMap((found) => - found.some(Boolean) ? Effect.succeed(false) : Effect.fail(error), + Effect.flatMap((groups) => + groups.every(Boolean) ? Effect.succeed(false) : Effect.fail(error), ), ); }), diff --git a/packages/stack/src/BinaryResolver.unit.test.ts b/packages/stack/src/BinaryResolver.unit.test.ts index 15db92a556..caadb00058 100644 --- a/packages/stack/src/BinaryResolver.unit.test.ts +++ b/packages/stack/src/BinaryResolver.unit.test.ts @@ -16,7 +16,7 @@ import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"; import { ChildProcessSpawner } from "effect/unstable/process"; import { BinaryResolver, type BinarySpec } from "./BinaryResolver.ts"; import { DownloadError } from "./errors.ts"; -import { detectPlatform, postgrestAssetName } from "./Platform.ts"; +import { detectPlatform, postgresAssetName, postgrestAssetName } from "./Platform.ts"; import { DEFAULT_VERSIONS } from "./versions.ts"; const postgresVersion = DEFAULT_VERSIONS.postgres; @@ -450,6 +450,20 @@ describe("BinaryResolver.resolveWithMetadata concurrency", () => { ); }); +/** Resolves the real cacheDir a `postgres` spec would use on the host running the test. */ +const resolvePostgresCacheDir = Effect.gen(function* () { + const platform = yield* detectPlatform; + const assetName = postgresAssetName(platform); + if (assetName === null) { + return yield* Effect.die(`unsupported test platform: ${platform.os}-${platform.arch}`); + } + return BinaryResolver.cachePath("/cache-root/bin", { + service: "postgres", + version: postgresVersion, + assetName, + }); +}); + /** Resolves the real cacheDir a `postgrest` spec would use on the host running the test. */ const resolvePostgrestCacheDir = Effect.gen(function* () { const platform = yield* detectPlatform; @@ -767,6 +781,60 @@ describe("BinaryResolver.resolveWithMetadata cache completeness", () => { }).pipe(Effect.provide(layer)); }); + it.live("rejects a postgres legacy cache with the init script but no bin payload", () => { + // The init script alone cannot run postgres — the health check invokes + // bin/pg_isready and the script needs the server binaries. A partial + // extraction stopping after share/ must not suppress the Docker fallback. + const fakeFs = createFakeCacheFs(); + const spawner = mockExtractingSpawner(fakeFs); + const httpLayer = mockOfflineHttpClient(); + + const layer = BinaryResolver.make("/cache-root").pipe( + Layer.provide(fakeFs.layer), + Layer.provide(Path.layer), + Layer.provide(httpLayer), + Layer.provide(spawner.layer), + ); + + return Effect.gen(function* () { + const resolver = yield* BinaryResolver; + const spec: BinarySpec = { service: "postgres", version: postgresVersion }; + const cacheDir = yield* resolvePostgresCacheDir; + + fakeFs.seedDirWithFile(cacheDir, "share/supabase-cli/bin/supabase-postgres-init.sh"); + + const error = yield* resolver.resolveWithMetadata(spec).pipe(Effect.flip); + expect(error).toBeInstanceOf(DownloadError); + }).pipe(Effect.provide(layer)); + }); + + it.live("accepts a postgres legacy cache carrying the full expected layout", () => { + const fakeFs = createFakeCacheFs(); + const spawner = mockExtractingSpawner(fakeFs); + const httpLayer = mockOfflineHttpClient(); + + const layer = BinaryResolver.make("/cache-root").pipe( + Layer.provide(fakeFs.layer), + Layer.provide(Path.layer), + Layer.provide(httpLayer), + Layer.provide(spawner.layer), + ); + + return Effect.gen(function* () { + const resolver = yield* BinaryResolver; + const spec: BinarySpec = { service: "postgres", version: postgresVersion }; + const cacheDir = yield* resolvePostgresCacheDir; + + fakeFs.seedDirWithFile(cacheDir, "share/supabase-cli/bin/supabase-postgres-init.sh"); + fakeFs.seedDirWithFile(cacheDir, "bin/pg_isready"); + fakeFs.seedDirWithFile(cacheDir, "bin/postgres"); + + const result = yield* resolver.resolveWithMetadata(spec); + expect(result.path).toBe(cacheDir); + expect(result.downloaded).toBe(false); + }).pipe(Effect.provide(layer)); + }); + it.live("rejects a partial markerless leftover that lacks the service entrypoint", () => { // A pre-staging writer killed mid-extraction leaves a non-empty dir with // no executable. Resolving it would mask the DownloadError that lets the From 8b4bc210dded82a38c7a7ff4f454d174bab3e54e Mon Sep 17 00:00:00 2001 From: 7ttp <117663341+7ttp@users.noreply.github.com> Date: Tue, 4 Aug 2026 14:34:02 +0530 Subject: [PATCH 6/7] require psql and lib in trusted postgres caches --- packages/stack/src/BinaryResolver.ts | 5 +++++ packages/stack/src/BinaryResolver.unit.test.ts | 2 ++ 2 files changed, 7 insertions(+) diff --git a/packages/stack/src/BinaryResolver.ts b/packages/stack/src/BinaryResolver.ts index 674640aff7..449e1f9a2f 100644 --- a/packages/stack/src/BinaryResolver.ts +++ b/packages/stack/src/BinaryResolver.ts @@ -98,6 +98,11 @@ const SERVICE_ENTRYPOINTS: Partial< ["share/supabase-cli/bin/supabase-postgres-init.sh"], ["bin/pg_isready"], ["bin/postgres", "bin/postgres.exe"], + // The init service drives all provisioning through psql, and the server + // loads its shared libraries from lib/ (LD_/DYLD_LIBRARY_PATH in + // services/postgres.ts) — a cache missing either can't boot. + ["bin/psql", "bin/psql.exe"], + ["lib"], ], postgrest: [["postgrest", "postgrest.exe"]], auth: [["auth"]], diff --git a/packages/stack/src/BinaryResolver.unit.test.ts b/packages/stack/src/BinaryResolver.unit.test.ts index caadb00058..3c9faa8feb 100644 --- a/packages/stack/src/BinaryResolver.unit.test.ts +++ b/packages/stack/src/BinaryResolver.unit.test.ts @@ -828,6 +828,8 @@ describe("BinaryResolver.resolveWithMetadata cache completeness", () => { fakeFs.seedDirWithFile(cacheDir, "share/supabase-cli/bin/supabase-postgres-init.sh"); fakeFs.seedDirWithFile(cacheDir, "bin/pg_isready"); fakeFs.seedDirWithFile(cacheDir, "bin/postgres"); + fakeFs.seedDirWithFile(cacheDir, "bin/psql"); + fakeFs.seedDirWithFile(cacheDir, "lib/libpq.dylib"); const result = yield* resolver.resolveWithMetadata(spec); expect(result.path).toBe(cacheDir); From f2d519b8f9ebce38ab70dd008d97883e4733e266 Mon Sep 17 00:00:00 2001 From: 7ttp <117663341+7ttp@users.noreply.github.com> Date: Tue, 4 Aug 2026 14:35:33 +0530 Subject: [PATCH 7/7] merge stale entrypoint doc comments --- packages/stack/src/BinaryResolver.ts | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/packages/stack/src/BinaryResolver.ts b/packages/stack/src/BinaryResolver.ts index 449e1f9a2f..c616e4b06b 100644 --- a/packages/stack/src/BinaryResolver.ts +++ b/packages/stack/src/BinaryResolver.ts @@ -78,18 +78,15 @@ const cachePath = (baseDir: string, info: AssetInfo): string => const CACHE_COMPLETE_MARKER = ".supabase-cache-complete"; /** - * The file each service's runner actually executes from a resolved directory - * (see `services/*.ts`). A markerless legacy cache entry is only trusted as a - * download-failure fallback when this file is present — mere non-emptiness - * would also accept a partial leftover from a killed pre-staging writer, and - * "resolving" one of those masks the DownloadError that lets the stack fall - * back to a Docker image instead of exec-ing a missing binary. - */ -/** - * AND-of-ORs: every inner group must have at least one member present. The - * paths are exactly what each services/*.ts invokes from a resolved directory - * — postgres needs BOTH its init script and the bin/ payload the script and - * health check run, while postgrest's Windows .zip carries the .exe suffix. + * The paths each service's runner actually executes from a resolved directory + * (see `services/*.ts`), checked as an AND-of-ORs: every inner group must have + * at least one member present (alternates cover e.g. postgrest's Windows .zip + * carrying the .exe suffix). A markerless legacy cache entry is only trusted + * as a download-failure fallback when the full layout is present — mere + * non-emptiness would also accept a partial leftover from a killed + * pre-staging writer, and "resolving" one of those masks the DownloadError + * that lets the stack fall back to a Docker image instead of exec-ing a + * missing binary. */ const SERVICE_ENTRYPOINTS: Partial< Record>>