From c4e92ec3418d3ebae79229c4453eb6b0ff768838 Mon Sep 17 00:00:00 2001 From: Susana Cardoso Ferreira Date: Fri, 28 Aug 2026 16:02:53 +0000 Subject: [PATCH] fix(cli): improve confirmation prompt handling --- cli/cliui/prompt.go | 8 +++++-- cli/cliui/prompt_test.go | 47 ++++++++++++++++++++++++++++++++++++++++ cli/root.go | 1 + 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/cli/cliui/prompt.go b/cli/cliui/prompt.go index ca038db3de5..803ce1ac3c6 100644 --- a/cli/cliui/prompt.go +++ b/cli/cliui/prompt.go @@ -125,8 +125,12 @@ func Prompt(inv *serpent.Invocation, opts PromptOptions) (string, error) { case err := <-errCh: return "", err case line := <-lineCh: - if opts.IsConfirm && line != "yes" && line != "y" { - return line, xerrors.Errorf("got %q: %w", line, ErrCanceled) + if opts.IsConfirm { + answer := strings.ToLower(strings.TrimSpace(line)) + if answer != ConfirmYes && answer != "y" { + return line, xerrors.Errorf("got %q: %w", line, ErrCanceled) + } + line = ConfirmYes } if opts.Validate != nil { err := opts.Validate(line) diff --git a/cli/cliui/prompt_test.go b/cli/cliui/prompt_test.go index 90f6fade9b1..b3a2f0cdaba 100644 --- a/cli/cliui/prompt_test.go +++ b/cli/cliui/prompt_test.go @@ -58,6 +58,53 @@ func TestPrompt(t *testing.T) { require.Equal(t, "yes", resp) }) + t.Run("ConfirmNormalizesInput", func(t *testing.T) { + t.Parallel() + + for _, input := range []string{"Yes", "YES", " yes ", "Y", " y "} { + input := input + t.Run(input, func(t *testing.T) { + t.Parallel() + + ctx := testutil.Context(t, testutil.WaitShort) + ptty := ptytest.New(t) + doneChan := make(chan string) + go func() { + resp, err := newPrompt(ctx, ptty, cliui.PromptOptions{ + Text: "Example", + IsConfirm: true, + }, nil) + assert.NoError(t, err) + doneChan <- resp + }() + ptty.ExpectMatch(ctx, "Example") + ptty.WriteLine(input) + resp := testutil.TryReceive(ctx, t, doneChan) + require.Equal(t, "yes", resp) + }) + } + }) + + t.Run("ConfirmDefaultNoReturnsCanceled", func(t *testing.T) { + t.Parallel() + + ctx := testutil.Context(t, testutil.WaitShort) + ptty := ptytest.New(t) + doneChan := make(chan error) + go func() { + _, err := newPrompt(ctx, ptty, cliui.PromptOptions{ + Text: "Example", + IsConfirm: true, + Default: cliui.ConfirmNo, + }, nil) + doneChan <- err + }() + ptty.ExpectMatch(ctx, "Example") + ptty.WriteLine("") + err := testutil.TryReceive(ctx, t, doneChan) + require.ErrorIs(t, err, cliui.ErrCanceled) + }) + t.Run("Skip", func(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) diff --git a/cli/root.go b/cli/root.go index 747e01f971e..619eeca87ed 100644 --- a/cli/root.go +++ b/cli/root.go @@ -228,6 +228,7 @@ func (r *RootCmd) RunWithSubcommands(subcommands []*serpent.Command) { err = exitErr.err } if errors.Is(err, cliui.ErrCanceled) { + _, _ = fmt.Fprintln(os.Stderr, "Aborted.") //nolint:revive,gocritic os.Exit(code) }