Repository: supabase/cli
Pull request #812 (2023-01-25 05:38:30)

chore: improve error handling with command suggestion
## What kind of change does this PR introduce?

Bug fix

## What is the current behavior?

sometimes the suggested `--debug` flag doesn't help

## What is the new behavior?

- If the error is due to a problem with user's host system, report the underlying error instead of prompting for debug flag.
- include the working directory in error report

## Additional context

Add any other context or screenshots.



Patch:
diff --git a/cmd/root.go b/cmd/root.go
index 602f6cc7..0ab0c28c 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -34,8 +34,7 @@ func IsExperimental(cmd *cobra.Command) bool {
 
 var (
 	// Passed from `-ldflags`: https://stackoverflow.com/q/11354518.
-	version    string
-	suggestion string
+	version string
 
 	rootCmd = &cobra.Command{
 		Use:     "supabase",
@@ -49,7 +48,7 @@ var (
 			if viper.GetBool("DEBUG") {
 				cmd.SetContext(utils.WithTraceContext(cmd.Context()))
 			} else {
-				suggestion = "Try rerunning the command with --debug to troubleshoot the error."
+				utils.CmdSuggestion = "Try rerunning the command with --debug to troubleshoot the error."
 			}
 			workdir := viper.GetString("WORKDIR")
 			if workdir == "" {
@@ -65,8 +64,8 @@ var (
 
 func Execute() {
 	if err := rootCmd.Execute(); err != nil {
-		if len(suggestion) > 0 {
-			fmt.Fprintln(os.Stderr, suggestion)
+		if len(utils.CmdSuggestion) > 0 {
+			fmt.Fprintln(os.Stderr, utils.CmdSuggestion)
 		}
 		os.Exit(1)
 	}
diff --git a/internal/db/branch/switch_/switch_.go b/internal/db/branch/switch_/switch_.go
index ca03aaee..88ef3e0a 100644
--- a/internal/db/branch/switch_/switch_.go
+++ b/internal/db/branch/switch_/switch_.go
@@ -16,9 +16,6 @@ import (
 func Run(ctx context.Context, target string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
 	// 1. Sanity checks
 	{
-		if err := utils.AssertSupabaseCliIsSetUpFS(fsys); err != nil {
-			return err
-		}
 		if err := utils.LoadConfigFS(fsys); err != nil {
 			return err
 		}
diff --git a/internal/db/start/start.go b/internal/db/start/start.go
index 3c222ae3..6339dc61 100644
--- a/internal/db/start/start.go
+++ b/internal/db/start/start.go
@@ -22,9 +22,6 @@ import (
 )
 
 func Run(ctx context.Context, fsys afero.Fs) error {
-	if err := utils.AssertSupabaseCliIsSetUpFS(fsys); err != nil {
-		return err
-	}
 	if err := utils.LoadConfigFS(fsys); err != nil {
 		return err
 	}
diff --git a/internal/functions/serve/serve.go b/internal/functions/serve/serve.go
index bf3e8e40..0d5d7373 100644
--- a/internal/functions/serve/serve.go
+++ b/internal/functions/serve/serve.go
@@ -43,9 +43,6 @@ func ParseEnvFile(envFilePath string) ([]string, error) {
 func Run(ctx context.Context, slug string, envFilePath string, noVerifyJWT *bool, importMapPath string, fsys afero.Fs) error {
 	// 1. Sanity checks.
 	{
-		if err := utils.AssertSupabaseCliIsSetUpFS(fsys); err != nil {
-			return err
-		}
 		if err := utils.LoadConfigFS(fsys); err != nil {
 			return err
 		}
diff --git a/internal/start/start.go b/internal/start/start.go
index 3a7e6c0d..36e5afd5 100644
--- a/internal/start/start.go
+++ b/internal/start/start.go
@@ -27,9 +27,6 @@ var errUnhealthy = errors.New("service not healthy")
 func Run(ctx context.Context, fsys afero.Fs, excludedContainers []string, ignoreHealthCheck bool) error {
 	// Sanity checks.
 	{
-		if err := utils.AssertSupabaseCliIsSetUpFS(fsys); err != nil {
-			return err
-		}
 		if err := utils.LoadConfigFS(fsys); err != nil {
 			return err
 		}
diff --git a/internal/status/status.go b/internal/status/status.go
index 1d910179..8ea7bf46 100644
--- a/internal/status/status.go
+++ b/internal/status/status.go
@@ -68,9 +68,6 @@ func sliceContains(s []string, e string) bool {
 func Run(ctx context.Context, names CustomName, format string, fsys afero.Fs) error {
 	// Sanity checks.
 	{
-		if err := utils.AssertSupabaseCliIsSetUpFS(fsys); err != nil {
-			return err
-		}
 		if err := utils.LoadConfigFS(fsys); err != nil {
 			return err
 		}
diff --git a/internal/utils/config.go b/internal/utils/config.go
index a93de349..fd55b647 100644
--- a/internal/utils/config.go
+++ b/internal/utils/config.go
@@ -163,10 +163,13 @@ func LoadConfig() error {
 
 func LoadConfigFS(fsys afero.Fs) error {
 	// TODO: provide a config interface for all sub commands to use fsys
-	if _, err := toml.DecodeFS(afero.NewIOFS(fsys), ConfigPath, &Config); errors.Is(err, os.ErrNotExist) {
-		return fmt.Errorf("Missing config: %w", err)
-	} else if err != nil {
-		return fmt.Errorf("Failed to read config: %w", err)
+	if _, err := toml.DecodeFS(afero.NewIOFS(fsys), ConfigPath, &Config); err != nil {
+		CmdSuggestion = fmt.Sprintf("Have you set up the project with %s?", Aqua("supabase init"))
+		cwd, osErr := os.Getwd()
+		if osErr != nil {
+			cwd = "current directory"
+		}
+		return fmt.Errorf("cannot read config in %s: %w", cwd, err)
 	}
 
 	// Process decoded TOML.
diff --git a/internal/utils/misc.go b/internal/utils/misc.go
index d3f0f267..7ce5a5d4 100644
--- a/internal/utils/misc.go
+++ b/internal/utils/misc.go
@@ -85,6 +85,8 @@ DO 'BEGIN WHILE (
 )
 
 var (
+	CmdSuggestion string
+
 	// pg_dumpall --globals-only --no-role-passwords --dbname $DB_URL \
 	// | sed '/^CREATE ROLE postgres;/d' \
 	// | sed '/^ALTER ROLE postgres WITH /d' \


Test patch:
diff --git a/internal/db/branch/switch_/switch__test.go b/internal/db/branch/switch_/switch__test.go
index 101483f7..ae8e1228 100644
--- a/internal/db/branch/switch_/switch__test.go
+++ b/internal/db/branch/switch_/switch__test.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"net/http"
+	"os"
 	"path/filepath"
 	"testing"
 
@@ -72,7 +73,7 @@ func TestSwitchCommand(t *testing.T) {
 		// Run test
 		err := Run(context.Background(), "target", fsys)
 		// Check error
-		assert.ErrorContains(t, err, "Have you set up the project with supabase init?")
+		assert.ErrorIs(t, err, os.ErrNotExist)
 	})
 
 	t.Run("throws error on malformed config", func(t *testing.T) {
@@ -82,7 +83,7 @@ func TestSwitchCommand(t *testing.T) {
 		// Run test
 		err := Run(context.Background(), "target", fsys)
 		// Check error
-		assert.ErrorContains(t, err, "Failed to read config: toml")
+		assert.ErrorContains(t, err, "toml: line 0: unexpected EOF; expected key separator '='")
 	})
 
 	t.Run("throws error on missing database", func(t *testing.T) {
diff --git a/internal/db/diff/migra_test.go b/internal/db/diff/migra_test.go
index c132022e..dbe378f7 100644
--- a/internal/db/diff/migra_test.go
+++ b/internal/db/diff/migra_test.go
@@ -5,6 +5,7 @@ import (
 	"errors"
 	"io"
 	"net/http"
+	"os"
 	"path/filepath"
 	"strings"
 	"testing"
@@ -68,7 +69,7 @@ func TestRunMigra(t *testing.T) {
 		// Run test
 		err := RunMigra(context.Background(), []string{"public"}, "", "", fsys)
 		// Check error
-		assert.ErrorContains(t, err, "Missing config: open supabase/config.toml: file does not exist")
+		assert.ErrorIs(t, err, os.ErrNotExist)
 	})
 
 	t.Run("throws error on missing project", func(t *testing.T) {
diff --git a/internal/db/reset/reset_test.go b/internal/db/reset/reset_test.go
index 7af5f49e..d4d0157a 100644
--- a/internal/db/reset/reset_test.go
+++ b/internal/db/reset/reset_test.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"net/http"
+	"os"
 	"path/filepath"
 	"testing"
 	"time"
@@ -25,7 +26,7 @@ func TestResetCommand(t *testing.T) {
 
 	t.Run("throws error on missing config", func(t *testing.T) {
 		err := Run(context.Background(), afero.NewMemMapFs())
-		assert.ErrorContains(t, err, "Missing config: open supabase/config.toml: file does not exist")
+		assert.ErrorIs(t, err, os.ErrNotExist)
 	})
 
 	t.Run("throws error on db is not started", func(t *testing.T) {
diff --git a/internal/db/test/test_test.go b/internal/db/test/test_test.go
index 08bc897d..bcb3a162 100644
--- a/internal/db/test/test_test.go
+++ b/internal/db/test/test_test.go
@@ -6,6 +6,7 @@ import (
 	"errors"
 	"io/fs"
 	"net/http"
+	"os"
 	"path/filepath"
 	"strings"
 	"testing"
@@ -101,7 +102,7 @@ func TestRunCommand(t *testing.T) {
 		// Run test
 		err := Run(context.Background(), fsys)
 		// Check error
-		assert.ErrorContains(t, err, "Missing config: open supabase/config.toml: file does not exist")
+		assert.ErrorIs(t, err, os.ErrNotExist)
 	})
 
 	t.Run("throws error on missing database", func(t *testing.T) {
diff --git a/internal/link/link_test.go b/internal/link/link_test.go
index 9cbc1090..3977346d 100644
--- a/internal/link/link_test.go
+++ b/internal/link/link_test.go
@@ -3,6 +3,7 @@ package link
 import (
 	"context"
 	"errors"
+	"os"
 	"strings"
 	"testing"
 
@@ -61,7 +62,7 @@ func TestPreRun(t *testing.T) {
 		// Run test
 		err := PreRun(project, fsys)
 		// Check error
-		assert.ErrorContains(t, err, "Missing config: open supabase/config.toml: file does not exist")
+		assert.ErrorIs(t, err, os.ErrNotExist)
 	})
 }
 
diff --git a/internal/start/start_test.go b/internal/start/start_test.go
index 6798a349..dc395a06 100644
--- a/internal/start/start_test.go
+++ b/internal/start/start_test.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"errors"
 	"net/http"
+	"os"
 	"testing"
 
 	"github.com/docker/docker/api/types"
@@ -19,7 +20,7 @@ import (
 func TestStartCommand(t *testing.T) {
 	t.Run("throws error on missing config", func(t *testing.T) {
 		err := Run(context.Background(), afero.NewMemMapFs(), []string{}, false)
-		assert.ErrorContains(t, err, "Have you set up the project with supabase init?")
+		assert.ErrorIs(t, err, os.ErrNotExist)
 	})
 
 	t.Run("throws error on invalid config", func(t *testing.T) {
@@ -29,7 +30,7 @@ func TestStartCommand(t *testing.T) {
 		// Run test
 		err := Run(context.Background(), fsys, []string{}, false)
 		// Check error
-		assert.ErrorContains(t, err, "Failed to read config: toml")
+		assert.ErrorContains(t, err, "toml: line 0: unexpected EOF; expected key separator '='")
 	})
 
 	t.Run("throws error on missing docker", func(t *testing.T) {
diff --git a/internal/status/status_test.go b/internal/status/status_test.go
index 735f13ea..b2f07d75 100644
--- a/internal/status/status_test.go
+++ b/internal/status/status_test.go
@@ -6,6 +6,7 @@ import (
 	"errors"
 	"io"
 	"net/http"
+	"os"
 	"strings"
 	"testing"
 
@@ -54,7 +55,7 @@ func TestStatusCommand(t *testing.T) {
 
 	t.Run("throws error on missing config", func(t *testing.T) {
 		err := Run(context.Background(), CustomName{}, OutputPretty, afero.NewMemMapFs())
-		assert.ErrorContains(t, err, "Have you set up the project with supabase init?")
+		assert.ErrorIs(t, err, os.ErrNotExist)
 	})
 
 	t.Run("throws error on invalid config", func(t *testing.T) {
@@ -64,7 +65,7 @@ func TestStatusCommand(t *testing.T) {
 		// Run test
 		err := Run(context.Background(), CustomName{}, OutputPretty, fsys)
 		// Check error
-		assert.ErrorContains(t, err, "Failed to read config: toml")
+		assert.ErrorContains(t, err, "toml: line 0: unexpected EOF; expected key separator '='")
 	})
 
 	t.Run("throws error on missing docker", func(t *testing.T) {
diff --git a/internal/stop/stop_test.go b/internal/stop/stop_test.go
index daa23c33..e8751638 100644
--- a/internal/stop/stop_test.go
+++ b/internal/stop/stop_test.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"errors"
 	"net/http"
+	"os"
 	"testing"
 
 	"github.com/docker/docker/api/types"
@@ -52,7 +53,7 @@ func TestStopCommand(t *testing.T) {
 		// Run test
 		err := Run(context.Background(), false, fsys)
 		// Check error
-		assert.ErrorContains(t, err, "Missing config: open supabase/config.toml: file does not exist")
+		assert.ErrorIs(t, err, os.ErrNotExist)
 	})
 
 	t.Run("throws error on stop failure", func(t *testing.T) {