From 85254bfd4f584116c0997d5d0ad3a9cec3663070 Mon Sep 17 00:00:00 2001 From: 7ttp <117663341+7ttp@users.noreply.github.com> Date: Wed, 15 Jul 2026 13:42:21 +0530 Subject: [PATCH 1/6] fix --- .../legacy/commands/gen/types/SIDE_EFFECTS.md | 4 +- .../commands/gen/types/types.handler.ts | 21 ++++---- .../gen/types/types.integration.test.ts | 50 +++++++++++++------ 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md index c7accf16f1..33ad03b21f 100644 --- a/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md @@ -5,7 +5,7 @@ | Path | Format | When | | ----------------------------------------- | ---------- | ---------------------------------------------------------------------------------------- | | `~/.supabase/access-token` | plain text | when `SUPABASE_ACCESS_TOKEN` unset and `--linked` or `--project-id` | -| `/supabase/config.toml` | TOML | when selecting schemas from config; required for `--local`, best-effort otherwise | +| `/supabase/config.toml` | TOML | when selecting schemas; `--local` uses embedded defaults when the file is missing | | `/supabase/.temp/rest-version` | plain text | `--local` only, when `db.major_version > 14` — forces v9 compat if the tag contains `v9` | | `/supabase/.temp/pgmeta-version` | plain text | `--local` only — overrides the pg-meta docker image tag | @@ -95,6 +95,8 @@ Not applicable. ## Notes - Exactly one of `--local`, `--linked`, `--project-id`, or `--db-url` must be specified. +- With `--local`, a missing `supabase/config.toml` uses the embedded config defaults, + matching the Go CLI. - `--lang` flag accepts `typescript` (default), `go`, `swift`, or `python`. Project-ref paths use the Management API for TypeScript, and use a project database host + temporary login role + pg-meta for other languages. diff --git a/apps/cli/src/legacy/commands/gen/types/types.handler.ts b/apps/cli/src/legacy/commands/gen/types/types.handler.ts index 87113c6a42..8b95a09710 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.handler.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.handler.ts @@ -1,6 +1,6 @@ -import { loadProjectConfig } from "@supabase/config"; +import { loadProjectConfig, ProjectConfigSchema } from "@supabase/config"; import { ChildProcessSpawner } from "effect/unstable/process"; -import { Effect, FileSystem, Option, Path, Stdio, Stream } from "effect"; +import { Effect, FileSystem, Option, Path, Schema, Stdio, Stream } from "effect"; import { LegacyDebugFlag, LegacyDnsResolverFlag, @@ -84,6 +84,7 @@ function isProjectNotFound(cause: unknown) { } const GEN_TYPES_COMMAND_PATH = ["gen", "types"] as const; +const defaultProjectConfig = Schema.decodeUnknownSync(ProjectConfigSchema)({}); function ensureMutuallyExclusive( group: ReadonlyArray, @@ -527,12 +528,8 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le yield* Effect.gen(function* () { if (flags.local) { - const loaded = yield* loadConfig(); - if (loaded === null) { - return yield* Effect.fail( - new Error("failed to load config: supabase/config.toml not found"), - ); - } + // Go's Config.Load merges embedded defaults when config.toml is absent. + const config = (yield* loadConfig())?.config ?? defaultProjectConfig; const paths = legacyTempPaths(path, cliConfig.workdir); // Go resolves Config.Api.Image from the rest-version file only when @@ -540,7 +537,7 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le // (pkg/config/config.go:657-666, internal/gen/types/types.go:69). Gate and trim // identically so we don't force v9 on older databases. const restVersion = - loaded.config.db.major_version > 14 + config.db.major_version > 14 ? (yield* fs .readFileString(paths.restVersion) .pipe(Effect.orElseSucceed(() => ""))).trim() @@ -551,9 +548,9 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le .pipe(Effect.orElseSucceed(() => "")); const includedSchemas = ( - schemas.length > 0 ? schemas : defaultSchemas(loaded.config.api.schemas) + schemas.length > 0 ? schemas : defaultSchemas(config.api.schemas) ).join(","); - const projectId = loaded.config.project_id ?? path.basename(cliConfig.workdir); + const projectId = config.project_id ?? path.basename(cliConfig.workdir); yield* assertLocalDbRunning(projectId); yield* runPgMeta({ @@ -567,7 +564,7 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le host: "db", port: 5432, probeHost: legacyGetHostname(), - probePort: loaded.config.db.port, + probePort: config.db.port, networkMode: localNetworkId(projectId), includedSchemas, postgrestV9Compat: flags.postgrestV9Compat || forcedV9, diff --git a/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts b/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts index 67015337f6..c8514bd46a 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts @@ -1,7 +1,7 @@ import { existsSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { createServer } from "node:net"; import { tmpdir } from "node:os"; -import { join } from "node:path"; +import { basename, join } from "node:path"; import { describe, expect, it } from "@effect/vitest"; import { BunServices } from "@effect/platform-bun"; import type { @@ -62,7 +62,12 @@ import type { import { legacyGenCommand } from "../gen.command.ts"; import type { LegacyGenTypesFlags } from "./types.command.ts"; import { legacyGenTypes } from "./types.handler.ts"; -import { parseQueryTimeoutSeconds, resolvePgmetaImage } from "./types.shared.ts"; +import { + localDbContainerId, + localNetworkId, + parseQueryTimeoutSeconds, + resolvePgmetaImage, +} from "./types.shared.ts"; function writeConfig(workdir: string, contents: string) { const supabaseDir = join(workdir, "supabase"); @@ -2631,22 +2636,39 @@ describe("legacy gen types", () => { }, ); - it.live("fails local generation when supabase/config.toml is missing", () => { + it.live("generates locally with Go defaults when supabase/config.toml is missing", () => { const workdir = mkdtempSync(join(tmpdir(), "supabase-gen-types-local-no-config-")); - const { layer } = setup({ workdir, skipConfig: true }); + const docker = captureDockerRun(); + const probes: Array<{ host: string; port: number }> = []; + const { layer, out, child } = setup({ + workdir, + skipConfig: true, + childStdout: ["generated"], + onSpawn: docker.onSpawn, + sslProbeLayer: Layer.succeed(LegacyPgDeltaSslProbe, { + requireSsl: () => Effect.succeed(false), + requireSslForHost: (host, port) => + Effect.sync(() => { + probes.push({ host, port }); + return false; + }), + }), + }); return Effect.gen(function* () { - const exit = yield* legacyGenTypes(defaultFlags({ local: true })).pipe( - Effect.provide(layer), - Effect.exit, - ); + yield* legacyGenTypes(defaultFlags({ local: true })).pipe(Effect.provide(layer)); - expect(Exit.isFailure(exit)).toBe(true); - if (Exit.isFailure(exit)) { - expect(String(exit.cause)).toContain( - "failed to load config: supabase/config.toml not found", - ); - } + const projectId = basename(workdir); + expect(child.spawned[0]).toEqual({ + command: "docker", + args: ["container", "inspect", localDbContainerId(projectId)], + }); + expect(child.spawned[1]?.args).toContain(localNetworkId(projectId)); + expect(probes).toEqual([{ host: "127.0.0.1", port: 54322 }]); + expect(docker.env.has("PG_META_GENERATE_TYPES_INCLUDED_SCHEMAS=public,graphql_public")).toBe( + true, + ); + expect(out.stdoutText).toContain("generated"); }); }); From 4f18539f1834b6e2f06af178f27c5da16c900095 Mon Sep 17 00:00:00 2001 From: 7ttp <117663341+7ttp@users.noreply.github.com> Date: Wed, 15 Jul 2026 14:18:39 +0530 Subject: [PATCH 2/6] Nits --- .../legacy/commands/gen/types/SIDE_EFFECTS.md | 10 +++- .../commands/gen/types/types.handler.ts | 17 ++++--- .../gen/types/types.integration.test.ts | 46 +++++++++++++++++++ .../shared/legacy-db-config.toml-read.ts | 28 +++++++++++ 4 files changed, 90 insertions(+), 11 deletions(-) diff --git a/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md index 33ad03b21f..4ef5a89369 100644 --- a/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md @@ -6,6 +6,7 @@ | ----------------------------------------- | ---------- | ---------------------------------------------------------------------------------------- | | `~/.supabase/access-token` | plain text | when `SUPABASE_ACCESS_TOKEN` unset and `--linked` or `--project-id` | | `/supabase/config.toml` | TOML | when selecting schemas; `--local` uses embedded defaults when the file is missing | +| `{/supabase}/.env*` | dotenv | `--local`; resolves the same nested environment overrides as the legacy CLI | | `/supabase/.temp/rest-version` | plain text | `--local` only, when `db.major_version > 14` — forces v9 compat if the tag contains `v9` | | `/supabase/.temp/pgmeta-version` | plain text | `--local` only — overrides the pg-meta docker image tag | @@ -59,6 +60,11 @@ default 10s pg-delta probe timeout. | Variable | Purpose | Required? | | ---------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | `SUPABASE_ACCESS_TOKEN` | auth token for linked/project-id mode | no (falls back to keyring → `~/.supabase/access-token`) | +| `SUPABASE_PROJECT_ID` | local Docker container and network project ID | no (falls back to the workdir name) | +| `SUPABASE_DB_PORT` | local database probe port | no (defaults to `54322`) | +| `SUPABASE_DB_MAJOR_VERSION` | local PostgreSQL major version | no (defaults to `17`) | +| `SUPABASE_API_SCHEMAS` | local schemas used when `--schema` is omitted | no (defaults to `public,graphql_public`) | +| `SUPABASE_ENV` | selects nested dotenv files for local generation | no (defaults to `development`) | | `SUPABASE_PROFILE` | built-in profile name or YAML file path | no (falls back to `~/.supabase/profile` -> `supabase`) | | `SUPABASE_DB_PASSWORD` | database password for `--local` and the `--linked` workdir project | no (defaults to `postgres`; **ignored** for ad-hoc `--project-id`, which always mints a temporary login role) | | `SUPABASE_SERVICES_HOSTNAME` | host used for the local TLS probe | no (defaults to `127.0.0.1`) | @@ -95,8 +101,8 @@ Not applicable. ## Notes - Exactly one of `--local`, `--linked`, `--project-id`, or `--db-url` must be specified. -- With `--local`, a missing `supabase/config.toml` uses the embedded config defaults, - matching the Go CLI. +- With `--local`, a missing `supabase/config.toml` uses the embedded config defaults plus + shell and nested dotenv overrides, matching the legacy CLI. - `--lang` flag accepts `typescript` (default), `go`, `swift`, or `python`. Project-ref paths use the Management API for TypeScript, and use a project database host + temporary login role + pg-meta for other languages. diff --git a/apps/cli/src/legacy/commands/gen/types/types.handler.ts b/apps/cli/src/legacy/commands/gen/types/types.handler.ts index 8b95a09710..02a8ed7dd3 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.handler.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.handler.ts @@ -1,6 +1,6 @@ -import { loadProjectConfig, ProjectConfigSchema } from "@supabase/config"; +import { loadProjectConfig } from "@supabase/config"; import { ChildProcessSpawner } from "effect/unstable/process"; -import { Effect, FileSystem, Option, Path, Schema, Stdio, Stream } from "effect"; +import { Effect, FileSystem, Option, Path, Stdio, Stream } from "effect"; import { LegacyDebugFlag, LegacyDnsResolverFlag, @@ -26,6 +26,7 @@ import { mapLegacyHttpError } from "../../../shared/legacy-http-errors.ts"; import { LegacyDbConfigResolver } from "../../../shared/legacy-db-config.service.ts"; import type { LegacyDbConfigFlags } from "../../../shared/legacy-db-config.types.ts"; import { legacyPoolerConfigFromConnectionString } from "../../../shared/legacy-db-config.parse.ts"; +import { legacyReadDbToml } from "../../../shared/legacy-db-config.toml-read.ts"; import type { LegacyPgConnInput } from "../../../shared/legacy-db-connection.service.ts"; import { legacyToPostgresURL } from "../../../shared/legacy-postgres-url.ts"; import { legacyTempPaths } from "../../../shared/legacy-temp-paths.ts"; @@ -84,7 +85,6 @@ function isProjectNotFound(cause: unknown) { } const GEN_TYPES_COMMAND_PATH = ["gen", "types"] as const; -const defaultProjectConfig = Schema.decodeUnknownSync(ProjectConfigSchema)({}); function ensureMutuallyExclusive( group: ReadonlyArray, @@ -528,8 +528,8 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le yield* Effect.gen(function* () { if (flags.local) { - // Go's Config.Load merges embedded defaults when config.toml is absent. - const config = (yield* loadConfig())?.config ?? defaultProjectConfig; + const config = yield* legacyReadDbToml(fs, path, cliConfig.workdir); + const projectId = Option.getOrElse(config.projectId, () => path.basename(cliConfig.workdir)); const paths = legacyTempPaths(path, cliConfig.workdir); // Go resolves Config.Api.Image from the rest-version file only when @@ -537,7 +537,7 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le // (pkg/config/config.go:657-666, internal/gen/types/types.go:69). Gate and trim // identically so we don't force v9 on older databases. const restVersion = - config.db.major_version > 14 + config.majorVersion > 14 ? (yield* fs .readFileString(paths.restVersion) .pipe(Effect.orElseSucceed(() => ""))).trim() @@ -548,9 +548,8 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le .pipe(Effect.orElseSucceed(() => "")); const includedSchemas = ( - schemas.length > 0 ? schemas : defaultSchemas(config.api.schemas) + schemas.length > 0 ? schemas : defaultSchemas(config.apiSchemas) ).join(","); - const projectId = config.project_id ?? path.basename(cliConfig.workdir); yield* assertLocalDbRunning(projectId); yield* runPgMeta({ @@ -564,7 +563,7 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le host: "db", port: 5432, probeHost: legacyGetHostname(), - probePort: config.db.port, + probePort: config.port, networkMode: localNetworkId(projectId), includedSchemas, postgrestV9Compat: flags.postgrestV9Compat || forcedV9, diff --git a/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts b/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts index c8514bd46a..1477d8165b 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts @@ -2672,6 +2672,52 @@ describe("legacy gen types", () => { }); }); + it.live("honors local dotenv overrides when supabase/config.toml is missing", () => { + const workdir = mkdtempSync(join(tmpdir(), "supabase-gen-types-local-no-config-env-")); + const supabaseDir = join(workdir, "supabase"); + mkdirSync(supabaseDir, { recursive: true }); + writeFileSync( + join(supabaseDir, ".env"), + [ + "SUPABASE_PROJECT_ID=configless-env-project", + "SUPABASE_DB_PORT=55432", + "SUPABASE_API_SCHEMAS=private,graphql_public", + "", + ].join("\n"), + ); + const docker = captureDockerRun(); + const probes: Array<{ host: string; port: number }> = []; + const { layer, out, child } = setup({ + workdir, + skipConfig: true, + childStdout: ["generated"], + onSpawn: docker.onSpawn, + sslProbeLayer: Layer.succeed(LegacyPgDeltaSslProbe, { + requireSsl: () => Effect.succeed(false), + requireSslForHost: (host, port) => + Effect.sync(() => { + probes.push({ host, port }); + return false; + }), + }), + }); + + return Effect.gen(function* () { + yield* legacyGenTypes(defaultFlags({ local: true })).pipe(Effect.provide(layer)); + + expect(child.spawned[0]).toEqual({ + command: "docker", + args: ["container", "inspect", localDbContainerId("configless-env-project")], + }); + expect(child.spawned[1]?.args).toContain(localNetworkId("configless-env-project")); + expect(probes).toEqual([{ host: "127.0.0.1", port: 55432 }]); + expect( + docker.env.has("PG_META_GENERATE_TYPES_INCLUDED_SCHEMAS=public,private,graphql_public"), + ).toBe(true); + expect(out.stdoutText).toContain("generated"); + }); + }); + it.live("reports a generic inspect failure when docker emits no stderr", () => { const workdir = mkdtempSync(join(tmpdir(), "supabase-gen-types-local-empty-stderr-")); writeConfig( diff --git a/apps/cli/src/legacy/shared/legacy-db-config.toml-read.ts b/apps/cli/src/legacy/shared/legacy-db-config.toml-read.ts index 0e11809a56..be93cdf033 100644 --- a/apps/cli/src/legacy/shared/legacy-db-config.toml-read.ts +++ b/apps/cli/src/legacy/shared/legacy-db-config.toml-read.ts @@ -51,6 +51,7 @@ interface LegacyDbTomlValues { * rather than `process.env` alone (e.g. `SUPABASE_EXPERIMENTAL_PG_DELTA`). */ readonly envLookup: (name: string) => string | undefined; + readonly apiSchemas: ReadonlyArray; /** `[db] port`, default 54322 (`packages/config/src/db.ts`). */ readonly port: number; /** `[db] shadow_port`, default 54320. */ @@ -169,6 +170,7 @@ const DEFAULT_PORT = 54322; const DEFAULT_SHADOW_PORT = 54320; const DEFAULT_MAJOR_VERSION = 17; const DEFAULT_PASSWORD = "postgres"; +const DEFAULT_API_SCHEMAS = ["public", "graphql_public"] as const; /** `[edge_runtime] deno_version` default (`config.toml` template). 2 → the current edge-runtime image. */ const DEFAULT_DENO_VERSION = 2; @@ -273,6 +275,7 @@ function legacyResolveValidatedRemoteProjectId( * `AutomaticEnv` — `config.go:635-637`), so the block value must beat the env override. */ const LEGACY_ENV_OVERRIDABLE_KEYS: ReadonlyArray = [ + "api.schemas", "db.port", "db.shadow_port", "db.major_version", @@ -470,6 +473,19 @@ function resolveConfigInt(value: unknown, lookup: EnvLookup): number | "absent" return "invalid"; } +function resolveStringSlice( + value: unknown, + fallback: ReadonlyArray, + lookup: EnvLookup, +): ReadonlyArray | undefined { + if (value === undefined) return fallback; + if (typeof value === "string") return legacyExpandEnv(value, lookup).split(","); + if (!Array.isArray(value) || !value.every((item): item is string => typeof item === "string")) { + return undefined; + } + return value.map((item) => legacyExpandEnv(item, lookup)); +} + /** * Replicates Go's `path.Join("supabase", pattern)` for a relative seed `sql_paths` * entry (`pkg/config/config.go:881-886`). Go's `path.Join` runs `path.Clean`, which @@ -1834,9 +1850,21 @@ const readDbTomlCore = Effect.fnUntraced(function* ( apiRaw?.["auto_expose_new_tables"], lookup, ); + const apiSchemas = resolveStringSlice( + (remoteOverrideKeys.has("api.schemas") ? undefined : envOverride("SUPABASE_API_SCHEMAS")) ?? + apiRaw?.["schemas"], + DEFAULT_API_SCHEMAS, + lookup, + ); + if (apiSchemas === undefined) { + return yield* Effect.fail( + new LegacyDbConfigLoadError({ message: "failed to parse config: invalid api.schemas." }), + ); + } const values: LegacyDbTomlValues = { envLookup: envOverride, + apiSchemas, port, shadowPort, password: passwordRaw !== undefined ? legacyExpandEnv(passwordRaw, lookup) : DEFAULT_PASSWORD, From 24fd9a7b9add94fca6a42cff542bf1bcb10f8b96 Mon Sep 17 00:00:00 2001 From: 7ttp <117663341+7ttp@users.noreply.github.com> Date: Wed, 15 Jul 2026 14:41:08 +0530 Subject: [PATCH 3/6] parity --- .../src/legacy/commands/gen/types/types.handler.ts | 8 ++++++-- .../commands/gen/types/types.integration.test.ts | 9 ++++++++- .../legacy/shared/legacy-db-config.toml-read.ts | 14 +++++++++++--- .../shared/legacy-db-config.toml-read.unit.test.ts | 5 +++-- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/apps/cli/src/legacy/commands/gen/types/types.handler.ts b/apps/cli/src/legacy/commands/gen/types/types.handler.ts index 02a8ed7dd3..603eb414d1 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.handler.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.handler.ts @@ -26,7 +26,10 @@ import { mapLegacyHttpError } from "../../../shared/legacy-http-errors.ts"; import { LegacyDbConfigResolver } from "../../../shared/legacy-db-config.service.ts"; import type { LegacyDbConfigFlags } from "../../../shared/legacy-db-config.types.ts"; import { legacyPoolerConfigFromConnectionString } from "../../../shared/legacy-db-config.parse.ts"; -import { legacyReadDbToml } from "../../../shared/legacy-db-config.toml-read.ts"; +import { + legacyApplyProjectEnv, + legacyReadDbToml, +} from "../../../shared/legacy-db-config.toml-read.ts"; import type { LegacyPgConnInput } from "../../../shared/legacy-db-connection.service.ts"; import { legacyToPostgresURL } from "../../../shared/legacy-postgres-url.ts"; import { legacyTempPaths } from "../../../shared/legacy-temp-paths.ts"; @@ -529,6 +532,7 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le yield* Effect.gen(function* () { if (flags.local) { const config = yield* legacyReadDbToml(fs, path, cliConfig.workdir); + yield* legacyApplyProjectEnv(config.projectEnv, Object.keys(config.projectEnv)); const projectId = Option.getOrElse(config.projectId, () => path.basename(cliConfig.workdir)); const paths = legacyTempPaths(path, cliConfig.workdir); @@ -633,5 +637,5 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le schemas.length > 0 ? schemas : schemasFromConfig(loaded?.config.api.schemas), false, ); - }).pipe(Effect.ensuring(telemetryState.flush)); + }).pipe(Effect.scoped, Effect.ensuring(telemetryState.flush)); }); diff --git a/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts b/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts index 1477d8165b..f8e8ec5305 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts @@ -2682,6 +2682,8 @@ describe("legacy gen types", () => { "SUPABASE_PROJECT_ID=configless-env-project", "SUPABASE_DB_PORT=55432", "SUPABASE_API_SCHEMAS=private,graphql_public", + "SUPABASE_SERVICES_HOSTNAME=host.docker.internal", + "SUPABASE_INTERNAL_IMAGE_REGISTRY=mirror.example.com", "", ].join("\n"), ); @@ -2710,10 +2712,15 @@ describe("legacy gen types", () => { args: ["container", "inspect", localDbContainerId("configless-env-project")], }); expect(child.spawned[1]?.args).toContain(localNetworkId("configless-env-project")); - expect(probes).toEqual([{ host: "127.0.0.1", port: 55432 }]); + expect(probes).toEqual([{ host: "host.docker.internal", port: 55432 }]); expect( docker.env.has("PG_META_GENERATE_TYPES_INCLUDED_SCHEMAS=public,private,graphql_public"), ).toBe(true); + expect( + child.spawned[1]?.args.some((arg) => + arg.startsWith("mirror.example.com/supabase/postgres-meta:"), + ), + ).toBe(true); expect(out.stdoutText).toContain("generated"); }); }); diff --git a/apps/cli/src/legacy/shared/legacy-db-config.toml-read.ts b/apps/cli/src/legacy/shared/legacy-db-config.toml-read.ts index be93cdf033..e17dd5f5be 100644 --- a/apps/cli/src/legacy/shared/legacy-db-config.toml-read.ts +++ b/apps/cli/src/legacy/shared/legacy-db-config.toml-read.ts @@ -43,6 +43,7 @@ type EnvLookup = (name: string) => string | undefined; * and aborts the command rather than running against the default local database). */ interface LegacyDbTomlValues { + readonly projectEnv: Readonly>; /** * Resolves a `SUPABASE_*` env var with Go's precedence: shell env (non-empty) * wins, then the loaded project `.env*` files (non-empty), else undefined. @@ -479,7 +480,10 @@ function resolveStringSlice( lookup: EnvLookup, ): ReadonlyArray | undefined { if (value === undefined) return fallback; - if (typeof value === "string") return legacyExpandEnv(value, lookup).split(","); + if (typeof value === "string") { + const expanded = legacyExpandEnv(value, lookup); + return expanded.length === 0 ? [] : expanded.split(","); + } if (!Array.isArray(value) || !value.every((item): item is string => typeof item === "string")) { return undefined; } @@ -619,9 +623,12 @@ export const legacyLoadProjectEnv = Effect.fnUntraced(function* ( * re-checks). The `acquireRelease` finalizer deletes only the keys it set when the * scope closes, so in-process test workers don't leak env between cases. */ -export const legacyApplyProjectEnv = (loaded: Record) => +export const legacyApplyProjectEnv = ( + loaded: Readonly>, + keys: ReadonlyArray = LEGACY_PROCESS_ENV_APPLY_KEYS, +) => Effect.forEach( - LEGACY_PROCESS_ENV_APPLY_KEYS, + keys, (key) => { const value = loaded[key]; if (value === undefined || process.env[key] !== undefined) { @@ -1863,6 +1870,7 @@ const readDbTomlCore = Effect.fnUntraced(function* ( } const values: LegacyDbTomlValues = { + projectEnv, envLookup: envOverride, apiSchemas, port, diff --git a/apps/cli/src/legacy/shared/legacy-db-config.toml-read.unit.test.ts b/apps/cli/src/legacy/shared/legacy-db-config.toml-read.unit.test.ts index 493f1f8276..4919c78ef8 100644 --- a/apps/cli/src/legacy/shared/legacy-db-config.toml-read.unit.test.ts +++ b/apps/cli/src/legacy/shared/legacy-db-config.toml-read.unit.test.ts @@ -929,11 +929,12 @@ describe("legacyReadDbToml", () => { ); }); - it.effect("keeps [api] auto_expose_new_tables tri-state None when absent", () => { - const dir = withConfig("[api]\n"); + it.effect("decodes empty api schemas while keeping auto_expose_new_tables absent", () => { + const dir = withConfig('[api]\nschemas = ""\n'); return read(dir).pipe( Effect.tap((v) => Effect.sync(() => { + expect(v.apiSchemas).toEqual([]); expect(Option.isNone(v.baseline.apiAutoExposeNewTables)).toBe(true); rmSync(dir, { recursive: true, force: true }); }), From 11c303dc5153c07b5120a3506c17b03b26755693 Mon Sep 17 00:00:00 2001 From: 7ttp <117663341+7ttp@users.noreply.github.com> Date: Wed, 15 Jul 2026 14:57:41 +0530 Subject: [PATCH 4/6] fix: keep DB_PASSWORD out of local typegen. --- apps/cli/src/legacy/commands/gen/types/types.handler.ts | 5 ++++- .../src/legacy/commands/gen/types/types.integration.test.ts | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/cli/src/legacy/commands/gen/types/types.handler.ts b/apps/cli/src/legacy/commands/gen/types/types.handler.ts index 603eb414d1..3f090e6090 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.handler.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.handler.ts @@ -532,7 +532,10 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le yield* Effect.gen(function* () { if (flags.local) { const config = yield* legacyReadDbToml(fs, path, cliConfig.workdir); - yield* legacyApplyProjectEnv(config.projectEnv, Object.keys(config.projectEnv)); + yield* legacyApplyProjectEnv( + config.projectEnv, + Object.keys(config.projectEnv).filter((key) => key !== "SUPABASE_DB_PASSWORD"), + ); const projectId = Option.getOrElse(config.projectId, () => path.basename(cliConfig.workdir)); const paths = legacyTempPaths(path, cliConfig.workdir); diff --git a/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts b/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts index f8e8ec5305..5d75f2caa9 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts @@ -2681,6 +2681,7 @@ describe("legacy gen types", () => { [ "SUPABASE_PROJECT_ID=configless-env-project", "SUPABASE_DB_PORT=55432", + "SUPABASE_DB_PASSWORD=remote-password", "SUPABASE_API_SCHEMAS=private,graphql_public", "SUPABASE_SERVICES_HOSTNAME=host.docker.internal", "SUPABASE_INTERNAL_IMAGE_REGISTRY=mirror.example.com", @@ -2716,6 +2717,11 @@ describe("legacy gen types", () => { expect( docker.env.has("PG_META_GENERATE_TYPES_INCLUDED_SCHEMAS=public,private,graphql_public"), ).toBe(true); + expect( + docker.env.has( + "PG_META_DB_URL=postgresql://postgres:postgres@db:5432/postgres?connect_timeout=10", + ), + ).toBe(true); expect( child.spawned[1]?.args.some((arg) => arg.startsWith("mirror.example.com/supabase/postgres-meta:"), From bdbed3f2f80aa5eae1df5b973758d1fe4f155477 Mon Sep 17 00:00:00 2001 From: 7ttp <117663341+7ttp@users.noreply.github.com> Date: Wed, 15 Jul 2026 15:09:34 +0530 Subject: [PATCH 5/6] fix: ignore shell DB_PASSWORD for local typegen --- apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md | 2 +- apps/cli/src/legacy/commands/gen/types/types.handler.ts | 8 ++------ .../legacy/commands/gen/types/types.integration.test.ts | 4 ++-- apps/cli/src/legacy/commands/gen/types/types.shared.ts | 4 ---- apps/cli/src/legacy/commands/gen/types/types.unit.test.ts | 5 +---- 5 files changed, 6 insertions(+), 17 deletions(-) diff --git a/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md index 4ef5a89369..6c37a42088 100644 --- a/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md @@ -66,7 +66,7 @@ default 10s pg-delta probe timeout. | `SUPABASE_API_SCHEMAS` | local schemas used when `--schema` is omitted | no (defaults to `public,graphql_public`) | | `SUPABASE_ENV` | selects nested dotenv files for local generation | no (defaults to `development`) | | `SUPABASE_PROFILE` | built-in profile name or YAML file path | no (falls back to `~/.supabase/profile` -> `supabase`) | -| `SUPABASE_DB_PASSWORD` | database password for `--local` and the `--linked` workdir project | no (defaults to `postgres`; **ignored** for ad-hoc `--project-id`, which always mints a temporary login role) | +| `SUPABASE_DB_PASSWORD` | database password for the `--linked` workdir project | no (**ignored** for `--local`; ad-hoc `--project-id` always mints a temporary login role instead of using it) | | `SUPABASE_SERVICES_HOSTNAME` | host used for the local TLS probe | no (defaults to `127.0.0.1`) | | `SUPABASE_INTERNAL_IMAGE_REGISTRY` | pg-meta image registry override (`docker.io` → Docker Hub; any other value → that registry) | no (defaults to the ECR registry) | | `SUPABASE_CA_SKIP_VERIFY` | when `true`, prints a TLS-verification-disabled warning to stderr | no | diff --git a/apps/cli/src/legacy/commands/gen/types/types.handler.ts b/apps/cli/src/legacy/commands/gen/types/types.handler.ts index 3f090e6090..a483c362e7 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.handler.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.handler.ts @@ -48,7 +48,6 @@ import { defaultSchemas, buildPostgresUrl, localDbContainerId, - localDbPassword, localNetworkId, parseDatabaseUrl, parseQueryTimeoutSeconds, @@ -532,10 +531,7 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le yield* Effect.gen(function* () { if (flags.local) { const config = yield* legacyReadDbToml(fs, path, cliConfig.workdir); - yield* legacyApplyProjectEnv( - config.projectEnv, - Object.keys(config.projectEnv).filter((key) => key !== "SUPABASE_DB_PASSWORD"), - ); + yield* legacyApplyProjectEnv(config.projectEnv, Object.keys(config.projectEnv)); const projectId = Option.getOrElse(config.projectId, () => path.basename(cliConfig.workdir)); const paths = legacyTempPaths(path, cliConfig.workdir); @@ -564,7 +560,7 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le host: "db", port: 5432, user: "postgres", - password: localDbPassword(), + password: config.password, database: "postgres", }), host: "db", diff --git a/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts b/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts index 5d75f2caa9..3008d85d59 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts @@ -2179,7 +2179,7 @@ describe("legacy gen types", () => { }), ); - it.live("uses sanitized local docker ids and env-backed local db passwords", () => + it.live("uses sanitized local docker ids and ignores shell db passwords", () => Effect.tryPromise({ try: () => withSslProbeServer(async (port) => { @@ -2218,7 +2218,7 @@ describe("legacy gen types", () => { expect(child.spawned[1]?.args).toContain("supabase_network_demo_project_with_spaces"); expect( docker.env.has( - "PG_META_DB_URL=postgresql://postgres:secret-password@db:5432/postgres?connect_timeout=10", + "PG_META_DB_URL=postgresql://postgres:postgres@db:5432/postgres?connect_timeout=10", ), ).toBe(true); } finally { diff --git a/apps/cli/src/legacy/commands/gen/types/types.shared.ts b/apps/cli/src/legacy/commands/gen/types/types.shared.ts index 4480a03ada..1b3ffc0adb 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.shared.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.shared.ts @@ -93,10 +93,6 @@ export function parseQueryTimeoutSeconds( }); } -export function localDbPassword() { - return process.env["SUPABASE_DB_PASSWORD"] ?? "postgres"; -} - export function parseDatabaseUrl( url: string, ): Effect.Effect { diff --git a/apps/cli/src/legacy/commands/gen/types/types.unit.test.ts b/apps/cli/src/legacy/commands/gen/types/types.unit.test.ts index 44bf4d9598..bab388fa5b 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.unit.test.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.unit.test.ts @@ -7,7 +7,6 @@ import { defaultSchemas, legacyRootCaBundle, localDbContainerId, - localDbPassword, localNetworkId, parseDatabaseUrl, parseQueryTimeoutSeconds, @@ -209,7 +208,7 @@ describe("schema and id helpers", () => { expect(localDbContainerId(longId)).toBe(`supabase_db_${"a".repeat(40)}`); }); - it("reads the services hostname and db password from the environment", () => { + it("reads the services hostname from the environment", () => { expect( withEnv("DOCKER_HOST", undefined, () => withEnv("SUPABASE_SERVICES_HOSTNAME", undefined, () => legacyGetHostname()), @@ -218,8 +217,6 @@ describe("schema and id helpers", () => { expect(withEnv("SUPABASE_SERVICES_HOSTNAME", "db.internal", () => legacyGetHostname())).toBe( "db.internal", ); - expect(withEnv("SUPABASE_DB_PASSWORD", undefined, () => localDbPassword())).toBe("postgres"); - expect(withEnv("SUPABASE_DB_PASSWORD", "secret", () => localDbPassword())).toBe("secret"); }); it("brackets ipv6 hosts in the generated postgres url", () => { From a126dfb1485858f6a3dd0b65ea1d8714f5df665b Mon Sep 17 00:00:00 2001 From: 7ttp <117663341+7ttp@users.noreply.github.com> Date: Wed, 15 Jul 2026 17:16:59 +0530 Subject: [PATCH 6/6] revert --- apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md | 2 +- apps/cli/src/legacy/commands/gen/types/types.handler.ts | 8 ++++++-- .../legacy/commands/gen/types/types.integration.test.ts | 4 ++-- apps/cli/src/legacy/commands/gen/types/types.shared.ts | 4 ++++ apps/cli/src/legacy/commands/gen/types/types.unit.test.ts | 5 ++++- 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md index 6c37a42088..4ef5a89369 100644 --- a/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md @@ -66,7 +66,7 @@ default 10s pg-delta probe timeout. | `SUPABASE_API_SCHEMAS` | local schemas used when `--schema` is omitted | no (defaults to `public,graphql_public`) | | `SUPABASE_ENV` | selects nested dotenv files for local generation | no (defaults to `development`) | | `SUPABASE_PROFILE` | built-in profile name or YAML file path | no (falls back to `~/.supabase/profile` -> `supabase`) | -| `SUPABASE_DB_PASSWORD` | database password for the `--linked` workdir project | no (**ignored** for `--local`; ad-hoc `--project-id` always mints a temporary login role instead of using it) | +| `SUPABASE_DB_PASSWORD` | database password for `--local` and the `--linked` workdir project | no (defaults to `postgres`; **ignored** for ad-hoc `--project-id`, which always mints a temporary login role) | | `SUPABASE_SERVICES_HOSTNAME` | host used for the local TLS probe | no (defaults to `127.0.0.1`) | | `SUPABASE_INTERNAL_IMAGE_REGISTRY` | pg-meta image registry override (`docker.io` → Docker Hub; any other value → that registry) | no (defaults to the ECR registry) | | `SUPABASE_CA_SKIP_VERIFY` | when `true`, prints a TLS-verification-disabled warning to stderr | no | diff --git a/apps/cli/src/legacy/commands/gen/types/types.handler.ts b/apps/cli/src/legacy/commands/gen/types/types.handler.ts index a483c362e7..3f090e6090 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.handler.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.handler.ts @@ -48,6 +48,7 @@ import { defaultSchemas, buildPostgresUrl, localDbContainerId, + localDbPassword, localNetworkId, parseDatabaseUrl, parseQueryTimeoutSeconds, @@ -531,7 +532,10 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le yield* Effect.gen(function* () { if (flags.local) { const config = yield* legacyReadDbToml(fs, path, cliConfig.workdir); - yield* legacyApplyProjectEnv(config.projectEnv, Object.keys(config.projectEnv)); + yield* legacyApplyProjectEnv( + config.projectEnv, + Object.keys(config.projectEnv).filter((key) => key !== "SUPABASE_DB_PASSWORD"), + ); const projectId = Option.getOrElse(config.projectId, () => path.basename(cliConfig.workdir)); const paths = legacyTempPaths(path, cliConfig.workdir); @@ -560,7 +564,7 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le host: "db", port: 5432, user: "postgres", - password: config.password, + password: localDbPassword(), database: "postgres", }), host: "db", diff --git a/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts b/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts index 3008d85d59..5d75f2caa9 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts @@ -2179,7 +2179,7 @@ describe("legacy gen types", () => { }), ); - it.live("uses sanitized local docker ids and ignores shell db passwords", () => + it.live("uses sanitized local docker ids and env-backed local db passwords", () => Effect.tryPromise({ try: () => withSslProbeServer(async (port) => { @@ -2218,7 +2218,7 @@ describe("legacy gen types", () => { expect(child.spawned[1]?.args).toContain("supabase_network_demo_project_with_spaces"); expect( docker.env.has( - "PG_META_DB_URL=postgresql://postgres:postgres@db:5432/postgres?connect_timeout=10", + "PG_META_DB_URL=postgresql://postgres:secret-password@db:5432/postgres?connect_timeout=10", ), ).toBe(true); } finally { diff --git a/apps/cli/src/legacy/commands/gen/types/types.shared.ts b/apps/cli/src/legacy/commands/gen/types/types.shared.ts index 1b3ffc0adb..4480a03ada 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.shared.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.shared.ts @@ -93,6 +93,10 @@ export function parseQueryTimeoutSeconds( }); } +export function localDbPassword() { + return process.env["SUPABASE_DB_PASSWORD"] ?? "postgres"; +} + export function parseDatabaseUrl( url: string, ): Effect.Effect { diff --git a/apps/cli/src/legacy/commands/gen/types/types.unit.test.ts b/apps/cli/src/legacy/commands/gen/types/types.unit.test.ts index bab388fa5b..44bf4d9598 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.unit.test.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.unit.test.ts @@ -7,6 +7,7 @@ import { defaultSchemas, legacyRootCaBundle, localDbContainerId, + localDbPassword, localNetworkId, parseDatabaseUrl, parseQueryTimeoutSeconds, @@ -208,7 +209,7 @@ describe("schema and id helpers", () => { expect(localDbContainerId(longId)).toBe(`supabase_db_${"a".repeat(40)}`); }); - it("reads the services hostname from the environment", () => { + it("reads the services hostname and db password from the environment", () => { expect( withEnv("DOCKER_HOST", undefined, () => withEnv("SUPABASE_SERVICES_HOSTNAME", undefined, () => legacyGetHostname()), @@ -217,6 +218,8 @@ describe("schema and id helpers", () => { expect(withEnv("SUPABASE_SERVICES_HOSTNAME", "db.internal", () => legacyGetHostname())).toBe( "db.internal", ); + expect(withEnv("SUPABASE_DB_PASSWORD", undefined, () => localDbPassword())).toBe("postgres"); + expect(withEnv("SUPABASE_DB_PASSWORD", "secret", () => localDbPassword())).toBe("secret"); }); it("brackets ipv6 hosts in the generated postgres url", () => {