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

supabase / cli / 17171136196

23 Aug 2025 04:19AM UTC coverage: 54.821% (+0.1%) from 54.699%
17171136196

push

github

web-flow
feat: support profile flag for development (#4071)

47 of 55 new or added lines in 5 files covered. (85.45%)

7 existing lines in 4 files now uncovered.

6192 of 11295 relevant lines covered (54.82%)

6.06 hits per line

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

89.69
/internal/utils/api.go
1
package utils
2

3
import (
4
        "context"
5
        "encoding/json"
6
        "fmt"
7
        "log"
8
        "net"
9
        "net/http"
10
        "sync"
11

12
        "github.com/go-errors/errors"
13
        "github.com/spf13/afero"
14
        "github.com/supabase/cli/internal/utils/cloudflare"
15
        supabase "github.com/supabase/cli/pkg/api"
16
        "github.com/supabase/cli/pkg/cast"
17
)
18

19
const (
20
        DNS_GO_NATIVE  = "native"
21
        DNS_OVER_HTTPS = "https"
22
)
23

24
var (
25
        clientOnce sync.Once
26
        apiClient  *supabase.ClientWithResponses
27

28
        DNSResolver = EnumFlag{
29
                Allowed: []string{DNS_GO_NATIVE, DNS_OVER_HTTPS},
30
                Value:   DNS_GO_NATIVE,
31
        }
32
)
33

34
// Performs DNS lookup via HTTPS, in case firewall blocks native netgo resolver.
35
func FallbackLookupIP(ctx context.Context, host string) ([]string, error) {
13✔
36
        if net.ParseIP(host) != nil {
14✔
37
                return []string{host}, nil
1✔
38
        }
1✔
39
        // Ref: https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/make-api-requests/dns-json
40
        cf := cloudflare.NewCloudflareAPI()
12✔
41
        data, err := cf.DNSQuery(ctx, cloudflare.DNSParams{Name: host})
12✔
42
        if err != nil {
17✔
43
                return nil, err
5✔
44
        }
5✔
45
        // Look for first valid IP
46
        var resolved []string
7✔
47
        for _, answer := range data.Answer {
14✔
48
                if answer.Type == cloudflare.TypeA || answer.Type == cloudflare.TypeAAAA {
13✔
49
                        resolved = append(resolved, answer.Data)
6✔
50
                }
6✔
51
        }
52
        if len(resolved) == 0 {
8✔
53
                return nil, errors.Errorf("failed to locate valid IP for %s; resolves to %#v", host, data.Answer)
1✔
54
        }
1✔
55
        return resolved, nil
6✔
56
}
57

58
func ResolveCNAME(ctx context.Context, host string) (string, error) {
5✔
59
        // Ref: https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/make-api-requests/dns-json
5✔
60
        cf := cloudflare.NewCloudflareAPI()
5✔
61
        data, err := cf.DNSQuery(ctx, cloudflare.DNSParams{Name: host, Type: cast.Ptr(cloudflare.TypeCNAME)})
5✔
62
        if err != nil {
5✔
63
                return "", err
×
64
        }
×
65
        // Look for first valid IP
66
        for _, answer := range data.Answer {
9✔
67
                if answer.Type == cloudflare.TypeCNAME {
6✔
68
                        return answer.Data, nil
2✔
69
                }
2✔
70
        }
71
        serialized, err := json.MarshalIndent(data.Answer, "", "    ")
3✔
72
        if err != nil {
3✔
73
                // we ignore the error (not great), and use the underlying struct in our error message
×
74
                return "", errors.Errorf("failed to locate appropriate CNAME record for %s; resolves to %+v", host, data.Answer)
×
75
        }
×
76
        return "", errors.Errorf("failed to locate appropriate CNAME record for %s; resolves to %+v", host, serialized)
3✔
77
}
78

79
type DialContextFunc func(context.Context, string, string) (net.Conn, error)
80

81
// Wraps a DialContext with DNS-over-HTTPS as fallback resolver
82
func withFallbackDNS(dialContext DialContextFunc) DialContextFunc {
5✔
83
        dnsOverHttps := func(ctx context.Context, network, address string) (net.Conn, error) {
9✔
84
                host, port, err := net.SplitHostPort(address)
4✔
85
                if err != nil {
5✔
86
                        return nil, errors.Errorf("failed to split host port: %w", err)
1✔
87
                }
1✔
88
                ip, err := FallbackLookupIP(ctx, host)
3✔
89
                if err != nil {
4✔
90
                        return nil, err
1✔
91
                }
1✔
92
                conn, err := dialContext(ctx, network, net.JoinHostPort(ip[0], port))
2✔
93
                if err != nil {
3✔
94
                        return nil, errors.Errorf("failed to dial fallback: %w", err)
1✔
95
                }
1✔
96
                return conn, nil
1✔
97
        }
98
        if DNSResolver.Value == DNS_OVER_HTTPS {
7✔
99
                return dnsOverHttps
2✔
100
        }
2✔
101
        nativeWithFallback := func(ctx context.Context, network, address string) (net.Conn, error) {
5✔
102
                conn, err := dialContext(ctx, network, address)
2✔
103
                // Workaround when pure Go DNS resolver fails https://github.com/golang/go/issues/12524
2✔
104
                if err, ok := err.(net.Error); ok && err.Timeout() {
4✔
105
                        if conn, err := dnsOverHttps(ctx, network, address); err == nil {
3✔
106
                                return conn, nil
1✔
107
                        }
1✔
108
                }
109
                if err != nil {
2✔
110
                        return nil, errors.Errorf("failed to dial native: %w", err)
1✔
111
                }
1✔
UNCOV
112
                return conn, nil
×
113
        }
114
        return nativeWithFallback
3✔
115
}
116

117
func GetSupabase() *supabase.ClientWithResponses {
146✔
118
        clientOnce.Do(func() {
175✔
119
                token, err := LoadAccessTokenFS(afero.NewOsFs())
29✔
120
                if err != nil {
29✔
121
                        log.Fatalln(err)
×
122
                }
×
123
                if t, ok := http.DefaultTransport.(*http.Transport); ok {
30✔
124
                        t.DialContext = withFallbackDNS(t.DialContext)
1✔
125
                }
1✔
126
                apiClient, err = supabase.NewClientWithResponses(
29✔
127
                        GetSupabaseAPIHost(),
29✔
128
                        supabase.WithRequestEditorFn(func(ctx context.Context, req *http.Request) error {
182✔
129
                                req.Header.Set("Authorization", "Bearer "+token)
153✔
130
                                req.Header.Set("User-Agent", "SupabaseCLI/"+Version)
153✔
131
                                return nil
153✔
132
                        }),
153✔
133
                )
134
                if err != nil {
29✔
135
                        log.Fatalln(err)
×
136
                }
×
137
        })
138
        return apiClient
146✔
139
}
140

141
// Used by unit tests
142
var DefaultApiHost = CurrentProfile.APIURL
143

144
var RegionMap = map[string]string{
145
        "ap-northeast-1": "Northeast Asia (Tokyo)",
146
        "ap-northeast-2": "Northeast Asia (Seoul)",
147
        "ap-south-1":     "South Asia (Mumbai)",
148
        "ap-southeast-1": "Southeast Asia (Singapore)",
149
        "ap-southeast-2": "Oceania (Sydney)",
150
        "ca-central-1":   "Canada (Central)",
151
        "eu-central-1":   "Central EU (Frankfurt)",
152
        "eu-west-1":      "West EU (Ireland)",
153
        "eu-west-2":      "West EU (London)",
154
        "eu-west-3":      "West EU (Paris)",
155
        "sa-east-1":      "South America (São Paulo)",
156
        "us-east-1":      "East US (North Virginia)",
157
        "us-west-1":      "West US (North California)",
158
        "us-west-2":      "West US (Oregon)",
159
}
160

161
func GetSupabaseAPIHost() string {
34✔
162
        return CurrentProfile.APIURL
34✔
163
}
34✔
164

165
func GetSupabaseDashboardURL() string {
11✔
166
        return CurrentProfile.DashboardURL
11✔
167
}
11✔
168

169
func GetSupabaseHost(projectRef string) string {
56✔
170
        return fmt.Sprintf("%s.%s", projectRef, CurrentProfile.ProjectHost)
56✔
171
}
56✔
172

173
func GetSupabaseDbHost(projectRef string) string {
7✔
174
        return "db." + GetSupabaseHost(projectRef)
7✔
175
}
7✔
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