Repository: supabase/cli
Pull request #314 (2022-07-25 08:04:20)

chore: some refactoring in preparation for migration test
## What kind of change does this PR introduce?

refactoring

## What is the current behavior?

Please link any relevant issues here.

## What is the new behavior?

- compile more regex patterns in `utils` package
- defaults to use `MkdirAll`

## Additional context

Add any other context or screenshots.



Patch:
diff --git a/cmd/db.go b/cmd/db.go
index 758a61c0..7966c95e 100644
--- a/cmd/db.go
+++ b/cmd/db.go
@@ -1,6 +1,7 @@
 package cmd
 
 import (
+	"github.com/spf13/afero"
 	"github.com/spf13/cobra"
 	"github.com/supabase/cli/internal/db/branch/create"
 	"github.com/supabase/cli/internal/db/branch/delete"
@@ -87,7 +88,7 @@ var (
 		Short: "Set the remote database to push migrations to.",
 		Args:  cobra.ExactArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			return set.Run(args[0])
+			return set.Run(args[0], afero.NewOsFs())
 		},
 	}
 
diff --git a/internal/db/push/push.go b/internal/db/push/push.go
index ab02af09..62f3a17d 100644
--- a/internal/db/push/push.go
+++ b/internal/db/push/push.go
@@ -5,7 +5,6 @@ import (
 	"errors"
 	"fmt"
 	"os"
-	"regexp"
 
 	pgx "github.com/jackc/pgx/v4"
 	"github.com/supabase/cli/internal/utils"
@@ -17,7 +16,7 @@ func Run(dryRun bool) error {
 	if dryRun {
 		fmt.Println("DRY RUN: migrations will *not* be pushed to the database.")
 	}
-	urlBytes, err := os.ReadFile("supabase/.temp/remote-db-url")
+	urlBytes, err := os.ReadFile(utils.RemoteDbPath)
 	if errors.Is(err, os.ErrNotExist) {
 		return errors.New("Remote database is not set. Run " + utils.Aqua("supabase db remote set") + " first.")
 	} else if err != nil {
@@ -66,9 +65,8 @@ Try running `+utils.Aqua("supabase db remote set")+".", err)
 		fmt.Println("Applying unapplied migrations...")
 	}
 
-	re := regexp.MustCompile(`([0-9]+)_.*\.sql`)
 	for i, migration := range migrations {
-		matches := re.FindStringSubmatch(migration.Name())
+		matches := utils.MigrateFilePattern.FindStringSubmatch(migration.Name())
 		if len(matches) == 0 {
 			return errors.New("Can't process file in supabase/migrations: " + migration.Name())
 		}
diff --git a/internal/db/remote/changes/changes.go b/internal/db/remote/changes/changes.go
index 1998454a..af970c48 100644
--- a/internal/db/remote/changes/changes.go
+++ b/internal/db/remote/changes/changes.go
@@ -35,7 +35,7 @@ func Run() error {
 		}
 	}
 
-	urlBytes, err := os.ReadFile("supabase/.temp/remote-db-url")
+	urlBytes, err := os.ReadFile(utils.RemoteDbPath)
 	if errors.Is(err, os.ErrNotExist) {
 		return errors.New("Remote database is not set. Run " + utils.Aqua("supabase db remote set") + " first.")
 	} else if err != nil {
@@ -163,9 +163,8 @@ func run(p utils.Program, url string) error {
 			return conflictErr
 		}
 
-		re := regexp.MustCompile(`([0-9]+)_.*\.sql`)
 		for i, remoteTimestamp := range remoteMigrations {
-			localTimestamp := re.FindStringSubmatch(localMigrations[i].Name())[1]
+			localTimestamp := utils.MigrateFilePattern.FindStringSubmatch(localMigrations[i].Name())[1]
 
 			if localTimestamp == remoteTimestamp {
 				continue
diff --git a/internal/db/remote/commit/commit.go b/internal/db/remote/commit/commit.go
index 788c3acc..9250273d 100644
--- a/internal/db/remote/commit/commit.go
+++ b/internal/db/remote/commit/commit.go
@@ -35,7 +35,7 @@ func Run() error {
 		}
 	}
 
-	urlBytes, err := os.ReadFile("supabase/.temp/remote-db-url")
+	urlBytes, err := os.ReadFile(utils.RemoteDbPath)
 	if errors.Is(err, os.ErrNotExist) {
 		return errors.New("Remote database is not set. Run " + utils.Aqua("supabase db remote set") + " first.")
 	} else if err != nil {
@@ -161,9 +161,8 @@ func run(p utils.Program, url string) error {
 			return conflictErr
 		}
 
-		re := regexp.MustCompile(`([0-9]+)_.*\.sql`)
 		for i, remoteTimestamp := range remoteMigrations {
-			localTimestamp := re.FindStringSubmatch(localMigrations[i].Name())[1]
+			localTimestamp := utils.MigrateFilePattern.FindStringSubmatch(localMigrations[i].Name())[1]
 
 			if localTimestamp == remoteTimestamp {
 				continue
diff --git a/internal/db/remote/set/set.go b/internal/db/remote/set/set.go
index 6a92e34c..787b94fd 100644
--- a/internal/db/remote/set/set.go
+++ b/internal/db/remote/set/set.go
@@ -5,22 +5,22 @@ import (
 	"errors"
 	"fmt"
 	u "net/url"
-	"os"
-	"regexp"
+	"path/filepath"
 
 	pgx "github.com/jackc/pgx/v4"
+	"github.com/spf13/afero"
 	"github.com/supabase/cli/internal/utils"
 )
 
-func Run(url string) error {
+func Run(url string, fsys afero.Fs) error {
 	// Sanity checks.
 	{
-		if err := utils.LoadConfig(); err != nil {
+		if err := utils.LoadConfigFS(fsys); err != nil {
 			return err
 		}
 	}
 
-	matches := regexp.MustCompile(`^postgres(?:ql)?://postgres:(.+)@(.+?)(:\d+)?/postgres$`).FindStringSubmatch(url)
+	matches := utils.PostgresUrlPattern.FindStringSubmatch(url)
 	if len(matches) != 4 {
 		return errors.New("URL is not a valid Supabase connection string.")
 	}
@@ -81,12 +81,11 @@ CREATE TABLE supabase_migrations.schema_migrations (version text NOT NULL PRIMAR
 		if err := utils.MkdirIfNotExist("supabase/migrations"); err != nil {
 			return err
 		}
-		localMigrations, err := os.ReadDir("supabase/migrations")
+		localMigrations, err := afero.ReadDir(fsys, "supabase/migrations")
 		if err != nil {
 			return err
 		}
 
-		re := regexp.MustCompile(`([0-9]+)_.*\.sql`)
 		for i, remoteTimestamp := range remoteMigrations {
 			if i >= len(localMigrations) {
 				return errors.New(`The remote database was applied with migration(s) that cannot be found locally. Try updating the project from version control. Otherwise:
@@ -95,8 +94,7 @@ CREATE TABLE supabase_migrations.schema_migrations (version text NOT NULL PRIMAR
 3. Run ` + utils.Aqua("supabase db remote commit") + ".")
 			}
 
-			localTimestamp := re.FindStringSubmatch(localMigrations[i].Name())[1]
-
+			localTimestamp := utils.MigrateFilePattern.FindStringSubmatch(localMigrations[i].Name())[1]
 			if localTimestamp == remoteTimestamp {
 				continue
 			}
@@ -110,10 +108,10 @@ CREATE TABLE supabase_migrations.schema_migrations (version text NOT NULL PRIMAR
 	rows.Close()
 
 	// 3. Write .env
-	if err := utils.MkdirIfNotExist("supabase/.temp"); err != nil {
+	if err := utils.MkdirIfNotExistFS(fsys, filepath.Dir(utils.RemoteDbPath)); err != nil {
 		return err
 	}
-	if err := os.WriteFile("supabase/.temp/remote-db-url", []byte(url), 0600); err != nil {
+	if err := afero.WriteFile(fsys, utils.RemoteDbPath, []byte(url), 0600); err != nil {
 		return err
 	}
 
diff --git a/internal/functions/deploy/deploy.go b/internal/functions/deploy/deploy.go
index 0f50587d..1b405afc 100644
--- a/internal/functions/deploy/deploy.go
+++ b/internal/functions/deploy/deploy.go
@@ -42,7 +42,7 @@ Enter your project ref: `, utils.GetSupabaseDashboardURL())
 
 				projectRef := strings.TrimSpace(scanner.Text())
 
-				if err := utils.MkdirIfNotExist("supabase/.temp"); err != nil {
+				if err := utils.MkdirIfNotExist(filepath.Dir(utils.ProjectRefPath)); err != nil {
 					return err
 				}
 				if err := os.WriteFile(utils.ProjectRefPath, []byte(projectRef), 0644); err != nil {
diff --git a/internal/functions/new/new.go b/internal/functions/new/new.go
index 1db3c7de..762b61c5 100644
--- a/internal/functions/new/new.go
+++ b/internal/functions/new/new.go
@@ -4,34 +4,30 @@ import (
 	"errors"
 	"fmt"
 	"os"
+	"path/filepath"
 
 	"github.com/supabase/cli/internal/utils"
 )
 
 func Run(slug string) error {
 	// 1. Sanity checks.
+	funcDir := "supabase/functions/" + slug
 	{
 		if err := utils.ValidateFunctionSlug(slug); err != nil {
 			return err
 		}
-		if _, err := os.Stat("supabase/functions/" + slug); !errors.Is(err, os.ErrNotExist) {
+		if _, err := os.Stat(funcDir); !errors.Is(err, os.ErrNotExist) {
 			return errors.New("Function " + utils.Aqua(slug) + " already exists locally.")
 		}
 	}
 
 	// 2. Create new function.
 	{
-		if err := utils.MkdirIfNotExist("supabase"); err != nil {
-			return err
-		}
-		if err := utils.MkdirIfNotExist("supabase/functions"); err != nil {
-			return err
-		}
-		if err := utils.MkdirIfNotExist("supabase/functions/" + slug); err != nil {
+		if err := utils.MkdirIfNotExist(funcDir); err != nil {
 			return err
 		}
 		if err := os.WriteFile(
-			"supabase/functions/"+slug+"/index.ts",
+			filepath.Join(funcDir, "index.ts"),
 			[]byte(`// Follow this setup guide to integrate the Deno language server with your editor:
 // https://deno.land/manual/getting_started/setup_your_environment
 // This enables autocomplete, go to definition, etc.
@@ -64,6 +60,6 @@ serve(async (req) => {
 		}
 	}
 
-	fmt.Println("Created new Function at " + utils.Bold("supabase/functions/"+slug))
+	fmt.Println("Created new Function at " + utils.Bold(funcDir))
 	return nil
 }
diff --git a/internal/init/init.go b/internal/init/init.go
index f2776136..28671b40 100644
--- a/internal/init/init.go
+++ b/internal/init/init.go
@@ -5,6 +5,7 @@ import (
 	_ "embed"
 	"errors"
 	"os"
+	"path/filepath"
 
 	"github.com/spf13/afero"
 	"github.com/supabase/cli/internal/utils"
@@ -43,7 +44,7 @@ func run(fsys afero.Fs) error {
 		}
 	}
 
-	if err := utils.MkdirIfNotExistFS(fsys, "supabase"); err != nil {
+	if err := utils.MkdirIfNotExistFS(fsys, filepath.Dir(utils.ConfigPath)); err != nil {
 		return err
 	}
 
diff --git a/internal/link/link.go b/internal/link/link.go
index db82e34c..d0e415cd 100644
--- a/internal/link/link.go
+++ b/internal/link/link.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"io"
 	"net/http"
+	"path/filepath"
 
 	"github.com/spf13/afero"
 	"github.com/supabase/cli/internal/utils"
@@ -45,10 +46,7 @@ func Run(projectRef string, fsys afero.Fs) error {
 
 	// 2. Save project ref
 	{
-		if err := utils.MkdirIfNotExistFS(fsys, "supabase"); err != nil {
-			return err
-		}
-		if err := utils.MkdirIfNotExistFS(fsys, "supabase/.temp"); err != nil {
+		if err := utils.MkdirIfNotExistFS(fsys, filepath.Dir(utils.ProjectRefPath)); err != nil {
 			return err
 		}
 		if err := afero.WriteFile(fsys, utils.ProjectRefPath, []byte(projectRef), 0644); err != nil {
diff --git a/internal/utils/config.go b/internal/utils/config.go
index 93c93295..26d8d938 100644
--- a/internal/utils/config.go
+++ b/internal/utils/config.go
@@ -126,10 +126,10 @@ type (
 )
 
 func LoadConfig() error {
-	return loadConfig(afero.NewOsFs())
+	return LoadConfigFS(afero.NewOsFs())
 }
 
-func loadConfig(fsys afero.Fs) 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); err == nil {
 		// skip
diff --git a/internal/utils/misc.go b/internal/utils/misc.go
index a1276d9f..4a44113e 100644
--- a/internal/utils/misc.go
+++ b/internal/utils/misc.go
@@ -52,6 +52,7 @@ DO 'BEGIN WHILE (SELECT COUNT(*) FROM pg_replication_slots) > 0 LOOP END LOOP; E
 
 	ConfigPath     = "supabase/config.toml"
 	ProjectRefPath = "supabase/.temp/project-ref"
+	RemoteDbPath   = "supabase/.temp/remote-db-url"
 )
 
 var (
@@ -60,6 +61,8 @@ var (
 
 	AccessTokenPattern = regexp.MustCompile(`^sbp_[a-f0-9]{40}$`)
 	ProjectRefPattern  = regexp.MustCompile(`^[a-z]{20}$`)
+	PostgresUrlPattern = regexp.MustCompile(`^postgres(?:ql)?://postgres:(.+)@(.+?)(:\d+)?/postgres$`)
+	MigrateFilePattern = regexp.MustCompile(`([0-9]+)_.*\.sql`)
 )
 
 func GetCurrentTimestamp() string {
@@ -166,7 +169,7 @@ func MkdirIfNotExist(path string) error {
 }
 
 func MkdirIfNotExistFS(fsys afero.Fs, path string) error {
-	if err := fsys.Mkdir(path, 0755); err != nil && !errors.Is(err, os.ErrExist) {
+	if err := fsys.MkdirAll(path, 0755); err != nil && !errors.Is(err, os.ErrExist) {
 		return err
 	}
 


Test patch:
diff --git a/internal/utils/config_test.go b/internal/utils/config_test.go
index 77227b45..cffc80bf 100644
--- a/internal/utils/config_test.go
+++ b/internal/utils/config_test.go
@@ -11,7 +11,7 @@ func TestConfigParsing(t *testing.T) {
 	t.Run("classic config file", func(t *testing.T) {
 		fsys := afero.NewMemMapFs()
 		assert.NoError(t, WriteConfig(fsys, false))
-		assert.NoError(t, loadConfig(fsys))
+		assert.NoError(t, LoadConfigFS(fsys))
 	})
 
 	t.Run("config file with environment variables", func(t *testing.T) {
@@ -20,7 +20,7 @@ func TestConfigParsing(t *testing.T) {
 
 		t.Setenv("AZURE_CLIENT_ID", "hello")
 		t.Setenv("AZURE_SECRET", "this is cool")
-		assert.NoError(t, loadConfig(fsys))
+		assert.NoError(t, LoadConfigFS(fsys))
 		assert.NoError(t, InterpolateEnvInConfig())
 
 		assert.Equal(t, "hello", Config.Auth.External["azure"].ClientId)
@@ -30,7 +30,7 @@ func TestConfigParsing(t *testing.T) {
 	t.Run("config file with environment variables fails when unset", func(t *testing.T) {
 		fsys := afero.NewMemMapFs()
 		assert.NoError(t, WriteConfig(fsys, true))
-		assert.NoError(t, loadConfig(fsys))
+		assert.NoError(t, LoadConfigFS(fsys))
 		assert.Error(t, InterpolateEnvInConfig())
 	})
 }