Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ require (
github.com/mithrandie/csvq-driver v1.7.0
github.com/muesli/reflow v0.3.0
github.com/oapi-codegen/nullable v1.1.0
github.com/olekukonko/tablewriter v1.1.0
github.com/slack-go/slack v0.17.3
github.com/spf13/afero v1.15.0
github.com/spf13/cobra v1.10.1
Expand Down Expand Up @@ -251,7 +252,8 @@ require (
github.com/oapi-codegen/runtime v1.1.2 // indirect
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/olekukonko/errors v1.1.0 // indirect
github.com/olekukonko/ll v0.0.9 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
Expand Down
9 changes: 6 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
Expand Down Expand Up @@ -770,8 +769,12 @@ github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//J
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY=
github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
Expand Down
200 changes: 168 additions & 32 deletions internal/status/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import (
"net/http"
"net/url"
"os"
"reflect"
"strings"
"sync"
"time"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/go-errors/errors"
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
"github.com/spf13/afero"
"github.com/supabase/cli/internal/utils"
"github.com/supabase/cli/internal/utils/flags"
Expand All @@ -26,9 +26,11 @@ import (

type CustomName struct {
ApiURL string `env:"api.url,default=API_URL"`
RestURL string `env:"api.rest_url,default=REST_URL"`
GraphqlURL string `env:"api.graphql_url,default=GRAPHQL_URL"`
StorageS3URL string `env:"api.storage_s3_url,default=STORAGE_S3_URL"`
McpURL string `env:"api.mcp_url,default=MCP_URL"`
FunctionsURL string `env:"api.functions_url,default=FUNCTIONS_URL"`
DbURL string `env:"db.url,default=DB_URL"`
StudioURL string `env:"studio.url,default=STUDIO_URL"`
InbucketURL string `env:"inbucket.url,default=INBUCKET_URL,deprecated"`
Expand All @@ -42,6 +44,24 @@ type CustomName struct {
StorageS3SecretAccessKey string `env:"storage.s3_secret_access_key,default=S3_PROTOCOL_ACCESS_KEY_SECRET"`
StorageS3Region string `env:"storage.s3_region,default=S3_PROTOCOL_REGION"`
}
type OutputType string

const (
Text OutputType = "text"
Link OutputType = "link"
Key OutputType = "key"
)

type OutputItem struct {
Label string
Value string
Type OutputType
}

type OutputGroup struct {
Name string
Items []OutputItem
}

func (c *CustomName) toValues(exclude ...string) map[string]string {
values := map[string]string{
Expand All @@ -56,7 +76,9 @@ func (c *CustomName) toValues(exclude ...string) map[string]string {

if apiEnabled {
values[c.ApiURL] = utils.Config.Api.ExternalUrl
values[c.RestURL] = utils.GetApiUrl("/rest/v1")
values[c.GraphqlURL] = utils.GetApiUrl("/graphql/v1")
values[c.FunctionsURL] = utils.GetApiUrl("/functions/v1")
if studioEnabled {
values[c.McpURL] = utils.GetApiUrl("/mcp")
}
Expand Down Expand Up @@ -210,43 +232,157 @@ func printStatus(names CustomName, format string, w io.Writer, exclude ...string

func PrettyPrint(w io.Writer, exclude ...string) {
names := CustomName{
ApiURL: " " + utils.Aqua("API URL"),
GraphqlURL: " " + utils.Aqua("GraphQL URL"),
StorageS3URL: " " + utils.Aqua("S3 Storage URL"),
McpURL: " " + utils.Aqua("MCP URL"),
DbURL: " " + utils.Aqua("Database URL"),
StudioURL: " " + utils.Aqua("Studio URL"),
InbucketURL: " " + utils.Aqua("Inbucket URL"),
MailpitURL: " " + utils.Aqua("Mailpit URL"),
PublishableKey: " " + utils.Aqua("Publishable key"),
SecretKey: " " + utils.Aqua("Secret key"),
JWTSecret: " " + utils.Aqua("JWT secret"),
AnonKey: " " + utils.Aqua("anon key"),
ServiceRoleKey: "" + utils.Aqua("service_role key"),
StorageS3AccessKeyId: " " + utils.Aqua("S3 Access Key"),
StorageS3SecretAccessKey: " " + utils.Aqua("S3 Secret Key"),
StorageS3Region: " " + utils.Aqua("S3 Region"),
ApiURL: "API_URL",
RestURL: "REST_URL",
GraphqlURL: "GRAPHQL_URL",
FunctionsURL: "FUNCTIONS_URL",
StorageS3URL: "STORAGE_S3_URL",
McpURL: "MCP_URL",
DbURL: "DB_URL",
StudioURL: "STUDIO_URL",
InbucketURL: "INBUCKET_URL",
MailpitURL: "MAILPIT_URL",
PublishableKey: "PUBLISHABLE_KEY",
SecretKey: "SECRET_KEY",
JWTSecret: "JWT_SECRET",
AnonKey: "ANON_KEY",
ServiceRoleKey: "SERVICE_ROLE_KEY",
StorageS3AccessKeyId: "S3_PROTOCOL_ACCESS_KEY_ID",
StorageS3SecretAccessKey: "S3_PROTOCOL_SECRET_ACCESS_KEY",
StorageS3Region: "S3_PROTOCOL_REGION",
}
values := names.toValues(exclude...)
// Iterate through map in order of declared struct fields
t := reflect.TypeOf(names)
val := reflect.ValueOf(names)
for i := 0; i < val.NumField(); i++ {
k := val.Field(i).String()
if tag := t.Field(i).Tag.Get("env"); isDeprecated(tag) {
continue

groups := []OutputGroup{
{
Name: "🛠️ Development Tools",
Items: []OutputItem{
{Label: "Studio", Value: values[names.StudioURL], Type: Link},
{Label: "Mailpit", Value: values[names.MailpitURL], Type: Link},
{Label: "MCP", Value: values[names.McpURL], Type: Link},
},
},
{
Name: "🌐 APIs",
Items: []OutputItem{
{Label: "Project URL", Value: values[names.ApiURL], Type: Link},
{Label: "REST", Value: values[names.RestURL], Type: Link},
{Label: "GraphQL", Value: values[names.GraphqlURL], Type: Link},
{Label: "Edge Functions", Value: values[names.FunctionsURL], Type: Link},
},
},
{
Name: "🗄️ Database",
Items: []OutputItem{
{Label: "URL", Value: values[names.DbURL], Type: Link},
},
},
{
Name: "🔑 Authentication Keys",
Items: []OutputItem{
{Label: "Publishable", Value: values[names.PublishableKey], Type: Key},
{Label: "Secret", Value: values[names.SecretKey], Type: Key},
},
},
{
Name: "📦 Storage (S3)",
Items: []OutputItem{
{Label: "URL", Value: values[names.StorageS3URL], Type: Link},
{Label: "Access Key", Value: values[names.StorageS3AccessKeyId], Type: Key},
{Label: "Secret Key", Value: values[names.StorageS3SecretAccessKey], Type: Key},
{Label: "Region", Value: values[names.StorageS3Region], Type: Text},
},
},
}

for _, group := range groups {
// ensure at least one item in the group is non-empty
shouldPrint := false
for _, item := range group.Items {
if item.Value != "" {
shouldPrint = true
break
}
}
if v, ok := values[k]; ok {
fmt.Fprintf(w, "%s: %s\n", k, v)
if shouldPrint {
printTable(w, group.Name, group.Items)
fmt.Fprintln(w)
}
}
}

func isDeprecated(tag string) bool {
for part := range strings.SplitSeq(tag, ",") {
if strings.EqualFold(part, "deprecated") {
return true
func printTable(w io.Writer, title string, rows []OutputItem) {
table := tablewriter.NewTable(w,
// Rounded corners
tablewriter.WithSymbols(tw.NewSymbols(tw.StyleRounded)),

// Table content formatting
tablewriter.WithConfig(tablewriter.Config{
Header: tw.CellConfig{
Formatting: tw.CellFormatting{
AutoFormat: tw.Off,
MergeMode: tw.MergeHorizontal,
},
Alignment: tw.CellAlignment{
Global: tw.AlignLeft,
},
Filter: tw.CellFilter{
Global: func(s []string) []string {
for i := range s {
s[i] = utils.Bold(s[i])
}
return s
},
},
},
Row: tw.CellConfig{
Alignment: tw.CellAlignment{
Global: tw.AlignLeft,
},
ColMaxWidths: tw.CellWidth{
PerColumn: map[int]int{0: 16},
},
Filter: tw.CellFilter{
PerColumn: []func(string) string{
func(s string) string {
return utils.Green(s)
},
},
},
},
Behavior: tw.Behavior{
Compact: tw.Compact{
Merge: tw.On,
},
},
}),
)

// Set title as header (merged across all columns)
table.Header(title, title)

var appendError error

// Add data rows with values colored based on type
for _, row := range rows {
if row.Value != "" {
switch row.Type {
case Link:
appendError = table.Append(row.Label, utils.Aqua(row.Value))
case Key:
appendError = table.Append(row.Label, utils.Yellow(row.Value))
case Text:
appendError = table.Append(row.Label, row.Value)
}
}
}
return false

if appendError != nil {
fmt.Fprintln(utils.GetDebugLogger(), appendError)
}

renderError := table.Render()
if renderError != nil {
fmt.Fprintln(utils.GetDebugLogger(), renderError)
}
}
4 changes: 4 additions & 0 deletions internal/utils/colors.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ func Yellow(str string) string {
return lipgloss.NewStyle().Foreground(lipgloss.Color("11")).Render(str)
}

func Green(str string) string {
return lipgloss.NewStyle().Foreground(lipgloss.Color("10")).Render(str)
}

// For errors.
func Red(str string) string {
return lipgloss.NewStyle().Foreground(lipgloss.Color("9")).Render(str)
Expand Down
33 changes: 7 additions & 26 deletions pkg/api/types.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.