From df608514497df723ea78b637d7a9ef52ca48d29a Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Wed, 4 Mar 2026 01:10:07 +0200 Subject: [PATCH 1/2] Migrate stop command to output event system --- cmd/stop.go | 16 ++++++++++--- internal/container/stop.go | 17 ++++++++------ internal/ui/run_stop.go | 47 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 internal/ui/run_stop.go diff --git a/cmd/stop.go b/cmd/stop.go index aaa5c4d..480a46e 100644 --- a/cmd/stop.go +++ b/cmd/stop.go @@ -5,7 +5,9 @@ import ( "os" "github.com/localstack/lstk/internal/container" + "github.com/localstack/lstk/internal/output" "github.com/localstack/lstk/internal/runtime" + "github.com/localstack/lstk/internal/ui" "github.com/spf13/cobra" ) @@ -22,14 +24,22 @@ func newStopCmd() *cobra.Command { os.Exit(1) } - onProgress := func(msg string) { - fmt.Println(msg) + if ui.IsInteractive() { + if err := ui.RunStop(cmd.Context(), rt); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + return } - if err := container.Stop(cmd.Context(), rt, onProgress); err != nil { + if err := container.Stop(cmd.Context(), rt, output.NewPlainSink(os.Stdout)); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } }, } } + +func init() { + rootCmd.AddCommand(newStopCmd()) +} diff --git a/internal/container/stop.go b/internal/container/stop.go index cdac02d..7af4319 100644 --- a/internal/container/stop.go +++ b/internal/container/stop.go @@ -4,12 +4,12 @@ import ( "context" "fmt" - "github.com/containerd/errdefs" "github.com/localstack/lstk/internal/config" + "github.com/localstack/lstk/internal/output" "github.com/localstack/lstk/internal/runtime" ) -func Stop(ctx context.Context, rt runtime.Runtime, onProgress func(string)) error { +func Stop(ctx context.Context, rt runtime.Runtime, sink output.Sink) error { cfg, err := config.Get() if err != nil { return fmt.Errorf("failed to get config: %w", err) @@ -17,14 +17,17 @@ func Stop(ctx context.Context, rt runtime.Runtime, onProgress func(string)) erro for _, c := range cfg.Containers { name := c.Name() - onProgress(fmt.Sprintf("Stopping %s...", name)) + running, err := rt.IsRunning(ctx, name) + if err != nil || !running { + return fmt.Errorf("%s is not running", name) + } + output.EmitSpinnerStart(sink, fmt.Sprintf("Stopping %s", name)) if err := rt.Stop(ctx, name); err != nil { - if errdefs.IsNotFound(err) { - return fmt.Errorf("%s is not running", name) - } + output.EmitSpinnerStop(sink) return fmt.Errorf("failed to stop %s: %w", name, err) } - onProgress(fmt.Sprintf("%s stopped", name)) + output.EmitSpinnerStop(sink) + output.EmitSuccess(sink, fmt.Sprintf("%s stopped", name)) } return nil diff --git a/internal/ui/run_stop.go b/internal/ui/run_stop.go new file mode 100644 index 0000000..b917a93 --- /dev/null +++ b/internal/ui/run_stop.go @@ -0,0 +1,47 @@ +package ui + +import ( + "context" + "errors" + "os" + + tea "github.com/charmbracelet/bubbletea" + "github.com/localstack/lstk/internal/container" + "github.com/localstack/lstk/internal/output" + "github.com/localstack/lstk/internal/runtime" +) + +func RunStop(parentCtx context.Context, rt runtime.Runtime) error { + _, cancel := context.WithCancel(parentCtx) + defer cancel() + + app := NewApp("", "", "", cancel) + p := tea.NewProgram(app, tea.WithInput(os.Stdin), tea.WithOutput(os.Stdout)) + runErrCh := make(chan error, 1) + + go func() { + err := container.Stop(parentCtx, rt, output.NewTUISink(programSender{p: p})) + runErrCh <- err + if err != nil && !errors.Is(err, context.Canceled) { + p.Send(runErrMsg{err: err}) + return + } + p.Send(runDoneMsg{}) + }() + + model, err := p.Run() + if err != nil { + return err + } + + if app, ok := model.(App); ok && app.Err() != nil { + return app.Err() + } + + runErr := <-runErrCh + if runErr != nil && !errors.Is(runErr, context.Canceled) { + return runErr + } + + return nil +} From 2be6f47e0691f53ba2f69f7cabd627a1beca7deb Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Wed, 4 Mar 2026 13:19:56 +0200 Subject: [PATCH 2/2] Review comments --- cmd/stop.go | 3 --- internal/container/stop.go | 5 ++++- internal/ui/run_stop.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/stop.go b/cmd/stop.go index 480a46e..33bda33 100644 --- a/cmd/stop.go +++ b/cmd/stop.go @@ -40,6 +40,3 @@ func newStopCmd() *cobra.Command { } } -func init() { - rootCmd.AddCommand(newStopCmd()) -} diff --git a/internal/container/stop.go b/internal/container/stop.go index 7af4319..1191b76 100644 --- a/internal/container/stop.go +++ b/internal/container/stop.go @@ -18,7 +18,10 @@ func Stop(ctx context.Context, rt runtime.Runtime, sink output.Sink) error { for _, c := range cfg.Containers { name := c.Name() running, err := rt.IsRunning(ctx, name) - if err != nil || !running { + if err != nil { + return fmt.Errorf("checking %s running: %w", name, err) + } + if !running { return fmt.Errorf("%s is not running", name) } output.EmitSpinnerStart(sink, fmt.Sprintf("Stopping %s", name)) diff --git a/internal/ui/run_stop.go b/internal/ui/run_stop.go index b917a93..1c4c417 100644 --- a/internal/ui/run_stop.go +++ b/internal/ui/run_stop.go @@ -12,7 +12,7 @@ import ( ) func RunStop(parentCtx context.Context, rt runtime.Runtime) error { - _, cancel := context.WithCancel(parentCtx) + ctx, cancel := context.WithCancel(parentCtx) defer cancel() app := NewApp("", "", "", cancel) @@ -20,7 +20,7 @@ func RunStop(parentCtx context.Context, rt runtime.Runtime) error { runErrCh := make(chan error, 1) go func() { - err := container.Stop(parentCtx, rt, output.NewTUISink(programSender{p: p})) + err := container.Stop(ctx, rt, output.NewTUISink(programSender{p: p})) runErrCh <- err if err != nil && !errors.Is(err, context.Canceled) { p.Send(runErrMsg{err: err})