From 6fa4c974cf1bb4c085aed052e700ea355aa2a3c1 Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Thu, 25 Jun 2026 13:03:07 +0200 Subject: [PATCH 01/29] feat(db): add reset sql path override (#5691) Adds a repeatable `--sql-paths` flag to `supabase db reset` so users can override `[db.seed].sql_paths` for a single reset without editing `config.toml`. The flag accepts the same file path or glob pattern syntax as `sql_paths`, resolves relative values from the `supabase` directory, and remains mutually exclusive with `--no-seed`. Passing `--sql-paths` force-enables seeding for that reset even when config disables seeding, and remote resets warn before applying override seeds to a `--linked` or `--db-url` target. The TypeScript legacy command wrapper forwards the flag to the current Go implementation while `db reset` remains on the legacy path. Addresses #2191 Supersedes #4680 --- apps/cli-go/cmd/db.go | 76 ++++++++- apps/cli-go/cmd/db_test.go | 100 ++++++++++++ apps/cli-go/docs/supabase/db/reset.md | 4 + apps/cli/docs/go-cli-porting-status.md | 2 +- .../legacy/commands/db/reset/SIDE_EFFECTS.md | 13 +- .../legacy/commands/db/reset/reset.command.ts | 9 ++ .../legacy/commands/db/reset/reset.handler.ts | 1 + .../db/reset/reset.integration.test.ts | 147 ++++++++++++++++++ 8 files changed, 342 insertions(+), 10 deletions(-) create mode 100644 apps/cli/src/legacy/commands/db/reset/reset.integration.test.ts diff --git a/apps/cli-go/cmd/db.go b/apps/cli-go/cmd/db.go index 3f8d3d82a8..df04364e44 100644 --- a/apps/cli-go/cmd/db.go +++ b/apps/cli-go/cmd/db.go @@ -1,8 +1,10 @@ package cmd import ( + "errors" "fmt" "os" + "path" "path/filepath" "github.com/spf13/afero" @@ -24,6 +26,7 @@ import ( "github.com/supabase/cli/legacy/branch/delete" "github.com/supabase/cli/legacy/branch/list" "github.com/supabase/cli/legacy/branch/switch_" + "github.com/supabase/cli/pkg/config" "github.com/supabase/cli/pkg/migration" ) @@ -300,15 +303,23 @@ var ( }, } - noSeed bool - lastVersion uint + noSeed bool + lastVersion uint + seedSqlPaths []string dbResetCmd = &cobra.Command{ Use: "reset", Short: "Resets the local database to current migrations", + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := validateDbResetSeedFlags(noSeed, seedSqlPaths); err != nil { + return err + } + warnRemoteResetSeedOverride(cmd, seedSqlPaths) + return nil + }, RunE: func(cmd *cobra.Command, args []string) error { - if noSeed { - utils.Config.Db.Seed.Enabled = false + if err := applyDbResetSeedFlags(noSeed, seedSqlPaths); err != nil { + return err } return reset.Run(cmd.Context(), migrationVersion, lastVersion, flags.DbConfig, afero.NewOsFs()) }, @@ -470,6 +481,62 @@ func resolvePullDiffEngine(engineFlagChanged bool, engine string, pgDeltaDefault return pgDeltaDefault } +func validateDbResetSeedFlags(noSeed bool, patterns []string) error { + if noSeed && len(patterns) > 0 { + utils.CmdSuggestion = fmt.Sprintf("Use either %s to skip seeding or %s to override seed files, not both.", utils.Aqua("--no-seed"), utils.Aqua("--sql-paths")) + return errors.New("--no-seed cannot be used with --sql-paths") + } + for _, pattern := range patterns { + if len(pattern) == 0 { + utils.CmdSuggestion = fmt.Sprintf("Pass a non-empty file path or glob pattern to %s.", utils.Aqua("--sql-paths")) + return errors.New("--sql-paths requires a non-empty path or glob pattern") + } + } + return nil +} + +func warnRemoteResetSeedOverride(cmd *cobra.Command, patterns []string) { + if len(patterns) == 0 { + return + } + if cmd.Flags().Changed("linked") || cmd.Flags().Changed("db-url") { + fmt.Fprintln(os.Stderr, utils.Yellow("WARNING:"), "--sql-paths overrides [db.seed].sql_paths and seeds the remote database selected by --linked or --db-url.") + } +} + +func applyDbResetSeedFlags(noSeed bool, patterns []string) error { + if noSeed { + utils.Config.Db.Seed.Enabled = false + return nil + } + if len(patterns) == 0 { + return nil + } + resolved, err := resolveSeedSqlPaths(patterns) + if err != nil { + return err + } + utils.Config.Db.Seed.Enabled = true + utils.Config.Db.Seed.SqlPaths = resolved + return nil +} + +func resolveSeedSqlPaths(patterns []string) ([]string, error) { + resolved := make([]string, len(patterns)) + base := config.NewPathBuilder("").SupabaseDirPath + for i, pattern := range patterns { + if len(pattern) == 0 { + return nil, errors.New("--sql-paths requires a non-empty path or glob pattern") + } + if !filepath.IsAbs(pattern) { + resolved[i] = path.Join(base, pattern) + } else { + resolved[i] = pattern + } + } + return resolved, nil +} + func init() { // Build branch command dbBranchCmd.AddCommand(dbBranchCreateCmd) @@ -570,6 +637,7 @@ func init() { resetFlags.Bool("linked", false, "Resets the linked project with local migrations.") resetFlags.Bool("local", true, "Resets the local database with local migrations.") resetFlags.BoolVar(&noSeed, "no-seed", false, "Skip running the seed script after reset.") + resetFlags.StringArrayVar(&seedSqlPaths, "sql-paths", nil, "Override [db.seed].sql_paths for this reset. May be repeated; each value accepts a SQL file path or glob pattern relative to the supabase directory and force-enables seeding.") dbResetCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local") resetFlags.StringVar(&migrationVersion, "version", "", "Reset up to the specified version.") resetFlags.UintVar(&lastVersion, "last", 0, "Reset up to the last n migration versions.") diff --git a/apps/cli-go/cmd/db_test.go b/apps/cli-go/cmd/db_test.go index 654278d059..d12bb1cf8f 100644 --- a/apps/cli-go/cmd/db_test.go +++ b/apps/cli-go/cmd/db_test.go @@ -1,9 +1,12 @@ package cmd import ( + "path/filepath" "testing" + "github.com/spf13/pflag" "github.com/stretchr/testify/assert" + "github.com/supabase/cli/internal/utils" ) func TestResolvePullDiffEngine(t *testing.T) { @@ -45,3 +48,100 @@ func TestResolveDiffEngine(t *testing.T) { assert.False(t, resolveDiffEngine(false, true, false, true)) }) } + +func TestResolveSeedSqlPaths(t *testing.T) { + t.Run("resolves relative paths against the supabase directory", func(t *testing.T) { + absoluteSeedPath := filepath.Join(t.TempDir(), "seed.sql") + got, err := resolveSeedSqlPaths([]string{ + "./seeds/minimal.sql", + "./seeds/demo/*.sql", + "./seeds/tenant,one.sql", + absoluteSeedPath, + }) + + assert.NoError(t, err) + assert.Equal(t, []string{ + filepath.Join(utils.SupabaseDirPath, "seeds", "minimal.sql"), + filepath.Join(utils.SupabaseDirPath, "seeds", "demo", "*.sql"), + filepath.Join(utils.SupabaseDirPath, "seeds", "tenant,one.sql"), + absoluteSeedPath, + }, got) + }) + + t.Run("rejects empty paths", func(t *testing.T) { + got, err := resolveSeedSqlPaths([]string{""}) + assert.Nil(t, got) + assert.EqualError(t, err, "--sql-paths requires a non-empty path or glob pattern") + }) +} + +func TestValidateDbResetSeedFlags(t *testing.T) { + t.Run("rejects no seed with sql paths", func(t *testing.T) { + utils.CmdSuggestion = "" + t.Cleanup(func() { utils.CmdSuggestion = "" }) + + err := validateDbResetSeedFlags(true, []string{"./seed.sql"}) + + assert.EqualError(t, err, "--no-seed cannot be used with --sql-paths") + assert.Contains(t, utils.CmdSuggestion, "Use either") + assert.Contains(t, utils.CmdSuggestion, "--no-seed") + assert.Contains(t, utils.CmdSuggestion, "--sql-paths") + }) + + t.Run("rejects empty sql paths", func(t *testing.T) { + utils.CmdSuggestion = "" + t.Cleanup(func() { utils.CmdSuggestion = "" }) + + err := validateDbResetSeedFlags(false, []string{""}) + + assert.EqualError(t, err, "--sql-paths requires a non-empty path or glob pattern") + assert.Contains(t, utils.CmdSuggestion, "non-empty") + assert.Contains(t, utils.CmdSuggestion, "--sql-paths") + }) +} + +func TestApplyDbResetSeedFlags(t *testing.T) { + oldSeed := utils.Config.Db.Seed + t.Cleanup(func() { utils.Config.Db.Seed = oldSeed }) + + t.Run("leaves config unchanged without seed flags", func(t *testing.T) { + utils.Config.Db.Seed.Enabled = false + utils.Config.Db.Seed.SqlPaths = []string{"supabase/original.sql"} + + assert.NoError(t, applyDbResetSeedFlags(false, nil)) + assert.False(t, utils.Config.Db.Seed.Enabled) + assert.Equal(t, []string{"supabase/original.sql"}, []string(utils.Config.Db.Seed.SqlPaths)) + }) + + t.Run("disables seed when no seed is set", func(t *testing.T) { + utils.Config.Db.Seed.Enabled = true + utils.Config.Db.Seed.SqlPaths = []string{"supabase/original.sql"} + + assert.NoError(t, applyDbResetSeedFlags(true, nil)) + assert.False(t, utils.Config.Db.Seed.Enabled) + assert.Equal(t, []string{"supabase/original.sql"}, []string(utils.Config.Db.Seed.SqlPaths)) + }) + + t.Run("force enables seed and overrides sql paths", func(t *testing.T) { + utils.Config.Db.Seed.Enabled = false + utils.Config.Db.Seed.SqlPaths = []string{"supabase/original.sql"} + + assert.NoError(t, applyDbResetSeedFlags(false, []string{"./seeds/base.sql"})) + assert.True(t, utils.Config.Db.Seed.Enabled) + assert.Equal(t, []string{filepath.Join(utils.SupabaseDirPath, "seeds", "base.sql")}, []string(utils.Config.Db.Seed.SqlPaths)) + }) +} + +func TestSeedSqlPathsFlagPreservesCommas(t *testing.T) { + var values []string + flags := pflag.NewFlagSet("test", pflag.ContinueOnError) + flags.StringArrayVar(&values, "sql-paths", nil, "") + + assert.NoError(t, flags.Parse([]string{ + "--sql-paths", + "./seeds/tenant,one.sql", + "--sql-paths", + "./seeds/two.sql", + })) + assert.Equal(t, []string{"./seeds/tenant,one.sql", "./seeds/two.sql"}, values) +} diff --git a/apps/cli-go/docs/supabase/db/reset.md b/apps/cli-go/docs/supabase/db/reset.md index acb9b9832b..9a60a67711 100644 --- a/apps/cli-go/docs/supabase/db/reset.md +++ b/apps/cli-go/docs/supabase/db/reset.md @@ -6,4 +6,8 @@ Requires the local development stack to be started by running `supabase start`. Recreates the local Postgres container and applies all local migrations found in `supabase/migrations` directory. If test data is defined in `supabase/seed.sql`, it will be seeded after the migrations are run. Any other data or schema changes made during local development will be discarded. +Use the `--no-seed` flag to skip seeding entirely. To override `[db.seed].sql_paths` for a single reset, pass one or more `--sql-paths` flags. Each value accepts the same file path or glob pattern syntax as `sql_paths`, relative to the `supabase` directory. Passing `--sql-paths` force-enables seeding for that reset even when `[db.seed].enabled = false`. + When running db reset with `--linked` or `--db-url` flag, a SQL script is executed to identify and drop all user created entities in the remote database. Since Postgres roles are cluster level entities, any custom roles created through the dashboard or `supabase/roles.sql` will not be deleted by remote reset. + +If you combine `--sql-paths` with `--linked` or `--db-url`, the override seed files are applied to the selected remote database after migrations. Use this only when you intend to seed that remote target. diff --git a/apps/cli/docs/go-cli-porting-status.md b/apps/cli/docs/go-cli-porting-status.md index 9cebb1a5f5..9b22759d95 100644 --- a/apps/cli/docs/go-cli-porting-status.md +++ b/apps/cli/docs/go-cli-porting-status.md @@ -302,7 +302,7 @@ Legend: | `db dump` | `ported` | [`../src/legacy/commands/db/dump/dump.command.ts`](../src/legacy/commands/db/dump/dump.command.ts) | | `db push` | `wrapped` | [`../src/legacy/commands/db/push/push.command.ts`](../src/legacy/commands/db/push/push.command.ts) | | `db pull` | `ported` | [`../src/legacy/commands/db/pull/pull.command.ts`](../src/legacy/commands/db/pull/pull.command.ts) — native pg-delta / migra; `--declarative` (deprecated alias `--use-pg-delta`) + `--diff-engine` (migra\|pg-delta); `--experimental` / initial `pg_dump` delegate to Go | -| `db reset` | `wrapped` | [`../src/legacy/commands/db/reset/reset.command.ts`](../src/legacy/commands/db/reset/reset.command.ts) | +| `db reset` | `wrapped` | [`../src/legacy/commands/db/reset/reset.command.ts`](../src/legacy/commands/db/reset/reset.command.ts) — includes Go-parity `--sql-paths` override for `[db.seed].sql_paths` | | `db lint` | `ported` | [`../src/legacy/commands/db/lint/lint.command.ts`](../src/legacy/commands/db/lint/lint.command.ts) | | `db start` | `wrapped` | [`../src/legacy/commands/db/start/start.command.ts`](../src/legacy/commands/db/start/start.command.ts) | | `db query` | `ported` | [`../src/legacy/commands/db/query/query.command.ts`](../src/legacy/commands/db/query/query.command.ts) | diff --git a/apps/cli/src/legacy/commands/db/reset/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/db/reset/SIDE_EFFECTS.md index a2c24f24d2..d1291e27a4 100644 --- a/apps/cli/src/legacy/commands/db/reset/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/db/reset/SIDE_EFFECTS.md @@ -2,11 +2,11 @@ ## Files Read -| Path | Format | When | -| -------------------------------- | ---------- | ------------------------------------------------- | -| `~/.supabase/access-token` | plain text | when `SUPABASE_ACCESS_TOKEN` unset and `--linked` | -| `/supabase/migrations/` | directory | always, to load migration files | -| seed files from config | SQL | unless `--no-seed` is set | +| Path | Format | When | +| --------------------------------------- | ---------- | ------------------------------------------------- | +| `~/.supabase/access-token` | plain text | when `SUPABASE_ACCESS_TOKEN` unset and `--linked` | +| `/supabase/migrations/` | directory | always, to load migration files | +| seed files from config or `--sql-paths` | SQL | unless `--no-seed` is set | ## Files Written @@ -52,6 +52,9 @@ Not applicable. ## Notes - `--no-seed` skips running the seed script after reset. +- `--sql-paths` overrides `[db.seed].sql_paths` for one reset; repeat it to seed multiple files or glob patterns. +- `--sql-paths` force-enables seeding for that reset even when `[db.seed].enabled = false`. +- With `--linked` or `--db-url`, `--sql-paths` seeds the selected remote database after migrations. - `--version` resets up to the specified migration version. - `--last` resets up to the last n migration versions; mutually exclusive with `--version`. - `--db-url`, `--linked`, and `--local` (default true) are mutually exclusive. diff --git a/apps/cli/src/legacy/commands/db/reset/reset.command.ts b/apps/cli/src/legacy/commands/db/reset/reset.command.ts index 11764e9db7..15a61c8d88 100644 --- a/apps/cli/src/legacy/commands/db/reset/reset.command.ts +++ b/apps/cli/src/legacy/commands/db/reset/reset.command.ts @@ -2,6 +2,8 @@ import { Command, Flag } from "effect/unstable/cli"; import type * as CliCommand from "effect/unstable/cli/Command"; import { legacyDbReset } from "./reset.handler.ts"; +const noSqlPaths: ReadonlyArray = []; + const config = { dbUrl: Flag.string("db-url").pipe( Flag.withDescription( @@ -18,6 +20,13 @@ const config = { noSeed: Flag.boolean("no-seed").pipe( Flag.withDescription("Skip running the seed script after reset."), ), + sqlPaths: Flag.string("sql-paths").pipe( + Flag.atLeast(0), + Flag.withDescription( + "Override [db.seed].sql_paths for this reset. May be repeated; each value accepts a SQL file path or glob pattern relative to the supabase directory and force-enables seeding.", + ), + Flag.withDefault(noSqlPaths), + ), version: Flag.string("version").pipe( Flag.withDescription("Reset up to the specified version."), Flag.optional, diff --git a/apps/cli/src/legacy/commands/db/reset/reset.handler.ts b/apps/cli/src/legacy/commands/db/reset/reset.handler.ts index 406485fe53..de35923ac2 100644 --- a/apps/cli/src/legacy/commands/db/reset/reset.handler.ts +++ b/apps/cli/src/legacy/commands/db/reset/reset.handler.ts @@ -9,6 +9,7 @@ export const legacyDbReset = Effect.fn("legacy.db.reset")(function* (flags: Lega if (flags.linked) args.push("--linked"); if (flags.local) args.push("--local"); if (flags.noSeed) args.push("--no-seed"); + for (const path of flags.sqlPaths) args.push("--sql-paths", path); if (Option.isSome(flags.version)) args.push("--version", flags.version.value); if (Option.isSome(flags.last)) args.push("--last", String(flags.last.value)); yield* proxy.exec(args); diff --git a/apps/cli/src/legacy/commands/db/reset/reset.integration.test.ts b/apps/cli/src/legacy/commands/db/reset/reset.integration.test.ts new file mode 100644 index 0000000000..bd533d2adc --- /dev/null +++ b/apps/cli/src/legacy/commands/db/reset/reset.integration.test.ts @@ -0,0 +1,147 @@ +import { describe, expect, it } from "@effect/vitest"; +import { Effect, Layer, Option } from "effect"; +import { CliOutput, Command } from "effect/unstable/cli"; +import { mockOutput } from "../../../../../tests/helpers/mocks.ts"; +import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts"; +import { textCliOutputFormatter } from "../../../../shared/output/text-formatter.ts"; +import { legacyDbResetCommand } from "./reset.command.ts"; +import { legacyDbReset } from "./reset.handler.ts"; +import type { LegacyDbResetFlags } from "./reset.command.ts"; + +function setupLegacyDbReset() { + const calls: Array> = []; + const layer = Layer.succeed(LegacyGoProxy, { + exec: (args) => + Effect.sync(() => { + calls.push(args); + }), + execCapture: () => Effect.succeed(""), + }); + return { layer, calls }; +} + +const baseFlags: LegacyDbResetFlags = { + dbUrl: Option.none(), + linked: false, + local: false, + noSeed: false, + sqlPaths: [], + version: Option.none(), + last: Option.none(), +}; + +describe("legacy db reset", () => { + it.live("forwards the empty-array baseline without seed override flags", () => { + const { layer, calls } = setupLegacyDbReset(); + return Effect.gen(function* () { + yield* legacyDbReset(baseFlags); + expect(calls).toEqual([["db", "reset"]]); + }).pipe(Effect.provide(layer)); + }); + + it.live("forwards --no-seed alone", () => { + const { layer, calls } = setupLegacyDbReset(); + return Effect.gen(function* () { + yield* legacyDbReset({ ...baseFlags, noSeed: true }); + expect(calls).toEqual([["db", "reset", "--no-seed"]]); + }).pipe(Effect.provide(layer)); + }); + + it.live("forwards a single --sql-paths flag", () => { + const { layer, calls } = setupLegacyDbReset(); + return Effect.gen(function* () { + yield* legacyDbReset({ + ...baseFlags, + sqlPaths: ["./seeds/base.sql"], + }); + expect(calls).toEqual([["db", "reset", "--sql-paths", "./seeds/base.sql"]]); + }).pipe(Effect.provide(layer)); + }); + + it.live("forwards repeated --sql-paths flags in order", () => { + const { layer, calls } = setupLegacyDbReset(); + return Effect.gen(function* () { + yield* legacyDbReset({ + ...baseFlags, + sqlPaths: ["./seeds/base.sql", "./seeds/demo/*.sql"], + }); + expect(calls).toEqual([ + ["db", "reset", "--sql-paths", "./seeds/base.sql", "--sql-paths", "./seeds/demo/*.sql"], + ]); + }).pipe(Effect.provide(layer)); + }); + + it.live("forwards --no-seed with --sql-paths so Go owns the diagnostic", () => { + const { layer, calls } = setupLegacyDbReset(); + return Effect.gen(function* () { + yield* legacyDbReset({ + ...baseFlags, + noSeed: true, + sqlPaths: ["./seeds/base.sql"], + }); + expect(calls).toEqual([["db", "reset", "--no-seed", "--sql-paths", "./seeds/base.sql"]]); + }).pipe(Effect.provide(layer)); + }); + + it.live("forwards an empty --sql-paths value so Go owns the diagnostic", () => { + const { layer, calls } = setupLegacyDbReset(); + return Effect.gen(function* () { + yield* legacyDbReset({ + ...baseFlags, + sqlPaths: [""], + }); + expect(calls).toEqual([["db", "reset", "--sql-paths", ""]]); + }).pipe(Effect.provide(layer)); + }); + + it("parses repeated --sql-paths flags from the command surface", async () => { + const { layer, calls } = setupLegacyDbReset(); + await Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + yield* Command.runWith(legacyDbResetCommand, { version: "0.0.0-test" })([ + "--sql-paths", + "./seeds/base.sql", + "--sql-paths", + "./seeds/demo/*.sql", + ]); + expect(calls).toEqual([ + ["db", "reset", "--sql-paths", "./seeds/base.sql", "--sql-paths", "./seeds/demo/*.sql"], + ]); + }), + ).pipe( + Effect.provide( + Layer.mergeAll( + layer, + mockOutput({ format: "text" }).layer, + CliOutput.layer(textCliOutputFormatter()), + ), + ), + ) as Effect.Effect, + ); + }); + + it("forwards mutually exclusive seed flags from the command surface", async () => { + const { layer, calls } = setupLegacyDbReset(); + await Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + yield* Command.runWith(legacyDbResetCommand, { version: "0.0.0-test" })([ + "--no-seed", + "--sql-paths", + "./seeds/base.sql", + ]); + expect(calls).toEqual([["db", "reset", "--no-seed", "--sql-paths", "./seeds/base.sql"]]); + }), + ).pipe( + Effect.provide( + Layer.mergeAll( + layer, + mockOutput({ format: "text" }).layer, + CliOutput.layer(textCliOutputFormatter()), + ), + ), + ) as Effect.Effect, + ); + }); +}); From 229493c288043cf0b9d69dbbcc1e16db848a5c44 Mon Sep 17 00:00:00 2001 From: Luc Peng Date: Thu, 25 Jun 2026 22:11:36 +0800 Subject: [PATCH 02/29] fix(cli): detect stale local postgres before declarative sync (#5646) ## What - Add a declarative schema sync guard that inspects the local Postgres container image tag before generating a migration. - Fail with an actionable stop/start suggestion when the running local Postgres container tag is stale. - Cover missing, matching, registry-different, and stale local container cases with regression tests. ## Why A stale local Postgres container after a CLI upgrade can produce declarative sync output against an inconsistent local stack, such as incorrect extension drops. ## Validation - `cd apps/cli-go && go test ./cmd ./internal/db/declarative` - `cd apps/cli-go && GOMAXPROCS=4 go test ./...` - `git diff --check` Fixes #5555 --------- Co-authored-by: Julien Goux --- apps/cli-go/cmd/db_schema_declarative.go | 52 ++++- apps/cli-go/cmd/db_schema_declarative_test.go | 70 +++++++ .../internal/db/declarative/declarative.go | 18 +- .../db/declarative/declarative_test.go | 9 +- .../commands/db/diff/diff.integration.test.ts | 1 + .../commands/db/pull/pull.integration.test.ts | 1 + ...eclarative.orchestrate.integration.test.ts | 1 + .../declarative/declarative.smart-target.ts | 4 + .../declarative/generate/generate.handler.ts | 5 +- .../generate/generate.integration.test.ts | 56 ++++++ .../schema/declarative/sync/sync.handler.ts | 7 +- .../declarative/sync/sync.integration.test.ts | 181 +++++++++++++++++- .../db/shared/legacy-pgdelta.seam.layer.ts | 148 ++++++++++++++ .../legacy-pgdelta.seam.layer.unit.test.ts | 56 ++++++ .../db/shared/legacy-pgdelta.seam.service.ts | 9 + 15 files changed, 606 insertions(+), 12 deletions(-) create mode 100644 apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.seam.layer.unit.test.ts diff --git a/apps/cli-go/cmd/db_schema_declarative.go b/apps/cli-go/cmd/db_schema_declarative.go index 45f3f6aaaa..bcc689ca0b 100644 --- a/apps/cli-go/cmd/db_schema_declarative.go +++ b/apps/cli-go/cmd/db_schema_declarative.go @@ -8,6 +8,8 @@ import ( "strings" "time" + "github.com/containerd/errdefs" + "github.com/docker/docker/api/types/container" "github.com/jackc/pgconn" "github.com/jackc/pgx/v4" "github.com/spf13/afero" @@ -157,6 +159,39 @@ func ensureLocalDatabaseStarted(ctx context.Context, local bool, isRunning func( return nil } +type inspectContainerFunc func(context.Context, string) (container.InspectResponse, error) + +func dockerImageTag(image string) string { + image = strings.TrimSpace(image) + index := strings.LastIndexByte(image, ':') + if index < 0 || index == len(image)-1 { + return "" + } + return image[index+1:] +} + +func ensureLocalPostgresImageCurrent(ctx context.Context, inspect inspectContainerFunc) error { + resp, err := inspect(ctx, utils.DbId) + if err != nil { + if errdefs.IsNotFound(err) { + return nil + } + return fmt.Errorf("failed to inspect local Postgres container: %w", err) + } + if resp.Config == nil || len(strings.TrimSpace(resp.Config.Image)) == 0 { + return nil + } + actual := strings.TrimSpace(resp.Config.Image) + expected := strings.TrimSpace(utils.GetRegistryImageUrl(utils.Config.Db.Image)) + actualTag := dockerImageTag(actual) + expectedTag := dockerImageTag(expected) + if len(actualTag) == 0 || len(expectedTag) == 0 || actualTag == expectedTag { + return nil + } + utils.CmdSuggestion = fmt.Sprintf("Run %s, then %s before syncing declarative schemas.", utils.Aqua("supabase stop --all --no-backup"), utils.Aqua("supabase start")) + return fmt.Errorf("local Postgres container image is stale: running %s but expected %s", actual, expected) +} + // hasExplicitTargetFlag returns true if the user explicitly set --local, --linked, or --db-url. func hasExplicitTargetFlag(cmd *cobra.Command) bool { return cmd.Flags().Changed("local") || cmd.Flags().Changed("linked") || cmd.Flags().Changed("db-url") @@ -208,6 +243,11 @@ func runDeclarativeGenerate(cmd *cobra.Command, args []string) error { // When an explicit target flag is provided, use the direct path. if hasExplicitTargetFlag(cmd) { + if cmd.Flags().Changed("local") { + if err := ensureLocalPostgresImageCurrent(ctx, utils.Docker.ContainerInspect); err != nil { + return err + } + } if err := ensureLocalDatabaseStarted(ctx, declarativeLocal, utils.AssertSupabaseDbIsRunning, func(ctx context.Context) error { return start.Run(ctx, "", fsys) }); err != nil { @@ -267,6 +307,9 @@ func runDeclarativeGenerate(cmd *cobra.Command, args []string) error { switch choice.Index { case 0: // Local database + if err := ensureLocalPostgresImageCurrent(ctx, utils.Docker.ContainerInspect); err != nil { + return err + } if err := ensureLocalDatabaseStarted(ctx, true, utils.AssertSupabaseDbIsRunning, func(ctx context.Context) error { return start.Run(ctx, "", fsys) }); err != nil { @@ -309,6 +352,9 @@ func runDeclarativeGenerate(cmd *cobra.Command, args []string) error { } } else { // No migrations — generate from local DB + if err := ensureLocalPostgresImageCurrent(ctx, utils.Docker.ContainerInspect); err != nil { + return err + } if err := ensureLocalDatabaseStarted(ctx, true, utils.AssertSupabaseDbIsRunning, func(ctx context.Context) error { return start.Run(ctx, "", fsys) }); err != nil { @@ -325,9 +371,10 @@ func runDeclarativeSync(cmd *cobra.Command, args []string) error { ctx := cmd.Context() fsys := afero.NewOsFs() console := utils.NewConsole() + declarativeFilesExist := hasDeclarativeFiles(fsys) // Step 1: Check if declarative dir has files - if !hasDeclarativeFiles(fsys) { + if !declarativeFilesExist { if !isTTY() && !viper.GetBool("YES") { return fmt.Errorf("no declarative schema found. Run %s first", utils.Aqua("supabase db schema declarative generate")) } @@ -416,6 +463,9 @@ func runDeclarativeSync(cmd *cobra.Command, args []string) error { } if shouldApply { + if err := ensureLocalPostgresImageCurrent(ctx, utils.Docker.ContainerInspect); err != nil { + return err + } if applyErr := applyMigrationToLocal(ctx, path, fsys); applyErr != nil { fmt.Fprintln(os.Stderr, utils.Red("Migration failed to apply: "+applyErr.Error())) diff --git a/apps/cli-go/cmd/db_schema_declarative_test.go b/apps/cli-go/cmd/db_schema_declarative_test.go index a799ad0fb8..738e204111 100644 --- a/apps/cli-go/cmd/db_schema_declarative_test.go +++ b/apps/cli-go/cmd/db_schema_declarative_test.go @@ -6,6 +6,8 @@ import ( "path/filepath" "testing" + "github.com/containerd/errdefs" + "github.com/docker/docker/api/types/container" "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -194,6 +196,74 @@ func TestEnsureLocalDatabaseStarted(t *testing.T) { }) } +func TestDockerImageTag(t *testing.T) { + testCases := map[string]string{ + "public.ecr.aws/supabase/postgres:17.6.1.138": "17.6.1.138", + "localhost:5000/supabase/postgres:17.6.1.138": "17.6.1.138", + "supabase/postgres": "", + } + for image, expected := range testCases { + t.Run(image, func(t *testing.T) { + assert.Equal(t, expected, dockerImageTag(image)) + }) + } +} + +func TestEnsureLocalPostgresImageCurrent(t *testing.T) { + originalImage := utils.Config.Db.Image + originalSuggestion := utils.CmdSuggestion + t.Cleanup(func() { + utils.Config.Db.Image = originalImage + utils.CmdSuggestion = originalSuggestion + }) + utils.Config.Db.Image = "supabase/postgres:17.6.1.138" + expectedImage := utils.GetRegistryImageUrl(utils.Config.Db.Image) + + t.Run("passes when no local container exists", func(t *testing.T) { + utils.CmdSuggestion = "" + err := ensureLocalPostgresImageCurrent(context.Background(), func(context.Context, string) (container.InspectResponse, error) { + return container.InspectResponse{}, errdefs.ErrNotFound + }) + + assert.NoError(t, err) + assert.Empty(t, utils.CmdSuggestion) + }) + + t.Run("passes when local container image matches expected postgres image", func(t *testing.T) { + utils.CmdSuggestion = "" + err := ensureLocalPostgresImageCurrent(context.Background(), func(_ context.Context, containerID string) (container.InspectResponse, error) { + assert.Equal(t, utils.DbId, containerID) + return container.InspectResponse{Config: &container.Config{Image: expectedImage}}, nil + }) + + assert.NoError(t, err) + assert.Empty(t, utils.CmdSuggestion) + }) + + t.Run("passes when registry differs but postgres tag matches", func(t *testing.T) { + utils.CmdSuggestion = "" + err := ensureLocalPostgresImageCurrent(context.Background(), func(context.Context, string) (container.InspectResponse, error) { + return container.InspectResponse{Config: &container.Config{Image: "docker.io/supabase/postgres:17.6.1.138"}}, nil + }) + + assert.NoError(t, err) + assert.Empty(t, utils.CmdSuggestion) + }) + + t.Run("fails when local container image is stale", func(t *testing.T) { + utils.CmdSuggestion = "" + err := ensureLocalPostgresImageCurrent(context.Background(), func(context.Context, string) (container.InspectResponse, error) { + return container.InspectResponse{Config: &container.Config{Image: "public.ecr.aws/supabase/postgres:17.6.1.106"}}, nil + }) + + assert.ErrorContains(t, err, "local Postgres container image is stale") + assert.ErrorContains(t, err, "17.6.1.106") + assert.ErrorContains(t, err, "17.6.1.138") + assert.Contains(t, utils.CmdSuggestion, "supabase stop --all --no-backup") + assert.Contains(t, utils.CmdSuggestion, "supabase start") + }) +} + func TestHasDeclarativeFiles(t *testing.T) { t.Run("returns false when dir does not exist", func(t *testing.T) { assert.False(t, hasDeclarativeFiles(mockFsys())) diff --git a/apps/cli-go/internal/db/declarative/declarative.go b/apps/cli-go/internal/db/declarative/declarative.go index 9b17efa078..c79df6289f 100644 --- a/apps/cli-go/internal/db/declarative/declarative.go +++ b/apps/cli-go/internal/db/declarative/declarative.go @@ -363,8 +363,8 @@ func getGenerateBaselineCatalogRef(ctx context.Context, noCache bool, fsys afero // getMigrationsCatalogRef returns a catalog reference representing local // migrations applied to a shadow database. // -// A migration-content hash keys the cache so it is reused only when local -// migration state is unchanged. +// A migration-content hash plus setup-input token keys the cache so it is reused +// only when both local migration state and platform baseline inputs are unchanged. func getMigrationsCatalogRef(ctx context.Context, noCache bool, fsys afero.Fs, prefix string, options ...func(*pgx.ConnConfig)) (string, error) { migrations, err := migration.ListLocalMigrations(utils.MigrationsDir, afero.NewIOFS(fsys)) if err != nil { @@ -390,7 +390,7 @@ func getMigrationsCatalogRef(ctx context.Context, noCache bool, fsys afero.Fs, p } } } - hash, err := pgcache.HashMigrations(fsys) + hash, err := migrationsCatalogCacheKey(fsys) if err != nil { return "", err } @@ -762,6 +762,18 @@ func declarativeCatalogCacheKey(fsys afero.Fs) (string, error) { return setup + "-" + schemaHash, nil } +func migrationsCatalogCacheKey(fsys afero.Fs) (string, error) { + migrationsHash, err := hashMigrations(fsys) + if err != nil { + return "", err + } + setup, err := setupInputsToken(fsys) + if err != nil { + return "", err + } + return setup + "-" + migrationsHash, nil +} + func sanitizedCatalogPrefix(prefix string) string { prefix = strings.TrimSpace(prefix) if len(prefix) == 0 { diff --git a/apps/cli-go/internal/db/declarative/declarative_test.go b/apps/cli-go/internal/db/declarative/declarative_test.go index fa17a12a2b..cbd67d29de 100644 --- a/apps/cli-go/internal/db/declarative/declarative_test.go +++ b/apps/cli-go/internal/db/declarative/declarative_test.go @@ -241,22 +241,27 @@ func TestGetMigrationsCatalogRefUsesCache(t *testing.T) { fsys := afero.NewMemMapFs() p := filepath.Join(utils.MigrationsDir, "20240101000000_first.sql") require.NoError(t, afero.WriteFile(fsys, p, []byte("create table a();"), 0644)) - hash, err := hashMigrations(fsys) + legacyHash, err := hashMigrations(fsys) require.NoError(t, err) + stalePath := filepath.Join(utils.TempDir, "pgdelta", "catalog-local-migrations-"+legacyHash+"-1000.json") + require.NoError(t, afero.WriteFile(fsys, stalePath, []byte(`{"version":"stale"}`), 0644)) + hash, err := migrationsCatalogCacheKey(fsys) + require.NoError(t, err) cachePath := filepath.Join(utils.TempDir, "pgdelta", "catalog-local-migrations-"+hash+"-1000.json") require.NoError(t, afero.WriteFile(fsys, cachePath, []byte(`{"version":1}`), 0644)) ref, err := getMigrationsCatalogRef(t.Context(), false, fsys, "local") require.NoError(t, err) assert.Equal(t, cachePath, ref) + assert.NotEqual(t, stalePath, ref) } func TestGetMigrationsCatalogRefUsesProjectPrefix(t *testing.T) { fsys := afero.NewMemMapFs() p := filepath.Join(utils.MigrationsDir, "20240101000000_first.sql") require.NoError(t, afero.WriteFile(fsys, p, []byte("create table a();"), 0644)) - hash, err := hashMigrations(fsys) + hash, err := migrationsCatalogCacheKey(fsys) require.NoError(t, err) cachePath := filepath.Join(utils.TempDir, "pgdelta", "catalog-testproject-migrations-"+hash+"-1000.json") diff --git a/apps/cli/src/legacy/commands/db/diff/diff.integration.test.ts b/apps/cli/src/legacy/commands/db/diff/diff.integration.test.ts index bb52810856..bc2d759e52 100644 --- a/apps/cli/src/legacy/commands/db/diff/diff.integration.test.ts +++ b/apps/cli/src/legacy/commands/db/diff/diff.integration.test.ts @@ -63,6 +63,7 @@ function setup(workdir: string, opts: SetupOpts = {}) { }, execInherit: () => Effect.succeed(0), ensureLocalDatabaseStarted: () => Effect.void, + ensureLocalPostgresImageCurrent: () => Effect.void, provisionShadow: ({ mode, targetLocal, usePgDelta, projectRef }) => { provisionCalls.push({ mode, targetLocal, usePgDelta, projectRef }); return Effect.succeed({ diff --git a/apps/cli/src/legacy/commands/db/pull/pull.integration.test.ts b/apps/cli/src/legacy/commands/db/pull/pull.integration.test.ts index f7ee1b0396..794b61ab5e 100644 --- a/apps/cli/src/legacy/commands/db/pull/pull.integration.test.ts +++ b/apps/cli/src/legacy/commands/db/pull/pull.integration.test.ts @@ -81,6 +81,7 @@ function setup(workdir: string, opts: SetupOpts = {}) { exportCatalog: () => Effect.succeed("supabase/.temp/pgdelta/x.json"), execInherit: () => Effect.succeed(0), ensureLocalDatabaseStarted: () => Effect.void, + ensureLocalPostgresImageCurrent: () => Effect.void, provisionShadow: ({ mode, usePgDelta, targetLocal, projectRef }) => { provisionCalls.push({ mode, usePgDelta, targetLocal, projectRef }); return Effect.succeed({ diff --git a/apps/cli/src/legacy/commands/db/schema/declarative/declarative.orchestrate.integration.test.ts b/apps/cli/src/legacy/commands/db/schema/declarative/declarative.orchestrate.integration.test.ts index ff49d1be91..19da790379 100644 --- a/apps/cli/src/legacy/commands/db/schema/declarative/declarative.orchestrate.integration.test.ts +++ b/apps/cli/src/legacy/commands/db/schema/declarative/declarative.orchestrate.integration.test.ts @@ -29,6 +29,7 @@ function mockSeam(paths: Record) { }, execInherit: () => Effect.succeed(0), ensureLocalDatabaseStarted: () => Effect.void, + ensureLocalPostgresImageCurrent: () => Effect.void, provisionShadow: () => Effect.die("provisionShadow not used in declarative tests"), removeShadowContainer: () => Effect.void, }); diff --git a/apps/cli/src/legacy/commands/db/schema/declarative/declarative.smart-target.ts b/apps/cli/src/legacy/commands/db/schema/declarative/declarative.smart-target.ts index 167520a3d4..7e6cbab323 100644 --- a/apps/cli/src/legacy/commands/db/schema/declarative/declarative.smart-target.ts +++ b/apps/cli/src/legacy/commands/db/schema/declarative/declarative.smart-target.ts @@ -19,6 +19,7 @@ import { LegacyDeclarativeApplyError, LegacyDeclarativeInvalidDbUrlError, } from "./declarative.errors.ts"; +import type { LegacyDeclarativeShadowDbError } from "../../shared/legacy-pgdelta.errors.ts"; import { LegacyDeclarativeSeam } from "../../shared/legacy-pgdelta.seam.service.ts"; /** @@ -84,10 +85,12 @@ export const legacyResolveSmartTargetUrl = Effect.fnUntraced(function* ( path: Path.Path, workdir: string, linkedRef: Option.Option, + beforeLocalTarget: Effect.Effect = Effect.void, ) { if (!hasMigrations) { // No migrations → generate from local. Go runs ensureLocalDatabaseStarted first // (db_schema_declarative.go:291), starting a stopped stack. + yield* beforeLocalTarget; yield* (yield* LegacyDeclarativeSeam).ensureLocalDatabaseStarted(); return legacyLocalUrl(local); } @@ -149,6 +152,7 @@ export const legacyResolveSmartTargetUrl = Effect.fnUntraced(function* ( // "Local database" choice: Go runs ensureLocalDatabaseStarted before the reset // prompt (db_schema_declarative.go:249), starting a stopped stack. + yield* beforeLocalTarget; yield* (yield* LegacyDeclarativeSeam).ensureLocalDatabaseStarted(); let shouldReset = flags.reset; diff --git a/apps/cli/src/legacy/commands/db/schema/declarative/generate/generate.handler.ts b/apps/cli/src/legacy/commands/db/schema/declarative/generate/generate.handler.ts index 5e090a8633..fbcb89a721 100644 --- a/apps/cli/src/legacy/commands/db/schema/declarative/generate/generate.handler.ts +++ b/apps/cli/src/legacy/commands/db/schema/declarative/generate/generate.handler.ts @@ -132,14 +132,16 @@ export const legacyDbSchemaDeclarativeGenerate = Effect.fn("legacy.db.schema.dec let targetUrl: string; let overwrite: boolean; if (hasExplicitTarget) { + const seam = yield* LegacyDeclarativeSeam; if (Option.isSome(flags.local)) { // Target selection keys off flag presence (Go's `Changed`), but the // auto-start gates on the boolean VALUE: Go passes `declarativeLocal` to // `ensureLocalDatabaseStarted` (`db_schema_declarative.go:190`), which // short-circuits `if !local { return nil }` (`:127-128`). So `--local=false` // selects the local target but must NOT start a stopped stack. + yield* seam.ensureLocalPostgresImageCurrent(); if (Option.getOrElse(flags.local, () => false)) { - yield* (yield* LegacyDeclarativeSeam).ensureLocalDatabaseStarted(); + yield* seam.ensureLocalDatabaseStarted(); } targetUrl = legacyLocalUrl(local); } else { @@ -206,6 +208,7 @@ export const legacyDbSchemaDeclarativeGenerate = Effect.fn("legacy.db.schema.dec path, cliConfig.workdir, linkedRef, + (yield* LegacyDeclarativeSeam).ensureLocalPostgresImageCurrent(), ); overwrite = true; } diff --git a/apps/cli/src/legacy/commands/db/schema/declarative/generate/generate.integration.test.ts b/apps/cli/src/legacy/commands/db/schema/declarative/generate/generate.integration.test.ts index b49cf92592..938ceab885 100644 --- a/apps/cli/src/legacy/commands/db/schema/declarative/generate/generate.integration.test.ts +++ b/apps/cli/src/legacy/commands/db/schema/declarative/generate/generate.integration.test.ts @@ -58,6 +58,7 @@ interface SetupOpts { networkId?: Option.Option; projectId?: Option.Option; exportFailsForMode?: LegacyCatalogMode; + staleLocalImage?: boolean; } function setup(workdir: string, opts: SetupOpts = {}) { @@ -71,6 +72,7 @@ function setup(workdir: string, opts: SetupOpts = {}) { const seamCalls: LegacyCatalogMode[] = []; const seamExportCalls: Array<{ mode: LegacyCatalogMode; projectRef?: string }> = []; const execInheritCalls: ReadonlyArray[] = []; + const localPostgresImageChecks: Array = []; let ensureStartedCalls = 0; const seam = Layer.succeed(LegacyDeclarativeSeam, { exportCatalog: ({ mode, projectRef }) => { @@ -88,6 +90,20 @@ function setup(workdir: string, opts: SetupOpts = {}) { Effect.sync(() => { ensureStartedCalls += 1; }), + ensureLocalPostgresImageCurrent: () => + Effect.sync(() => { + localPostgresImageChecks.push(true); + }).pipe( + Effect.flatMap(() => + opts.staleLocalImage === true + ? Effect.fail( + new LegacyDeclarativeShadowDbError({ + message: "local Postgres container image is stale", + }), + ) + : Effect.void, + ), + ), provisionShadow: () => Effect.die("provisionShadow not used in declarative tests"), removeShadowContainer: () => Effect.void, }); @@ -148,6 +164,7 @@ function setup(workdir: string, opts: SetupOpts = {}) { edgeCalls, resolverCalls, proxyCalls, + localPostgresImageChecks, get ensureStartedCalls() { return ensureStartedCalls; }, @@ -228,6 +245,23 @@ describe("legacy db schema declarative generate integration", () => { }).pipe(Effect.provide(s.layer)); }); + it.effect("explicit --local checks the local Postgres image before generating", () => { + const s = setup(tmp.current, { experimental: true, staleLocalImage: true }); + return Effect.gen(function* () { + const exit = yield* Effect.exit( + legacyDbSchemaDeclarativeGenerate(flags({ local: Option.some(true) })), + ); + expect(Exit.isFailure(exit)).toBe(true); + expect(failError(exit)).toMatchObject({ + _tag: "LegacyDeclarativeShadowDbError", + message: "local Postgres container image is stale", + }); + expect(s.localPostgresImageChecks).toHaveLength(1); + expect(s.ensureStartedCalls).toBe(0); + expect(s.edgeCalls).toEqual([]); + }).pipe(Effect.provide(s.layer)); + }); + it.effect("honors --yes to overwrite existing declarative files without prompting", () => { // Pre-seed the declarative dir so the overwrite branch is reached. With --yes, // Go's confirmOverwrite returns true immediately (Console.PromptYesNo); the @@ -416,6 +450,7 @@ describe("legacy db schema declarative generate integration", () => { expect(s.seamCalls).toContain("baseline"); // ... but did NOT auto-start (value is false). expect(s.ensureStartedCalls).toBe(0); + expect(s.localPostgresImageChecks).toHaveLength(1); }).pipe(Effect.provide(s.layer)); }); @@ -561,6 +596,27 @@ describe("legacy db schema declarative generate integration", () => { }).pipe(Effect.provide(s.layer)); }); + it.effect("smart mode: local target checks the local Postgres image before generating", () => { + mkdirSync(join(tmp.current, "supabase", "migrations"), { recursive: true }); + writeFileSync(join(tmp.current, "supabase", "migrations", "0001_init.sql"), "select 1;"); + const s = setup(tmp.current, { + experimental: true, + stdinIsTty: true, + staleLocalImage: true, + promptSelectResponses: ["local"], + }); + return Effect.gen(function* () { + const exit = yield* Effect.exit(legacyDbSchemaDeclarativeGenerate(flags())); + expect(Exit.isFailure(exit)).toBe(true); + expect(failError(exit)).toMatchObject({ + _tag: "LegacyDeclarativeShadowDbError", + message: "local Postgres container image is stale", + }); + expect(s.localPostgresImageChecks).toHaveLength(1); + expect(s.edgeCalls).toEqual([]); + }).pipe(Effect.provide(s.layer)); + }); + it.effect( "smart mode: caches the linked project even when the user picks local (Go PostRun)", () => { diff --git a/apps/cli/src/legacy/commands/db/schema/declarative/sync/sync.handler.ts b/apps/cli/src/legacy/commands/db/schema/declarative/sync/sync.handler.ts index 8213bd9df9..d70db8625c 100644 --- a/apps/cli/src/legacy/commands/db/schema/declarative/sync/sync.handler.ts +++ b/apps/cli/src/legacy/commands/db/schema/declarative/sync/sync.handler.ts @@ -109,7 +109,6 @@ export const legacyDbSchemaDeclarativeSync = Effect.fn("legacy.db.schema.declara pgDeltaEnabled: toml.pgDelta.enabled, configPath: path.join("supabase", "config.toml"), }); - // `path.resolve` (not `path.join`) so an absolute `declarative_schema_path` is // used as-is, matching Go's `config.resolve` (which only prefixes the workdir onto // a relative path). `path.join(workdir, abs)` would mangle the absolute path. @@ -131,6 +130,8 @@ export const legacyDbSchemaDeclarativeSync = Effect.fn("legacy.db.schema.declara schema: flags.schema, noCache: flags.noCache, }; + const ensureLocalPostgresImageCurrent = seam.ensureLocalPostgresImageCurrent(); + const declarativeFilesExist = yield* declarativeDirHasFiles(fs, declarativeDir); // Go's `saveApplyDebugBundle`: warn (rather than masking the apply error) and // treat the bundle path as empty when the debug directory cannot be created, so @@ -148,7 +149,7 @@ export const legacyDbSchemaDeclarativeSync = Effect.fn("legacy.db.schema.declara ); // Step 1: declarative files must exist; in a TTY, offer to generate them. - if (!(yield* declarativeDirHasFiles(fs, declarativeDir))) { + if (!declarativeFilesExist) { const noFiles = new LegacyDeclarativeNonInteractiveError({ message: "no declarative schema found. Run supabase db schema declarative generate first", }); @@ -207,6 +208,7 @@ export const legacyDbSchemaDeclarativeSync = Effect.fn("legacy.db.schema.declara path, cliConfig.workdir, linkedRef, + ensureLocalPostgresImageCurrent, ); const generated = yield* legacyGenerateDeclarativeOutput(run, targetUrl); yield* legacyWriteDeclarativeSchemas(fs, path, declarativeDir, generated); @@ -307,6 +309,7 @@ export const legacyDbSchemaDeclarativeSync = Effect.fn("legacy.db.schema.declara if (!shouldApply) return; // Step 8: apply the migration to the local database (native). + yield* ensureLocalPostgresImageCurrent; const applyExit = yield* applyMigrationToLocal( { port: toml.port, password: toml.password, dnsResolver }, migrationPath, diff --git a/apps/cli/src/legacy/commands/db/schema/declarative/sync/sync.integration.test.ts b/apps/cli/src/legacy/commands/db/schema/declarative/sync/sync.integration.test.ts index 0420d274f0..aa2718ac62 100644 --- a/apps/cli/src/legacy/commands/db/schema/declarative/sync/sync.integration.test.ts +++ b/apps/cli/src/legacy/commands/db/schema/declarative/sync/sync.integration.test.ts @@ -24,10 +24,24 @@ import { LegacyEdgeRuntimeScript, } from "../../../../../shared/legacy-edge-runtime-script.service.ts"; import { LegacyPgDeltaSslProbe } from "../../../../../shared/legacy-pgdelta-ssl-probe.service.ts"; +import { LegacyDeclarativeShadowDbError } from "../../../shared/legacy-pgdelta.errors.ts"; import { LegacyDeclarativeSeam } from "../../../shared/legacy-pgdelta.seam.service.ts"; import type { LegacyDbSchemaDeclarativeSyncFlags } from "./sync.command.ts"; import { legacyDbSchemaDeclarativeSync } from "./sync.handler.ts"; +const EXPORT_JSON = JSON.stringify({ + version: 1, + mode: "declarative", + files: [ + { + path: "schemas/public/tables/players.sql", + order: 0, + statements: 1, + sql: "create table players ();", + }, + ], +}); + interface SetupOpts { experimental?: boolean; yes?: boolean; @@ -40,6 +54,8 @@ interface SetupOpts { promptTextResponses?: ReadonlyArray; networkId?: string; projectId?: Option.Option; + staleLocalImage?: boolean; + exportJson?: string; } function setup(workdir: string, opts: SetupOpts = {}) { @@ -51,6 +67,7 @@ function setup(workdir: string, opts: SetupOpts = {}) { const telemetry = mockLegacyTelemetryStateTracked(); const cache = mockLegacyLinkedProjectCacheTracked(); const execInheritCalls: ReadonlyArray[] = []; + const localPostgresImageChecks: Array = []; const seam = Layer.succeed(LegacyDeclarativeSeam, { exportCatalog: ({ mode }) => Effect.succeed(`supabase/.temp/pgdelta/${mode}.json`), execInherit: (args) => @@ -59,12 +76,33 @@ function setup(workdir: string, opts: SetupOpts = {}) { return opts.resetExitCode ?? 0; }), ensureLocalDatabaseStarted: () => Effect.void, + ensureLocalPostgresImageCurrent: () => + Effect.sync(() => { + localPostgresImageChecks.push(true); + }).pipe( + Effect.flatMap(() => + opts.staleLocalImage === true + ? Effect.fail( + new LegacyDeclarativeShadowDbError({ + message: "local Postgres container image is stale", + }), + ) + : Effect.void, + ), + ), provisionShadow: () => Effect.die("provisionShadow not used in declarative tests"), removeShadowContainer: () => Effect.void, }); const edge = Layer.succeed(LegacyEdgeRuntimeScript, { - run: (_opts: LegacyEdgeRuntimeRunOpts) => - Effect.succeed({ stdout: opts.diffSql ?? "", stderr: "" }), + run: (runOpts: LegacyEdgeRuntimeRunOpts) => + Effect.succeed({ + stdout: + opts.exportJson !== undefined && + runOpts.errPrefix === "error exporting declarative schema" + ? opts.exportJson + : (opts.diffSql ?? ""), + stderr: "", + }), }); const dbExec: string[] = []; const dbConn = Layer.succeed(LegacyDbConnection, { @@ -123,7 +161,7 @@ function setup(workdir: string, opts: SetupOpts = {}) { Layer.succeed(LegacyPgDeltaSslProbe, { requireSsl: () => Effect.succeed(false) }), BunServices.layer, ); - return { layer, out, execInheritCalls, dbExec, cache }; + return { layer, out, execInheritCalls, dbExec, cache, localPostgresImageChecks }; } const flags = ( @@ -205,6 +243,59 @@ describe("legacy db schema declarative sync integration", () => { }).pipe(Effect.provide(layer)); }); + it.effect("non-interactive default dry-run does not check the local Postgres image", () => { + seedDeclarative(tmp.current); + const s = setup(tmp.current, { + experimental: true, + staleLocalImage: true, + diffSql: "ALTER TABLE a ADD COLUMN b int;\n", + }); + return Effect.gen(function* () { + yield* legacyDbSchemaDeclarativeSync(flags()); + const migrations = readdirSync(join(tmp.current, "supabase", "migrations")); + expect(migrations).toHaveLength(1); + expect(s.localPostgresImageChecks).toEqual([]); + expect(s.dbExec).toEqual([]); + }).pipe(Effect.provide(s.layer)); + }); + + it.effect("--apply checks the local Postgres image before applying", () => { + seedDeclarative(tmp.current); + const s = setup(tmp.current, { + experimental: true, + staleLocalImage: true, + diffSql: "ALTER TABLE a ADD COLUMN b int;\n", + }); + return Effect.gen(function* () { + const exit = yield* Effect.exit( + legacyDbSchemaDeclarativeSync(flags({ apply: Option.some(true) })), + ); + expect(Exit.isFailure(exit)).toBe(true); + expect(failError(exit)).toMatchObject({ + _tag: "LegacyDeclarativeShadowDbError", + message: "local Postgres container image is stale", + }); + expect(s.localPostgresImageChecks).toHaveLength(1); + expect(s.dbExec).toEqual([]); + }).pipe(Effect.provide(s.layer)); + }); + + it.effect("--no-apply skips the local Postgres image check", () => { + seedDeclarative(tmp.current); + const s = setup(tmp.current, { + experimental: true, + staleLocalImage: true, + diffSql: "ALTER TABLE a ADD COLUMN b int;\n", + }); + return Effect.gen(function* () { + yield* legacyDbSchemaDeclarativeSync(flags({ noApply: Option.some(true) })); + const migrations = readdirSync(join(tmp.current, "supabase", "migrations")); + expect(migrations).toHaveLength(1); + expect(s.localPostgresImageChecks).toEqual([]); + expect(s.dbExec).toEqual([]); + }).pipe(Effect.provide(s.layer)); + }); + it.effect("--yes bypasses the bootstrap prompt when no declarative files exist", () => { // Without --yes + non-TTY this fails at the "no declarative schema found" gate // (prior test). With --yes, Go's PromptYesNo auto-confirms, so the bootstrap is @@ -239,6 +330,90 @@ describe("legacy db schema declarative sync integration", () => { }).pipe(Effect.provide(s.layer)); }); + it.effect("bootstrap linked target does not run the local Postgres image check", () => { + // The stale-image guard only matters once bootstrap chooses a local source. A + // linked/custom bootstrap can build fresh catalogs and skip local apply, so it + // must reach the target prompt before any local-container inspection. + mkdirSync(join(tmp.current, "supabase", "migrations"), { recursive: true }); + writeFileSync(join(tmp.current, "supabase", "migrations", "0001_init.sql"), "select 1;"); + const s = setup(tmp.current, { + experimental: true, + stdinIsTty: true, + staleLocalImage: true, + projectId: Option.some("abcdefghijklmnopqrst"), + promptConfirmResponses: [true], // generate a new one? yes + promptSelectResponses: ["linked"], + }); + return Effect.gen(function* () { + const exit = yield* Effect.exit( + legacyDbSchemaDeclarativeSync(flags({ noCache: true, noApply: Option.some(true) })), + ); + expect(s.localPostgresImageChecks).toEqual([]); + expect(JSON.stringify(exit)).not.toContain("local Postgres container image is stale"); + expect((s.out.promptSelectCalls[0]?.options ?? []).map((o) => o.value)).toEqual([ + "local", + "linked", + "custom", + ]); + }).pipe(Effect.provide(s.layer)); + }); + + it.effect("bootstrap linked target checks the local Postgres image before apply", () => { + mkdirSync(join(tmp.current, "supabase", "migrations"), { recursive: true }); + writeFileSync(join(tmp.current, "supabase", "migrations", "0001_init.sql"), "select 1;"); + const s = setup(tmp.current, { + experimental: true, + stdinIsTty: true, + staleLocalImage: true, + projectId: Option.some("abcdefghijklmnopqrst"), + diffSql: "ALTER TABLE a ADD COLUMN b int;\n", + exportJson: EXPORT_JSON, + promptConfirmResponses: [true], // generate a new one? yes + promptSelectResponses: ["linked"], + }); + return Effect.gen(function* () { + const exit = yield* Effect.exit( + legacyDbSchemaDeclarativeSync( + flags({ + noCache: true, + apply: Option.some(true), + name: Option.some("bootstrap_apply"), + }), + ), + ); + expect(Exit.isFailure(exit)).toBe(true); + expect(failError(exit)).toMatchObject({ + _tag: "LegacyDeclarativeShadowDbError", + message: "local Postgres container image is stale", + }); + expect(s.localPostgresImageChecks).toHaveLength(1); + expect(s.dbExec).toEqual([]); + }).pipe(Effect.provide(s.layer)); + }); + + it.effect("bootstrap local target checks the local Postgres image", () => { + mkdirSync(join(tmp.current, "supabase", "migrations"), { recursive: true }); + writeFileSync(join(tmp.current, "supabase", "migrations", "0001_init.sql"), "select 1;"); + const s = setup(tmp.current, { + experimental: true, + stdinIsTty: true, + staleLocalImage: true, + promptConfirmResponses: [true], // generate a new one? yes + promptSelectResponses: ["local"], + }); + return Effect.gen(function* () { + const exit = yield* Effect.exit( + legacyDbSchemaDeclarativeSync(flags({ noCache: true, noApply: Option.some(true) })), + ); + expect(Exit.isFailure(exit)).toBe(true); + expect(failError(exit)).toMatchObject({ + _tag: "LegacyDeclarativeShadowDbError", + message: "local Postgres container image is stale", + }); + expect(s.localPostgresImageChecks).toHaveLength(1); + }).pipe(Effect.provide(s.layer)); + }); + it.effect("bootstrap: an unreadable migrations path is treated as no migrations", () => { // Go's delegated hasMigrationFiles returns false on ANY ListLocalMigrations error // (db_schema_declarative.go:164-169), flowing into the no-migrations local generate. diff --git a/apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.seam.layer.ts b/apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.seam.layer.ts index 30c03b827f..634c4c7b56 100644 --- a/apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.seam.layer.ts +++ b/apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.seam.layer.ts @@ -6,7 +6,9 @@ import { LegacyNetworkIdFlag, LegacyProfileFlag } from "../../../../shared/legac import { resolveBinary } from "../../../../shared/legacy/go-proxy.layer.ts"; import { LegacyCliConfig } from "../../../config/legacy-cli-config.service.ts"; import { containerCliExitCode, spawnContainerCli } from "../../../shared/legacy-container-cli.ts"; +import { legacyResolveDbImage } from "../../../shared/legacy-db-image.ts"; import { legacyReadDbToml } from "../../../shared/legacy-db-config.toml-read.ts"; +import { legacyGetRegistryImageUrl } from "../../../shared/legacy-docker-registry.ts"; import { legacyResolveLocalProjectId, localDbContainerId, @@ -265,6 +267,116 @@ export const legacyDeclarativeSeamLayer = Layer.effect( } }), ), + ensureLocalPostgresImageCurrent: () => + Effect.scoped( + Effect.gen(function* () { + const toml = yield* legacyReadDbToml(fs, path, cliConfig.workdir).pipe( + Effect.mapError( + (error) => + new LegacyDeclarativeShadowDbError({ + message: `failed to read config for local Postgres image check: ${error.message}`, + }), + ), + ); + const image = yield* legacyResolveDbImage( + fs, + path, + cliConfig.workdir, + toml.majorVersion, + Option.getOrUndefined(toml.orioledbVersion), + ); + const tomlProjectId = toml.projectId; + const projectId = legacyResolveLocalProjectId( + Option.getOrUndefined(cliConfig.projectId), + Option.getOrUndefined(tomlProjectId), + cliConfig.workdir, + ); + const containerId = localDbContainerId(projectId); + const child = yield* spawnContainerCli(spawner, ["container", "inspect", containerId], { + stdin: "ignore", + stdout: "pipe", + stderr: "pipe", + extendEnv: true, + }).pipe( + Effect.mapError( + () => + new LegacyDeclarativeShadowDbError({ + message: "failed to inspect local Postgres container.", + }), + ), + ); + const stdoutChunks: Array = []; + const stderrChunks: Array = []; + yield* Stream.runForEach(child.stdout, (chunk) => + Effect.sync(() => { + stdoutChunks.push(chunk); + }), + ).pipe( + Effect.mapError( + () => + new LegacyDeclarativeShadowDbError({ + message: "failed to inspect local Postgres container.", + }), + ), + ); + yield* Stream.runForEach(child.stderr, (chunk) => + Effect.sync(() => { + stderrChunks.push(chunk); + }), + ).pipe( + Effect.mapError( + () => + new LegacyDeclarativeShadowDbError({ + message: "failed to inspect local Postgres container.", + }), + ), + ); + const inspectExit = yield* child.exitCode.pipe( + Effect.map(Number), + Effect.mapError( + () => + new LegacyDeclarativeShadowDbError({ + message: "failed to inspect local Postgres container.", + }), + ), + ); + const decodeChunks = (chunks: ReadonlyArray): string => { + const total = chunks.reduce((size, chunk) => size + chunk.length, 0); + const bytes = new Uint8Array(total); + let offset = 0; + for (const chunk of chunks) { + bytes.set(chunk, offset); + offset += chunk.length; + } + return new TextDecoder().decode(bytes).trim(); + }; + const stderr = decodeChunks(stderrChunks); + const stdout = decodeChunks(stdoutChunks); + if (inspectExit !== 0) { + if (legacyIsMissingContainerInspectError(stderr)) return; + return yield* Effect.fail( + new LegacyDeclarativeShadowDbError({ + message: + stderr.length > 0 + ? `failed to inspect local Postgres container: ${stderr}` + : "failed to inspect local Postgres container.", + }), + ); + } + const actual = legacyResolveContainerInspectImageName(stdout); + const expected = legacyGetRegistryImageUrl(image).trim(); + const actualTag = dockerImageTag(actual); + const expectedTag = dockerImageTag(expected); + if (actualTag.length === 0 || expectedTag.length === 0 || actualTag === expectedTag) { + return; + } + return yield* Effect.fail( + new LegacyDeclarativeShadowDbError({ + message: `local Postgres container image is stale: running ${actual} but expected ${expected}. Run supabase stop --all --no-backup, then supabase start before syncing declarative schemas.`, + }), + ); + }), + ), provisionShadow: ({ mode, targetLocal, usePgDelta, schema, projectRef }) => Effect.scoped( Effect.gen(function* () { @@ -398,3 +510,39 @@ const failure = (exitCode?: number) => ? "failed to provision the shadow database." : `failed to provision the shadow database: exit ${exitCode}`, }); + +function dockerImageTag(image: string): string { + const trimmed = image.trim(); + const index = trimmed.lastIndexOf(":"); + if (index < 0 || index === trimmed.length - 1) return ""; + return trimmed.slice(index + 1); +} + +export function legacyIsMissingContainerInspectError(stderr: string): boolean { + return stderr.toLowerCase().includes("no such container"); +} + +export function legacyResolveContainerInspectImageName(stdout: string): string { + const trimmed = stdout.trim(); + if (trimmed.length === 0) return ""; + let parsed: unknown; + try { + parsed = JSON.parse(trimmed); + } catch { + return trimmed; + } + const inspect = Array.isArray(parsed) ? parsed[0] : parsed; + if (!isJsonRecord(inspect)) return ""; + const imageName = inspect["ImageName"]; + if (typeof imageName === "string" && imageName.trim().length > 0) { + return imageName.trim(); + } + const config = inspect["Config"]; + if (!isJsonRecord(config)) return ""; + const configImage = config["Image"]; + return typeof configImage === "string" && configImage.trim().length > 0 ? configImage.trim() : ""; +} + +function isJsonRecord(value: unknown): value is { readonly [key: string]: unknown } { + return typeof value === "object" && value !== null; +} diff --git a/apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.seam.layer.unit.test.ts b/apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.seam.layer.unit.test.ts new file mode 100644 index 0000000000..b6b0251a10 --- /dev/null +++ b/apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.seam.layer.unit.test.ts @@ -0,0 +1,56 @@ +import { describe, expect, it } from "vitest"; + +import { + legacyIsMissingContainerInspectError, + legacyResolveContainerInspectImageName, +} from "./legacy-pgdelta.seam.layer.ts"; + +describe("legacyIsMissingContainerInspectError", () => { + it("matches Docker and Podman missing-container stderr", () => { + expect(legacyIsMissingContainerInspectError("Error: No such container: supabase_db_test")).toBe( + true, + ); + expect(legacyIsMissingContainerInspectError("Error: no such container: supabase_db_test")).toBe( + true, + ); + }); + + it("does not match unrelated inspect failures", () => { + expect(legacyIsMissingContainerInspectError("Cannot connect to the Docker daemon")).toBe(false); + }); +}); + +describe("legacyResolveContainerInspectImageName", () => { + it("reads Docker's config image from inspect JSON", () => { + expect( + legacyResolveContainerInspectImageName( + JSON.stringify([{ Config: { Image: "public.ecr.aws/supabase/postgres:17.4.1.056" } }]), + ), + ).toBe("public.ecr.aws/supabase/postgres:17.4.1.056"); + }); + + it("prefers Podman's image name from inspect JSON", () => { + expect( + legacyResolveContainerInspectImageName( + JSON.stringify([ + { + Image: "sha256:0123456789", + ImageName: "public.ecr.aws/supabase/postgres:17.4.1.056", + }, + ]), + ), + ).toBe("public.ecr.aws/supabase/postgres:17.4.1.056"); + }); + + it("keeps raw formatter output as a compatibility fallback", () => { + expect(legacyResolveContainerInspectImageName("supabase/postgres:15.1.0")).toBe( + "supabase/postgres:15.1.0", + ); + }); + + it("returns empty when JSON inspect output has no image-name field", () => { + expect(legacyResolveContainerInspectImageName(JSON.stringify([{ Image: "sha256:0123" }]))).toBe( + "", + ); + }); +}); diff --git a/apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.seam.service.ts b/apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.seam.service.ts index de657d0af7..16593f5f75 100644 --- a/apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.seam.service.ts +++ b/apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.seam.service.ts @@ -73,6 +73,15 @@ interface LegacyDeclarativeSeamShape { * of failing to connect, matching Go. */ readonly ensureLocalDatabaseStarted: () => Effect.Effect; + /** + * Checks the running local Postgres container image tag against the currently + * resolved Postgres image. A missing container is accepted: catalog cache keys + * self-invalidate on setup inputs, and local-apply paths will start/connect later. + */ + readonly ensureLocalPostgresImageCurrent: () => Effect.Effect< + void, + LegacyDeclarativeShadowDbError + >; /** * Provisions a live shadow database via the bundled Go binary's hidden * `db __shadow` command and returns it running (the container is NOT removed — From 9332ce07e08a024c1fdb7e5b5a49790f872f59c4 Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Thu, 25 Jun 2026 17:13:01 +0200 Subject: [PATCH 03/29] chore(ci): allow API sync automerge (#5698) Generated API sync PRs only touch generated output, but the Go API files still inherited the catch-all CODEOWNERS rule and requested CLI team review. This adds an ownerless CODEOWNERS override for `apps/cli-go/pkg/api/*.gen.go` so green Go API sync PRs can be auto-merged by the existing app bypass. It also removes the ignored self-approval steps from both API sync workflows. GitHub rejects those approvals for PRs created by the same app, and the workflows already enable auto-merge with the app token after opening or updating the PR. --- .github/CODEOWNERS | 1 + .github/workflows/api-package-sync.yml | 8 -------- .github/workflows/cli-go-api-sync.yml | 8 -------- 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8cfe4c1b59..c39f909e34 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -13,4 +13,5 @@ # Generated code. These ownerless rules override the catch-all above so # CI-green sync PRs (e.g. Management API OpenAPI spec) can be auto-merged. +/apps/cli-go/pkg/api/*.gen.go /packages/api/src/generated/ diff --git a/.github/workflows/api-package-sync.yml b/.github/workflows/api-package-sync.yml index 20a2f31178..bb1117b38a 100644 --- a/.github/workflows/api-package-sync.yml +++ b/.github/workflows/api-package-sync.yml @@ -65,14 +65,6 @@ jobs: branch: sync/api-package base: develop - - name: Approve a PR - if: steps.check.outputs.has_changes == 'true' && steps.cpr.outputs.pull-request-operation == 'created' - continue-on-error: true - run: gh pr review --approve --repo "${{ github.repository }}" "${STEPS_CPR_OUTPUTS_PULL_REQUEST_NUMBER}" - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} - STEPS_CPR_OUTPUTS_PULL_REQUEST_NUMBER: ${{ steps.cpr.outputs.pull-request-number }} - - name: Enable Pull Request Automerge if: steps.check.outputs.has_changes == 'true' run: gh pr merge --auto --squash --repo "${{ github.repository }}" "${STEPS_CPR_OUTPUTS_PULL_REQUEST_NUMBER}" diff --git a/.github/workflows/cli-go-api-sync.yml b/.github/workflows/cli-go-api-sync.yml index f4656d09a4..2ae59ca5a0 100644 --- a/.github/workflows/cli-go-api-sync.yml +++ b/.github/workflows/cli-go-api-sync.yml @@ -61,14 +61,6 @@ jobs: branch: sync/api-types base: develop - - name: Approve a PR - if: steps.check.outputs.has_changes == 'true' && steps.cpr.outputs.pull-request-operation == 'created' - continue-on-error: true - run: gh pr review --approve --repo "${{ github.repository }}" "${STEPS_CPR_OUTPUTS_PULL_REQUEST_NUMBER}" - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} - STEPS_CPR_OUTPUTS_PULL_REQUEST_NUMBER: ${{ steps.cpr.outputs.pull-request-number }} - - name: Enable Pull Request Automerge if: steps.check.outputs.has_changes == 'true' run: gh pr merge --auto --squash --repo "${{ github.repository }}" "${STEPS_CPR_OUTPUTS_PULL_REQUEST_NUMBER}" From 7eb5d161ed27f88f3b11cb74de2093319b912396 Mon Sep 17 00:00:00 2001 From: "supabase-cli-releaser[bot]" <246109035+supabase-cli-releaser[bot]@users.noreply.github.com> Date: Thu, 25 Jun 2026 17:19:41 +0200 Subject: [PATCH 04/29] chore: sync API types from infrastructure (#5697) This PR was automatically created to sync API types from the infrastructure repository. Changes were detected in the generated API code after syncing with the latest spec from infrastructure. Co-authored-by: supabase-cli-releaser[bot] <246109035+supabase-cli-releaser[bot]@users.noreply.github.com> --- apps/cli-go/pkg/api/client.gen.go | 163 ++++++++++++++++++++++++++++++ apps/cli-go/pkg/api/types.gen.go | 8 ++ 2 files changed, 171 insertions(+) diff --git a/apps/cli-go/pkg/api/client.gen.go b/apps/cli-go/pkg/api/client.gen.go index 528ea9d8bd..7ff5b0a26c 100644 --- a/apps/cli-go/pkg/api/client.gen.go +++ b/apps/cli-go/pkg/api/client.gen.go @@ -218,6 +218,9 @@ type ClientInterface interface { // V1GetProjectLogs request V1GetProjectLogs(ctx context.Context, ref string, params *V1GetProjectLogsParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // V1GetProjectLogsAll request + V1GetProjectLogsAll(ctx context.Context, ref string, params *V1GetProjectLogsAllParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // V1GetProjectUsageApiCount request V1GetProjectUsageApiCount(ctx context.Context, ref string, params *V1GetProjectUsageApiCountParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -1267,6 +1270,18 @@ func (c *Client) V1GetProjectLogs(ctx context.Context, ref string, params *V1Get return c.Client.Do(req) } +func (c *Client) V1GetProjectLogsAll(ctx context.Context, ref string, params *V1GetProjectLogsAllParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1GetProjectLogsAllRequest(c.Server, ref, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) V1GetProjectUsageApiCount(ctx context.Context, ref string, params *V1GetProjectUsageApiCountParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewV1GetProjectUsageApiCountRequest(c.Server, ref, params) if err != nil { @@ -5316,6 +5331,94 @@ func NewV1GetProjectLogsRequest(server string, ref string, params *V1GetProjectL return nil, err } + operationPath := fmt.Sprintf("/v1/projects/%s/analytics/endpoints/logs", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Sql != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "sql", runtime.ParamLocationQuery, *params.Sql); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.IsoTimestampStart != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "iso_timestamp_start", runtime.ParamLocationQuery, *params.IsoTimestampStart); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.IsoTimestampEnd != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "iso_timestamp_end", runtime.ParamLocationQuery, *params.IsoTimestampEnd); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewV1GetProjectLogsAllRequest generates requests for V1GetProjectLogsAll +func NewV1GetProjectLogsAllRequest(server string, ref string, params *V1GetProjectLogsAllParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "ref", runtime.ParamLocationPath, ref) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + operationPath := fmt.Sprintf("/v1/projects/%s/analytics/endpoints/logs.all", pathParam0) if operationPath[0] == '/' { operationPath = "." + operationPath @@ -11562,6 +11665,9 @@ type ClientWithResponsesInterface interface { // V1GetProjectLogsWithResponse request V1GetProjectLogsWithResponse(ctx context.Context, ref string, params *V1GetProjectLogsParams, reqEditors ...RequestEditorFn) (*V1GetProjectLogsResponse, error) + // V1GetProjectLogsAllWithResponse request + V1GetProjectLogsAllWithResponse(ctx context.Context, ref string, params *V1GetProjectLogsAllParams, reqEditors ...RequestEditorFn) (*V1GetProjectLogsAllResponse, error) + // V1GetProjectUsageApiCountWithResponse request V1GetProjectUsageApiCountWithResponse(ctx context.Context, ref string, params *V1GetProjectUsageApiCountParams, reqEditors ...RequestEditorFn) (*V1GetProjectUsageApiCountResponse, error) @@ -12844,6 +12950,28 @@ func (r V1GetProjectLogsResponse) StatusCode() int { return 0 } +type V1GetProjectLogsAllResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *AnalyticsResponse +} + +// Status returns HTTPResponse.Status +func (r V1GetProjectLogsAllResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r V1GetProjectLogsAllResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type V1GetProjectUsageApiCountResponse struct { Body []byte HTTPResponse *http.Response @@ -16119,6 +16247,15 @@ func (c *ClientWithResponses) V1GetProjectLogsWithResponse(ctx context.Context, return ParseV1GetProjectLogsResponse(rsp) } +// V1GetProjectLogsAllWithResponse request returning *V1GetProjectLogsAllResponse +func (c *ClientWithResponses) V1GetProjectLogsAllWithResponse(ctx context.Context, ref string, params *V1GetProjectLogsAllParams, reqEditors ...RequestEditorFn) (*V1GetProjectLogsAllResponse, error) { + rsp, err := c.V1GetProjectLogsAll(ctx, ref, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseV1GetProjectLogsAllResponse(rsp) +} + // V1GetProjectUsageApiCountWithResponse request returning *V1GetProjectUsageApiCountResponse func (c *ClientWithResponses) V1GetProjectUsageApiCountWithResponse(ctx context.Context, ref string, params *V1GetProjectUsageApiCountParams, reqEditors ...RequestEditorFn) (*V1GetProjectUsageApiCountResponse, error) { rsp, err := c.V1GetProjectUsageApiCount(ctx, ref, params, reqEditors...) @@ -18567,6 +18704,32 @@ func ParseV1GetProjectLogsResponse(rsp *http.Response) (*V1GetProjectLogsRespons return response, nil } +// ParseV1GetProjectLogsAllResponse parses an HTTP response from a V1GetProjectLogsAllWithResponse call +func ParseV1GetProjectLogsAllResponse(rsp *http.Response) (*V1GetProjectLogsAllResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &V1GetProjectLogsAllResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest AnalyticsResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseV1GetProjectUsageApiCountResponse parses an HTTP response from a V1GetProjectUsageApiCountWithResponse call func ParseV1GetProjectUsageApiCountResponse(rsp *http.Response) (*V1GetProjectUsageApiCountResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/apps/cli-go/pkg/api/types.gen.go b/apps/cli-go/pkg/api/types.gen.go index 7f40e23798..16267c8dc4 100644 --- a/apps/cli-go/pkg/api/types.gen.go +++ b/apps/cli-go/pkg/api/types.gen.go @@ -5328,6 +5328,14 @@ type V1GetProjectLogsParams struct { IsoTimestampEnd *time.Time `form:"iso_timestamp_end,omitempty" json:"iso_timestamp_end,omitempty"` } +// V1GetProjectLogsAllParams defines parameters for V1GetProjectLogsAll. +type V1GetProjectLogsAllParams struct { + // Sql Custom SQL query to execute on the logs. See [querying logs](/docs/guides/telemetry/logs?queryGroups=product&product=postgres&queryGroups=source&source=edge_logs#querying-with-the-logs-explorer) for more details. + Sql *string `form:"sql,omitempty" json:"sql,omitempty"` + IsoTimestampStart *time.Time `form:"iso_timestamp_start,omitempty" json:"iso_timestamp_start,omitempty"` + IsoTimestampEnd *time.Time `form:"iso_timestamp_end,omitempty" json:"iso_timestamp_end,omitempty"` +} + // V1GetProjectUsageApiCountParams defines parameters for V1GetProjectUsageApiCount. type V1GetProjectUsageApiCountParams struct { Interval *V1GetProjectUsageApiCountParamsInterval `form:"interval,omitempty" json:"interval,omitempty"` From 2997addba065d43cfa49e2024dbfcb653da79c41 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 00:12:05 +0000 Subject: [PATCH 05/29] fix(deps): bump github.com/posthog/posthog-go from 1.15.0 to 1.15.1 in /apps/cli-go in the go-minor group across 1 directory (#5703) Bumps the go-minor group with 1 update in the /apps/cli-go directory: [github.com/posthog/posthog-go](https://github.com/posthog/posthog-go). Updates `github.com/posthog/posthog-go` from 1.15.0 to 1.15.1
Release notes

Sourced from github.com/posthog/posthog-go's releases.

1.15.1

Unreleased

Changelog

Sourced from github.com/posthog/posthog-go's changelog.

1.15.1

Patch Changes

  • 52b7373: Stop sending ignored top-level capture fields and rely on canonical event properties.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/posthog/posthog-go&package-manager=go_modules&previous-version=1.15.0&new-version=1.15.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/cli-go/go.mod | 2 +- apps/cli-go/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/cli-go/go.mod b/apps/cli-go/go.mod index 7eba170c0a..3da2229523 100644 --- a/apps/cli-go/go.mod +++ b/apps/cli-go/go.mod @@ -42,7 +42,7 @@ require ( github.com/multigres/multigres v0.0.0-20260126223308-f5a52171bbc4 github.com/oapi-codegen/nullable v1.1.0 github.com/olekukonko/tablewriter v1.1.4 - github.com/posthog/posthog-go v1.15.0 + github.com/posthog/posthog-go v1.15.1 github.com/spf13/afero v1.15.0 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 diff --git a/apps/cli-go/go.sum b/apps/cli-go/go.sum index 15f5a0f2f2..043956a8ad 100644 --- a/apps/cli-go/go.sum +++ b/apps/cli-go/go.sum @@ -941,8 +941,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polyfloyd/go-errorlint v1.8.0 h1:DL4RestQqRLr8U4LygLw8g2DX6RN1eBJOpa2mzsrl1Q= github.com/polyfloyd/go-errorlint v1.8.0/go.mod h1:G2W0Q5roxbLCt0ZQbdoxQxXktTjwNyDbEaj3n7jvl4s= -github.com/posthog/posthog-go v1.15.0 h1:Fizkdct7zGg050hnYpxEiq/iD/OJO7tVaQE9Vyoh0q0= -github.com/posthog/posthog-go v1.15.0/go.mod h1:xsVOW9YImilUcazwPNEq4PJDqEZf2KeCS758zXjwkPg= +github.com/posthog/posthog-go v1.15.1 h1:9rwjaEzyp5mss/fp0vYjLOhIaXCikfgRHRSzMMHgwJ8= +github.com/posthog/posthog-go v1.15.1/go.mod h1:xsVOW9YImilUcazwPNEq4PJDqEZf2KeCS758zXjwkPg= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= From bf6b315612806c86422cce225a2dfa82898f0655 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 00:12:14 +0000 Subject: [PATCH 06/29] fix(docker): bump supabase/storage-api from v1.61.3 to v1.61.4 in /apps/cli-go/pkg/config/templates in the docker-minor group (#5704) Bumps the docker-minor group in /apps/cli-go/pkg/config/templates with 1 update: supabase/storage-api. Updates `supabase/storage-api` from v1.61.3 to v1.61.4 [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=supabase/storage-api&package-manager=docker&previous-version=v1.61.3&new-version=v1.61.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/cli-go/pkg/config/templates/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/cli-go/pkg/config/templates/Dockerfile b/apps/cli-go/pkg/config/templates/Dockerfile index ebbc983688..4bb926ee09 100644 --- a/apps/cli-go/pkg/config/templates/Dockerfile +++ b/apps/cli-go/pkg/config/templates/Dockerfile @@ -12,7 +12,7 @@ FROM timberio/vector:0.53.0-alpine AS vector FROM supabase/supavisor:2.9.7 AS supavisor FROM supabase/gotrue:v2.191.0 AS gotrue FROM supabase/realtime:v2.108.0 AS realtime -FROM supabase/storage-api:v1.61.3 AS storage +FROM supabase/storage-api:v1.61.4 AS storage FROM supabase/logflare:1.45.4 AS logflare # Append to JobImages when adding new dependencies below FROM supabase/pgadmin-schema-diff:cli-0.0.5 AS differ From 1171646dd97a7d028d4d0a039c257d88c240e265 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 00:12:35 +0000 Subject: [PATCH 07/29] chore(ci): bump softprops/action-gh-release from 3.0.0 to 3.0.1 in the actions-major group (#5705) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the actions-major group with 1 update: [softprops/action-gh-release](https://github.com/softprops/action-gh-release). Updates `softprops/action-gh-release` from 3.0.0 to 3.0.1
Release notes

Sourced from softprops/action-gh-release's releases.

v3.0.1

3.0.1

  • maintenance release with updated dependencies
Changelog

Sourced from softprops/action-gh-release's changelog.

3.0.1

  • maintenance release with updated dependencies

3.0.0

3.0.0 is a major release that moves the action runtime from Node 20 to Node 24. Use v3 on GitHub-hosted runners and self-hosted fleets that already support the Node 24 Actions runtime. If you still need the last Node 20-compatible line, stay on v2.6.2.

What's Changed

Other Changes 🔄

  • Move the action runtime and bundle target to Node 24
  • Update @types/node to the Node 24 line and allow future Dependabot updates
  • Keep the floating major tag on v3; v2 remains pinned to the latest 2.x release

2.6.2

What's Changed

Other Changes 🔄

2.6.1

2.6.1 is a patch release focused on restoring linked discussion thread creation when discussion_category_name is set. It fixes [#764](https://github.com/softprops/action-gh-release/issues/764), where the draft-first publish flow stopped carrying the discussion category through the final publish step.

If you still hit an issue after upgrading, please open a report with the bug template and include a minimal repro or sanitized workflow snippet where possible.

What's Changed

Bug fixes 🐛

2.6.0

2.6.0 is a minor release centered on previous_tag support for generate_release_notes, which lets workflows pin GitHub's comparison base explicitly instead of relying on the default range. It also includes the recent concurrent asset upload recovery fix, a working_directory docs sync, a checked-bundle freshness guard for maintainers, and clearer immutable-prerelease guidance where GitHub platform behavior imposes constraints on how prerelease asset uploads can be published.

... (truncated)

Commits
  • 718ea10 release 3.0.1
  • f1a938b chore(deps): bump esbuild from 0.28.0 to 0.28.1 (#802)
  • 0066ead chore(deps): bump vite from 8.0.14 to 8.0.16 (#806)
  • dc643ca chore(deps): bump the npm group with 3 updates (#805)
  • 85ee99b chore(deps): bump actions/checkout in the github-actions group (#804)
  • 9ed3cf9 chore(deps): bump the npm group with 2 updates (#800)
  • 3efcac8 chore(deps): bump the npm group with 3 updates (#798)
  • 05d6b91 chore(deps): bump brace-expansion from 5.0.5 to 5.0.6 (#797)
  • 403a524 chore(deps): bump @​types/node from 24.12.2 to 24.12.3 in the npm group (#796)
  • 437e073 chore(deps): bump the npm group with 4 updates (#792)
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=softprops/action-gh-release&package-manager=github_actions&previous-version=3.0.0&new-version=3.0.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release-shared.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-shared.yml b/.github/workflows/release-shared.yml index 5a4577a695..79d6ba7387 100644 --- a/.github/workflows/release-shared.yml +++ b/.github/workflows/release-shared.yml @@ -382,7 +382,7 @@ jobs: done - name: Create draft GitHub Release - uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 + uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1 with: tag_name: v${{ inputs.version }} name: v${{ inputs.version }} From 28d20853c7046dffcbdd7f2a9f7dfa12dcffc640 Mon Sep 17 00:00:00 2001 From: "supabase-cli-releaser[bot]" <246109035+supabase-cli-releaser[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:45:16 +0200 Subject: [PATCH 08/29] chore(api): sync Management API OpenAPI spec (#5707) This PR was automatically created to sync the generated `@supabase/api` package with the latest Management API OpenAPI document. Changes were detected in the upstream OpenAPI document exposed by `https://api.supabase.com/api/v1-json`. Co-authored-by: jgoux <1443499+jgoux@users.noreply.github.com> --- packages/api/src/generated/contracts.ts | 64 +++++++++--- packages/api/src/generated/effect-client.ts | 18 ++++ packages/api/src/generated/openapi.json | 107 ++++++++++++++++++-- 3 files changed, 170 insertions(+), 19 deletions(-) diff --git a/packages/api/src/generated/contracts.ts b/packages/api/src/generated/contracts.ts index d43bc75d55..5a71e79a54 100644 --- a/packages/api/src/generated/contracts.ts +++ b/packages/api/src/generated/contracts.ts @@ -2421,11 +2421,7 @@ export const V1GetJitAccessConfigOutput = Schema.Union( }), Schema.Struct({ state: Schema.Literal("unavailable"), - unavailableReason: Schema.Literals([ - "manual_migration_required", - "postgres_upgrade_required", - "temporarily_unavailable", - ]), + unavailableReason: Schema.Literals(["postgres_upgrade_required", "temporarily_unavailable"]), }), ], { mode: "oneOf" }, @@ -3112,6 +3108,39 @@ export const V1GetProjectLogsOutput = Schema.Struct({ ), ), }); +export const V1GetProjectLogsAllInput = Schema.Struct({ + ref: Schema.String.check(Schema.isMinLength(20)) + .check(Schema.isMaxLength(20)) + .check(Schema.isPattern(new RegExp("^[a-z]+$"))), + sql: Schema.optionalKey(Schema.String), + iso_timestamp_start: Schema.optionalKey(Schema.String.annotate({ format: "date-time" })), + iso_timestamp_end: Schema.optionalKey(Schema.String.annotate({ format: "date-time" })), +}); +export const V1GetProjectLogsAllOutput = Schema.Struct({ + result: Schema.optionalKey(Schema.Array(Schema.Unknown)), + error: Schema.optionalKey( + Schema.Union( + [ + Schema.String, + Schema.Struct({ + code: Schema.Number.check(Schema.isFinite()), + errors: Schema.Array( + Schema.Struct({ + domain: Schema.String, + location: Schema.String, + locationType: Schema.String, + message: Schema.String, + reason: Schema.String, + }), + ), + message: Schema.String, + status: Schema.String, + }), + ], + { mode: "oneOf" }, + ), + ), +}); export const V1GetProjectPgbouncerConfigInput = Schema.Struct({ ref: Schema.String.check(Schema.isMinLength(20)) .check(Schema.isMaxLength(20)) @@ -5510,11 +5539,7 @@ export const V1UpdateJitAccessConfigOutput = Schema.Union( }), Schema.Struct({ state: Schema.Literal("unavailable"), - unavailableReason: Schema.Literals([ - "manual_migration_required", - "postgres_upgrade_required", - "temporarily_unavailable", - ]), + unavailableReason: Schema.Literals(["postgres_upgrade_required", "temporarily_unavailable"]), }), ], { mode: "oneOf" }, @@ -6151,6 +6176,7 @@ export const openApiOperationIdMap = { "v1-get-project-function-combined-stats": "v1GetProjectFunctionCombinedStats", "v1-get-project-legacy-api-keys": "v1GetProjectLegacyApiKeys", "v1-get-project-logs": "v1GetProjectLogs", + "v1-get-project-logs-all": "v1GetProjectLogsAll", "v1-get-project-pgbouncer-config": "v1GetProjectPgbouncerConfig", "v1-get-project-signing-key": "v1GetProjectSigningKey", "v1-get-project-signing-keys": "v1GetProjectSigningKeys", @@ -7456,9 +7482,9 @@ export const operationDefinitions = { v1GetProjectLogs: { id: "v1GetProjectLogs", description: - "Executes a SQL query on the project's logs.\n\nEither the `iso_timestamp_start` and `iso_timestamp_end` parameters must be provided.\nIf both are not provided, only the last 1 minute of logs will be queried.\nThe timestamp range must be no more than 24 hours and is rounded to the nearest minute. If the range is more than 24 hours, a validation error will be thrown.\n\nNote: Unless the `sql` parameter is provided, only edge_logs will be queried. See the [log query docs](/docs/guides/telemetry/logs?queryGroups=product&product=postgres&queryGroups=source&source=edge_logs#querying-with-the-logs-explorer:~:text=logs%20from%20the-,Sources,-drop%2Ddown%3A) for all available sources.", + "Executes an SQL or LQL query on the project's unified logs stream.\n\nEither the `iso_timestamp_start` and `iso_timestamp_end` parameters must be provided.\nIf both are not provided, only the last 1 minute of logs will be queried.\nThe timestamp range must be no more than 24 hours and is rounded to the nearest minute. If the range is more than 24 hours, a validation error will be thrown.\n\nFilter by the `source` column to specify specific log sources, such as edge_logs, postgres_logs, etc.\n\nNote: SQL must be written in **ClickHouse SQL dialect**.", method: "GET", - path: "/v1/projects/{ref}/analytics/endpoints/logs.all", + path: "/v1/projects/{ref}/analytics/endpoints/logs", pathParams: ["ref"], queryParams: ["sql", "iso_timestamp_start", "iso_timestamp_end"], headerParams: [], @@ -7467,6 +7493,20 @@ export const operationDefinitions = { inputSchema: V1GetProjectLogsInput, outputSchema: V1GetProjectLogsOutput, }, + v1GetProjectLogsAll: { + id: "v1GetProjectLogsAll", + description: + "Executes a SQL query on the project's logs.\n\nEither the `iso_timestamp_start` and `iso_timestamp_end` parameters must be provided.\nIf both are not provided, only the last 1 minute of logs will be queried.\nThe timestamp range must be no more than 24 hours and is rounded to the nearest minute. If the range is more than 24 hours, a validation error will be thrown.\n\nNote: Unless the `sql` parameter is provided, only edge_logs will be queried. See the [log query docs](/docs/guides/telemetry/logs?queryGroups=product&product=postgres&queryGroups=source&source=edge_logs#querying-with-the-logs-explorer:~:text=logs%20from%20the-,Sources,-drop%2Ddown%3A) for all available sources.", + method: "GET", + path: "/v1/projects/{ref}/analytics/endpoints/logs.all", + pathParams: ["ref"], + queryParams: ["sql", "iso_timestamp_start", "iso_timestamp_end"], + headerParams: [], + requestBody: { kind: "none" }, + response: { kind: "json" }, + inputSchema: V1GetProjectLogsAllInput, + outputSchema: V1GetProjectLogsAllOutput, + }, v1GetProjectPgbouncerConfig: { id: "v1GetProjectPgbouncerConfig", description: "Get project's pgbouncer config", diff --git a/packages/api/src/generated/effect-client.ts b/packages/api/src/generated/effect-client.ts index 9517c63ec8..43648171dd 100644 --- a/packages/api/src/generated/effect-client.ts +++ b/packages/api/src/generated/effect-client.ts @@ -1199,6 +1199,20 @@ export const versionedEffectOperations = { input, ); }), + getProjectLogsAll: ( + input: typeof operationDefinitions.v1GetProjectLogsAll.inputSchema.Type, + ): Effect.Effect< + typeof operationDefinitions.v1GetProjectLogsAll.outputSchema.Type, + SupabaseApiError, + SupabaseApiClient + > => + Effect.gen(function* () { + const client = yield* SupabaseApiClient; + return yield* client.execute<"v1GetProjectLogsAll">( + operationDefinitions.v1GetProjectLogsAll, + input, + ); + }), getProjectPgbouncerConfig: ( input: typeof operationDefinitions.v1GetProjectPgbouncerConfig.inputSchema.Type, ): Effect.Effect< @@ -2671,6 +2685,10 @@ export function executeApiClientOperation( return Schema.decodeUnknownEffect(operationDefinitions.v1GetProjectLogs.inputSchema)( input, ).pipe(Effect.flatMap((decoded) => api.v1.getProjectLogs(decoded))); + case "v1GetProjectLogsAll": + return Schema.decodeUnknownEffect(operationDefinitions.v1GetProjectLogsAll.inputSchema)( + input, + ).pipe(Effect.flatMap((decoded) => api.v1.getProjectLogsAll(decoded))); case "v1GetProjectPgbouncerConfig": return Schema.decodeUnknownEffect( operationDefinitions.v1GetProjectPgbouncerConfig.inputSchema, diff --git a/packages/api/src/generated/openapi.json b/packages/api/src/generated/openapi.json index 0be9013f5f..46a45efcd9 100644 --- a/packages/api/src/generated/openapi.json +++ b/packages/api/src/generated/openapi.json @@ -6671,8 +6671,9 @@ }, "/v1/projects/{ref}/analytics/endpoints/logs.all": { "get": { - "description": "Executes a SQL query on the project's logs.\n\nEither the `iso_timestamp_start` and `iso_timestamp_end` parameters must be provided.\nIf both are not provided, only the last 1 minute of logs will be queried.\nThe timestamp range must be no more than 24 hours and is rounded to the nearest minute. If the range is more than 24 hours, a validation error will be thrown.\n\nNote: Unless the `sql` parameter is provided, only edge_logs will be queried. See the [log query docs](/docs/guides/telemetry/logs?queryGroups=product&product=postgres&queryGroups=source&source=edge_logs#querying-with-the-logs-explorer:~:text=logs%20from%20the-,Sources,-drop%2Ddown%3A) for all available sources. \n", - "operationId": "v1-get-project-logs", + "deprecated": true, + "description": "Executes a SQL query on the project's logs.\n\nEither the `iso_timestamp_start` and `iso_timestamp_end` parameters must be provided.\nIf both are not provided, only the last 1 minute of logs will be queried.\nThe timestamp range must be no more than 24 hours and is rounded to the nearest minute. If the range is more than 24 hours, a validation error will be thrown.\n\nNote: Unless the `sql` parameter is provided, only edge_logs will be queried. See the [log query docs](/docs/guides/telemetry/logs?queryGroups=product&product=postgres&queryGroups=source&source=edge_logs#querying-with-the-logs-explorer:~:text=logs%20from%20the-,Sources,-drop%2Ddown%3A) for all available sources.\n", + "operationId": "v1-get-project-logs-all", "parameters": [ { "name": "ref", @@ -6731,6 +6732,9 @@ "401": { "description": "Unauthorized" }, + "402": { + "description": "Usage exceeded. Enable additional usage to continue querying" + }, "403": { "description": "Forbidden action" }, @@ -6758,6 +6762,99 @@ "x-oauth-scope": "analytics:read" } }, + "/v1/projects/{ref}/analytics/endpoints/logs": { + "get": { + "deprecated": false, + "description": "Executes an SQL or LQL query on the project's unified logs stream.\n\nEither the `iso_timestamp_start` and `iso_timestamp_end` parameters must be provided.\nIf both are not provided, only the last 1 minute of logs will be queried.\nThe timestamp range must be no more than 24 hours and is rounded to the nearest minute. If the range is more than 24 hours, a validation error will be thrown.\n\nFilter by the `source` column to specify specific log sources, such as edge_logs, postgres_logs, etc.\n\nNote: SQL must be written in **ClickHouse SQL dialect**.\n", + "operationId": "v1-get-project-logs", + "parameters": [ + { + "name": "ref", + "required": true, + "in": "path", + "description": "Project ref", + "schema": { + "minLength": 20, + "maxLength": 20, + "pattern": "^[a-z]+$", + "example": "abcdefghijklmnopqrst", + "type": "string" + } + }, + { + "name": "sql", + "required": false, + "in": "query", + "description": "Custom SQL query to execute on the logs. See [querying logs](/docs/guides/telemetry/logs?queryGroups=product&product=postgres&queryGroups=source&source=edge_logs#querying-with-the-logs-explorer) for more details.", + "schema": { + "type": "string" + } + }, + { + "name": "iso_timestamp_start", + "required": false, + "in": "query", + "schema": { + "format": "date-time", + "example": "2025-03-01T00:00:00Z", + "type": "string" + } + }, + { + "name": "iso_timestamp_end", + "required": false, + "in": "query", + "schema": { + "format": "date-time", + "example": "2025-03-01T23:59:59Z", + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AnalyticsResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "402": { + "description": "Usage exceeded. Enable additional usage to continue querying" + }, + "403": { + "description": "Forbidden action" + }, + "429": { + "description": "Rate limit exceeded" + } + }, + "security": [ + { + "bearer": [] + }, + { + "fga_permissions": ["analytics_logs_read"] + } + ], + "summary": "Gets all project's logs in a single log stream", + "tags": ["Analytics"], + "x-badges": [ + { + "name": "OAuth scope: analytics:read", + "position": "after" + } + ], + "x-endpoint-owners": ["analytics"], + "x-oauth-scope": "analytics:read" + } + }, "/v1/projects/{ref}/analytics/endpoints/usage.api-counts": { "get": { "operationId": "v1-get-project-usage-api-count", @@ -12921,11 +13018,7 @@ }, "unavailableReason": { "type": "string", - "enum": [ - "manual_migration_required", - "postgres_upgrade_required", - "temporarily_unavailable" - ] + "enum": ["postgres_upgrade_required", "temporarily_unavailable"] } }, "required": ["state", "unavailableReason"] From a4a253040bd5c168875537749d23f8d9baa0ecdc Mon Sep 17 00:00:00 2001 From: Andrew Valleteau Date: Fri, 26 Jun 2026 12:04:19 +0200 Subject: [PATCH 09/29] test(cli): supabox-backed live test suite + cli-e2e-ci dispatch (#5699) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a `live` test category that exercises the built CLI against a **real Supabase platform** — a fulls supabox stack stood up in CI by cli-e2e-ci — and the cli-side trigger that runs it for a PR. ## What changed - **`live` Vitest project** (`*.live.test.ts`) + harness in `apps/cli/tests/helpers/live.ts`: - `runSupabaseLive` (drives the built binary via `SUPABASE_PROFILE=supabase-local`), `describeLive` (gated on `SUPABASE_ACCESS_TOKEN`), and `describeLiveProject` / `requireLiveProjectRef` (gated on `SUPABASE_LIVE_PROJECT_REF` for project-scoped suites). - `tests/live-global-setup.ts` fail-fast reachability probe. - nx `test:live` target (auto-derived from the project) + `nx.json` default; **removed** the recursive `test:live` package script (it shadowed the nx target and looped). - **8 live scenarios**: `orgs list` (+JSON, +invalid-token negative), `projects list` (+JSON), `functions list` / `branches list` (project-scoped), and a `functions list` unknown-project (404) negative. - **`.github/workflows/dispatch-cli-e2e-ci.yml`**: on PRs labeled `run-live-e2e-ci`, fires a `repository_dispatch` to cli-e2e-ci with the PR head SHA; cli-e2e-ci builds that SHA, runs the suite, and reports a `cli-e2e-ci / live` commit status back. Distinct from the staging `live-e2e.yml`. ## Why There was no backend-hitting live coverage — existing `*.e2e.test.ts` use fake tokens. This validates real Management-API flows end-to-end against supabox. Refs CLI-1825 / CLI-1834 / CLI-1831. ## Reviewer notes - The `live` project is **inert by default** — it only runs when `SUPABASE_ACCESS_TOKEN` is set (the cli-e2e-ci runner provides supabox's seeded PAT), so it does not touch the normal unit/integration/e2e loop. - Validated green in cli-e2e-ci on Blacksmith: **8 passed, 0 skipped**, including the project-scoped suites (the runner provisions a project and sets `SUPABASE_LIVE_PROJECT_REF`). - The dispatch loop needs, on the infra side: a `run-live-e2e-ci` label on this repo, the App's `contents: write` on cli-e2e-ci (to dispatch) and `statuses: write` here (for the back-status). The build/test half is proven without them. --------- Co-authored-by: Claude --- .github/workflows/dispatch-cli-e2e-ci.yml | 57 +++++++++++++++ apps/cli/package.json | 3 +- .../commands/branches/list/list.live.test.ts | 30 ++++++++ .../commands/functions/list/list.live.test.ts | 52 +++++++++++++ .../commands/orgs/list/list.live.test.ts | 52 +++++++++++++ .../commands/projects/list/list.live.test.ts | 33 +++++++++ apps/cli/tests/helpers/live-env.ts | 73 +++++++++++++++++++ apps/cli/tests/helpers/live.ts | 66 +++++++++++++++++ apps/cli/tests/live-global-setup.ts | 39 ++++++++++ apps/cli/vitest.config.ts | 16 ++++ nx.json | 11 +++ 11 files changed, 431 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/dispatch-cli-e2e-ci.yml create mode 100644 apps/cli/src/legacy/commands/branches/list/list.live.test.ts create mode 100644 apps/cli/src/legacy/commands/functions/list/list.live.test.ts create mode 100644 apps/cli/src/legacy/commands/orgs/list/list.live.test.ts create mode 100644 apps/cli/src/legacy/commands/projects/list/list.live.test.ts create mode 100644 apps/cli/tests/helpers/live-env.ts create mode 100644 apps/cli/tests/helpers/live.ts create mode 100644 apps/cli/tests/live-global-setup.ts diff --git a/.github/workflows/dispatch-cli-e2e-ci.yml b/.github/workflows/dispatch-cli-e2e-ci.yml new file mode 100644 index 0000000000..4dfecad569 --- /dev/null +++ b/.github/workflows/dispatch-cli-e2e-ci.yml @@ -0,0 +1,57 @@ +name: Dispatch cli-e2e-ci + +# Asks the supabase/cli-e2e-ci harness to run the cli `test:live` suite against +# a full supabox stack, built from THIS PR's head commit (CLI-1825 / CLI-1831). +# +# This is distinct from `live-e2e.yml`, which runs the cli-e2e package against +# real staging (api.supabase.green). Here the suite runs against a local supabox +# stack stood up inside the private cli-e2e-ci repo; we only fire the trigger and +# pass our head SHA — cli-e2e-ci checks that SHA out into its `cli` submodule. +# +# Opt-in by label to keep the expensive full-stack run off every PR: add the +# `run-live-e2e-ci` label (re-dispatches on each subsequent push while labeled). +# cli-e2e-ci reports a `cli-e2e-ci / live` commit status back onto the head SHA. +# +# Fork PRs cannot dispatch (no access to the App secret); run cli-e2e-ci's own +# workflow_dispatch with `cli_ref` for those. +on: + pull_request: + types: [labeled, synchronize, reopened] + +permissions: + contents: read + +jobs: + dispatch: + # Same-repo PRs only: fork PRs don't receive secrets (GH_APP_PRIVATE_KEY), so + # the App-token step would fail and leave a red check. Skip them cleanly — + # fork PRs use cli-e2e-ci's workflow_dispatch with `cli_ref` instead. + if: >- + contains(github.event.pull_request.labels.*.name, 'run-live-e2e-ci') + && github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + steps: + # App token scoped to cli-e2e-ci with contents:write — the + # repository_dispatch REST endpoint requires write on the target repo. + - name: Create GitHub App token + id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.GH_APP_CLIENT_ID }} + private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} + owner: supabase + repositories: cli-e2e-ci + permission-contents: write + + - name: Dispatch live run to cli-e2e-ci + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + CLI_SHA: ${{ github.event.pull_request.head.sha }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + echo "Dispatching cli-e2e-ci live run for PR #${PR_NUMBER} @ ${CLI_SHA}" + # Build the nested client_payload with jq — `gh api -f` sends a flat + # body and would not nest `client_payload.*` correctly. + jq -n --arg sha "$CLI_SHA" --argjson pr "$PR_NUMBER" \ + '{event_type: "cli-pr", client_payload: {cli_sha: $sha, pr_number: $pr}}' \ + | gh api -X POST repos/supabase/cli-e2e-ci/dispatches --input - diff --git a/apps/cli/package.json b/apps/cli/package.json index ac72985d2d..019c783f62 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -112,7 +112,8 @@ "entry": [ "src/shared/cli/bin.ts", "src/**/*.test.ts", - "src/**/*.e2e.test.ts" + "src/**/*.e2e.test.ts", + "src/**/*.live.test.ts" ], "ignore": [ "scripts/*.ts", diff --git a/apps/cli/src/legacy/commands/branches/list/list.live.test.ts b/apps/cli/src/legacy/commands/branches/list/list.live.test.ts new file mode 100644 index 0000000000..65422ad51b --- /dev/null +++ b/apps/cli/src/legacy/commands/branches/list/list.live.test.ts @@ -0,0 +1,30 @@ +import { expect, test } from "vitest"; + +import { + describeLiveProject, + requireLiveProjectRef, + runSupabaseLive, +} from "../../../../../tests/helpers/live.ts"; + +const LIVE_TIMEOUT_MS = 120_000; + +// Project-scoped read-only scenario. Skipped unless SUPABASE_LIVE_PROJECT_REF is +// set — i.e. a project has been provisioned on the stack (the cli-e2e-ci runner +// does this; a control-plane-only stack, like local macOS, skips it). +// +// Entry point for the branching lifecycle tracked in CLI-1834 +// (create / switch / delete) — extend here once a provisioned project is +// available on the full stack. +describeLiveProject("supabase branches list (live)", () => { + test("lists branches for the project", { timeout: LIVE_TIMEOUT_MS }, async () => { + const ref = requireLiveProjectRef(); + const { exitCode, stdout, stderr } = await runSupabaseLive([ + "branches", + "list", + "--project-ref", + ref, + ]); + expect(`${stdout}${stderr}`).not.toContain("Unauthorized"); + expect(exitCode).toBe(0); + }); +}); diff --git a/apps/cli/src/legacy/commands/functions/list/list.live.test.ts b/apps/cli/src/legacy/commands/functions/list/list.live.test.ts new file mode 100644 index 0000000000..2bfa93b86f --- /dev/null +++ b/apps/cli/src/legacy/commands/functions/list/list.live.test.ts @@ -0,0 +1,52 @@ +import { expect, test } from "vitest"; + +import { + describeLive, + describeLiveProject, + requireLiveProjectRef, + runSupabaseLive, +} from "../../../../../tests/helpers/live.ts"; + +const LIVE_TIMEOUT_MS = 120_000; + +// Project-scoped read-only scenario. Skipped unless SUPABASE_LIVE_PROJECT_REF is +// set — i.e. a project has been provisioned on the stack (the cli-e2e-ci runner +// does this; a control-plane-only stack, like local macOS, skips it). +// +// This is the entry point for the broader edge-functions coverage tracked in +// CLI-1834 (deploy + invoke over :443 / {ref}.supabase.red), which needs the +// project's gateway reachable from the host — author those here as they become +// runnable on the full stack. +describeLiveProject("supabase functions list (live)", () => { + test("lists edge functions for the project", { timeout: LIVE_TIMEOUT_MS }, async () => { + const ref = requireLiveProjectRef(); + const { exitCode, stdout, stderr } = await runSupabaseLive([ + "functions", + "list", + "--project-ref", + ref, + ]); + expect(`${stdout}${stderr}`).not.toContain("Unauthorized"); + expect(exitCode).toBe(0); + }); +}); + +// Project-scoped error path that needs NO provisioned project: a valid token +// with an unknown `--project-ref` must reach the live Management API, come back +// 404, and surface as a non-zero exit (not a crash, not "Unauthorized"). This +// exercises the `--project-ref` request path + error mapping on a control-plane- +// only stack, so it runs under `describeLive`, not `describeLiveProject`. +describeLive("supabase functions list — unknown project (live)", () => { + test("fails with a 404 for an unknown project ref", { timeout: LIVE_TIMEOUT_MS }, async () => { + const { exitCode, stdout, stderr } = await runSupabaseLive([ + "functions", + "list", + "--project-ref", + "a".repeat(20), // well-formed (20 lowercase chars) but nonexistent ref + ]); + const out = `${stdout}${stderr}`; + expect(exitCode).not.toBe(0); + expect(out).not.toContain("Unauthorized"); + expect(out).toContain("404"); + }); +}); diff --git a/apps/cli/src/legacy/commands/orgs/list/list.live.test.ts b/apps/cli/src/legacy/commands/orgs/list/list.live.test.ts new file mode 100644 index 0000000000..515e8a855d --- /dev/null +++ b/apps/cli/src/legacy/commands/orgs/list/list.live.test.ts @@ -0,0 +1,52 @@ +import { expect, test } from "vitest"; +import { describeLive, runSupabaseLive } from "../../../../../tests/helpers/live.ts"; + +const LIVE_TIMEOUT_MS = 60_000; + +// Harness smoke for the `live` Vitest project: the canonical example of a live +// test. It exercises the full path — built binary → SUPABASE_PROFILE resolution +// → authenticated Management API request against the running platform — with a +// read-only call, so it is safe to run repeatedly and creates no resources. +// +// Gated by `describeLive`: skipped unless SUPABASE_ACCESS_TOKEN is set (the +// cli-e2e-ci runner provides supabox's seeded PAT). Broader lifecycle scenarios +// (projects, functions, branching, db, storage) build on this same harness. +describeLive("supabase orgs list (live)", () => { + test( + "lists organizations for the authenticated token", + { timeout: LIVE_TIMEOUT_MS }, + async () => { + const { exitCode, stdout, stderr } = await runSupabaseLive(["orgs", "list"]); + expect(`${stdout}${stderr}`).not.toContain("Unauthorized"); + expect(exitCode).toBe(0); + }, + ); + + test( + "emits machine-readable JSON with --output-format json", + { timeout: LIVE_TIMEOUT_MS }, + async () => { + const { exitCode, stdout } = await runSupabaseLive([ + "orgs", + "list", + "--output-format", + "json", + ]); + expect(exitCode).toBe(0); + // stdout must be payload-only valid JSON in json mode (no spinner/log noise). + expect(() => JSON.parse(stdout)).not.toThrow(); + }, + ); + + // Negative path: a bad token must round-trip to the real Management API, come + // back 401, and surface as a non-zero exit with the upstream "Unauthorized" + // message — i.e. the cli's auth + error mapping work against the live stack, + // not just the golden path. Overrides only the token (profile stays set). + test("fails with Unauthorized for an invalid token", { timeout: LIVE_TIMEOUT_MS }, async () => { + const { exitCode, stdout, stderr } = await runSupabaseLive(["orgs", "list"], { + env: { SUPABASE_ACCESS_TOKEN: `sbp_${"0".repeat(40)}` }, + }); + expect(exitCode).not.toBe(0); + expect(`${stdout}${stderr}`).toContain("Unauthorized"); + }); +}); diff --git a/apps/cli/src/legacy/commands/projects/list/list.live.test.ts b/apps/cli/src/legacy/commands/projects/list/list.live.test.ts new file mode 100644 index 0000000000..8c4ca20f33 --- /dev/null +++ b/apps/cli/src/legacy/commands/projects/list/list.live.test.ts @@ -0,0 +1,33 @@ +import { expect, test } from "vitest"; + +import { describeLive, runSupabaseLive } from "../../../../../tests/helpers/live.ts"; + +const LIVE_TIMEOUT_MS = 60_000; + +// Account-level read-only live scenario, alongside `orgs list`. Lists every +// project the authenticated token can access — no project ref required, so it +// runs against just the control plane (no provisioned project instance needed). +// Safe to run repeatedly; creates nothing. +describeLive("supabase projects list (live)", () => { + test("lists projects for the authenticated token", { timeout: LIVE_TIMEOUT_MS }, async () => { + const { exitCode, stdout, stderr } = await runSupabaseLive(["projects", "list"]); + expect(`${stdout}${stderr}`).not.toContain("Unauthorized"); + expect(exitCode).toBe(0); + }); + + test( + "emits machine-readable JSON with --output-format json", + { timeout: LIVE_TIMEOUT_MS }, + async () => { + const { exitCode, stdout } = await runSupabaseLive([ + "projects", + "list", + "--output-format", + "json", + ]); + expect(exitCode).toBe(0); + // stdout must be payload-only valid JSON in json mode (no spinner/log noise). + expect(() => JSON.parse(stdout)).not.toThrow(); + }, + ); +}); diff --git a/apps/cli/tests/helpers/live-env.ts b/apps/cli/tests/helpers/live-env.ts new file mode 100644 index 0000000000..f6705d71e0 --- /dev/null +++ b/apps/cli/tests/helpers/live-env.ts @@ -0,0 +1,73 @@ +/** + * Environment-only helpers for the `live` Vitest project, with **no Vitest test + * APIs imported**. Vitest evaluates `globalSetup` (live-global-setup.ts) in a + * separate context before the test workers, where importing `describe`/`test` + * is not valid — so the global setup imports the env helpers from here, while + * the test-facing pieces (`describeLive`, `runSupabaseLive`, …) live in + * `live.ts` and re-export these. + * + * Environment contract (provided by the cli-e2e-ci runner): + * - `SUPABASE_ACCESS_TOKEN` — required; the platform PAT (supabox seeds a + * deterministic `sbp_…` token into its mgmt-api database). + * - `SUPABASE_PROFILE` — selects the API base URL; defaults to `supabase-local` + * (→ `http://localhost:8080`, `project_host: supabase.red`). Note the cli does + * NOT honor `SUPABASE_API_URL` (Go parity) — the profile is the override. + * - `SUPABASE_LIVE_API_URL` — base URL the readiness check probes; defaults to + * `http://localhost:8080`. + * - `SUPABASE_LIVE_PROJECT_REF` — a provisioned project; gates project-scoped + * suites (functions, branches, db, storage). + * - `NODE_EXTRA_CA_CERTS` — trusts the supabox CA for `*.supabase.red` TLS; + * inherited by the subprocess via the parent environment. + */ + +/** Default profile for the host runner: api_url → localhost:8080, project_host → supabase.red. */ +export const LIVE_DEFAULT_PROFILE = "supabase-local"; + +/** + * Default subprocess exit timeout for live runs. `runSupabase` otherwise caps at + * 60s, which would kill a slow-but-valid supabox call before the live tests' + * own (60–120s+) timeouts fire. Generous, but under the `live` project's 300s + * cap so the per-test timeout stays the real gate. Callers may override. + */ +export const LIVE_EXIT_TIMEOUT_MS = 240_000; + +/** Management API base URL probed by the live readiness check. */ +export function liveApiBaseUrl(): string { + return process.env["SUPABASE_LIVE_API_URL"] ?? "http://localhost:8080"; +} + +/** + * True when the environment carries a platform access token, i.e. the live + * suite is expected to run. Used to gate `describeLive` so live tests are inert + * in the default test loop. + */ +export function isLiveConfigured(): boolean { + return Boolean(process.env["SUPABASE_ACCESS_TOKEN"]); +} + +/** + * Project ref for project-scoped live scenarios (functions, branches, db, + * storage, …). The cli-e2e-ci runner sets this once a project has been + * provisioned on the stack; absent → those suites skip. Returns `undefined` + * when unset so callers can branch; use `requireLiveProjectRef` inside a + * `describeLiveProject` block where presence is already guaranteed. + */ +export function liveProjectRef(): string | undefined { + return process.env["SUPABASE_LIVE_PROJECT_REF"]; +} + +/** + * The live project ref, or a thrown error if unset. Safe to call inside a + * `describeLiveProject` block (the gate guarantees it is present) and gives a + * typed `string` without a non-null assertion. + */ +export function requireLiveProjectRef(): string { + const ref = liveProjectRef(); + if (!ref) { + throw new Error( + "SUPABASE_LIVE_PROJECT_REF must be set for project-scoped live tests " + + "(the cli-e2e-ci runner sets it after provisioning a project).", + ); + } + return ref; +} diff --git a/apps/cli/tests/helpers/live.ts b/apps/cli/tests/helpers/live.ts new file mode 100644 index 0000000000..78d2558190 --- /dev/null +++ b/apps/cli/tests/helpers/live.ts @@ -0,0 +1,66 @@ +import { describe } from "vitest"; + +import { runSupabase } from "./cli.ts"; +import { + isLiveConfigured, + LIVE_DEFAULT_PROFILE, + LIVE_EXIT_TIMEOUT_MS, + liveProjectRef, +} from "./live-env.ts"; + +/** + * Test-facing helpers for the `live` Vitest project (`*.live.test.ts`): + * black-box CLI subprocess tests that run against a *real* Supabase platform — + * in CI a local supabox stack (see the `supabase/cli-e2e-ci` harness). + * + * This module imports Vitest test APIs (`describe`), so it must NOT be imported + * from `globalSetup` (Vitest evaluates that in a different context). The + * env-only helpers live in `./live-env.ts`; `globalSetup` imports from there. + * They are re-exported below so test files have a single import site. + */ + +// Re-export the env-only helpers so `*.live.test.ts` files import everything +// from `helpers/live.ts`. +export { + isLiveConfigured, + LIVE_DEFAULT_PROFILE, + LIVE_EXIT_TIMEOUT_MS, + liveApiBaseUrl, + liveProjectRef, + requireLiveProjectRef, +} from "./live-env.ts"; + +/** + * `describe` that runs only when the live environment is configured. Use this + * for every live suite so the file is inert (skipped, not failed) outside the + * cli-e2e-ci runner. + */ +export const describeLive = describe.skipIf(!isLiveConfigured()); + +/** + * `describe` for project-scoped live suites: runs only when the live env is + * configured AND a project ref is available. On a control-plane-only stack + * (e.g. local macOS where project instances can't be built) these skip rather + * than fail. See `requireLiveProjectRef`. + */ +export const describeLiveProject = describe.skipIf(!isLiveConfigured() || !liveProjectRef()); + +/** + * Spawn the built CLI against the live platform, injecting the profile so the + * Management API base resolves to the stack. Defaults to the `legacy` shell, + * which hosts the platform commands (orgs, projects, branches, functions, …). + */ +export function runSupabaseLive( + args: string[], + options?: Parameters[1], +): ReturnType { + return runSupabase(args, { + entrypoint: "legacy", + ...options, + exitTimeoutMs: options?.exitTimeoutMs ?? LIVE_EXIT_TIMEOUT_MS, + env: { + SUPABASE_PROFILE: process.env["SUPABASE_PROFILE"] ?? LIVE_DEFAULT_PROFILE, + ...options?.env, + }, + }); +} diff --git a/apps/cli/tests/live-global-setup.ts b/apps/cli/tests/live-global-setup.ts new file mode 100644 index 0000000000..d7584e757a --- /dev/null +++ b/apps/cli/tests/live-global-setup.ts @@ -0,0 +1,39 @@ +// Import from the Vitest-free env module — globalSetup runs in a context where +// importing Vitest test APIs (which `helpers/live.ts` pulls in) is not valid. +import { isLiveConfigured, liveApiBaseUrl } from "./helpers/live-env.ts"; + +/** + * Global setup for the `live` Vitest project. When the live environment is not + * configured the suite is skipped (via `describeLive`) and this is a no-op. + * + * When it IS configured (the cli-e2e-ci runner sets `SUPABASE_ACCESS_TOKEN`), + * fail fast with a clear message if the platform is unreachable, so a + * misconfigured stack surfaces as a setup error rather than dozens of opaque + * per-test timeouts. + */ +export async function setup(): Promise { + if (!isLiveConfigured()) { + return; + } + + // Reachability gate only. Any HTTP response — including 401/404 — proves the + // Management API is up and routing, which is all this probe needs to assert. + // supabox's mgmt-api requires auth on every route and exposes no public health + // endpoint (`/v1/health` 404s; an unauthenticated request is rejected by the + // auth middleware with 401), so we deliberately do NOT require a 2xx here. + // Functional and auth coverage is the live tests' job (e.g. `orgs list`). + const probeUrl = `${liveApiBaseUrl()}/v1/organizations`; + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 30_000); + try { + await fetch(probeUrl, { signal: controller.signal }); + } catch (error) { + const reason = error instanceof Error ? error.message : String(error); + throw new Error( + `Live platform is not reachable at ${probeUrl}: ${reason}.\n` + + "Ensure the supabox stack is up and the host can reach mgmt-api (see cli-e2e-ci).", + ); + } finally { + clearTimeout(timeout); + } +} diff --git a/apps/cli/vitest.config.ts b/apps/cli/vitest.config.ts index 412bff1fc1..a8c53dd4e5 100644 --- a/apps/cli/vitest.config.ts +++ b/apps/cli/vitest.config.ts @@ -31,6 +31,7 @@ export default defineConfig({ "**/*.unit.test.ts", "**/*.integration.test.ts", "**/*.e2e.test.ts", + "**/*.live.test.ts", "**/*.command.ts", "src/app.ts", "src/bin.ts", @@ -66,6 +67,21 @@ export default defineConfig({ hookTimeout: 120_000, }, }, + { + plugins: [dockerfileTextPlugin()], + test: { + // Live tests run against a real platform (a supabox stack in CI) and + // are gated by `describeLive`, so they are inert unless the live env + // is configured. Never part of the default unit/integration/e2e loop. + name: "live", + include: ["**/*.live.test.ts"], + fileParallelism: false, + maxWorkers: 1, + globalSetup: ["tests/live-global-setup.ts"], + testTimeout: 300_000, + hookTimeout: 300_000, + }, + }, ], }, }); diff --git a/nx.json b/nx.json index 7a0dabb5fe..128d1740b7 100644 --- a/nx.json +++ b/nx.json @@ -75,6 +75,17 @@ "{projectRoot}/dist/**/*" ] }, + "test:live": { + "parallelism": false, + "cache": false, + "dependsOn": [ + "build" + ], + "inputs": [ + "default", + "{projectRoot}/dist/**/*" + ] + }, "dev": { "cache": false } From 2abc6f00344570a34f90ace37d8eb797a874d557 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 26 Jun 2026 08:38:10 -0400 Subject: [PATCH 10/29] fix(realtime): unblock and bump realtime to v2.111.8 (#5701) ## What kind of change does this PR introduce? - Revert https://github.com/supabase/cli/pull/5628 fixed by https://github.com/supabase/realtime/pull/1984 - Sync template/tests to latest Realtime version ## What is the current behavior? Realtime update blocked. Co-authored-by: Andrew Valleteau --- .github/dependabot.yml | 6 ------ apps/cli-go/pkg/config/templates/Dockerfile | 2 +- .../src/next/commands/list/list.integration.test.ts | 2 +- .../next/commands/start/start.integration.test.ts | 12 ++++++------ .../next/commands/update/update.integration.test.ts | 2 +- packages/stack/src/StackBuilder.unit.test.ts | 4 ++-- packages/stack/src/versions.ts | 2 +- 7 files changed, 12 insertions(+), 18 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 255d864933..7aa08a043b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -65,12 +65,6 @@ updates: - dependency-name: "axllent/mailpit" - dependency-name: "darthsim/imgproxy" - dependency-name: "timberio/vector" - # Held back: v2.109.0+ adds a setup_supabase_realtime_admin migration - # that fails against the CLI's local Postgres and breaks `supabase start`. - # Remove once the CLI's local stack is compatible with the new migration. - - dependency-name: "supabase/realtime" - versions: - - ">= 2.109.0" cooldown: default-days: 7 exclude: diff --git a/apps/cli-go/pkg/config/templates/Dockerfile b/apps/cli-go/pkg/config/templates/Dockerfile index 4bb926ee09..47c7f6a17e 100644 --- a/apps/cli-go/pkg/config/templates/Dockerfile +++ b/apps/cli-go/pkg/config/templates/Dockerfile @@ -11,7 +11,7 @@ FROM supabase/edge-runtime:v1.74.2 AS edgeruntime FROM timberio/vector:0.53.0-alpine AS vector FROM supabase/supavisor:2.9.7 AS supavisor FROM supabase/gotrue:v2.191.0 AS gotrue -FROM supabase/realtime:v2.108.0 AS realtime +FROM supabase/realtime:v2.111.8 AS realtime FROM supabase/storage-api:v1.61.4 AS storage FROM supabase/logflare:1.45.4 AS logflare # Append to JobImages when adding new dependencies below diff --git a/apps/cli/src/next/commands/list/list.integration.test.ts b/apps/cli/src/next/commands/list/list.integration.test.ts index 1e95fe5eb4..52819a17c9 100644 --- a/apps/cli/src/next/commands/list/list.integration.test.ts +++ b/apps/cli/src/next/commands/list/list.integration.test.ts @@ -39,7 +39,7 @@ function writeStackMetadata(stackDir: string, apiPort: number, dbPort: number) { postgrest: "14.5", auth: "2.188.0-rc.15", "edge-runtime": DEFAULT_VERSIONS["edge-runtime"], - realtime: "2.78.10", + realtime: "2.111.8", storage: "1.41.8", imgproxy: "v3.8.0", mailpit: "v1.30.2", diff --git a/apps/cli/src/next/commands/start/start.integration.test.ts b/apps/cli/src/next/commands/start/start.integration.test.ts index f0c7f83ed3..7179a25000 100644 --- a/apps/cli/src/next/commands/start/start.integration.test.ts +++ b/apps/cli/src/next/commands/start/start.integration.test.ts @@ -68,7 +68,7 @@ function mockStartVersionState( postgrest: "14.5", auth: "2.188.0-rc.15", "edge-runtime": DEFAULT_VERSIONS["edge-runtime"], - realtime: "2.78.10", + realtime: "2.111.8", storage: "1.41.8", imgproxy: "v3.8.0", mailpit: "v1.30.2", @@ -86,7 +86,7 @@ function mockStartVersionState( postgrest: "14.5", auth: "2.188.0-rc.15", "edge-runtime": DEFAULT_VERSIONS["edge-runtime"], - realtime: "2.78.10", + realtime: "2.111.8", storage: "1.41.8", imgproxy: "v3.8.0", mailpit: "v1.30.2", @@ -101,7 +101,7 @@ function mockStartVersionState( postgrest: "14.5", auth: "2.188.0-rc.15", "edge-runtime": DEFAULT_VERSIONS["edge-runtime"], - realtime: "2.78.10", + realtime: "2.111.8", storage: "1.41.8", imgproxy: "v3.8.0", mailpit: "v1.30.2", @@ -116,7 +116,7 @@ function mockStartVersionState( postgrest: "14.5", auth: "2.188.0-rc.15", "edge-runtime": DEFAULT_VERSIONS["edge-runtime"], - realtime: "2.78.10", + realtime: "2.111.8", storage: "1.41.8", imgproxy: "v3.8.0", mailpit: "v1.30.2", @@ -370,7 +370,7 @@ describe("start", () => { postgrest: "14.5", auth: "2.187.0", "edge-runtime": DEFAULT_VERSIONS["edge-runtime"], - realtime: "2.78.10", + realtime: "2.111.8", storage: "1.41.8", imgproxy: "v3.8.0", mailpit: "v1.30.2", @@ -423,7 +423,7 @@ describe("start", () => { postgrest: "14.5", auth: "2.187.0", "edge-runtime": DEFAULT_VERSIONS["edge-runtime"], - realtime: "2.78.10", + realtime: "2.111.8", storage: "1.41.8", imgproxy: "v3.8.0", mailpit: "v1.30.2", diff --git a/apps/cli/src/next/commands/update/update.integration.test.ts b/apps/cli/src/next/commands/update/update.integration.test.ts index 316129e88d..8e23e1a4fe 100644 --- a/apps/cli/src/next/commands/update/update.integration.test.ts +++ b/apps/cli/src/next/commands/update/update.integration.test.ts @@ -184,7 +184,7 @@ describe("update handler", () => { postgrest: "14.4", auth: "2.180.0", "edge-runtime": DEFAULT_VERSIONS["edge-runtime"], - realtime: "2.78.10", + realtime: "2.111.8", storage: "1.39.1", imgproxy: "v3.8.0", mailpit: "v1.30.2", diff --git a/packages/stack/src/StackBuilder.unit.test.ts b/packages/stack/src/StackBuilder.unit.test.ts index 953fd7c01a..3d3c2c0039 100644 --- a/packages/stack/src/StackBuilder.unit.test.ts +++ b/packages/stack/src/StackBuilder.unit.test.ts @@ -456,8 +456,8 @@ describe("StackBuilder", () => { }); const realtimeDef = graph.startOrder.find((service) => service.name === "realtime"); - expect(realtimeDef?.args).toContain("supabase/realtime:v2.78.10"); - expect(realtimeDef?.args).not.toContain("public.ecr.aws/supabase/realtime:v2.78.10"); + expect(realtimeDef?.args).toContain("supabase/realtime:v2.111.8"); + expect(realtimeDef?.args).not.toContain("public.ecr.aws/supabase/realtime:v2.111.8"); }).pipe(Effect.provide(layer)); }); }); diff --git a/packages/stack/src/versions.ts b/packages/stack/src/versions.ts index 1f5cbec4cb..273e47ffcc 100644 --- a/packages/stack/src/versions.ts +++ b/packages/stack/src/versions.ts @@ -50,7 +50,7 @@ export const DEFAULT_VERSIONS: VersionManifest = { postgrest: "14.5", auth: "2.188.0-rc.15", "edge-runtime": "1.73.13", - realtime: "2.78.10", + realtime: "2.111.8", storage: "1.41.8", imgproxy: "v3.8.0", mailpit: "v1.30.2", From 81265b8a3c999367a45eebd3a55a1e44f0c48940 Mon Sep 17 00:00:00 2001 From: Vaibhav <117663341+7ttp@users.noreply.github.com> Date: Fri, 26 Jun 2026 18:17:17 +0530 Subject: [PATCH 11/29] fix(cli): skip empty schema_paths globs in db diff (#5702) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## TL;DR Fixes a regression in `supabase db diff` where declarative `schema_paths` entries that currently match no files would abort the command instead of being skipped ## What’s fixed? loading path now ignores empty `schema_paths` glob matches while still preserving existing behavior for valid matches, deterministic ordering, deduping, and invalid glob errors **Before:** Projects using declarative schema config like: would fail if one of those globs matched nothing, with an error like: ```text no files matched pattern: supabase/schemas/materialized_views/*.sql ``` That turned a normal incremental-adoption setup into a fatal `db diff` failure **After:** skips empty `schema_paths` glob entries and continues diffing with the files that do exist, matching the expected existing behavior ## Ref - closes #5700 --- apps/cli-go/internal/db/diff/diff.go | 7 +++- apps/cli-go/internal/db/diff/diff_test.go | 49 +++++++++++++++++++++++ apps/cli-go/pkg/config/config.go | 39 +++++++++++++++++- apps/cli-go/pkg/config/config_test.go | 28 +++++++++++++ 4 files changed, 121 insertions(+), 2 deletions(-) diff --git a/apps/cli-go/internal/db/diff/diff.go b/apps/cli-go/internal/db/diff/diff.go index aa286eea5b..5d2790e5b9 100644 --- a/apps/cli-go/internal/db/diff/diff.go +++ b/apps/cli-go/internal/db/diff/diff.go @@ -23,6 +23,7 @@ import ( "github.com/spf13/afero" "github.com/supabase/cli/internal/db/start" "github.com/supabase/cli/internal/utils" + configpkg "github.com/supabase/cli/pkg/config" "github.com/supabase/cli/pkg/migration" "github.com/supabase/cli/pkg/parser" ) @@ -70,7 +71,11 @@ func loadDeclaredSchemas(fsys afero.Fs) ([]string, error) { } } if schemas := utils.Config.Db.Migrations.SchemaPaths; len(schemas) > 0 { - return schemas.Files(afero.NewIOFS(fsys)) + return schemas.Files( + afero.NewIOFS(fsys), + configpkg.WithSkipEmptyGlobs(), + configpkg.WithErrorOnAllSkippedGlobs(), + ) } if exists, err := afero.DirExists(fsys, utils.SchemasDir); err != nil { return nil, errors.Errorf("failed to check schemas: %w", err) diff --git a/apps/cli-go/internal/db/diff/diff_test.go b/apps/cli-go/internal/db/diff/diff_test.go index e0df5b328a..2a6a2d4ca4 100644 --- a/apps/cli-go/internal/db/diff/diff_test.go +++ b/apps/cli-go/internal/db/diff/diff_test.go @@ -409,3 +409,52 @@ func TestLoadSchemas(t *testing.T) { assert.NoError(t, err) assert.ElementsMatch(t, expected, schemas) } + +func TestLoadSchemasSkipsEmptySchemaPathGlobs(t *testing.T) { + fsys := afero.NewMemMapFs() + matched := filepath.Join(utils.SupabaseDirPath, "schemas", "tables", "players.sql") + require.NoError(t, afero.WriteFile(fsys, matched, nil, 0644)) + utils.Config.Db.Migrations.SchemaPaths = []string{ + filepath.Join(utils.SupabaseDirPath, "schemas", "tables", "*.sql"), + filepath.Join(utils.SupabaseDirPath, "schemas", "materialized_views", "*.sql"), + } + t.Cleanup(func() { + utils.Config.Db.Migrations.SchemaPaths = nil + }) + + schemas, err := loadDeclaredSchemas(fsys) + + assert.NoError(t, err) + assert.Equal(t, []string{filepath.ToSlash(matched)}, schemas) +} + +func TestLoadSchemasErrorsOnMissingLiteralSchemaPath(t *testing.T) { + fsys := afero.NewMemMapFs() + utils.Config.Db.Migrations.SchemaPaths = []string{ + filepath.Join(utils.SupabaseDirPath, "schemas", "tables", "players.sql"), + } + t.Cleanup(func() { + utils.Config.Db.Migrations.SchemaPaths = nil + }) + + schemas, err := loadDeclaredSchemas(fsys) + + assert.ErrorContains(t, err, "no files matched pattern") + assert.Empty(t, schemas) +} + +func TestLoadSchemasErrorsWhenAllSchemaPathGlobsAreEmpty(t *testing.T) { + fsys := afero.NewMemMapFs() + utils.Config.Db.Migrations.SchemaPaths = []string{ + filepath.Join(utils.SupabaseDirPath, "schemas", "tables", "*.sql"), + filepath.Join(utils.SupabaseDirPath, "schemas", "views", "*.sql"), + } + t.Cleanup(func() { + utils.Config.Db.Migrations.SchemaPaths = nil + }) + + schemas, err := loadDeclaredSchemas(fsys) + + assert.ErrorContains(t, err, "no files matched pattern") + assert.Empty(t, schemas) +} diff --git a/apps/cli-go/pkg/config/config.go b/apps/cli-go/pkg/config/config.go index b2bf3f4a99..b9f253230a 100644 --- a/apps/cli-go/pkg/config/config.go +++ b/apps/cli-go/pkg/config/config.go @@ -97,11 +97,35 @@ func (p *RequestPolicy) UnmarshalText(text []byte) error { type Glob []string +type globOptions struct { + skipEmptyGlobs bool + errorOnAllSkipped bool +} + +type GlobOption func(*globOptions) + +func WithSkipEmptyGlobs() GlobOption { + return func(o *globOptions) { + o.skipEmptyGlobs = true + } +} + +func WithErrorOnAllSkippedGlobs() GlobOption { + return func(o *globOptions) { + o.errorOnAllSkipped = true + } +} + // Match the glob patterns in the given FS to get a deduplicated // array of all migrations files to apply in the declared order. -func (g Glob) Files(fsys fs.FS) ([]string, error) { +func (g Glob) Files(fsys fs.FS, options ...GlobOption) ([]string, error) { + opts := globOptions{} + for _, apply := range options { + apply(&opts) + } var result []string var allErrors []error + var skipped []string set := make(map[string]struct{}) for _, pattern := range g { // Glob expects / as path separator on windows @@ -109,6 +133,10 @@ func (g Glob) Files(fsys fs.FS) ([]string, error) { if err != nil { allErrors = append(allErrors, errors.Errorf("failed to glob files: %w", err)) } else if len(matches) == 0 { + if opts.skipEmptyGlobs && hasGlobMeta(pattern) { + skipped = append(skipped, pattern) + continue + } allErrors = append(allErrors, errors.Errorf("no files matched pattern: %s", pattern)) } sort.Strings(matches) @@ -121,9 +149,18 @@ func (g Glob) Files(fsys fs.FS) ([]string, error) { } } } + if opts.errorOnAllSkipped && len(result) == 0 && len(skipped) > 0 { + for _, pattern := range skipped { + allErrors = append(allErrors, errors.Errorf("no files matched pattern: %s", pattern)) + } + } return result, errors.Join(allErrors...) } +func hasGlobMeta(pattern string) bool { + return strings.ContainsAny(pattern, `*?[`) +} + // We follow these rules when adding new config: // 1. Update init_config.toml (and init_config.test.toml) with the new key, default value, and comments to explain usage. // 2. Update config struct with new field and toml tag (spelled in snake_case). diff --git a/apps/cli-go/pkg/config/config_test.go b/apps/cli-go/pkg/config/config_test.go index fa38005152..6c2697ca0e 100644 --- a/apps/cli-go/pkg/config/config_test.go +++ b/apps/cli-go/pkg/config/config_test.go @@ -676,6 +676,34 @@ func TestGlobFiles(t *testing.T) { // Validate files assert.Empty(t, files) }) + + t.Run("skips empty globs when configured", func(t *testing.T) { + fsys := fs.MapFS{ + "supabase/schemas/tables/players.sql": &fs.MapFile{}, + } + g := Glob{ + "supabase/schemas/tables/*.sql", + "supabase/schemas/materialized_views/*.sql", + } + + files, err := g.Files(fsys, WithSkipEmptyGlobs()) + + assert.NoError(t, err) + assert.Equal(t, []string{"supabase/schemas/tables/players.sql"}, files) + }) + + t.Run("errors when all skipped globs are empty and configured to fail", func(t *testing.T) { + fsys := fs.MapFS{} + g := Glob{ + "supabase/schemas/tables/*.sql", + "supabase/schemas/materialized_views/*.sql", + } + + files, err := g.Files(fsys, WithSkipEmptyGlobs(), WithErrorOnAllSkippedGlobs()) + + assert.ErrorContains(t, err, "no files matched pattern") + assert.Empty(t, files) + }) } func TestLoadFunctionImportMap(t *testing.T) { From 5008dbb7e9fd211ec100cc09c58b26b430c803f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 12:50:21 +0000 Subject: [PATCH 12/29] fix(docker): bump supabase/realtime from v2.111.8 to v2.111.10 in /apps/cli-go/pkg/config/templates in the docker-minor group (#5709) Bumps the docker-minor group in /apps/cli-go/pkg/config/templates with 1 update: supabase/realtime. Updates `supabase/realtime` from v2.111.8 to v2.111.10 [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=supabase/realtime&package-manager=docker&previous-version=v2.111.8&new-version=v2.111.10)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/cli-go/pkg/config/templates/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/cli-go/pkg/config/templates/Dockerfile b/apps/cli-go/pkg/config/templates/Dockerfile index 47c7f6a17e..0e496091a5 100644 --- a/apps/cli-go/pkg/config/templates/Dockerfile +++ b/apps/cli-go/pkg/config/templates/Dockerfile @@ -11,7 +11,7 @@ FROM supabase/edge-runtime:v1.74.2 AS edgeruntime FROM timberio/vector:0.53.0-alpine AS vector FROM supabase/supavisor:2.9.7 AS supavisor FROM supabase/gotrue:v2.191.0 AS gotrue -FROM supabase/realtime:v2.111.8 AS realtime +FROM supabase/realtime:v2.111.10 AS realtime FROM supabase/storage-api:v1.61.4 AS storage FROM supabase/logflare:1.45.4 AS logflare # Append to JobImages when adding new dependencies below From ce2120e5f4710c0753eec81189d07f7909abfcca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 14:51:23 +0200 Subject: [PATCH 13/29] chore(deps): bump github.com/posthog/posthog-go from 1.15.1 to 1.16.0 in /apps/cli-go in the go-minor group across 1 directory (#5710) Bumps the go-minor group with 1 update in the /apps/cli-go directory: [github.com/posthog/posthog-go](https://github.com/posthog/posthog-go). Updates `github.com/posthog/posthog-go` from 1.15.1 to 1.16.0
Release notes

Sourced from github.com/posthog/posthog-go's releases.

1.16.0

Unreleased

Changelog

Sourced from github.com/posthog/posthog-go's changelog.

1.16.0

Minor Changes

  • 1068ec9: Add a BeforeSend hook for modifying or dropping messages before they are sent.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/posthog/posthog-go&package-manager=go_modules&previous-version=1.15.1&new-version=1.16.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/cli-go/go.mod | 2 +- apps/cli-go/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/cli-go/go.mod b/apps/cli-go/go.mod index 3da2229523..ad4638aa6a 100644 --- a/apps/cli-go/go.mod +++ b/apps/cli-go/go.mod @@ -42,7 +42,7 @@ require ( github.com/multigres/multigres v0.0.0-20260126223308-f5a52171bbc4 github.com/oapi-codegen/nullable v1.1.0 github.com/olekukonko/tablewriter v1.1.4 - github.com/posthog/posthog-go v1.15.1 + github.com/posthog/posthog-go v1.16.0 github.com/spf13/afero v1.15.0 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 diff --git a/apps/cli-go/go.sum b/apps/cli-go/go.sum index 043956a8ad..9fc5523b06 100644 --- a/apps/cli-go/go.sum +++ b/apps/cli-go/go.sum @@ -941,8 +941,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polyfloyd/go-errorlint v1.8.0 h1:DL4RestQqRLr8U4LygLw8g2DX6RN1eBJOpa2mzsrl1Q= github.com/polyfloyd/go-errorlint v1.8.0/go.mod h1:G2W0Q5roxbLCt0ZQbdoxQxXktTjwNyDbEaj3n7jvl4s= -github.com/posthog/posthog-go v1.15.1 h1:9rwjaEzyp5mss/fp0vYjLOhIaXCikfgRHRSzMMHgwJ8= -github.com/posthog/posthog-go v1.15.1/go.mod h1:xsVOW9YImilUcazwPNEq4PJDqEZf2KeCS758zXjwkPg= +github.com/posthog/posthog-go v1.16.0 h1:sU6ifnBcyjiqCGwz92b1A1uKnSALZIFeFn/YnPfTyF8= +github.com/posthog/posthog-go v1.16.0/go.mod h1:xsVOW9YImilUcazwPNEq4PJDqEZf2KeCS758zXjwkPg= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= From 279d0c0484038f24dbf5e77024eaf00919edc2b6 Mon Sep 17 00:00:00 2001 From: Vaibhav <117663341+7ttp@users.noreply.github.com> Date: Fri, 26 Jun 2026 21:24:54 +0530 Subject: [PATCH 14/29] fix(cli): shorten serve argv (#5712) ## TL;DR fixes a regression in `supabase functions serve` by removing the bundled Edge Runtime bootstrap script from the spawned `docker run ... sh -c ...` argv and mounting it into the container as `/root/index.ts` instead This keeps the startup command small, preserves the runtime behavior, and avoids the `ENAMETOOLONG: name too long, uv_spawn` failure... ## What's fixed? `functions serve` ts path was embedding the full bundled runtime template directly into the docker entrypoint command on win, that made the final spawn argv large enough to fail before docker even started, which surfaced as `ENAMETOOLONG: name too long, uv_spawn` **Before:** the runtime template was embedded directly into the docker startup command and hit spawn length limits **After:** the template is mounted from a temp file, so the startup command stays short ## ref: - closes https://github.com/supabase/cli/issues/5711 --- .../functions/serve/serve.integration.test.ts | 17 ++++- apps/cli/src/shared/functions/serve.ts | 64 +++++++++---------- .../src/shared/functions/serve.unit.test.ts | 32 ++++------ 3 files changed, 56 insertions(+), 57 deletions(-) diff --git a/apps/cli/src/legacy/commands/functions/serve/serve.integration.test.ts b/apps/cli/src/legacy/commands/functions/serve/serve.integration.test.ts index d234aa388f..be575f1409 100644 --- a/apps/cli/src/legacy/commands/functions/serve/serve.integration.test.ts +++ b/apps/cli/src/legacy/commands/functions/serve/serve.integration.test.ts @@ -477,6 +477,14 @@ describe("legacy functions serve integration", () => { expect(dockerRun.args).toContain("--add-host"); expect(dockerRun.args).toContain("host.docker.internal:host-gateway"); expect(dockerRun.args).toContain("public.ecr.aws/supabase/edge-runtime:v1.73.13"); + expect( + extractFlagValues(dockerRun.args, "-v").some((value) => + value.endsWith(":/root/index.ts:ro"), + ), + ).toBe(true); + expect(dockerRun.args[dockerRun.args.length - 1]).toBe( + "edge-runtime start --main-service=/root --port=8081 --policy=per_worker\n", + ); const envs = yield* Effect.promise(() => extractDockerEnvEntries(dockerRun)); expect(envs).toContain("HELLO=WORLD"); @@ -1147,7 +1155,14 @@ describe("legacy functions serve integration", () => { } const commandScript = dockerRun.args[dockerRun.args.length - 1] ?? ""; - expect(commandScript).toContain("cat <<'EOF' > /root/index.ts"); + expect(commandScript).toBe( + "edge-runtime start --main-service=/root --port=8081 --policy=per_worker\n", + ); + expect( + extractFlagValues(dockerRun.args, "-v").some((value) => + value.endsWith(":/root/index.ts:ro"), + ), + ).toBe(true); expect(commandScript).not.toContain("@ts-nocheck"); expect(commandScript).not.toContain("declare const Deno"); expect(commandScript).not.toContain("declare const EdgeRuntime"); diff --git a/apps/cli/src/shared/functions/serve.ts b/apps/cli/src/shared/functions/serve.ts index 057f878e9a..91874fb603 100644 --- a/apps/cli/src/shared/functions/serve.ts +++ b/apps/cli/src/shared/functions/serve.ts @@ -85,6 +85,7 @@ const dockerLogDiagnosticTailLength = 4_096; const remoteJwksTimeoutMs = 10_000; const legacyDefaultEdgeRuntimeVersion = "v1.74.1"; const defaultSupabaseEnv = "development"; +const serveMainContainerPath = "/root/index.ts"; const clerkDomainPattern = /^(clerk([.][a-z0-9-]+){2,}|([a-z0-9-]+[.])+clerk[.]accounts[.]dev)$/; const shellVariableNamePattern = /^[A-Za-z_][A-Za-z0-9_]*$/; let cachedLegacyFunctionsServeMainTemplate: string | undefined; @@ -1229,30 +1230,26 @@ const writeStoppedServingMessage = Effect.fnUntraced(function* () { yield* output.raw(`Stopped serving ${styleText("bold", functionsDirName)}\n`, "stdout"); }); -// The Go CLI writes the runtime template to /root/index.ts via a quoted `<<'EOF'` -// heredoc; we keep the same terminator for byte-parity with its entrypoint. A line -// equal to the terminator inside the template would close the heredoc early and -// silently corrupt the script, so fail loudly instead. `serve.main.ts` (the only -// template) is asserted to contain no such line by a unit test. -const serveEntrypointHeredocTerminator = "EOF"; - -export function buildServeEntrypointScript( - template: string, +export function buildServeEntrypointCommand( command: ReadonlyArray, multilineEnvScriptPath?: string, ) { - if (template.split("\n").includes(serveEntrypointHeredocTerminator)) { - throw new Error( - `functions serve runtime template contains a line equal to the heredoc terminator "${serveEntrypointHeredocTerminator}"`, - ); - } - return `cat <<'${serveEntrypointHeredocTerminator}' > /root/index.ts -${template} -${serveEntrypointHeredocTerminator} -${multilineEnvScriptPath === undefined ? "" : `. ${multilineEnvScriptPath}\n`}${command.join(" ")} + return `${multilineEnvScriptPath === undefined ? "" : `. ${multilineEnvScriptPath}\n`}${command.join(" ")} `; } +async function writeServeMainTemplateFile(template: string) { + // Mount the bundled runtime template instead of embedding it in `sh -c` so + // Windows does not hit `uv_spawn` ENAMETOOLONG on path-heavy projects. + const dir = await mkdtemp(join(tmpdir(), "supabase-functions-serve-main-")); + const pathname = join(dir, "index.ts"); + await writeFile(pathname, template); + return { + bind: `${pathname}:${serveMainContainerPath}:ro`, + cleanup: () => rm(dir, { recursive: true, force: true }), + } as const; +} + function edgeRuntimeImageTag(version: string) { return version.startsWith("v") ? version : `v${version}`; } @@ -1420,6 +1417,9 @@ const startEdgeRuntime = Effect.fnUntraced(function* (input: { ...(input.debug ? ["--verbose"] : []), ]; const serveMainTemplate = yield* Effect.promise(() => getLegacyFunctionsServeMainTemplate()); + const serveMainTemplateFile = yield* Effect.tryPromise(() => + writeServeMainTemplateFile(serveMainTemplate), + ).pipe(Effect.mapError((cause) => (cause instanceof Error ? cause : new Error(String(cause))))); const command = [ "run", "-d", @@ -1437,6 +1437,8 @@ const startEdgeRuntime = Effect.fnUntraced(function* (input: { `com.supabase.cli.project=${labels["com.supabase.cli.project"]}`, "--label", `com.docker.compose.project=${labels["com.docker.compose.project"]}`, + "-v", + serveMainTemplateFile.bind, ...([...binds] as ReadonlyArray).flatMap((bind) => ["-v", bind]), ...(dockerMultilineEnvScript === undefined ? [] : ["-v", dockerMultilineEnvScript.bind]), ...(dockerEnvFile === undefined ? [] : ["--env-file", dockerEnvFile.path]), @@ -1450,26 +1452,18 @@ const startEdgeRuntime = Effect.fnUntraced(function* (input: { "sh", legacyGetRegistryImageUrl(`supabase/edge-runtime:${edgeRuntimeImageTag(edgeRuntimeVersion)}`), "-c", - buildServeEntrypointScript( - serveMainTemplate, - runtimeCommand, - dockerMultilineEnvScript?.scriptPath, - ), + buildServeEntrypointCommand(runtimeCommand, dockerMultilineEnvScript?.scriptPath), ]; - const cleanupRuntimeArtifacts = + const cleanupRuntimeArtifacts = Effect.all([ + Effect.tryPromise(() => serveMainTemplateFile.cleanup()).pipe(Effect.orDie), dockerEnvFile === undefined - ? dockerMultilineEnvScript === undefined - ? Effect.void - : Effect.tryPromise(() => dockerMultilineEnvScript.cleanup()).pipe(Effect.orDie) - : Effect.tryPromise(() => dockerEnvFile.cleanup()).pipe( - Effect.andThen( - dockerMultilineEnvScript === undefined - ? Effect.void - : Effect.tryPromise(() => dockerMultilineEnvScript.cleanup()).pipe(Effect.orDie), - ), - Effect.orDie, - ); + ? Effect.void + : Effect.tryPromise(() => dockerEnvFile.cleanup()).pipe(Effect.orDie), + dockerMultilineEnvScript === undefined + ? Effect.void + : Effect.tryPromise(() => dockerMultilineEnvScript.cleanup()).pipe(Effect.orDie), + ]).pipe(Effect.asVoid); return yield* Effect.gen(function* () { yield* output.raw("Setting up Edge Functions runtime...\n", "stderr"); diff --git a/apps/cli/src/shared/functions/serve.unit.test.ts b/apps/cli/src/shared/functions/serve.unit.test.ts index f265c28412..a12ac1b9db 100644 --- a/apps/cli/src/shared/functions/serve.unit.test.ts +++ b/apps/cli/src/shared/functions/serve.unit.test.ts @@ -1,34 +1,24 @@ import { describe, expect, it } from "vitest"; import { bundleServeMainTemplate } from "./serve-main-bundler.ts"; -import { buildServeEntrypointScript } from "./serve.ts"; +import { buildServeEntrypointCommand } from "./serve.ts"; -describe("buildServeEntrypointScript", () => { - const template = ['import { x } from "y";', "Deno.serve(() => new Response());"].join("\n"); - - it("writes the template through the heredoc and appends the runtime command", () => { - const script = buildServeEntrypointScript(template, ["edge-runtime", "start"]); - expect(script).toContain("cat <<'EOF' > /root/index.ts"); - expect(script).toContain(template); - expect(script).toContain("edge-runtime start"); - expect(script).not.toContain(". /"); +describe("buildServeEntrypointCommand", () => { + it("returns the runtime command without embedding the template body", () => { + const script = buildServeEntrypointCommand(["edge-runtime", "start"]); + expect(script).toBe("edge-runtime start\n"); + expect(script).not.toContain("Deno.serve"); }); it("sources the multiline env script before the runtime command when provided", () => { - const script = buildServeEntrypointScript(template, ["edge-runtime", "start"], "/root/env.sh"); + const script = buildServeEntrypointCommand(["edge-runtime", "start"], "/root/env.sh"); expect(script).toContain(". /root/env.sh\nedge-runtime start"); }); - it("fails loudly when the template contains a bare heredoc terminator line", () => { - const poisoned = ["line-1", "EOF", "line-3"].join("\n"); - expect(() => buildServeEntrypointScript(poisoned, ["edge-runtime", "start"])).toThrow( - 'heredoc terminator "EOF"', - ); - }); - - it("does not let the real bundled serve.main.ts template close the heredoc early", async () => { + it("keeps the spawned command short even with the real bundled template", async () => { const bundled = await bundleServeMainTemplate(); - expect(bundled.split("\n")).not.toContain("EOF"); - expect(() => buildServeEntrypointScript(bundled, ["edge-runtime", "start"])).not.toThrow(); + const script = buildServeEntrypointCommand(["edge-runtime", "start"]); + expect(bundled.length).toBeGreaterThan(20_000); + expect(script.length).toBeLessThan(128); }); }); From 64df2ba83785d849fd230ef7aea7cf5a60de63c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 27 Jun 2026 00:07:39 +0000 Subject: [PATCH 15/29] fix(docker): bump the docker-minor group in /apps/cli-go/pkg/config/templates with 2 updates (#5717) Bumps the docker-minor group in /apps/cli-go/pkg/config/templates with 2 updates: supabase/realtime and supabase/storage-api. Updates `supabase/realtime` from v2.111.10 to v2.112.0 Updates `supabase/storage-api` from v1.61.4 to v1.61.5 Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/cli-go/pkg/config/templates/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/cli-go/pkg/config/templates/Dockerfile b/apps/cli-go/pkg/config/templates/Dockerfile index 0e496091a5..b6dbed42a8 100644 --- a/apps/cli-go/pkg/config/templates/Dockerfile +++ b/apps/cli-go/pkg/config/templates/Dockerfile @@ -11,8 +11,8 @@ FROM supabase/edge-runtime:v1.74.2 AS edgeruntime FROM timberio/vector:0.53.0-alpine AS vector FROM supabase/supavisor:2.9.7 AS supavisor FROM supabase/gotrue:v2.191.0 AS gotrue -FROM supabase/realtime:v2.111.10 AS realtime -FROM supabase/storage-api:v1.61.4 AS storage +FROM supabase/realtime:v2.112.0 AS realtime +FROM supabase/storage-api:v1.61.5 AS storage FROM supabase/logflare:1.45.4 AS logflare # Append to JobImages when adding new dependencies below FROM supabase/pgadmin-schema-diff:cli-0.0.5 AS differ From 47ab46d6f95b5bae9bde6f9f9619392aaea3282a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 27 Jun 2026 00:08:33 +0000 Subject: [PATCH 16/29] chore(deps): bump github.com/posthog/posthog-go from 1.16.0 to 1.16.1 in /apps/cli-go in the go-minor group across 1 directory (#5718) Bumps the go-minor group with 1 update in the /apps/cli-go directory: [github.com/posthog/posthog-go](https://github.com/posthog/posthog-go). Updates `github.com/posthog/posthog-go` from 1.16.0 to 1.16.1
Release notes

Sourced from github.com/posthog/posthog-go's releases.

1.16.1

Unreleased

Changelog

Sourced from github.com/posthog/posthog-go's changelog.

1.16.1

Patch Changes

  • 922cfff: Validate user-supplied event UUIDs before sending and generate a fallback UUID when invalid.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/posthog/posthog-go&package-manager=go_modules&previous-version=1.16.0&new-version=1.16.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/cli-go/go.mod | 2 +- apps/cli-go/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/cli-go/go.mod b/apps/cli-go/go.mod index ad4638aa6a..23a73ea8ad 100644 --- a/apps/cli-go/go.mod +++ b/apps/cli-go/go.mod @@ -42,7 +42,7 @@ require ( github.com/multigres/multigres v0.0.0-20260126223308-f5a52171bbc4 github.com/oapi-codegen/nullable v1.1.0 github.com/olekukonko/tablewriter v1.1.4 - github.com/posthog/posthog-go v1.16.0 + github.com/posthog/posthog-go v1.16.1 github.com/spf13/afero v1.15.0 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 diff --git a/apps/cli-go/go.sum b/apps/cli-go/go.sum index 9fc5523b06..66726ca0a8 100644 --- a/apps/cli-go/go.sum +++ b/apps/cli-go/go.sum @@ -941,8 +941,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polyfloyd/go-errorlint v1.8.0 h1:DL4RestQqRLr8U4LygLw8g2DX6RN1eBJOpa2mzsrl1Q= github.com/polyfloyd/go-errorlint v1.8.0/go.mod h1:G2W0Q5roxbLCt0ZQbdoxQxXktTjwNyDbEaj3n7jvl4s= -github.com/posthog/posthog-go v1.16.0 h1:sU6ifnBcyjiqCGwz92b1A1uKnSALZIFeFn/YnPfTyF8= -github.com/posthog/posthog-go v1.16.0/go.mod h1:xsVOW9YImilUcazwPNEq4PJDqEZf2KeCS758zXjwkPg= +github.com/posthog/posthog-go v1.16.1 h1:uEbaaYT361a3ImI0D1DYUyNLWN7Y9V9gLqCbQ/z5SxQ= +github.com/posthog/posthog-go v1.16.1/go.mod h1:xsVOW9YImilUcazwPNEq4PJDqEZf2KeCS758zXjwkPg= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= From 0084d6e18cf32a2819c3272237f788a84fb70010 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 28 Jun 2026 00:08:51 +0000 Subject: [PATCH 17/29] chore(deps): bump github.com/oapi-codegen/runtime from 1.4.1 to 1.4.2 in /apps/cli-go in the go-minor group across 1 directory (#5719) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the go-minor group with 1 update in the /apps/cli-go/pkg directory: [github.com/oapi-codegen/runtime](https://github.com/oapi-codegen/runtime). Updates `github.com/oapi-codegen/runtime` from 1.4.1 to 1.4.2
Release notes

Sourced from github.com/oapi-codegen/runtime's releases.

Bug fix for required parameters

This is a bug fix to address a regression introduced in oapi-codegen v2.7.0

🐛 Bug fixes

Sponsors

We would like to thank our sponsors for their support during this release.

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/oapi-codegen/runtime&package-manager=go_modules&previous-version=1.4.1&new-version=1.4.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/cli-go/go.mod | 2 +- apps/cli-go/go.sum | 4 ++-- apps/cli-go/pkg/go.mod | 2 +- apps/cli-go/pkg/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/cli-go/go.mod b/apps/cli-go/go.mod index 23a73ea8ad..4eae3aa6cb 100644 --- a/apps/cli-go/go.mod +++ b/apps/cli-go/go.mod @@ -334,7 +334,7 @@ require ( github.com/nishanths/predeclared v0.2.2 // indirect github.com/nunnatsa/ginkgolinter v0.19.1 // indirect github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 // indirect - github.com/oapi-codegen/runtime v1.4.1 // indirect + github.com/oapi-codegen/runtime v1.4.2 // indirect github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect diff --git a/apps/cli-go/go.sum b/apps/cli-go/go.sum index 66726ca0a8..ba611b836c 100644 --- a/apps/cli-go/go.sum +++ b/apps/cli-go/go.sum @@ -865,8 +865,8 @@ github.com/oapi-codegen/nullable v1.1.0 h1:eAh8JVc5430VtYVnq00Hrbpag9PFRGWLjxR1/ github.com/oapi-codegen/nullable v1.1.0/go.mod h1:KUZ3vUzkmEKY90ksAmit2+5juDIhIZhfDl+0PwOQlFY= github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 h1:ykgG34472DWey7TSjd8vIfNykXgjOgYJZoQbKfEeY/Q= github.com/oapi-codegen/oapi-codegen/v2 v2.4.1/go.mod h1:N5+lY1tiTDV3V1BeHtOxeWXHoPVeApvsvjJqegfoaz8= -github.com/oapi-codegen/runtime v1.4.1 h1:9nwLoI+KrWxzbBcp0jO/R8uXqbik/HUyCvPeU68Y/qo= -github.com/oapi-codegen/runtime v1.4.1/go.mod h1:GwV7hC2hviaMzj+ITfHVRESK5J2W/GefVwIND/bMGvU= +github.com/oapi-codegen/runtime v1.4.2 h1:GMxFVYLzoYLua+/KvzgSphkyK1lLTReQI9Vf4hvATKE= +github.com/oapi-codegen/runtime v1.4.2/go.mod h1:GwV7hC2hviaMzj+ITfHVRESK5J2W/GefVwIND/bMGvU= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= diff --git a/apps/cli-go/pkg/go.mod b/apps/cli-go/pkg/go.mod index 1a3f84dd34..e1bb10f8ca 100644 --- a/apps/cli-go/pkg/go.mod +++ b/apps/cli-go/pkg/go.mod @@ -20,7 +20,7 @@ require ( github.com/jackc/pgx/v4 v4.18.3 github.com/joho/godotenv v1.5.1 github.com/oapi-codegen/nullable v1.1.0 - github.com/oapi-codegen/runtime v1.4.1 + github.com/oapi-codegen/runtime v1.4.2 github.com/spf13/afero v1.15.0 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 diff --git a/apps/cli-go/pkg/go.sum b/apps/cli-go/pkg/go.sum index bed1b36aba..f34fbaeeef 100644 --- a/apps/cli-go/pkg/go.sum +++ b/apps/cli-go/pkg/go.sum @@ -132,8 +132,8 @@ github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/oapi-codegen/nullable v1.1.0 h1:eAh8JVc5430VtYVnq00Hrbpag9PFRGWLjxR1/3KntMs= github.com/oapi-codegen/nullable v1.1.0/go.mod h1:KUZ3vUzkmEKY90ksAmit2+5juDIhIZhfDl+0PwOQlFY= -github.com/oapi-codegen/runtime v1.4.1 h1:9nwLoI+KrWxzbBcp0jO/R8uXqbik/HUyCvPeU68Y/qo= -github.com/oapi-codegen/runtime v1.4.1/go.mod h1:GwV7hC2hviaMzj+ITfHVRESK5J2W/GefVwIND/bMGvU= +github.com/oapi-codegen/runtime v1.4.2 h1:GMxFVYLzoYLua+/KvzgSphkyK1lLTReQI9Vf4hvATKE= +github.com/oapi-codegen/runtime v1.4.2/go.mod h1:GwV7hC2hviaMzj+ITfHVRESK5J2W/GefVwIND/bMGvU= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= From 44705dfa204ab25908844385a82d9951df0a7943 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2026 00:19:17 +0000 Subject: [PATCH 18/29] chore(deps): bump the npm-major group with 26 updates (#5720) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the npm-major group with 26 updates: | Package | From | To | | --- | --- | --- | | [verdaccio](https://github.com/verdaccio/verdaccio) | `6.7.2` | `6.7.4` | | [smol-toml](https://github.com/squirrelchat/smol-toml) | `1.6.1` | `1.7.0` | | [@supabase/supabase-js](https://github.com/supabase/supabase-js/tree/HEAD/packages/core/supabase-js) | `2.108.1` | `2.108.2` | | [@anthropic-ai/claude-agent-sdk](https://github.com/anthropics/claude-agent-sdk-typescript) | `0.3.177` | `0.3.185` | | [@anthropic-ai/sdk](https://github.com/anthropics/anthropic-sdk-typescript) | `0.104.1` | `0.105.0` | | [@clack/prompts](https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts) | `1.5.1` | `1.6.0` | | [ink](https://github.com/vadimdemedes/ink) | `7.0.6` | `7.1.0` | | [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg) | `8.21.0` | `8.22.0` | | [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.37.0` | `5.38.2` | | [fumadocs-core](https://github.com/fuma-nama/fumadocs) | `16.10.2` | `16.10.5` | | [fumadocs-ui](https://github.com/fuma-nama/fumadocs) | `16.10.2` | `16.10.5` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.9.3` | `26.0.0` | | [@effect/atom-react](https://github.com/Effect-TS/effect-smol/tree/HEAD/packages/atom/react) | `4.0.0-beta.83` | `4.0.0-beta.85` | | [@effect/platform-bun](https://github.com/Effect-TS/effect/tree/HEAD/packages/platform-bun) | `4.0.0-beta.83` | `4.0.0-beta.85` | | [@effect/platform-node](https://github.com/Effect-TS/effect/tree/HEAD/packages/platform-node) | `4.0.0-beta.83` | `4.0.0-beta.85` | | [@effect/sql-pg](https://github.com/Effect-TS/effect/tree/HEAD/packages/sql-pg) | `4.0.0-beta.83` | `4.0.0-beta.85` | | [@effect/vitest](https://github.com/Effect-TS/effect/tree/HEAD/packages/vitest) | `4.0.0-beta.84` | `4.0.0-beta.85` | | [@nx/devkit](https://github.com/nrwl/nx/tree/HEAD/packages/devkit) | `22.7.5` | `23.0.0` | | [@typescript/native-preview](https://github.com/microsoft/typescript-go) | `7.0.0-dev.20260614.1` | `7.0.0-dev.20260621.1` | | [@vitest/coverage-istanbul](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-istanbul) | `4.1.8` | `4.1.9` | | [effect](https://github.com/Effect-TS/effect/tree/HEAD/packages/effect) | `4.0.0-beta.83` | `4.0.0-beta.85` | | [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) | `6.16.1` | `6.17.1` | | [oxfmt](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt) | `0.54.0` | `0.55.0` | | [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) | `1.69.0` | `1.70.0` | | [tldts](https://github.com/remusao/tldts) | `6.1.86` | `7.4.3` | | [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `4.1.8` | `4.1.9` | Updates `verdaccio` from 6.7.2 to 6.7.4
Release notes

Sourced from verdaccio's releases.

v6.7.4

Patch Changes

  • 0205c78: fix: run jwt middleware before middleware plugins

    Register the JWT middleware before middleware plugins are loaded so that req.remote_user (anonymous by default) is available inside a plugin's register_middlewares. The API router keeps its own JWT middleware behind a guard so it is not executed twice.

    Backport of verdaccio/verdaccio#5697

    Closes #5167

v6.7.3

Patch Changes

  • f8fdfc2: fix: enforce generated npm token metadata

    Generated npm tokens (POST /-/npm/v1/tokens) stored their readonly and cidr_whitelist restrictions but never enforced them, and deleting a token did not revoke it for the package APIs. A token marked read-only or pinned to a CIDR range could still publish packages and change dist-tags, and a deleted token remained usable.

    Generated tokens now embed a server-issued key (in the JWT claim, or in the encrypted legacy AES payload) and a new enforceGeneratedTokenMetadata middleware looks that key up on each request, rejecting the token when it is missing/revoked, used outside its CIDR whitelist, or used for a write while read-only. Enforcement applies to both AES and JWT API-token modes.

    Note: tokens issued before upgrading carry no key and are not retroactively constrained — regenerate them to apply the restrictions.

  • be80623: fix: allow npm token create without readonly/cidr_whitelist

    npm token create in npm >= 11 (and the npm 12 prereleases) rewrote the request body: it no longer sends readonly and only sends cidr_whitelist when --cidr is passed. The POST /-/npm/v1/tokens endpoint required both, so modern npm clients failed with 422 the parameters are not valid.

    The endpoint now defaults readonly to false and cidr_whitelist to [] when they are absent, while still rejecting values of the wrong type.

  • 75c85d5: Update verdaccio dependencies to the latest npm dist-tag (@verdaccio/ui-theme tracks next-9):

    • @verdaccio/ui-theme: 9.0.0-next-9.199.0.0-next-9.20
  • d5e5332: chore: update dependencies

    Updates runtime dependencies @verdaccio/ui-theme (9.0.0-next-9.19) and

... (truncated)

Changelog

Sourced from verdaccio's changelog.

6.7.4

Patch Changes

  • 0205c78: fix: run jwt middleware before middleware plugins

    Register the JWT middleware before middleware plugins are loaded so that req.remote_user (anonymous by default) is available inside a plugin's register_middlewares. The API router keeps its own JWT middleware behind a guard so it is not executed twice.

    Backport of verdaccio/verdaccio#5697

    Closes #5167

6.7.3

Patch Changes

  • f8fdfc2: fix: enforce generated npm token metadata

    Generated npm tokens (POST /-/npm/v1/tokens) stored their readonly and cidr_whitelist restrictions but never enforced them, and deleting a token did not revoke it for the package APIs. A token marked read-only or pinned to a CIDR range could still publish packages and change dist-tags, and a deleted token remained usable.

    Generated tokens now embed a server-issued key (in the JWT claim, or in the encrypted legacy AES payload) and a new enforceGeneratedTokenMetadata middleware looks that key up on each request, rejecting the token when it is missing/revoked, used outside its CIDR whitelist, or used for a write while read-only. Enforcement applies to both AES and JWT API-token modes.

    Note: tokens issued before upgrading carry no key and are not retroactively constrained — regenerate them to apply the restrictions.

  • be80623: fix: allow npm token create without readonly/cidr_whitelist

    npm token create in npm >= 11 (and the npm 12 prereleases) rewrote the request body: it no longer sends readonly and only sends cidr_whitelist when --cidr is passed. The POST /-/npm/v1/tokens endpoint required both, so modern npm clients failed with 422 the parameters are not valid.

    The endpoint now defaults readonly to false and cidr_whitelist to [] when they are absent, while still rejecting values of the wrong type.

  • 75c85d5: Update verdaccio dependencies to the latest npm dist-tag (@verdaccio/ui-theme tracks next-9):

    • @verdaccio/ui-theme: 9.0.0-next-9.199.0.0-next-9.20
  • d5e5332: chore: update dependencies

... (truncated)

Commits

Updates `smol-toml` from 1.6.1 to 1.7.0
Release notes

Sourced from smol-toml's releases.

v1.7.0

This version slightly changes the behaviour of stringify: integers beyond the safe range are always emitted as float numbers.

String decode logic has been rewritten, it is a bit faster now and uses a single-pass approach instead of a dual-pass approach as it did previously. The code should be a bit smaller too, though I didn't actually measure that.

The package is now published with source-maps, declaration-maps, and a copy of the original TypeScript source files. This will improve your DX if you're like me and like Ctrl+Click'ing things a lot. ;)

What's Changed

New Contributors

Full Changelog: https://github.com/squirrelchat/smol-toml/compare/v1.6.1...v1.7.0

Commits
  • a62f06f revert: keep using vite 7
  • 89aa9a3 chore: remove prepare script
  • 17c7974 chore: make devEngine more lax w/ node version
  • e5280a3 ci: checkout repo first
  • 241c256 chore: version bump
  • 0bfe7f4 chore: build cjs with rolldown instead of esbuild
  • e0620ab chore: fmt
  • 96114cb test: add tests for large integers
  • f4537b6 fix: handle missed edge-cases in string parse
  • 7b39aed chore: include source files in published package
  • Additional commits viewable in compare view

Updates `@supabase/supabase-js` from 2.108.1 to 2.108.2
Release notes

Sourced from @​supabase/supabase-js's releases.

v2.108.2

2.108.2 (2026-06-15)

🩹 Fixes

  • auth: preserve valid session on refresh failure and cooldown repeat failures (#2436)
  • realtime: clarify httpSend() 404 error and server migration note (#2444)
  • release: pin Deno and bound JSR publish to survive stranded-task hangs (#2439)
  • release: restore JSR publish flags and enable for beta (#2440)

❤️ Thank You

v2.108.2-canary.5

2.108.2-canary.5 (2026-06-15)

This was a version bump only, there were no code changes.

v2.108.2-canary.4

2.108.2-canary.4 (2026-06-12)

🩹 Fixes

  • realtime: clarify httpSend() 404 error and server migration note (#2444)

❤️ Thank You

v2.108.2-canary.3

2.108.2-canary.3 (2026-06-11)

This was a version bump only, there were no code changes.

v2.108.2-canary.2

2.108.2-canary.2 (2026-06-11)

🩹 Fixes

  • release: restore JSR publish flags and enable for beta (#2440)

❤️ Thank You

v2.108.2-canary.1

2.108.2-canary.1 (2026-06-11)

🩹 Fixes

... (truncated)

Changelog

Sourced from @​supabase/supabase-js's changelog.

2.108.2 (2026-06-15)

This was a version bump only for @​supabase/supabase-js to align it with other projects, there were no code changes.

2.108.0 (2026-06-08)

This was a version bump only for @​supabase/supabase-js to align it with other projects, there were no code changes.

2.107.0 (2026-06-02)

🚀 Features

  • auth: remove navigator.locks-based mutex; introduce commit guard + dispose() (#2392)
  • supabase: update X-Client-Info to structured metadata format (#2359)
  • realtime: allow httpSend to send binary payload (#2400)

❤️ Thank You

2.106.2 (2026-05-25)

🩹 Fixes

  • misc: add react-native export condition for Hermes-safe resolution (#2393)

❤️ Thank You

2.106.1 (2026-05-20)

🩹 Fixes

  • misc: hide dynamic import from hermesc (#2381)

❤️ Thank You

2.106.0 (2026-05-18)

🚀 Features

  • supabase: W3C/OpenTelemetry trace context propagation (#2163)

... (truncated)

Commits

Updates `@anthropic-ai/claude-agent-sdk` from 0.3.177 to 0.3.185
Release notes

Sourced from @​anthropic-ai/claude-agent-sdk's releases.

v0.3.185

What's changed

  • Updated to parity with Claude Code v2.1.185

Update

npm install @anthropic-ai/claude-agent-sdk@0.3.185
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.185
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.185
# or
bun add @anthropic-ai/claude-agent-sdk@0.3.185

v0.3.183

What's changed

  • Updated to parity with Claude Code v2.1.183

Update

npm install @anthropic-ai/claude-agent-sdk@0.3.183
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.183
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.183
# or
bun add @anthropic-ai/claude-agent-sdk@0.3.183

v0.3.181

What's changed

  • Added errorCode, canUserPurchaseCredits, and hasChargeableSavedPaymentMethod fields to SDKRateLimitInfo for detecting credits-required rate limits
  • Added tool_use_meta.icon_url to assistant messages, populated from MCP server directory metadata
  • Fixed SDK-hosted Remote Control sessions dropping file_attachments from inbound user messages

Update

npm install @anthropic-ai/claude-agent-sdk@0.3.181
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.181
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.181
# or
</tr></table>

... (truncated)

Changelog

Sourced from @​anthropic-ai/claude-agent-sdk's changelog.

0.3.185

  • Updated to parity with Claude Code v2.1.185

0.3.184

  • Updated to parity with Claude Code v2.1.184

0.3.183

  • Updated to parity with Claude Code v2.1.183

0.3.182

  • Updated to parity with Claude Code v2.1.182

0.3.181

  • Added errorCode, canUserPurchaseCredits, and hasChargeableSavedPaymentMethod fields to SDKRateLimitInfo for detecting credits-required rate limits
  • Added tool_use_meta.icon_url to assistant messages, populated from MCP server directory metadata
  • Fixed SDK-hosted Remote Control sessions dropping file_attachments from inbound user messages

0.3.180

  • Updated to parity with Claude Code v2.1.180

0.3.179

  • Added optional tool_use_meta sidecar to assistant messages with display-friendly names for tool calls, so SDK consumers can render human-readable labels instead of raw wire names
  • Fixed -p mode exiting before a completed background agent's notification was delivered, causing interim text to ship as the final result
  • Fixed remote (stream-json) sessions appearing busy for the entire duration of a background workflow — the turn result is now emitted at the turn boundary and the session reports idle while background tasks continue

0.3.178

  • Spawn failures on an existing native binary now explain the likely libc mismatch (musl binary on a glibc host) and suggest options.pathToClaudeCodeExecutable
  • Permission-denied advisory messages now carry typed denial reasons (safetyCheck, asyncAgent), enabling SDK consumers to programmatically match denial causes
  • Fixed UserPromptSubmit hook block feedback not being emitted to the SDK event stream — consumers can now see why a prompt was blocked by a hook instead of a silent hang
  • Remote Control workers now send a worker_shutting_down system message on graceful exit so remote clients can show why the session ended
  • Fixed MCP server-level specs (mcp__server, mcp__server__*) in disallowedTools being silently ignored — they now correctly remove all tools from the named server
Commits

Updates `@anthropic-ai/sdk` from 0.104.1 to 0.105.0
Release notes

Sourced from @​anthropic-ai/sdk's releases.

sdk: v0.105.0

0.105.0 (2026-06-18)

Full Changelog: sdk-v0.104.2...sdk-v0.105.0

Features

  • api: add support for new code_execution_20260120 tool (8dc2b54)
  • stream: lazily parse partial tool json input (#99) (e55ceee)

Chores

  • internal/deps: bump swc to 1.15.40 (#97) (a1d4d75)
  • internal: use are the types wrong directly (#94) (3d362af)
  • tests: stop using deprecated models (#98) (65ae1af)

sdk: v0.104.2

0.104.2 (2026-06-15)

Full Changelog: sdk-v0.104.1...sdk-v0.104.2

Chores

  • api: remove retired models from API and SDKs (a942876)
Changelog

Sourced from @​anthropic-ai/sdk's changelog.

0.105.0 (2026-06-18)

Full Changelog: sdk-v0.104.2...sdk-v0.105.0

Features

  • api: add support for new code_execution_20260120 tool (8dc2b54)
  • stream: lazily parse partial tool json input (#99) (e55ceee)

Chores

  • internal/deps: bump swc to 1.15.40 (#97) (a1d4d75)
  • internal: use are the types wrong directly (#94) (3d362af)
  • tests: stop using deprecated models (#98) (65ae1af)

0.104.2 (2026-06-15)

Full Changelog: sdk-v0.104.1...sdk-v0.104.2

Chores

  • api: remove retired models from API and SDKs (a942876)
Commits
  • ab700dc chore: release main
  • a322517 feat(api): add support for new code_execution_20260120 tool
  • 65a0106 feat(stream): lazily parse partial tool json input (#99)
  • 384ab51 chore(tests): stop using deprecated models (#98)
  • a49a191 chore(internal/deps): bump swc to 1.15.40 (#97)
  • 7ac63f3 chore(internal): use are the types wrong directly (#94)
  • fbee0d1 chore: release main
  • e984ba4 chore(api): remove retired models from API and SDKs
  • See full diff in compare view

Updates `@clack/prompts` from 1.5.1 to 1.6.0
Release notes

Sourced from @​clack/prompts's releases.

@​clack/prompts@​1.6.0

Minor Changes

  • #568 f87933f Thanks @​florian-lefebvre! - Updates default formatter of note() to note dim lines anymore

    If you want the old behavior, provide a format() function:

    import { note } from '@clack/prompts';
    +import { styleText } from 'node:util';
    

    note( 'You can edit the file src/index.jsx', 'Next steps.'

    • { format: (text) => styleText('dim', text) } );
  • #567 cc6aab5 Thanks @​dreyfus92! - Add keyboard instruction footers to select, multiselect, and groupMultiselect in the active state, matching autocomplete. No option — always shown.

  • Patch Changes

    Changelog

    Sourced from @​clack/prompts's changelog.

    1.6.0

    Minor Changes

    • #568 f87933f Thanks @​florian-lefebvre! - Updates default formatter of note() to note dim lines anymore

      If you want the old behavior, provide a format() function:

      import { note } from '@clack/prompts';
      +import { styleText } from 'node:util';
      

      note( 'You can edit the file src/index.jsx', 'Next steps.'

      • { format: (text) => styleText('dim', text) } );
  • #567 cc6aab5 Thanks @​dreyfus92! - Add keyboard instruction footers to select, multiselect, and groupMultiselect in the active state, matching autocomplete. No option — always shown.

  • Patch Changes

    Commits

    Updates `ink` from 7.0.6 to 7.1.0
    Release notes

    Sourced from ink's releases.

    v7.1.0


    https://github.com/vadimdemedes/ink/compare/v7.0.6...v7.1.0

    Commits

    Updates `pg` from 8.21.0 to 8.22.0
    Changelog

    Sourced from pg's changelog.

    pg@8.22.0

    Commits

    Updates `posthog-node` from 5.37.0 to 5.38.2
    Release notes

    Sourced from posthog-node's releases.

    posthog-node@5.38.2

    5.38.2

    Patch Changes

    posthog-node@5.38.1

    5.38.1

    Patch Changes

    • #3886 e6d7fe2 Thanks @​marandaneto! - Stop sending deprecated no-op top-level type, library, and library_version fields in event batch payloads. Use properties.$lib and properties.$lib_version for SDK metadata; legacy queued library and library_version values are used as fallbacks when the official $ properties are missing. (2026-06-18)
    • Updated dependencies [e6d7fe2]:
      • @​posthog/core@​1.35.2

    posthog-node@5.38.0

    5.38.0

    Minor Changes

    • #3845 a0553b3 Thanks @​marandaneto! - Add setPersonProperties() and unsetPersonProperties() helpers to manage person properties from the Node.js SDK. (2026-06-16)

    Patch Changes

    posthog-node@5.37.1

    5.37.1

    Patch Changes

    Changelog

    Sourced from posthog-node's changelog.

    5.38.2

    Patch Changes

    5.38.1

    Patch Changes

    • #3886 e6d7fe2 Thanks @​marandaneto! - Stop sending deprecated no-op top-level type, library, and library_version fields in event batch payloads. Use properties.$lib and properties.$lib_version for SDK metadata; legacy queued library and library_version values are used as fallbacks when the official $ properties are missing. (2026-06-18)
    • Updated dependencies [e6d7fe2]:
      • @​posthog/core@​1.35.2

    5.38.0

    Minor Changes

    • #3845 a0553b3 Thanks @​marandaneto! - Add setPersonProperties() and unsetPersonProperties() helpers to manage person properties from the Node.js SDK. (2026-06-16)

    Patch Changes

    5.37.1

    Patch Changes

    Commits
    • b0bd00f chore: update versions and lockfile [version bump]
    • 6b21f77 fix: validate custom event UUIDs (#3903)
    • 229efff chore: update versions and lockfile [version bump]
    • e6d7fe2 fix: remove ignored batch metadata fields (#3886)
    • f495510 chore: update versions and lockfile [version bump]
    • bd07ec4 feat(flags): add disableRemoteFeatureFlags option and runtime updateFlags (#3...
    • 4ff3bb3 chore: update versions and lockfile [version bump]
    • a0553b3 feat(node): add person property helpers (#3845)
    • 70d3dde chore: Generate versioned references only on release (#3858)
    • 47aea13 chore: update versions and lockfile [version bump]
    • Additional commits viewable in compare view

    Updates `fumadocs-core` from 16.10.2 to 16.10.5
    Commits

    Updates `fumadocs-ui` from 16.10.2 to 16.10.5
    Release notes

    Sourced from fumadocs-ui's releases.

    fumadocs-ui@16.10.3

    Patch Changes

    • 5499f59: type-safe provider props
      • fumadocs-core@16.10.3
    Commits

    Updates `@types/node` from 25.9.3 to 26.0.0
    Commits

    Updates `@effect/atom-react` from 4.0.0-beta.83 to 4.0.0-beta.85
    Changelog

    Sourced from @​effect/atom-react's changelog.

    4.0.0-beta.85

    Patch Changes

    4.0.0-beta.84

    Patch Changes

    Commits

    Updates `@effect/platform-bun` from 4.0.0-beta.83 to 4.0.0-beta.85
    Commits

    Updates `@effect/platform-node` from 4.0.0-beta.83 to 4.0.0-beta.85
    Commits

    Updates `@effect/sql-pg` from 4.0.0-beta.83 to 4.0.0-beta.85
    Commits

    Updates `@effect/vitest` from 4.0.0-beta.84 to 4.0.0-beta.85
    Commits

    Updates `@nx/devkit` from 22.7.5 to 23.0.0
    Release notes

    Sourced from @​nx/devkit's releases.

    23.0.0 (2026-06-16)

    🚀 Features