From 6086a640cee100caf8cf23759188a08748e55acf Mon Sep 17 00:00:00 2001 From: StoneTapeStudios Date: Mon, 20 Jan 2025 12:51:31 -0500 Subject: [PATCH 1/7] init commit, added configFile to global flags --- cmd/root.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/root.go b/cmd/root.go index 35540a897..acb7013dd 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -81,6 +81,7 @@ var ( } createTicket bool + configFile string rootCmd = &cobra.Command{ Use: "supabase", @@ -234,6 +235,7 @@ func init() { flags.Var(&utils.OutputFormat, "output", "output format of status variables") flags.Var(&utils.DNSResolver, "dns-resolver", "lookup domain names using the specified resolver") flags.BoolVar(&createTicket, "create-ticket", false, "create a support ticket for any CLI error") + flags.StringVar(&configFile, "config-file", "", "path to config file (default: supabase/config.toml)") cobra.CheckErr(viper.BindPFlags(flags)) rootCmd.SetVersionTemplate("{{.Version}}\n") From 68ba8dc1c51914d0db45353ef0c7b15f3e54262d Mon Sep 17 00:00:00 2001 From: StoneTapeStudios Date: Mon, 20 Jan 2025 14:00:36 -0500 Subject: [PATCH 2/7] fixed adding flag --- cmd/root.go | 21 ++++++++++----------- internal/utils/flags/config_path.go | 10 +++++++++- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index acb7013dd..b8f79b4f9 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -81,7 +81,6 @@ var ( } createTicket bool - configFile string rootCmd = &cobra.Command{ Use: "supabase", @@ -227,16 +226,16 @@ func init() { viper.AutomaticEnv() }) - flags := rootCmd.PersistentFlags() - flags.Bool("debug", false, "output debug logs to stderr") - flags.String("workdir", "", "path to a Supabase project directory") - flags.Bool("experimental", false, "enable experimental features") - flags.String("network-id", "", "use the specified docker network instead of a generated one") - flags.Var(&utils.OutputFormat, "output", "output format of status variables") - flags.Var(&utils.DNSResolver, "dns-resolver", "lookup domain names using the specified resolver") - flags.BoolVar(&createTicket, "create-ticket", false, "create a support ticket for any CLI error") - flags.StringVar(&configFile, "config-file", "", "path to config file (default: supabase/config.toml)") - cobra.CheckErr(viper.BindPFlags(flags)) + f := rootCmd.PersistentFlags() + f.Bool("debug", false, "output debug logs to stderr") + f.String("workdir", "", "path to a Supabase project directory") + f.Bool("experimental", false, "enable experimental features") + f.String("network-id", "", "use the specified docker network instead of a generated one") + f.StringVar(&flags.ConfigFile, "config-file", "", "path to config file (default: supabase/config.toml)") + f.Var(&utils.OutputFormat, "output", "output format of status variables") + f.Var(&utils.DNSResolver, "dns-resolver", "lookup domain names using the specified resolver") + f.BoolVar(&createTicket, "create-ticket", false, "create a support ticket for any CLI error") + cobra.CheckErr(viper.BindPFlags(f)) rootCmd.SetVersionTemplate("{{.Version}}\n") rootCmd.AddGroup(&cobra.Group{ID: groupQuickStart, Title: "Quick Start:"}) diff --git a/internal/utils/flags/config_path.go b/internal/utils/flags/config_path.go index d0ed105c7..bbc29d2d0 100644 --- a/internal/utils/flags/config_path.go +++ b/internal/utils/flags/config_path.go @@ -9,9 +9,17 @@ import ( "github.com/supabase/cli/internal/utils" ) +var ConfigFile string + func LoadConfig(fsys afero.Fs) error { utils.Config.ProjectId = ProjectRef - if err := utils.Config.Load("", utils.NewRootFS(fsys)); err != nil { + + configPath := "" + if ConfigFile != "" { + configPath = ConfigFile + } + + if err := utils.Config.Load(configPath, utils.NewRootFS(fsys)); err != nil { if errors.Is(err, os.ErrNotExist) { utils.CmdSuggestion = fmt.Sprintf("Have you set up the project with %s?", utils.Aqua("supabase init")) } From 7060af60c028faafb498c9c2715487a62160f01f Mon Sep 17 00:00:00 2001 From: StoneTapeStudios Date: Mon, 20 Jan 2025 16:03:47 -0500 Subject: [PATCH 3/7] test(start): add test for custom config path --- internal/start/start_test.go | 85 ++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/internal/start/start_test.go b/internal/start/start_test.go index bbd73ab93..e208fcfb8 100644 --- a/internal/start/start_test.go +++ b/internal/start/start_test.go @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/supabase/cli/internal/testing/apitest" "github.com/supabase/cli/internal/utils" + "github.com/supabase/cli/internal/utils/flags" "github.com/supabase/cli/pkg/config" "github.com/supabase/cli/pkg/pgtest" "github.com/supabase/cli/pkg/storage" @@ -91,6 +92,90 @@ func TestStartCommand(t *testing.T) { assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) }) + + t.Run("loads custom config path", func(t *testing.T) { + // Setup in-memory fs + fsys := afero.NewMemMapFs() + customPath := "custom/path/config.toml" + projectId := "test_project" + + // Create directories and required files + require.NoError(t, fsys.MkdirAll("custom/path", 0755)) + require.NoError(t, fsys.MkdirAll("supabase", 0755)) + require.NoError(t, afero.WriteFile(fsys, "supabase/seed.sql", []byte(""), 0644)) + require.NoError(t, afero.WriteFile(fsys, "supabase/roles.sql", []byte(""), 0644)) + + // Store original values + originalDbId := utils.DbId + originalConfigFile := flags.ConfigFile + + // Restore original values after test + t.Cleanup(func() { + utils.DbId = originalDbId + flags.ConfigFile = originalConfigFile + gock.Off() + }) + + // Write config file + require.NoError(t, afero.WriteFile(fsys, customPath, []byte(`# Test configuration +project_id = "`+projectId+`" + +[api] +enabled = true +port = 54331 +schemas = ["public", "storage", "graphql_public"] +extra_search_path = ["public", "extensions"] +max_rows = 1000 + +[db] +port = 54332 +shadow_port = 54330 +major_version = 15 + +[studio] +port = 54333 + +[inbucket] +port = 54334 + +[storage] +file_size_limit = "50MiB" + +[auth] +site_url = "http://localhost:54331" +additional_redirect_urls = ["http://localhost:54331"] +jwt_expiry = 3600 +enable_signup = true`), 0644)) + + // Setup mock docker + require.NoError(t, apitest.MockDocker(utils.Docker)) + + // Mock container list check + gock.New(utils.Docker.DaemonHost()). + Get("/v" + utils.Docker.ClientVersion() + "/containers/json"). + Reply(http.StatusOK). + JSON([]types.Container{}) + + // Mock container checks + utils.DbId = "supabase_db_" + projectId + gock.New(utils.Docker.DaemonHost()). + Get("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.DbId + "/json"). + Times(2). + Reply(http.StatusOK). + JSON(types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ + State: &types.ContainerState{ + Running: true, + Health: &types.Health{Status: types.Healthy}, + }, + }}) + + // Set the custom config path + flags.ConfigFile = customPath + // Run test + err := Run(context.Background(), fsys, []string{}, false) + assert.NoError(t, err) + assert.Empty(t, apitest.ListUnmatchedRequests()) + }) } func TestDatabaseStart(t *testing.T) { From 79bbc1e72994abcc71bdfc3fe91bb57f70b6b334 Mon Sep 17 00:00:00 2001 From: StoneTapeStudios Date: Tue, 21 Jan 2025 14:24:02 -0500 Subject: [PATCH 4/7] revert rename of local var to flags and import flags under alias --- cmd/root.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index b8f79b4f9..ae60e14cb 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -16,7 +16,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/supabase/cli/internal/utils" - "github.com/supabase/cli/internal/utils/flags" + flagsutils "github.com/supabase/cli/internal/utils/flags" "golang.org/x/mod/semver" ) @@ -104,12 +104,12 @@ var ( } ctx, _ = signal.NotifyContext(ctx, os.Interrupt) if cmd.Flags().Lookup("project-ref") != nil { - if err := flags.ParseProjectRef(ctx, fsys); err != nil { + if err := flagsutils.ParseProjectRef(ctx, fsys); err != nil { return err } } } - if err := flags.ParseDatabaseConfig(cmd.Flags(), fsys); err != nil { + if err := flagsutils.ParseDatabaseConfig(cmd.Flags(), fsys); err != nil { return err } // Prepare context @@ -226,16 +226,16 @@ func init() { viper.AutomaticEnv() }) - f := rootCmd.PersistentFlags() - f.Bool("debug", false, "output debug logs to stderr") - f.String("workdir", "", "path to a Supabase project directory") - f.Bool("experimental", false, "enable experimental features") - f.String("network-id", "", "use the specified docker network instead of a generated one") - f.StringVar(&flags.ConfigFile, "config-file", "", "path to config file (default: supabase/config.toml)") - f.Var(&utils.OutputFormat, "output", "output format of status variables") - f.Var(&utils.DNSResolver, "dns-resolver", "lookup domain names using the specified resolver") - f.BoolVar(&createTicket, "create-ticket", false, "create a support ticket for any CLI error") - cobra.CheckErr(viper.BindPFlags(f)) + flags := rootCmd.PersistentFlags() + flags.Bool("debug", false, "output debug logs to stderr") + flags.String("workdir", "", "path to a Supabase project directory") + flags.Bool("experimental", false, "enable experimental features") + flags.String("network-id", "", "use the specified docker network instead of a generated one") + flags.StringVar(&flagsutils.ConfigFile, "config-file", "", "path to config file (default: supabase/config.toml)") + flags.Var(&utils.OutputFormat, "output", "output format of status variables") + flags.Var(&utils.DNSResolver, "dns-resolver", "lookup domain names using the specified resolver") + flags.BoolVar(&createTicket, "create-ticket", false, "create a support ticket for any CLI error") + cobra.CheckErr(viper.BindPFlags(flags)) rootCmd.SetVersionTemplate("{{.Version}}\n") rootCmd.AddGroup(&cobra.Group{ID: groupQuickStart, Title: "Quick Start:"}) @@ -264,6 +264,6 @@ func addSentryScope(scope *sentry.Scope) { scope.SetContext("Services", imageToVersion) scope.SetContext("Config", map[string]interface{}{ "Image Registry": utils.GetRegistry(), - "Project ID": flags.ProjectRef, + "Project ID": flagsutils.ProjectRef, }) } From c5b22334a205ff3d8ee4bba0a451a624fd60326c Mon Sep 17 00:00:00 2001 From: StoneTapeStudios Date: Fri, 31 Jan 2025 10:56:23 -0500 Subject: [PATCH 5/7] formatting --- internal/start/start_test.go | 44 ++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/internal/start/start_test.go b/internal/start/start_test.go index e208fcfb8..5d812f409 100644 --- a/internal/start/start_test.go +++ b/internal/start/start_test.go @@ -118,34 +118,34 @@ func TestStartCommand(t *testing.T) { // Write config file require.NoError(t, afero.WriteFile(fsys, customPath, []byte(`# Test configuration -project_id = "`+projectId+`" + project_id = "`+projectId+`" -[api] -enabled = true -port = 54331 -schemas = ["public", "storage", "graphql_public"] -extra_search_path = ["public", "extensions"] -max_rows = 1000 + [api] + enabled = true + port = 54331 + schemas = ["public", "storage", "graphql_public"] + extra_search_path = ["public", "extensions"] + max_rows = 1000 -[db] -port = 54332 -shadow_port = 54330 -major_version = 15 + [db] + port = 54332 + shadow_port = 54330 + major_version = 15 -[studio] -port = 54333 + [studio] + port = 54333 -[inbucket] -port = 54334 + [inbucket] + port = 54334 -[storage] -file_size_limit = "50MiB" + [storage] + file_size_limit = "50MiB" -[auth] -site_url = "http://localhost:54331" -additional_redirect_urls = ["http://localhost:54331"] -jwt_expiry = 3600 -enable_signup = true`), 0644)) + [auth] + site_url = "http://localhost:54331" + additional_redirect_urls = ["http://localhost:54331"] + jwt_expiry = 3600 + enable_signup = true`), 0644)) // Setup mock docker require.NoError(t, apitest.MockDocker(utils.Docker)) From fb19d7690443dd58b942d19dc0692f9a356c34b3 Mon Sep 17 00:00:00 2001 From: StoneTapeStudios Date: Fri, 31 Jan 2025 11:17:34 -0500 Subject: [PATCH 6/7] formatting changes --- internal/start/start_test.go | 14 +++++++------- internal/utils/flags/config_path.go | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/start/start_test.go b/internal/start/start_test.go index 5d812f409..5c6d91732 100644 --- a/internal/start/start_test.go +++ b/internal/start/start_test.go @@ -98,24 +98,24 @@ func TestStartCommand(t *testing.T) { fsys := afero.NewMemMapFs() customPath := "custom/path/config.toml" projectId := "test_project" - + // Create directories and required files require.NoError(t, fsys.MkdirAll("custom/path", 0755)) require.NoError(t, fsys.MkdirAll("supabase", 0755)) require.NoError(t, afero.WriteFile(fsys, "supabase/seed.sql", []byte(""), 0644)) require.NoError(t, afero.WriteFile(fsys, "supabase/roles.sql", []byte(""), 0644)) - + // Store original values originalDbId := utils.DbId originalConfigFile := flags.ConfigFile - + // Restore original values after test t.Cleanup(func() { utils.DbId = originalDbId flags.ConfigFile = originalConfigFile gock.Off() }) - + // Write config file require.NoError(t, afero.WriteFile(fsys, customPath, []byte(`# Test configuration project_id = "`+projectId+`" @@ -146,10 +146,10 @@ func TestStartCommand(t *testing.T) { additional_redirect_urls = ["http://localhost:54331"] jwt_expiry = 3600 enable_signup = true`), 0644)) - + // Setup mock docker require.NoError(t, apitest.MockDocker(utils.Docker)) - + // Mock container list check gock.New(utils.Docker.DaemonHost()). Get("/v" + utils.Docker.ClientVersion() + "/containers/json"). @@ -165,7 +165,7 @@ func TestStartCommand(t *testing.T) { JSON(types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ State: &types.ContainerState{ Running: true, - Health: &types.Health{Status: types.Healthy}, + Health: &types.Health{Status: types.Healthy}, }, }}) diff --git a/internal/utils/flags/config_path.go b/internal/utils/flags/config_path.go index bbc29d2d0..5dfd7366c 100644 --- a/internal/utils/flags/config_path.go +++ b/internal/utils/flags/config_path.go @@ -13,12 +13,12 @@ var ConfigFile string func LoadConfig(fsys afero.Fs) error { utils.Config.ProjectId = ProjectRef - + configPath := "" if ConfigFile != "" { configPath = ConfigFile } - + if err := utils.Config.Load(configPath, utils.NewRootFS(fsys)); err != nil { if errors.Is(err, os.ErrNotExist) { utils.CmdSuggestion = fmt.Sprintf("Have you set up the project with %s?", utils.Aqua("supabase init")) From e64fd9ba983bfd517e29fbbb4c200562a8980278 Mon Sep 17 00:00:00 2001 From: StoneTapeStudios Date: Mon, 10 Feb 2025 12:55:18 -0500 Subject: [PATCH 7/7] feat(cli): handle workdir based on config file path Adds support for setting the working directory based on the provided config file path: - Handles both absolute and relative config paths - Normalizes paths across platforms - Maintains Windows compatibility by handling drive letters - Preserves existing behavior when no config file is specified This addresses the feedback about workdir handling in the CLI by ensuring the working directory is properly set based on the config file location. Tests added: - Relative config paths - Absolute config paths - Non-existent config paths - Malformed config files --- internal/start/start_test.go | 178 ++++++++++++++++++++++------ internal/utils/flags/config_path.go | 40 ++++++- 2 files changed, 182 insertions(+), 36 deletions(-) diff --git a/internal/start/start_test.go b/internal/start/start_test.go index 5c6d91732..90efd2410 100644 --- a/internal/start/start_test.go +++ b/internal/start/start_test.go @@ -4,8 +4,10 @@ import ( "bytes" "context" "errors" + "fmt" "net/http" "os" + "path/filepath" "regexp" "testing" @@ -15,6 +17,7 @@ import ( "github.com/h2non/gock" "github.com/jackc/pgconn" "github.com/spf13/afero" + "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/supabase/cli/internal/testing/apitest" @@ -94,13 +97,12 @@ func TestStartCommand(t *testing.T) { }) t.Run("loads custom config path", func(t *testing.T) { - // Setup in-memory fs fsys := afero.NewMemMapFs() - customPath := "custom/path/config.toml" + customPath := filepath.ToSlash("custom/path/config.toml") projectId := "test_project" // Create directories and required files - require.NoError(t, fsys.MkdirAll("custom/path", 0755)) + require.NoError(t, fsys.MkdirAll(filepath.Dir(customPath), 0755)) require.NoError(t, fsys.MkdirAll("supabase", 0755)) require.NoError(t, afero.WriteFile(fsys, "supabase/seed.sql", []byte(""), 0644)) require.NoError(t, afero.WriteFile(fsys, "supabase/roles.sql", []byte(""), 0644)) @@ -108,47 +110,88 @@ func TestStartCommand(t *testing.T) { // Store original values originalDbId := utils.DbId originalConfigFile := flags.ConfigFile + originalWorkdir := viper.GetString("WORKDIR") - // Restore original values after test t.Cleanup(func() { utils.DbId = originalDbId flags.ConfigFile = originalConfigFile + viper.Set("WORKDIR", originalWorkdir) gock.Off() }) // Write config file - require.NoError(t, afero.WriteFile(fsys, customPath, []byte(`# Test configuration + require.NoError(t, afero.WriteFile(fsys, customPath, []byte(` project_id = "`+projectId+`" - - [api] - enabled = true - port = 54331 - schemas = ["public", "storage", "graphql_public"] - extra_search_path = ["public", "extensions"] - max_rows = 1000 - [db] port = 54332 - shadow_port = 54330 - major_version = 15 + major_version = 15`), 0644)) - [studio] - port = 54333 + // Setup mock docker + require.NoError(t, apitest.MockDocker(utils.Docker)) - [inbucket] - port = 54334 + // Mock container list check + gock.New(utils.Docker.DaemonHost()). + Get("/v" + utils.Docker.ClientVersion() + "/containers/json"). + Reply(http.StatusOK). + JSON([]types.Container{}) - [storage] - file_size_limit = "50MiB" + // Mock container health check + utils.DbId = "supabase_db_" + projectId + gock.New(utils.Docker.DaemonHost()). + Get("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.DbId + "/json"). + Times(2). + Reply(http.StatusOK). + JSON(types.ContainerJSON{ + ContainerJSONBase: &types.ContainerJSONBase{ + State: &types.ContainerState{ + Running: true, + Health: &types.Health{Status: types.Healthy}, + }, + }, + }) - [auth] - site_url = "http://localhost:54331" - additional_redirect_urls = ["http://localhost:54331"] - jwt_expiry = 3600 - enable_signup = true`), 0644)) + flags.ConfigFile = customPath + + err := Run(context.Background(), fsys, []string{}, false) + assert.NoError(t, err) + assert.Equal(t, filepath.ToSlash("custom/path"), viper.GetString("WORKDIR")) + assert.Empty(t, apitest.ListUnmatchedRequests()) + }) + + t.Run("handles absolute config path", func(t *testing.T) { + // Setup in-memory fs + fsys := afero.NewMemMapFs() + absPath := "/absolute/path/config.toml" + projectId := "abs_project" + + // Create directories and required files + require.NoError(t, fsys.MkdirAll("/absolute/path", 0755)) + require.NoError(t, fsys.MkdirAll("/supabase", 0755)) + require.NoError(t, afero.WriteFile(fsys, "/supabase/seed.sql", []byte(""), 0644)) + require.NoError(t, afero.WriteFile(fsys, "/supabase/roles.sql", []byte(""), 0644)) + + // Store original values + originalDbId := utils.DbId + originalConfigFile := flags.ConfigFile + originalWorkdir := viper.GetString("WORKDIR") + + t.Cleanup(func() { + utils.DbId = originalDbId + flags.ConfigFile = originalConfigFile + viper.Set("WORKDIR", originalWorkdir) + gock.Off() + }) + + // Write config file + require.NoError(t, afero.WriteFile(fsys, absPath, []byte(` + project_id = "`+projectId+`" + [db] + port = 54332 + major_version = 15`), 0644)) // Setup mock docker require.NoError(t, apitest.MockDocker(utils.Docker)) + defer gock.OffAll() // Mock container list check gock.New(utils.Docker.DaemonHost()). @@ -156,26 +199,74 @@ func TestStartCommand(t *testing.T) { Reply(http.StatusOK). JSON([]types.Container{}) - // Mock container checks + // Mock container health check utils.DbId = "supabase_db_" + projectId gock.New(utils.Docker.DaemonHost()). Get("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.DbId + "/json"). Times(2). Reply(http.StatusOK). - JSON(types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ - State: &types.ContainerState{ - Running: true, - Health: &types.Health{Status: types.Healthy}, + JSON(types.ContainerJSON{ + ContainerJSONBase: &types.ContainerJSONBase{ + State: &types.ContainerState{ + Running: true, + Health: &types.Health{Status: types.Healthy}, + }, }, - }}) + }) // Set the custom config path - flags.ConfigFile = customPath + flags.ConfigFile = absPath + // Run test err := Run(context.Background(), fsys, []string{}, false) assert.NoError(t, err) + assert.Equal(t, "/absolute/path", viper.GetString("WORKDIR")) assert.Empty(t, apitest.ListUnmatchedRequests()) }) + + t.Run("handles non-existent config directory", func(t *testing.T) { + // Setup in-memory fs + fsys := afero.NewMemMapFs() + nonExistentPath := "non/existent/path/config.toml" + + // Store original values + originalConfigFile := flags.ConfigFile + + t.Cleanup(func() { + flags.ConfigFile = originalConfigFile + }) + + // Set the custom config path + flags.ConfigFile = nonExistentPath + + // Run test + err := Run(context.Background(), fsys, []string{}, false) + assert.ErrorIs(t, err, os.ErrNotExist) + }) + + t.Run("handles malformed config in custom path", func(t *testing.T) { + // Setup in-memory fs + fsys := afero.NewMemMapFs() + customPath := "custom/config.toml" + + // Create directory and malformed config + require.NoError(t, fsys.MkdirAll("custom", 0755)) + require.NoError(t, afero.WriteFile(fsys, customPath, []byte("malformed toml content"), 0644)) + + // Store original values + originalConfigFile := flags.ConfigFile + + t.Cleanup(func() { + flags.ConfigFile = originalConfigFile + }) + + // Set the custom config path + flags.ConfigFile = customPath + + // Run test + err := Run(context.Background(), fsys, []string{}, false) + assert.ErrorContains(t, err, "toml: ") + }) } func TestDatabaseStart(t *testing.T) { @@ -368,3 +459,24 @@ func TestFormatMapForEnvConfig(t *testing.T) { } }) } + +// Helper function to reduce duplication +func setupTestConfig(t *testing.T, fsys afero.Fs, configPath, projectId string, isAbsolute bool) { + // Create directories and required files + require.NoError(t, fsys.MkdirAll(filepath.Dir(configPath), 0755)) + supabasePath := "supabase" + if isAbsolute { + supabasePath = "/supabase" + } + require.NoError(t, fsys.MkdirAll(supabasePath, 0755)) + require.NoError(t, afero.WriteFile(fsys, filepath.Join(supabasePath, "seed.sql"), []byte(""), 0644)) + require.NoError(t, afero.WriteFile(fsys, filepath.Join(supabasePath, "roles.sql"), []byte(""), 0644)) + + // Write minimal config file + configContent := fmt.Sprintf(` + project_id = "%s" + [db] + port = 54332 + major_version = 15`, projectId) + require.NoError(t, afero.WriteFile(fsys, configPath, []byte(configContent), 0644)) +} diff --git a/internal/utils/flags/config_path.go b/internal/utils/flags/config_path.go index 5dfd7366c..843f8576a 100644 --- a/internal/utils/flags/config_path.go +++ b/internal/utils/flags/config_path.go @@ -3,28 +3,62 @@ package flags import ( "fmt" "os" + "path/filepath" + "strings" "github.com/go-errors/errors" "github.com/spf13/afero" + "github.com/spf13/viper" "github.com/supabase/cli/internal/utils" ) var ConfigFile string func LoadConfig(fsys afero.Fs) error { + // Early return if no config file specified + if ConfigFile == "" { + utils.Config.ProjectId = ProjectRef + return nil + } + utils.Config.ProjectId = ProjectRef - configPath := "" - if ConfigFile != "" { - configPath = ConfigFile + // Step 1: Normalize the config path + configPath := filepath.ToSlash(ConfigFile) + + // Step 2: Handle absolute paths and set workdir + var workdir string + if filepath.IsAbs(ConfigFile) { + // Remove drive letter if present (Windows) + if i := strings.Index(configPath, ":"); i > 0 { + configPath = configPath[i+1:] + } + // Ensure path starts with / + if !strings.HasPrefix(configPath, "/") { + configPath = "/" + configPath + } + workdir = filepath.Dir(configPath) + } else { + workdir = filepath.Dir(configPath) + } + + // Step 3: Normalize workdir + workdir = filepath.ToSlash(workdir) + if filepath.IsAbs(ConfigFile) && !strings.HasPrefix(workdir, "/") { + workdir = "/" + workdir } + // Step 4: Set workdir in viper + viper.Set("WORKDIR", workdir) + + // Step 5: Load and validate config if err := utils.Config.Load(configPath, utils.NewRootFS(fsys)); err != nil { if errors.Is(err, os.ErrNotExist) { utils.CmdSuggestion = fmt.Sprintf("Have you set up the project with %s?", utils.Aqua("supabase init")) } return err } + utils.UpdateDockerIds() return nil }