diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2e4f967307..cb2ebe6d72 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -33,7 +33,7 @@ updates: patterns: - "*" - package-ecosystem: "docker" - directory: "pkg/config/templates" + directory: "/apps/cli-go/pkg/config/templates" schedule: interval: "cron" cronjob: "0 0 * * *" diff --git a/.github/workflows/cli-go-mirror-image.yml b/.github/workflows/cli-go-mirror-image.yml index 92250bd79f..d3e34f2bf3 100644 --- a/.github/workflows/cli-go-mirror-image.yml +++ b/.github/workflows/cli-go-mirror-image.yml @@ -15,8 +15,6 @@ on: description: "org/image:tag" required: true type: string - paths: - - apps/cli-go/** permissions: contents: read @@ -50,6 +48,3 @@ jobs: dst: | public.ecr.aws/supabase/${{ steps.strip.outputs.image }} ghcr.io/supabase/${{ steps.strip.outputs.image }} -defaults: - run: - working-directory: apps/cli-go diff --git a/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___network_restrictions/2.response.json b/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___network_restrictions/2.response.json index dfb40cd92e..4a9ee4c083 100644 --- a/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___network_restrictions/2.response.json +++ b/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___network_restrictions/2.response.json @@ -12,6 +12,7 @@ "config": { "dbAllowedCidrs": ["0.0.0.0/0"], "dbAllowedCidrsV6": ["::/0"] - } + }, + "status": "applied" } } diff --git a/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___network_restrictions/4.response.json b/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___network_restrictions/4.response.json index cba240d2d5..88e8640a1c 100644 --- a/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___network_restrictions/4.response.json +++ b/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___network_restrictions/4.response.json @@ -12,6 +12,7 @@ "config": { "dbAllowedCidrs": ["0.0.0.0/0"], "dbAllowedCidrsV6": ["::/0"] - } + }, + "status": "applied" } } diff --git a/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___network_restrictions/default.response.json b/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___network_restrictions/default.response.json index 2766a91b1c..0e3cadf24b 100644 --- a/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___network_restrictions/default.response.json +++ b/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___network_restrictions/default.response.json @@ -12,6 +12,7 @@ "config": { "dbAllowedCidrs": ["0.0.0.0/0"], "dbAllowedCidrsV6": ["::/0"] - } + }, + "status": "applied" } } diff --git a/apps/cli-e2e/fixtures/scenarios/branches-get-returns-json-output-with-output-json/interactions.json b/apps/cli-e2e/fixtures/scenarios/branches-get-returns-json-output-with-output-json/interactions.json index 6a6fa3d0b4..7498935db3 100644 --- a/apps/cli-e2e/fixtures/scenarios/branches-get-returns-json-output-with-output-json/interactions.json +++ b/apps/cli-e2e/fixtures/scenarios/branches-get-returns-json-output-with-output-json/interactions.json @@ -125,6 +125,7 @@ "body": [ { "connection_string": "postgresql://postgres:password@my-branch.supabase.co:5432/postgres", + "connectionString": "postgresql://postgres:password@my-branch.supabase.co:5432/postgres", "database_type": "PRIMARY", "db_host": "my-branch.supabase.co", "db_name": "postgres", diff --git a/apps/cli-e2e/fixtures/scenarios/network-restrictions-get-renders-fixture-data-in-output/interactions.json b/apps/cli-e2e/fixtures/scenarios/network-restrictions-get-renders-fixture-data-in-output/interactions.json index bb472e2e01..40e570e6a1 100644 --- a/apps/cli-e2e/fixtures/scenarios/network-restrictions-get-renders-fixture-data-in-output/interactions.json +++ b/apps/cli-e2e/fixtures/scenarios/network-restrictions-get-renders-fixture-data-in-output/interactions.json @@ -26,7 +26,8 @@ "config": { "dbAllowedCidrs": ["0.0.0.0/0"], "dbAllowedCidrsV6": ["::/0"] - } + }, + "status": "applied" } } }, diff --git a/apps/cli-e2e/fixtures/scenarios/network-restrictions-get-returns-json-output-with-output-json/interactions.json b/apps/cli-e2e/fixtures/scenarios/network-restrictions-get-returns-json-output-with-output-json/interactions.json index 11e99a6ef3..58a0472b91 100644 --- a/apps/cli-e2e/fixtures/scenarios/network-restrictions-get-returns-json-output-with-output-json/interactions.json +++ b/apps/cli-e2e/fixtures/scenarios/network-restrictions-get-returns-json-output-with-output-json/interactions.json @@ -26,7 +26,8 @@ "config": { "dbAllowedCidrs": ["0.0.0.0/0"], "dbAllowedCidrsV6": ["::/0"] - } + }, + "status": "applied" } } }, diff --git a/apps/cli-e2e/package.json b/apps/cli-e2e/package.json index c493f0694d..6a5908fab7 100644 --- a/apps/cli-e2e/package.json +++ b/apps/cli-e2e/package.json @@ -66,7 +66,8 @@ } }, "implicitDependencies": [ - "cli-go" + "cli-go", + "supabase" ] } } diff --git a/apps/cli-go/internal/db/reset/reset.go b/apps/cli-go/internal/db/reset/reset.go index d86f344975..7d841f4ba3 100644 --- a/apps/cli-go/internal/db/reset/reset.go +++ b/apps/cli-go/internal/db/reset/reset.go @@ -146,7 +146,10 @@ func initDatabase(ctx context.Context, options ...func(*pgx.ConnConfig)) error { return err } defer conn.Close(context.Background()) - return start.InitSchema14(ctx, conn) + if err := start.InitSchema14(ctx, conn); err != nil { + return err + } + return start.ApplyApiPrivileges(ctx, conn) } // Recreate postgres database by connecting to template1 diff --git a/apps/cli-go/internal/db/start/start.go b/apps/cli-go/internal/db/start/start.go index cf9cdc8dfd..6f1eeacde3 100644 --- a/apps/cli-go/internal/db/start/start.go +++ b/apps/cli-go/internal/db/start/start.go @@ -384,6 +384,9 @@ func SetupDatabase(ctx context.Context, conn *pgx.Conn, host string, w io.Writer if err := initSchema(ctx, conn, host, w); err != nil { return err } + if err := ApplyApiPrivileges(ctx, conn); err != nil { + return err + } // Create vault secrets first so roles.sql can reference them if err := vault.UpsertVaultSecrets(ctx, utils.Config.Db.Vault, conn); err != nil { return err @@ -394,3 +397,38 @@ func SetupDatabase(ctx context.Context, conn *pgx.Conn, host string, w io.Writer } return err } + +// RevokeDefaultDataApiPrivilegesSql matches the SQL that Studio runs at cloud project creation +// when the "Default privileges for new entities" toggle is off. It removes the default GRANTs +// applied by the initial schema so newly-created entities in `public` owned by `postgres` are +// not exposed through the Data API roles until explicit GRANTs are issued. +const RevokeDefaultDataApiPrivilegesSql = ` +alter default privileges for role postgres in schema public + revoke select, insert, update, delete on tables from anon, authenticated, service_role; +alter default privileges for role postgres in schema public + revoke usage, select on sequences from anon, authenticated, service_role; +alter default privileges for role postgres in schema public + revoke execute on functions from anon, authenticated, service_role; +` + +// ApplyApiPrivileges adjusts the default privileges on the `public` schema to match the +// `[api].auto_expose_new_tables` flag in config.toml. The flag is tri-state to give users a +// safe migration window: +// +// - unset (default today): keep the bundled initial-schema GRANTs in place, so local matches +// long-standing behaviour. This implicit default flips to false on May 30, 2026, and the +// flag is removed entirely in October 2026 (always-revoked behaviour). +// - true: explicit opt-in to today's behaviour. Treated identically to unset for now; from +// May 30 the CLI will warn that the flag is being deprecated. +// - false: revoke the default Data API GRANTs so newly-created entities in `public` require +// explicit GRANTs to surface through the Data API, matching the new cloud default. +func ApplyApiPrivileges(ctx context.Context, conn *pgx.Conn) error { + if utils.Config.Api.AutoExposeNewTables == nil || *utils.Config.Api.AutoExposeNewTables { + return nil + } + file, err := migration.NewMigrationFromReader(strings.NewReader(RevokeDefaultDataApiPrivilegesSql)) + if err != nil { + return err + } + return file.ExecBatch(ctx, conn) +} diff --git a/apps/cli-go/internal/db/start/start_test.go b/apps/cli-go/internal/db/start/start_test.go index 39f23d8bad..b4d6411bcd 100644 --- a/apps/cli-go/internal/db/start/start_test.go +++ b/apps/cli-go/internal/db/start/start_test.go @@ -259,6 +259,43 @@ func TestSetupDatabase(t *testing.T) { assert.Empty(t, apitest.ListUnmatchedRequests()) }) + t.Run("revokes default data api privileges when auto_expose_new_tables is false", func(t *testing.T) { + utils.Config.Db.MajorVersion = 14 + flag := false + utils.Config.Api.AutoExposeNewTables = &flag + defer func() { + utils.Config.Db.MajorVersion = 15 + utils.Config.Api.AutoExposeNewTables = nil + }() + utils.Config.Db.Port = 5432 + utils.GlobalsSql = "create schema public" + utils.InitialSchemaPg14Sql = "create schema private" + // Setup in-memory fs + fsys := afero.NewMemMapFs() + roles := "create role postgres" + require.NoError(t, afero.WriteFile(fsys, utils.CustomRolesPath, []byte(roles), 0644)) + // Setup mock postgres: the revoke SQL must execute between the initial schema and roles.sql + conn := pgtest.NewConn() + defer conn.Close(t) + conn.Query(utils.GlobalsSql). + Reply("CREATE SCHEMA"). + Query(utils.InitialSchemaPg14Sql). + Reply("CREATE SCHEMA"). + Query("alter default privileges for role postgres in schema public\n revoke select, insert, update, delete on tables from anon, authenticated, service_role"). + Reply("ALTER DEFAULT PRIVILEGES"). + Query("alter default privileges for role postgres in schema public\n revoke usage, select on sequences from anon, authenticated, service_role"). + Reply("ALTER DEFAULT PRIVILEGES"). + Query("alter default privileges for role postgres in schema public\n revoke execute on functions from anon, authenticated, service_role"). + Reply("ALTER DEFAULT PRIVILEGES"). + Query(roles). + Reply("CREATE ROLE") + // Run test + err := SetupLocalDatabase(context.Background(), "", fsys, io.Discard, conn.Intercept) + // Check error + assert.NoError(t, err) + assert.Empty(t, apitest.ListUnmatchedRequests()) + }) + t.Run("throws error on connect failure", func(t *testing.T) { utils.Config.Db.Port = 0 // Run test diff --git a/apps/cli-go/internal/seed/buckets/buckets.go b/apps/cli-go/internal/seed/buckets/buckets.go index afad6a36db..2647032ce7 100644 --- a/apps/cli-go/internal/seed/buckets/buckets.go +++ b/apps/cli-go/internal/seed/buckets/buckets.go @@ -11,7 +11,9 @@ import ( ) func Run(ctx context.Context, projectRef string, interactive bool, fsys afero.Fs) error { - if len(projectRef) == 0 && len(utils.Config.Storage.Buckets) == 0 { + if len(projectRef) == 0 && + len(utils.Config.Storage.Buckets) == 0 && + !utils.Config.Storage.VectorBuckets.Enabled { return nil } api, err := client.NewStorageAPI(ctx, projectRef) @@ -47,7 +49,7 @@ func Run(ctx context.Context, projectRef string, interactive bool, fsys afero.Fs return err } } - if utils.Config.Storage.VectorBuckets.Enabled && len(projectRef) > 0 { + if utils.Config.Storage.VectorBuckets.Enabled { fmt.Fprintln(os.Stderr, "Updating vector buckets...") if err := api.UpsertVectorBuckets(ctx, utils.Config.Storage.VectorBuckets.Buckets, prune); err != nil { return err diff --git a/apps/cli-go/internal/seed/buckets/buckets_test.go b/apps/cli-go/internal/seed/buckets/buckets_test.go index 687f904b2a..733a34271a 100644 --- a/apps/cli-go/internal/seed/buckets/buckets_test.go +++ b/apps/cli-go/internal/seed/buckets/buckets_test.go @@ -74,4 +74,51 @@ public = false` assert.Len(t, gock.Pending(), 1) assert.Empty(t, apitest.ListUnmatchedRequests()) }) + + t.Run("seeds vector buckets locally", func(t *testing.T) { + t.Cleanup(func() { + utils.Config.Storage.VectorBuckets.Enabled = false + clear(utils.Config.Storage.VectorBuckets.Buckets) + gock.OffAll() + }) + utils.Config.Storage.VectorBuckets.Enabled = true + utils.Config.Storage.VectorBuckets.Buckets = map[string]struct{}{ + "documents-openai": {}, + "existing-vec": {}, + } + // Setup mock api: regular buckets list is empty, vector list has one + // configured bucket plus one stale bucket that should be left alone + // because non-interactive prune defaults to false. + gock.New(utils.Config.Api.ExternalUrl). + Get("/storage/v1/bucket"). + Reply(http.StatusOK). + JSON([]storage.BucketResponse{}) + gock.New(utils.Config.Api.ExternalUrl). + Post("/storage/v1/vector/ListVectorBuckets"). + Reply(http.StatusOK). + JSON(storage.ListVectorBucketsResponse{ + VectorBuckets: []storage.VectorBucket{ + {VectorBucketName: "existing-vec"}, + {VectorBucketName: "stale-vec"}, + }, + }) + gock.New(utils.Config.Api.ExternalUrl). + Post("/storage/v1/vector/CreateVectorBucket"). + Reply(http.StatusOK). + JSON(map[string]string{}) + gock.New(utils.Config.Api.ExternalUrl). + Post("/storage/v1/vector/DeleteVectorBucket"). + Reply(http.StatusOK). + JSON(map[string]string{}) + // Run test + err := Run(context.Background(), "", false, afero.NewMemMapFs()) + // Check error + assert.NoError(t, err) + assert.Empty(t, apitest.ListUnmatchedRequests()) + // The DeleteVectorBucket mock should remain pending because non-interactive + // prune returns the default (false) and skips the delete. + pending := gock.Pending() + require.Len(t, pending, 1) + assert.Contains(t, pending[0].Request().URLStruct.Path, "DeleteVectorBucket") + }) } diff --git a/apps/cli-go/internal/start/start.go b/apps/cli-go/internal/start/start.go index fb9c886372..88ab8e7ed3 100644 --- a/apps/cli-go/internal/start/start.go +++ b/apps/cli-go/internal/start/start.go @@ -251,6 +251,7 @@ func run(ctx context.Context, fsys afero.Fs, excludedContainers []string, dbConf isImgProxyEnabled := utils.Config.Storage.ImageTransformation != nil && utils.Config.Storage.ImageTransformation.Enabled && !isContainerExcluded(utils.Config.Storage.ImgProxyImage, excluded) isS3ProtocolEnabled := utils.Config.Storage.S3Protocol != nil && utils.Config.Storage.S3Protocol.Enabled + isVectorBucketsEnabled := utils.Config.Storage.VectorBuckets.Enabled fmt.Fprintln(os.Stderr, "Starting containers...") workdir, err := os.Getwd() @@ -938,35 +939,39 @@ EOF // Start Storage. if isStorageEnabled { dockerStoragePath := "/mnt" + storageEnv := []string{ + "DB_MIGRATIONS_FREEZE_AT=" + utils.Config.Storage.TargetMigration, + "ANON_KEY=" + utils.Config.Auth.AnonKey.Value, + "SERVICE_KEY=" + utils.Config.Auth.ServiceRoleKey.Value, + "AUTH_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value, + fmt.Sprintf("JWT_JWKS=%s", jwks), + fmt.Sprintf("DATABASE_URL=postgresql://supabase_storage_admin:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database), + fmt.Sprintf("FILE_SIZE_LIMIT=%v", utils.Config.Storage.FileSizeLimit), + "STORAGE_BACKEND=file", + "FILE_STORAGE_BACKEND_PATH=" + dockerStoragePath, + "TENANT_ID=stub", + // TODO: https://github.com/supabase/storage-api/issues/55 + "STORAGE_S3_REGION=" + utils.Config.Storage.S3Credentials.Region, + "GLOBAL_S3_BUCKET=stub", + fmt.Sprintf("ENABLE_IMAGE_TRANSFORMATION=%t", isImgProxyEnabled), + fmt.Sprintf("IMGPROXY_URL=http://%s:5001", utils.ImgProxyId), + "TUS_URL_PATH=/storage/v1/upload/resumable", + fmt.Sprintf("S3_PROTOCOL_ENABLED=%t", isS3ProtocolEnabled), + "S3_PROTOCOL_ACCESS_KEY_ID=" + utils.Config.Storage.S3Credentials.AccessKeyId, + "S3_PROTOCOL_ACCESS_KEY_SECRET=" + utils.Config.Storage.S3Credentials.SecretAccessKey, + "S3_PROTOCOL_PREFIX=/storage/v1", + "UPLOAD_FILE_SIZE_LIMIT=52428800000", + "UPLOAD_FILE_SIZE_LIMIT_STANDARD=5242880000", + "SIGNED_UPLOAD_URL_EXPIRATION_TIME=7200", + } + if isVectorBucketsEnabled { + storageEnv = appendStorageVectorEnv(storageEnv, dbConfig) + } if _, err := utils.DockerStart( ctx, container.Config{ Image: utils.Config.Storage.Image, - Env: []string{ - "DB_MIGRATIONS_FREEZE_AT=" + utils.Config.Storage.TargetMigration, - "ANON_KEY=" + utils.Config.Auth.AnonKey.Value, - "SERVICE_KEY=" + utils.Config.Auth.ServiceRoleKey.Value, - "AUTH_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value, - fmt.Sprintf("JWT_JWKS=%s", jwks), - fmt.Sprintf("DATABASE_URL=postgresql://supabase_storage_admin:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database), - fmt.Sprintf("FILE_SIZE_LIMIT=%v", utils.Config.Storage.FileSizeLimit), - "STORAGE_BACKEND=file", - "FILE_STORAGE_BACKEND_PATH=" + dockerStoragePath, - "TENANT_ID=stub", - // TODO: https://github.com/supabase/storage-api/issues/55 - "STORAGE_S3_REGION=" + utils.Config.Storage.S3Credentials.Region, - "GLOBAL_S3_BUCKET=stub", - fmt.Sprintf("ENABLE_IMAGE_TRANSFORMATION=%t", isImgProxyEnabled), - fmt.Sprintf("IMGPROXY_URL=http://%s:5001", utils.ImgProxyId), - "TUS_URL_PATH=/storage/v1/upload/resumable", - fmt.Sprintf("S3_PROTOCOL_ENABLED=%t", isS3ProtocolEnabled), - "S3_PROTOCOL_ACCESS_KEY_ID=" + utils.Config.Storage.S3Credentials.AccessKeyId, - "S3_PROTOCOL_ACCESS_KEY_SECRET=" + utils.Config.Storage.S3Credentials.SecretAccessKey, - "S3_PROTOCOL_PREFIX=/storage/v1", - "UPLOAD_FILE_SIZE_LIMIT=52428800000", - "UPLOAD_FILE_SIZE_LIMIT_STANDARD=5242880000", - "SIGNED_UPLOAD_URL_EXPIRATION_TIME=7200", - }, + Env: storageEnv, Healthcheck: &container.HealthConfig{ // For some reason, localhost resolves to IPv6 address on GitPod which breaks healthcheck. Test: []string{ @@ -1371,6 +1376,42 @@ func appendGotrueExternalProviderEnv(env []string) []string { return env } +// appendStorageVectorEnv wires the storage container with the vector-bucket +// env contract from supabase/storage#1094. The CLI provides three CLI-owned +// defaults that the operator can override from their shell environment: +// +// - VECTOR_BUCKET_PROVIDER selects the local provider; pgvector is the only +// locally-available implementation. +// - VECTOR_STORE_MIGRATIONS_ENABLED tells storage to run its vector-store +// migrations on boot. Defaults on so a fresh stack is usable, but operators +// who run those migrations out of band can disable it. +// - VECTOR_DATABASE_URL hands storage a connection string with createdb +// permission so it can manage its own vectors database. We default to the +// postgres superuser on the local stack, mirroring the existing DATABASE_URL +// credentials, but operators are expected to override this to reach an +// external postgres in self-hosted setups. +func appendStorageVectorEnv(env []string, dbConfig pgconn.Config) []string { + envOrDefault := func(key, def string) string { + if v, ok := os.LookupEnv(key); ok { + return key + "=" + v + } + return key + "=" + def + } + defaultVectorURL := fmt.Sprintf( + "postgresql://postgres:%s@%s:%d/%s", + dbConfig.Password, + dbConfig.Host, + dbConfig.Port, + dbConfig.Database, + ) + return append(env, + envOrDefault("VECTOR_ENABLED", "true"), + envOrDefault("VECTOR_BUCKET_PROVIDER", "pgvector"), + envOrDefault("VECTOR_STORE_MIGRATIONS_ENABLED", "true"), + envOrDefault("VECTOR_DATABASE_URL", defaultVectorURL), + ) +} + func printSecurityNotice() { fmt.Fprintln(os.Stderr, utils.Yellow("Local dev security notice")) fmt.Fprintln(os.Stderr, "All services bind to 0.0.0.0 (network-accessible, not just localhost)") diff --git a/apps/cli-go/internal/telemetry/service.go b/apps/cli-go/internal/telemetry/service.go index 39e0b6c073..26869ad5a8 100644 --- a/apps/cli-go/internal/telemetry/service.go +++ b/apps/cli-go/internal/telemetry/service.go @@ -154,7 +154,11 @@ func (s *Service) ClearDistinctID() error { } func (s *Service) NeedsIdentityStitch() bool { - return s != nil && s.state.DistinctID == "" && s.canSend() + return s != nil && s.state.DistinctID == "" && s.canSend() && !s.isEphemeralIdentityRuntime() +} + +func (s *Service) isEphemeralIdentityRuntime() bool { + return s.isCI || (s.isFirstRun && !s.isTTY) } func (s *Service) GroupIdentify(groupType string, groupKey string, properties map[string]any) error { diff --git a/apps/cli-go/internal/telemetry/service_test.go b/apps/cli-go/internal/telemetry/service_test.go index c46a793bed..5a19198c83 100644 --- a/apps/cli-go/internal/telemetry/service_test.go +++ b/apps/cli-go/internal/telemetry/service_test.go @@ -222,6 +222,7 @@ func TestServiceNeedsIdentityStitch(t *testing.T) { service, err := NewService(fsys, Options{ Analytics: analytics, Now: func() time.Time { return now }, + IsTTY: true, }) require.NoError(t, err) @@ -233,6 +234,45 @@ func TestServiceNeedsIdentityStitch(t *testing.T) { require.NoError(t, service.StitchLogin("user-123")) assert.False(t, service.NeedsIdentityStitch()) }) + + t.Run("false in CI even with empty DistinctID", func(t *testing.T) { + ciFsys := afero.NewMemMapFs() + ciService, err := NewService(ciFsys, Options{ + Analytics: &fakeAnalytics{enabled: true}, + Now: func() time.Time { return now }, + IsCI: true, + }) + require.NoError(t, err) + assert.False(t, ciService.NeedsIdentityStitch()) + }) + + t.Run("false in first-run non-TTY runtime", func(t *testing.T) { + ephemeralFsys := afero.NewMemMapFs() + ephemeralService, err := NewService(ephemeralFsys, Options{ + Analytics: &fakeAnalytics{enabled: true}, + Now: func() time.Time { return now }, + }) + require.NoError(t, err) + assert.False(t, ephemeralService.NeedsIdentityStitch()) + }) + + t.Run("true in persisted non-TTY runtime", func(t *testing.T) { + persistedFsys := afero.NewMemMapFs() + require.NoError(t, SaveState(State{ + Enabled: true, + DeviceID: uuid.NewString(), + SessionID: uuid.NewString(), + SessionLastActive: now, + SchemaVersion: SchemaVersion, + }, persistedFsys)) + + persistedService, err := NewService(persistedFsys, Options{ + Analytics: &fakeAnalytics{enabled: true}, + Now: func() time.Time { return now }, + }) + require.NoError(t, err) + assert.True(t, persistedService.NeedsIdentityStitch()) + }) } func TestServiceCaptureHonorsConsentAndEnvOptOut(t *testing.T) { diff --git a/apps/cli-go/pkg/config/api.go b/apps/cli-go/pkg/config/api.go index 3fd3b6f187..2fdcabe103 100644 --- a/apps/cli-go/pkg/config/api.go +++ b/apps/cli-go/pkg/config/api.go @@ -14,6 +14,15 @@ type ( Schemas []string `toml:"schemas" json:"schemas"` ExtraSearchPath []string `toml:"extra_search_path" json:"extra_search_path"` MaxRows uint `toml:"max_rows" json:"max_rows"` + // When unset (default today), new tables, views, sequences and functions created in + // the `public` schema by `postgres` are automatically reachable through the Data API + // roles `anon`, `authenticated`, and `service_role`, matching long-standing local + // behaviour. Set to false to match the new cloud default and require explicit GRANTs + // to expose entities through the Data API; set to true to opt out of the upcoming + // transition once the platform default flips. Stored as a pointer so the migration + // path (unset -> default true today, default false from May 30, removed in October) + // can flip the implicit value without losing the explicit user choice. + AutoExposeNewTables *bool `toml:"auto_expose_new_tables,omitempty" json:"auto_expose_new_tables,omitempty"` // Local only config Image string `toml:"-" json:"-"` KongImage string `toml:"-" json:"-"` diff --git a/apps/cli-go/pkg/config/api_test.go b/apps/cli-go/pkg/config/api_test.go index 92859c671c..ff5bb4a104 100644 --- a/apps/cli-go/pkg/config/api_test.go +++ b/apps/cli-go/pkg/config/api_test.go @@ -134,3 +134,10 @@ func TestApiDiff(t *testing.T) { assertSnapshotEqual(t, diff) }) } + +func TestApiAutoExposeNewTablesDefault(t *testing.T) { + t.Run("is unset on a fresh config so today's implicit-true behaviour applies", func(t *testing.T) { + cfg := NewConfig() + assert.Nil(t, cfg.Api.AutoExposeNewTables) + }) +} diff --git a/apps/cli-go/pkg/config/templates/Dockerfile b/apps/cli-go/pkg/config/templates/Dockerfile index 99efdf2cf8..fa434fad4a 100644 --- a/apps/cli-go/pkg/config/templates/Dockerfile +++ b/apps/cli-go/pkg/config/templates/Dockerfile @@ -1,19 +1,19 @@ # Exposed for updates by .github/dependabot.yml -FROM supabase/postgres:17.6.1.106 AS pg +FROM supabase/postgres:17.6.1.131 AS pg # Append to ServiceImages when adding new dependencies below FROM library/kong:2.8.1 AS kong FROM axllent/mailpit:v1.22.3 AS mailpit -FROM postgrest/postgrest:v14.10 AS postgrest -FROM supabase/postgres-meta:v0.96.4 AS pgmeta -FROM supabase/studio:2026.04.28-sha-89d08a2 AS studio +FROM postgrest/postgrest:v14.12 AS postgrest +FROM supabase/postgres-meta:v0.96.6 AS pgmeta +FROM supabase/studio:2026.05.25-sha-65c570e AS studio FROM darthsim/imgproxy:v3.8.0 AS imgproxy -FROM supabase/edge-runtime:v1.73.13 AS edgeruntime +FROM supabase/edge-runtime:v1.74.0 AS edgeruntime FROM timberio/vector:0.53.0-alpine AS vector -FROM supabase/supavisor:2.7.4 AS supavisor -FROM supabase/gotrue:v2.188.1 AS gotrue -FROM supabase/realtime:v2.86.3 AS realtime -FROM supabase/storage-api:v1.54.1 AS storage -FROM supabase/logflare:1.39.1 AS logflare +FROM supabase/supavisor:2.9.5 AS supavisor +FROM supabase/gotrue:v2.189.0 AS gotrue +FROM supabase/realtime:v2.100.0 AS realtime +FROM supabase/storage-api:v1.60.0 AS storage +FROM supabase/logflare:1.42.0 AS logflare # Append to JobImages when adding new dependencies below FROM supabase/pgadmin-schema-diff:cli-0.0.5 AS differ FROM supabase/migra:3.0.1663481299 AS migra diff --git a/apps/cli-go/pkg/config/templates/config.toml b/apps/cli-go/pkg/config/templates/config.toml index d34c36c9ba..c172cc4f4e 100644 --- a/apps/cli-go/pkg/config/templates/config.toml +++ b/apps/cli-go/pkg/config/templates/config.toml @@ -16,6 +16,12 @@ extra_search_path = ["public", "extensions"] # The maximum number of rows returns from a view, table, or stored procedure. Limits payload size # for accidental or malicious requests. max_rows = 1000 +# Controls whether new tables, views, sequences and functions created in the `public` schema by +# `postgres` are reachable through the Data API roles (`anon`, `authenticated`, `service_role`) +# without explicit GRANTs. Leave unset today to preserve local behaviour. The implicit default +# flips to `false` on 2026-05-30 to match the new cloud default, and the field is removed in +# 2026-10-30 once the always-revoked behaviour is permanent. Set to `false` to opt in early. +# auto_expose_new_tables = false [api.tls] # Enable HTTPS endpoints locally using a self-signed certificate. @@ -138,7 +144,6 @@ max_catalogs = 2 # [storage.analytics.buckets.my-warehouse] # Store vector embeddings in S3 for large and durable datasets -# This feature is only available on the hosted platform. [storage.vector] enabled = false max_buckets = 10 diff --git a/apps/cli/AGENTS.md b/apps/cli/AGENTS.md index e59fed8f5c..dc2d2382b6 100644 --- a/apps/cli/AGENTS.md +++ b/apps/cli/AGENTS.md @@ -88,6 +88,17 @@ Always check `src/shared/` before writing new infrastructure. Do not duplicate w | `shared/runtime/` | `Browser`, `Stdin`, `Tty`, `ProcessControl`, `RuntimeInfo` services + layers | | `shared/telemetry/` | `withCommandInstrumentation`, `Analytics`, tracing | +Also check the following `legacy/` infrastructure before writing equivalent helpers from scratch: + +| Path | What it provides | +| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `legacy/config/legacy-cli-config.layer.ts` | `LegacyCliConfig` — resolves `SUPABASE_PROFILE` (built-in name **or** YAML file path), `--workdir`, `--experimental`, project-id from `supabase/config.toml` | +| `legacy/config/legacy-project-ref.layer.ts` | `LegacyProjectRefResolver` — `--project-ref` flag → env → linked-project.json → config fallback chain; matches Go's resolver order | +| `legacy/telemetry/legacy-telemetry-state.layer.ts` | `LegacyTelemetryState.flush` — writes `~/.supabase/telemetry.json`, runs in every command's `Effect.ensuring` | +| `legacy/telemetry/legacy-linked-project-cache.layer.ts` | `LegacyLinkedProjectCache.cache(ref)` — writes `~/.supabase//linked-project.json` after `--project-ref` resolves; bypasses generated schema validation (uses raw HTTP client) | +| `legacy/auth/legacy-http-debug.layer.ts` | `legacyHttpClientLayer` — wraps the HTTP transport with a `--debug` stderr logger in Go's `log.LstdFlags` format | +| `legacy/output/legacy-glamour-table.ts` | `renderGlamourTable(headers, rows)` — byte-exact ASCII match for Go's `glamour.RenderTable(..., AsciiStyle)` | + --- ## Phase 0: Go Binary Wrapper @@ -139,6 +150,21 @@ src/legacy/commands// SIDE_EFFECTS.md # Required for every legacy command — see section below ``` +When a command grows beyond a single handler file, follow the optional helper-file shape that emerged from the backups port: + +``` +src/legacy/commands// + .command.ts # Effect CLI Command + flag wiring + layer provide + .handler.ts # native Effect handler + .errors.ts # Data.TaggedError types + .layers.ts # runtime layer composition for the command family + .format.ts # text formatters (timestamps, regions, booleans) + .encoders.ts # Go-compatible JSON / YAML / TOML / env encoders + SIDE_EFFECTS.md +``` + +The `.format.ts` and `.encoders.ts` files should be pure functions with no Effect or service dependencies — that keeps them unit-testable and makes Go-parity rules explicit (e.g. JSON key sort order, env-var SCREAMING_SNAKE_CASE flattening, empty arrays coerced to null). + Commands with subcommands use nested directories: ``` @@ -192,6 +218,27 @@ Many Management API commands in `next/commands/` have already been implemented. --- +## Legacy Port: Hoist Before You Duplicate + +Before writing handler code for a new port, scan the already-ported commands for overlapping logic. If two commands need the same helper (HTTP-error mapping, output encoder, formatter, runtime layer composition), hoist it instead of inlining a copy. + +Decision rule: + +- **Used by one command only** → keep it in the command's own directory (e.g. `backups/backups.errors.ts`). +- **Used by ≥2 commands in the same command family** → keep it in the family root (e.g. `backups/backups.encoders.ts` is shared by `list` and `restore`). +- **Used by ≥2 commands across families** → hoist to `src/legacy/shared/` (create the directory if it doesn't exist) and refactor the existing call sites in the same change. Do not leave the older command using its inlined copy while the new command uses the hoisted version. + +Concrete examples worth watching for as more commands land: + +- HTTP-error → tagged-error mapping (`backups.errors.ts:mapLegacyBackupHttpError`) — almost every Management API command will need this shape. +- Go-compatible JSON / YAML / TOML / env encoders (`backups.encoders.ts`) — the flag `--output {json,yaml,toml,env}` is supported by many Go subcommands. +- Glamour-table rendering helpers and column padding — currently in `legacy/output/legacy-glamour-table.ts`, already correctly hoisted. +- Timestamp / region / boolean formatters (`backups.format.ts`) — likely shared the moment a second command renders a backup/project/region field. + +This rule is consistent with the repo-wide **Refactoring Policy** ("delete obsolete helpers, shims, and parallel code paths as part of the refactor") — it just makes the policy concrete for the legacy-port workflow. + +--- + ## Legacy Port: Go CLI Output Parity The legacy shell is a **strict 1:1 port** — not a redesign. The compatibility contract covers: @@ -206,6 +253,50 @@ When in doubt about expected output or behavior, run the equivalent command agai --- +## Legacy Port: Go Parity Checklist + +When porting a Management-API-style command, verify each item before marking the command as `ported`: + +1. **Telemetry + linked-project writes run on every invocation** — Go uses `PersistentPostRun` (see `apps/cli-go/cmd/root.go:176`). Wrap the handler body in `.pipe(Effect.ensuring(linkedProjectCache.cache(ref)), Effect.ensuring(telemetryState.flush))` so both files are written on success **and** failure. See `backups/list/list.handler.ts:74-114` as the canonical pattern. + +2. **Errors go to stderr in text mode, byte-matching Go's template** — `Output.fail` now writes a frame-free message to stderr followed by the "Try rerunning the command with --debug to get more details." suggestion when `--debug` is unset. Don't reintroduce clack's `■ … │` frame. Reference: commits `ee041834`, `cf4f574b`. + +3. **`--debug` logs every HTTP request on stderr** — Format `"HTTP YYYY/MM/DD HH:MM:SS : \n"` (Go's `log.LstdFlags|log.Lmsgprefix`). Provided automatically by `legacyHttpClientLayer`; ensure that layer (not the raw `HttpClient.layer`) is what every legacy command's runtime composes. Reference: commit `39cfec20`. + +4. **`SUPABASE_PROFILE` is dual-mode** — accept either a built-in name (`supabase`, `supabase-staging`, `supabase-local`) **or** a filesystem path to a YAML file with `api_url:` / `gotrue_url:` / `db_url:` keys. cli-e2e harness relies on the file-path mode. Reference: commit `288c2937`. + +5. **`Layer.provide` does not share to siblings inside `Layer.mergeAll`** — if two sibling layers each require `LegacyCliConfig`, provide it to both explicitly. Smoke-test the bundled binary (`bun run build && ./dist/supabase-legacy …`) when changing production layer wiring; in-process tests don't always catch the missing-service panic. Reference: commit `a816b12e`, `backups.layers.ts:32-46`. + +6. **Both `--output` (Go) and `--output-format` (TS) must be honored** — Go's `--output` (`pretty|json|yaml|toml|env`) takes priority when set. Pattern in `backups/list/list.handler.ts:85-113`: branch on `goOutputFlag` first, then fall through to TS `--output-format` text/json/stream-json. + +7. **PostHog telemetry payload matches Go 1:1** — see the next section. + +--- + +## Legacy Port: Telemetry Parity + +The legacy shell sends the same PostHog events to the same product analytics pipeline as the Go CLI. Drift is silent (no test will catch it) and breaks dashboards. The rules: + +- **The canonical catalog is `shared/telemetry/event-catalog.ts`** — a 1:1 mirror of `apps/cli-go/internal/telemetry/events.go`. Reference its exported constants (`EventCommandExecuted`, `PropFlags`, `EnvSignalPresenceKeys`, …) instead of writing bare strings. When the Go catalog changes, update the TS catalog in the same PR. +- **Native legacy commands wrap with `withLegacyCommandInstrumentation`** (from `legacy/telemetry/legacy-command-instrumentation.ts`) — _not_ the shared `withCommandInstrumentation`. The legacy variant emits Go-shape properties: a single `flags` map (vs `flags_used`/`flag_values`), `is_agent: boolean` (vs `ai_tool: string`), and `env_signals`. +- **Pass `flags` to the wrapper** so boolean flag values can be detected and logged verbatim: `handler(flags).pipe(withLegacyCommandInstrumentation({ flags }), ...)`. Sensitive values become the literal string `""` to match Go. +- **Use `safeFlags: ["flag-name"]`** to whitelist flags that Go marks with `markFlagTelemetrySafe` (grep `apps/cli-go/cmd/*.go`). Today these are `--project-ref` (sso, branches, link, functions, projects/api-keys), `--project-id` (gen/types), `--org-id` (projects/create), and `--version` (migration/squash). +- **Proxy handlers (`LegacyGoProxy.exec`) must NOT wrap with any instrumentation.** The Go subprocess fires its own telemetry; a TS wrapper would double-count `cli_command_executed`. +- **When promoting a command from proxy to native, reproduce every `phtelemetry.*` call in the Go counterpart.** Grep `apps/cli-go/internal//` for `service.Capture`, `service.Alias`, `service.Identify`, `service.GroupIdentify`, and `TrackUpgradeSuggested`. The current Go custom events that legacy ports must reproduce when natively ported: + + | Command | Event | Identity / groups | Go source | + | ------------------------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------- | + | `login` | `cli_login_completed` | `analytics.alias(gotrueId, deviceId)` + `analytics.identify(gotrueId)` after token persists | `internal/login/login.go:283-296` | + | `link` | `cli_project_linked` | `analytics.groupIdentify("organization", slug, …)` + `analytics.groupIdentify("project", ref, …)` after link write | `internal/link/link.go:60` | + | `start` | `cli_stack_started` | none — fired after stack health check passes | `internal/start/start.go:1245` | + | `sso/{list,create,update,remove}`, `branches/{create,update}` | `cli_upgrade_suggested` | none — payload is `{feature_key, org_slug}`, fired inside billing-gate error branch | 7 call-sites under `internal/{sso,branches}/` | + + Reference pattern for login: `next/commands/login/login.handler.ts:38-62`. + +- **Tracing layer is local-only observability**, not PostHog. Span names (`legacy..`) and the NDJSON exporter never leave the user's machine. No parity implication. + +--- + ## Legacy Port: File Location Compatibility The legacy shell bridges two worlds: it must behave exactly like the Go CLI for existing users, and it must lay the groundwork for a seamless upgrade to the next shell. @@ -311,6 +402,7 @@ Read https://www.effect.solutions/testing for Effect testing patterns. Note that - If a test needs multiple service replacements or `Layer.mergeAll(...)`, it likely belongs in `*.integration.test.ts`. - Prefer assertions on outputs and accumulated state over spy-heavy interaction tests. - Keep `*.e2e.test.ts` focused on golden paths, CLI surface behavior, and subprocess correctness, not branch-by-branch coverage. +- **Forbidden pattern (do not add):** spawning the CLI to assert that `--help` renders a flag. Help text is dynamic over flag wiring and is exercised by the integration test's flag parser. The two backups e2e files removed alongside this guidance update are the canonical example of what not to write. --- diff --git a/apps/cli/docs/go-cli-porting-status.md b/apps/cli/docs/go-cli-porting-status.md index ed5588abff..1a29409300 100644 --- a/apps/cli/docs/go-cli-porting-status.md +++ b/apps/cli/docs/go-cli-porting-status.md @@ -10,7 +10,7 @@ Reference: ## Legend - `ported`: TS command exists and the flag/parameter surface is materially aligned with the old Go CLI -- `partial`: TS feature exists but differs materially from the old Go CLI shape, flag surface, or invocation style. This includes feature parity delivered through framework-built global flags such as `--help` and `--completions` instead of matching Go subcommands exactly. +- `partial`: TS feature exists but differs materially from the old Go CLI shape, flag surface, or invocation style. This includes feature parity delivered through framework-built global flags such as `--help` instead of matching Go subcommands exactly. - `missing`: no TS command/subcommand exists yet Percentages and counts below are based on final leaf commands only. Command groups like `db`, `functions`, and `completion` are not counted as commands. @@ -19,29 +19,29 @@ Percentages and counts below are based on final leaf commands only. Command grou | Metric | Count | Percent | | ------------------------- | ------: | ------: | -| Fully ported commands | 2 / 94 | 2.1% | -| Partially ported commands | 59 / 94 | 62.8% | +| Fully ported commands | 6 / 94 | 6.4% | +| Partially ported commands | 55 / 94 | 58.5% | ## Family Summary -| Family | Final commands | `ported` | `partial` | `missing` | Represented in TS | -| ------------------------- | -------------: | --------: | ---------: | --------: | ----------------: | -| Quick Start | 1 | 0 (0%) | 0 (0%) | 1 (100%) | 0 (0%) | -| Project / Stack Lifecycle | 9 | 2 (22.2%) | 7 (77.8%) | 0 (0%) | 9 (100%) | -| Database | 19 | 0 (0%) | 0 (0%) | 19 (100%) | 0 (0%) | -| Code Generation | 3 | 0 (0%) | 0 (0%) | 3 (100%) | 0 (0%) | -| Functions | 6 | 0 (0%) | 0 (0%) | 6 (100%) | 0 (0%) | -| Storage | 4 | 0 (0%) | 0 (0%) | 4 (100%) | 0 (0%) | -| Management APIs | 47 | 0 (0%) | 47 (100%) | 0 (0%) | 47 (100%) | -| Additional Commands | 5 | 0 (0%) | 5 (100.0%) | 0 (0%) | 5 (100.0%) | +| Family | Final commands | `ported` | `partial` | `missing` | Represented in TS | +| ------------------------- | -------------: | --------: | --------: | --------: | ----------------: | +| Quick Start | 1 | 0 (0%) | 0 (0%) | 1 (100%) | 0 (0%) | +| Project / Stack Lifecycle | 9 | 2 (22.2%) | 7 (77.8%) | 0 (0%) | 9 (100%) | +| Database | 19 | 0 (0%) | 0 (0%) | 19 (100%) | 0 (0%) | +| Code Generation | 3 | 0 (0%) | 0 (0%) | 3 (100%) | 0 (0%) | +| Functions | 6 | 0 (0%) | 0 (0%) | 6 (100%) | 0 (0%) | +| Storage | 4 | 0 (0%) | 0 (0%) | 4 (100%) | 0 (0%) | +| Management APIs | 47 | 0 (0%) | 47 (100%) | 0 (0%) | 47 (100%) | +| Additional Commands | 5 | 4 (80%) | 1 (20%) | 0 (0%) | 5 (100.0%) | ## Global Flags Overview This tracker is command-focused, but root global flag drift is large enough to note separately. -| Surface | TS path | Missing old flags/params | Extra TS flags/params | Notes | -| ----------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `supabase` global flags | [`../src/shared/cli/global-flags.ts`](../src/shared/cli/global-flags.ts) | `--create-ticket`, `--debug`, `--dns-resolver`, `--experimental`, `--network-id`, `--output`, `--profile`, `--workdir`, `--yes` | `--output-format` | Root flag parity is still far from the Go CLI, but the framework already provides global `--help` and `--completions`, so help and shell completion have feature parity even though they no longer live under explicit Go-style subcommands. | +| Surface | TS path | Missing old flags/params | Extra TS flags/params | Notes | +| ----------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `supabase` global flags | [`../src/shared/cli/global-flags.ts`](../src/shared/cli/global-flags.ts) | `--create-ticket`, `--debug`, `--dns-resolver`, `--experimental`, `--network-id`, `--output`, `--profile`, `--workdir`, `--yes` | `--output-format` | Root flag parity is still far from the Go CLI, but the framework already provides global `--help`, and `supabase completion ` is restored as a Go-style subcommand. The framework's `--completions` global flag remains available for `next/` users. | ## TS-only Commands @@ -193,122 +193,123 @@ These route-first equivalents are intentionally lower-level than the old Go comm ## Additional Commands -| Old command | TS status | TS command path or `missing` | Missing flags/params | Extra TS flags/params | Notes | -| ----------------------- | --------- | ----------------------------------- | ------------------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------- | -| `completion bash` | `partial` | `supabase --completions bash` | Go-style `completion bash` subcommand shape | `-` | Feature parity exists via the framework-provided global `--completions` flag instead of a dedicated subcommand tree. | -| `completion fish` | `partial` | `supabase --completions fish` | Go-style `completion fish` subcommand shape | `-` | Feature parity exists via the framework-provided global `--completions` flag instead of a dedicated subcommand tree. | -| `completion powershell` | `partial` | `supabase --completions powershell` | Go-style `completion powershell` subcommand shape | `-` | Feature parity exists via the framework-provided global `--completions` flag instead of a dedicated subcommand tree. | -| `completion zsh` | `partial` | `supabase --completions zsh` | Go-style `completion zsh` subcommand shape | `-` | Feature parity exists via the framework-provided global `--completions` flag instead of a dedicated subcommand tree. | -| `help` | `partial` | `supabase --help` | Go-style top-level `help` command shape | `-` | Feature parity exists via the framework-provided global `--help` flag instead of a dedicated `help` command. | +| Old command | TS status | TS command path or `missing` | Missing flags/params | Extra TS flags/params | Notes | +| ----------------------- | --------- | -------------------------------- | --------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------ | +| `completion bash` | `ported` | `supabase completion bash` | `-` | `-` | Proxies verbatim to the Go binary so the emitted script is byte-identical to Cobra's output (CLI-1532). | +| `completion fish` | `ported` | `supabase completion fish` | `-` | `-` | Proxies verbatim to the Go binary so the emitted script is byte-identical to Cobra's output (CLI-1532). | +| `completion powershell` | `ported` | `supabase completion powershell` | `-` | `-` | Proxies verbatim to the Go binary so the emitted script is byte-identical to Cobra's output (CLI-1532). | +| `completion zsh` | `ported` | `supabase completion zsh` | `-` | `-` | Proxies verbatim to the Go binary so the emitted script is byte-identical to Cobra's output (CLI-1532). | +| `help` | `partial` | `supabase --help` | Go-style top-level `help` command shape | `-` | Feature parity exists via the framework-provided global `--help` flag instead of a dedicated `help` command. | ## Legacy Shell Wrapping Status Phase 0 proxy wrappers in the legacy shell (`src/legacy/`). Each wrapped command forwards to the bundled Go binary via `LegacyGoProxy`. +The `migration` command group also accepts Go's top-level `migrations` alias and forwards singular `migration` argv to Go. Legend: - `wrapped`: Phase 0 proxy wrapper exists in the legacy shell - `missing`: no legacy shell command yet -| Command | Legacy status | Legacy command path | -| -------------------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `orgs list` | `wrapped` | [`../src/legacy/commands/orgs/list/list.command.ts`](../src/legacy/commands/orgs/list/list.command.ts) | -| `orgs create` | `wrapped` | [`../src/legacy/commands/orgs/create/create.command.ts`](../src/legacy/commands/orgs/create/create.command.ts) | -| `projects list` | `wrapped` | [`../src/legacy/commands/projects/list/list.command.ts`](../src/legacy/commands/projects/list/list.command.ts) | -| `projects create` | `wrapped` | [`../src/legacy/commands/projects/create/create.command.ts`](../src/legacy/commands/projects/create/create.command.ts) | -| `projects delete` | `wrapped` | [`../src/legacy/commands/projects/delete/delete.command.ts`](../src/legacy/commands/projects/delete/delete.command.ts) | -| `projects api-keys` | `wrapped` | [`../src/legacy/commands/projects/api-keys/api-keys.command.ts`](../src/legacy/commands/projects/api-keys/api-keys.command.ts) | -| `branches list` | `wrapped` | [`../src/legacy/commands/branches/list/list.command.ts`](../src/legacy/commands/branches/list/list.command.ts) | -| `branches create` | `wrapped` | [`../src/legacy/commands/branches/create/create.command.ts`](../src/legacy/commands/branches/create/create.command.ts) | -| `branches get` | `wrapped` | [`../src/legacy/commands/branches/get/get.command.ts`](../src/legacy/commands/branches/get/get.command.ts) | -| `branches update` | `wrapped` | [`../src/legacy/commands/branches/update/update.command.ts`](../src/legacy/commands/branches/update/update.command.ts) | -| `branches pause` | `wrapped` | [`../src/legacy/commands/branches/pause/pause.command.ts`](../src/legacy/commands/branches/pause/pause.command.ts) | -| `branches unpause` | `wrapped` | [`../src/legacy/commands/branches/unpause/unpause.command.ts`](../src/legacy/commands/branches/unpause/unpause.command.ts) | -| `branches delete` | `wrapped` | [`../src/legacy/commands/branches/delete/delete.command.ts`](../src/legacy/commands/branches/delete/delete.command.ts) | -| `branches disable` | `wrapped` | [`../src/legacy/commands/branches/disable/disable.command.ts`](../src/legacy/commands/branches/disable/disable.command.ts) | -| `secrets list` | `wrapped` | [`../src/legacy/commands/secrets/list/list.command.ts`](../src/legacy/commands/secrets/list/list.command.ts) | -| `secrets set` | `wrapped` | [`../src/legacy/commands/secrets/set/set.command.ts`](../src/legacy/commands/secrets/set/set.command.ts) | -| `secrets unset` | `wrapped` | [`../src/legacy/commands/secrets/unset/unset.command.ts`](../src/legacy/commands/secrets/unset/unset.command.ts) | -| `config push` | `wrapped` | [`../src/legacy/commands/config/push/push.command.ts`](../src/legacy/commands/config/push/push.command.ts) | -| `backups list` | `wrapped` | [`../src/legacy/commands/backups/list/list.command.ts`](../src/legacy/commands/backups/list/list.command.ts) | -| `backups restore` | `wrapped` | [`../src/legacy/commands/backups/restore/restore.command.ts`](../src/legacy/commands/backups/restore/restore.command.ts) | -| `snippets list` | `wrapped` | [`../src/legacy/commands/snippets/list/list.command.ts`](../src/legacy/commands/snippets/list/list.command.ts) | -| `snippets download` | `wrapped` | [`../src/legacy/commands/snippets/download/download.command.ts`](../src/legacy/commands/snippets/download/download.command.ts) | -| `sso list` | `wrapped` | [`../src/legacy/commands/sso/list/list.command.ts`](../src/legacy/commands/sso/list/list.command.ts) | -| `sso add` | `wrapped` | [`../src/legacy/commands/sso/add/add.command.ts`](../src/legacy/commands/sso/add/add.command.ts) | -| `sso remove` | `wrapped` | [`../src/legacy/commands/sso/remove/remove.command.ts`](../src/legacy/commands/sso/remove/remove.command.ts) | -| `sso update` | `wrapped` | [`../src/legacy/commands/sso/update/update.command.ts`](../src/legacy/commands/sso/update/update.command.ts) | -| `sso show` | `wrapped` | [`../src/legacy/commands/sso/show/show.command.ts`](../src/legacy/commands/sso/show/show.command.ts) | -| `sso info` | `wrapped` | [`../src/legacy/commands/sso/info/info.command.ts`](../src/legacy/commands/sso/info/info.command.ts) | -| `domains create` | `wrapped` | [`../src/legacy/commands/domains/create/create.command.ts`](../src/legacy/commands/domains/create/create.command.ts) | -| `domains get` | `wrapped` | [`../src/legacy/commands/domains/get/get.command.ts`](../src/legacy/commands/domains/get/get.command.ts) | -| `domains reverify` | `wrapped` | [`../src/legacy/commands/domains/reverify/reverify.command.ts`](../src/legacy/commands/domains/reverify/reverify.command.ts) | -| `domains activate` | `wrapped` | [`../src/legacy/commands/domains/activate/activate.command.ts`](../src/legacy/commands/domains/activate/activate.command.ts) | -| `domains delete` | `wrapped` | [`../src/legacy/commands/domains/delete/delete.command.ts`](../src/legacy/commands/domains/delete/delete.command.ts) | -| `vanity-subdomains get` | `wrapped` | [`../src/legacy/commands/vanity-subdomains/get/get.command.ts`](../src/legacy/commands/vanity-subdomains/get/get.command.ts) | -| `vanity-subdomains check-availability` | `wrapped` | [`../src/legacy/commands/vanity-subdomains/check-availability/check-availability.command.ts`](../src/legacy/commands/vanity-subdomains/check-availability/check-availability.command.ts) | -| `vanity-subdomains activate` | `wrapped` | [`../src/legacy/commands/vanity-subdomains/activate/activate.command.ts`](../src/legacy/commands/vanity-subdomains/activate/activate.command.ts) | -| `vanity-subdomains delete` | `wrapped` | [`../src/legacy/commands/vanity-subdomains/delete/delete.command.ts`](../src/legacy/commands/vanity-subdomains/delete/delete.command.ts) | -| `network-bans get` | `wrapped` | [`../src/legacy/commands/network-bans/get/get.command.ts`](../src/legacy/commands/network-bans/get/get.command.ts) | -| `network-bans remove` | `wrapped` | [`../src/legacy/commands/network-bans/remove/remove.command.ts`](../src/legacy/commands/network-bans/remove/remove.command.ts) | -| `network-restrictions get` | `wrapped` | [`../src/legacy/commands/network-restrictions/get/get.command.ts`](../src/legacy/commands/network-restrictions/get/get.command.ts) | -| `network-restrictions update` | `wrapped` | [`../src/legacy/commands/network-restrictions/update/update.command.ts`](../src/legacy/commands/network-restrictions/update/update.command.ts) | -| `encryption get-root-key` | `wrapped` | [`../src/legacy/commands/encryption/get-root-key/get-root-key.command.ts`](../src/legacy/commands/encryption/get-root-key/get-root-key.command.ts) | -| `encryption update-root-key` | `wrapped` | [`../src/legacy/commands/encryption/update-root-key/update-root-key.command.ts`](../src/legacy/commands/encryption/update-root-key/update-root-key.command.ts) | -| `ssl-enforcement get` | `wrapped` | [`../src/legacy/commands/ssl-enforcement/get/get.command.ts`](../src/legacy/commands/ssl-enforcement/get/get.command.ts) | -| `ssl-enforcement update` | `wrapped` | [`../src/legacy/commands/ssl-enforcement/update/update.command.ts`](../src/legacy/commands/ssl-enforcement/update/update.command.ts) | -| `postgres-config get` | `wrapped` | [`../src/legacy/commands/postgres-config/get/get.command.ts`](../src/legacy/commands/postgres-config/get/get.command.ts) | -| `postgres-config update` | `wrapped` | [`../src/legacy/commands/postgres-config/update/update.command.ts`](../src/legacy/commands/postgres-config/update/update.command.ts) | -| `postgres-config delete` | `wrapped` | [`../src/legacy/commands/postgres-config/delete/delete.command.ts`](../src/legacy/commands/postgres-config/delete/delete.command.ts) | -| `login` | `wrapped` | [`../src/legacy/commands/login/login.command.ts`](../src/legacy/commands/login/login.command.ts) | -| `logout` | `wrapped` | [`../src/legacy/commands/logout/logout.command.ts`](../src/legacy/commands/logout/logout.command.ts) | -| `link` | `wrapped` | [`../src/legacy/commands/link/link.command.ts`](../src/legacy/commands/link/link.command.ts) | -| `unlink` | `wrapped` | [`../src/legacy/commands/unlink/unlink.command.ts`](../src/legacy/commands/unlink/unlink.command.ts) | -| `bootstrap` | `wrapped` | [`../src/legacy/commands/bootstrap/bootstrap.command.ts`](../src/legacy/commands/bootstrap/bootstrap.command.ts) | -| `init` | `wrapped` | [`../src/legacy/commands/init/init.command.ts`](../src/legacy/commands/init/init.command.ts) | -| `services` | `wrapped` | [`../src/legacy/commands/services/services.command.ts`](../src/legacy/commands/services/services.command.ts) | -| `start` | `wrapped` | [`../src/legacy/commands/start/start.command.ts`](../src/legacy/commands/start/start.command.ts) | -| `stop` | `wrapped` | [`../src/legacy/commands/stop/stop.command.ts`](../src/legacy/commands/stop/stop.command.ts) | -| `status` | `wrapped` | [`../src/legacy/commands/status/status.command.ts`](../src/legacy/commands/status/status.command.ts) | -| `migration list` | `wrapped` | [`../src/legacy/commands/migration/list/list.command.ts`](../src/legacy/commands/migration/list/list.command.ts) | -| `migration new` | `wrapped` | [`../src/legacy/commands/migration/new/new.command.ts`](../src/legacy/commands/migration/new/new.command.ts) | -| `migration repair` | `wrapped` | [`../src/legacy/commands/migration/repair/repair.command.ts`](../src/legacy/commands/migration/repair/repair.command.ts) | -| `migration squash` | `wrapped` | [`../src/legacy/commands/migration/squash/squash.command.ts`](../src/legacy/commands/migration/squash/squash.command.ts) | -| `migration up` | `wrapped` | [`../src/legacy/commands/migration/up/up.command.ts`](../src/legacy/commands/migration/up/up.command.ts) | -| `migration down` | `wrapped` | [`../src/legacy/commands/migration/down/down.command.ts`](../src/legacy/commands/migration/down/down.command.ts) | -| `migration fetch` | `wrapped` | [`../src/legacy/commands/migration/fetch/fetch.command.ts`](../src/legacy/commands/migration/fetch/fetch.command.ts) | -| `gen types` | `wrapped` | [`../src/legacy/commands/gen/types/types.command.ts`](../src/legacy/commands/gen/types/types.command.ts) | -| `gen signing-key` | `wrapped` | [`../src/legacy/commands/gen/signing-key/signing-key.command.ts`](../src/legacy/commands/gen/signing-key/signing-key.command.ts) | -| `gen bearer-jwt` | `wrapped` | [`../src/legacy/commands/gen/bearer-jwt/bearer-jwt.command.ts`](../src/legacy/commands/gen/bearer-jwt/bearer-jwt.command.ts) | -| `gen keys` | `wrapped` | [`../src/legacy/commands/gen/keys/keys.command.ts`](../src/legacy/commands/gen/keys/keys.command.ts) | -| `functions list` | `wrapped` | [`../src/legacy/commands/functions/list/list.command.ts`](../src/legacy/commands/functions/list/list.command.ts) | -| `functions delete` | `wrapped` | [`../src/legacy/commands/functions/delete/delete.command.ts`](../src/legacy/commands/functions/delete/delete.command.ts) | -| `functions download` | `wrapped` | [`../src/legacy/commands/functions/download/download.command.ts`](../src/legacy/commands/functions/download/download.command.ts) | -| `functions deploy` | `wrapped` | [`../src/legacy/commands/functions/deploy/deploy.command.ts`](../src/legacy/commands/functions/deploy/deploy.command.ts) | -| `functions new` | `wrapped` | [`../src/legacy/commands/functions/new/new.command.ts`](../src/legacy/commands/functions/new/new.command.ts) | -| `functions serve` | `wrapped` | [`../src/legacy/commands/functions/serve/serve.command.ts`](../src/legacy/commands/functions/serve/serve.command.ts) | -| `storage ls` | `wrapped` | [`../src/legacy/commands/storage/ls/ls.command.ts`](../src/legacy/commands/storage/ls/ls.command.ts) | -| `storage cp` | `wrapped` | [`../src/legacy/commands/storage/cp/cp.command.ts`](../src/legacy/commands/storage/cp/cp.command.ts) | -| `storage mv` | `wrapped` | [`../src/legacy/commands/storage/mv/mv.command.ts`](../src/legacy/commands/storage/mv/mv.command.ts) | -| `storage rm` | `wrapped` | [`../src/legacy/commands/storage/rm/rm.command.ts`](../src/legacy/commands/storage/rm/rm.command.ts) | -| `test db` | `wrapped` | [`../src/legacy/commands/test/db/db.command.ts`](../src/legacy/commands/test/db/db.command.ts) | -| `test new` | `wrapped` | [`../src/legacy/commands/test/new/new.command.ts`](../src/legacy/commands/test/new/new.command.ts) | -| `seed buckets` | `wrapped` | [`../src/legacy/commands/seed/buckets/buckets.command.ts`](../src/legacy/commands/seed/buckets/buckets.command.ts) | -| `db diff` | `wrapped` | [`../src/legacy/commands/db/diff/diff.command.ts`](../src/legacy/commands/db/diff/diff.command.ts) | -| `db dump` | `wrapped` | [`../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` | `wrapped` | [`../src/legacy/commands/db/pull/pull.command.ts`](../src/legacy/commands/db/pull/pull.command.ts) — includes `--diff-engine` (migra\|pg-delta, mutually exclusive with `--use-pg-delta`) | -| `db reset` | `wrapped` | [`../src/legacy/commands/db/reset/reset.command.ts`](../src/legacy/commands/db/reset/reset.command.ts) | -| `db lint` | `wrapped` | [`../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` | `wrapped` | [`../src/legacy/commands/db/query/query.command.ts`](../src/legacy/commands/db/query/query.command.ts) | -| `db advisors` | `wrapped` | [`../src/legacy/commands/db/advisors/advisors.command.ts`](../src/legacy/commands/db/advisors/advisors.command.ts) | -| `db test` | `wrapped` | [`../src/legacy/commands/db/test/test.command.ts`](../src/legacy/commands/db/test/test.command.ts) | -| `db branch create` | `wrapped` | [`../src/legacy/commands/db/branch/create/create.command.ts`](../src/legacy/commands/db/branch/create/create.command.ts) | -| `db branch delete` | `wrapped` | [`../src/legacy/commands/db/branch/delete/delete.command.ts`](../src/legacy/commands/db/branch/delete/delete.command.ts) | -| `db branch list` | `wrapped` | [`../src/legacy/commands/db/branch/list/list.command.ts`](../src/legacy/commands/db/branch/list/list.command.ts) | -| `db branch switch` | `wrapped` | [`../src/legacy/commands/db/branch/switch/switch.command.ts`](../src/legacy/commands/db/branch/switch/switch.command.ts) | -| `db remote changes` | `wrapped` | [`../src/legacy/commands/db/remote/changes/changes.command.ts`](../src/legacy/commands/db/remote/changes/changes.command.ts) | -| `db remote commit` | `wrapped` | [`../src/legacy/commands/db/remote/commit/commit.command.ts`](../src/legacy/commands/db/remote/commit/commit.command.ts) | -| `db schema declarative sync` | `wrapped` | [`../src/legacy/commands/db/schema/declarative/sync/sync.command.ts`](../src/legacy/commands/db/schema/declarative/sync/sync.command.ts) — `--apply` and `--no-apply` are mutually exclusive; `--no-apply` overrides the global `--yes` flag | -| `db schema declarative generate` | `wrapped` | [`../src/legacy/commands/db/schema/declarative/generate/generate.command.ts`](../src/legacy/commands/db/schema/declarative/generate/generate.command.ts) | +| Command | Legacy status | Legacy command path | +| -------------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `orgs list` | `ported` | [`../src/legacy/commands/orgs/list/list.command.ts`](../src/legacy/commands/orgs/list/list.command.ts) | +| `orgs create` | `ported` | [`../src/legacy/commands/orgs/create/create.command.ts`](../src/legacy/commands/orgs/create/create.command.ts) | +| `projects list` | `wrapped` | [`../src/legacy/commands/projects/list/list.command.ts`](../src/legacy/commands/projects/list/list.command.ts) | +| `projects create` | `wrapped` | [`../src/legacy/commands/projects/create/create.command.ts`](../src/legacy/commands/projects/create/create.command.ts) | +| `projects delete` | `wrapped` | [`../src/legacy/commands/projects/delete/delete.command.ts`](../src/legacy/commands/projects/delete/delete.command.ts) | +| `projects api-keys` | `wrapped` | [`../src/legacy/commands/projects/api-keys/api-keys.command.ts`](../src/legacy/commands/projects/api-keys/api-keys.command.ts) | +| `branches list` | `ported` | [`../src/legacy/commands/branches/list/list.command.ts`](../src/legacy/commands/branches/list/list.command.ts) | +| `branches create` | `ported` | [`../src/legacy/commands/branches/create/create.command.ts`](../src/legacy/commands/branches/create/create.command.ts) | +| `branches get` | `ported` | [`../src/legacy/commands/branches/get/get.command.ts`](../src/legacy/commands/branches/get/get.command.ts) | +| `branches update` | `ported` | [`../src/legacy/commands/branches/update/update.command.ts`](../src/legacy/commands/branches/update/update.command.ts) | +| `branches pause` | `ported` | [`../src/legacy/commands/branches/pause/pause.command.ts`](../src/legacy/commands/branches/pause/pause.command.ts) | +| `branches unpause` | `ported` | [`../src/legacy/commands/branches/unpause/unpause.command.ts`](../src/legacy/commands/branches/unpause/unpause.command.ts) | +| `branches delete` | `ported` | [`../src/legacy/commands/branches/delete/delete.command.ts`](../src/legacy/commands/branches/delete/delete.command.ts) | +| `branches disable` | `ported` | [`../src/legacy/commands/branches/disable/disable.command.ts`](../src/legacy/commands/branches/disable/disable.command.ts) | +| `secrets list` | `ported` | [`../src/legacy/commands/secrets/list/list.command.ts`](../src/legacy/commands/secrets/list/list.command.ts) | +| `secrets set` | `ported` | [`../src/legacy/commands/secrets/set/set.command.ts`](../src/legacy/commands/secrets/set/set.command.ts) | +| `secrets unset` | `ported` | [`../src/legacy/commands/secrets/unset/unset.command.ts`](../src/legacy/commands/secrets/unset/unset.command.ts) | +| `config push` | `wrapped` | [`../src/legacy/commands/config/push/push.command.ts`](../src/legacy/commands/config/push/push.command.ts) | +| `backups list` | `ported` | [`../src/legacy/commands/backups/list/list.command.ts`](../src/legacy/commands/backups/list/list.command.ts) | +| `backups restore` | `ported` | [`../src/legacy/commands/backups/restore/restore.command.ts`](../src/legacy/commands/backups/restore/restore.command.ts) | +| `snippets list` | `wrapped` | [`../src/legacy/commands/snippets/list/list.command.ts`](../src/legacy/commands/snippets/list/list.command.ts) | +| `snippets download` | `wrapped` | [`../src/legacy/commands/snippets/download/download.command.ts`](../src/legacy/commands/snippets/download/download.command.ts) | +| `sso list` | `ported` | [`../src/legacy/commands/sso/list/list.command.ts`](../src/legacy/commands/sso/list/list.command.ts) | +| `sso add` | `ported` | [`../src/legacy/commands/sso/add/add.command.ts`](../src/legacy/commands/sso/add/add.command.ts) | +| `sso remove` | `ported` | [`../src/legacy/commands/sso/remove/remove.command.ts`](../src/legacy/commands/sso/remove/remove.command.ts) | +| `sso update` | `ported` | [`../src/legacy/commands/sso/update/update.command.ts`](../src/legacy/commands/sso/update/update.command.ts) | +| `sso show` | `ported` | [`../src/legacy/commands/sso/show/show.command.ts`](../src/legacy/commands/sso/show/show.command.ts) | +| `sso info` | `ported` | [`../src/legacy/commands/sso/info/info.command.ts`](../src/legacy/commands/sso/info/info.command.ts) | +| `domains create` | `wrapped` | [`../src/legacy/commands/domains/create/create.command.ts`](../src/legacy/commands/domains/create/create.command.ts) | +| `domains get` | `wrapped` | [`../src/legacy/commands/domains/get/get.command.ts`](../src/legacy/commands/domains/get/get.command.ts) | +| `domains reverify` | `wrapped` | [`../src/legacy/commands/domains/reverify/reverify.command.ts`](../src/legacy/commands/domains/reverify/reverify.command.ts) | +| `domains activate` | `wrapped` | [`../src/legacy/commands/domains/activate/activate.command.ts`](../src/legacy/commands/domains/activate/activate.command.ts) | +| `domains delete` | `wrapped` | [`../src/legacy/commands/domains/delete/delete.command.ts`](../src/legacy/commands/domains/delete/delete.command.ts) | +| `vanity-subdomains get` | `wrapped` | [`../src/legacy/commands/vanity-subdomains/get/get.command.ts`](../src/legacy/commands/vanity-subdomains/get/get.command.ts) | +| `vanity-subdomains check-availability` | `wrapped` | [`../src/legacy/commands/vanity-subdomains/check-availability/check-availability.command.ts`](../src/legacy/commands/vanity-subdomains/check-availability/check-availability.command.ts) | +| `vanity-subdomains activate` | `wrapped` | [`../src/legacy/commands/vanity-subdomains/activate/activate.command.ts`](../src/legacy/commands/vanity-subdomains/activate/activate.command.ts) | +| `vanity-subdomains delete` | `wrapped` | [`../src/legacy/commands/vanity-subdomains/delete/delete.command.ts`](../src/legacy/commands/vanity-subdomains/delete/delete.command.ts) | +| `network-bans get` | `wrapped` | [`../src/legacy/commands/network-bans/get/get.command.ts`](../src/legacy/commands/network-bans/get/get.command.ts) | +| `network-bans remove` | `wrapped` | [`../src/legacy/commands/network-bans/remove/remove.command.ts`](../src/legacy/commands/network-bans/remove/remove.command.ts) | +| `network-restrictions get` | `ported` | [`../src/legacy/commands/network-restrictions/get/get.command.ts`](../src/legacy/commands/network-restrictions/get/get.command.ts) | +| `network-restrictions update` | `ported` | [`../src/legacy/commands/network-restrictions/update/update.command.ts`](../src/legacy/commands/network-restrictions/update/update.command.ts) | +| `encryption get-root-key` | `wrapped` | [`../src/legacy/commands/encryption/get-root-key/get-root-key.command.ts`](../src/legacy/commands/encryption/get-root-key/get-root-key.command.ts) | +| `encryption update-root-key` | `wrapped` | [`../src/legacy/commands/encryption/update-root-key/update-root-key.command.ts`](../src/legacy/commands/encryption/update-root-key/update-root-key.command.ts) | +| `ssl-enforcement get` | `ported` | [`../src/legacy/commands/ssl-enforcement/get/get.command.ts`](../src/legacy/commands/ssl-enforcement/get/get.command.ts) | +| `ssl-enforcement update` | `ported` | [`../src/legacy/commands/ssl-enforcement/update/update.command.ts`](../src/legacy/commands/ssl-enforcement/update/update.command.ts) | +| `postgres-config get` | `wrapped` | [`../src/legacy/commands/postgres-config/get/get.command.ts`](../src/legacy/commands/postgres-config/get/get.command.ts) | +| `postgres-config update` | `wrapped` | [`../src/legacy/commands/postgres-config/update/update.command.ts`](../src/legacy/commands/postgres-config/update/update.command.ts) | +| `postgres-config delete` | `wrapped` | [`../src/legacy/commands/postgres-config/delete/delete.command.ts`](../src/legacy/commands/postgres-config/delete/delete.command.ts) | +| `login` | `wrapped` | [`../src/legacy/commands/login/login.command.ts`](../src/legacy/commands/login/login.command.ts) | +| `logout` | `wrapped` | [`../src/legacy/commands/logout/logout.command.ts`](../src/legacy/commands/logout/logout.command.ts) | +| `link` | `wrapped` | [`../src/legacy/commands/link/link.command.ts`](../src/legacy/commands/link/link.command.ts) | +| `unlink` | `wrapped` | [`../src/legacy/commands/unlink/unlink.command.ts`](../src/legacy/commands/unlink/unlink.command.ts) | +| `bootstrap` | `wrapped` | [`../src/legacy/commands/bootstrap/bootstrap.command.ts`](../src/legacy/commands/bootstrap/bootstrap.command.ts) | +| `init` | `wrapped` | [`../src/legacy/commands/init/init.command.ts`](../src/legacy/commands/init/init.command.ts) | +| `services` | `wrapped` | [`../src/legacy/commands/services/services.command.ts`](../src/legacy/commands/services/services.command.ts) | +| `start` | `wrapped` | [`../src/legacy/commands/start/start.command.ts`](../src/legacy/commands/start/start.command.ts) | +| `stop` | `wrapped` | [`../src/legacy/commands/stop/stop.command.ts`](../src/legacy/commands/stop/stop.command.ts) | +| `status` | `wrapped` | [`../src/legacy/commands/status/status.command.ts`](../src/legacy/commands/status/status.command.ts) | +| `migration list` | `wrapped` | [`../src/legacy/commands/migration/list/list.command.ts`](../src/legacy/commands/migration/list/list.command.ts) | +| `migration new` | `wrapped` | [`../src/legacy/commands/migration/new/new.command.ts`](../src/legacy/commands/migration/new/new.command.ts) | +| `migration repair` | `wrapped` | [`../src/legacy/commands/migration/repair/repair.command.ts`](../src/legacy/commands/migration/repair/repair.command.ts) | +| `migration squash` | `wrapped` | [`../src/legacy/commands/migration/squash/squash.command.ts`](../src/legacy/commands/migration/squash/squash.command.ts) | +| `migration up` | `wrapped` | [`../src/legacy/commands/migration/up/up.command.ts`](../src/legacy/commands/migration/up/up.command.ts) | +| `migration down` | `wrapped` | [`../src/legacy/commands/migration/down/down.command.ts`](../src/legacy/commands/migration/down/down.command.ts) | +| `migration fetch` | `wrapped` | [`../src/legacy/commands/migration/fetch/fetch.command.ts`](../src/legacy/commands/migration/fetch/fetch.command.ts) | +| `gen types` | `wrapped` | [`../src/legacy/commands/gen/types/types.command.ts`](../src/legacy/commands/gen/types/types.command.ts) | +| `gen signing-key` | `wrapped` | [`../src/legacy/commands/gen/signing-key/signing-key.command.ts`](../src/legacy/commands/gen/signing-key/signing-key.command.ts) | +| `gen bearer-jwt` | `wrapped` | [`../src/legacy/commands/gen/bearer-jwt/bearer-jwt.command.ts`](../src/legacy/commands/gen/bearer-jwt/bearer-jwt.command.ts) | +| `gen keys` | `wrapped` | [`../src/legacy/commands/gen/keys/keys.command.ts`](../src/legacy/commands/gen/keys/keys.command.ts) | +| `functions list` | `wrapped` | [`../src/legacy/commands/functions/list/list.command.ts`](../src/legacy/commands/functions/list/list.command.ts) | +| `functions delete` | `wrapped` | [`../src/legacy/commands/functions/delete/delete.command.ts`](../src/legacy/commands/functions/delete/delete.command.ts) | +| `functions download` | `wrapped` | [`../src/legacy/commands/functions/download/download.command.ts`](../src/legacy/commands/functions/download/download.command.ts) | +| `functions deploy` | `wrapped` | [`../src/legacy/commands/functions/deploy/deploy.command.ts`](../src/legacy/commands/functions/deploy/deploy.command.ts) | +| `functions new` | `wrapped` | [`../src/legacy/commands/functions/new/new.command.ts`](../src/legacy/commands/functions/new/new.command.ts) | +| `functions serve` | `wrapped` | [`../src/legacy/commands/functions/serve/serve.command.ts`](../src/legacy/commands/functions/serve/serve.command.ts) | +| `storage ls` | `wrapped` | [`../src/legacy/commands/storage/ls/ls.command.ts`](../src/legacy/commands/storage/ls/ls.command.ts) | +| `storage cp` | `wrapped` | [`../src/legacy/commands/storage/cp/cp.command.ts`](../src/legacy/commands/storage/cp/cp.command.ts) | +| `storage mv` | `wrapped` | [`../src/legacy/commands/storage/mv/mv.command.ts`](../src/legacy/commands/storage/mv/mv.command.ts) | +| `storage rm` | `wrapped` | [`../src/legacy/commands/storage/rm/rm.command.ts`](../src/legacy/commands/storage/rm/rm.command.ts) | +| `test db` | `wrapped` | [`../src/legacy/commands/test/db/db.command.ts`](../src/legacy/commands/test/db/db.command.ts) | +| `test new` | `wrapped` | [`../src/legacy/commands/test/new/new.command.ts`](../src/legacy/commands/test/new/new.command.ts) | +| `seed buckets` | `wrapped` | [`../src/legacy/commands/seed/buckets/buckets.command.ts`](../src/legacy/commands/seed/buckets/buckets.command.ts) | +| `db diff` | `wrapped` | [`../src/legacy/commands/db/diff/diff.command.ts`](../src/legacy/commands/db/diff/diff.command.ts) | +| `db dump` | `wrapped` | [`../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` | `wrapped` | [`../src/legacy/commands/db/pull/pull.command.ts`](../src/legacy/commands/db/pull/pull.command.ts) — includes `--diff-engine` (migra\|pg-delta, mutually exclusive with `--use-pg-delta`) | +| `db reset` | `wrapped` | [`../src/legacy/commands/db/reset/reset.command.ts`](../src/legacy/commands/db/reset/reset.command.ts) | +| `db lint` | `wrapped` | [`../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` | `wrapped` | [`../src/legacy/commands/db/query/query.command.ts`](../src/legacy/commands/db/query/query.command.ts) | +| `db advisors` | `wrapped` | [`../src/legacy/commands/db/advisors/advisors.command.ts`](../src/legacy/commands/db/advisors/advisors.command.ts) | +| `db test` | `wrapped` | [`../src/legacy/commands/db/test/test.command.ts`](../src/legacy/commands/db/test/test.command.ts) | +| `db branch create` | `wrapped` | [`../src/legacy/commands/db/branch/create/create.command.ts`](../src/legacy/commands/db/branch/create/create.command.ts) | +| `db branch delete` | `wrapped` | [`../src/legacy/commands/db/branch/delete/delete.command.ts`](../src/legacy/commands/db/branch/delete/delete.command.ts) | +| `db branch list` | `wrapped` | [`../src/legacy/commands/db/branch/list/list.command.ts`](../src/legacy/commands/db/branch/list/list.command.ts) | +| `db branch switch` | `wrapped` | [`../src/legacy/commands/db/branch/switch/switch.command.ts`](../src/legacy/commands/db/branch/switch/switch.command.ts) | +| `db remote changes` | `wrapped` | [`../src/legacy/commands/db/remote/changes/changes.command.ts`](../src/legacy/commands/db/remote/changes/changes.command.ts) | +| `db remote commit` | `wrapped` | [`../src/legacy/commands/db/remote/commit/commit.command.ts`](../src/legacy/commands/db/remote/commit/commit.command.ts) | +| `db schema declarative sync` | `wrapped` | [`../src/legacy/commands/db/schema/declarative/sync/sync.command.ts`](../src/legacy/commands/db/schema/declarative/sync/sync.command.ts) | +| `db schema declarative generate` | `wrapped` | [`../src/legacy/commands/db/schema/declarative/generate/generate.command.ts`](../src/legacy/commands/db/schema/declarative/generate/generate.command.ts) | diff --git a/apps/cli/package.json b/apps/cli/package.json index 67ab24316f..fa98c8d27c 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -49,22 +49,25 @@ "@supabase/stack": "workspace:*", "@tsconfig/bun": "catalog:", "@types/bun": "catalog:", - "@types/react": "^19.2.14", + "@types/react": "^19.2.15", "@typescript/native-preview": "catalog:", "@vercel/detect-agent": "^1.2.3", "@vitest/coverage-istanbul": "catalog:", + "dotenv": "^17.4.2", "effect": "catalog:", - "ink": "^7.0.3", + "ink": "^7.0.4", "ink-spinner": "^5.0.0", "knip": "catalog:", "oxfmt": "catalog:", "oxlint": "catalog:", "oxlint-tsgolint": "catalog:", - "posthog-node": "^5.34.3", + "posthog-node": "^5.35.5", "react": "^19.2.6", "react-devtools-core": "^7.0.1", - "semantic-release": "^24.2.9", - "vitest": "catalog:" + "semantic-release": "^25.0.3", + "smol-toml": "^1.6.1", + "vitest": "catalog:", + "yaml": "^2.9.0" }, "optionalDependencies": { "@supabase/cli-darwin-arm64": "workspace:*", @@ -98,7 +101,8 @@ ], "ignore": [ "scripts/*.ts", - "tests/**/*.ts" + "tests/**/*.ts", + "src/shared/telemetry/event-catalog.ts" ], "ignoreBinaries": [ "nx" diff --git a/apps/cli/scripts/build.ts b/apps/cli/scripts/build.ts index 07cca0deff..ca2a67a6da 100644 --- a/apps/cli/scripts/build.ts +++ b/apps/cli/scripts/build.ts @@ -12,7 +12,7 @@ const MUSL_TARGETS = [ nfpmArch: "arm64", }, { - bunTarget: "bun-linux-x64-musl", + bunTarget: "bun-linux-x64-musl-baseline", pkg: "cli-linux-x64-musl", nfpmArch: "amd64", }, @@ -62,14 +62,14 @@ const TARGETS = [ ext: "", }, { - bunTarget: "bun-linux-x64", + bunTarget: "bun-linux-x64-baseline", pkg: "cli-linux-x64", archive: `supabase_${version}_linux_amd64.tar.gz`, nfpmArch: "amd64", ext: "", }, { - bunTarget: "bun-windows-x64", + bunTarget: "bun-windows-x64-baseline", pkg: "cli-windows-x64", archive: `supabase_${version}_windows_amd64.zip`, ext: ".exe", @@ -93,8 +93,8 @@ const GO_TARGETS: Record = { "bun-darwin-arm64": { goos: "darwin", goarch: "arm64" }, "bun-darwin-x64": { goos: "darwin", goarch: "amd64" }, "bun-linux-arm64": { goos: "linux", goarch: "arm64" }, - "bun-linux-x64": { goos: "linux", goarch: "amd64" }, - "bun-windows-x64": { goos: "windows", goarch: "amd64" }, + "bun-linux-x64-baseline": { goos: "linux", goarch: "amd64" }, + "bun-windows-x64-baseline": { goos: "windows", goarch: "amd64" }, "bun-windows-arm64": { goos: "windows", goarch: "arm64" }, }; @@ -102,7 +102,7 @@ function libcForBunTarget(target: string): "glibc" | "musl" | "" { if (!target.startsWith("bun-linux-")) { return ""; } - return target.endsWith("-musl") ? "musl" : "glibc"; + return target.includes("-musl") ? "musl" : "glibc"; } async function buildTarget(target: (typeof TARGETS)[number]) { diff --git a/apps/cli/src/legacy/SIDE_EFFECTS_TEMPLATE.md b/apps/cli/src/legacy/SIDE_EFFECTS_TEMPLATE.md index 50d8bd4786..a27b7f3ab6 100644 --- a/apps/cli/src/legacy/SIDE_EFFECTS_TEMPLATE.md +++ b/apps/cli/src/legacy/SIDE_EFFECTS_TEMPLATE.md @@ -74,6 +74,19 @@ | `1` | authentication error (no token found) | | `1` | network / connection failure | +## Telemetry Events Fired + + + +| Event | When | Notable properties / groups | +| ---------------------- | ------------------------------------------ | ----------------------------------- | +| `cli_command_executed` | post-run, success or failure (via wrapper) | `exit_code`, `duration_ms`, `flags` | + ## Output