From e0d219d68f27ebebbc02a12c4a317a0700ddf898 Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Mon, 22 Jun 2026 13:52:51 +0200 Subject: [PATCH] chore(cli): read pg image from manifest --- apps/cli/src/legacy/shared/legacy-db-image.ts | 9 ++-- .../shared/legacy-db-image.unit.test.ts | 3 +- .../src/shared/services/dockerfile-images.ts | 40 ++++++++++++++ .../src/shared/services/services.shared.ts | 53 ++++++------------- 4 files changed, 63 insertions(+), 42 deletions(-) create mode 100644 apps/cli/src/shared/services/dockerfile-images.ts diff --git a/apps/cli/src/legacy/shared/legacy-db-image.ts b/apps/cli/src/legacy/shared/legacy-db-image.ts index 6dd455c481..61710718d9 100644 --- a/apps/cli/src/legacy/shared/legacy-db-image.ts +++ b/apps/cli/src/legacy/shared/legacy-db-image.ts @@ -1,4 +1,5 @@ import { Effect, type FileSystem, type Path } from "effect"; +import { dockerfileServiceImage } from "../../shared/services/dockerfile-images.ts"; /** * Resolves the local Postgres Docker image the way Go's `config.Load` does @@ -6,13 +7,11 @@ import { Effect, type FileSystem, type Path } from "effect"; * pg_dump / shadow-DB container (`db dump`, declarative). Promote/extend this if * the full service-image resolution is ever needed. * - * The image tags are baked into the Go binary via the embedded Dockerfile - * (`pkg/config/templates/Dockerfile`, parsed into `config.Images`), so they are - * mirrored here as constants rather than read from any file. + * The default PG image is read from the same embedded Dockerfile manifest Go parses + * into `config.Images`, so the TS port tracks Dependabot bumps in that source. */ -// `FROM supabase/postgres:17.6.1.136 AS pg` (the embedded Dockerfile `pg` stage). -const LEGACY_PG_IMAGE = "supabase/postgres:17.6.1.136"; +const LEGACY_PG_IMAGE = dockerfileServiceImage("pg"); // `pkg/config/constants.go:12-14`. const LEGACY_PG14 = "supabase/postgres:14.1.0.89"; const LEGACY_PG15 = "supabase/postgres:15.8.1.085"; diff --git a/apps/cli/src/legacy/shared/legacy-db-image.unit.test.ts b/apps/cli/src/legacy/shared/legacy-db-image.unit.test.ts index 981a808efc..f74184da96 100644 --- a/apps/cli/src/legacy/shared/legacy-db-image.unit.test.ts +++ b/apps/cli/src/legacy/shared/legacy-db-image.unit.test.ts @@ -5,6 +5,7 @@ import { BunServices } from "@effect/platform-bun"; import { describe, expect, it } from "@effect/vitest"; import { Effect, FileSystem, Path } from "effect"; +import { dockerfileServiceImage } from "../../shared/services/dockerfile-images.ts"; import { legacyResolveDbImage } from "./legacy-db-image.ts"; const withTemp = () => mkdtempSync(join(tmpdir(), "legacy-db-image-")); @@ -22,7 +23,7 @@ describe("legacyResolveDbImage", () => { return Effect.gen(function* () { expect(yield* resolve(dir, 14)).toBe("supabase/postgres:14.1.0.89"); expect(yield* resolve(dir, 15)).toBe("supabase/postgres:15.8.1.085"); - expect(yield* resolve(dir, 17)).toBe("supabase/postgres:17.6.1.136"); + expect(yield* resolve(dir, 17)).toBe(dockerfileServiceImage("pg")); rmSync(dir, { recursive: true, force: true }); }); }); diff --git a/apps/cli/src/shared/services/dockerfile-images.ts b/apps/cli/src/shared/services/dockerfile-images.ts new file mode 100644 index 0000000000..d9982ddf9f --- /dev/null +++ b/apps/cli/src/shared/services/dockerfile-images.ts @@ -0,0 +1,40 @@ +import serviceImagesDockerfile from "../../../../cli-go/pkg/config/templates/Dockerfile" with { type: "text" }; + +export interface DockerfileImageSpec { + readonly alias: string; + readonly image: string; +} + +const FROM_LINE_PATTERN = /^FROM\s+(.+):([^:\s]+)\s+AS\s+([^\s#]+)/i; + +export function parseDockerfileServiceImages( + dockerfile: string, +): ReadonlyArray { + return dockerfile + .split("\n") + .map((line) => line.trim()) + .flatMap((line) => { + const match = FROM_LINE_PATTERN.exec(line); + if (match === null) { + return []; + } + + const [, repository, tag, alias] = match; + if (repository === undefined || tag === undefined || alias === undefined) { + return []; + } + + return [{ alias, image: `${repository}:${tag}` }]; + }); +} + +export const dockerfileServiceImages = parseDockerfileServiceImages(serviceImagesDockerfile); + +export function dockerfileServiceImage(alias: string): string { + const service = dockerfileServiceImages.find((image) => image.alias === alias); + if (service === undefined) { + throw new Error(`Missing service image alias '${alias}' in Dockerfile manifest.`); + } + + return service.image; +} diff --git a/apps/cli/src/shared/services/services.shared.ts b/apps/cli/src/shared/services/services.shared.ts index 502485391f..96f36cb10d 100644 --- a/apps/cli/src/shared/services/services.shared.ts +++ b/apps/cli/src/shared/services/services.shared.ts @@ -3,8 +3,14 @@ import { makeApiClient, type ApiClient } from "@supabase/api/effect"; import { Data, Duration, Effect, Exit, Redacted } from "effect"; import * as HttpClient from "effect/unstable/http/HttpClient"; import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"; -import serviceImagesDockerfile from "../../../../cli-go/pkg/config/templates/Dockerfile" with { type: "text" }; import { renderGlamourTable } from "../../legacy/output/legacy-glamour-table.ts"; +import { + dockerfileServiceImages, + parseDockerfileServiceImages, + type DockerfileImageSpec, +} from "./dockerfile-images.ts"; + +export { parseDockerfileServiceImages } from "./dockerfile-images.ts"; export type RemoteServiceName = "postgres" | "auth" | "postgrest" | "storage"; export type OptionalRemoteServiceName = Exclude; @@ -20,11 +26,6 @@ interface ServiceImageSpec { readonly remoteService: RemoteServiceName | undefined; } -interface DockerfileImageSpec { - readonly alias: string; - readonly image: string; -} - interface ServiceImageAliasSpec { readonly alias: string; readonly remoteService: RemoteServiceName | undefined; @@ -43,36 +44,10 @@ const SERVICE_IMAGE_ALIASES: ReadonlyArray = [ { alias: "supavisor", remoteService: undefined }, ]; -const FROM_LINE_PATTERN = /^FROM\s+(.+):([^:\s]+)\s+AS\s+([^\s#]+)/i; - -export function parseDockerfileServiceImages( - dockerfile: string, -): ReadonlyArray { - return dockerfile - .split("\n") - .map((line) => line.trim()) - .flatMap((line) => { - const match = FROM_LINE_PATTERN.exec(line); - if (match === null) { - return []; - } - - const [, repository, tag, alias] = match; - if (repository === undefined || tag === undefined || alias === undefined) { - return []; - } - - return [{ alias, image: `${repository}:${tag}` }]; - }); -} - -export function localServiceImagesFromDockerfile( - dockerfile: string, +function localServiceImagesFromSpecs( + specs: ReadonlyArray, ): ReadonlyArray { - const imagesByAlias = new Map( - parseDockerfileServiceImages(dockerfile).map((service) => [service.alias, service.image]), - ); - + const imagesByAlias = new Map(specs.map((service) => [service.alias, service.image])); return SERVICE_IMAGE_ALIASES.map((service) => { const image = imagesByAlias.get(service.alias); if (image === undefined) { @@ -86,7 +61,13 @@ export function localServiceImagesFromDockerfile( }); } -const LOCAL_SERVICE_IMAGES = localServiceImagesFromDockerfile(serviceImagesDockerfile); +export function localServiceImagesFromDockerfile( + dockerfile: string, +): ReadonlyArray { + return localServiceImagesFromSpecs(parseDockerfileServiceImages(dockerfile)); +} + +const LOCAL_SERVICE_IMAGES = localServiceImagesFromSpecs(dockerfileServiceImages); const TABLE_HEADERS = ["SERVICE IMAGE", "LOCAL", "LINKED"] as const;