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

supabase / cli / 18668903542

21 Oct 2025 12:31AM UTC coverage: 54.524% (-0.08%) from 54.604%
18668903542

Pull #4320

github

web-flow
Merge 498644fbe into ec88366e1
Pull Request #4320: show api keys with deprecated flag

1 of 9 new or added lines in 3 files covered. (11.11%)

5 existing lines in 1 file now uncovered.

6381 of 11703 relevant lines covered (54.52%)

6.07 hits per line

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

0.0
/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/utils"
21
        "github.com/supabase/cli/internal/utils/flags"
22
        "golang.org/x/mod/semver"
23
)
24

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

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

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

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

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

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

84
        createTicket bool
85

86
        rootCmd = &cobra.Command{
87
                Use:     "supabase",
88
                Short:   "Supabase CLI " + utils.Version,
89
                Version: utils.Version,
90
                PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
×
91
                        if IsExperimental(cmd) && !viper.GetBool("EXPERIMENTAL") {
×
92
                                return errors.New("must set the --experimental flag to run this command")
×
93
                        }
×
94
                        cmd.SilenceUsage = true
×
95
                        // Load profile before changing workdir
×
96
                        ctx := cmd.Context()
×
97
                        fsys := afero.NewOsFs()
×
98
                        if err := utils.LoadProfile(ctx, fsys); err != nil {
×
99
                                return err
×
100
                        }
×
101
                        if err := utils.ChangeWorkDir(fsys); err != nil {
×
102
                                return err
×
103
                        }
×
104
                        // Add common flags
105
                        if IsManagementAPI(cmd) {
×
106
                                if err := promptLogin(fsys); err != nil {
×
107
                                        return err
×
108
                                }
×
109
                                ctx, _ = signal.NotifyContext(ctx, os.Interrupt)
×
110
                                if cmd.Flags().Lookup("project-ref") != nil {
×
111
                                        if err := flags.ParseProjectRef(ctx, fsys); err != nil {
×
112
                                                return err
×
113
                                        }
×
114
                                }
115
                        }
116
                        if err := flags.ParseDatabaseConfig(ctx, cmd.Flags(), fsys); err != nil {
×
117
                                return err
×
118
                        }
×
119
                        // Prepare context
120
                        if viper.GetBool("DEBUG") {
×
121
                                http.DefaultTransport = debug.NewTransport()
×
122
                                fmt.Fprintln(os.Stderr, cmd.Root().Short)
×
123
                                fmt.Fprintf(os.Stderr, "Using profile: %s (%s)\n", utils.CurrentProfile.Name, utils.CurrentProfile.ProjectHost)
×
124
                        }
×
125
                        cmd.SetContext(ctx)
×
126
                        // Setup sentry last to ignore errors from parsing cli flags
×
127
                        apiHost, err := url.Parse(utils.GetSupabaseAPIHost())
×
128
                        if err != nil {
×
129
                                return err
×
130
                        }
×
131
                        sentryOpts.Environment = apiHost.Host
×
132
                        return sentry.Init(sentryOpts)
×
133
                },
134
                SilenceErrors: true,
135
        }
136
)
137

138
func Execute() {
×
139
        defer recoverAndExit()
×
140
        if err := rootCmd.Execute(); err != nil {
×
141
                panic(err)
×
142
        }
143
        // Check upgrade last because --version flag is initialised after execute
144
        version, err := checkUpgrade(rootCmd.Context(), afero.NewOsFs())
×
145
        if err != nil {
×
146
                fmt.Fprintln(utils.GetDebugLogger(), err)
×
147
        }
×
148
        if len(utils.CmdSuggestion) > 0 {
×
149
                fmt.Fprintln(os.Stderr, utils.CmdSuggestion)
×
150
        }
×
NEW
151
        if semver.Compare(version, "v"+utils.Version) > 0 {
×
NEW
152
                fmt.Fprintln(os.Stderr, suggestUpgrade(version))
×
NEW
153
        }
×
154
}
155

156
func checkUpgrade(ctx context.Context, fsys afero.Fs) (string, error) {
×
157
        if shouldFetchRelease(fsys) {
×
158
                version, err := utils.GetLatestRelease(ctx)
×
159
                if exists, _ := afero.DirExists(fsys, utils.SupabaseDirPath); exists {
×
160
                        // If user is offline, write an empty file to skip subsequent checks
×
161
                        err = utils.WriteFile(utils.CliVersionPath, []byte(version), fsys)
×
162
                }
×
163
                return version, err
×
164
        }
165
        version, err := afero.ReadFile(fsys, utils.CliVersionPath)
×
166
        if err != nil {
×
167
                return "", errors.Errorf("failed to read cli version: %w", err)
×
168
        }
×
169
        return string(version), nil
×
170
}
171

172
func shouldFetchRelease(fsys afero.Fs) bool {
×
173
        // Always fetch latest release when using --version flag
×
174
        if vf := rootCmd.Flag("version"); vf != nil && vf.Changed {
×
175
                return true
×
176
        }
×
177
        if fi, err := fsys.Stat(utils.CliVersionPath); err == nil {
×
178
                expiry := fi.ModTime().Add(time.Hour * 10)
×
179
                // Skip if last checked is less than 10 hours ago
×
180
                return time.Now().After(expiry)
×
181
        }
×
182
        return true
×
183
}
184

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

191
func recoverAndExit() {
×
192
        err := recover()
×
193
        if err == nil {
×
194
                return
×
195
        }
×
196
        var msg string
×
197
        switch err := err.(type) {
×
198
        case string:
×
199
                msg = err
×
200
        case error:
×
201
                if !errors.Is(err, context.Canceled) &&
×
202
                        len(utils.CmdSuggestion) == 0 &&
×
203
                        !viper.GetBool("DEBUG") {
×
204
                        utils.CmdSuggestion = utils.SuggestDebugFlag
×
205
                }
×
206
                if e, ok := err.(*errors.Error); ok && len(utils.Version) == 0 {
×
207
                        fmt.Fprintln(os.Stderr, string(e.Stack()))
×
208
                }
×
209
                msg = err.Error()
×
210
        default:
×
211
                msg = fmt.Sprintf("%#v", err)
×
212
        }
213
        // Log error to console
214
        fmt.Fprintln(os.Stderr, utils.Red(msg))
×
215
        if len(utils.CmdSuggestion) > 0 {
×
216
                fmt.Fprintln(os.Stderr, utils.CmdSuggestion)
×
217
        }
×
218
        // Report error to sentry
219
        if createTicket && len(utils.SentryDsn) > 0 {
×
220
                sentry.ConfigureScope(addSentryScope)
×
221
                eventId := sentry.CurrentHub().Recover(err)
×
222
                if eventId != nil && sentry.Flush(2*time.Second) {
×
223
                        fmt.Fprintln(os.Stderr, "Sent crash report:", *eventId)
×
224
                        fmt.Fprintln(os.Stderr, "Quote the crash ID above when filing a bug report: https://github.com/supabase/cli/issues/new/choose")
×
225
                }
×
226
        }
227
        os.Exit(1)
×
228
}
229

230
func init() {
×
231
        cobra.OnInitialize(func() {
×
232
                viper.SetEnvPrefix("SUPABASE")
×
233
                viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
×
234
                viper.AutomaticEnv()
×
235
        })
×
236

237
        flags := rootCmd.PersistentFlags()
×
238
        flags.Bool("yes", false, "answer yes to all prompts")
×
239
        flags.Bool("debug", false, "output debug logs to stderr")
×
240
        flags.String("workdir", "", "path to a Supabase project directory")
×
241
        flags.Bool("experimental", false, "enable experimental features")
×
242
        flags.String("network-id", "", "use the specified docker network instead of a generated one")
×
243
        flags.String("profile", "supabase", "use a specific profile for connecting to Supabase API")
×
244
        flags.VarP(&utils.OutputFormat, "output", "o", "output format of status variables")
×
245
        flags.Var(&utils.DNSResolver, "dns-resolver", "lookup domain names using the specified resolver")
×
246
        flags.BoolVar(&createTicket, "create-ticket", false, "create a support ticket for any CLI error")
×
247
        cobra.CheckErr(viper.BindPFlags(flags))
×
248

×
249
        rootCmd.SetVersionTemplate("{{.Version}}\n")
×
250
        rootCmd.AddGroup(&cobra.Group{ID: groupQuickStart, Title: "Quick Start:"})
×
251
        rootCmd.AddGroup(&cobra.Group{ID: groupLocalDev, Title: "Local Development:"})
×
252
        rootCmd.AddGroup(&cobra.Group{ID: groupManagementAPI, Title: "Management APIs:"})
×
253
}
254

255
// instantiate new rootCmd is a bit tricky with cobra, but it can be done later with the following
256
// approach for example: https://github.com/portworx/pxc/tree/master/cmd
257
func GetRootCmd() *cobra.Command {
×
258
        return rootCmd
×
259
}
×
260

261
func addSentryScope(scope *sentry.Scope) {
×
262
        serviceImages := utils.Config.GetServiceImages()
×
263
        imageToVersion := make(map[string]any, len(serviceImages))
×
264
        for _, image := range serviceImages {
×
265
                parts := strings.Split(image, ":")
×
266
                // Bypasses sentry's IP sanitization rule, ie. 15.1.0.147
×
267
                if net.ParseIP(parts[1]) != nil {
×
268
                        imageToVersion[parts[0]] = "v" + parts[1]
×
269
                } else {
×
270
                        imageToVersion[parts[0]] = parts[1]
×
271
                }
×
272
        }
273
        scope.SetContext("Services", imageToVersion)
×
274
        scope.SetContext("Config", map[string]any{
×
275
                "Image Registry": utils.GetRegistry(),
×
276
                "Project ID":     flags.ProjectRef,
×
277
        })
×
278
}
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