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

supabase / cli / 14447243540

14 Apr 2025 01:44PM UTC coverage: 51.026% (+0.07%) from 50.961%
14447243540

push

github

sweatybridge
chore: correct typo

0 of 1 new or added line in 1 file covered. (0.0%)

39 existing lines in 2 files now uncovered.

6986 of 13691 relevant lines covered (51.03%)

184.45 hits per line

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

71.63
/pkg/function/deploy.go
1
package function
2

3
import (
4
        "context"
5
        "encoding/json"
6
        "fmt"
7
        "io"
8
        "io/fs"
9
        "mime/multipart"
10
        "os"
11
        "path/filepath"
12

13
        "github.com/go-errors/errors"
14
        "github.com/supabase/cli/pkg/api"
15
        "github.com/supabase/cli/pkg/cast"
16
        "github.com/supabase/cli/pkg/config"
17
        "github.com/supabase/cli/pkg/queue"
18
)
19

20
var ErrNoDeploy = errors.New("All Functions are up to date.")
21

22
func (s *EdgeRuntimeAPI) Deploy(ctx context.Context, functionConfig config.FunctionConfig, fsys fs.FS) error {
3✔
23
        if s.eszip != nil {
3✔
UNCOV
24
                return s.UpsertFunctions(ctx, functionConfig)
×
UNCOV
25
        }
×
26
        // Convert all paths in functions config to relative when using api deploy
27
        var toDeploy []api.FunctionDeployMetadata
3✔
28
        for slug, fc := range functionConfig {
7✔
29
                if !fc.Enabled {
4✔
30
                        fmt.Fprintln(os.Stderr, "Skipped deploying Function:", slug)
×
UNCOV
31
                        continue
×
32
                }
33
                meta := api.FunctionDeployMetadata{
4✔
34
                        Name:           &slug,
4✔
35
                        EntrypointPath: toRelPath(fc.Entrypoint),
4✔
36
                        ImportMapPath:  cast.Ptr(toRelPath(fc.ImportMap)),
4✔
37
                        VerifyJwt:      &fc.VerifyJWT,
4✔
38
                }
4✔
39
                files := make([]string, len(fc.StaticFiles))
4✔
40
                for i, sf := range fc.StaticFiles {
4✔
UNCOV
41
                        files[i] = toRelPath(sf)
×
UNCOV
42
                }
×
43
                meta.StaticPatterns = &files
4✔
44
                toDeploy = append(toDeploy, meta)
4✔
45
        }
46
        if len(toDeploy) == 0 {
3✔
47
                return errors.New(ErrNoDeploy)
×
48
        } else if len(toDeploy) == 1 {
5✔
49
                param := api.V1DeployAFunctionParams{Slug: toDeploy[0].Name}
2✔
50
                _, err := s.upload(ctx, param, toDeploy[0], fsys)
2✔
51
                return err
2✔
52
        }
2✔
53
        return s.bulkUpload(ctx, toDeploy, fsys)
1✔
54
}
55

56
func toRelPath(fp string) string {
8✔
57
        if filepath.IsAbs(fp) {
8✔
UNCOV
58
                if cwd, err := os.Getwd(); err == nil {
×
UNCOV
59
                        if relPath, err := filepath.Rel(cwd, fp); err == nil {
×
UNCOV
60
                                fp = relPath
×
UNCOV
61
                        }
×
62
                }
63
        }
64
        return filepath.ToSlash(fp)
8✔
65
}
66

67
func (s *EdgeRuntimeAPI) bulkUpload(ctx context.Context, toDeploy []api.FunctionDeployMetadata, fsys fs.FS) error {
1✔
68
        jq := queue.NewJobQueue(s.maxJobs)
1✔
69
        toUpdate := make([]api.BulkUpdateFunctionBody, len(toDeploy))
1✔
70
        for i, meta := range toDeploy {
3✔
71
                param := api.V1DeployAFunctionParams{
2✔
72
                        Slug:       meta.Name,
2✔
73
                        BundleOnly: cast.Ptr(true),
2✔
74
                }
2✔
75
                bundle := func() error {
4✔
76
                        fmt.Fprintln(os.Stderr, "Deploying Function:", *meta.Name)
2✔
77
                        resp, err := s.upload(ctx, param, meta, fsys)
2✔
78
                        if err != nil {
2✔
UNCOV
79
                                return err
×
UNCOV
80
                        }
×
81
                        toUpdate[i].Id = resp.Id
2✔
82
                        toUpdate[i].Name = resp.Name
2✔
83
                        toUpdate[i].Slug = resp.Slug
2✔
84
                        toUpdate[i].Version = resp.Version
2✔
85
                        toUpdate[i].EntrypointPath = resp.EntrypointPath
2✔
86
                        toUpdate[i].ImportMap = resp.ImportMap
2✔
87
                        toUpdate[i].ImportMapPath = resp.ImportMapPath
2✔
88
                        toUpdate[i].VerifyJwt = resp.VerifyJwt
2✔
89
                        toUpdate[i].Status = api.BulkUpdateFunctionBodyStatus(resp.Status)
2✔
90
                        toUpdate[i].CreatedAt = resp.CreatedAt
2✔
91
                        return nil
2✔
92
                }
93
                if err := jq.Put(bundle); err != nil {
2✔
UNCOV
94
                        return err
×
UNCOV
95
                }
×
96
        }
97
        if err := jq.Collect(); err != nil {
1✔
UNCOV
98
                return err
×
99
        }
×
100
        if resp, err := s.client.V1BulkUpdateFunctionsWithResponse(ctx, s.project, toUpdate); err != nil {
1✔
UNCOV
101
                return errors.Errorf("failed to bulk update: %w", err)
×
102
        } else if resp.JSON200 == nil {
1✔
103
                return errors.Errorf("unexpected bulk update status %d: %s", resp.StatusCode(), string(resp.Body))
×
104
        }
×
105
        return nil
1✔
106
}
107

108
func (s *EdgeRuntimeAPI) upload(ctx context.Context, param api.V1DeployAFunctionParams, meta api.FunctionDeployMetadata, fsys fs.FS) (*api.DeployFunctionResponse, error) {
4✔
109
        body, w := io.Pipe()
4✔
110
        form := multipart.NewWriter(w)
4✔
111
        ctx, cancel := context.WithCancelCause(ctx)
4✔
112
        go func() {
8✔
113
                defer w.Close()
4✔
114
                defer form.Close()
4✔
115
                if err := writeForm(form, meta, fsys); err != nil {
4✔
UNCOV
116
                        // Since we are streaming files to the POST request body, any errors
×
UNCOV
117
                        // should be propagated to the request context to cancel the upload.
×
UNCOV
118
                        cancel(err)
×
UNCOV
119
                }
×
120
        }()
121
        resp, err := s.client.V1DeployAFunctionWithBodyWithResponse(ctx, s.project, &param, form.FormDataContentType(), body)
4✔
122
        if cause := context.Cause(ctx); cause != ctx.Err() {
4✔
123
                return nil, cause
×
124
        } else if err != nil {
5✔
125
                return nil, errors.Errorf("failed to deploy function: %w", err)
1✔
126
        } else if resp.JSON201 == nil {
4✔
UNCOV
127
                return nil, errors.Errorf("unexpected deploy status %d: %s", resp.StatusCode(), string(resp.Body))
×
128
        }
×
129
        return resp.JSON201, nil
3✔
130
}
131

132
func writeForm(form *multipart.Writer, meta api.FunctionDeployMetadata, fsys fs.FS) error {
7✔
133
        m, err := form.CreateFormField("metadata")
7✔
134
        if err != nil {
7✔
UNCOV
135
                return errors.Errorf("failed to create metadata: %w", err)
×
UNCOV
136
        }
×
137
        enc := json.NewEncoder(m)
3✔
138
        if err := enc.Encode(meta); err != nil {
3✔
UNCOV
139
                return errors.Errorf("failed to encode metadata: %w", err)
×
140
        }
×
141
        uploadAsset := func(srcPath string, r io.Reader) error {
6✔
142
                fmt.Fprintf(os.Stderr, "Uploading asset (%s): %s\n", *meta.Name, srcPath)
3✔
143
                f, err := form.CreateFormFile("file", srcPath)
3✔
144
                if err != nil {
3✔
NEW
145
                        return errors.Errorf("failed to create form: %w", err)
×
UNCOV
146
                }
×
147
                if _, err := io.Copy(f, r); err != nil {
3✔
UNCOV
148
                        return errors.Errorf("failed to write form: %w", err)
×
149
                }
×
150
                return nil
3✔
151
        }
152
        addFile := func(srcPath string, w io.Writer) error {
6✔
153
                f, err := fsys.Open(filepath.FromSlash(srcPath))
3✔
154
                if err != nil {
3✔
UNCOV
155
                        return errors.Errorf("failed to read file: %w", err)
×
UNCOV
156
                }
×
157
                defer f.Close()
3✔
158
                if fi, err := f.Stat(); err != nil {
3✔
UNCOV
159
                        return errors.Errorf("failed to stat file: %w", err)
×
160
                } else if fi.IsDir() {
4✔
161
                        return errors.New("file path is a directory: " + srcPath)
1✔
162
                }
1✔
163
                r := io.TeeReader(f, w)
2✔
164
                return uploadAsset(srcPath, r)
2✔
165
        }
166
        // Add import map
167
        importMap := ImportMap{}
3✔
168
        if imPath := cast.Val(meta.ImportMapPath, ""); len(imPath) > 0 {
5✔
169
                if err := importMap.LoadAsDeno(imPath, fsys, uploadAsset); err != nil {
3✔
170
                        return err
1✔
171
                }
1✔
172
        }
173
        // Add static files
174
        patterns := config.Glob(cast.Val(meta.StaticPatterns, []string{}))
2✔
175
        files, err := patterns.Files(fsys)
2✔
176
        if err != nil {
2✔
177
                fmt.Fprintln(os.Stderr, "WARN:", err)
×
UNCOV
178
        }
×
179
        for _, sfPath := range files {
4✔
180
                if err := addFile(sfPath, io.Discard); err != nil {
3✔
181
                        return err
1✔
182
                }
1✔
183
        }
184
        return importMap.WalkImportPaths(meta.EntrypointPath, addFile)
1✔
185
}
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