fix: address Plan 2 final-review findings

- tui/doc.go: remove teatest blank import (test harness in prod binary)
- cmd: honor wizard HaltOnError toggle in addition to --halt-on-error flag
- cmd: wire SIGINT/SIGTERM to root context via signal.NotifyContext
- dashboard/run: derive engine context and cancel on tea.Program exit
  so the engine goroutine does not outlive Run
This commit is contained in:
MDietermann
2026-04-13 22:26:54 +02:00
parent ea5667e040
commit 2ebaa6b5c6
3 changed files with 20 additions and 10 deletions

View File

@@ -4,6 +4,8 @@ import (
"context" "context"
"fmt" "fmt"
"os" "os"
"os/signal"
"syscall"
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -41,7 +43,13 @@ func main() {
Version: version, Version: version,
} }
root.AddCommand(newInstallCmd()) root.AddCommand(newInstallCmd())
if err := root.Execute(); err != nil {
// Wire SIGINT/SIGTERM to a cancellable context so long-running phases
// (engine.Run, sudo keepalive) observe the cancellation promptly.
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
if err := root.ExecuteContext(ctx); err != nil {
fmt.Fprintln(os.Stderr, "error:", err) fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1) os.Exit(1)
} }
@@ -140,7 +148,8 @@ func newInstallCmd() *cobra.Command {
fmt.Fprintln(os.Stdout, "installation cancelled") fmt.Fprintln(os.Stdout, "installation cancelled")
return nil return nil
} }
sum, runErr = dashboard.Run(ctx, Registry, finalState, halt) // Either the CLI flag or the wizard toggle can force halt-on-error.
sum, runErr = dashboard.Run(ctx, Registry, finalState, halt || finalState.HaltOnError)
} }
ended := time.Now().UTC() ended := time.Now().UTC()

View File

@@ -23,10 +23,18 @@ func Run(ctx context.Context, reg *module.Registry, state tui.WizardState, halt
m := NewModel(selected, nameByID) m := NewModel(selected, nameByID)
p := tea.NewProgram(m, tea.WithContext(ctx)) p := tea.NewProgram(m, tea.WithContext(ctx))
// Derived context so that when p.Run() returns (user pressed q, tea
// errored, parent ctx cancelled) the engine goroutine is cancelled
// too. Deferred cancel ensures no orphan goroutine outlives Run.
engineCtx, cancelEngine := context.WithCancel(ctx)
defer cancelEngine()
reporter := tui.NewTeaReporter(p) reporter := tui.NewTeaReporter(p)
go func() { go func() {
summary, err := engine.Run(ctx, reg, engine.RunOptions{ // TODO(plan-3): forward state.Parallelism once engine.RunOptions
// gains a Parallelism field and the engine supports concurrent Apply.
summary, err := engine.Run(engineCtx, reg, engine.RunOptions{
Selected: selected, Selected: selected,
HaltOnError: halt, HaltOnError: halt,
Reporter: reporter, Reporter: reporter,

View File

@@ -2,10 +2,3 @@
// that collects user choices and the Bubble Tea dashboard that renders // that collects user choices and the Bubble Tea dashboard that renders
// live module status during engine.Run. // live module status during engine.Run.
package tui package tui
import (
_ "github.com/charmbracelet/bubbletea"
_ "github.com/charmbracelet/huh"
_ "github.com/charmbracelet/lipgloss"
_ "github.com/charmbracelet/x/exp/teatest"
)