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

supabase / cli / 13365596164

17 Feb 2025 08:07AM UTC coverage: 58.167% (-0.4%) from 58.596%
13365596164

push

github

sweatybridge
fix: verify jwt by default when deploying without config

18 of 20 new or added lines in 5 files covered. (90.0%)

57 existing lines in 2 files now uncovered.

7760 of 13341 relevant lines covered (58.17%)

201.93 hits per line

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

68.9
/internal/functions/deploy/upload.go
1
package deploy
2

3
import (
4
        "bytes"
5
        "context"
6
        "encoding/json"
7
        "fmt"
8
        "io"
9
        "mime/multipart"
10
        "os"
11
        "path"
12
        "path/filepath"
13
        "regexp"
14
        "strings"
15

16
        "github.com/go-errors/errors"
17
        "github.com/spf13/afero"
18
        "github.com/supabase/cli/internal/utils"
19
        "github.com/supabase/cli/internal/utils/flags"
20
        "github.com/supabase/cli/pkg/api"
21
        "github.com/supabase/cli/pkg/cast"
22
        "github.com/supabase/cli/pkg/config"
23
)
24

25
func deploy(ctx context.Context, functionConfig config.FunctionConfig, fsys afero.Fs) error {
3✔
26
        bundleOnly := len(functionConfig) > 1
3✔
27
        var toUpdate []api.BulkUpdateFunctionBody
3✔
28
        for slug, fc := range functionConfig {
7✔
29
                if !fc.Enabled {
7✔
30
                        fmt.Fprintln(os.Stderr, "Skipped deploying Function:", slug)
3✔
31
                        continue
3✔
32
                }
33
                fmt.Fprintln(os.Stderr, "Deploying Function:", slug)
1✔
34
                param := api.V1DeployAFunctionParams{Slug: &slug}
1✔
35
                if bundleOnly {
1✔
UNCOV
36
                        param.BundleOnly = &bundleOnly
×
UNCOV
37
                }
×
38
                meta := api.FunctionDeployMetadata{
1✔
39
                        Name:           &slug,
1✔
40
                        EntrypointPath: fc.Entrypoint,
1✔
41
                        ImportMapPath:  &fc.ImportMap,
1✔
42
                        VerifyJwt:      &fc.VerifyJWT,
1✔
43
                }
1✔
44
                if len(fc.StaticFiles) > 0 {
1✔
45
                        meta.StaticPatterns = &fc.StaticFiles
×
46
                }
×
47
                resp, err := upload(ctx, param, meta, fsys)
1✔
48
                if err != nil {
2✔
49
                        return err
1✔
50
                }
1✔
UNCOV
51
                toUpdate = append(toUpdate, api.BulkUpdateFunctionBody{
×
UNCOV
52
                        CreatedAt:      resp.CreatedAt,
×
UNCOV
53
                        EntrypointPath: resp.EntrypointPath,
×
UNCOV
54
                        Id:             resp.Id,
×
UNCOV
55
                        ImportMap:      resp.ImportMap,
×
UNCOV
56
                        ImportMapPath:  resp.ImportMapPath,
×
UNCOV
57
                        Name:           resp.Name,
×
UNCOV
58
                        Slug:           resp.Slug,
×
UNCOV
59
                        Status:         api.BulkUpdateFunctionBodyStatus(resp.Status),
×
UNCOV
60
                        VerifyJwt:      resp.VerifyJwt,
×
UNCOV
61
                        Version:        resp.Version,
×
UNCOV
62
                })
×
63
        }
64
        if len(toUpdate) > 1 {
2✔
UNCOV
65
                if resp, err := utils.GetSupabase().V1BulkUpdateFunctionsWithResponse(ctx, flags.ProjectRef, toUpdate); err != nil {
×
66
                        return errors.Errorf("failed to bulk update: %w", err)
×
UNCOV
67
                } else if resp.JSON200 == nil {
×
68
                        return errors.Errorf("unexpected bulk update status %d: %s", resp.StatusCode(), string(resp.Body))
×
69
                }
×
70
        }
71
        return nil
2✔
72
}
73

74
func upload(ctx context.Context, param api.V1DeployAFunctionParams, meta api.FunctionDeployMetadata, fsys afero.Fs) (*api.DeployFunctionResponse, error) {
1✔
75
        body, w := io.Pipe()
1✔
76
        form := multipart.NewWriter(w)
1✔
77
        ctx, cancel := context.WithCancelCause(ctx)
1✔
78
        go func() {
2✔
79
                defer w.Close()
1✔
80
                defer form.Close()
1✔
81
                if err := writeForm(form, meta, fsys); err != nil {
1✔
82
                        // Since we are streaming files to the POST request body, any errors
×
83
                        // should be propagated to the request context to cancel the upload.
×
84
                        cancel(err)
×
85
                }
×
86
        }()
87
        resp, err := utils.GetSupabase().V1DeployAFunctionWithBodyWithResponse(ctx, flags.ProjectRef, &param, form.FormDataContentType(), body)
1✔
88
        if cause := context.Cause(ctx); cause != ctx.Err() {
1✔
89
                return nil, cause
×
90
        } else if err != nil {
2✔
91
                return nil, errors.Errorf("failed to deploy function: %w", err)
1✔
92
        } else if resp.JSON201 == nil {
1✔
93
                return nil, errors.Errorf("unexpected deploy status %d: %s", resp.StatusCode(), string(resp.Body))
×
94
        }
×
UNCOV
95
        return resp.JSON201, nil
×
96
}
97

98
func writeForm(form *multipart.Writer, meta api.FunctionDeployMetadata, fsys afero.Fs) error {
4✔
99
        m, err := form.CreateFormField("metadata")
4✔
100
        if err != nil {
4✔
101
                return errors.Errorf("failed to create metadata: %w", err)
×
102
        }
×
103
        enc := json.NewEncoder(m)
3✔
104
        if err := enc.Encode(meta); err != nil {
3✔
105
                return errors.Errorf("failed to encode metadata: %w", err)
×
106
        }
×
107
        addFile := func(srcPath string, w io.Writer) error {
6✔
108
                f, err := fsys.Open(filepath.FromSlash(srcPath))
3✔
109
                if err != nil {
3✔
110
                        return errors.Errorf("failed to read file: %w", err)
×
111
                }
×
112
                defer f.Close()
3✔
113
                if fi, err := f.Stat(); err != nil {
3✔
114
                        return errors.Errorf("failed to stat file: %w", err)
×
115
                } else if fi.IsDir() {
4✔
116
                        return errors.New("file path is a directory: " + srcPath)
1✔
117
                }
1✔
118
                fmt.Fprintln(os.Stderr, "Uploading asset:", srcPath)
2✔
119
                r := io.TeeReader(f, w)
2✔
120
                dst, err := form.CreateFormFile("file", srcPath)
2✔
121
                if err != nil {
2✔
122
                        return errors.Errorf("failed to create form: %w", err)
×
123
                }
×
124
                if _, err := io.Copy(dst, r); err != nil {
2✔
125
                        return errors.Errorf("failed to write form: %w", err)
×
126
                }
×
127
                return nil
2✔
128
        }
129
        // Add import map
130
        importMap := utils.ImportMap{}
3✔
131
        if imPath := cast.Val(meta.ImportMapPath, ""); len(imPath) > 0 {
5✔
132
                data, err := afero.ReadFile(fsys, filepath.FromSlash(imPath))
2✔
133
                if err != nil {
3✔
134
                        return errors.Errorf("failed to load import map: %w", err)
1✔
135
                }
1✔
136
                if err := importMap.Parse(data); err != nil {
1✔
137
                        return err
×
138
                }
×
139
                // TODO: replace with addFile once edge runtime supports jsonc
140
                fmt.Fprintln(os.Stderr, "Uploading asset:", imPath)
1✔
141
                f, err := form.CreateFormFile("file", imPath)
1✔
142
                if err != nil {
1✔
143
                        return errors.Errorf("failed to create import map: %w", err)
×
144
                }
×
145
                if _, err := f.Write(data); err != nil {
1✔
146
                        return errors.Errorf("failed to write import map: %w", err)
×
147
                }
×
148
        }
149
        // Add static files
150
        for _, pattern := range cast.Val(meta.StaticPatterns, []string{}) {
4✔
151
                matches, err := afero.Glob(fsys, pattern)
2✔
152
                if err != nil {
2✔
153
                        return errors.Errorf("failed to glob files: %w", err)
×
154
                }
×
155
                for _, sfPath := range matches {
3✔
156
                        if err := addFile(sfPath, io.Discard); err != nil {
1✔
157
                                return err
×
158
                        }
×
159
                }
160
        }
161
        return walkImportPaths(meta.EntrypointPath, importMap, addFile)
2✔
162
}
163

164
// Ref: https://regex101.com/r/DfBdJA/1
165
var importPathPattern = regexp.MustCompile(`(?i)(?:import|export)\s+(?:{[^{}]+}|.*?)\s*(?:from)?\s*['"](.*?)['"]|import\(\s*['"](.*?)['"]\)`)
166

167
func walkImportPaths(srcPath string, importMap utils.ImportMap, readFile func(curr string, w io.Writer) error) error {
4✔
168
        seen := map[string]struct{}{}
4✔
169
        // DFS because it's more efficient to pop from end of array
4✔
170
        q := make([]string, 1)
4✔
171
        q[0] = srcPath
4✔
172
        for len(q) > 0 {
22✔
173
                curr := q[len(q)-1]
18✔
174
                q = q[:len(q)-1]
18✔
175
                // Assume no file is symlinked
18✔
176
                if _, ok := seen[curr]; ok {
25✔
177
                        continue
7✔
178
                }
179
                seen[curr] = struct{}{}
11✔
180
                // Read into memory for regex match later
11✔
181
                var buf bytes.Buffer
11✔
182
                if err := readFile(curr, &buf); errors.Is(err, os.ErrNotExist) {
14✔
183
                        fmt.Fprintln(os.Stderr, utils.Yellow("WARNING:"), err)
3✔
184
                        continue
3✔
185
                } else if err != nil {
9✔
186
                        return err
1✔
187
                }
1✔
188
                // Traverse all modules imported by the current source file
189
                for _, matches := range importPathPattern.FindAllStringSubmatch(buf.String(), -1) {
109✔
190
                        if len(matches) < 3 {
102✔
191
                                continue
×
192
                        }
193
                        // Matches 'from' clause if present, else fallback to 'import'
194
                        mod := matches[1]
102✔
195
                        if len(mod) == 0 {
106✔
196
                                mod = matches[2]
4✔
197
                        }
4✔
198
                        mod = strings.TrimSpace(mod)
102✔
199
                        // Substitute kv from import map
102✔
200
                        for k, v := range importMap.Imports {
154✔
201
                                if strings.HasPrefix(mod, k) {
54✔
202
                                        mod = v + mod[len(k):]
2✔
203
                                }
2✔
204
                        }
205
                        // Deno import path must begin with these prefixes
206
                        if strings.HasPrefix(mod, "./") || strings.HasPrefix(mod, "../") {
122✔
207
                                mod = path.Join(path.Dir(curr), mod)
20✔
208
                        } else if !strings.HasPrefix(mod, "/") {
178✔
209
                                continue
76✔
210
                        }
211
                        if len(path.Ext(mod)) > 0 {
40✔
212
                                // Cleans import path to help detect duplicates
14✔
213
                                q = append(q, path.Clean(mod))
14✔
214
                        }
14✔
215
                }
216
        }
217
        return nil
3✔
218
}
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

© 2025 Coveralls, Inc