Skip to content
Open
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
2 changes: 1 addition & 1 deletion apps/cli/docs/go-cli-porting-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ These commands exist in the TS CLI today but have no direct top-level equivalent
| `db dump` | `ported` | `legacy/commands/db/dump/` | `n/a` | `n/a` | Native TS port. Streams `pg_dump`/`pg_dumpall` via a Docker container (`LegacyDockerRun`); schema/data/role modes, `--dry-run` script print, IPv4 transaction-pooler fallback. |
| `db lint` | `ported` | `legacy/commands/db/lint/` | `n/a` | `n/a` | Native TS port. Runs `plpgsql_check` in a rolled-back transaction via LegacyDbConnection; emits Go-parity pretty JSON. |
| `db pull` | `ported` | `legacy/commands/db/pull/` | `n/a` | `n/a` | Native TS port. Native pg-delta / migra migration + `--declarative` pg-delta export; reconciles `schema_migrations`. The initial-migra pull dumps the remote schema natively (`pg_dump`) then appends the migra diff. Only `--experimental` (structured dump) still delegates to Go, pending a TS PostgreSQL DDL parser for `format.WriteStructuredSchemas`. |
| `db push` | `ported` | `legacy/commands/db/push/` | `n/a` | `n/a` | Native TS port. Connects local/linked/`--db-url`; pushes pending migrations, `--include-seed` seeds (`seed_files` hash tracking), `--include-roles`, `[db.vault]` secrets; `--dry-run`. `encrypted:` vault secrets + best-effort pg-delta catalog cache not ported (no output impact). |
| `db push` | `ported` | `legacy/commands/db/push/` | `n/a` | `n/a` | Native TS port. Connects local/linked/`--db-url`; pushes pending migrations, `--include-seed` seeds (`seed_files` hash tracking), `--include-roles`, `[db.vault]` secrets including decrypted `encrypted:` values; `--dry-run`; best-effort pg-delta migrations-catalog cache (warning-only on failure). |
| `db reset` | `ported` | `legacy/commands/db/reset/` | `n/a` | `n/a` | Remote path native (drop user schemas, vault upsert, MigrateAndSeed, `--version`/`--last`, `--sql-paths` seed override). Local path native: running check, recreate + migrate + seed via the hidden Go `db __db-bootstrap` seam, storage-gated bucket seeding (reuses `seed buckets`), git-branch `Finished…` line. Only the niche `--experimental` remote schema-files path still delegates to the Go binary (telemetry-disabled). |
| `db start` | `ported` | `legacy/commands/db/start/` | `n/a` | `n/a` | Native TS port. Validates config, checks "already running" (prints Go's line), else delegates the container bootstrap (create + health + initial schema/roles/migrations/seed + `_current_branch`) to the hidden Go `db __db-bootstrap --mode start` seam. No status table / `cli_stack_started` (those are `supabase start`). `--from-backup` supported. |
| `inspect report` | `ported` | `legacy/commands/inspect/report/` | `n/a` | `n/a` | Native TS port. Runs every inspect query via server-side `COPY ... CSV`, writes 14 CSVs under `<output-dir>/<date>/`, then renders a Go-parity Glamour rules summary (bounded csvq-subset evaluator; custom `[experimental.inspect.rules]` supported). |
Expand Down
22 changes: 2 additions & 20 deletions apps/cli/src/legacy/commands/db/diff/diff.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { LegacyCliConfig } from "../../../config/legacy-cli-config.service.ts";
import { legacyAqua, legacyYellow } from "../../../shared/legacy-colors.ts";
import { legacyReadDbToml } from "../../../shared/legacy-db-config.toml-read.ts";
import { LegacyDbConfigResolver } from "../../../shared/legacy-db-config.service.ts";
import type { LegacyPgConnInput } from "../../../shared/legacy-db-connection.service.ts";
import type { LegacyDbConnType } from "../../../shared/legacy-db-target-flags.ts";
import { legacyGetHostname } from "../../../shared/legacy-hostname.ts";
import { legacyMakeDir } from "../../../shared/legacy-make-dir.ts";
Expand Down Expand Up @@ -45,23 +44,6 @@ import {
const warnDiff = `WARNING: The diff tool is not foolproof, so you may need to manually rearrange and modify the generated migration.
Run ${legacyAqua("supabase db reset")} to verify that the new migration does not generate errors.`;

/** Builds a plain Postgres URL from a resolved connection (Go's `ToPostgresURL`). */
const connToUrl = (conn: LegacyPgConnInput): string =>
legacyToPostgresURL({
host: conn.host,
port: conn.port,
user: conn.user,
password: conn.password,
database: conn.database,
...(conn.options !== undefined ? { options: conn.options } : {}),
...(conn.runtimeParams !== undefined ? { runtimeParams: conn.runtimeParams } : {}),
// Preserve a `--db-url` connect_timeout; Go's ToPostgresURL serializes the
// parsed ConnectTimeout (`connect.go`), defaulting to 10 only when unset.
...(conn.connectTimeoutSeconds !== undefined
? { connectTimeoutSeconds: conn.connectTimeoutSeconds }
: {}),
});

/**
* Rebuilds the `db diff` argv for the pgAdmin / pg-schema delegate path. Flags
* stay flags (the Go-proxy channel-parity rule). The explicit `--from`/`--to` and
Expand Down Expand Up @@ -235,7 +217,7 @@ export const legacyDbDiff = Effect.fn("legacy.db.diff")(function* (flags: Legacy
mergedLinkedRef = ref2;
cfg = yield* legacyReadDbToml(fs, path, cliConfig.workdir, ref2);
}
return connToUrl(resolved.conn);
return legacyToPostgresURL(resolved.conn);
}
case "migrations":
return yield* seam.exportCatalog({
Expand Down Expand Up @@ -362,7 +344,7 @@ export const legacyDbDiff = Effect.fn("legacy.db.diff")(function* (flags: Legacy
});
const linkedRef = Option.getOrUndefined(resolved.ref ?? Option.none());
if (linkedRef !== undefined) linkedRefForCache = linkedRef;
const targetUrl = connToUrl(resolved.conn);
const targetUrl = legacyToPostgresURL(resolved.conn);

// Read config with the resolved linked ref so a matching `[remotes.<ref>]`
// block merges before the engine/format/runtime are read — Go loads config
Expand Down
21 changes: 2 additions & 19 deletions apps/cli/src/legacy/commands/db/pull/pull.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,23 +91,6 @@ const DEPRECATION_LINE =
/** Migration-file mode for the initial pg_dump seed (Go's `OpenFile(..., 0644)`). */
const MIGRATION_FILE_MODE = 0o644;

/** Builds a plain Postgres URL from a resolved connection (Go's `ToPostgresURL`). */
const connToUrl = (conn: LegacyPgConnInput): string =>
legacyToPostgresURL({
host: conn.host,
port: conn.port,
user: conn.user,
password: conn.password,
database: conn.database,
...(conn.options !== undefined ? { options: conn.options } : {}),
...(conn.runtimeParams !== undefined ? { runtimeParams: conn.runtimeParams } : {}),
// Preserve a `--db-url` connect_timeout; Go's ToPostgresURL serializes the
// parsed ConnectTimeout (`connect.go`), defaulting to 10 only when unset.
...(conn.connectTimeoutSeconds !== undefined
? { connectTimeoutSeconds: conn.connectTimeoutSeconds }
: {}),
});

/** Rebuilds the `db pull` argv for the Go-delegated `--experimental` structured-dump branch. */
const rebuildDelegateArgs = (flags: LegacyDbPullFlags): Array<string> => {
const args = ["db", "pull"];
Expand Down Expand Up @@ -233,7 +216,7 @@ export const legacyDbPull = Effect.fn("legacy.db.pull")(function* (flags: Legacy
});
const linkedRef = Option.getOrUndefined(resolved.ref ?? Option.none());
if (linkedRef !== undefined) linkedRefForCache = linkedRef;
const targetUrl = connToUrl(resolved.conn);
const targetUrl = legacyToPostgresURL(resolved.conn);

// Reload config with the resolved linked ref so a matching `[remotes.<ref>]`
// block merges before the engine/format/runtime/declarative paths are read —
Expand Down Expand Up @@ -294,7 +277,7 @@ export const legacyDbPull = Effect.fn("legacy.db.pull")(function* (flags: Legacy
.pipe(Effect.orElseSucceed(() => Option.none()));
if (Option.isSome(pooler)) {
yield* legacyEmitPoolerFallbackWarning(resolved.conn.host);
return yield* attempt(connToUrl(pooler.value));
return yield* attempt(legacyToPostgresURL(pooler.value));
}
}
return yield* Effect.fail(error);
Expand Down
40 changes: 24 additions & 16 deletions apps/cli/src/legacy/commands/db/push/SIDE_EFFECTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ linked/remote Postgres database.

## Files Written

| Path | Format | When |
| ------------------------------------------------ | ------ | ------------------------------------------------------------------------- |
| `~/.supabase/<workdir-hash>/linked-project.json` | JSON | on the `--linked` path (post-run cache, Go's `ensureProjectGroupsCached`) |
| `~/.supabase/telemetry.json` | JSON | always (post-run telemetry flush) |

No project files are written. All other effects are database mutations (below).
| Path | Format | When |
| ------------------------------------------------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `~/.supabase/<workdir-hash>/linked-project.json` | JSON | on the `--linked` path (post-run cache, Go's `ensureProjectGroupsCached`) |
| `~/.supabase/telemetry.json` | JSON | always (post-run telemetry flush) |
| `<workdir>/supabase/.temp/pgdelta/catalog-<prefix>-migrations-<hash>-<ts>.json` | JSON | best-effort, after a successful migration apply, when pg-delta is enabled (`[experimental.pgdelta] enabled` or `SUPABASE_EXPERIMENTAL_PG_DELTA`); a failure only warns on stderr and never fails the push (Go's `pgcache.TryCacheMigrationsCatalog`) |
Comment thread
7ttp marked this conversation as resolved.
| `<workdir>/supabase/.temp/pgdelta/pgdelta-target-ca.crt` | PEM | same gate as above, when the target requires SSL (`legacyPreparePgDeltaRef`) |

## Database Mutations

Expand All @@ -43,11 +43,13 @@ No project files are written. All other effects are database mutations (below).

## Environment Variables

| Variable | Purpose | Required? |
| ----------------------- | ------------------------------------------- | ------------------------------------------------------- |
| `SUPABASE_ACCESS_TOKEN` | auth token for the `--linked` resolver path | no (falls back to keyring → `~/.supabase/access-token`) |
| `SUPABASE_DB_PASSWORD` | password for the linked/remote connection | no (`--password`/`-p` takes precedence) |
| `SUPABASE_YES` | auto-confirm prompts (Go's `viper YES`) | no (also `--yes`) |
| Variable | Purpose | Required? |
| ---------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------- |
| `SUPABASE_ACCESS_TOKEN` | auth token for the `--linked` resolver path | no (falls back to keyring → `~/.supabase/access-token`) |
| `SUPABASE_DB_PASSWORD` | password for the linked/remote connection | no (`--password`/`-p` takes precedence) |
| `SUPABASE_YES` | auto-confirm prompts (Go's `viper YES`) | no (also `--yes`) |
| `SUPABASE_EXPERIMENTAL_PG_DELTA` | enables the migrations-catalog cache when `[experimental.pgdelta].enabled` is unset | no (project `.env` or shell) |
| `SUPABASE_INTERNAL_IMAGE_REGISTRY` | overrides the pg-delta edge-runtime image registry for the cache export | no (project `.env` or shell) |

## Exit Codes

Expand Down Expand Up @@ -97,8 +99,14 @@ stdout is payload-only. A single `result` object is emitted:
- **`--dry-run`** prints the plan (roles / migrations / seeds) and applies nothing.
- **`[db.migrations].enabled = false`** / **`[db.seed].enabled = false`** print a
skip notice naming the project ref (empty for local/db-url).
- **Vault**: only non-empty, non-`env()` `[db.vault]` literals are synced (Go syncs
secrets with a non-empty SHA256). **Known gap vs Go**: `encrypted:`-prefixed
vault secrets are currently skipped — dotenvx/ECIES decryption is not yet ported.
- **Migrations catalog cache** (Go's best-effort `pgcache.TryCacheMigrationsCatalog`,
warning-only) is not ported; it produces no output, so parity is preserved.
- **Vault**: non-empty, non-`env()` `[db.vault]` values are synced after config
load, including decrypted `encrypted:` values.
- **Migrations catalog cache**: ported (Go's best-effort `pgcache.TryCacheMigrationsCatalog`).
After a successful migration apply, when pg-delta is enabled, exports the target's
pg-delta catalog via the edge-runtime stack and writes it under
`supabase/.temp/pgdelta/`, pruning older snapshots for the same prefix (retains 2).
A failure only warns on stderr (`Warning: failed to cache migrations catalog: …`)
and never fails the push, matching Go exactly. Reuses `legacyExportCatalogPgDelta`
(the same pg-delta export path `db pull`/`db diff` use, which always mounts the
project root at `/workspace`) rather than a second copy, so the ENOENT bug fixed in
Go's `pgcache/cache.go` (supabase/cli#5921) has no TS equivalent.
41 changes: 36 additions & 5 deletions apps/cli/src/legacy/commands/db/push/push.handler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Effect, FileSystem, Option, Path } from "effect";
import { Clock, Effect, FileSystem, Option, Path } from "effect";

import { CliArgs } from "../../../../shared/cli/cli-args.service.ts";
import { LegacyDnsResolverFlag } from "../../../../shared/legacy/global-flags.ts";
Expand All @@ -9,6 +9,7 @@ import { LegacyProjectRefResolver } from "../../../config/legacy-project-ref.ser
import { legacyAqua, legacyBold } from "../../../shared/legacy-colors.ts";
import { LegacyDbConfigResolver } from "../../../shared/legacy-db-config.service.ts";
import {
legacyApplyProjectEnv,
legacyCheckDbToml,
legacyLoadProjectEnv,
} from "../../../shared/legacy-db-config.toml-read.ts";
Expand All @@ -18,10 +19,17 @@ import {
legacySeedGlobals,
} from "../../../shared/legacy-migration-apply.ts";
import { legacyPromptYesNo } from "../../../shared/legacy-prompt-yes-no.ts";
import { legacyToPostgresURL } from "../../../shared/legacy-postgres-url.ts";
import { resolveLegacyDbTargetFlags } from "../../../shared/legacy-db-target-flags.ts";
import { LegacyLinkedProjectCache } from "../../../telemetry/legacy-linked-project-cache.service.ts";
import { LegacyTelemetryState } from "../../../telemetry/legacy-telemetry-state.service.ts";
import { legacyListLocalMigrations } from "../shared/legacy-pgdelta.cache.ts";
import { redactLegacyConnectionString } from "../../../shared/legacy-db-config.parse.ts";
import { legacyParseBoolEnv } from "../shared/legacy-diff-engine.ts";
import {
legacyListLocalMigrations,
legacyTryCacheMigrationsCatalog,
} from "../shared/legacy-pgdelta.cache.ts";
import { type LegacyPgDeltaContext } from "../shared/legacy-pgdelta.ts";
import {
LEGACY_ERR_MISSING_LOCAL,
LEGACY_ERR_MISSING_REMOTE,
Expand Down Expand Up @@ -92,6 +100,7 @@ export const legacyDbPush = Effect.fn("legacy.db.push")(function* (flags: Legacy
let linkedRefForCache: string | undefined;

const body = Effect.gen(function* () {
yield* legacyApplyProjectEnv(projectEnv);
const target = resolveLegacyDbTargetFlags(cliArgs.args);
// cobra MarkFlagsMutuallyExclusive("db-url", "linked", "local"), keyed off the
// explicitly-set flags (cobra's `Changed`), not the `--linked` default value.
Expand Down Expand Up @@ -286,9 +295,30 @@ export const legacyDbPush = Effect.fn("legacy.db.push")(function* (flags: Legacy
}
yield* legacyUpsertVaultSecrets(session, vaultSecrets);
yield* legacyApplyMigrations(session, fs, path, pending, applyError);
// Go best-effort caches the migrations catalog for pg-delta; a failure
// only warns (`push.go:99-101`). The catalog cache is not yet ported, so
// there is nothing to warn about — parity is preserved (no extra output).
const cacheEnabled =
toml.pgDelta.enabled ||
legacyParseBoolEnv(toml.envLookup("SUPABASE_EXPERIMENTAL_PG_DELTA"));
const pgDeltaCtx: LegacyPgDeltaContext = {
projectId: Option.getOrElse(cliConfig.projectId, () => ""),
cwd: workdir,
npmVersion: Option.getOrUndefined(toml.pgDelta.npmVersion),
denoVersion: toml.denoVersion,
};
yield* legacyTryCacheMigrationsCatalog(fs, path, pgDeltaCtx, {
enabled: cacheEnabled,
targetUrl: legacyToPostgresURL(cfg.conn),
Comment thread
7ttp marked this conversation as resolved.
conn: cfg.conn,
isLocal: cfg.isLocal,
migrationsDir: path.join(workdir, "supabase", "migrations"),
Comment thread
7ttp marked this conversation as resolved.
nowMillis: yield* Clock.currentTimeMillis,
}).pipe(
Effect.catch((error) =>
output.raw(
`Warning: failed to cache migrations catalog: ${redactLegacyConnectionString(error.message)}\n`,
"stderr",
),
),
);
} else {
yield* output.raw("Schema migrations are up to date.\n", "stderr");
}
Expand Down Expand Up @@ -336,5 +366,6 @@ export const legacyDbPush = Effect.fn("legacy.db.push")(function* (flags: Legacy
),
),
Effect.ensuring(telemetryState.flush),
Effect.scoped,
);
});
Loading
Loading