Repository: supabase/cli
Pull request #864 (2023-02-14 07:01:24)

fix: defaults to serve all functions
## What kind of change does this PR introduce?

chore

## What is the current behavior?

`functions serve` errors without `--all` arg

## What is the new behavior?

`supabase functions serve` defaults to serving all functions

## Additional context

Add any other context or screenshots.



Patch:
diff --git a/cmd/functions.go b/cmd/functions.go
index 1c307b4e..932f0a81 100644
--- a/cmd/functions.go
+++ b/cmd/functions.go
@@ -95,7 +95,6 @@ var (
 	}
 
 	envFilePath string
-	serveAll    bool
 
 	functionsServeCmd = &cobra.Command{
 		Use:   "serve <Function name>",
@@ -111,7 +110,7 @@ var (
 			if len(args) > 0 {
 				slug = args[0]
 			}
-			return serve.Run(ctx, slug, envFilePath, noVerifyJWT, importMapPath, serveAll, afero.NewOsFs())
+			return serve.Run(ctx, slug, envFilePath, noVerifyJWT, importMapPath, afero.NewOsFs())
 		},
 	}
 )
@@ -125,7 +124,8 @@ func init() {
 	functionsServeCmd.Flags().BoolVar(noVerifyJWT, "no-verify-jwt", false, "Disable JWT verification for the Function.")
 	functionsServeCmd.Flags().StringVar(&envFilePath, "env-file", "", "Path to an env file to be populated to the Function environment.")
 	functionsServeCmd.Flags().StringVar(&importMapPath, "import-map", "", "Path to import map file.")
-	functionsServeCmd.Flags().BoolVar(&serveAll, "all", false, "Serve all functions (caution: Experimental feature)")
+	functionsServeCmd.Flags().Bool("all", true, "Serve all functions (caution: Experimental feature)")
+	cobra.CheckErr(functionsServeCmd.Flags().MarkHidden("all"))
 	functionsDownloadCmd.Flags().StringVar(&projectRef, "project-ref", "", "Project ref of the Supabase project.")
 	functionsCmd.AddCommand(functionsDeleteCmd)
 	functionsCmd.AddCommand(functionsDeployCmd)
diff --git a/internal/functions/serve/serve.go b/internal/functions/serve/serve.go
index 90b43006..453b3514 100644
--- a/internal/functions/serve/serve.go
+++ b/internal/functions/serve/serve.go
@@ -23,12 +23,17 @@ const (
 	customDockerImportMapPath = "/home/deno/import_map.json"
 )
 
-func ParseEnvFile(envFilePath string) ([]string, error) {
+func ParseEnvFile(envFilePath string, fsys afero.Fs) ([]string, error) {
 	env := []string{}
 	if len(envFilePath) == 0 {
 		return env, nil
 	}
-	envMap, err := godotenv.Read(envFilePath)
+	f, err := fsys.Open(envFilePath)
+	if err != nil {
+		return env, err
+	}
+	defer f.Close()
+	envMap, err := godotenv.Parse(f)
 	if err != nil {
 		return env, err
 	}
@@ -41,8 +46,8 @@ func ParseEnvFile(envFilePath string) ([]string, error) {
 	return env, nil
 }
 
-func Run(ctx context.Context, slug string, envFilePath string, noVerifyJWT *bool, importMapPath string, serveAll bool, fsys afero.Fs) error {
-	if serveAll {
+func Run(ctx context.Context, slug string, envFilePath string, noVerifyJWT *bool, importMapPath string, fsys afero.Fs) error {
+	if len(slug) == 0 {
 		return runServeAll(ctx, envFilePath, noVerifyJWT, importMapPath, fsys)
 	}
 
@@ -81,7 +86,7 @@ func Run(ctx context.Context, slug string, envFilePath string, noVerifyJWT *bool
 	}
 
 	// 2. Parse user defined env
-	userEnv, err := ParseEnvFile(envFilePath)
+	userEnv, err := ParseEnvFile(envFilePath, fsys)
 	if err != nil {
 		return err
 	}
@@ -236,20 +241,17 @@ func runServeAll(ctx context.Context, envFilePath string, noVerifyJWT *bool, imp
 				return fmt.Errorf("Failed to read env file: %w", err)
 			}
 		}
-		if importMapPath != "" {
-			// skip
-		} else if f, err := fsys.Stat(utils.FallbackImportMapPath); err == nil && !f.IsDir() {
-			importMapPath = utils.FallbackImportMapPath
-		}
-		if importMapPath != "" {
-			if _, err := fsys.Stat(importMapPath); err != nil {
-				return fmt.Errorf("Failed to read import map: %w", err)
+		if importMapPath == "" {
+			if f, err := fsys.Stat(utils.FallbackImportMapPath); err == nil && !f.IsDir() {
+				importMapPath = utils.FallbackImportMapPath
 			}
+		} else if _, err := fsys.Stat(importMapPath); err != nil {
+			return fmt.Errorf("Failed to read import map: %w", err)
 		}
 	}
 
 	// 2. Parse user defined env
-	userEnv, err := ParseEnvFile(envFilePath)
+	userEnv, err := ParseEnvFile(envFilePath, fsys)
 	if err != nil {
 		return err
 	}
diff --git a/internal/utils/config.go b/internal/utils/config.go
index fd55b647..ba3a04e1 100644
--- a/internal/utils/config.go
+++ b/internal/utils/config.go
@@ -1,7 +1,6 @@
 package utils
 
 import (
-	"bytes"
 	_ "embed"
 	"errors"
 	"fmt"
@@ -60,12 +59,8 @@ var (
 	}
 
 	//go:embed templates/init_config.toml
-	initConfigEmbed       string
-	initConfigTemplate, _ = template.New("initConfig").Parse(initConfigEmbed)
-
-	//go:embed templates/init_config.test.toml
-	testInitConfigEmbed       string
-	testInitConfigTemplate, _ = template.New("initConfig.test").Parse(testInitConfigEmbed)
+	initConfigEmbed    string
+	initConfigTemplate = template.Must(template.New("initConfig").Parse(initConfigEmbed))
 )
 
 // Type for turning human-friendly bytes string ("5MB", "32kB") into an int64 during toml decoding.
@@ -352,38 +347,32 @@ func LoadConfigFS(fsys afero.Fs) error {
 	return nil
 }
 
-func WriteConfig(fsys afero.Fs, test bool) error {
-	// Using current directory name as project id
-	cwd, err := os.Getwd()
-	if err != nil {
-		return err
-	}
-	dir := filepath.Base(cwd)
-
-	var initConfigBuf bytes.Buffer
-	var tmpl *template.Template
-	if test {
-		tmpl = testInitConfigTemplate
-	} else {
-		tmpl = initConfigTemplate
-	}
-
-	if err := tmpl.Execute(
-		&initConfigBuf,
-		struct{ ProjectId string }{ProjectId: dir},
-	); err != nil {
-		return err
+func InitConfig(projectId string, fsys afero.Fs) error {
+	// Defaults to current directory name as project id
+	if len(projectId) == 0 {
+		cwd, err := os.Getwd()
+		if err != nil {
+			return err
+		}
+		projectId = filepath.Base(cwd)
 	}
-
+	// Create config file
 	if err := MkdirIfNotExistFS(fsys, filepath.Dir(ConfigPath)); err != nil {
 		return err
 	}
-
-	if err := afero.WriteFile(fsys, ConfigPath, initConfigBuf.Bytes(), 0644); err != nil {
+	f, err := fsys.OpenFile(ConfigPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+	if err != nil {
 		return err
 	}
+	defer f.Close()
+	// Update from template
+	return initConfigTemplate.Execute(f, struct{ ProjectId string }{
+		ProjectId: projectId,
+	})
+}
 
-	return nil
+func WriteConfig(fsys afero.Fs, _test bool) error {
+	return InitConfig("", fsys)
 }
 
 func removeDuplicates(slice []string) (result []string) {


Test patch:
diff --git a/internal/functions/serve/serve_test.go b/internal/functions/serve/serve_test.go
index 845bbea0..ada01856 100644
--- a/internal/functions/serve/serve_test.go
+++ b/internal/functions/serve/serve_test.go
@@ -2,6 +2,7 @@ package serve
 
 import (
 	"context"
+	"errors"
 	"net/http"
 	"testing"
 
@@ -35,9 +36,96 @@ func TestServeCommand(t *testing.T) {
 			Post("/v" + utils.Docker.ClientVersion() + "/containers").
 			Reply(http.StatusServiceUnavailable)
 		// Run test
-		err := Run(context.Background(), "test-func", "", nil, "", false, fsys)
+		err := Run(context.Background(), "test-func", "", nil, "", fsys)
 		// Check error
 		assert.ErrorContains(t, err, "request returned Service Unavailable for API route and version http://localhost/v1.41/containers/supabase_deno_relay_serve/exec")
 		assert.Empty(t, apitest.ListUnmatchedRequests())
 	})
+
+	t.Run("serves all functions", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		require.NoError(t, utils.InitConfig("test", fsys))
+		require.NoError(t, afero.WriteFile(fsys, ".env", []byte{}, 0644))
+		require.NoError(t, afero.WriteFile(fsys, utils.FallbackImportMapPath, []byte{}, 0644))
+		// Setup mock docker
+		require.NoError(t, apitest.MockDocker(utils.Docker))
+		defer gock.OffAll()
+		gock.New(utils.Docker.DaemonHost()).
+			Get("/v" + utils.Docker.ClientVersion() + "/containers/supabase_db_test/json").
+			Reply(http.StatusOK).
+			JSON(types.ContainerJSON{})
+		containerId := "supabase_deno_relay_test"
+		gock.New(utils.Docker.DaemonHost()).
+			Delete("/v" + utils.Docker.ClientVersion() + "/containers/" + containerId).
+			Reply(http.StatusOK)
+		apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.EdgeRuntimeImage), containerId)
+		require.NoError(t, apitest.MockDockerLogs(utils.Docker, containerId, "success"))
+		// Run test
+		noVerifyJWT := true
+		err := Run(context.Background(), "", ".env", &noVerifyJWT, "", fsys)
+		// Check error
+		assert.NoError(t, err)
+		assert.Empty(t, apitest.ListUnmatchedRequests())
+	})
+
+	t.Run("throws error on missing config", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		// Run test
+		err := Run(context.Background(), "", "", nil, "", fsys)
+		// Check error
+		assert.ErrorContains(t, err, "open supabase/config.toml: file does not exist")
+	})
+
+	t.Run("throws error on missing db", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		require.NoError(t, utils.InitConfig("test", fsys))
+		// Setup mock docker
+		require.NoError(t, apitest.MockDocker(utils.Docker))
+		defer gock.OffAll()
+		gock.New(utils.Docker.DaemonHost()).
+			Get("/v" + utils.Docker.ClientVersion() + "/containers/supabase_db_test/json").
+			ReplyError(errors.New("network error"))
+		// Run test
+		err := Run(context.Background(), "", "", nil, "", fsys)
+		// Check error
+		assert.ErrorContains(t, err, "supabase start is not running.")
+	})
+
+	t.Run("throws error on missing env file", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		require.NoError(t, utils.InitConfig("test", fsys))
+		// Setup mock docker
+		require.NoError(t, apitest.MockDocker(utils.Docker))
+		defer gock.OffAll()
+		gock.New(utils.Docker.DaemonHost()).
+			Get("/v" + utils.Docker.ClientVersion() + "/containers/supabase_db_test/json").
+			Reply(http.StatusOK).
+			JSON(types.ContainerJSON{})
+		// Run test
+		err := Run(context.Background(), "", ".env", nil, "", fsys)
+		// Check error
+		assert.ErrorContains(t, err, "open .env: file does not exist")
+	})
+
+	t.Run("throws error on missing import map", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		require.NoError(t, utils.InitConfig("test", fsys))
+		require.NoError(t, afero.WriteFile(fsys, ".env", []byte{}, 0644))
+		// Setup mock docker
+		require.NoError(t, apitest.MockDocker(utils.Docker))
+		defer gock.OffAll()
+		gock.New(utils.Docker.DaemonHost()).
+			Get("/v" + utils.Docker.ClientVersion() + "/containers/supabase_db_test/json").
+			Reply(http.StatusOK).
+			JSON(types.ContainerJSON{})
+		// Run test
+		err := Run(context.Background(), "", ".env", nil, "import_map.json", fsys)
+		// Check error
+		assert.ErrorContains(t, err, "open import_map.json: file does not exist")
+	})
 }
diff --git a/internal/utils/config_test.go b/internal/utils/config_test.go
index 2c3079f2..0df7a038 100644
--- a/internal/utils/config_test.go
+++ b/internal/utils/config_test.go
@@ -1,14 +1,28 @@
 package utils
 
 import (
+	_ "embed"
 	"testing"
+	"text/template"
 
 	"github.com/BurntSushi/toml"
 	"github.com/spf13/afero"
 	"github.com/stretchr/testify/assert"
 )
 
+var (
+	//go:embed templates/init_config.test.toml
+	testInitConfigEmbed    string
+	testInitConfigTemplate = template.Must(template.New("initConfig.test").Parse(testInitConfigEmbed))
+)
+
 func TestConfigParsing(t *testing.T) {
+	// Reset global variable
+	copy := initConfigTemplate
+	t.Cleanup(func() {
+		initConfigTemplate = copy
+	})
+
 	t.Run("classic config file", func(t *testing.T) {
 		fsys := afero.NewMemMapFs()
 		assert.NoError(t, WriteConfig(fsys, false))
@@ -16,20 +30,25 @@ func TestConfigParsing(t *testing.T) {
 	})
 
 	t.Run("config file with environment variables", func(t *testing.T) {
+		initConfigTemplate = testInitConfigTemplate
+		// Setup in-memory fs
 		fsys := afero.NewMemMapFs()
 		assert.NoError(t, WriteConfig(fsys, true))
-
+		// Run test
 		t.Setenv("AZURE_CLIENT_ID", "hello")
 		t.Setenv("AZURE_SECRET", "this is cool")
 		assert.NoError(t, LoadConfigFS(fsys))
-
+		// Check error
 		assert.Equal(t, "hello", Config.Auth.External["azure"].ClientId)
 		assert.Equal(t, "this is cool", Config.Auth.External["azure"].Secret)
 	})
 
 	t.Run("config file with environment variables fails when unset", func(t *testing.T) {
+		initConfigTemplate = testInitConfigTemplate
+		// Setup in-memory fs
 		fsys := afero.NewMemMapFs()
 		assert.NoError(t, WriteConfig(fsys, true))
+		// Run test
 		assert.Error(t, LoadConfigFS(fsys))
 	})
 }