Skip to content

Commit 6e9c0d7

Browse files
committed
Show styled Docker error in all modes, including interactive
1 parent 273738e commit 6e9c0d7

7 files changed

Lines changed: 58 additions & 15 deletions

File tree

cmd/logs.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ func newLogsCmd(cfg *env.Env, tel *telemetry.Client) *cobra.Command {
3333
if err != nil {
3434
return err
3535
}
36+
if err := checkRuntimeHealth(cmd.Context(), rt, cfg); err != nil {
37+
return err
38+
}
3639
appConfig, err := config.Get()
3740
if err != nil {
3841
return fmt.Errorf("failed to get config: %w", err)

cmd/root.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,17 @@ func isInteractiveMode(cfg *env.Env) bool {
185185
return !cfg.NonInteractive && ui.IsInteractive()
186186
}
187187

188+
// checkRuntimeHealth checks if the runtime is healthy and emits an error
189+
// through the sink if not in interactive mode. Returns a SilentError to
190+
// suppress duplicate error printing.
191+
func checkRuntimeHealth(ctx context.Context, rt runtime.Runtime, cfg *env.Env) error {
192+
if err := rt.IsHealthy(ctx); err != nil {
193+
rt.EmitUnhealthyError(output.NewPlainSink(os.Stdout), err)
194+
return output.NewSilentError(err)
195+
}
196+
return nil
197+
}
198+
188199
const maxLogSize = 1 << 20 // 1 MB
189200

190201
func newLogger() (log.Logger, func(), error) {

cmd/status.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,19 @@ func newStatusCmd(cfg *env.Env, tel *telemetry.Client) *cobra.Command {
2525
RunE: commandWithTelemetry("status", tel, func(cmd *cobra.Command, args []string) error {
2626
rt, err := runtime.NewDockerRuntime(cfg.DockerHost)
2727
if err != nil {
28+
output.EmitError(output.NewPlainSink(os.Stdout), output.ErrorEvent{
29+
Title: "Docker is not available",
30+
Summary: err.Error(),
31+
Actions: []output.ErrorAction{
32+
{Label: "See help:", Value: "lstk -h"},
33+
{Label: "Install Docker:", Value: "https://docs.docker.com/get-docker/"},
34+
},
35+
})
36+
return output.NewSilentError(err)
37+
}
38+
if err := checkRuntimeHealth(cmd.Context(), rt, cfg); err != nil {
2839
return err
2940
}
30-
3141
appCfg, err := config.Get()
3242
if err != nil {
3343
return fmt.Errorf("failed to get config: %w", err)

cmd/stop.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ func newStopCmd(cfg *env.Env, tel *telemetry.Client) *cobra.Command {
2828
if err != nil {
2929
return err
3030
}
31+
if err := checkRuntimeHealth(cmd.Context(), rt, cfg); err != nil {
32+
return err
33+
}
3134
appConfig, err := config.Get()
3235
if err != nil {
3336
return fmt.Errorf("failed to get config: %w", err)

internal/output/plain_format.go

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ import (
44
"fmt"
55
"strings"
66
"time"
7+
8+
"github.com/charmbracelet/lipgloss"
9+
)
10+
11+
var (
12+
errorTitleStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#C33820"))
13+
errorSecondaryStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241"))
14+
errorActionStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("69"))
15+
errorValueStyle = lipgloss.NewStyle().Bold(true)
16+
errorMutedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("245"))
717
)
818

919
// FormatEventLine converts an output event into a single display line.
@@ -130,21 +140,27 @@ func formatMessageEvent(e MessageEvent) string {
130140

131141
func formatErrorEvent(e ErrorEvent) string {
132142
var sb strings.Builder
133-
sb.WriteString("Error: ")
134-
sb.WriteString(e.Title)
143+
sb.WriteString(errorTitleStyle.Render("✗ " + e.Title))
135144
if e.Summary != "" {
136-
sb.WriteString("\n ")
145+
sb.WriteString("\n")
146+
sb.WriteString(errorSecondaryStyle.Render("> "))
137147
sb.WriteString(e.Summary)
138148
}
139149
if e.Detail != "" {
140150
sb.WriteString("\n ")
141-
sb.WriteString(e.Detail)
151+
sb.WriteString(errorMutedStyle.Render(e.Detail))
142152
}
143-
for _, action := range e.Actions {
144-
sb.WriteString("\n " + ErrorActionPrefix)
145-
sb.WriteString(action.Label)
146-
sb.WriteString(" ")
147-
sb.WriteString(action.Value)
153+
if len(e.Actions) > 0 {
154+
sb.WriteString("\n")
155+
for i, action := range e.Actions {
156+
sb.WriteString("\n")
157+
if i > 0 {
158+
sb.WriteString(errorMutedStyle.Render(ErrorActionPrefix + action.Label + " " + action.Value))
159+
} else {
160+
sb.WriteString(errorActionStyle.Render(ErrorActionPrefix+action.Label+" "))
161+
sb.WriteString(errorValueStyle.Render(action.Value))
162+
}
163+
}
148164
}
149165
return sb.String()
150166
}

internal/output/plain_format_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,19 +84,19 @@ func TestFormatEventLine(t *testing.T) {
8484
{
8585
name: "error event title only",
8686
event: ErrorEvent{Title: "Connection failed"},
87-
want: "Error: Connection failed",
87+
want: " Connection failed",
8888
wantOK: true,
8989
},
9090
{
9191
name: "error event with summary",
9292
event: ErrorEvent{Title: "Auth failed", Summary: "Invalid token"},
93-
want: "Error: Auth failed\n Invalid token",
93+
want: " Auth failed\n> Invalid token",
9494
wantOK: true,
9595
},
9696
{
9797
name: "error event with detail",
9898
event: ErrorEvent{Title: "Auth failed", Summary: "Invalid token", Detail: "Token expired at 2024-01-01"},
99-
want: "Error: Auth failed\n Invalid token\n Token expired at 2024-01-01",
99+
want: " Auth failed\n> Invalid token\n Token expired at 2024-01-01",
100100
wantOK: true,
101101
},
102102
{
@@ -108,7 +108,7 @@ func TestFormatEventLine(t *testing.T) {
108108
{Label: "Start Docker:", Value: "open -a Docker"},
109109
},
110110
},
111-
want: "Error: Docker not running\n Cannot connect to Docker daemon\n ==> Start Docker: open -a Docker",
111+
want: " Docker not running\n> Cannot connect to Docker daemon\n\n==> Start Docker: open -a Docker",
112112
wantOK: true,
113113
},
114114
{

internal/output/plain_sink_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ func TestPlainSink_EmitsErrorEvent(t *testing.T) {
146146
Actions: []ErrorAction{{Label: "Start Docker:", Value: "open -a Docker"}},
147147
})
148148

149-
expected := "Error: Connection failed\n Cannot connect to Docker\n ==> Start Docker: open -a Docker\n"
149+
expected := " Connection failed\n> Cannot connect to Docker\n\n==> Start Docker: open -a Docker\n"
150150
assert.Equal(t, expected, out.String())
151151
}
152152

0 commit comments

Comments
 (0)