Skip to content
Open
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 @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We currently use markdown table renderer from bubbletea

func RenderTable(markdown string) error {

Shall we stick with it for consistency?

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.