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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions apps/cli/docs/go-cli-divergences.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ These commands exist in the TS CLI today but have no direct top-level equivalent
Under the `SUPABASE_USE_PG_DELTA_NEXT=false` legacy opt-out the flag is
accepted but has no effect, since the legacy edge-runtime engine does not
emit coverage diagnostics. Default behavior (omitted flag) matches Go.
- `gen types` has two TS-only `--lang` values (the Go reference accepted only
`typescript`/`go`/`swift`/`python`): `json` emits the language-neutral
`GeneratorMetadata` introspection document (the `@supabase/postgrest-typegen`
contract, for third-party type generators), and `dart` pipes that document
through the `supabase_typegen` Dart package on the host, requiring the Dart
SDK on PATH and `supabase_typegen` as a dev dependency of the current
project. `dart` generates one schema per run and therefore accepts at most
one `--schema`. Both require a pg-meta image that ships a `json` output for
`PG_META_GENERATE_TYPES` (supabase/postgres-meta#1110, stacked on the #1084
postgrest-typegen extraction).
- `db push` has a TS-only `--skip-vault` flag. It applies migrations without
resolving or updating `[db.vault]` secrets; default behavior still matches Go.
- Every legacy command that resolves a linked project ref for its own database
Expand Down
11 changes: 10 additions & 1 deletion apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,19 @@ config for that ref to build the fallback connection (the saved workdir
| -------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------- |
| `docker`/`podman container inspect supabase_db_<project_id>` | `--local` | assert `supabase start` is running |
| `docker`/`podman run --rm --network <net> --env … <pgmeta> node dist/server/server.js` | `--local`, `--db-url`, project-ref paths with non-TypeScript `--lang` | run pg-meta to generate types from a live database |
| `dart run supabase_typegen --input - --output - --schema <schema>` | `--lang dart`, after the pg-meta container exits successfully | turn pg-meta's json metadata into Dart source |

A raw TCP `SSLRequest` probe is also opened to the target database host/port to
detect TLS support before launching pg-meta, with the default 10s pg-delta probe
timeout.

For `--lang dart` the pg-meta container runs the `json` generator; its stdout is
collected instead of printed and piped to the Dart typegen's stdin. The Dart SDK
must be on PATH and `supabase_typegen` must be a dev dependency of the current
project. The typegen generates one schema per run, so `--lang dart` accepts at
most one `--schema` (defaulting to `public`) and fails otherwise instead of
silently dropping schemas.

## Environment Variables

| Variable | Purpose | Required? |
Expand Down Expand Up @@ -108,7 +116,8 @@ Not applicable.
- With `--local`, a missing `supabase/config.toml` uses the embedded config defaults plus
shell and nested dotenv overrides, matching the legacy CLI.
- **Sanctioned intentional divergence (CLI-1988 parity ruling):**
`--lang` accepts `typescript` (default), `go`, `swift`, or `python`. Project-ref paths
`--lang` accepts `typescript` (default), `go`, `swift`, `python`, `dart`, or `json`
(the last two are TS-only, see `docs/go-cli-divergences.md`). Project-ref paths
(`--linked`, `--project-id`, and the implicit linked fallback) use the Management API
for TypeScript, and run pg-meta locally against the project database (temporary
login-role credentials, preview-branch fallback) for the other languages. The old Go
Expand Down
6 changes: 5 additions & 1 deletion apps/cli/src/legacy/commands/gen/types/types.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { legacyParseSchemaFlags } from "../../../shared/legacy-schema-flags.ts";
import { legacyGenTypes } from "./types.handler.ts";
import { legacyGenTypesRuntimeLayer } from "./types.layers.ts";

const LANG_VALUES = ["typescript", "go", "swift", "python"] as const;
const LANG_VALUES = ["typescript", "go", "swift", "python", "dart", "json"] as const;
const SWIFT_ACCESS_CONTROL_VALUES = ["internal", "public"] as const;

const config = {
Expand Down Expand Up @@ -77,6 +77,10 @@ export const legacyGenTypesCommand = Command.make("types", commandConfig).pipe(
command: "supabase gen types --db-url 'postgresql://...' --schema public --schema auth",
description: "Generate types from a database URL",
},
{
command: "supabase gen types --local --lang=dart",
description: "Generate Dart types from the local dev database",
},
]),
Command.withHandler((flags) =>
legacyGenTypes(flags).pipe(
Expand Down
76 changes: 71 additions & 5 deletions apps/cli/src/legacy/commands/gen/types/types.handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { loadProjectConfig } from "@supabase/config";
import { ChildProcessSpawner } from "effect/unstable/process";
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
import { Effect, FileSystem, Option, Path, Stdio, Stream } from "effect";
import {
LegacyDnsResolverFlag,
Expand Down Expand Up @@ -46,6 +46,9 @@ import { LegacyGenTypesNetworkError, LegacyGenTypesUnexpectedStatusError } from
import { legacyGetHostname } from "../../../shared/legacy-hostname.ts";
import { LegacyPlatformApiFactory } from "../../../auth/legacy-platform-api-factory.service.ts";
import {
DART_TYPEGEN_ARGS,
DART_TYPEGEN_COMMAND,
DART_TYPEGEN_UNAVAILABLE_MESSAGE,
defaultSchemas,
buildPostgresUrl,
localDbContainerId,
Expand Down Expand Up @@ -257,6 +260,10 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le
const schemas = flags.schema;
const lang = flags.lang;
const swiftAccessControl = flags.swiftAccessControl;
// Dart types are produced by piping pg-meta's language-neutral `json`
// generator metadata through the supabase_typegen package, so the container
// always runs the json generator for `--lang dart`.
const pgMetaLang = lang === "dart" ? "json" : lang;

const loadConfig = () => loadProjectConfig(cliConfig.workdir, { goViperCompat: true });
const loadConfigForRef = (projectRef: string) =>
Expand Down Expand Up @@ -416,7 +423,7 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le
`PG_META_DB_URL=${target.url}`,
`PG_CONN_TIMEOUT_SECS=${queryTimeoutSeconds}`,
`PG_QUERY_TIMEOUT_SECS=${queryTimeoutSeconds}`,
`PG_META_GENERATE_TYPES=${lang}`,
`PG_META_GENERATE_TYPES=${pgMetaLang}`,
`PG_META_GENERATE_TYPES_INCLUDED_SCHEMAS=${input.includedSchemas}`,
`PG_META_GENERATE_TYPES_SWIFT_ACCESS_CONTROL=${swiftAccessControl}`,
`PG_META_GENERATE_TYPES_DETECT_ONE_TO_ONE_RELATIONSHIPS=${String(!input.postgrestV9Compat)}`,
Expand Down Expand Up @@ -456,11 +463,18 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le
stderr: "pipe",
});

// For `--lang dart` the container emits the json generator
// metadata, which is collected and piped into the Dart typegen
// after the run instead of being printed.
let stderrText = "";
const [exitCode] = yield* Effect.all(
const [exitCode, stdoutText] = yield* Effect.all(
[
child.exitCode.pipe(Effect.map(Number)),
forwardByteStream(child.stdout, (text) => output.raw(text, "stdout")),
lang === "dart"
? collectByteStream(child.stdout)
: forwardByteStream(child.stdout, (text) => output.raw(text, "stdout")).pipe(
Effect.map(() => ""),
),
forwardByteStream(child.stderr, (text) =>
Effect.sync(() => {
stderrText += text;
Expand All @@ -469,7 +483,7 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le
],
{ concurrency: "unbounded" },
);
return { exitCode, stderrText };
return { exitCode, stderrText, stdoutText };
});

const runTarget = (conn: LegacyPgConnInput) =>
Expand All @@ -481,6 +495,18 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le
probePort: conn.port,
});

// The Dart typegen generates one schema per run; without this guard a
// multi-schema selection would silently come out as only one schema.
const dartSchemas = input.includedSchemas.split(",").filter((schema) => schema.length > 0);
if (lang === "dart" && dartSchemas.length > 1) {
return yield* Effect.fail(
new Error(
`--lang dart generates one schema per run, but got: ${input.includedSchemas}. ` +
"Pass a single --schema and run the command once per schema.",
),
);
}

const result =
input.poolerFallback === undefined
? yield* buildRun(input)
Expand All @@ -498,6 +524,46 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le
if (result.exitCode !== 0) {
return yield* Effect.fail(new Error(`error running container: exit ${result.exitCode}`));
}

if (lang === "dart") {
yield* runDartTypegen(result.stdoutText, dartSchemas[0] ?? "public");
}
}),
);

// Pipes the json generator metadata into the supabase_typegen package on
// the host: the generated Dart code arrives on stdout like every other
// language, and the package's own summary line stays on stderr.
const runDartTypegen = (metadataJson: string, schema: string) =>
Effect.scoped(
Effect.gen(function* () {
const child = yield* spawner
.spawn(
ChildProcess.make(DART_TYPEGEN_COMMAND, [...DART_TYPEGEN_ARGS, "--schema", schema], {
stdin: Stream.succeed(new TextEncoder().encode(metadataJson)),
stdout: "pipe",
stderr: "pipe",
}),
)
.pipe(Effect.mapError(() => new Error(DART_TYPEGEN_UNAVAILABLE_MESSAGE)));

const [exitCode] = yield* Effect.all(
[
child.exitCode.pipe(Effect.map(Number)),
forwardByteStream(child.stdout, (text) => output.raw(text, "stdout")),
forwardByteStream(child.stderr, (text) => output.raw(text, "stderr")),
],
{ concurrency: "unbounded" },
);
if (exitCode !== 0) {
return yield* Effect.fail(
new Error(
`error running the supabase_typegen package: exit ${exitCode}. ` +
"If the package could not be resolved, add supabase_typegen as a dev " +
"dependency of the current project to generate Dart types.",
),
);
}
}),
);

Expand Down
Loading