Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 58 additions & 7 deletions pkg/function/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ func writeForm(form *multipart.Writer, meta api.FunctionDeployMetadata, fsys fs.
if err := importMap.Parse(data); err != nil {
return err
}
if err := importMap.Resolve(imPath, fsys); 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)
Expand Down Expand Up @@ -201,6 +204,41 @@ func (m *ImportMap) Parse(data []byte) error {
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*['"](.*?)['"]\)`)

Expand Down Expand Up @@ -237,22 +275,35 @@ func walkImportPaths(srcPath string, importMap ImportMap, readFile func(curr str
}
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
}
}
// Deno import path must begin with these prefixes
if strings.HasPrefix(mod, "./") || strings.HasPrefix(mod, "../") {
mod = path.Join(path.Dir(curr), mod)
} else if !strings.HasPrefix(mod, "/") {
// Ignore URLs and directories
if len(path.Ext(mod)) == 0 {
continue
}
if len(path.Ext(mod)) > 0 {
// Cleans import path to help detect duplicates
q = append(q, path.Clean(mod))
// 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, "/")
}
28 changes: 25 additions & 3 deletions pkg/function/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ func TestImportPaths(t *testing.T) {
fsys.On("ReadFile", "testdata/modules/imports.ts").Once()
fsys.On("ReadFile", "testdata/geometries/Geometries.js").Once()
// Run test
im := ImportMap{}
err := walkImportPaths("testdata/modules/imports.ts", im, fsys.ReadFile)
err := walkImportPaths("testdata/modules/imports.ts", ImportMap{}, fsys.ReadFile)
// Check error
assert.NoError(t, err)
fsys.AssertExpectations(t)
Expand All @@ -65,10 +64,33 @@ func TestImportPaths(t *testing.T) {
fsys.On("ReadFile", "testdata/shared/whatever.ts").Once()
fsys.On("ReadFile", "testdata/shared/mod.ts").Once()
fsys.On("ReadFile", "testdata/nested/index.ts").Once()
// Run test
// 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 := walkImportPaths("testdata/modules/imports.ts", im, 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 := walkImportPaths("testdata/modules/imports.ts", im, fsys.ReadFile)
// Check error
assert.NoError(t, err)
Expand Down
6 changes: 5 additions & 1 deletion pkg/function/testdata/nested/deno.json
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
{}
{
"imports": {
"module": "jsr:@supabase/functions-js/edge-runtime.d.ts"
}
}
1 change: 1 addition & 0 deletions pkg/function/testdata/nested/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "module";
7 changes: 6 additions & 1 deletion pkg/function/testdata/writes_import_map.form
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ Content-Disposition: form-data; name="metadata"
Content-Disposition: form-data; name="file"; filename="testdata/nested/deno.json"
Content-Type: application/octet-stream

{}
{
"imports": {
"module": "jsr:@supabase/functions-js/edge-runtime.d.ts"
}
}

--test
Content-Disposition: form-data; name="file"; filename="testdata/geometries/Geometries.js"
Expand All @@ -18,3 +22,4 @@ Content-Type: application/octet-stream
Content-Disposition: form-data; name="file"; filename="testdata/nested/index.ts"
Content-Type: application/octet-stream

import "module";