Skip to content

Commit 343ecfe

Browse files
authored
feat: support profile flag for development (#4071)
2 parents 9a9f94a + 3be770c commit 343ecfe

File tree

19 files changed

+341
-103
lines changed

19 files changed

+341
-103
lines changed

cmd/root.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,16 @@ var (
9292
return errors.New("must set the --experimental flag to run this command")
9393
}
9494
cmd.SilenceUsage = true
95-
// Change workdir
95+
// Load profile before changing workdir
96+
ctx := cmd.Context()
9697
fsys := afero.NewOsFs()
98+
if err := utils.LoadProfile(ctx, fsys); err != nil {
99+
return err
100+
}
97101
if err := utils.ChangeWorkDir(fsys); err != nil {
98102
return err
99103
}
100104
// Add common flags
101-
ctx := cmd.Context()
102105
if IsManagementAPI(cmd) {
103106
if err := promptLogin(fsys); err != nil {
104107
return err
@@ -236,6 +239,7 @@ func init() {
236239
flags.String("workdir", "", "path to a Supabase project directory")
237240
flags.Bool("experimental", false, "enable experimental features")
238241
flags.String("network-id", "", "use the specified docker network instead of a generated one")
242+
flags.String("profile", "supabase", "use a specific profile for connecting to Supabase API")
239243
flags.VarP(&utils.OutputFormat, "output", "o", "output format of status variables")
240244
flags.Var(&utils.DNSResolver, "dns-resolver", "lookup domain names using the specified resolver")
241245
flags.BoolVar(&createTicket, "create-ticket", false, "create a support ticket for any CLI error")

go.mod

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ require (
2020
github.com/getsentry/sentry-go v0.35.1
2121
github.com/go-errors/errors v1.5.1
2222
github.com/go-git/go-git/v5 v5.16.2
23+
github.com/go-playground/validator/v10 v10.27.0
24+
github.com/go-viper/mapstructure/v2 v2.4.0
2325
github.com/go-xmlfmt/xmlfmt v1.1.3
2426
github.com/google/go-github/v62 v62.0.0
2527
github.com/google/go-querystring v1.1.0
@@ -131,6 +133,7 @@ require (
131133
github.com/firefart/nonamedreturns v1.0.6 // indirect
132134
github.com/fvbommel/sortorder v1.1.0 // indirect
133135
github.com/fzipp/gocyclo v0.6.0 // indirect
136+
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
134137
github.com/getkin/kin-openapi v0.131.0 // indirect
135138
github.com/ghostiam/protogetter v0.3.15 // indirect
136139
github.com/go-critic/go-critic v0.13.0 // indirect
@@ -140,14 +143,15 @@ require (
140143
github.com/go-logr/stdr v1.2.2 // indirect
141144
github.com/go-openapi/jsonpointer v0.21.0 // indirect
142145
github.com/go-openapi/swag v0.23.1 // indirect
146+
github.com/go-playground/locales v0.14.1 // indirect
147+
github.com/go-playground/universal-translator v0.18.1 // indirect
143148
github.com/go-toolsmith/astcast v1.1.0 // indirect
144149
github.com/go-toolsmith/astcopy v1.1.0 // indirect
145150
github.com/go-toolsmith/astequal v1.2.0 // indirect
146151
github.com/go-toolsmith/astfmt v1.1.0 // indirect
147152
github.com/go-toolsmith/astp v1.1.0 // indirect
148153
github.com/go-toolsmith/strparse v1.1.0 // indirect
149154
github.com/go-toolsmith/typep v1.1.0 // indirect
150-
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
151155
github.com/gobwas/glob v0.2.3 // indirect
152156
github.com/godbus/dbus/v5 v5.1.0 // indirect
153157
github.com/gofrs/flock v0.12.1 // indirect
@@ -206,6 +210,7 @@ require (
206210
github.com/ldez/grignotin v0.9.0 // indirect
207211
github.com/ldez/tagliatelle v0.7.1 // indirect
208212
github.com/ldez/usetesting v0.4.3 // indirect
213+
github.com/leodido/go-urn v1.4.0 // indirect
209214
github.com/leonklingele/grouper v1.1.2 // indirect
210215
github.com/lib/pq v1.10.9 // indirect
211216
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect

go.sum

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,8 @@ github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQ
296296
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
297297
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
298298
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
299+
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
300+
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
299301
github.com/getkin/kin-openapi v0.131.0 h1:NO2UeHnFKRYhZ8wg6Nyh5Cq7dHk4suQQr72a4pMrDxE=
300302
github.com/getkin/kin-openapi v0.131.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
301303
github.com/getsentry/sentry-go v0.35.1 h1:iopow6UVLE2aXu46xKVIs8Z9D/YZkJrHkgozrxa+tOQ=
@@ -334,6 +336,14 @@ github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1
334336
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
335337
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
336338
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
339+
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
340+
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
341+
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
342+
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
343+
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
344+
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
345+
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
346+
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
337347
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
338348
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
339349
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
@@ -641,6 +651,8 @@ github.com/ldez/tagliatelle v0.7.1 h1:bTgKjjc2sQcsgPiT902+aadvMjCeMHrY7ly2XKFORI
641651
github.com/ldez/tagliatelle v0.7.1/go.mod h1:3zjxUpsNB2aEZScWiZTHrAXOl1x25t3cRmzfK1mlo2I=
642652
github.com/ldez/usetesting v0.4.3 h1:pJpN0x3fMupdTf/IapYjnkhiY1nSTN+pox1/GyBRw3k=
643653
github.com/ldez/usetesting v0.4.3/go.mod h1:eEs46T3PpQ+9RgN9VjpY6qWdiw2/QmfiDeWmdZdrjIQ=
654+
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
655+
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
644656
github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY=
645657
github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA=
646658
github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=

internal/bootstrap/bootstrap_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/spf13/afero"
1212
"github.com/stretchr/testify/assert"
1313
"github.com/stretchr/testify/require"
14+
"github.com/supabase/cli/internal/utils"
1415
"github.com/supabase/cli/internal/utils/flags"
1516
"github.com/supabase/cli/pkg/api"
1617
)
@@ -64,6 +65,7 @@ func TestWriteEnv(t *testing.T) {
6465

6566
t.Run("writes .env", func(t *testing.T) {
6667
flags.ProjectRef = "testing"
68+
utils.CurrentProfile.ProjectHost = "supabase.co"
6769
// Setup in-memory fs
6870
fsys := afero.NewMemMapFs()
6971
// Run test
@@ -80,6 +82,7 @@ SUPABASE_URL="https://testing.supabase.co"`, string(env))
8082

8183
t.Run("merges with .env.example", func(t *testing.T) {
8284
flags.ProjectRef = "testing"
85+
utils.CurrentProfile.ProjectHost = "supabase.co"
8386
// Setup in-memory fs
8487
fsys := afero.NewMemMapFs()
8588
example, err := godotenv.Marshal(map[string]string{

internal/functions/list/list_test.go

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package list
33
import (
44
"context"
55
"errors"
6+
"net/http"
67
"testing"
78

89
"github.com/h2non/gock"
@@ -14,14 +15,15 @@ import (
1415
)
1516

1617
func TestFunctionsListCommand(t *testing.T) {
18+
// Setup valid project ref
19+
project := apitest.RandomProjectRef()
20+
// Setup valid access token
21+
token := apitest.RandomAccessToken(t)
22+
t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
23+
1724
t.Run("lists all functions", func(t *testing.T) {
1825
// Setup in-memory fs
1926
fsys := afero.NewMemMapFs()
20-
// Setup valid project ref
21-
project := apitest.RandomProjectRef()
22-
// Setup valid access token
23-
token := apitest.RandomAccessToken(t)
24-
t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
2527
// Flush pending mocks after test execution
2628
defer gock.OffAll()
2729

@@ -53,23 +55,23 @@ func TestFunctionsListCommand(t *testing.T) {
5355
assert.Empty(t, apitest.ListUnmatchedRequests())
5456
})
5557

56-
t.Run("throws error on missing access token", func(t *testing.T) {
58+
t.Run("throws error on service unavailable", func(t *testing.T) {
5759
// Setup in-memory fs
5860
fsys := afero.NewMemMapFs()
61+
// Flush pending mocks after test execution
62+
defer gock.OffAll()
63+
gock.New(utils.DefaultApiHost).
64+
Get("/v1/projects/" + project + "/functions").
65+
Reply(http.StatusServiceUnavailable)
5966
// Run test
60-
err := Run(context.Background(), "", fsys)
67+
err := Run(context.Background(), project, fsys)
6168
// Check error
6269
assert.ErrorContains(t, err, "Unexpected error retrieving functions")
6370
})
6471

6572
t.Run("throws error on network error", func(t *testing.T) {
6673
// Setup in-memory fs
6774
fsys := afero.NewMemMapFs()
68-
// Setup valid project ref
69-
project := apitest.RandomProjectRef()
70-
// Setup valid access token
71-
token := apitest.RandomAccessToken(t)
72-
t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
7375
// Flush pending mocks after test execution
7476
defer gock.OffAll()
7577
gock.New(utils.DefaultApiHost).

internal/hostnames/activate/activate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func Run(ctx context.Context, projectRef string, includeRawOutput bool, fsys afe
2727
{
2828
resp, err := utils.GetSupabase().V1ActivateCustomHostnameWithResponse(ctx, projectRef)
2929
if err != nil {
30-
return errors.Errorf("failed to active custom hostname: %w", err)
30+
return errors.Errorf("failed to activate custom hostname: %w", err)
3131
}
3232
if resp.JSON201 == nil {
3333
return errors.New("failed to activate custom hostname config: " + string(resp.Body))

internal/hostnames/common_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import (
77

88
"github.com/h2non/gock"
99
"github.com/stretchr/testify/assert"
10+
"github.com/supabase/cli/internal/utils"
1011
)
1112

1213
func TestVerifyCNAME(t *testing.T) {
14+
utils.CurrentProfile.ProjectHost = "supabase.co"
1315
defer gock.OffAll()
1416
gock.New("https://1.1.1.1").
1517
Get("/dns-query").
@@ -40,5 +42,5 @@ func TestVerifyCNAMEFailures(t *testing.T) {
4042
},
4143
}})
4244
err := VerifyCNAME(context.Background(), "foobarbaz", "hello.custom-domain.com")
43-
assert.ErrorContains(t, err, "expected custom hostname 'hello.custom-domain.com' to have a CNAME record pointing to your project at 'foobarbaz.supabase.co.', but it failed to resolve: failed to locate appropriate CNAME record for hello.custom-domain.com")
45+
assert.ErrorContains(t, err, "failed to locate appropriate CNAME record for hello.custom-domain.com")
4446
}

internal/login/login_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func TestLoginCommand(t *testing.T) {
4040
Token: token,
4141
Fsys: afero.NewMemMapFs(),
4242
}))
43-
saved, err := credentials.StoreProvider.Get(utils.AccessTokenKey)
43+
saved, err := credentials.StoreProvider.Get(utils.CurrentProfile.Name)
4444
assert.NoError(t, err)
4545
assert.Equal(t, token, saved)
4646
})
@@ -83,7 +83,7 @@ func TestLoginCommand(t *testing.T) {
8383
expectedBrowserUrl := fmt.Sprintf("%s/cli/login?session_id=%s&token_name=%s&public_key=%s", utils.GetSupabaseDashboardURL(), sessionId, tokenName, publicKey)
8484
assert.Contains(t, out.String(), expectedBrowserUrl)
8585

86-
saved, err := credentials.StoreProvider.Get(utils.AccessTokenKey)
86+
saved, err := credentials.StoreProvider.Get(utils.CurrentProfile.Name)
8787
assert.NoError(t, err)
8888
assert.Equal(t, token, saved)
8989
assert.Empty(t, apitest.ListUnmatchedRequests())

internal/logout/logout_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func TestLogoutCommand(t *testing.T) {
3535

3636
t.Run("removes all Supabase CLI credentials", func(t *testing.T) {
3737
keyring.MockInit()
38-
require.NoError(t, credentials.StoreProvider.Set(utils.AccessTokenKey, token))
38+
require.NoError(t, credentials.StoreProvider.Set(utils.CurrentProfile.Name, token))
3939
require.NoError(t, credentials.StoreProvider.Set("project1", "password1"))
4040
require.NoError(t, credentials.StoreProvider.Set("project2", "password2"))
4141
t.Cleanup(fstest.MockStdin(t, "y"))
@@ -44,7 +44,7 @@ func TestLogoutCommand(t *testing.T) {
4444
// Check error
4545
assert.NoError(t, err)
4646
// Check that access token has been removed
47-
saved, _ := credentials.StoreProvider.Get(utils.AccessTokenKey)
47+
saved, _ := credentials.StoreProvider.Get(utils.CurrentProfile.Name)
4848
assert.Empty(t, saved)
4949
// check that project 1 has been removed
5050
saved, _ = credentials.StoreProvider.Get("project1")
@@ -56,14 +56,14 @@ func TestLogoutCommand(t *testing.T) {
5656

5757
t.Run("skips logout by default", func(t *testing.T) {
5858
keyring.MockInit()
59-
require.NoError(t, credentials.StoreProvider.Set(utils.AccessTokenKey, token))
59+
require.NoError(t, credentials.StoreProvider.Set(utils.CurrentProfile.Name, token))
6060
// Setup in-memory fs
6161
fsys := afero.NewMemMapFs()
6262
// Run test
6363
err := Run(context.Background(), os.Stdout, fsys)
6464
// Check error
6565
assert.ErrorIs(t, err, context.Canceled)
66-
saved, err := credentials.StoreProvider.Get(utils.AccessTokenKey)
66+
saved, err := credentials.StoreProvider.Get(utils.CurrentProfile.Name)
6767
assert.NoError(t, err)
6868
assert.Equal(t, token, saved)
6969
})

internal/utils/access_token.go

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package utils
22

33
import (
4+
"fmt"
45
"os"
56
"path/filepath"
67
"regexp"
@@ -32,12 +33,20 @@ func LoadAccessTokenFS(fsys afero.Fs) (string, error) {
3233
}
3334

3435
func loadAccessToken(fsys afero.Fs) (string, error) {
36+
logger := GetDebugLogger()
3537
// Env takes precedence
3638
if accessToken := os.Getenv("SUPABASE_ACCESS_TOKEN"); accessToken != "" {
39+
fmt.Fprintln(logger, "Using access token from env var...")
3740
return accessToken, nil
3841
}
39-
// Load from native credentials store
42+
// Load from current profile
43+
if accessToken, err := credentials.StoreProvider.Get(CurrentProfile.Name); err == nil {
44+
fmt.Fprintln(logger, "Using access token for profile:", CurrentProfile.Name)
45+
return accessToken, nil
46+
}
47+
// Load from legacy key for backwards compatibility
4048
if accessToken, err := credentials.StoreProvider.Get(AccessTokenKey); err == nil {
49+
fmt.Fprintln(logger, "Using access token from credentials store...")
4150
return accessToken, nil
4251
}
4352
// Fallback to token file
@@ -55,6 +64,7 @@ func fallbackLoadToken(fsys afero.Fs) (string, error) {
5564
} else if err != nil {
5665
return "", errors.Errorf("failed to read access token file: %w", err)
5766
}
67+
fmt.Fprintln(GetDebugLogger(), "Using access token from file:", path)
5868
return string(accessToken), nil
5969
}
6070

@@ -63,8 +73,10 @@ func SaveAccessToken(accessToken string, fsys afero.Fs) error {
6373
if !AccessTokenPattern.MatchString(accessToken) {
6474
return errors.New(ErrInvalidToken)
6575
}
66-
// Save to native credentials store
67-
if err := credentials.StoreProvider.Set(AccessTokenKey, accessToken); err == nil {
76+
// Save to current profile
77+
if err := credentials.StoreProvider.Set(CurrentProfile.Name, accessToken); err != nil {
78+
fmt.Fprintln(GetDebugLogger(), err)
79+
} else {
6880
return nil
6981
}
7082
// Fallback to token file
@@ -87,19 +99,20 @@ func fallbackSaveToken(accessToken string, fsys afero.Fs) error {
8799

88100
func DeleteAccessToken(fsys afero.Fs) error {
89101
// Always delete the fallback token file to handle legacy CLI
90-
if err := fallbackDeleteToken(fsys); err == nil {
91-
// Typically user system should only have either token file or keyring.
92-
// But we delete from both just in case.
93-
_ = credentials.StoreProvider.Delete(AccessTokenKey)
94-
return nil
95-
} else if !errors.Is(err, os.ErrNotExist) {
102+
if err := fallbackDeleteToken(fsys); err != nil && !errors.Is(err, os.ErrNotExist) {
96103
return err
97104
}
98-
// Fallback not found, delete from native credentials store
99-
err := credentials.StoreProvider.Delete(AccessTokenKey)
100-
if errors.Is(err, credentials.ErrNotSupported) || errors.Is(err, keyring.ErrNotFound) {
101-
return errors.New(ErrNotLoggedIn)
102-
} else if err != nil {
105+
// Always delete from legacy keyring
106+
if err := credentials.StoreProvider.Delete(AccessTokenKey); err != nil && !errors.Is(err, keyring.ErrNotFound) {
107+
fmt.Fprintln(GetDebugLogger(), err)
108+
}
109+
// Delete profile from native credentials store
110+
if err := credentials.StoreProvider.Delete(CurrentProfile.Name); err != nil {
111+
if errors.Is(err, credentials.ErrNotSupported) ||
112+
errors.Is(err, keyring.ErrUnsupportedPlatform) ||
113+
errors.Is(err, keyring.ErrNotFound) {
114+
return errors.New(ErrNotLoggedIn)
115+
}
103116
return errors.Errorf("failed to delete access token from keyring: %w", err)
104117
}
105118
return nil

0 commit comments

Comments
 (0)