diff --git a/apps/cli/src/commands/gen/types/SIDE_EFFECTS.md b/apps/cli/src/commands/gen/types/SIDE_EFFECTS.md index c82eb7e2da..e9ceaabecd 100644 --- a/apps/cli/src/commands/gen/types/SIDE_EFFECTS.md +++ b/apps/cli/src/commands/gen/types/SIDE_EFFECTS.md @@ -28,7 +28,7 @@ instead of inspecting `supabase_db_*`. | `/supabase/config.json` or `config.toml` (`[db]` values only) | JSON/TOML | every `--remote=` resolution, regardless of `--schema` — `DbConfigResolver` reads it through `readDbConfig` (`config.json` preferred when both exist) to fill a passwordless local DSN's `[db].password` and to classify the target host as local vs. remote. A present-but-unparseable config file fails the command (`DbConfigLoadError`) even on this path, independent of the schema-selecting load above. Resolving a remote project's connection reads the whole config the same strict way (auth hooks, SMTP, email templates, the `signing_keys_path` file, `[db.vault]` secrets), now for `--lang=typescript` too; a failure there fails other languages and falls back to the Management API for TypeScript. | | `{/supabase}/.env*` | dotenv | the local target; resolves the same nested environment overrides as the CLI. Every `--remote=` resolution also reads it (same `.env..local`/`.env.local`/`.env.`/`.env` search) to populate the `PG*` fallbacks used while parsing the DSN. | | `~/.pg_service.conf` (or a `PGSERVICEFILE`/DSN `servicefile=` override) | libpq service file | `--remote=`, only when the DSN or `PGSERVICE` names a `service` — its settings fill in for connection fields the DSN itself omits | -| `Package.resolved` (nearest) | JSON | `--lang swift` without `SUPABASE_TYPEGEN`, on macOS or Linux x86_64: the first one found walking up from the working directory, including the copies inside an `.xcodeproj` or `.xcworkspace`; the supabase-swift version it pins picks the `supabase-typegen` release | +| `Package.resolved` (nearest) | JSON | `--lang swift` on macOS or Linux x86_64: the first one found walking up from the working directory, including the copies inside an `.xcodeproj` or `.xcworkspace`; the supabase-swift version it pins picks the `supabase-typegen` release | | `/supabase/.temp/rest-version` | plain text | the local target, when `db.major_version > 14` — forces v9 compat if the tag contains `v9`; and `--lang=typescript` on the linked project, where it is required for direct generation and, without its leading `v`, becomes `__InternalSupabase.PostgrestVersion` | | `$PGSSLROOTCERT`, `$PGSSLCERT` / `$PGSSLKEY`, `pg_service.conf` | PEM, libpq | a remote project (any `--lang`), when `PGSSLCERT`/`PGSSLKEY` or `PGSERVICE` are set; `$PGSSLROOTCERT` also replaces the pinned Supabase CA; the PEM files are skipped under `PGSSLMODE=disable` | @@ -87,8 +87,7 @@ Any other `--lang` whose registry entry is out-of-process runs its own tool the same way: stdout is the output, stderr is folded into the error on failure. The tool inherits the CLI's entire environment, so a `PUB_CACHE` or proxy setting reaches it unchanged. For `--lang swift` the binary is -`SUPABASE_TYPEGEN` when set, else the cached release binary of the -supabase-swift version `Package.resolved` pins, downloaded once from +the cached release binary of the supabase-swift version `Package.resolved` pins, downloaded once from `https://github.com/supabase/supabase-swift/releases/download/v/`, else `supabase-typegen` from `PATH`. Introspection and the in-process languages never shell out. On a native or Docker-based managed stack @@ -111,7 +110,6 @@ way `--remote=` does. | `SUPABASE_DB_PASSWORD` | database password for a remote project (shell env > project `.env*` > config); withheld, with a stderr `Not sending SUPABASE_DB_PASSWORD to : this directory is linked to . Using a temporary login role instead (needs supabase login or SUPABASE_ACCESS_TOKEN).` line, when the target differs from `.temp/project-ref`, which mints a temporary login role instead. **Not read by the local target**, which uses `[db].password` (default `postgres`) | no | | `SUPABASE_SERVICES_HOSTNAME` | host used to reach the local database on the legacy Docker Compose stack; read from the shell env first, then project `.env*` | no (defaults to the shell `DOCKER_HOST`'s tcp host, else `127.0.0.1`) | | `SUPABASE_WORKDIR` | working directory `supabase/config.toml`/`config.json` is read from (`--workdir` takes priority) | no — when unset, the CLI walks up from cwd looking for `supabase/config.toml` or `supabase/config.json`; when SET (flag or env) the directory is used exactly as given and **no ancestor is searched** | -| `SUPABASE_TYPEGEN` | path of the `supabase-typegen` binary `--lang swift` runs, skipping the version lookup and download | no | | `XDG_CACHE_HOME`, `HOME` | locate the cache directory of the `--lang swift` release binary | no | | `PATH`, `PATHEXT`, `ComSpec` | find the tool of an out-of-process `--lang`; `PATHEXT` and `ComSpec` matter on Windows only | no (`PATHEXT` defaults to `.COM;.EXE;.BAT;.CMD`, `ComSpec` to `cmd.exe`; without `PATH` the tool is reported as not installed) | | `PGSSLMODE`, `PGSSLROOTCERT`, `PGSSLCERT`, `PGSSLKEY`, `PGSSLPASSWORD`, `PGSERVICE`, `PGSERVICEFILE` | a remote project (any `--lang`): client-cert pair outside `sslmode=disable`; invalid values fail (TypeScript falls back to the Management API instead); an explicit `sslmode`/`sslrootcert` replaces the Supabase CA pin (see Notes) | no | diff --git a/apps/cli/src/commands/gen/types/types.generator.integration.test.ts b/apps/cli/src/commands/gen/types/types.generator.integration.test.ts index 3866e553e0..73e33f2244 100644 --- a/apps/cli/src/commands/gen/types/types.generator.integration.test.ts +++ b/apps/cli/src/commands/gen/types/types.generator.integration.test.ts @@ -99,6 +99,94 @@ const withFakeDart = ( return yield* body({ cwd, bin }); }).pipe(Effect.provide(BunServices.layer)); +/** A stand-in `supabase-typegen` that echoes its arguments and its stdin. */ +const FAKE_TYPEGEN_BINARY = `#!/bin/sh +printf 'supabase-typegen args: %s\\n' "$*" +cat +`; + +const SWIFT_VERSION = "3.0.0-alpha.1"; + +/** Pins supabase-swift the way a SwiftPM `Package.resolved` does. */ +const PACKAGE_RESOLVED = JSON.stringify({ + pins: [ + { + identity: "supabase-swift", + kind: "remoteSourceControl", + location: "https://github.com/supabase/supabase-swift", + state: { revision: "0000000", version: SWIFT_VERSION }, + }, + ], + version: 3, +}); + +const hasSwiftReleaseAsset = ["darwin-arm64", "darwin-x64", "linux-x64"].includes( + `${process.platform}-${process.arch}`, +); + +const withFakeSupabaseTypegen = ( + body: (context: { + readonly cwd: string; + readonly install: (directory: string) => Effect.Effect; + }) => Effect.Effect, +) => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const cwd = yield* fs.realPath( + yield* fs.makeTempDirectoryScoped({ prefix: "gen-types-swift-" }), + ); + const install = (directory: string) => + Effect.gen(function* () { + const binary = path.join(directory, "supabase-typegen"); + yield* fs.makeDirectory(directory, { recursive: true }); + yield* fs.writeFileString(binary, FAKE_TYPEGEN_BINARY); + yield* fs.chmod(binary, 0o755); + return binary; + }).pipe(Effect.orDie); + return yield* body({ cwd, install }); + }).pipe(Effect.provide(BunServices.layer)); + +const expectFakeSupabaseTypegenOutput = (output: string) => { + expect(output).toContain("supabase-typegen args: --access-control internal\n"); + expect(output).toContain('"version":1'); +}; + +describe.skipIf(process.platform === "win32")("genTypesGeneratorLayer for --lang swift", () => { + it.effect.skipIf(!hasSwiftReleaseAsset)( + "runs the cached release binary under XDG_CACHE_HOME for the pinned version", + () => + withFakeSupabaseTypegen(({ cwd, install }) => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + yield* fs.writeFileString(`${cwd}/Package.resolved`, PACKAGE_RESOLVED); + yield* install(`${cwd}/cache/supabase/typegen/${SWIFT_VERSION}`); + const output = yield* generate("swift").pipe( + Effect.provide(layerIn(cwd, { PATH: "/usr/bin:/bin", XDG_CACHE_HOME: `${cwd}/cache` })), + ); + expectFakeSupabaseTypegenOutput(output); + }), + ), + ); + + it.effect.skipIf(!hasSwiftReleaseAsset)( + "runs the cached release binary under HOME when XDG_CACHE_HOME is unset", + () => + withFakeSupabaseTypegen(({ cwd, install }) => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const caches = process.platform === "darwin" ? "Library/Caches" : ".cache"; + yield* fs.writeFileString(`${cwd}/Package.resolved`, PACKAGE_RESOLVED); + yield* install(`${cwd}/home/${caches}/supabase/typegen/${SWIFT_VERSION}`); + const output = yield* generate("swift").pipe( + Effect.provide(layerIn(cwd, { PATH: "/usr/bin:/bin", HOME: `${cwd}/home` })), + ); + expectFakeSupabaseTypegenOutput(output); + }), + ), + ); +}); + describe.skipIf(process.platform === "win32")( "genTypesGeneratorLayer with a real subprocess", () => { diff --git a/apps/cli/src/commands/gen/types/types.generator.layer.ts b/apps/cli/src/commands/gen/types/types.generator.layer.ts index 447a9a2f8f..7332d5e8d3 100644 --- a/apps/cli/src/commands/gen/types/types.generator.layer.ts +++ b/apps/cli/src/commands/gen/types/types.generator.layer.ts @@ -72,6 +72,8 @@ export const genTypesGeneratorLayer = Layer.effect( PATH: yield* optionalEnv("PATH"), PATHEXT: yield* optionalEnv("PATHEXT"), ComSpec: yield* optionalEnv("ComSpec"), + XDG_CACHE_HOME: yield* optionalEnv("XDG_CACHE_HOME"), + HOME: yield* optionalEnv("HOME"), }; return GenTypesGenerator.of({ generate: Effect.fn("GenTypes.generate")(function* (input: GenTypesGenerateInput) { diff --git a/apps/cli/src/commands/gen/types/types.typegen-host.ts b/apps/cli/src/commands/gen/types/types.typegen-host.ts index 0776ea4c05..080cbb9f61 100644 --- a/apps/cli/src/commands/gen/types/types.typegen-host.ts +++ b/apps/cli/src/commands/gen/types/types.typegen-host.ts @@ -15,7 +15,10 @@ import { withProcessSpanScoped } from "../../../shared/telemetry/spans.ts"; export interface TypegenHostOptions { /** Where out-of-process tools run: the directory the command was invoked from. */ readonly cwd: string; - /** What the registry's Windows lookup reads; the child inherits the full environment. */ + /** + * What the registry's drivers read: the Windows tool lookup and the Swift binary cache. + * The child inherits the full environment. + */ readonly env: Readonly>; readonly platform: NodeJS.Platform; readonly spawner: ChildProcessSpawner.ChildProcessSpawner["Service"];