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

supabase / cli / 23898594452

02 Apr 2026 11:39AM UTC coverage: 63.607%. First build
23898594452

Pull #5019

github

web-flow
Merge 2345c8405 into 05d65b6d4
Pull Request #5019: feat: add posthog telemetry

462 of 643 new or added lines in 18 files covered. (71.85%)

9681 of 15220 relevant lines covered (63.61%)

6.85 hits per line

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

80.16
/cmd/root_analytics.go
1
package cmd
2

3
import (
4
        "os"
5
        "sort"
6
        "strconv"
7
        "strings"
8

9
        "github.com/google/uuid"
10
        "github.com/spf13/cobra"
11
        "github.com/spf13/pflag"
12
        "github.com/supabase/cli/internal/telemetry"
13
        "github.com/supabase/cli/internal/utils"
14
        "github.com/supabase/cli/internal/utils/agent"
15
        "golang.org/x/term"
16
)
17

18
const (
19
        telemetrySafeValueAnnotation = "supabase.com/telemetry-safe-value"
20
        redactedTelemetryValue       = "<redacted>"
21
        maxTelemetryEnvValueLength   = 80
22
)
23

24
var telemetryEnvPresenceVars = []string{
25
        // AI tools signals
26
        "CURSOR_AGENT",
27
        "CURSOR_TRACE_ID",
28
        "GEMINI_CLI",
29
        "CODEX_SANDBOX",
30
        "CODEX_CI",
31
        "CODEX_THREAD_ID",
32
        "ANTIGRAVITY_AGENT",
33
        "AUGMENT_AGENT",
34
        "OPENCODE_CLIENT",
35
        "CLAUDECODE",
36
        "CLAUDE_CODE",
37
        "REPL_ID",
38
        "COPILOT_MODEL",
39
        "COPILOT_ALLOW_ALL",
40
        "COPILOT_GITHUB_TOKEN",
41
        // CI signals
42
        "CI",
43
        "GITHUB_ACTIONS",
44
        "BUILDKITE",
45
        "TF_BUILD",
46
        "JENKINS_URL",
47
        "GITLAB_CI",
48
        // Extra signals
49
        "GITHUB_TOKEN",
50
        "GITHUB_HEAD_REF",
51
        "BITBUCKET_CLONE_DIR",
52
        // Supabase environment signals
53
        "SUPABASE_ACCESS_TOKEN",
54
        "SUPABASE_HOME",
55
        "SYSTEMROOT",
56
        "SUPABASE_SSL_DEBUG",
57
        "SUPABASE_CA_SKIP_VERIFY",
58
        "SSL_CERT_FILE",
59
        "SSL_CERT_DIR",
60
        "NPM_CONFIG_REGISTRY",
61
        "SUPABASE_SERVICE_ROLE_KEY",
62
        "SUPABASE_PROJECT_ID",
63
        "SUPABASE_POSTGRES_URL",
64
        "SUPABASE_ENV",
65
}
66

67
var telemetryEnvValueVars = []string{
68
        "AI_AGENT",
69
        "CURSOR_EXTENSION_HOST_ROLE",
70
        "TERM",
71
        "TERM_PROGRAM",
72
        "TERM_PROGRAM_VERSION",
73
        "TERM_COLOR_MODE",
74
}
75

76
func commandAnalyticsContext(cmd *cobra.Command) telemetry.CommandContext {
1✔
77
        return telemetry.CommandContext{
1✔
78
                RunID:   uuid.NewString(),
1✔
79
                Command: commandName(cmd),
1✔
80
                Flags:   changedFlagValues(cmd),
1✔
81
        }
1✔
82
}
1✔
83

84
func commandName(cmd *cobra.Command) string {
3✔
85
        path := strings.TrimSpace(cmd.CommandPath())
3✔
86
        rootName := strings.TrimSpace(cmd.Root().Name())
3✔
87
        if path == rootName || path == "" {
4✔
88
                return rootName
1✔
89
        }
1✔
90
        return strings.TrimSpace(strings.TrimPrefix(path, rootName))
2✔
91
}
92

93
func changedFlagValues(cmd *cobra.Command) map[string]any {
1✔
94
        flags := changedFlags(cmd)
1✔
95
        if len(flags) == 0 {
1✔
NEW
96
                return nil
×
NEW
97
        }
×
98
        values := make(map[string]any, len(flags))
1✔
99
        for _, flag := range flags {
5✔
100
                values[flag.Name] = telemetryFlagValue(flag)
4✔
101
        }
4✔
102
        return values
1✔
103
}
104

105
func changedFlags(cmd *cobra.Command) []*pflag.Flag {
1✔
106
        seen := make(map[string]struct{})
1✔
107
        var result []*pflag.Flag
1✔
108
        collect := func(flags *pflag.FlagSet) {
4✔
109
                if flags == nil {
3✔
NEW
110
                        return
×
NEW
111
                }
×
112
                flags.Visit(func(flag *pflag.Flag) {
7✔
113
                        if _, ok := seen[flag.Name]; ok {
4✔
NEW
114
                                return
×
NEW
115
                        }
×
116
                        seen[flag.Name] = struct{}{}
4✔
117
                        result = append(result, flag)
4✔
118
                })
119
        }
120
        for current := cmd; current != nil; current = current.Parent() {
3✔
121
                collect(current.PersistentFlags())
2✔
122
        }
2✔
123
        collect(cmd.Flags())
1✔
124
        sort.Slice(result, func(i, j int) bool {
4✔
125
                return result[i].Name < result[j].Name
3✔
126
        })
3✔
127
        return result
1✔
128
}
129

130
func markFlagTelemetrySafe(flag *pflag.Flag) {
13✔
131
        if flag == nil {
13✔
NEW
132
                return
×
NEW
133
        }
×
134
        if flag.Annotations == nil {
26✔
135
                flag.Annotations = map[string][]string{}
13✔
136
        }
13✔
137
        flag.Annotations[telemetrySafeValueAnnotation] = []string{"true"}
13✔
138
}
139

140
func telemetryFlagValue(flag *pflag.Flag) any {
4✔
141
        if flag == nil {
4✔
NEW
142
                return nil
×
NEW
143
        }
×
144
        if isTelemetrySafeFlag(flag) || isBooleanFlag(flag) || isEnumFlag(flag) {
7✔
145
                return actualTelemetryFlagValue(flag)
3✔
146
        }
3✔
147
        return redactedTelemetryValue
1✔
148
}
149

150
func isTelemetrySafeFlag(flag *pflag.Flag) bool {
4✔
151
        if flag == nil || flag.Annotations == nil {
7✔
152
                return false
3✔
153
        }
3✔
154
        values, ok := flag.Annotations[telemetrySafeValueAnnotation]
1✔
155
        return ok && len(values) > 0 && values[0] == "true"
1✔
156
}
157

158
func isBooleanFlag(flag *pflag.Flag) bool {
6✔
159
        return flag != nil && flag.Value.Type() == "bool"
6✔
160
}
6✔
161

162
func isEnumFlag(flag *pflag.Flag) bool {
2✔
163
        if flag == nil {
2✔
NEW
164
                return false
×
NEW
165
        }
×
166
        _, ok := flag.Value.(*utils.EnumFlag)
2✔
167
        return ok
2✔
168
}
169

170
func actualTelemetryFlagValue(flag *pflag.Flag) any {
3✔
171
        if isBooleanFlag(flag) {
4✔
172
                value, err := strconv.ParseBool(flag.Value.String())
1✔
173
                if err == nil {
2✔
174
                        return value
1✔
175
                }
1✔
176
        }
177
        return flag.Value.String()
2✔
178
}
179

NEW
180
func telemetryIsCI() bool {
×
NEW
181
        return os.Getenv("CI") != "" ||
×
NEW
182
                os.Getenv("GITHUB_ACTIONS") != "" ||
×
NEW
183
                os.Getenv("BUILDKITE") != "" ||
×
NEW
184
                os.Getenv("TF_BUILD") != "" ||
×
NEW
185
                os.Getenv("JENKINS_URL") != "" ||
×
NEW
186
                os.Getenv("GITLAB_CI") != ""
×
NEW
187
}
×
188

NEW
189
func telemetryIsTTY() bool {
×
NEW
190
        return term.IsTerminal(int(os.Stdout.Fd())) //nolint:gosec // G115: stdout fd is a small int on supported platforms
×
NEW
191
}
×
192

193
func telemetryIsAgent() bool {
2✔
194
        return agent.IsAgent()
2✔
195
}
2✔
196

197
func telemetryEnvSignals() map[string]any {
1✔
198
        return envSignals(telemetryEnvPresenceVars, telemetryEnvValueVars)
1✔
199
}
1✔
200

201
func envSignals(presenceKeys []string, valueKeys []string) map[string]any {
2✔
202
        signals := make(map[string]any, len(presenceKeys)+len(valueKeys))
2✔
203
        for _, key := range presenceKeys {
39✔
204
                if hasTelemetryEnvValue(key) {
41✔
205
                        signals[key] = true
4✔
206
                }
4✔
207
        }
208
        for _, key := range valueKeys {
10✔
209
                if value := telemetryEnvValue(key); value != "" {
11✔
210
                        signals[key] = value
3✔
211
                }
3✔
212
        }
213
        if len(signals) == 0 {
2✔
NEW
214
                return nil
×
NEW
215
        }
×
216
        return signals
2✔
217
}
218

219
func hasTelemetryEnvValue(key string) bool {
37✔
220
        return strings.TrimSpace(os.Getenv(key)) != ""
37✔
221
}
37✔
222

223
func telemetryEnvValue(key string) string {
8✔
224
        value := strings.TrimSpace(os.Getenv(key))
8✔
225
        if value == "" {
13✔
226
                return ""
5✔
227
        }
5✔
228
        if len(value) > maxTelemetryEnvValueLength {
4✔
229
                return value[:maxTelemetryEnvValueLength]
1✔
230
        }
1✔
231
        return value
2✔
232
}
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