@@ -10,15 +10,16 @@ import (
1010 "net/http"
1111 "net/url"
1212 "os"
13- "reflect"
1413 "slices"
15- "strings"
1614 "sync"
1715 "time"
1816
17+ "github.com/Netflix/go-env"
1918 "github.com/docker/docker/api/types"
2019 "github.com/docker/docker/api/types/container"
2120 "github.com/go-errors/errors"
21+ "github.com/olekukonko/tablewriter"
22+ "github.com/olekukonko/tablewriter/tw"
2223 "github.com/spf13/afero"
2324 "github.com/supabase/cli/internal/utils"
2425 "github.com/supabase/cli/internal/utils/flags"
@@ -27,9 +28,11 @@ import (
2728
2829type CustomName struct {
2930 ApiURL string `env:"api.url,default=API_URL"`
31+ RestURL string `env:"api.rest_url,default=REST_URL"`
3032 GraphqlURL string `env:"api.graphql_url,default=GRAPHQL_URL"`
3133 StorageS3URL string `env:"api.storage_s3_url,default=STORAGE_S3_URL"`
3234 McpURL string `env:"api.mcp_url,default=MCP_URL"`
35+ FunctionsURL string `env:"api.functions_url,default=FUNCTIONS_URL"`
3336 DbURL string `env:"db.url,default=DB_URL"`
3437 StudioURL string `env:"studio.url,default=STUDIO_URL"`
3538 InbucketURL string `env:"inbucket.url,default=INBUCKET_URL,deprecated"`
@@ -54,10 +57,15 @@ func (c *CustomName) toValues(exclude ...string) map[string]string {
5457 authEnabled := utils .Config .Auth .Enabled && ! slices .Contains (exclude , utils .GotrueId ) && ! slices .Contains (exclude , utils .ShortContainerImageName (utils .Config .Auth .Image ))
5558 inbucketEnabled := utils .Config .Inbucket .Enabled && ! slices .Contains (exclude , utils .InbucketId ) && ! slices .Contains (exclude , utils .ShortContainerImageName (utils .Config .Inbucket .Image ))
5659 storageEnabled := utils .Config .Storage .Enabled && ! slices .Contains (exclude , utils .StorageId ) && ! slices .Contains (exclude , utils .ShortContainerImageName (utils .Config .Storage .Image ))
60+ functionsEnabled := utils .Config .EdgeRuntime .Enabled && ! slices .Contains (exclude , utils .EdgeRuntimeId ) && ! slices .Contains (exclude , utils .ShortContainerImageName (utils .Config .EdgeRuntime .Image ))
5761
5862 if apiEnabled {
5963 values [c .ApiURL ] = utils .Config .Api .ExternalUrl
64+ values [c .RestURL ] = utils .GetApiUrl ("/rest/v1" )
6065 values [c .GraphqlURL ] = utils .GetApiUrl ("/graphql/v1" )
66+ if functionsEnabled {
67+ values [c .FunctionsURL ] = utils .GetApiUrl ("/functions/v1" )
68+ }
6169 if studioEnabled {
6270 values [c .McpURL ] = utils .GetApiUrl ("/mcp" )
6371 }
@@ -210,44 +218,159 @@ func printStatus(names CustomName, format string, w io.Writer, exclude ...string
210218}
211219
212220func PrettyPrint (w io.Writer , exclude ... string ) {
213- names := CustomName {
214- ApiURL : " " + utils .Aqua ("API URL" ),
215- GraphqlURL : " " + utils .Aqua ("GraphQL URL" ),
216- StorageS3URL : " " + utils .Aqua ("S3 Storage URL" ),
217- McpURL : " " + utils .Aqua ("MCP URL" ),
218- DbURL : " " + utils .Aqua ("Database URL" ),
219- StudioURL : " " + utils .Aqua ("Studio URL" ),
220- InbucketURL : " " + utils .Aqua ("Inbucket URL" ),
221- MailpitURL : " " + utils .Aqua ("Mailpit URL" ),
222- PublishableKey : " " + utils .Aqua ("Publishable key" ),
223- SecretKey : " " + utils .Aqua ("Secret key" ),
224- JWTSecret : " " + utils .Aqua ("JWT secret" ),
225- AnonKey : " " + utils .Aqua ("anon key" ),
226- ServiceRoleKey : "" + utils .Aqua ("service_role key" ),
227- StorageS3AccessKeyId : " " + utils .Aqua ("S3 Access Key" ),
228- StorageS3SecretAccessKey : " " + utils .Aqua ("S3 Secret Key" ),
229- StorageS3Region : " " + utils .Aqua ("S3 Region" ),
221+ logger := utils .GetDebugLogger ()
222+
223+ names := CustomName {}
224+ if err := env .Unmarshal (env.EnvSet {}, & names ); err != nil {
225+ fmt .Fprintln (logger , err )
230226 }
231227 values := names .toValues (exclude ... )
232- // Iterate through map in order of declared struct fields
233- t := reflect .TypeOf (names )
234- val := reflect .ValueOf (names )
235- for i := 0 ; i < val .NumField (); i ++ {
236- k := val .Field (i ).String ()
237- if tag := t .Field (i ).Tag .Get ("env" ); isDeprecated (tag ) {
228+
229+ groups := []OutputGroup {
230+ {
231+ Name : "🛠️ Development Tools" ,
232+ Items : []OutputItem {
233+ {Label : "Studio" , Value : values [names .StudioURL ], Type : Link },
234+ {Label : "Mailpit" , Value : values [names .MailpitURL ], Type : Link },
235+ {Label : "MCP" , Value : values [names .McpURL ], Type : Link },
236+ },
237+ },
238+ {
239+ Name : "🌐 APIs" ,
240+ Items : []OutputItem {
241+ {Label : "Project URL" , Value : values [names .ApiURL ], Type : Link },
242+ {Label : "REST" , Value : values [names .RestURL ], Type : Link },
243+ {Label : "GraphQL" , Value : values [names .GraphqlURL ], Type : Link },
244+ {Label : "Edge Functions" , Value : values [names .FunctionsURL ], Type : Link },
245+ },
246+ },
247+ {
248+ Name : "🗄️ Database" ,
249+ Items : []OutputItem {
250+ {Label : "URL" , Value : values [names .DbURL ], Type : Link },
251+ },
252+ },
253+ {
254+ Name : "🔑 Authentication Keys" ,
255+ Items : []OutputItem {
256+ {Label : "Publishable" , Value : values [names .PublishableKey ], Type : Key },
257+ {Label : "Secret" , Value : values [names .SecretKey ], Type : Key },
258+ },
259+ },
260+ {
261+ Name : "📦 Storage (S3)" ,
262+ Items : []OutputItem {
263+ {Label : "URL" , Value : values [names .StorageS3URL ], Type : Link },
264+ {Label : "Access Key" , Value : values [names .StorageS3AccessKeyId ], Type : Key },
265+ {Label : "Secret Key" , Value : values [names .StorageS3SecretAccessKey ], Type : Key },
266+ {Label : "Region" , Value : values [names .StorageS3Region ], Type : Text },
267+ },
268+ },
269+ }
270+
271+ for _ , group := range groups {
272+ if err := group .printTable (w ); err != nil {
273+ fmt .Fprintln (logger , err )
274+ } else {
275+ fmt .Fprintln (w )
276+ }
277+ }
278+ }
279+
280+ type OutputType string
281+
282+ const (
283+ Text OutputType = "text"
284+ Link OutputType = "link"
285+ Key OutputType = "key"
286+ )
287+
288+ type OutputItem struct {
289+ Label string
290+ Value string
291+ Type OutputType
292+ }
293+
294+ type OutputGroup struct {
295+ Name string
296+ Items []OutputItem
297+ }
298+
299+ func (g * OutputGroup ) printTable (w io.Writer ) error {
300+ table := tablewriter .NewTable (w ,
301+ // Rounded corners
302+ tablewriter .WithSymbols (tw .NewSymbols (tw .StyleRounded )),
303+
304+ // Table content formatting
305+ tablewriter .WithConfig (tablewriter.Config {
306+ Header : tw.CellConfig {
307+ Formatting : tw.CellFormatting {
308+ AutoFormat : tw .Off ,
309+ MergeMode : tw .MergeHorizontal ,
310+ },
311+ Alignment : tw.CellAlignment {
312+ Global : tw .AlignLeft ,
313+ },
314+ Filter : tw.CellFilter {
315+ Global : func (s []string ) []string {
316+ for i := range s {
317+ s [i ] = utils .Bold (s [i ])
318+ }
319+ return s
320+ },
321+ },
322+ },
323+ Row : tw.CellConfig {
324+ Alignment : tw.CellAlignment {
325+ Global : tw .AlignLeft ,
326+ },
327+ ColMaxWidths : tw.CellWidth {
328+ PerColumn : map [int ]int {0 : 16 },
329+ },
330+ Filter : tw.CellFilter {
331+ PerColumn : []func (string ) string {
332+ func (s string ) string {
333+ return utils .Green (s )
334+ },
335+ },
336+ },
337+ },
338+ Behavior : tw.Behavior {
339+ Compact : tw.Compact {
340+ Merge : tw .On ,
341+ },
342+ },
343+ }),
344+
345+ // Set title as header (merged across all columns)
346+ tablewriter .WithHeader ([]string {g .Name , g .Name }),
347+ )
348+
349+ // Add data rows with values colored based on type
350+ shouldRender := false
351+ for _ , row := range g .Items {
352+ if row .Value == "" {
238353 continue
239354 }
240- if v , ok := values [k ]; ok {
241- fmt .Fprintf (w , "%s: %s\n " , k , v )
355+ value := row .Value
356+ switch row .Type {
357+ case Link :
358+ value = utils .Aqua (row .Value )
359+ case Key :
360+ value = utils .Yellow (row .Value )
361+ }
362+ if err := table .Append (row .Label , value ); err != nil {
363+ return errors .Errorf ("failed to append row: %w" , err )
242364 }
365+ shouldRender = true
243366 }
244- }
245367
246- func isDeprecated ( tag string ) bool {
247- for part := range strings . SplitSeq ( tag , "," ) {
248- if strings . EqualFold ( part , "deprecated" ) {
249- return true
368+ // Ensure at least one item in the group is non-empty
369+ if shouldRender {
370+ if err := table . Render (); err != nil {
371+ return errors . Errorf ( "failed to render table: %w" , err )
250372 }
251373 }
252- return false
374+
375+ return nil
253376}
0 commit comments