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

supabase / cli / 18926015369

30 Oct 2025 12:29AM UTC coverage: 54.718% (-0.01%) from 54.728%
18926015369

Pull #4372

github

web-flow
Merge 8c4304595 into 891a5df91
Pull Request #4372: fix: toggle `DENO_NO_PACKAGE_JSON` conditionally

11 of 16 new or added lines in 2 files covered. (68.75%)

5 existing lines in 1 file now uncovered.

6396 of 11689 relevant lines covered (54.72%)

6.13 hits per line

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

74.42
/internal/functions/deploy/deploy.go
1
package deploy
2

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

10
        "github.com/go-errors/errors"
11
        "github.com/spf13/afero"
12
        "github.com/supabase/cli/internal/functions/delete"
13
        "github.com/supabase/cli/internal/utils"
14
        "github.com/supabase/cli/internal/utils/flags"
15
        "github.com/supabase/cli/pkg/api"
16
        "github.com/supabase/cli/pkg/config"
17
        "github.com/supabase/cli/pkg/function"
18
)
19

20
func Run(ctx context.Context, slugs []string, useDocker bool, noVerifyJWT *bool, importMapPath string, maxJobs uint, prune bool, fsys afero.Fs) error {
7✔
21
        // Load function config and project id
7✔
22
        if err := flags.LoadConfig(fsys); err != nil {
7✔
23
                return err
×
24
        } else if len(slugs) > 0 {
11✔
25
                for _, s := range slugs {
9✔
26
                        if err := utils.ValidateFunctionSlug(s); err != nil {
6✔
27
                                return err
1✔
28
                        }
1✔
29
                }
30
        } else if slugs, err = GetFunctionSlugs(fsys); err != nil {
3✔
31
                return err
×
32
        }
×
33
        // TODO: require all functions to be deployed from config for v2
34
        if len(slugs) == 0 {
7✔
35
                return errors.Errorf("No Functions specified or found in %s", utils.Bold(utils.FunctionsDir))
1✔
36
        }
1✔
37
        // Flag import map is specified relative to current directory instead of workdir
38
        cwd, err := os.Getwd()
5✔
39
        if err != nil {
5✔
40
                return errors.Errorf("failed to get working directory: %w", err)
×
41
        }
×
42
        if len(importMapPath) > 0 {
5✔
43
                if !filepath.IsAbs(importMapPath) {
×
44
                        importMapPath = filepath.Join(utils.CurrentDirAbs, importMapPath)
×
45
                }
×
46
                if importMapPath, err = filepath.Rel(cwd, importMapPath); err != nil {
×
47
                        return errors.Errorf("failed to resolve relative path: %w", err)
×
48
                }
×
49
        }
50
        functionConfig, err := GetFunctionConfig(slugs, importMapPath, noVerifyJWT, fsys)
5✔
51
        if err != nil {
5✔
52
                return err
×
53
        }
×
54
        // Deploy new and updated functions
55
        opt := function.WithMaxJobs(maxJobs)
5✔
56
        if useDocker {
10✔
57
                if utils.IsDockerRunning(ctx) {
10✔
58
                        opt = function.WithBundler(NewDockerBundler(fsys))
5✔
59
                } else {
5✔
60
                        fmt.Fprintln(os.Stderr, utils.Yellow("WARNING:"), "Docker is not running")
×
61
                }
×
62
        }
63
        api := function.NewEdgeRuntimeAPI(flags.ProjectRef, *utils.GetSupabase(), opt)
5✔
64
        if err := api.Deploy(ctx, functionConfig, afero.NewIOFS(fsys)); errors.Is(err, function.ErrNoDeploy) {
5✔
65
                fmt.Fprintln(os.Stderr, err)
×
66
                return nil
×
67
        } else if err != nil {
5✔
68
                return err
×
69
        }
×
70
        fmt.Printf("Deployed Functions on project %s: %s\n", utils.Aqua(flags.ProjectRef), strings.Join(slugs, ", "))
5✔
71
        url := fmt.Sprintf("%s/project/%v/functions", utils.GetSupabaseDashboardURL(), flags.ProjectRef)
5✔
72
        fmt.Println("You can inspect your deployment in the Dashboard: " + url)
5✔
73
        if !prune {
10✔
74
                return nil
5✔
75
        }
5✔
76
        return pruneFunctions(ctx, functionConfig)
×
77
}
78

79
func GetFunctionSlugs(fsys afero.Fs) (slugs []string, err error) {
7✔
80
        pattern := filepath.Join(utils.FunctionsDir, "*", "index.ts")
7✔
81
        paths, err := afero.Glob(fsys, pattern)
7✔
82
        if err != nil {
7✔
83
                return nil, errors.Errorf("failed to glob function slugs: %w", err)
×
84
        }
×
85
        for _, path := range paths {
11✔
86
                slug := filepath.Base(filepath.Dir(path))
4✔
87
                if utils.FuncSlugPattern.MatchString(slug) {
7✔
88
                        slugs = append(slugs, slug)
3✔
89
                }
3✔
90
        }
91
        // Add all function slugs declared in config file
92
        for slug := range utils.Config.Functions {
15✔
93
                slugs = append(slugs, slug)
8✔
94
        }
8✔
95
        return slugs, nil
7✔
96
}
97

98
func GetFunctionConfig(slugs []string, importMapPath string, noVerifyJWT *bool, fsys afero.Fs) (config.FunctionConfig, error) {
14✔
99
        // Although some functions do not require import map, it's more convenient to setup
14✔
100
        // vscode deno extension with a single import map for all functions.
14✔
101
        fallbackExists := true
14✔
102
        functionsUsingDeprecatedGlobalFallback := []string{}
14✔
103
        functionsUsingDeprecatedImportMap := []string{}
14✔
104
        if _, err := fsys.Stat(utils.FallbackImportMapPath); errors.Is(err, os.ErrNotExist) {
23✔
105
                fallbackExists = false
9✔
106
        } else if err != nil {
14✔
107
                return nil, errors.Errorf("failed to fallback import map: %w", err)
×
108
        }
×
109
        functionConfig := make(config.FunctionConfig, len(slugs))
14✔
110
        for _, name := range slugs {
34✔
111
                function, ok := utils.Config.Functions[name]
20✔
112
                if !ok {
26✔
113
                        function.Enabled = true
6✔
114
                        function.VerifyJWT = true
6✔
115
                }
6✔
116
                // Precedence order: flag > config > fallback
117
                functionDir := filepath.Join(utils.FunctionsDir, name)
20✔
118
                if len(function.Entrypoint) == 0 {
28✔
119
                        function.Entrypoint = filepath.Join(functionDir, "index.ts")
8✔
120
                }
8✔
121
                if len(importMapPath) > 0 {
22✔
122
                        function.ImportMap = importMapPath
2✔
123
                } else if len(function.ImportMap) == 0 {
33✔
124
                        denoJsonPath := filepath.Join(functionDir, "deno.json")
13✔
125
                        denoJsoncPath := filepath.Join(functionDir, "deno.jsonc")
13✔
126
                        importMapPath := filepath.Join(functionDir, "import_map.json")
13✔
127
                        if _, err := fsys.Stat(denoJsonPath); err == nil {
13✔
128
                                function.ImportMap = denoJsonPath
×
129
                        } else if _, err := fsys.Stat(denoJsoncPath); err == nil {
13✔
130
                                function.ImportMap = denoJsoncPath
×
131
                        } else if _, err := fsys.Stat(importMapPath); err == nil {
13✔
132
                                function.ImportMap = importMapPath
×
133
                                functionsUsingDeprecatedImportMap = append(functionsUsingDeprecatedImportMap, name)
×
134
                        } else if fallbackExists {
16✔
135
                                function.ImportMap = utils.FallbackImportMapPath
3✔
136
                                functionsUsingDeprecatedGlobalFallback = append(functionsUsingDeprecatedGlobalFallback, name)
3✔
137
                        }
3✔
138
                }
139
                packageJsonPath := filepath.Join(functionDir, "package.json")
20✔
140
                packageJsonExists := false
20✔
141
                if _, err := fsys.Stat(packageJsonPath); err == nil {
20✔
NEW
142
                        packageJsonExists = true
×
NEW
143
                }
×
144
                function.UsePackageJson = len(function.ImportMap) == 0 && packageJsonExists
20✔
145
                if noVerifyJWT != nil {
24✔
146
                        function.VerifyJWT = !*noVerifyJWT
4✔
147
                }
4✔
148
                functionConfig[name] = function
20✔
149
        }
150
        if len(functionsUsingDeprecatedImportMap) > 0 {
14✔
151
                fmt.Fprintln(os.Stderr,
×
152
                        utils.Yellow("WARNING:"),
×
153
                        "Functions using deprecated import_map.json (please migrate to deno.json):",
×
154
                        utils.Aqua(strings.Join(functionsUsingDeprecatedImportMap, ", ")),
×
155
                )
×
156
        }
×
157
        if len(functionsUsingDeprecatedGlobalFallback) > 0 {
16✔
158
                fmt.Fprintln(os.Stderr,
2✔
159
                        utils.Yellow("WARNING:"),
2✔
160
                        "Functions using fallback import map:",
2✔
161
                        utils.Aqua(strings.Join(functionsUsingDeprecatedGlobalFallback, ", ")),
2✔
162
                )
2✔
163
                fmt.Fprintln(os.Stderr,
2✔
164
                        "Please use recommended per function dependency declaration ",
2✔
165
                        utils.Aqua("https://supabase.com/docs/guides/functions/import-maps"),
2✔
166
                )
2✔
167
        }
2✔
168
        return functionConfig, nil
14✔
169
}
170

171
// pruneFunctions deletes functions that exist remotely but not locally
172
func pruneFunctions(ctx context.Context, functionConfig config.FunctionConfig) error {
3✔
173
        resp, err := utils.GetSupabase().V1ListAllFunctionsWithResponse(ctx, flags.ProjectRef)
3✔
174
        if err != nil {
3✔
175
                return errors.Errorf("failed to list functions: %w", err)
×
176
        } else if resp.JSON200 == nil {
3✔
177
                return errors.Errorf("unexpected list functions status %d: %s", resp.StatusCode(), string(resp.Body))
×
178
        }
×
179
        // No need to delete disabled functions
180
        var toDelete []string
3✔
181
        for _, deployed := range *resp.JSON200 {
13✔
182
                if deployed.Status == api.FunctionResponseStatusREMOVED {
12✔
183
                        continue
2✔
184
                } else if _, exists := functionConfig[deployed.Slug]; exists {
13✔
185
                        continue
5✔
186
                }
187
                toDelete = append(toDelete, deployed.Slug)
3✔
188
        }
189
        if len(toDelete) == 0 {
4✔
190
                fmt.Fprintln(os.Stderr, "No Functions to prune.")
1✔
191
                return nil
1✔
192
        }
1✔
193
        // Confirm before pruning functions
194
        msg := fmt.Sprintln(confirmPruneAll(toDelete))
2✔
195
        if shouldDelete, err := utils.NewConsole().PromptYesNo(ctx, msg, false); err != nil {
2✔
196
                return err
×
197
        } else if !shouldDelete {
2✔
198
                return errors.New(context.Canceled)
×
199
        }
×
200
        for _, slug := range toDelete {
5✔
201
                fmt.Fprintln(os.Stderr, "Deleting Function:", slug)
3✔
202
                if err := delete.Undeploy(ctx, flags.ProjectRef, slug); errors.Is(err, delete.ErrNoDelete) {
4✔
203
                        fmt.Fprintln(utils.GetDebugLogger(), err)
1✔
204
                } else if err != nil {
3✔
205
                        return err
×
206
                }
×
207
        }
208
        return nil
2✔
209
}
210

211
func confirmPruneAll(pending []string) string {
2✔
212
        msg := fmt.Sprintln("Do you want to delete the following Functions from your project?")
2✔
213
        for _, slug := range pending {
5✔
214
                msg += fmt.Sprintf(" • %s\n", utils.Bold(slug))
3✔
215
        }
3✔
216
        return msg
2✔
217
}
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