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

supabase / cli / 24134227371

08 Apr 2026 12:01PM UTC coverage: 63.788%. First build
24134227371

Pull #5010

github

web-flow
Merge 03bac0987 into d03a45bc0
Pull Request #5010: Prod deploy

595 of 825 new or added lines in 28 files covered. (72.12%)

9778 of 15329 relevant lines covered (63.79%)

6.89 hits per line

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

8.84
/cmd/root.go
1
package cmd
2

3
import (
4
        "context"
5
        "fmt"
6
        "net"
7
        "net/http"
8
        "net/url"
9
        "os"
10
        "os/signal"
11
        "strings"
12
        "time"
13

14
        "github.com/getsentry/sentry-go"
15
        "github.com/go-errors/errors"
16
        "github.com/spf13/afero"
17
        "github.com/spf13/cobra"
18
        "github.com/spf13/viper"
19
        "github.com/supabase/cli/internal/debug"
20
        "github.com/supabase/cli/internal/telemetry"
21
        "github.com/supabase/cli/internal/utils"
22
        "github.com/supabase/cli/internal/utils/flags"
23
        "golang.org/x/mod/semver"
24
)
25

26
const (
27
        groupQuickStart    = "quick-start"
28
        groupLocalDev      = "local-dev"
29
        groupManagementAPI = "management-api"
30
)
31

32
func IsManagementAPI(cmd *cobra.Command) bool {
×
33
        for cmd != cmd.Root() {
×
34
                if cmd.GroupID == groupManagementAPI {
×
35
                        return true
×
36
                }
×
37
                // Find the last assigned group
38
                if len(cmd.GroupID) > 0 {
×
39
                        break
×
40
                }
41
                cmd = cmd.Parent()
×
42
        }
43
        return false
×
44
}
45

46
func promptLogin(fsys afero.Fs) error {
×
47
        if _, err := utils.LoadAccessTokenFS(fsys); err == utils.ErrMissingToken {
×
48
                utils.CmdSuggestion = fmt.Sprintf("Run %s first.", utils.Aqua("supabase login"))
×
49
                return errors.New("You need to be logged-in in order to use Management API commands.")
×
50
        } else {
×
51
                return err
×
52
        }
×
53
}
54

55
var experimental = []*cobra.Command{
56
        bansCmd,
57
        restrictionsCmd,
58
        vanityCmd,
59
        sslEnforcementCmd,
60
        genKeysCmd,
61
        postgresCmd,
62
        storageCmd,
63
        dbDeclarativeCmd,
64
}
65

66
func IsExperimental(cmd *cobra.Command) bool {
×
67
        for _, exp := range experimental {
×
68
                if cmd == exp || cmd.Parent() == exp {
×
69
                        return true
×
70
                }
×
71
        }
72
        return false
×
73
}
74

75
var (
76
        sentryOpts = sentry.ClientOptions{
77
                Dsn:        utils.SentryDsn,
78
                Release:    utils.Version,
79
                ServerName: "<redacted>",
80
                // Set TracesSampleRate to 1.0 to capture 100%
81
                // of transactions for performance monitoring.
82
                // We recommend adjusting this value in production,
83
                TracesSampleRate: 1.0,
84
        }
85

86
        createTicket bool
87

88
        rootCmd = &cobra.Command{
89
                Use:     "supabase",
90
                Short:   "Supabase CLI " + utils.Version,
91
                Version: utils.Version,
92
                PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
×
93
                        if IsExperimental(cmd) && !viper.GetBool("EXPERIMENTAL") {
×
94
                                return errors.New("must set the --experimental flag to run this command")
×
95
                        }
×
96
                        cmd.SilenceUsage = true
×
97
                        // Load profile before changing workdir
×
98
                        ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
×
99
                        fsys := afero.NewOsFs()
×
100
                        if err := utils.LoadProfile(ctx, fsys); err != nil {
×
101
                                return err
×
102
                        }
×
103
                        if err := utils.ChangeWorkDir(fsys); err != nil {
×
104
                                return err
×
105
                        }
×
106
                        // Add common flags
107
                        if IsManagementAPI(cmd) {
×
108
                                if err := promptLogin(fsys); err != nil {
×
109
                                        return err
×
110
                                }
×
111
                                if cmd.Flags().Lookup("project-ref") != nil {
×
112
                                        if err := flags.ParseProjectRef(ctx, fsys); err != nil {
×
113
                                                return err
×
114
                                        }
×
115
                                }
116
                        }
117
                        if err := flags.ParseDatabaseConfig(ctx, cmd.Flags(), fsys); err != nil {
×
118
                                return err
×
119
                        }
×
120
                        // Prepare context
121
                        if viper.GetBool("DEBUG") {
×
122
                                http.DefaultTransport = debug.NewTransport()
×
123
                                fmt.Fprintln(os.Stderr, cmd.Root().Short)
×
124
                                fmt.Fprintf(os.Stderr, "Using profile: %s (%s)\n", utils.CurrentProfile.Name, utils.CurrentProfile.ProjectHost)
×
125
                        }
×
NEW
126
                        isTTY := telemetryIsTTY()
×
NEW
127
                        isCI := telemetryIsCI()
×
NEW
128
                        isAgent := telemetryIsAgent()
×
NEW
129
                        envSignals := telemetryEnvSignals()
×
NEW
130
                        service, err := telemetry.NewService(fsys, telemetry.Options{
×
NEW
131
                                Now:        time.Now,
×
NEW
132
                                IsTTY:      isTTY,
×
NEW
133
                                IsCI:       isCI,
×
NEW
134
                                IsAgent:    isAgent,
×
NEW
135
                                EnvSignals: envSignals,
×
NEW
136
                                CLIName:    utils.Version,
×
NEW
137
                        })
×
NEW
138
                        if err != nil {
×
NEW
139
                                fmt.Fprintln(utils.GetDebugLogger(), err)
×
NEW
140
                        } else {
×
NEW
141
                                ctx = telemetry.WithService(ctx, service)
×
NEW
142
                        }
×
NEW
143
                        ctx = telemetry.WithCommandContext(ctx, commandAnalyticsContext(cmd))
×
144
                        cmd.SetContext(ctx)
×
145
                        // Setup sentry last to ignore errors from parsing cli flags
×
146
                        apiHost, err := url.Parse(utils.GetSupabaseAPIHost())
×
147
                        if err != nil {
×
148
                                return err
×
149
                        }
×
150
                        sentryOpts.Environment = apiHost.Host
×
151
                        return sentry.Init(sentryOpts)
×
152
                },
153
                SilenceErrors: true,
154
        }
155
)
156

157
func Execute() {
×
158
        defer recoverAndExit()
×
NEW
159
        startedAt := time.Now()
×
NEW
160
        executedCmd, err := rootCmd.ExecuteC()
×
NEW
161
        if executedCmd != nil {
×
NEW
162
                if service := telemetry.FromContext(executedCmd.Context()); service != nil {
×
NEW
163
                        _ = service.Capture(executedCmd.Context(), telemetry.EventCommandExecuted, map[string]any{
×
NEW
164
                                telemetry.PropExitCode:   exitCode(err),
×
NEW
165
                                telemetry.PropDurationMs: time.Since(startedAt).Milliseconds(),
×
NEW
166
                        }, nil)
×
NEW
167
                        _ = service.Close()
×
NEW
168
                }
×
169
        }
NEW
170
        if err != nil {
×
171
                panic(err)
×
172
        }
173
        // Check upgrade last because --version flag is initialised after execute
NEW
174
        ctx := rootCmd.Context()
×
NEW
175
        if executedCmd != nil {
×
NEW
176
                ctx = executedCmd.Context()
×
NEW
177
        }
×
NEW
178
        version, err := checkUpgrade(ctx, afero.NewOsFs())
×
179
        if err != nil {
×
180
                fmt.Fprintln(utils.GetDebugLogger(), err)
×
181
        }
×
182
        if semver.Compare(version, "v"+utils.Version) > 0 {
×
183
                fmt.Fprintln(os.Stderr, suggestUpgrade(version))
×
184
        }
×
185
        if len(utils.CmdSuggestion) > 0 {
×
186
                fmt.Fprintln(os.Stderr, utils.CmdSuggestion)
×
187
        }
×
188
}
189

NEW
190
func exitCode(err error) int {
×
NEW
191
        if err != nil {
×
NEW
192
                return 1
×
NEW
193
        }
×
NEW
194
        return 0
×
195
}
196

197
func checkUpgrade(ctx context.Context, fsys afero.Fs) (string, error) {
×
198
        if shouldFetchRelease(fsys) {
×
199
                version, err := utils.GetLatestRelease(ctx)
×
200
                if exists, _ := afero.DirExists(fsys, utils.SupabaseDirPath); exists {
×
201
                        // If user is offline, write an empty file to skip subsequent checks
×
202
                        err = utils.WriteFile(utils.CliVersionPath, []byte(version), fsys)
×
203
                }
×
204
                return version, err
×
205
        }
206
        version, err := afero.ReadFile(fsys, utils.CliVersionPath)
×
207
        if err != nil {
×
208
                return "", errors.Errorf("failed to read cli version: %w", err)
×
209
        }
×
210
        return string(version), nil
×
211
}
212

213
func shouldFetchRelease(fsys afero.Fs) bool {
×
214
        // Always fetch latest release when using --version flag
×
215
        if vf := rootCmd.Flag("version"); vf != nil && vf.Changed {
×
216
                return true
×
217
        }
×
218
        if fi, err := fsys.Stat(utils.CliVersionPath); err == nil {
×
219
                expiry := fi.ModTime().Add(time.Hour * 10)
×
220
                // Skip if last checked is less than 10 hours ago
×
221
                return time.Now().After(expiry)
×
222
        }
×
223
        return true
×
224
}
225

226
func suggestUpgrade(version string) string {
×
227
        const guide = "https://supabase.com/docs/guides/cli/getting-started#updating-the-supabase-cli"
×
228
        return fmt.Sprintf(`A new version of Supabase CLI is available: %s (currently installed v%s)
×
229
We recommend updating regularly for new features and bug fixes: %s`, utils.Yellow(version), utils.Version, utils.Bold(guide))
×
230
}
×
231

232
func recoverAndExit() {
×
233
        err := recover()
×
234
        if err == nil {
×
235
                return
×
236
        }
×
237
        var msg string
×
238
        switch err := err.(type) {
×
239
        case string:
×
240
                msg = err
×
241
        case error:
×
242
                if !errors.Is(err, context.Canceled) &&
×
243
                        len(utils.CmdSuggestion) == 0 &&
×
244
                        !viper.GetBool("DEBUG") {
×
245
                        utils.CmdSuggestion = utils.SuggestDebugFlag
×
246
                }
×
247
                if e, ok := err.(*errors.Error); ok && len(utils.Version) == 0 {
×
248
                        fmt.Fprintln(os.Stderr, string(e.Stack()))
×
249
                }
×
250
                msg = err.Error()
×
251
        default:
×
252
                msg = fmt.Sprintf("%#v", err)
×
253
        }
254
        // Log error to console
255
        fmt.Fprintln(os.Stderr, utils.Red(msg))
×
256
        if len(utils.CmdSuggestion) > 0 {
×
257
                fmt.Fprintln(os.Stderr, utils.CmdSuggestion)
×
258
        }
×
259
        // Report error to sentry
260
        if createTicket && len(utils.SentryDsn) > 0 {
×
261
                sentry.ConfigureScope(addSentryScope)
×
262
                eventId := sentry.CurrentHub().Recover(err)
×
263
                if eventId != nil && sentry.Flush(2*time.Second) {
×
264
                        fmt.Fprintln(os.Stderr, "Sent crash report:", *eventId)
×
265
                        fmt.Fprintln(os.Stderr, "Quote the crash ID above when filing a bug report: https://github.com/supabase/cli/issues/new/choose")
×
266
                }
×
267
        }
268
        os.Exit(1)
×
269
}
270

271
func init() {
1✔
272
        cobra.OnInitialize(func() {
1✔
273
                viper.SetEnvPrefix("SUPABASE")
×
274
                viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
×
275
                viper.AutomaticEnv()
×
276
        })
×
277

278
        flags := rootCmd.PersistentFlags()
1✔
279
        flags.Bool("yes", false, "answer yes to all prompts")
1✔
280
        flags.Bool("debug", false, "output debug logs to stderr")
1✔
281
        flags.String("workdir", "", "path to a Supabase project directory")
1✔
282
        flags.Bool("experimental", false, "enable experimental features")
1✔
283
        flags.String("network-id", "", "use the specified docker network instead of a generated one")
1✔
284
        flags.String("profile", "supabase", "use a specific profile for connecting to Supabase API")
1✔
285
        flags.VarP(&utils.OutputFormat, "output", "o", "output format of status variables")
1✔
286
        flags.Var(&utils.DNSResolver, "dns-resolver", "lookup domain names using the specified resolver")
1✔
287
        flags.BoolVar(&createTicket, "create-ticket", false, "create a support ticket for any CLI error")
1✔
288
        flags.VarP(&utils.AgentMode, "agent", "", "Override agent detection: yes, no, or auto (default auto)")
1✔
289
        cobra.CheckErr(viper.BindPFlags(flags))
1✔
290

1✔
291
        rootCmd.SetVersionTemplate("{{.Version}}\n")
1✔
292
        rootCmd.AddGroup(&cobra.Group{ID: groupQuickStart, Title: "Quick Start:"})
1✔
293
        rootCmd.AddGroup(&cobra.Group{ID: groupLocalDev, Title: "Local Development:"})
1✔
294
        rootCmd.AddGroup(&cobra.Group{ID: groupManagementAPI, Title: "Management APIs:"})
1✔
295
}
296

297
// instantiate new rootCmd is a bit tricky with cobra, but it can be done later with the following
298
// approach for example: https://github.com/portworx/pxc/tree/master/cmd
299
func GetRootCmd() *cobra.Command {
×
300
        return rootCmd
×
301
}
×
302

303
func addSentryScope(scope *sentry.Scope) {
×
304
        serviceImages := utils.Config.GetServiceImages()
×
305
        imageToVersion := make(map[string]any, len(serviceImages))
×
306
        for _, image := range serviceImages {
×
307
                parts := strings.Split(image, ":")
×
308
                // Bypasses sentry's IP sanitization rule, ie. 15.1.0.147
×
309
                if net.ParseIP(parts[1]) != nil {
×
310
                        imageToVersion[parts[0]] = "v" + parts[1]
×
311
                } else {
×
312
                        imageToVersion[parts[0]] = parts[1]
×
313
                }
×
314
        }
315
        scope.SetContext("Services", imageToVersion)
×
316
        scope.SetContext("Config", map[string]any{
×
317
                "Image Registry": utils.GetRegistry(),
×
318
                "Project ID":     flags.ProjectRef,
×
319
        })
×
320
}
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