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

supabase / cli / 23224184388

18 Mar 2026 01:10AM UTC coverage: 60.017% (-1.8%) from 61.863%
23224184388

Pull #4970

github

web-flow
Merge 61991c119 into 24b7304bd
Pull Request #4970: feat: add apple-container runtime support

829 of 1660 new or added lines in 15 files covered. (49.94%)

8 existing lines in 3 files now uncovered.

8607 of 14341 relevant lines covered (60.02%)

7.63 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
        "syscall"
13
        "time"
14

15
        "github.com/getsentry/sentry-go"
16
        "github.com/go-errors/errors"
17
        "github.com/spf13/afero"
18
        "github.com/spf13/cobra"
19
        "github.com/spf13/viper"
20
        "github.com/supabase/cli/internal/debug"
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
}
64

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

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

85
        createTicket bool
86

87
        rootCmd = &cobra.Command{
88
                Use:     "supabase",
89
                Short:   "Supabase CLI " + utils.Version,
90
                Version: utils.Version,
91
                PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
×
92
                        if IsExperimental(cmd) && !viper.GetBool("EXPERIMENTAL") {
×
93
                                return errors.New("must set the --experimental flag to run this command")
×
94
                        }
×
95
                        cmd.SilenceUsage = true
×
96
                        // Load profile before changing workdir
×
NEW
97
                        ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt, syscall.SIGTERM)
×
98
                        fsys := afero.NewOsFs()
×
99
                        if err := utils.LoadProfile(ctx, fsys); err != nil {
×
100
                                return err
×
101
                        }
×
102
                        if err := utils.ChangeWorkDir(fsys); err != nil {
×
103
                                return err
×
104
                        }
×
105
                        // Add common flags
106
                        if IsManagementAPI(cmd) {
×
107
                                if err := promptLogin(fsys); err != nil {
×
108
                                        return err
×
109
                                }
×
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 semver.Compare(version, "v"+utils.Version) > 0 {
×
149
                fmt.Fprintln(os.Stderr, suggestUpgrade(version))
×
150
        }
×
151
        if len(utils.CmdSuggestion) > 0 {
×
152
                fmt.Fprintln(os.Stderr, utils.CmdSuggestion)
×
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
                }
×
NEW
206
                if e, ok := err.(*errors.Error); ok && viper.GetBool("DEBUG") {
×
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")
×
NEW
243
        flags.String("runtime", "", "container runtime for local development (docker|apple-container)")
×
244
        flags.String("profile", "supabase", "use a specific profile for connecting to Supabase API")
×
245
        flags.VarP(&utils.OutputFormat, "output", "o", "output format of status variables")
×
246
        flags.Var(&utils.DNSResolver, "dns-resolver", "lookup domain names using the specified resolver")
×
247
        flags.BoolVar(&createTicket, "create-ticket", false, "create a support ticket for any CLI error")
×
248
        flags.VarP(&utils.AgentMode, "agent", "", "Override agent detection: yes, no, or auto (default auto)")
×
249
        cobra.CheckErr(viper.BindPFlags(flags))
×
250

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

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

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