diff --git a/tools/homarr-dev/internal/tui/dev.go b/tools/homarr-dev/internal/tui/dev.go index 763ca9240..6a41d9d9a 100644 --- a/tools/homarr-dev/internal/tui/dev.go +++ b/tools/homarr-dev/internal/tui/dev.go @@ -4,7 +4,6 @@ import ( "bufio" "context" "fmt" - "io" "os" "os/exec" "path/filepath" @@ -129,6 +128,27 @@ type pullEvent struct { err error } +type outputTail struct { + data []byte +} + +func (tail *outputTail) Write(data []byte) (int, error) { + const limit = 32 * 1024 + tail.data = append(tail.data, data...) + if len(tail.data) > limit { + tail.data = tail.data[len(tail.data)-limit:] + } + return len(data), nil +} + +func (tail *outputTail) lastLine() string { + lines := strings.FieldsFunc(string(tail.data), func(char rune) bool { return char == '\n' || char == '\r' }) + if len(lines) == 0 { + return "" + } + return strings.TrimSpace(lines[len(lines)-1]) +} + func splitDockerProgress(data []byte, atEOF bool) (advance int, token []byte, err error) { for i, b := range data { if b == '\n' || b == '\r' { @@ -174,7 +194,7 @@ func startPull(ctx context.Context, cmd *exec.Cmd, events chan<- pullEvent) tea. if err == nil { err = scanner.Err() } else { - err = pullCommandError(err, lastLine) + err = commandErrorWithDetail(err, lastLine) } events <- pullEvent{done: true, err: err} }() @@ -182,7 +202,7 @@ func startPull(ctx context.Context, cmd *exec.Cmd, events chan<- pullEvent) tea. } } -func pullCommandError(err error, lastLine string) error { +func commandErrorWithDetail(err error, lastLine string) error { if lastLine == "" { return err } @@ -928,7 +948,11 @@ func (m prsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.rebuildCancel = cancel m.status = status rebuild := func() tea.Msg { - err := run.RebuildImageContext(ctx, image, io.Discard, io.Discard) + output := &outputTail{} + err := run.RebuildImageContext(ctx, image, output, output) + if err != nil { + err = commandErrorWithDetail(err, output.lastLine()) + } return rebuildDoneMsg{image: image, err: err, canceled: ctx.Err() != nil, action: action} } return m, tea.Batch(rebuild, m.spinner.Tick) diff --git a/tools/homarr-dev/internal/tui/dev_test.go b/tools/homarr-dev/internal/tui/dev_test.go index 2aa3252d6..ecd8d85bf 100644 --- a/tools/homarr-dev/internal/tui/dev_test.go +++ b/tools/homarr-dev/internal/tui/dev_test.go @@ -137,12 +137,20 @@ func TestPRImageCheckInProgressDoesNotLaunch(t *testing.T) { } func TestPullErrorIncludesLastDockerMessage(t *testing.T) { - err := pullCommandError(errors.New("exit status 1"), "manifest unknown") + err := commandErrorWithDetail(errors.New("exit status 1"), "manifest unknown") if !strings.Contains(err.Error(), "manifest unknown") { t.Fatalf("pull error = %v", err) } } +func TestOutputTailRetainsBuildFailure(t *testing.T) { + tail := &outputTail{} + _, _ = tail.Write([]byte("building\r\nERROR: Dockerfile not found\n")) + if got := tail.lastLine(); got != "ERROR: Dockerfile not found" { + t.Fatalf("last line = %q", got) + } +} + func TestDevTableRendersLocalImageSource(t *testing.T) { m := newPRsModel() m.rows = []prRow{{