Repository: supabase/cli
Pull request #1100 (2023-05-15 13:04:17)

fix: account for github head ref env var
## What kind of change does this PR introduce?

follow up #1081 

## What is the new behavior?

- GHA only checks out the head ref of branch by default. To get the branch name, we need to allow env var override.
- connecting realtime to preview branch errors due to missing `_realtime` schema. Let the container initialise itself.

## Additional context

Add any other context or screenshots.



Patch:
diff --git a/cmd/functions.go b/cmd/functions.go
index 0de3a2ab..4e6fb3ba 100644
--- a/cmd/functions.go
+++ b/cmd/functions.go
@@ -57,7 +57,7 @@ var (
 			if !cmd.Flags().Changed("no-verify-jwt") {
 				noVerifyJWT = nil
 			}
-			return deploy.Run(cmd.Context(), args[0], flags.ProjectRef, noVerifyJWT, false, importMapPath, afero.NewOsFs())
+			return deploy.Run(cmd.Context(), args[0], flags.ProjectRef, noVerifyJWT, useLegacyBundle, importMapPath, afero.NewOsFs())
 		},
 	}
 
@@ -106,7 +106,6 @@ func init() {
 	functionsDeployCmd.Flags().StringVar(&flags.ProjectRef, "project-ref", "", "Project ref of the Supabase project.")
 	functionsDeployCmd.Flags().BoolVar(&useLegacyBundle, "legacy-bundle", false, "Use legacy bundling mechanism.")
 	functionsDeployCmd.Flags().StringVar(&importMapPath, "import-map", "", "Path to import map file.")
-	cobra.CheckErr(functionsDeployCmd.Flags().MarkHidden("legacy-bundle"))
 	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.")
diff --git a/internal/functions/deploy/deploy.go b/internal/functions/deploy/deploy.go
index 0e890b34..c6b192a4 100644
--- a/internal/functions/deploy/deploy.go
+++ b/internal/functions/deploy/deploy.go
@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"io"
 	"net/http"
+	"os"
 	"os/exec"
 	"path/filepath"
 	"strings"
@@ -21,6 +22,7 @@ const eszipContentType = "application/vnd.denoland.eszip"
 
 func Run(ctx context.Context, slug string, projectRef string, noVerifyJWT *bool, useLegacyBundle bool, importMapPath string, fsys afero.Fs) error {
 	// 1. Sanity checks.
+	var scriptDir *utils.DenoScriptDir
 	{
 		// Load function config if any for fallbacks for some flags, but continue on error.
 		_ = utils.LoadConfigFS(fsys)
@@ -32,57 +34,78 @@ func Run(ctx context.Context, slug string, projectRef string, noVerifyJWT *bool,
 			}
 			noVerifyJWT = &x
 		}
-		resolved, err := utils.AbsImportMapPath(importMapPath, slug, fsys)
-		if err != nil {
-			return err
+		if importMapPath != "" {
+			// skip
+		} else if functionConfig, ok := utils.Config.Functions[slug]; ok && functionConfig.ImportMap != "" {
+			if filepath.IsAbs(functionConfig.ImportMap) {
+				importMapPath = functionConfig.ImportMap
+			} else {
+				importMapPath = filepath.Join(utils.SupabaseDirPath, functionConfig.ImportMap)
+			}
+		} 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)
+			}
 		}
-		importMapPath = resolved
 		if err := utils.ValidateFunctionSlug(slug); err != nil {
 			return err
 		}
 		if err := utils.InstallOrUpgradeDeno(ctx, fsys); err != nil {
 			return err
 		}
+
+		var err error
+		scriptDir, err = utils.CopyDenoScripts(ctx, fsys)
+		if err != nil {
+			return err
+		}
 	}
 
 	// 2. Bundle Function.
-	scriptDir, err := utils.CopyDenoScripts(ctx, fsys)
-	if err != nil {
-		return err
-	}
-	functionBody, err := bundleFunction(ctx, slug, importMapPath, scriptDir.BuildPath, fsys)
-	if err != nil {
-		return err
+	var functionBody io.Reader
+	var functionSize int
+	{
+		fmt.Println("Bundling " + utils.Bold(slug))
+		denoPath, err := utils.GetDenoPath()
+		if err != nil {
+			return err
+		}
+
+		functionPath := filepath.Join(utils.FunctionsDir, slug)
+		if _, err := fsys.Stat(functionPath); errors.Is(err, os.ErrNotExist) {
+			// allow deploy from within supabase/
+			functionPath = filepath.Join("functions", slug)
+			if _, err := fsys.Stat(functionPath); errors.Is(err, os.ErrNotExist) {
+				// allow deploy from current directory
+				functionPath = slug
+			}
+		}
+
+		buildScriptPath := scriptDir.BuildPath
+		args := []string{"run", "-A", buildScriptPath, filepath.Join(functionPath, "index.ts"), importMapPath}
+		if useLegacyBundle {
+			args = []string{"bundle", "--no-check=remote", "--quiet", filepath.Join(functionPath, "index.ts")}
+		}
+		cmd := exec.CommandContext(ctx, denoPath, args...)
+		var outBuf, errBuf bytes.Buffer
+		cmd.Stdout = &outBuf
+		cmd.Stderr = &errBuf
+		if err := cmd.Run(); err != nil {
+			return fmt.Errorf("Error bundling function: %w\n%v", err, errBuf.String())
+		}
+
+		functionBody = &outBuf
+		functionSize = outBuf.Len()
 	}
-	functionSize := functionBody.Len()
 
 	// 3. Deploy new Function.
 	fmt.Println("Deploying " + utils.Bold(slug) + " (script size: " + utils.Bold(units.HumanSize(float64(functionSize))) + ")")
 	return deployFunction(ctx, projectRef, slug, functionBody, !*noVerifyJWT, useLegacyBundle)
 }
 
-func bundleFunction(ctx context.Context, slug, importMapPath, buildScriptPath string, fsys afero.Fs) (*bytes.Buffer, error) {
-	fmt.Println("Bundling " + utils.Bold(slug))
-	denoPath, err := utils.GetDenoPath()
-	if err != nil {
-		return nil, err
-	}
-	entrypointPath, err := filepath.Abs(filepath.Join(utils.FunctionsDir, slug, "index.ts"))
-	if err != nil {
-		return nil, err
-	}
-	// Bundle function and import_map with deno
-	args := []string{"run", "-A", buildScriptPath, entrypointPath, importMapPath}
-	cmd := exec.CommandContext(ctx, denoPath, args...)
-	var outBuf, errBuf bytes.Buffer
-	cmd.Stdout = &outBuf
-	cmd.Stderr = &errBuf
-	if err := cmd.Run(); err != nil {
-		return nil, fmt.Errorf("Error bundling function: %w\n%v", err, errBuf.String())
-	}
-	return &outBuf, nil
-}
-
 func makeLegacyFunctionBody(functionBody io.Reader) (string, error) {
 	buf := new(strings.Builder)
 	_, err := io.Copy(buf, functionBody)
@@ -93,7 +116,6 @@ func makeLegacyFunctionBody(functionBody io.Reader) (string, error) {
 	return buf.String(), nil
 }
 
-// TODO: Remove `useLegacyBundle` after 2023-06-01
 func deployFunction(ctx context.Context, projectRef, slug string, functionBody io.Reader, verifyJWT, useLegacyBundle bool) error {
 	{
 		resp, err := utils.GetSupabase().GetFunctionWithResponse(ctx, projectRef, slug)
diff --git a/internal/functions/serve/serve.go b/internal/functions/serve/serve.go
index 886ebec0..6274d189 100644
--- a/internal/functions/serve/serve.go
+++ b/internal/functions/serve/serve.go
@@ -55,11 +55,32 @@ func Run(ctx context.Context, slug string, envFilePath string, noVerifyJWT *bool
 				return fmt.Errorf("Failed to read env file: %w", err)
 			}
 		}
-		resolved, err := utils.AbsImportMapPath(importMapPath, slug, fsys)
+		cwd, err := os.Getwd()
 		if err != nil {
 			return err
 		}
-		importMapPath = resolved
+		if importMapPath != "" {
+			if !filepath.IsAbs(importMapPath) {
+				importMapPath = filepath.Join(cwd, importMapPath)
+			}
+		} else if functionConfig, ok := utils.Config.Functions[slug]; ok && functionConfig.ImportMap != "" {
+			if filepath.IsAbs(functionConfig.ImportMap) {
+				importMapPath = functionConfig.ImportMap
+			} else {
+				importMapPath = filepath.Join(cwd, utils.SupabaseDirPath, functionConfig.ImportMap)
+			}
+		} else if f, err := fsys.Stat(utils.FallbackImportMapPath); err == nil && !f.IsDir() {
+			if filepath.IsAbs(utils.FallbackImportMapPath) {
+				importMapPath = utils.FallbackImportMapPath
+			} else {
+				importMapPath = filepath.Join(cwd, utils.FallbackImportMapPath)
+			}
+		}
+		if importMapPath != "" {
+			if _, err := fsys.Stat(importMapPath); err != nil {
+				return fmt.Errorf("Failed to read import map: %w", err)
+			}
+		}
 	}
 
 	// 2. Parse user defined env
diff --git a/internal/gen/keys/keys.go b/internal/gen/keys/keys.go
index 31810f1c..d47ebfac 100644
--- a/internal/gen/keys/keys.go
+++ b/internal/gen/keys/keys.go
@@ -87,6 +87,10 @@ func GenerateSecrets(ctx context.Context, projectRef, branch string, fsys afero.
 }
 
 func GetGitBranch(fsys afero.Fs) string {
+	head := os.Getenv("GITHUB_HEAD_REF")
+	if len(head) > 0 {
+		return head
+	}
 	branch := "main"
 	if gitRoot, err := utils.GetGitRoot(fsys); err == nil {
 		if repo, err := git.PlainOpen(*gitRoot); err == nil {
diff --git a/internal/start/start.go b/internal/start/start.go
index bd0f1609..b6d9dbd3 100644
--- a/internal/start/start.go
+++ b/internal/start/start.go
@@ -438,7 +438,7 @@ EOF
 					"DB_USER=supabase_admin",
 					"DB_PASSWORD=" + dbConfig.Password,
 					"DB_NAME=" + dbConfig.Database,
-					"DB_AFTER_CONNECT_QUERY=SET search_path TO _realtime",
+					"DB_AFTER_CONNECT_QUERY=CREATE SCHEMA IF NOT EXISTS _realtime; SET search_path TO _realtime",
 					"DB_ENC_KEY=supabaserealtime",
 					"API_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
 					"FLY_ALLOC_ID=abc123",
diff --git a/internal/utils/deno.go b/internal/utils/deno.go
index 1bd3582f..105748fe 100644
--- a/internal/utils/deno.go
+++ b/internal/utils/deno.go
@@ -320,28 +320,3 @@ func GetPathHash(path string) string {
 	digest := sha256.Sum256([]byte(path))
 	return hex.EncodeToString(digest[:])
 }
-
-func AbsImportMapPath(importMapPath, slug string, fsys afero.Fs) (string, error) {
-	if importMapPath == "" {
-		if functionConfig, ok := Config.Functions[slug]; ok && functionConfig.ImportMap != "" {
-			importMapPath = functionConfig.ImportMap
-			if !filepath.IsAbs(importMapPath) {
-				importMapPath = filepath.Join(SupabaseDirPath, importMapPath)
-			}
-		} else if exists, _ := afero.Exists(fsys, FallbackImportMapPath); exists {
-			importMapPath = FallbackImportMapPath
-		} else {
-			return importMapPath, nil
-		}
-	}
-	resolved, err := filepath.Abs(importMapPath)
-	if err != nil {
-		return "", err
-	}
-	if f, err := fsys.Stat(resolved); err != nil {
-		return "", fmt.Errorf("Failed to read import map: %w", err)
-	} else if f.IsDir() {
-		return "", errors.New("Importing directory is unsupported: " + resolved)
-	}
-	return resolved, nil
-}
diff --git a/internal/utils/denos/build.ts b/internal/utils/denos/build.ts
index dc241172..322e7508 100644
--- a/internal/utils/denos/build.ts
+++ b/internal/utils/denos/build.ts
@@ -1,19 +1,40 @@
 import { encode } from "https://deno.land/std@0.127.0/encoding/base64.ts";
+import * as path from "https://deno.land/std@0.127.0/path/mod.ts";
 import { writeAll } from "https://deno.land/std@0.162.0/streams/conversion.ts";
 import { compress } from "https://deno.land/x/brotli@0.1.7/mod.ts";
 import { build } from "https://deno.land/x/eszip@v0.35.0/mod.ts";
 
+const virtualBasePath = "file:///src/";
+
 async function buildAndWrite(p: string, importMapPath: string) {
-  const cwd = `file://${Deno.cwd()}/`
-  const entrypoint = new URL(p, cwd).href;
-  const importMap = new URL(importMapPath || "supabase/functions/import_map.json", cwd).href
+  const funcDirPath = path.dirname(p);
+  try {
+    await Deno.lstat(funcDirPath);
+  } catch (e) {
+    console.error(
+      `Error: Cannot access "${funcDirPath}". Check if directory exists and has read permissions.`,
+    );
+    Deno.exit(1);
+  }
+
+  const entrypoint = new URL("index.ts", virtualBasePath).href;
 
   const eszip = await build([entrypoint], async (specifier: string) => {
     const url = new URL(specifier);
     if (url.protocol === "file:") {
       console.error(specifier);
-      const actualPath = url.pathname;
+      // if the path is `file:///*`, treat it as a path from parent directory
+      let actualPath = specifier.replace("file:///", `./${funcDirPath}/../`);
+      // if the path is `file:///src/*`, treat it as a relative path from current dir
+      if (specifier.startsWith(virtualBasePath)) {
+        actualPath = specifier.replace(virtualBasePath, `./${funcDirPath}/`);
+      }
 
+      // If an import map path is set read file from the given path.
+      // Otherwise default to `import_map.json` in functions directory.
+      if (specifier.endsWith("import_map.json") && importMapPath) {
+        actualPath = importMapPath;
+      }
       try {
         const content = await Deno.readTextFile(actualPath);
         return {
@@ -39,7 +60,7 @@ async function buildAndWrite(p: string, importMapPath: string) {
     }
 
     return load(specifier);
-  }, importMap);
+  }, "file:///src/import_map.json");
   // compress ESZIP payload using Brotli
   const compressed = compress(eszip);
 


Test patch:
diff --git a/internal/functions/deploy/deploy_test.go b/internal/functions/deploy/deploy_test.go
index 739e470b..88fefb42 100644
--- a/internal/functions/deploy/deploy_test.go
+++ b/internal/functions/deploy/deploy_test.go
@@ -11,7 +11,6 @@ import (
 	"log"
 	"net/http"
 	"os"
-	"path/filepath"
 	"strings"
 	"testing"
 
@@ -341,17 +340,14 @@ verify_jwt = false
 		// Setup in-memory fs
 		fsys := afero.NewMemMapFs()
 		require.NoError(t, utils.WriteConfig(fsys, false))
-		require.NoError(t, afero.WriteFile(fsys, utils.FallbackImportMapPath, []byte(""), 0644))
-		absPath, err := filepath.Abs(utils.FallbackImportMapPath)
-		require.NoError(t, err)
-		require.NoError(t, afero.WriteFile(fsys, absPath, []byte("{}"), 0644))
+		require.NoError(t, afero.WriteFile(fsys, "supabase/functions/import_map.json", []byte(""), 0644))
 		// Setup valid project ref
 		project := apitest.RandomProjectRef()
 		// Setup valid access token
 		token := apitest.RandomAccessToken(t)
 		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
 		// Setup valid deno path
-		_, err = fsys.Create(utils.DenoPathOverride)
+		_, err := fsys.Create(utils.DenoPathOverride)
 		require.NoError(t, err)
 		// Setup mock api
 		defer gock.OffAll()
diff --git a/internal/utils/deno_test.go b/internal/utils/deno_test.go
index 9a5840bf..c6332be4 100644
--- a/internal/utils/deno_test.go
+++ b/internal/utils/deno_test.go
@@ -54,70 +54,3 @@ func TestResolveImports(t *testing.T) {
 		assert.Equal(t, "https://deno.land", resolved.Scopes["my-scope"]["my-mod"])
 	})
 }
-
-func TestImportMapPath(t *testing.T) {
-	t.Run("loads import map from default location", func(t *testing.T) {
-		// Setup in-memory fs
-		fsys := afero.NewMemMapFs()
-		require.NoError(t, afero.WriteFile(fsys, FallbackImportMapPath, []byte("{}"), 0644))
-		absPath, err := filepath.Abs(FallbackImportMapPath)
-		require.NoError(t, err)
-		require.NoError(t, afero.WriteFile(fsys, absPath, []byte("{}"), 0644))
-		// Run test
-		resolved, err := AbsImportMapPath("", "", fsys)
-		// Check error
-		assert.NoError(t, err)
-		assert.Equal(t, absPath, resolved)
-	})
-
-	t.Run("per function config takes precedence", func(t *testing.T) {
-		slug := "hello"
-		Config.Functions = map[string]function{
-			slug: {ImportMap: "import_map.json"},
-		}
-		absPath, err := filepath.Abs("supabase/import_map.json")
-		require.NoError(t, err)
-		// Setup in-memory fs
-		fsys := afero.NewMemMapFs()
-		require.NoError(t, afero.WriteFile(fsys, FallbackImportMapPath, []byte("{}"), 0644))
-		require.NoError(t, afero.WriteFile(fsys, absPath, []byte("{}"), 0644))
-		// Run test
-		resolved, err := AbsImportMapPath("", slug, fsys)
-		// Check error
-		assert.NoError(t, err)
-		assert.Equal(t, absPath, resolved)
-	})
-
-	t.Run("returns empty string if no fallback", func(t *testing.T) {
-		// Setup in-memory fs
-		fsys := afero.NewMemMapFs()
-		// Run test
-		resolved, err := AbsImportMapPath("", "", fsys)
-		// Check error
-		assert.NoError(t, err)
-		assert.Empty(t, resolved)
-	})
-
-	t.Run("throws error on missing file", func(t *testing.T) {
-		path := "/tmp/import_map"
-		// Setup in-memory fs
-		fsys := afero.NewMemMapFs()
-		// Run test
-		resolved, err := AbsImportMapPath(path, "", fsys)
-		// Check error
-		assert.ErrorIs(t, err, os.ErrNotExist)
-		assert.Empty(t, resolved)
-	})
-
-	t.Run("throws error on importing directory", func(t *testing.T) {
-		path := "/tmp/import_map"
-		// Setup in-memory fs
-		fsys := afero.NewMemMapFs()
-		require.NoError(t, fsys.MkdirAll(path, 0755))
-		// Run test
-		resolved, err := AbsImportMapPath(path, "", fsys)
-		// Check error
-		assert.ErrorContains(t, err, "Importing directory is unsupported: "+path)
-		assert.Empty(t, resolved)
-	})
-}