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

supabase / cli / 11389178479

17 Oct 2024 04:32PM UTC coverage: 59.748% (+0.03%) from 59.72%
11389178479

Pull #2777

github

sweatybridge
chore: remove unused string replacer
Pull Request #2777: fix: squash base config with mapstructure

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

5 existing lines in 1 file now uncovered.

6356 of 10638 relevant lines covered (59.75%)

5.99 hits per line

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

11.24
/cmd/root.go
1
package cmd
2

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

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

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

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

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

53
var experimental = []*cobra.Command{
54
        bansCmd,
55
        restrictionsCmd,
56
        vanityCmd,
57
        sslEnforcementCmd,
58
        genKeysCmd,
59
        postgresCmd,
60
        branchesCmd,
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
                        // Change workdir
×
96
                        fsys := afero.NewOsFs()
×
97
                        if err := utils.ChangeWorkDir(fsys); err != nil {
×
98
                                return err
×
99
                        }
×
100
                        // Add common flags
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
                        }
113
                        if err := flags.ParseDatabaseConfig(cmd.Flags(), fsys); err != nil {
×
114
                                return err
×
115
                        }
×
116
                        // Prepare context
117
                        if viper.GetBool("DEBUG") {
×
118
                                ctx = utils.WithTraceContext(ctx)
×
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

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
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

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
        }
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

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

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

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
                msg = err.Error()
×
203
        default:
×
204
                msg = fmt.Sprintf("%#v", err)
×
205
        }
206
        // Log error to console
207
        fmt.Fprintln(os.Stderr, utils.Red(msg))
×
208
        if len(utils.CmdSuggestion) > 0 {
×
209
                fmt.Fprintln(os.Stderr, utils.CmdSuggestion)
×
210
        }
×
211
        // Report error to sentry
212
        if createTicket && len(utils.SentryDsn) > 0 {
×
213
                sentry.ConfigureScope(addSentryScope)
×
214
                eventId := sentry.CurrentHub().Recover(err)
×
215
                if eventId != nil && sentry.Flush(2*time.Second) {
×
216
                        fmt.Fprintln(os.Stderr, "Sent crash report:", *eventId)
×
217
                        fmt.Fprintln(os.Stderr, "Quote the crash ID above when filing a bug report: https://github.com/supabase/cli/issues/new/choose")
×
218
                }
×
219
        }
220
        os.Exit(1)
×
221
}
222

223
func init() {
1✔
224
        cobra.OnInitialize(func() {
1✔
225
                viper.SetEnvPrefix("SUPABASE")
×
NEW
226
                viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
×
227
                viper.AutomaticEnv()
×
228
        })
×
229

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

1✔
240
        rootCmd.SetVersionTemplate("{{.Version}}\n")
1✔
241
        rootCmd.AddGroup(&cobra.Group{ID: groupQuickStart, Title: "Quick Start:"})
1✔
242
        rootCmd.AddGroup(&cobra.Group{ID: groupLocalDev, Title: "Local Development:"})
1✔
243
        rootCmd.AddGroup(&cobra.Group{ID: groupManagementAPI, Title: "Management APIs:"})
1✔
244
}
245

246
// instantiate new rootCmd is a bit tricky with cobra, but it can be done later with the following
247
// approach for example: https://github.com/portworx/pxc/tree/master/cmd
248
func GetRootCmd() *cobra.Command {
6✔
249
        return rootCmd
6✔
250
}
6✔
251

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