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

supabase / cli / 16976524820

14 Aug 2025 08:57PM UTC coverage: 54.808% (-0.5%) from 55.271%
16976524820

Pull #3969

github

web-flow
Merge 419dd46ad into b3d509dd5
Pull Request #3969: feat: generate jwt tokens from signing key

11 of 13 new or added lines in 1 file covered. (84.62%)

391 existing lines in 14 files now uncovered.

6196 of 11305 relevant lines covered (54.81%)

6.09 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

UNCOV
64
func IsExperimental(cmd *cobra.Command) bool {
×
65
        for _, exp := range experimental {
×
66
                if cmd == exp || cmd.Parent() == exp {
×
67
                        return true
×
68
                }
×
69
        }
UNCOV
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,
UNCOV
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
                        // Change workdir
×
96
                        fsys := afero.NewOsFs()
×
97
                        if err := utils.ChangeWorkDir(fsys); err != nil {
×
98
                                return err
×
99
                        }
×
100
                        // Add common flags
UNCOV
101
                        ctx := cmd.Context()
×
102
                        if IsManagementAPI(cmd) {
×
103
                                if err := promptLogin(fsys); err != nil {
×
104
                                        return err
×
105
                                }
×
106
                                ctx, _ = signal.NotifyContext(ctx, os.Interrupt)
×
107
                                if cmd.Flags().Lookup("project-ref") != nil {
×
108
                                        if err := flags.ParseProjectRef(ctx, fsys); err != nil {
×
109
                                                return err
×
110
                                        }
×
111
                                }
112
                        }
UNCOV
113
                        if err := flags.ParseDatabaseConfig(ctx, cmd.Flags(), fsys); err != nil {
×
114
                                return err
×
115
                        }
×
116
                        // Prepare context
UNCOV
117
                        if viper.GetBool("DEBUG") {
×
118
                                http.DefaultTransport = debug.NewTransport()
×
119
                                fmt.Fprintln(os.Stderr, cmd.Root().Short)
×
120
                        }
×
121
                        cmd.SetContext(ctx)
×
122
                        // Setup sentry last to ignore errors from parsing cli flags
×
123
                        apiHost, err := url.Parse(utils.GetSupabaseAPIHost())
×
124
                        if err != nil {
×
125
                                return err
×
126
                        }
×
127
                        sentryOpts.Environment = apiHost.Host
×
128
                        return sentry.Init(sentryOpts)
×
129
                },
130
                SilenceErrors: true,
131
        }
132
)
133

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

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

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

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

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

UNCOV
226
func init() {
×
227
        cobra.OnInitialize(func() {
×
228
                viper.SetEnvPrefix("SUPABASE")
×
229
                viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
×
230
                viper.AutomaticEnv()
×
231
        })
×
232

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

×
244
        rootCmd.SetVersionTemplate("{{.Version}}\n")
×
245
        rootCmd.AddGroup(&cobra.Group{ID: groupQuickStart, Title: "Quick Start:"})
×
246
        rootCmd.AddGroup(&cobra.Group{ID: groupLocalDev, Title: "Local Development:"})
×
247
        rootCmd.AddGroup(&cobra.Group{ID: groupManagementAPI, Title: "Management APIs:"})
×
248
}
249

250
// instantiate new rootCmd is a bit tricky with cobra, but it can be done later with the following
251
// approach for example: https://github.com/portworx/pxc/tree/master/cmd
UNCOV
252
func GetRootCmd() *cobra.Command {
×
253
        return rootCmd
×
254
}
×
255

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