• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / cli / 14399239229

11 Apr 2025 08:43AM UTC coverage: 51.143%. First build
14399239229

Pull #3417

github

web-flow
Merge 7f0ae8f1b into 96a407fc8
Pull Request #3417: fix: outside import with relative module graph

38 of 52 new or added lines in 3 files covered. (73.08%)

7022 of 13730 relevant lines covered (51.14%)

183.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.33
/internal/functions/deploy/bundle.go
1
package deploy
2

3
import (
4
        "context"
5
        "fmt"
6
        "io"
7
        "os"
8
        "path/filepath"
9
        "strings"
10

11
        "github.com/docker/docker/api/types/container"
12
        "github.com/docker/docker/api/types/network"
13
        "github.com/go-errors/errors"
14
        "github.com/spf13/afero"
15
        "github.com/spf13/viper"
16
        "github.com/supabase/cli/internal/utils"
17
        "github.com/supabase/cli/pkg/api"
18
        "github.com/supabase/cli/pkg/function"
19
)
20

21
type dockerBundler struct {
22
        fsys afero.Fs
23
}
24

25
func NewDockerBundler(fsys afero.Fs) function.EszipBundler {
7✔
26
        return &dockerBundler{fsys: fsys}
7✔
27
}
7✔
28

29
func (b *dockerBundler) Bundle(ctx context.Context, slug, entrypoint, importMap string, staticFiles []string, output io.Writer) (api.FunctionDeployMetadata, error) {
8✔
30
        meta := function.NewMetadata(slug, entrypoint, importMap, staticFiles)
8✔
31
        fmt.Fprintln(os.Stderr, "Bundling Function:", utils.Bold(slug))
8✔
32
        cwd, err := os.Getwd()
8✔
33
        if err != nil {
8✔
34
                return meta, errors.Errorf("failed to get working directory: %w", err)
×
35
        }
×
36
        // BitBucket pipelines require docker bind mounts to be world writable
37
        hostOutputDir := filepath.Join(utils.TempDir, fmt.Sprintf(".output_%s", slug))
8✔
38
        if err := b.fsys.MkdirAll(hostOutputDir, 0777); err != nil {
9✔
39
                return meta, errors.Errorf("failed to mkdir: %w", err)
1✔
40
        }
1✔
41
        defer func() {
14✔
42
                if err := b.fsys.RemoveAll(hostOutputDir); err != nil {
7✔
43
                        fmt.Fprintln(os.Stderr, err)
×
44
                }
×
45
        }()
46
        // Create bind mounts
47
        binds, err := GetBindMounts(cwd, utils.FunctionsDir, hostOutputDir, entrypoint, importMap, b.fsys)
7✔
48
        if err != nil {
7✔
49
                return meta, err
×
50
        }
×
51
        hostOutputPath := filepath.Join(hostOutputDir, "output.eszip")
7✔
52
        // Create exec command
7✔
53
        cmd := []string{"bundle", "--entrypoint", utils.ToDockerPath(entrypoint), "--output", utils.ToDockerPath(hostOutputPath)}
7✔
54
        if len(importMap) > 0 {
9✔
55
                cmd = append(cmd, "--import-map", utils.ToDockerPath(importMap))
2✔
56
        }
2✔
57
        for _, sf := range staticFiles {
8✔
58
                cmd = append(cmd, "--static", utils.ToDockerPath(sf))
1✔
59
        }
1✔
60
        if viper.GetBool("DEBUG") {
7✔
61
                cmd = append(cmd, "--verbose")
×
62
        }
×
63

64
        env := []string{}
7✔
65
        if custom_registry := os.Getenv("NPM_CONFIG_REGISTRY"); custom_registry != "" {
7✔
66
                env = append(env, "NPM_CONFIG_REGISTRY="+custom_registry)
×
67
        }
×
68
        // Run bundle
69
        if err := utils.DockerRunOnceWithConfig(
7✔
70
                ctx,
7✔
71
                container.Config{
7✔
72
                        Image:      utils.Config.EdgeRuntime.Image,
7✔
73
                        Env:        env,
7✔
74
                        Cmd:        cmd,
7✔
75
                        WorkingDir: utils.ToDockerPath(cwd),
7✔
76
                },
7✔
77
                container.HostConfig{
7✔
78
                        Binds: binds,
7✔
79
                },
7✔
80
                network.NetworkingConfig{},
7✔
81
                "",
7✔
82
                os.Stdout,
7✔
83
                os.Stderr,
7✔
84
        ); err != nil {
8✔
85
                return meta, err
1✔
86
        }
1✔
87
        // Read and compress
88
        eszipBytes, err := b.fsys.Open(hostOutputPath)
6✔
89
        if err != nil {
6✔
90
                return meta, errors.Errorf("failed to open eszip: %w", err)
×
91
        }
×
92
        defer eszipBytes.Close()
6✔
93
        return meta, function.Compress(eszipBytes, output)
6✔
94
}
95

96
func GetBindMounts(cwd, hostFuncDir, hostOutputDir, hostEntrypointPath, hostImportMapPath string, fsys afero.Fs) ([]string, error) {
7✔
97
        sep := string(filepath.Separator)
7✔
98
        // Docker requires all host paths to be absolute
7✔
99
        if !filepath.IsAbs(hostFuncDir) {
14✔
100
                hostFuncDir = filepath.Join(cwd, hostFuncDir)
7✔
101
        }
7✔
102
        if !strings.HasSuffix(hostFuncDir, sep) {
14✔
103
                hostFuncDir += sep
7✔
104
        }
7✔
105
        dockerFuncDir := utils.ToDockerPath(hostFuncDir)
7✔
106
        // TODO: bind ./supabase/functions:/home/deno/functions to hide PII?
7✔
107
        binds := []string{
7✔
108
                // Reuse deno cache directory, ie. DENO_DIR, between container restarts
7✔
109
                // https://denolib.gitbook.io/guide/advanced/deno_dir-code-fetch-and-cache
7✔
110
                utils.EdgeRuntimeId + ":/root/.cache/deno:rw",
7✔
111
                hostFuncDir + ":" + dockerFuncDir + ":ro",
7✔
112
        }
7✔
113
        if len(hostOutputDir) > 0 {
14✔
114
                if !filepath.IsAbs(hostOutputDir) {
14✔
115
                        hostOutputDir = filepath.Join(cwd, hostOutputDir)
7✔
116
                }
7✔
117
                if !strings.HasSuffix(hostOutputDir, sep) {
14✔
118
                        hostOutputDir += sep
7✔
119
                }
7✔
120
                if !strings.HasPrefix(hostOutputDir, hostFuncDir) {
14✔
121
                        dockerOutputDir := utils.ToDockerPath(hostOutputDir)
7✔
122
                        binds = append(binds, hostOutputDir+":"+dockerOutputDir+":rw")
7✔
123
                }
7✔
124
        }
125
        // Allow entrypoints outside the functions directory
126
        hostEntrypointDir := filepath.Dir(hostEntrypointPath)
7✔
127
        if len(hostEntrypointDir) > 0 {
14✔
128
                if !filepath.IsAbs(hostEntrypointDir) {
14✔
129
                        hostEntrypointDir = filepath.Join(cwd, hostEntrypointDir)
7✔
130
                }
7✔
131
                if !strings.HasSuffix(hostEntrypointDir, sep) {
14✔
132
                        hostEntrypointDir += sep
7✔
133
                }
7✔
134
                if !strings.HasPrefix(hostEntrypointDir, hostFuncDir) &&
7✔
135
                        !strings.HasPrefix(hostEntrypointDir, hostOutputDir) {
8✔
136
                        dockerEntrypointDir := utils.ToDockerPath(hostEntrypointDir)
1✔
137
                        binds = append(binds, hostEntrypointDir+":"+dockerEntrypointDir+":ro")
1✔
138
                }
1✔
139
        }
140

141
        // Imports outside of ./supabase/functions will be bound by absolute path
142
        if len(hostImportMapPath) > 0 {
9✔
143
                if !filepath.IsAbs(hostImportMapPath) {
4✔
144
                        hostImportMapPath = filepath.Join(cwd, hostImportMapPath)
2✔
145
                }
2✔
146
                importMap, err := utils.NewImportMap(hostImportMapPath, fsys)
2✔
147
                if err != nil {
2✔
148
                        return nil, err
×
149
                }
×
150

151
                modules := utils.BindHostModules(importMap)
2✔
152
                dockerImportMapPath := utils.ToDockerPath(hostImportMapPath)
2✔
153
                modules = append(modules, hostImportMapPath+":"+dockerImportMapPath+":ro")
2✔
154

2✔
155
                addFile := func(srcPath string, w io.Writer) error {
4✔
156
                        f, err := fsys.Open(filepath.FromSlash(srcPath))
2✔
157
                        if err != nil {
3✔
158
                                return errors.Errorf("failed to read file: %w", err)
1✔
159
                        }
1✔
160
                        defer f.Close()
1✔
161
                        if fi, err := f.Stat(); err != nil {
1✔
NEW
162
                                return errors.Errorf("failed to stat file: %w", err)
×
163
                        } else if fi.IsDir() {
1✔
NEW
164
                                return errors.New("file path is a directory: " + srcPath)
×
NEW
165
                        }
×
166

167
                        r := io.TeeReader(f, w)
1✔
168
                        _, err = io.Copy(io.Discard, r) // Discard the read data after writing to w
1✔
169
                        if err != nil {
1✔
NEW
170
                                return errors.Errorf("failed to copy file content: %w", err)
×
NEW
171
                        }
×
172

173
                        if !filepath.IsAbs(srcPath) || strings.HasPrefix(srcPath, hostFuncDir) {
2✔
174
                                return nil
1✔
175
                        }
1✔
176

NEW
177
                        dockerPath := utils.ToDockerPath(srcPath)
×
NEW
178
                        modules = append(modules, srcPath+":"+dockerPath+":ro")
×
NEW
179

×
NEW
180
                        return nil
×
181
                }
182

183
                // Resolving all Import Graph
184
                err = importMap.WalkImportPaths(hostEntrypointPath, addFile)
2✔
185
                if err != nil {
2✔
NEW
186
                        return nil, err
×
NEW
187
                }
×
188

189
                // Remove any duplicate mount points
190
                for _, mod := range modules {
4✔
191
                        hostPath := strings.Split(mod, ":")[0]
2✔
192
                        if !strings.HasPrefix(hostPath, hostFuncDir) &&
2✔
193
                                (len(hostOutputDir) == 0 || !strings.HasPrefix(hostPath, hostOutputDir)) &&
2✔
194
                                (len(hostEntrypointDir) == 0 || !strings.HasPrefix(hostPath, hostEntrypointDir)) {
3✔
195
                                binds = append(binds, mod)
1✔
196
                        }
1✔
197
                }
198
        }
199
        return binds, nil
7✔
200
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc