Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2a0000a
fix(deps): bump @typescript/native-preview from 7.0.0-dev.20260603.1 …
dependabot[bot] Jun 11, 2026
b497005
feat(cli): port supabase test db and test new (#5522)
Coly010 Jun 11, 2026
d564d1c
chore: sync API types from infrastructure (#5549)
supabase-cli-releaser[bot] Jun 11, 2026
aceffe8
fix(functions): add apikey compatibility header (#5509)
kallebysantos Jun 11, 2026
d1b5696
fix(deps): bump the npm-major group with 3 updates (#5557)
dependabot[bot] Jun 12, 2026
23a3f6c
fix(docker): bump supabase/logflare from 1.44.1 to 1.44.3 in /apps/cl…
dependabot[bot] Jun 12, 2026
e440151
test(cli): stabilize domains parity e2e (#5548)
jgoux Jun 12, 2026
0616225
fix(cli): auto-retry db dump/pull via the IPv4 pooler on IPv6-only ne…
avallete Jun 12, 2026
6c80e31
fix(cli): avoid auth for local typegen and services (#5553)
jgoux Jun 12, 2026
3ad9ff6
chore(api): sync Management API OpenAPI spec (#5564)
supabase-cli-releaser[bot] Jun 12, 2026
9b3cb17
feat(cli): port supabase inspect db to native TypeScript (#5554)
Coly010 Jun 12, 2026
a4a6879
chore: sync API types from infrastructure (#5562)
supabase-cli-releaser[bot] Jun 12, 2026
b9cad1d
chore(cli): organize lazy platform API factory (#5563)
jgoux Jun 12, 2026
e790eb3
feat(cli): enable pg-delta by default for new projects (#5511)
avallete Jun 12, 2026
8cb934b
fix(cli): handle custom domain response variants (#5552)
jgoux Jun 12, 2026
c66add0
fix(cli): Upload symlinked files when seeding storage buckets (#5499)
mittal-parth Jun 12, 2026
ccd052e
feat(cli): support high availability project creation (#5566)
jgoux Jun 12, 2026
6ad519a
fix(docker): bump the docker-minor group in /apps/cli-go/pkg/config/t…
dependabot[bot] Jun 13, 2026
b899193
fix(docker): bump supabase/postgres from 17.6.1.134 to 17.6.1.135 in …
dependabot[bot] Jun 13, 2026
20c4c86
fix(deps): bump the npm-major group with 7 updates (#5572)
dependabot[bot] Jun 13, 2026
ff83937
fix(deps): bump the npm-major group with 6 updates (#5573)
dependabot[bot] Jun 14, 2026
72675e2
fix(deps): bump @typescript/native-preview from 7.0.0-dev.20260606.1 …
dependabot[bot] Jun 15, 2026
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
16 changes: 15 additions & 1 deletion apps/cli-e2e/src/tests/database-core.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,5 +365,19 @@ describe("test db", () => {
expect(result.stderr).toContain("connect");
});

testParity(["test", "db", "--local"]);
// No testParity for `test db --local`: with no local Postgres listening in the
// harness, the only reachable path is the connection-failure path, and its
// stderr diverges by driver in ways that aren't cosmetic and can't be
// normalized away. Both now emit Go's leading diagnostic to stderr:
// Connecting to local database...
// but the connect-error body and trailing hint still differ by driver. Go (pgx):
// failed to connect to postgres: failed to connect to `host=… user=… database=…`: dial error (dial tcp …: connect: connection refused)
// Make sure your local IP is allowed in Network Restrictions and Network Bans.
// http://…/project/_/database/settings
// The TS port (@effect/sql-pg) prints the effect SqlError and the --debug hint:
// failed to connect to postgres: effect/sql/SqlError: PgClient: Failed to connect
// Try rerunning the command with --debug to troubleshoot the error.
// The meaningful contract (non-zero exit + a connect error on stderr) is
// covered by the behaviour test above. A real connect-path parity test would
// need a live local database in the harness.
});
8 changes: 7 additions & 1 deletion apps/cli-e2e/src/tests/domains.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { testBehaviour, testParity } from "./test-context";
import { isRecording, PROJECT_REF } from "./env";

const CONFIGURED_CNAME = "www.urgsimurksi.xyz";
const GO_CUSTOM_HOSTNAME_MACHINE_STATUS_PATTERNS = [
/^Custom hostname setup completed\. Project is now accessible at [^\n]+\.\n?/gm,
/^Custom hostname configuration complete, and ready for activation\.\n\nPlease ensure that your custom domain is set up as a CNAME record to your Supabase subdomain:\n[^\n]+ CNAME -> [^\n]+\n?/gm,
];

describe("domains", () => {
describe.todo("domains:create — requires mocking of 1.1.1.1 for DNS queries");
Expand Down Expand Up @@ -162,7 +166,9 @@ describe("domains", () => {
expect(result.stderr).toContain("502");
});

testParity(["domains", "get", "--project-ref", PROJECT_REF, "--output", "json"]);
testParity(["domains", "get", "--project-ref", PROJECT_REF, "--output", "json"], {
normalize: { stderr: { stripPatterns: GO_CUSTOM_HOSTNAME_MACHINE_STATUS_PATTERNS } },
});
});

describe("domains:reverify", () => {
Expand Down
29 changes: 28 additions & 1 deletion apps/cli-e2e/src/tests/test-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,31 @@ import {
} from "@supabase/cli-test-helpers";
import { ACCESS_TOKEN, isRecording, TARGET } from "./env.ts";

type ParityNormalizeOptions = NonNullable<Parameters<typeof runParity>[0]["normalize"]>;
type ParityChannelNormalizeOptions = Extract<
ParityNormalizeOptions,
{ stdout?: unknown; stderr?: unknown }
>;

function isChannelNormalizeOptions(
options: ParityNormalizeOptions | undefined,
): options is ParityChannelNormalizeOptions {
return options !== undefined && ("stdout" in options || "stderr" in options);
}

function withNormalizeVersions(
normalize: ParityNormalizeOptions | undefined,
versions: boolean | undefined,
): ParityNormalizeOptions | undefined {
if (versions === undefined) return normalize;
if (normalize === undefined) return { versions };
if (!isChannelNormalizeOptions(normalize)) return { ...normalize, versions };
return {
stdout: { ...normalize.stdout, versions },
stderr: { ...normalize.stderr, versions },
};
}

function slugify(name: string): string {
return name
.toLowerCase()
Expand Down Expand Up @@ -146,6 +171,7 @@ export function testParity(
workspaceSetup?: (dir: string) => void;
sortStdoutRows?: boolean;
normalizeVersions?: boolean;
normalize?: ParityNormalizeOptions;
},
): void {
const label = opts?.failureType
Expand All @@ -164,13 +190,14 @@ export function testParity(
}

try {
const normalize = withNormalizeVersions(opts?.normalize, opts?.normalizeVersions);
await runParity(
{
apiUrl: serverUrl,
accessToken: ACCESS_TOKEN,
workspaceSetup: opts?.workspaceSetup,
sortStdoutRows: opts?.sortStdoutRows,
normalize: { versions: opts?.normalizeVersions },
normalize,
},
cmd,
);
Expand Down
57 changes: 46 additions & 11 deletions apps/cli-go/cmd/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ var (
usePgAdmin bool
usePgSchema bool
usePgDelta bool
useDeclarative bool
pullDiffEngine = utils.EnumFlag{
Allowed: []string{"migra", "pg-delta"},
Value: "migra",
Expand All @@ -106,7 +107,7 @@ var (
return diff.RunExplicit(cmd.Context(), diffFrom, diffTo, schema, outputPath, afero.NewOsFs())
}
}
useDelta := shouldUsePgDelta()
useDelta := resolveDiffEngine(cmd.Flags().Changed("use-migra"), usePgAdmin, usePgSchema, shouldUsePgDelta())
if usePgAdmin {
return diff.RunPgAdmin(cmd.Context(), schema, file, flags.DbConfig, afero.NewOsFs())
}
Expand Down Expand Up @@ -176,12 +177,19 @@ var (
if len(args) > 0 {
name = args[0]
}
// Declarative export is opt-in via --declarative. Enabling pg-delta in config
// does not switch db pull to declarative output; it keeps the migration-file
// workflow and only defaults the shadow diff engine below.
useDeclarativePgDelta := useDeclarative
usePgDeltaDiff := resolvePullDiffEngine(
cmd.Flags().Changed("diff-engine"),
pullDiffEngine.Value,
shouldUsePgDelta(),
)
pullDiffer := diff.DiffSchemaMigra
usePgDeltaDiff := pullDiffEngine.Value == "pg-delta"
if usePgDeltaDiff {
pullDiffer = diff.DiffPgDelta
}
useDeclarativePgDelta := shouldUseDeclarativePgDeltaPull(usePgDeltaDiff)
return pull.Run(cmd.Context(), schema, flags.DbConfig, name, useDeclarativePgDelta, usePgDeltaDiff, pullDiffer, afero.NewOsFs())
},
PostRun: func(cmd *cobra.Command, args []string) {
Expand Down Expand Up @@ -210,8 +218,15 @@ var (
Use: "commit",
Short: "Commit remote changes as a new migration",
RunE: func(cmd *cobra.Command, args []string) error {
useDelta := shouldUsePgDelta()
return pull.Run(cmd.Context(), schema, flags.DbConfig, "remote_commit", useDelta, false, diff.DiffSchemaMigra, afero.NewOsFs())
// remote commit always writes a timestamped migration file. When pg-delta is
// enabled it only swaps the shadow diff engine; it never switches to the
// declarative export path.
usePgDeltaDiff := shouldUsePgDelta()
pullDiffer := diff.DiffSchemaMigra
if usePgDeltaDiff {
pullDiffer = diff.DiffPgDelta
}
return pull.Run(cmd.Context(), schema, flags.DbConfig, "remote_commit", false, usePgDeltaDiff, pullDiffer, afero.NewOsFs())
},
}

Expand Down Expand Up @@ -361,11 +376,28 @@ func shouldUsePgDelta() bool {
return utils.IsPgDeltaEnabled() || usePgDelta || viper.GetBool("EXPERIMENTAL_PG_DELTA")
}

func shouldUseDeclarativePgDeltaPull(usePgDeltaDiff bool) bool {
if usePgDeltaDiff {
// resolveDiffEngine reports whether `db diff` should run in pg-delta mode. The config /
// env default (pgDeltaDefault) applies unless an explicit non-pg-delta engine is selected:
// --use-migra, --use-pgadmin, or --use-pg-schema is an authoritative rollback that clears
// pg-delta mode so diff.Run skips pg-delta-specific declarative shadow setup and the
// PGDELTA_DEBUG capture path. --use-migra defaults to true, so only an explicit pass
// (useMigraChanged) counts as opting out.
func resolveDiffEngine(useMigraChanged, usePgAdmin, usePgSchema, pgDeltaDefault bool) bool {
if useMigraChanged || usePgAdmin || usePgSchema {
return false
}
return shouldUsePgDelta()
return pgDeltaDefault
}

// resolvePullDiffEngine selects whether migration-style db pull uses pg-delta for the
// shadow diff step. An explicit --diff-engine flag always wins, so --diff-engine migra is
// an authoritative rollback even when pg-delta is enabled in config; otherwise the default
// follows whether pg-delta is the active engine (config / env).
func resolvePullDiffEngine(engineFlagChanged bool, engine string, pgDeltaDefault bool) bool {
if engineFlagChanged {
return engine == "pg-delta"
}
return pgDeltaDefault
}

func init() {
Expand Down Expand Up @@ -427,15 +459,18 @@ func init() {
dbCmd.AddCommand(dbPushCmd)
// Build pull command
pullFlags := dbPullCmd.Flags()
// This flag activates declarative pull output through pg-delta instead of the
// legacy migration SQL pull path.
pullFlags.BoolVar(&usePgDelta, "use-pg-delta", false, "Use pg-delta to pull declarative schema.")
// --declarative switches pull output from a timestamped migration to declarative
// schema files exported through pg-delta. --use-pg-delta is the deprecated alias.
pullFlags.BoolVar(&useDeclarative, "declarative", false, "Pull schema as declarative files using pg-delta instead of creating a migration.")
pullFlags.BoolVar(&useDeclarative, "use-pg-delta", false, "Use pg-delta to pull declarative schema.")
cobra.CheckErr(pullFlags.MarkDeprecated("use-pg-delta", "use --declarative with [experimental.pgdelta] enabled = true in your config.toml instead."))
pullFlags.Var(&pullDiffEngine, "diff-engine", "Diff engine to use for migration-style db pull.")
pullFlags.StringSliceVarP(&schema, "schema", "s", []string{}, "Comma separated list of schema to include.")
pullFlags.String("db-url", "", "Pulls from the database specified by the connection string (must be percent-encoded).")
pullFlags.Bool("linked", true, "Pulls from the linked project.")
pullFlags.Bool("local", false, "Pulls from the local database.")
dbPullCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local")
dbPullCmd.MarkFlagsMutuallyExclusive("declarative", "diff-engine")
dbPullCmd.MarkFlagsMutuallyExclusive("use-pg-delta", "diff-engine")
pullFlags.StringVarP(&dbPassword, "password", "p", "", "Password to your remote Postgres database.")
cobra.CheckErr(viper.BindPFlag("DB_PASSWORD", pullFlags.Lookup("password")))
Expand Down
38 changes: 0 additions & 38 deletions apps/cli-go/cmd/db_pull_routing_test.go

This file was deleted.

47 changes: 47 additions & 0 deletions apps/cli-go/cmd/db_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package cmd

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestResolvePullDiffEngine(t *testing.T) {
t.Run("defaults to pg-delta when enabled in config", func(t *testing.T) {
assert.True(t, resolvePullDiffEngine(false, "migra", true))
})

t.Run("defaults to migra when pg-delta is not active", func(t *testing.T) {
assert.False(t, resolvePullDiffEngine(false, "migra", false))
})

t.Run("explicit --diff-engine migra overrides config default", func(t *testing.T) {
assert.False(t, resolvePullDiffEngine(true, "migra", true))
})

t.Run("explicit --diff-engine pg-delta wins when config disabled", func(t *testing.T) {
assert.True(t, resolvePullDiffEngine(true, "pg-delta", false))
})
}

func TestResolveDiffEngine(t *testing.T) {
t.Run("uses pg-delta when enabled in config and no engine flag set", func(t *testing.T) {
assert.True(t, resolveDiffEngine(false, false, false, true))
})

t.Run("uses migra when pg-delta is not active", func(t *testing.T) {
assert.False(t, resolveDiffEngine(false, false, false, false))
})

t.Run("explicit --use-migra clears config-driven pg-delta", func(t *testing.T) {
assert.False(t, resolveDiffEngine(true, false, false, true))
})

t.Run("explicit --use-pg-schema clears config-driven pg-delta", func(t *testing.T) {
assert.False(t, resolveDiffEngine(false, false, true, true))
})

t.Run("explicit --use-pgadmin clears config-driven pg-delta", func(t *testing.T) {
assert.False(t, resolveDiffEngine(false, true, false, true))
})
}
2 changes: 1 addition & 1 deletion apps/cli-go/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var (
initInteractive bool
createVscodeSettings bool
createIntellijSettings bool
initParams = utils.InitParams{}
initParams = utils.InitParams{UsePgDelta: true}

initCmd = &cobra.Command{
GroupID: groupLocalDev,
Expand Down
2 changes: 2 additions & 0 deletions apps/cli-go/docs/supabase/db/diff.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Runs [djrobstep/migra](https://github.com/djrobstep/migra) in a container to com

By default, all schemas in the target database are diffed. Use the `--schema public,extensions` flag to restrict diffing to a subset of schemas.

Projects created by a recent `supabase init` default to the pg-delta diff engine (`[experimental.pgdelta] enabled = true` in `config.toml`). Existing projects are unaffected and keep using migra unless they opt in. To fall back to the legacy migra engine, set `enabled = false` under `[experimental.pgdelta]`, or pass `--use-migra` for a single run.

While the diff command is able to capture most schema changes, there are cases where it is known to fail. Currently, this could happen if you schema contains:

- Changes to publication
Expand Down
4 changes: 2 additions & 2 deletions apps/cli-go/docs/supabase/db/pull.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ Optionally, a new row can be inserted into the migration history table to reflec

If no entries exist in the migration history table, the default diff engine uses `pg_dump` to capture all contents of the remote schemas you have created. Otherwise, this command will only diff schema changes against the remote database, similar to running `db diff --linked`.

Pass `--diff-engine pg-delta` to keep the migration-file `db pull` workflow while using pg-delta for the shadow diff step. On initial pull, pg-delta replaces `pg_dump` and produces the full migration from the shadow diff alone. Pass `--use-pg-delta` to switch to the declarative pg-delta export workflow instead.
Pass `--diff-engine pg-delta` to keep the migration-file `db pull` workflow while using pg-delta for the shadow diff step. On initial pull, pg-delta replaces `pg_dump` and produces the full migration from the shadow diff alone. Pass `--declarative` to switch to the declarative pg-delta export workflow instead.

When `[experimental.pgdelta] enabled = true` is set in `config.toml`, `db pull` defaults to the declarative export path. Explicit `--diff-engine pg-delta` still selects the migration-file workflow.
When `[experimental.pgdelta] enabled = true` (the default for projects created by a recent `supabase init`), the migration-file `db pull` workflow uses pg-delta for the shadow diff step by default; it does not switch to declarative output. Existing projects without the section are unaffected and keep using migra. To fall back to the legacy migra engine, set `enabled = false` under `[experimental.pgdelta]`, or pass `--diff-engine migra` for a single run.

When pulling from a remote database with `--db-url`, prefer a direct connection (`db.<project-ref>.supabase.co:5432`) over the connection pooler so pg-delta can introspect the full catalog reliably.

Expand Down
2 changes: 1 addition & 1 deletion apps/cli-go/internal/bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func Run(ctx context.Context, starter StarterTemplate, fsys afero.Fs, options ..
if err := downloadSample(ctx, client, starter.Url, fsys); err != nil {
return err
}
} else if err := initBlank.Run(ctx, fsys, false, utils.InitParams{Overwrite: true}); err != nil {
} else if err := initBlank.Run(ctx, fsys, false, utils.InitParams{Overwrite: true, UsePgDelta: true}); err != nil {
return err
}
// 1. Login
Expand Down
34 changes: 23 additions & 11 deletions apps/cli-go/internal/db/dump/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@ import (
func Run(ctx context.Context, path string, config pgconn.Config, dataOnly, roleOnly, dryRun bool, fsys afero.Fs, opts ...migration.DumpOptionFunc) error {
// Initialize output stream
outStream := (io.Writer)(os.Stdout)
exec := DockerExec
if dryRun {
fmt.Fprintln(os.Stderr, "DRY RUN: *only* printing the pg_dump script to console.")
exec = noExec
} else if len(path) > 0 {
f, err := fsys.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
Expand All @@ -36,15 +34,25 @@ func Run(ctx context.Context, path string, config pgconn.Config, dataOnly, roleO
if utils.IsLocalDatabase(config) {
db = "local"
}
if dataOnly {
fmt.Fprintf(os.Stderr, "Dumping data from %s database...\n", db)
return migration.DumpData(ctx, config, outStream, exec, opts...)
} else if roleOnly {
fmt.Fprintf(os.Stderr, "Dumping roles from %s database...\n", db)
return migration.DumpRole(ctx, config, outStream, exec, opts...)
return RunWithPoolerFallback(ctx, config, outStream, dryRun, func(ctx context.Context, config pgconn.Config, out io.Writer, exec migration.ExecFunc) error {
if dataOnly {
fmt.Fprintf(os.Stderr, "Dumping data from %s database...\n", db)
return migration.DumpData(ctx, config, out, exec, opts...)
} else if roleOnly {
fmt.Fprintf(os.Stderr, "Dumping roles from %s database...\n", db)
return migration.DumpRole(ctx, config, out, exec, opts...)
}
fmt.Fprintf(os.Stderr, "Dumping schemas from %s database...\n", db)
return migration.DumpSchema(ctx, config, out, exec, opts...)
})
}

// captureExec wraps DockerExec so the container's stderr is teed into errBuf
// (in addition to the user's terminal) for post-failure classification.
func captureExec(errBuf *strings.Builder) migration.ExecFunc {
return func(ctx context.Context, script string, env []string, w io.Writer) error {
return dockerExec(ctx, script, env, w, io.MultiWriter(os.Stderr, errBuf))
}
fmt.Fprintf(os.Stderr, "Dumping schemas from %s database...\n", db)
return migration.DumpSchema(ctx, config, outStream, exec, opts...)
}

func noExec(ctx context.Context, script string, env []string, w io.Writer) error {
Expand All @@ -69,6 +77,10 @@ func noExec(ctx context.Context, script string, env []string, w io.Writer) error
}

func DockerExec(ctx context.Context, script string, env []string, w io.Writer) error {
return dockerExec(ctx, script, env, w, os.Stderr)
}

func dockerExec(ctx context.Context, script string, env []string, w, errW io.Writer) error {
return utils.DockerRunOnceWithConfig(
ctx,
container.Config{
Expand All @@ -82,6 +94,6 @@ func DockerExec(ctx context.Context, script string, env []string, w io.Writer) e
network.NetworkingConfig{},
"",
w,
os.Stderr,
errW,
)
}
Loading
Loading