Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -39,6 +39,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 @@ -315,7 +316,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 v1.9.5 // indirect
Expand Down
9 changes: 6 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,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 @@ -829,8 +828,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
189 changes: 156 additions & 33 deletions internal/status/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ import (
"net/http"
"net/url"
"os"
"reflect"
"slices"
"strings"
"sync"
"time"

"github.com/Netflix/go-env"
"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 @@ -27,9 +28,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 @@ -54,10 +57,15 @@ func (c *CustomName) toValues(exclude ...string) map[string]string {
authEnabled := utils.Config.Auth.Enabled && !slices.Contains(exclude, utils.GotrueId) && !slices.Contains(exclude, utils.ShortContainerImageName(utils.Config.Auth.Image))
inbucketEnabled := utils.Config.Inbucket.Enabled && !slices.Contains(exclude, utils.InbucketId) && !slices.Contains(exclude, utils.ShortContainerImageName(utils.Config.Inbucket.Image))
storageEnabled := utils.Config.Storage.Enabled && !slices.Contains(exclude, utils.StorageId) && !slices.Contains(exclude, utils.ShortContainerImageName(utils.Config.Storage.Image))
functionsEnabled := utils.Config.EdgeRuntime.Enabled && !slices.Contains(exclude, utils.EdgeRuntimeId) && !slices.Contains(exclude, utils.ShortContainerImageName(utils.Config.EdgeRuntime.Image))

if apiEnabled {
values[c.ApiURL] = utils.Config.Api.ExternalUrl
values[c.RestURL] = utils.GetApiUrl("/rest/v1")
values[c.GraphqlURL] = utils.GetApiUrl("/graphql/v1")
if functionsEnabled {
values[c.FunctionsURL] = utils.GetApiUrl("/functions/v1")
}
if studioEnabled {
values[c.McpURL] = utils.GetApiUrl("/mcp")
}
Expand Down Expand Up @@ -210,44 +218,159 @@ 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"),
logger := utils.GetDebugLogger()

names := CustomName{}
if err := env.Unmarshal(env.EnvSet{}, &names); err != nil {
fmt.Fprintln(logger, err)
}
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) {

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 {
if err := group.printTable(w); err != nil {
fmt.Fprintln(logger, err)
} else {
fmt.Fprintln(w)
}
}
}

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 (g *OutputGroup) printTable(w io.Writer) error {
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)
tablewriter.WithHeader([]string{g.Name, g.Name}),
)

// Add data rows with values colored based on type
shouldRender := false
for _, row := range g.Items {
if row.Value == "" {
continue
}
if v, ok := values[k]; ok {
fmt.Fprintf(w, "%s: %s\n", k, v)
value := row.Value
switch row.Type {
case Link:
value = utils.Aqua(row.Value)
case Key:
value = utils.Yellow(row.Value)
}
if err := table.Append(row.Label, value); err != nil {
return errors.Errorf("failed to append row: %w", err)
}
shouldRender = true
}
}

func isDeprecated(tag string) bool {
for part := range strings.SplitSeq(tag, ",") {
if strings.EqualFold(part, "deprecated") {
return true
// Ensure at least one item in the group is non-empty
if shouldRender {
if err := table.Render(); err != nil {
return errors.Errorf("failed to render table: %w", err)
}
}
return false

return nil
}
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