diff --git a/internal/functions/deploy/bundle.go b/internal/functions/deploy/bundle.go index 436321493b..fa47b22fbf 100644 --- a/internal/functions/deploy/bundle.go +++ b/internal/functions/deploy/bundle.go @@ -127,13 +127,6 @@ func GetBindMounts(cwd, hostFuncDir, hostOutputDir, hostEntrypointPath, hostImpo if err != nil { return nil, err } - if len(hostImportMapPath) > 0 { - if !filepath.IsAbs(hostImportMapPath) { - hostImportMapPath = filepath.Join(cwd, hostImportMapPath) - } - dockerImportMapPath := utils.ToDockerPath(hostImportMapPath) - modules = append(modules, hostImportMapPath+":"+dockerImportMapPath+":ro") - } // Remove any duplicate mount points for _, mod := range modules { hostPath := strings.Split(mod, ":")[0] diff --git a/internal/utils/deno.go b/internal/utils/deno.go index ee6ac5de40..5bece3a5af 100644 --- a/internal/utils/deno.go +++ b/internal/utils/deno.go @@ -209,31 +209,20 @@ func CopyDenoScripts(ctx context.Context, fsys afero.Fs) (*DenoScriptDir, error) return &sd, nil } -func newImportMap(relJsonPath string, fsys afero.Fs) (function.ImportMap, error) { - var result function.ImportMap - if len(relJsonPath) == 0 { - return result, nil - } - data, err := afero.ReadFile(fsys, relJsonPath) - if err != nil { - return result, errors.Errorf("failed to load import map: %w", err) - } - if err := result.Parse(data); err != nil { - return result, err - } - unixPath := filepath.ToSlash(relJsonPath) - if err := result.Resolve(unixPath, afero.NewIOFS(fsys)); err != nil { - return result, err - } - return result, nil -} - func BindHostModules(cwd, relEntrypointPath, relImportMapPath string, fsys afero.Fs) ([]string, error) { - importMap, err := newImportMap(relImportMapPath, fsys) - if err != nil { - return nil, err - } var modules []string + bindModule := func(srcPath string, r io.Reader) error { + hostPath := filepath.Join(cwd, filepath.FromSlash(srcPath)) + dockerPath := ToDockerPath(hostPath) + modules = append(modules, hostPath+":"+dockerPath+":ro") + return nil + } + importMap := function.ImportMap{} + if imPath := filepath.ToSlash(relImportMapPath); len(imPath) > 0 { + if err := importMap.LoadAsDeno(imPath, afero.NewIOFS(fsys), bindModule); err != nil { + return nil, err + } + } // Resolving all Import Graph addModule := func(unixPath string, w io.Writer) error { hostPath := filepath.FromSlash(unixPath) diff --git a/internal/utils/deno_test.go b/internal/utils/deno_test.go index 32f6b42c74..a20b3acacd 100644 --- a/internal/utils/deno_test.go +++ b/internal/utils/deno_test.go @@ -1,8 +1,6 @@ package utils import ( - "os" - "path/filepath" "testing" "github.com/spf13/afero" @@ -10,56 +8,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestResolveImports(t *testing.T) { - t.Run("resolves relative directory", func(t *testing.T) { - importMap := []byte(`{ - "imports": { - "abs/": "/tmp/", - "root": "../../common", - "parent": "../tests", - "child": "child/", - "missing": "../missing" - } -}`) - // Setup in-memory fs - fsys := afero.NewMemMapFs() - cwd, err := os.Getwd() - require.NoError(t, err) - jsonPath := filepath.Join(cwd, FallbackImportMapPath) - require.NoError(t, afero.WriteFile(fsys, jsonPath, importMap, 0644)) - require.NoError(t, fsys.Mkdir(filepath.Join(cwd, "common"), 0755)) - require.NoError(t, fsys.Mkdir(filepath.Join(cwd, DbTestsDir), 0755)) - require.NoError(t, fsys.Mkdir(filepath.Join(cwd, FunctionsDir, "child"), 0755)) - // Run test - resolved, err := newImportMap(jsonPath, fsys) - // Check error - assert.NoError(t, err) - assert.Equal(t, "/tmp/", resolved.Imports["abs/"]) - assert.Equal(t, cwd+"/common", resolved.Imports["root"]) - assert.Equal(t, cwd+"/supabase/tests", resolved.Imports["parent"]) - assert.Equal(t, cwd+"/supabase/functions/child/", resolved.Imports["child"]) - assert.Equal(t, "../missing", resolved.Imports["missing"]) - }) - - t.Run("resolves parent scopes", func(t *testing.T) { - importMap := []byte(`{ - "scopes": { - "my-scope": { - "my-mod": "https://deno.land" - } - } -}`) - // Setup in-memory fs - fsys := afero.NewMemMapFs() - require.NoError(t, afero.WriteFile(fsys, FallbackImportMapPath, importMap, 0644)) - // Run test - resolved, err := newImportMap(FallbackImportMapPath, fsys) - // Check error - assert.NoError(t, err) - assert.Equal(t, "https://deno.land", resolved.Scopes["my-scope"]["my-mod"]) - }) -} - func TestBindModules(t *testing.T) { t.Run("binds docker imports", func(t *testing.T) { fsys := afero.NewMemMapFs() diff --git a/pkg/function/deno.go b/pkg/function/deno.go new file mode 100644 index 0000000000..bcc77a3de3 --- /dev/null +++ b/pkg/function/deno.go @@ -0,0 +1,180 @@ +package function + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/fs" + "os" + "path" + "path/filepath" + "regexp" + "strings" + + "github.com/go-errors/errors" + "github.com/tidwall/jsonc" +) + +type ImportMap struct { + Imports map[string]string `json:"imports"` + Scopes map[string]map[string]string `json:"scopes"` + // Fallback reference for deno.json + ImportMap string `json:"importMap"` +} + +func (m *ImportMap) LoadAsDeno(imPath string, fsys fs.FS, opts ...func(string, io.Reader) error) error { + if err := m.Load(imPath, fsys, opts...); err != nil { + return err + } + if name := path.Base(imPath); isDeno(name) && m.IsReference() { + imPath = path.Join(path.Dir(imPath), m.ImportMap) + if err := m.Load(imPath, fsys, opts...); err != nil { + return err + } + } + return nil +} + +func isDeno(name string) bool { + return strings.EqualFold(name, "deno.json") || + strings.EqualFold(name, "deno.jsonc") +} + +func (m *ImportMap) IsReference() bool { + // Ref: https://github.com/denoland/deno/blob/main/cli/schemas/config-file.v1.json#L273 + return len(m.Imports) == 0 && len(m.Scopes) == 0 && len(m.ImportMap) > 0 +} + +func (m *ImportMap) Load(imPath string, fsys fs.FS, opts ...func(string, io.Reader) error) error { + data, err := fs.ReadFile(fsys, filepath.FromSlash(imPath)) + if err != nil { + return errors.Errorf("failed to load import map: %w", err) + } + if err := m.Parse(data); err != nil { + return err + } + if err := m.Resolve(imPath, fsys); err != nil { + return err + } + for _, apply := range opts { + if err := apply(imPath, bytes.NewReader(data)); err != nil { + return err + } + } + return nil +} + +func (m *ImportMap) Parse(data []byte) error { + data = jsonc.ToJSONInPlace(data) + decoder := json.NewDecoder(bytes.NewReader(data)) + if err := decoder.Decode(&m); err != nil { + return errors.Errorf("failed to parse import map: %w", err) + } + return nil +} + +func (m *ImportMap) Resolve(imPath string, fsys fs.FS) error { + // Resolve all paths relative to current file + for k, v := range m.Imports { + m.Imports[k] = resolveHostPath(imPath, v, fsys) + } + for module, mapping := range m.Scopes { + for k, v := range mapping { + m.Scopes[module][k] = resolveHostPath(imPath, v, fsys) + } + } + return nil +} + +func resolveHostPath(jsonPath, hostPath string, fsys fs.FS) string { + // Leave absolute paths unchanged + if path.IsAbs(hostPath) { + return hostPath + } + resolved := path.Join(path.Dir(jsonPath), hostPath) + if _, err := fs.Stat(fsys, filepath.FromSlash(resolved)); err != nil { + // Leave URLs unchanged + return hostPath + } + // Directory imports need to be suffixed with / + // Ref: https://deno.com/manual@v1.33.0/basics/import_maps + if strings.HasSuffix(hostPath, "/") { + resolved += "/" + } + // Relative imports must be prefixed with ./ or ../ + if !path.IsAbs(resolved) { + resolved = "./" + resolved + } + return resolved +} + +// Ref: https://regex101.com/r/DfBdJA/1 +var importPathPattern = regexp.MustCompile(`(?i)(?:import|export)\s+(?:{[^{}]+}|.*?)\s*(?:from)?\s*['"](.*?)['"]|import\(\s*['"](.*?)['"]\)`) + +func (importMap *ImportMap) WalkImportPaths(srcPath string, readFile func(curr string, w io.Writer) error) error { + seen := map[string]struct{}{} + // DFS because it's more efficient to pop from end of array + q := make([]string, 1) + q[0] = srcPath + for len(q) > 0 { + curr := q[len(q)-1] + q = q[:len(q)-1] + // Assume no file is symlinked + if _, ok := seen[curr]; ok { + continue + } + seen[curr] = struct{}{} + // Read into memory for regex match later + var buf bytes.Buffer + if err := readFile(curr, &buf); errors.Is(err, os.ErrNotExist) { + fmt.Fprintln(os.Stderr, "WARN:", err) + continue + } else if err != nil { + return err + } + // Traverse all modules imported by the current source file + for _, matches := range importPathPattern.FindAllStringSubmatch(buf.String(), -1) { + if len(matches) < 3 { + continue + } + // Matches 'from' clause if present, else fallback to 'import' + mod := matches[1] + if len(mod) == 0 { + mod = matches[2] + } + mod = strings.TrimSpace(mod) + // Substitute kv from import map + substituted := false + for k, v := range importMap.Imports { + if strings.HasPrefix(mod, k) { + mod = v + mod[len(k):] + substituted = true + } + } + // Ignore URLs and directories, assuming no sloppy imports + // https://github.com/denoland/deno/issues/2506#issuecomment-2727635545 + if len(path.Ext(mod)) == 0 { + continue + } + // Deno import path must begin with one of these prefixes + if !isRelPath(mod) && !isAbsPath(mod) { + continue + } + if isRelPath(mod) && !substituted { + mod = path.Join(path.Dir(curr), mod) + } + // Cleans import path to help detect duplicates + q = append(q, path.Clean(mod)) + } + } + return nil +} + +func isRelPath(mod string) bool { + return strings.HasPrefix(mod, "./") || strings.HasPrefix(mod, "../") +} + +func isAbsPath(mod string) bool { + return strings.HasPrefix(mod, "/") +} diff --git a/pkg/function/deno_test.go b/pkg/function/deno_test.go new file mode 100644 index 0000000000..efa08c08d8 --- /dev/null +++ b/pkg/function/deno_test.go @@ -0,0 +1,201 @@ +package function + +import ( + "embed" + "io" + "os" + "testing" + fs "testing/fstest" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +//go:embed testdata +var testImports embed.FS + +type MockFS struct { + mock.Mock +} + +func (m *MockFS) ReadFile(srcPath string, w io.Writer) error { + _ = m.Called(srcPath) + data, err := testImports.ReadFile(srcPath) + if err != nil { + return err + } + if _, err := w.Write(data); err != nil { + return err + } + return nil +} + +func TestImportPaths(t *testing.T) { + t.Run("iterates all import paths", func(t *testing.T) { + // Setup in-memory fs + fsys := MockFS{} + fsys.On("ReadFile", "/modules/my-module.ts").Once() + fsys.On("ReadFile", "testdata/modules/imports.ts").Once() + fsys.On("ReadFile", "testdata/geometries/Geometries.js").Once() + // Run test + im := ImportMap{} + err := im.WalkImportPaths("testdata/modules/imports.ts", fsys.ReadFile) + // Check error + assert.NoError(t, err) + fsys.AssertExpectations(t) + }) + + t.Run("iterates with import map", func(t *testing.T) { + // Setup in-memory fs + fsys := MockFS{} + fsys.On("ReadFile", "/modules/my-module.ts").Once() + fsys.On("ReadFile", "testdata/modules/imports.ts").Once() + fsys.On("ReadFile", "testdata/geometries/Geometries.js").Once() + fsys.On("ReadFile", "testdata/shared/whatever.ts").Once() + fsys.On("ReadFile", "testdata/shared/mod.ts").Once() + fsys.On("ReadFile", "testdata/nested/index.ts").Once() + // Setup deno.json + im := ImportMap{Imports: map[string]string{ + "module-name/": "../shared/", + }} + assert.NoError(t, im.Resolve("testdata/modules/deno.json", testImports)) + // Run test + err := im.WalkImportPaths("testdata/modules/imports.ts", fsys.ReadFile) + // Check error + assert.NoError(t, err) + fsys.AssertExpectations(t) + }) + + t.Run("resolves legacy import map", func(t *testing.T) { + // Setup in-memory fs + fsys := MockFS{} + fsys.On("ReadFile", "/modules/my-module.ts").Once() + fsys.On("ReadFile", "testdata/modules/imports.ts").Once() + fsys.On("ReadFile", "testdata/geometries/Geometries.js").Once() + fsys.On("ReadFile", "testdata/shared/whatever.ts").Once() + fsys.On("ReadFile", "testdata/shared/mod.ts").Once() + fsys.On("ReadFile", "testdata/nested/index.ts").Once() + // Setup legacy import map + im := ImportMap{Imports: map[string]string{ + "module-name/": "./shared/", + }} + assert.NoError(t, im.Resolve("testdata/import_map.json", testImports)) + // Run test + err := im.WalkImportPaths("testdata/modules/imports.ts", fsys.ReadFile) + // Check error + assert.NoError(t, err) + fsys.AssertExpectations(t) + }) +} + +func TestResolveImports(t *testing.T) { + t.Run("resolves relative directory", func(t *testing.T) { + imPath := "supabase/functions/import_map.json" + // Setup in-memory fs + fsys := fs.MapFS{ + imPath: &fs.MapFile{Data: []byte(`{ + "imports": { + "abs/": "/tmp/", + "root": "../../common", + "parent": "../tests", + "child": "child/", + "missing": "../missing" + } + }`)}, + "/tmp/": &fs.MapFile{}, + "common": &fs.MapFile{}, + "supabase/tests": &fs.MapFile{}, + "supabase/functions/child": &fs.MapFile{}, + } + // Run test + resolved := ImportMap{} + err := resolved.Load(imPath, fsys) + // Check error + assert.NoError(t, err) + assert.Equal(t, "/tmp/", resolved.Imports["abs/"]) + assert.Equal(t, "./common", resolved.Imports["root"]) + assert.Equal(t, "./supabase/tests", resolved.Imports["parent"]) + assert.Equal(t, "./supabase/functions/child/", resolved.Imports["child"]) + assert.Equal(t, "../missing", resolved.Imports["missing"]) + }) + + t.Run("resolves parent scopes", func(t *testing.T) { + imPath := "supabase/functions/import_map.json" + // Setup in-memory fs + fsys := fs.MapFS{ + imPath: &fs.MapFile{Data: []byte(`{ + "scopes": { + "my-scope": { + "my-mod": "https://deno.land" + } + } + }`)}, + } + // Run test + resolved := ImportMap{} + err := resolved.Load(imPath, fsys) + // Check error + assert.NoError(t, err) + assert.Equal(t, "https://deno.land", resolved.Scopes["my-scope"]["my-mod"]) + }) +} + +func TestResolveDeno(t *testing.T) { + t.Run("resolves deno.json", func(t *testing.T) { + imPath := "supabase/functions/slug/deno.json" + // Setup in-memory fs + fsys := fs.MapFS{ + imPath: &fs.MapFile{Data: []byte(`{ + "imports": { + "@mod": "./mod.ts" + }, + "importMap": "../../import_map.json" + }`)}, + "supabase/functions/slug/mod.ts": &fs.MapFile{}, + } + // Run test + resolved := ImportMap{} + err := resolved.LoadAsDeno(imPath, fsys) + // Check error + assert.NoError(t, err) + assert.Equal(t, "./supabase/functions/slug/mod.ts", resolved.Imports["@mod"]) + }) + + t.Run("resolves fallback imports", func(t *testing.T) { + imPath := "supabase/functions/slug/deno.json" + // Setup in-memory fs + fsys := fs.MapFS{ + imPath: &fs.MapFile{Data: []byte(`{ + "importMap": "../../import_map.json" + }`)}, + "supabase/import_map.json": &fs.MapFile{Data: []byte(`{ + "imports": { + "my-mod": "https://deno.land" + } + }`)}, + } + // Run test + resolved := ImportMap{} + err := resolved.LoadAsDeno(imPath, fsys) + // Check error + assert.NoError(t, err) + assert.Equal(t, "https://deno.land", resolved.Imports["my-mod"]) + }) + + t.Run("throws error on missing import", func(t *testing.T) { + imPath := "supabase/functions/slug/deno.jsonc" + // Setup in-memory fs + fsys := fs.MapFS{ + imPath: &fs.MapFile{Data: []byte(`{ + "importMap": "../../import_map.json" + }`)}, + } + // Run test + resolved := ImportMap{} + err := resolved.LoadAsDeno(imPath, fsys) + // Check error + assert.ErrorIs(t, err, os.ErrNotExist) + assert.Empty(t, resolved.Imports) + assert.Empty(t, resolved.Scopes) + }) +} diff --git a/pkg/function/deploy.go b/pkg/function/deploy.go index c44155ca98..ed105b6681 100644 --- a/pkg/function/deploy.go +++ b/pkg/function/deploy.go @@ -1,7 +1,6 @@ package function import ( - "bytes" "context" "encoding/json" "fmt" @@ -9,17 +8,13 @@ import ( "io/fs" "mime/multipart" "os" - "path" "path/filepath" - "regexp" - "strings" "github.com/go-errors/errors" "github.com/supabase/cli/pkg/api" "github.com/supabase/cli/pkg/cast" "github.com/supabase/cli/pkg/config" "github.com/supabase/cli/pkg/queue" - "github.com/tidwall/jsonc" ) var ErrNoDeploy = errors.New("All Functions are up to date.") @@ -143,6 +138,17 @@ func writeForm(form *multipart.Writer, meta api.FunctionDeployMetadata, fsys fs. if err := enc.Encode(meta); err != nil { return errors.Errorf("failed to encode metadata: %w", err) } + uploadAsset := func(srcPath string, r io.Reader) error { + fmt.Fprintf(os.Stderr, "Uploading asset (%s): %s\n", *meta.Name, srcPath) + f, err := form.CreateFormFile("file", srcPath) + if err != nil { + return errors.Errorf("failed to create form: %w", err) + } + if _, err := io.Copy(f, r); err != nil { + return errors.Errorf("failed to write form: %w", err) + } + return nil + } addFile := func(srcPath string, w io.Writer) error { f, err := fsys.Open(filepath.FromSlash(srcPath)) if err != nil { @@ -154,39 +160,15 @@ func writeForm(form *multipart.Writer, meta api.FunctionDeployMetadata, fsys fs. } else if fi.IsDir() { return errors.New("file path is a directory: " + srcPath) } - fmt.Fprintf(os.Stderr, "Uploading asset (%s): %s\n", *meta.Name, srcPath) r := io.TeeReader(f, w) - dst, err := form.CreateFormFile("file", srcPath) - if err != nil { - return errors.Errorf("failed to create form: %w", err) - } - if _, err := io.Copy(dst, r); err != nil { - return errors.Errorf("failed to write form: %w", err) - } - return nil + return uploadAsset(srcPath, r) } // Add import map importMap := ImportMap{} if imPath := cast.Val(meta.ImportMapPath, ""); len(imPath) > 0 { - data, err := fs.ReadFile(fsys, filepath.FromSlash(imPath)) - if err != nil { - return errors.Errorf("failed to load import map: %w", err) - } - if err := importMap.Parse(data); err != nil { - return err - } - if err := importMap.Resolve(imPath, fsys); err != nil { + if err := importMap.LoadAsDeno(imPath, fsys, uploadAsset); err != nil { return err } - // TODO: replace with addFile once edge runtime supports jsonc - fmt.Fprintf(os.Stderr, "Uploading asset (%s): %s\n", *meta.Name, imPath) - f, err := form.CreateFormFile("file", imPath) - if err != nil { - return errors.Errorf("failed to create import map: %w", err) - } - if _, err := f.Write(data); err != nil { - return errors.Errorf("failed to write import map: %w", err) - } } // Add static files patterns := config.Glob(cast.Val(meta.StaticPatterns, []string{})) @@ -201,122 +183,3 @@ func writeForm(form *multipart.Writer, meta api.FunctionDeployMetadata, fsys fs. } return importMap.WalkImportPaths(meta.EntrypointPath, addFile) } - -type ImportMap struct { - Imports map[string]string `json:"imports"` - Scopes map[string]map[string]string `json:"scopes"` -} - -func (m *ImportMap) Parse(data []byte) error { - data = jsonc.ToJSONInPlace(data) - decoder := json.NewDecoder(bytes.NewReader(data)) - if err := decoder.Decode(&m); err != nil { - return errors.Errorf("failed to parse import map: %w", err) - } - return nil -} - -func (m *ImportMap) Resolve(imPath string, fsys fs.FS) error { - // Resolve all paths relative to current file - for k, v := range m.Imports { - m.Imports[k] = resolveHostPath(imPath, v, fsys) - } - for module, mapping := range m.Scopes { - for k, v := range mapping { - m.Scopes[module][k] = resolveHostPath(imPath, v, fsys) - } - } - return nil -} - -func resolveHostPath(jsonPath, hostPath string, fsys fs.FS) string { - // Leave absolute paths unchanged - if path.IsAbs(hostPath) { - return hostPath - } - resolved := path.Join(path.Dir(jsonPath), hostPath) - if _, err := fs.Stat(fsys, filepath.FromSlash(resolved)); err != nil { - // Leave URLs unchanged - return hostPath - } - // Directory imports need to be suffixed with / - // Ref: https://deno.com/manual@v1.33.0/basics/import_maps - if strings.HasSuffix(hostPath, "/") { - resolved += "/" - } - // Relative imports must be prefixed with ./ or ../ - if !path.IsAbs(resolved) { - resolved = "./" + resolved - } - return resolved -} - -// Ref: https://regex101.com/r/DfBdJA/1 -var importPathPattern = regexp.MustCompile(`(?i)(?:import|export)\s+(?:{[^{}]+}|.*?)\s*(?:from)?\s*['"](.*?)['"]|import\(\s*['"](.*?)['"]\)`) - -func (importMap *ImportMap) WalkImportPaths(srcPath string, readFile func(curr string, w io.Writer) error) error { - seen := map[string]struct{}{} - // DFS because it's more efficient to pop from end of array - q := make([]string, 1) - q[0] = srcPath - for len(q) > 0 { - curr := q[len(q)-1] - q = q[:len(q)-1] - // Assume no file is symlinked - if _, ok := seen[curr]; ok { - continue - } - seen[curr] = struct{}{} - // Read into memory for regex match later - var buf bytes.Buffer - if err := readFile(curr, &buf); errors.Is(err, os.ErrNotExist) { - fmt.Fprintln(os.Stderr, "WARN:", err) - continue - } else if err != nil { - return err - } - // Traverse all modules imported by the current source file - for _, matches := range importPathPattern.FindAllStringSubmatch(buf.String(), -1) { - if len(matches) < 3 { - continue - } - // Matches 'from' clause if present, else fallback to 'import' - mod := matches[1] - if len(mod) == 0 { - mod = matches[2] - } - mod = strings.TrimSpace(mod) - // Substitute kv from import map - substituted := false - for k, v := range importMap.Imports { - if strings.HasPrefix(mod, k) { - mod = v + mod[len(k):] - substituted = true - } - } - // Ignore URLs and directories, assuming no sloppy imports - // https://github.com/denoland/deno/issues/2506#issuecomment-2727635545 - if len(path.Ext(mod)) == 0 { - continue - } - // Deno import path must begin with one of these prefixes - if !isRelPath(mod) && !isAbsPath(mod) { - continue - } - if isRelPath(mod) && !substituted { - mod = path.Join(path.Dir(curr), mod) - } - // Cleans import path to help detect duplicates - q = append(q, path.Clean(mod)) - } - } - return nil -} - -func isRelPath(mod string) bool { - return strings.HasPrefix(mod, "./") || strings.HasPrefix(mod, "../") -} - -func isAbsPath(mod string) bool { - return strings.HasPrefix(mod, "/") -} diff --git a/pkg/function/deploy_test.go b/pkg/function/deploy_test.go index dbbb44b45d..5655697fa7 100644 --- a/pkg/function/deploy_test.go +++ b/pkg/function/deploy_test.go @@ -3,9 +3,7 @@ package function import ( "bytes" "context" - "embed" "errors" - "io" "mime/multipart" "net/http" "os" @@ -15,90 +13,12 @@ import ( "github.com/h2non/gock" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/supabase/cli/pkg/api" "github.com/supabase/cli/pkg/cast" "github.com/supabase/cli/pkg/config" ) -//go:embed testdata -var testImports embed.FS - -type MockFS struct { - mock.Mock -} - -func (m *MockFS) ReadFile(srcPath string, w io.Writer) error { - _ = m.Called(srcPath) - data, err := testImports.ReadFile(srcPath) - if err != nil { - return err - } - if _, err := w.Write(data); err != nil { - return err - } - return nil -} - -func TestImportPaths(t *testing.T) { - t.Run("iterates all import paths", func(t *testing.T) { - // Setup in-memory fs - fsys := MockFS{} - fsys.On("ReadFile", "/modules/my-module.ts").Once() - fsys.On("ReadFile", "testdata/modules/imports.ts").Once() - fsys.On("ReadFile", "testdata/geometries/Geometries.js").Once() - // Run test - im := ImportMap{} - err := im.WalkImportPaths("testdata/modules/imports.ts", fsys.ReadFile) - // Check error - assert.NoError(t, err) - fsys.AssertExpectations(t) - }) - - t.Run("iterates with import map", func(t *testing.T) { - // Setup in-memory fs - fsys := MockFS{} - fsys.On("ReadFile", "/modules/my-module.ts").Once() - fsys.On("ReadFile", "testdata/modules/imports.ts").Once() - fsys.On("ReadFile", "testdata/geometries/Geometries.js").Once() - fsys.On("ReadFile", "testdata/shared/whatever.ts").Once() - fsys.On("ReadFile", "testdata/shared/mod.ts").Once() - fsys.On("ReadFile", "testdata/nested/index.ts").Once() - // Setup deno.json - im := ImportMap{Imports: map[string]string{ - "module-name/": "../shared/", - }} - assert.NoError(t, im.Resolve("testdata/modules/deno.json", testImports)) - // Run test - err := im.WalkImportPaths("testdata/modules/imports.ts", fsys.ReadFile) - // Check error - assert.NoError(t, err) - fsys.AssertExpectations(t) - }) - - t.Run("resolves legacy import map", func(t *testing.T) { - // Setup in-memory fs - fsys := MockFS{} - fsys.On("ReadFile", "/modules/my-module.ts").Once() - fsys.On("ReadFile", "testdata/modules/imports.ts").Once() - fsys.On("ReadFile", "testdata/geometries/Geometries.js").Once() - fsys.On("ReadFile", "testdata/shared/whatever.ts").Once() - fsys.On("ReadFile", "testdata/shared/mod.ts").Once() - fsys.On("ReadFile", "testdata/nested/index.ts").Once() - // Setup legacy import map - im := ImportMap{Imports: map[string]string{ - "module-name/": "./shared/", - }} - assert.NoError(t, im.Resolve("testdata/import_map.json", testImports)) - // Run test - err := im.WalkImportPaths("testdata/modules/imports.ts", fsys.ReadFile) - // Check error - assert.NoError(t, err) - fsys.AssertExpectations(t) - }) -} - func assertFormEqual(t *testing.T, actual []byte) { snapshot := path.Join("testdata", path.Base(t.Name())+".form") expected, err := testImports.ReadFile(snapshot)