diff --git a/context/context.go b/context/context.go index 40a9383a3..dc5f71a3a 100644 --- a/context/context.go +++ b/context/context.go @@ -119,6 +119,7 @@ func (r *ResolvedRemotes) BaseRepo(io *iostreams.IOStreams) (ghrepo.Interface, e // hide the spinner in case a command started the progress indicator before base repo was fully // resolved, e.g. in `gh issue view` io.StopProgressIndicator() + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err := prompt.SurveyAskOne(&survey.Select{ Message: "Which should be the base repository (used for e.g. querying issues) for this directory?", Options: repoNames, diff --git a/internal/ghinstance/host.go b/internal/ghinstance/host.go index b96852a4d..30dafb796 100644 --- a/internal/ghinstance/host.go +++ b/internal/ghinstance/host.go @@ -36,12 +36,7 @@ func NormalizeHostname(h string) string { return hostname } -func HostnameValidator(v interface{}) error { - hostname, valid := v.(string) - if !valid { - return errors.New("hostname is not a string") - } - +func HostnameValidator(hostname string) error { if len(strings.TrimSpace(hostname)) < 1 { return errors.New("a value is required") } diff --git a/internal/ghinstance/host_test.go b/internal/ghinstance/host_test.go index d29cd45ea..5feecbe63 100644 --- a/internal/ghinstance/host_test.go +++ b/internal/ghinstance/host_test.go @@ -95,7 +95,7 @@ func TestNormalizeHostname(t *testing.T) { func TestHostnameValidator(t *testing.T) { tests := []struct { name string - input interface{} + input string wantsErr bool }{ { @@ -118,11 +118,6 @@ func TestHostnameValidator(t *testing.T) { input: "internal.instance:2205", wantsErr: true, }, - { - name: "non-string hostname", - input: 62, - wantsErr: true, - }, } for _, tt := range tests { diff --git a/internal/prompter/prompter.go b/internal/prompter/prompter.go new file mode 100644 index 000000000..49516bd6a --- /dev/null +++ b/internal/prompter/prompter.go @@ -0,0 +1,135 @@ +package prompter + +import ( + "fmt" + "io" + + "github.com/AlecAivazis/survey/v2" + "github.com/AlecAivazis/survey/v2/terminal" + "github.com/cli/cli/v2/internal/ghinstance" + "github.com/cli/cli/v2/pkg/surveyext" +) + +//go:generate moq -rm -out prompter_mock.go . Prompter +type Prompter interface { + Select(string, string, []string) (int, error) + MultiSelect(string, string, []string) (int, error) + Input(string, string) (string, error) + InputHostname() (string, error) + Password(string) (string, error) + AuthToken() (string, error) + Confirm(string, bool) (bool, error) + MarkdownEditor(string, string, bool) (string, error) +} + +func New(editorCmd string, stdin io.Reader, stdout, stderr io.Writer) Prompter { + return &surveyPrompter{ + editorCmd: editorCmd, + stdin: stdin.(terminal.FileReader), + stdout: stdout.(terminal.FileWriter), + stderr: stderr, + } +} + +type surveyPrompter struct { + editorCmd string + stdin terminal.FileReader + stdout terminal.FileWriter + stderr io.Writer +} + +func (p *surveyPrompter) Select(message, defaultValue string, options []string) (result int, err error) { + q := &survey.Select{ + Message: message, + Default: defaultValue, + Options: options, + PageSize: 20, + } + + err = p.ask(q, &result) + + return +} + +func (p *surveyPrompter) MultiSelect(message, defaultValue string, options []string) (result int, err error) { + q := &survey.MultiSelect{ + Message: message, + Default: defaultValue, + Options: options, + PageSize: 20, + } + + err = p.ask(q, &result) + + return +} + +func (p *surveyPrompter) ask(q survey.Prompt, response interface{}, opts ...survey.AskOpt) error { + opts = append(opts, survey.WithStdio(p.stdin, p.stdout, p.stderr)) + err := survey.AskOne(q, response, opts...) + if err == nil { + return nil + } + return fmt.Errorf("could not prompt: %w", err) +} + +func (p *surveyPrompter) Input(prompt, defaultValue string) (result string, err error) { + err = p.ask(&survey.Input{ + Message: prompt, + Default: defaultValue, + }, &result) + + return +} + +func (p *surveyPrompter) InputHostname() (result string, err error) { + err = p.ask( + &survey.Input{ + Message: "GHE hostname:", + }, &result, survey.WithValidator(func(v interface{}) error { + return ghinstance.HostnameValidator(v.(string)) + })) + + return +} + +func (p *surveyPrompter) Password(prompt string) (result string, err error) { + err = p.ask(&survey.Password{ + Message: prompt, + }, &result) + + return +} + +func (p *surveyPrompter) Confirm(prompt string, defaultValue bool) (result bool, err error) { + err = p.ask(&survey.Confirm{ + Message: prompt, + Default: defaultValue, + }, &result) + + return +} +func (p *surveyPrompter) MarkdownEditor(message, defaultValue string, blankAllowed bool) (result string, err error) { + + err = p.ask(&surveyext.GhEditor{ + BlankAllowed: blankAllowed, + EditorCommand: p.editorCmd, + Editor: &survey.Editor{ + Message: message, + Default: defaultValue, + FileName: "*.md", + HideDefault: true, + AppendDefault: true, + }, + }, &result) + + return +} + +func (p *surveyPrompter) AuthToken() (result string, err error) { + err = p.ask(&survey.Password{ + Message: "Paste your authentication token:", + }, &result, survey.WithValidator(survey.Required)) + + return +} diff --git a/internal/prompter/prompter_mock.go b/internal/prompter/prompter_mock.go new file mode 100644 index 000000000..eea5393b2 --- /dev/null +++ b/internal/prompter/prompter_mock.go @@ -0,0 +1,408 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package prompter + +import ( + "sync" +) + +// Ensure, that PrompterMock does implement Prompter. +// If this is not the case, regenerate this file with moq. +var _ Prompter = &PrompterMock{} + +// PrompterMock is a mock implementation of Prompter. +// +// func TestSomethingThatUsesPrompter(t *testing.T) { +// +// // make and configure a mocked Prompter +// mockedPrompter := &PrompterMock{ +// AuthTokenFunc: func() (string, error) { +// panic("mock out the AuthToken method") +// }, +// ConfirmFunc: func(s string, b bool) (bool, error) { +// panic("mock out the Confirm method") +// }, +// InputFunc: func(s1 string, s2 string) (string, error) { +// panic("mock out the Input method") +// }, +// InputHostnameFunc: func() (string, error) { +// panic("mock out the InputHostname method") +// }, +// MarkdownEditorFunc: func(s1 string, s2 string, b bool) (string, error) { +// panic("mock out the MarkdownEditor method") +// }, +// MultiSelectFunc: func(s1 string, s2 string, strings []string) (int, error) { +// panic("mock out the MultiSelect method") +// }, +// PasswordFunc: func(s string) (string, error) { +// panic("mock out the Password method") +// }, +// SelectFunc: func(s1 string, s2 string, strings []string) (int, error) { +// panic("mock out the Select method") +// }, +// } +// +// // use mockedPrompter in code that requires Prompter +// // and then make assertions. +// +// } +type PrompterMock struct { + // AuthTokenFunc mocks the AuthToken method. + AuthTokenFunc func() (string, error) + + // ConfirmFunc mocks the Confirm method. + ConfirmFunc func(s string, b bool) (bool, error) + + // InputFunc mocks the Input method. + InputFunc func(s1 string, s2 string) (string, error) + + // InputHostnameFunc mocks the InputHostname method. + InputHostnameFunc func() (string, error) + + // MarkdownEditorFunc mocks the MarkdownEditor method. + MarkdownEditorFunc func(s1 string, s2 string, b bool) (string, error) + + // MultiSelectFunc mocks the MultiSelect method. + MultiSelectFunc func(s1 string, s2 string, strings []string) (int, error) + + // PasswordFunc mocks the Password method. + PasswordFunc func(s string) (string, error) + + // SelectFunc mocks the Select method. + SelectFunc func(s1 string, s2 string, strings []string) (int, error) + + // calls tracks calls to the methods. + calls struct { + // AuthToken holds details about calls to the AuthToken method. + AuthToken []struct { + } + // Confirm holds details about calls to the Confirm method. + Confirm []struct { + // S is the s argument value. + S string + // B is the b argument value. + B bool + } + // Input holds details about calls to the Input method. + Input []struct { + // S1 is the s1 argument value. + S1 string + // S2 is the s2 argument value. + S2 string + } + // InputHostname holds details about calls to the InputHostname method. + InputHostname []struct { + } + // MarkdownEditor holds details about calls to the MarkdownEditor method. + MarkdownEditor []struct { + // S1 is the s1 argument value. + S1 string + // S2 is the s2 argument value. + S2 string + // B is the b argument value. + B bool + } + // MultiSelect holds details about calls to the MultiSelect method. + MultiSelect []struct { + // S1 is the s1 argument value. + S1 string + // S2 is the s2 argument value. + S2 string + // Strings is the strings argument value. + Strings []string + } + // Password holds details about calls to the Password method. + Password []struct { + // S is the s argument value. + S string + } + // Select holds details about calls to the Select method. + Select []struct { + // S1 is the s1 argument value. + S1 string + // S2 is the s2 argument value. + S2 string + // Strings is the strings argument value. + Strings []string + } + } + lockAuthToken sync.RWMutex + lockConfirm sync.RWMutex + lockInput sync.RWMutex + lockInputHostname sync.RWMutex + lockMarkdownEditor sync.RWMutex + lockMultiSelect sync.RWMutex + lockPassword sync.RWMutex + lockSelect sync.RWMutex +} + +// AuthToken calls AuthTokenFunc. +func (mock *PrompterMock) AuthToken() (string, error) { + if mock.AuthTokenFunc == nil { + panic("PrompterMock.AuthTokenFunc: method is nil but Prompter.AuthToken was just called") + } + callInfo := struct { + }{} + mock.lockAuthToken.Lock() + mock.calls.AuthToken = append(mock.calls.AuthToken, callInfo) + mock.lockAuthToken.Unlock() + return mock.AuthTokenFunc() +} + +// AuthTokenCalls gets all the calls that were made to AuthToken. +// Check the length with: +// len(mockedPrompter.AuthTokenCalls()) +func (mock *PrompterMock) AuthTokenCalls() []struct { +} { + var calls []struct { + } + mock.lockAuthToken.RLock() + calls = mock.calls.AuthToken + mock.lockAuthToken.RUnlock() + return calls +} + +// Confirm calls ConfirmFunc. +func (mock *PrompterMock) Confirm(s string, b bool) (bool, error) { + if mock.ConfirmFunc == nil { + panic("PrompterMock.ConfirmFunc: method is nil but Prompter.Confirm was just called") + } + callInfo := struct { + S string + B bool + }{ + S: s, + B: b, + } + mock.lockConfirm.Lock() + mock.calls.Confirm = append(mock.calls.Confirm, callInfo) + mock.lockConfirm.Unlock() + return mock.ConfirmFunc(s, b) +} + +// ConfirmCalls gets all the calls that were made to Confirm. +// Check the length with: +// len(mockedPrompter.ConfirmCalls()) +func (mock *PrompterMock) ConfirmCalls() []struct { + S string + B bool +} { + var calls []struct { + S string + B bool + } + mock.lockConfirm.RLock() + calls = mock.calls.Confirm + mock.lockConfirm.RUnlock() + return calls +} + +// Input calls InputFunc. +func (mock *PrompterMock) Input(s1 string, s2 string) (string, error) { + if mock.InputFunc == nil { + panic("PrompterMock.InputFunc: method is nil but Prompter.Input was just called") + } + callInfo := struct { + S1 string + S2 string + }{ + S1: s1, + S2: s2, + } + mock.lockInput.Lock() + mock.calls.Input = append(mock.calls.Input, callInfo) + mock.lockInput.Unlock() + return mock.InputFunc(s1, s2) +} + +// InputCalls gets all the calls that were made to Input. +// Check the length with: +// len(mockedPrompter.InputCalls()) +func (mock *PrompterMock) InputCalls() []struct { + S1 string + S2 string +} { + var calls []struct { + S1 string + S2 string + } + mock.lockInput.RLock() + calls = mock.calls.Input + mock.lockInput.RUnlock() + return calls +} + +// InputHostname calls InputHostnameFunc. +func (mock *PrompterMock) InputHostname() (string, error) { + if mock.InputHostnameFunc == nil { + panic("PrompterMock.InputHostnameFunc: method is nil but Prompter.InputHostname was just called") + } + callInfo := struct { + }{} + mock.lockInputHostname.Lock() + mock.calls.InputHostname = append(mock.calls.InputHostname, callInfo) + mock.lockInputHostname.Unlock() + return mock.InputHostnameFunc() +} + +// InputHostnameCalls gets all the calls that were made to InputHostname. +// Check the length with: +// len(mockedPrompter.InputHostnameCalls()) +func (mock *PrompterMock) InputHostnameCalls() []struct { +} { + var calls []struct { + } + mock.lockInputHostname.RLock() + calls = mock.calls.InputHostname + mock.lockInputHostname.RUnlock() + return calls +} + +// MarkdownEditor calls MarkdownEditorFunc. +func (mock *PrompterMock) MarkdownEditor(s1 string, s2 string, b bool) (string, error) { + if mock.MarkdownEditorFunc == nil { + panic("PrompterMock.MarkdownEditorFunc: method is nil but Prompter.MarkdownEditor was just called") + } + callInfo := struct { + S1 string + S2 string + B bool + }{ + S1: s1, + S2: s2, + B: b, + } + mock.lockMarkdownEditor.Lock() + mock.calls.MarkdownEditor = append(mock.calls.MarkdownEditor, callInfo) + mock.lockMarkdownEditor.Unlock() + return mock.MarkdownEditorFunc(s1, s2, b) +} + +// MarkdownEditorCalls gets all the calls that were made to MarkdownEditor. +// Check the length with: +// len(mockedPrompter.MarkdownEditorCalls()) +func (mock *PrompterMock) MarkdownEditorCalls() []struct { + S1 string + S2 string + B bool +} { + var calls []struct { + S1 string + S2 string + B bool + } + mock.lockMarkdownEditor.RLock() + calls = mock.calls.MarkdownEditor + mock.lockMarkdownEditor.RUnlock() + return calls +} + +// MultiSelect calls MultiSelectFunc. +func (mock *PrompterMock) MultiSelect(s1 string, s2 string, strings []string) (int, error) { + if mock.MultiSelectFunc == nil { + panic("PrompterMock.MultiSelectFunc: method is nil but Prompter.MultiSelect was just called") + } + callInfo := struct { + S1 string + S2 string + Strings []string + }{ + S1: s1, + S2: s2, + Strings: strings, + } + mock.lockMultiSelect.Lock() + mock.calls.MultiSelect = append(mock.calls.MultiSelect, callInfo) + mock.lockMultiSelect.Unlock() + return mock.MultiSelectFunc(s1, s2, strings) +} + +// MultiSelectCalls gets all the calls that were made to MultiSelect. +// Check the length with: +// len(mockedPrompter.MultiSelectCalls()) +func (mock *PrompterMock) MultiSelectCalls() []struct { + S1 string + S2 string + Strings []string +} { + var calls []struct { + S1 string + S2 string + Strings []string + } + mock.lockMultiSelect.RLock() + calls = mock.calls.MultiSelect + mock.lockMultiSelect.RUnlock() + return calls +} + +// Password calls PasswordFunc. +func (mock *PrompterMock) Password(s string) (string, error) { + if mock.PasswordFunc == nil { + panic("PrompterMock.PasswordFunc: method is nil but Prompter.Password was just called") + } + callInfo := struct { + S string + }{ + S: s, + } + mock.lockPassword.Lock() + mock.calls.Password = append(mock.calls.Password, callInfo) + mock.lockPassword.Unlock() + return mock.PasswordFunc(s) +} + +// PasswordCalls gets all the calls that were made to Password. +// Check the length with: +// len(mockedPrompter.PasswordCalls()) +func (mock *PrompterMock) PasswordCalls() []struct { + S string +} { + var calls []struct { + S string + } + mock.lockPassword.RLock() + calls = mock.calls.Password + mock.lockPassword.RUnlock() + return calls +} + +// Select calls SelectFunc. +func (mock *PrompterMock) Select(s1 string, s2 string, strings []string) (int, error) { + if mock.SelectFunc == nil { + panic("PrompterMock.SelectFunc: method is nil but Prompter.Select was just called") + } + callInfo := struct { + S1 string + S2 string + Strings []string + }{ + S1: s1, + S2: s2, + Strings: strings, + } + mock.lockSelect.Lock() + mock.calls.Select = append(mock.calls.Select, callInfo) + mock.lockSelect.Unlock() + return mock.SelectFunc(s1, s2, strings) +} + +// SelectCalls gets all the calls that were made to Select. +// Check the length with: +// len(mockedPrompter.SelectCalls()) +func (mock *PrompterMock) SelectCalls() []struct { + S1 string + S2 string + Strings []string +} { + var calls []struct { + S1 string + S2 string + Strings []string + } + mock.lockSelect.RLock() + calls = mock.calls.Select + mock.lockSelect.RUnlock() + return calls +} diff --git a/internal/prompter/test.go b/internal/prompter/test.go new file mode 100644 index 000000000..ae7574ea9 --- /dev/null +++ b/internal/prompter/test.go @@ -0,0 +1,22 @@ +package prompter + +import "fmt" + +// Test helpers + +func IndexFor(options []string, answer string) (int, error) { + for ix, a := range options { + if a == answer { + return ix, nil + } + } + return -1, NoSuchAnswerErr(answer) +} + +func NoSuchAnswerErr(answer string) error { + return fmt.Errorf("no such answer '%s'", answer) +} + +func NoSuchPromptErr(prompt string) error { + return fmt.Errorf("no such prompt '%s'", prompt) +} diff --git a/pkg/cmd/auth/login/login.go b/pkg/cmd/auth/login/login.go index 474c21588..06cdb59a9 100644 --- a/pkg/cmd/auth/login/login.go +++ b/pkg/cmd/auth/login/login.go @@ -6,14 +6,13 @@ import ( "net/http" "strings" - "github.com/AlecAivazis/survey/v2" "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/internal/config" "github.com/cli/cli/v2/internal/ghinstance" + "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/pkg/cmd/auth/shared" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" - "github.com/cli/cli/v2/pkg/prompt" "github.com/spf13/cobra" ) @@ -21,6 +20,7 @@ type LoginOptions struct { IO *iostreams.IOStreams Config func() (config.Config, error) HttpClient func() (*http.Client, error) + Prompter prompter.Prompter MainExecutable string @@ -38,6 +38,7 @@ func NewCmdLogin(f *cmdutil.Factory, runF func(*LoginOptions) error) *cobra.Comm IO: f.IOStreams, Config: f.Config, HttpClient: f.HttpClient, + Prompter: f.Prompter, } var tokenStdin bool @@ -129,7 +130,7 @@ func loginRun(opts *LoginOptions) error { hostname := opts.Hostname if opts.Interactive && hostname == "" { var err error - hostname, err = promptForHostname() + hostname, err = promptForHostname(opts) if err != nil { return err } @@ -159,15 +160,9 @@ func loginRun(opts *LoginOptions) error { existingToken, _ := cfg.AuthToken(hostname) if existingToken != "" && opts.Interactive { if err := shared.HasMinimumScopes(httpClient, hostname, existingToken); err == nil { - var keepGoing bool - err = prompt.SurveyAskOne(&survey.Confirm{ - Message: fmt.Sprintf( - "You're already logged into %s. Do you want to re-authenticate?", - hostname), - Default: false, - }, &keepGoing) + keepGoing, err := opts.Prompter.Confirm(fmt.Sprintf("You're already logged into %s. Do you want to re-authenticate?", hostname), false) if err != nil { - return fmt.Errorf("could not prompt: %w", err) + return err } if !keepGoing { return nil @@ -185,34 +180,28 @@ func loginRun(opts *LoginOptions) error { Scopes: opts.Scopes, Executable: opts.MainExecutable, GitProtocol: opts.GitProtocol, + Prompter: opts.Prompter, }) } -func promptForHostname() (string, error) { - var hostType int - err := prompt.SurveyAskOne(&survey.Select{ - Message: "What account do you want to log into?", - Options: []string{ +func promptForHostname(opts *LoginOptions) (string, error) { + hostType, err := opts.Prompter.Select( + "What account do you want to log into?", + "", + []string{ "GitHub.com", "GitHub Enterprise Server", - }, - }, &hostType) - + }) if err != nil { - return "", fmt.Errorf("could not prompt: %w", err) + return "", err } isEnterprise := hostType == 1 hostname := ghinstance.Default() if isEnterprise { - err := prompt.SurveyAskOne(&survey.Input{ - Message: "GHE hostname:", - }, &hostname, survey.WithValidator(ghinstance.HostnameValidator)) - if err != nil { - return "", fmt.Errorf("could not prompt: %w", err) - } + hostname, err = opts.Prompter.InputHostname() } - return hostname, nil + return hostname, err } diff --git a/pkg/cmd/auth/login/login_test.go b/pkg/cmd/auth/login/login_test.go index 4a48aece7..65de3065c 100644 --- a/pkg/cmd/auth/login/login_test.go +++ b/pkg/cmd/auth/login/login_test.go @@ -10,11 +10,11 @@ import ( "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/internal/config" + "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/internal/run" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/httpmock" "github.com/cli/cli/v2/pkg/iostreams" - "github.com/cli/cli/v2/pkg/prompt" "github.com/google/shlex" "github.com/stretchr/testify/assert" ) @@ -362,14 +362,14 @@ func Test_loginRun_Survey(t *testing.T) { stubHomeDir(t, t.TempDir()) tests := []struct { - name string - opts *LoginOptions - httpStubs func(*httpmock.Registry) - askStubs func(*prompt.AskStubber) - runStubs func(*run.CommandStubber) - wantHosts string - wantErrOut *regexp.Regexp - cfgStubs func(*config.ConfigMock) + name string + opts *LoginOptions + httpStubs func(*httpmock.Registry) + prompterStubs func(*prompter.PrompterMock) + runStubs func(*run.CommandStubber) + wantHosts string + wantErrOut *regexp.Regexp + cfgStubs func(*config.ConfigMock) }{ { name: "already authenticated", @@ -384,9 +384,13 @@ func Test_loginRun_Survey(t *testing.T) { httpStubs: func(reg *httpmock.Registry) { reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org")) }, - askStubs: func(as *prompt.AskStubber) { - as.StubPrompt("What account do you want to log into?").AnswerWith("GitHub.com") - as.StubPrompt("You're already logged into github.com. Do you want to re-authenticate?").AnswerWith(false) + prompterStubs: func(pm *prompter.PrompterMock) { + pm.SelectFunc = func(prompt, _ string, opts []string) (int, error) { + if prompt == "What account do you want to log into?" { + return prompter.IndexFor(opts, "GitHub.com") + } + return -1, prompter.NoSuchPromptErr(prompt) + } }, wantHosts: "", wantErrOut: nil, @@ -403,11 +407,16 @@ func Test_loginRun_Survey(t *testing.T) { user: jillv git_protocol: https `), - askStubs: func(as *prompt.AskStubber) { - as.StubPrompt("What is your preferred protocol for Git operations?").AnswerWith("HTTPS") - as.StubPrompt("Authenticate Git with your GitHub credentials?").AnswerWith(false) - as.StubPrompt("How would you like to authenticate GitHub CLI?").AnswerWith("Paste an authentication token") - as.StubPrompt("Paste your authentication token:").AnswerWith("def456") + prompterStubs: func(pm *prompter.PrompterMock) { + pm.SelectFunc = func(prompt, _ string, opts []string) (int, error) { + switch prompt { + case "What is your preferred protocol for Git operations?": + return prompter.IndexFor(opts, "HTTPS") + case "How would you like to authenticate GitHub CLI?": + return prompter.IndexFor(opts, "Paste an authentication token") + } + return -1, prompter.NoSuchPromptErr(prompt) + } }, runStubs: func(rs *run.CommandStubber) { rs.Register(`git config credential\.https:/`, 1, "") @@ -432,13 +441,21 @@ func Test_loginRun_Survey(t *testing.T) { opts: &LoginOptions{ Interactive: true, }, - askStubs: func(as *prompt.AskStubber) { - as.StubPrompt("What account do you want to log into?").AnswerWith("GitHub Enterprise Server") - as.StubPrompt("GHE hostname:").AnswerWith("brad.vickers") - as.StubPrompt("What is your preferred protocol for Git operations?").AnswerWith("HTTPS") - as.StubPrompt("Authenticate Git with your GitHub credentials?").AnswerWith(false) - as.StubPrompt("How would you like to authenticate GitHub CLI?").AnswerWith("Paste an authentication token") - as.StubPrompt("Paste your authentication token:").AnswerWith("def456") + prompterStubs: func(pm *prompter.PrompterMock) { + pm.SelectFunc = func(prompt, _ string, opts []string) (int, error) { + switch prompt { + case "What account do you want to log into?": + return prompter.IndexFor(opts, "GitHub Enterprise Server") + case "What is your preferred protocol for Git operations?": + return prompter.IndexFor(opts, "HTTPS") + case "How would you like to authenticate GitHub CLI?": + return prompter.IndexFor(opts, "Paste an authentication token") + } + return -1, prompter.NoSuchPromptErr(prompt) + } + pm.InputHostnameFunc = func() (string, error) { + return "brad.vickers", nil + } }, runStubs: func(rs *run.CommandStubber) { rs.Register(`git config credential\.https:/`, 1, "") @@ -463,12 +480,18 @@ func Test_loginRun_Survey(t *testing.T) { opts: &LoginOptions{ Interactive: true, }, - askStubs: func(as *prompt.AskStubber) { - as.StubPrompt("What account do you want to log into?").AnswerWith("GitHub.com") - as.StubPrompt("What is your preferred protocol for Git operations?").AnswerWith("HTTPS") - as.StubPrompt("Authenticate Git with your GitHub credentials?").AnswerWith(false) - as.StubPrompt("How would you like to authenticate GitHub CLI?").AnswerWith("Paste an authentication token") - as.StubPrompt("Paste your authentication token:").AnswerWith("def456") + prompterStubs: func(pm *prompter.PrompterMock) { + pm.SelectFunc = func(prompt, _ string, opts []string) (int, error) { + switch prompt { + case "What account do you want to log into?": + return prompter.IndexFor(opts, "GitHub.com") + case "What is your preferred protocol for Git operations?": + return prompter.IndexFor(opts, "HTTPS") + case "How would you like to authenticate GitHub CLI?": + return prompter.IndexFor(opts, "Paste an authentication token") + } + return -1, prompter.NoSuchPromptErr(prompt) + } }, runStubs: func(rs *run.CommandStubber) { rs.Register(`git config credential\.https:/`, 1, "") @@ -487,12 +510,18 @@ func Test_loginRun_Survey(t *testing.T) { opts: &LoginOptions{ Interactive: true, }, - askStubs: func(as *prompt.AskStubber) { - as.StubPrompt("What account do you want to log into?").AnswerWith("GitHub.com") - as.StubPrompt("What is your preferred protocol for Git operations?").AnswerWith("SSH") - as.StubPrompt("Generate a new SSH key to add to your GitHub account?").AnswerWith(false) - as.StubPrompt("How would you like to authenticate GitHub CLI?").AnswerWith("Paste an authentication token") - as.StubPrompt("Paste your authentication token:").AnswerWith("def456") + prompterStubs: func(pm *prompter.PrompterMock) { + pm.SelectFunc = func(prompt, _ string, opts []string) (int, error) { + switch prompt { + case "What account do you want to log into?": + return prompter.IndexFor(opts, "GitHub.com") + case "What is your preferred protocol for Git operations?": + return prompter.IndexFor(opts, "SSH") + case "How would you like to authenticate GitHub CLI?": + return prompter.IndexFor(opts, "Paste an authentication token") + } + return -1, prompter.NoSuchPromptErr(prompt) + } }, wantErrOut: regexp.MustCompile("Tip: you can generate a Personal Access Token here https://github.com/settings/tokens"), }, @@ -535,10 +564,17 @@ func Test_loginRun_Survey(t *testing.T) { httpmock.StringResponse(`{"data":{"viewer":{"login":"jillv"}}}`)) } - as := prompt.NewAskStubber(t) - if tt.askStubs != nil { - tt.askStubs(as) + pm := &prompter.PrompterMock{} + pm.ConfirmFunc = func(_ string, _ bool) (bool, error) { + return false, nil } + pm.AuthTokenFunc = func() (string, error) { + return "def456", nil + } + if tt.prompterStubs != nil { + tt.prompterStubs(pm) + } + tt.opts.Prompter = pm rs, restoreRun := run.Stub() defer restoreRun(t) diff --git a/pkg/cmd/auth/logout/logout.go b/pkg/cmd/auth/logout/logout.go index 4027fd0f8..029d5971a 100644 --- a/pkg/cmd/auth/logout/logout.go +++ b/pkg/cmd/auth/logout/logout.go @@ -79,6 +79,7 @@ func logoutRun(opts *LogoutOptions) error { if len(candidates) == 1 { hostname = candidates[0] } else { + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(&survey.Select{ Message: "What account do you want to log out of?", Options: candidates, diff --git a/pkg/cmd/auth/logout/logout_test.go b/pkg/cmd/auth/logout/logout_test.go index aa176f9f0..71bca8cd7 100644 --- a/pkg/cmd/auth/logout/logout_test.go +++ b/pkg/cmd/auth/logout/logout_test.go @@ -158,6 +158,7 @@ func Test_logoutRun_tty(t *testing.T) { return &http.Client{Transport: reg}, nil } + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) if tt.askStubs != nil { tt.askStubs(as) diff --git a/pkg/cmd/auth/refresh/refresh.go b/pkg/cmd/auth/refresh/refresh.go index 32d1dade4..81c6d36da 100644 --- a/pkg/cmd/auth/refresh/refresh.go +++ b/pkg/cmd/auth/refresh/refresh.go @@ -94,6 +94,7 @@ func refreshRun(opts *RefreshOptions) error { if len(candidates) == 1 { hostname = candidates[0] } else { + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err := prompt.SurveyAskOne(&survey.Select{ Message: "What account do you want to refresh auth for?", Options: candidates, diff --git a/pkg/cmd/auth/refresh/refresh_test.go b/pkg/cmd/auth/refresh/refresh_test.go index 3de01897f..0cb754117 100644 --- a/pkg/cmd/auth/refresh/refresh_test.go +++ b/pkg/cmd/auth/refresh/refresh_test.go @@ -272,6 +272,7 @@ func Test_refreshRun(t *testing.T) { ) tt.opts.httpClient = &http.Client{Transport: httpReg} + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) if tt.askStubs != nil { tt.askStubs(as) diff --git a/pkg/cmd/auth/shared/git_credential.go b/pkg/cmd/auth/shared/git_credential.go index fb8ba31c2..e6cbe3c29 100644 --- a/pkg/cmd/auth/shared/git_credential.go +++ b/pkg/cmd/auth/shared/git_credential.go @@ -7,17 +7,17 @@ import ( "path/filepath" "strings" - "github.com/AlecAivazis/survey/v2" "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/git" "github.com/cli/cli/v2/internal/ghinstance" + "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/internal/run" - "github.com/cli/cli/v2/pkg/prompt" "github.com/google/shlex" ) type GitCredentialFlow struct { Executable string + Prompter prompter.Prompter shouldSetup bool helper string @@ -32,13 +32,12 @@ func (flow *GitCredentialFlow) Prompt(hostname string) error { return nil } - err := prompt.SurveyAskOne(&survey.Confirm{ - Message: "Authenticate Git with your GitHub credentials?", - Default: true, - }, &flow.shouldSetup) + result, err := flow.Prompter.Confirm("Authenticate Git with your GitHub credentials?", true) if err != nil { - return fmt.Errorf("could not prompt: %w", err) + return err } + flow.shouldSetup = result + if flow.shouldSetup { if isGitMissing(gitErr) { return gitErr diff --git a/pkg/cmd/auth/shared/login_flow.go b/pkg/cmd/auth/shared/login_flow.go index 0afb84981..10a371872 100644 --- a/pkg/cmd/auth/shared/login_flow.go +++ b/pkg/cmd/auth/shared/login_flow.go @@ -6,14 +6,13 @@ import ( "os" "strings" - "github.com/AlecAivazis/survey/v2" "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/authflow" "github.com/cli/cli/v2/internal/ghinstance" + "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/pkg/cmd/ssh-key/add" "github.com/cli/cli/v2/pkg/iostreams" - "github.com/cli/cli/v2/pkg/prompt" "github.com/cli/cli/v2/pkg/ssh" ) @@ -35,6 +34,7 @@ type LoginOptions struct { Scopes []string Executable string GitProtocol string + Prompter prompter.Prompter sshContext ssh.Context } @@ -47,23 +47,23 @@ func Login(opts *LoginOptions) error { gitProtocol := strings.ToLower(opts.GitProtocol) if opts.Interactive && gitProtocol == "" { - var proto string - err := prompt.SurveyAskOne(&survey.Select{ - Message: "What is your preferred protocol for Git operations?", - Options: []string{ - "HTTPS", - "SSH", - }, - }, &proto) - if err != nil { - return fmt.Errorf("could not prompt: %w", err) + options := []string{ + "HTTPS", + "SSH", } + result, err := opts.Prompter.Select( + "What is your preferred protocol for Git operations?", + "", options) + if err != nil { + return err + } + proto := options[result] gitProtocol = strings.ToLower(proto) } var additionalScopes []string - credentialFlow := &GitCredentialFlow{Executable: opts.Executable} + credentialFlow := &GitCredentialFlow{Executable: opts.Executable, Prompter: opts.Prompter} if opts.Interactive && gitProtocol == "https" { if err := credentialFlow.Prompt(hostname); err != nil { return err @@ -80,34 +80,28 @@ func Login(opts *LoginOptions) error { } if len(pubKeys) > 0 { - var keyChoice int - err := prompt.SurveyAskOne(&survey.Select{ - Message: "Upload your SSH public key to your GitHub account?", - Options: append(pubKeys, "Skip"), - }, &keyChoice) + options := append(pubKeys, "Skip") + keyChoice, err := opts.Prompter.Select( + "Upload your SSH public key to your GitHub account?", "", + options) if err != nil { - return fmt.Errorf("could not prompt: %w", err) + return err } if keyChoice < len(pubKeys) { keyToUpload = pubKeys[keyChoice] } } else if opts.sshContext.HasKeygen() { - var sshChoice bool - err := prompt.SurveyAskOne(&survey.Confirm{ - Message: "Generate a new SSH key to add to your GitHub account?", - Default: true, - }, &sshChoice) - + sshChoice, err := opts.Prompter.Confirm("Generate a new SSH key to add to your GitHub account?", true) if err != nil { - return fmt.Errorf("could not prompt: %w", err) + return err } if sshChoice { - passphrase, err := promptForSshKeyPassphrase() + passphrase, err := opts.Prompter.Password( + "Enter a passphrase for your new SSH key (Optional)") if err != nil { - return fmt.Errorf("could not prompt for key passphrase: %w", err) + return err } - keyPair, err := opts.sshContext.GenerateSSHKey("id_ed25519", passphrase) if err != nil { return err @@ -117,12 +111,11 @@ func Login(opts *LoginOptions) error { } if keyToUpload != "" { - err := prompt.SurveyAskOne(&survey.Input{ - Message: "Title for your SSH key:", - Default: defaultSSHKeyTitle, - }, &keyTitle) + var err error + keyTitle, err = opts.Prompter.Input( + "Title for your SSH key:", defaultSSHKeyTitle) if err != nil { - return fmt.Errorf("could not prompt: %w", err) + return err } additionalScopes = append(additionalScopes, "admin:public_key") @@ -133,15 +126,14 @@ func Login(opts *LoginOptions) error { if opts.Web { authMode = 0 } else if opts.Interactive { - err := prompt.SurveyAskOne(&survey.Select{ - Message: "How would you like to authenticate GitHub CLI?", - Options: []string{ + var err error + authMode, err = opts.Prompter.Select( + "How would you like to authenticate GitHub CLI?", "", + []string{ "Login with a web browser", - "Paste an authentication token", - }, - }, &authMode) + "Paste an authentication token"}) if err != nil { - return fmt.Errorf("could not prompt: %w", err) + return err } } @@ -163,11 +155,9 @@ func Login(opts *LoginOptions) error { The minimum required scopes are %s. `, hostname, scopesSentence(minimumScopes, ghinstance.IsEnterprise(hostname)))) - err := prompt.SurveyAskOne(&survey.Password{ - Message: "Paste your authentication token:", - }, &authToken, survey.WithValidator(survey.Required)) + authToken, err := opts.Prompter.AuthToken() if err != nil { - return fmt.Errorf("could not prompt: %w", err) + return err } if err := HasMinimumScopes(httpClient, hostname, authToken); err != nil { @@ -221,19 +211,6 @@ func Login(opts *LoginOptions) error { return nil } -func promptForSshKeyPassphrase() (string, error) { - var sshPassphrase string - err := prompt.SurveyAskOne(&survey.Password{ - Message: "Enter a passphrase for your new SSH key (Optional)", - }, &sshPassphrase) - - if err != nil { - return "", err - } - - return sshPassphrase, nil -} - func scopesSentence(scopes []string, isEnterprise bool) string { quoted := make([]string, len(scopes)) for i, s := range scopes { diff --git a/pkg/cmd/auth/shared/login_flow_test.go b/pkg/cmd/auth/shared/login_flow_test.go index 467388c6a..40d045661 100644 --- a/pkg/cmd/auth/shared/login_flow_test.go +++ b/pkg/cmd/auth/shared/login_flow_test.go @@ -8,10 +8,10 @@ import ( "testing" "github.com/MakeNowJust/heredoc" + "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/internal/run" "github.com/cli/cli/v2/pkg/httpmock" "github.com/cli/cli/v2/pkg/iostreams" - "github.com/cli/cli/v2/pkg/prompt" "github.com/cli/cli/v2/pkg/ssh" "github.com/stretchr/testify/assert" ) @@ -47,14 +47,28 @@ func TestLogin_ssh(t *testing.T) { httpmock.REST("POST", "api/v3/user/keys"), httpmock.StringResponse(`{}`)) - ask := prompt.NewAskStubber(t) - - ask.StubPrompt("What is your preferred protocol for Git operations?").AnswerWith("SSH") - ask.StubPrompt("Generate a new SSH key to add to your GitHub account?").AnswerWith(true) - ask.StubPrompt("Enter a passphrase for your new SSH key (Optional)").AnswerWith("monkey") - ask.StubPrompt("Title for your SSH key:").AnswerWith("Test Key") - ask.StubPrompt("How would you like to authenticate GitHub CLI?").AnswerWith("Paste an authentication token") - ask.StubPrompt("Paste your authentication token:").AnswerWith("ATOKEN") + pm := &prompter.PrompterMock{} + pm.SelectFunc = func(prompt, _ string, opts []string) (int, error) { + switch prompt { + case "What is your preferred protocol for Git operations?": + return prompter.IndexFor(opts, "SSH") + case "How would you like to authenticate GitHub CLI?": + return prompter.IndexFor(opts, "Paste an authentication token") + } + return -1, prompter.NoSuchPromptErr(prompt) + } + pm.PasswordFunc = func(_ string) (string, error) { + return "monkey", nil + } + pm.ConfirmFunc = func(prompt string, _ bool) (bool, error) { + return true, nil + } + pm.AuthTokenFunc = func() (string, error) { + return "ATOKEN", nil + } + pm.InputFunc = func(_, _ string) (string, error) { + return "Test Key", nil + } rs, runRestore := run.Stub() defer runRestore(t) @@ -77,6 +91,7 @@ func TestLogin_ssh(t *testing.T) { err := Login(&LoginOptions{ IO: ios, Config: &cfg, + Prompter: pm, HTTPClient: &http.Client{Transport: &tr}, Hostname: "example.com", Interactive: true, diff --git a/pkg/cmd/extension/command.go b/pkg/cmd/extension/command.go index a79b99ad1..f8ca82ceb 100644 --- a/pkg/cmd/extension/command.go +++ b/pkg/cmd/extension/command.go @@ -6,13 +6,11 @@ import ( "os" "strings" - "github.com/AlecAivazis/survey/v2" "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/git" "github.com/cli/cli/v2/internal/ghrepo" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/extensions" - "github.com/cli/cli/v2/pkg/prompt" "github.com/cli/cli/v2/utils" "github.com/spf13/cobra" ) @@ -20,6 +18,7 @@ import ( func NewCmdExtension(f *cmdutil.Factory) *cobra.Command { m := f.ExtensionManager io := f.IOStreams + prompter := f.Prompter extCmd := cobra.Command{ Use: "extension", @@ -244,22 +243,15 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command { }, func() *cobra.Command { promptCreate := func() (string, extensions.ExtTemplateType, error) { - var extName string - var extTmplType int - err := prompt.SurveyAskOne(&survey.Input{ - Message: "Extension name:", - }, &extName) + extName, err := prompter.Input("Extension name:", "") if err != nil { return extName, -1, err } - err = prompt.SurveyAskOne(&survey.Select{ - Message: "What kind of extension?", - Options: []string{ - "Script (Bash, Ruby, Python, etc)", - "Go", - "Other Precompiled (C++, Rust, etc)", - }, - }, &extTmplType) + extTmplType, err := prompter.Select("What kind of extension?", "", []string{ + "Script (Bash, Ruby, Python, etc)", + "Go", + "Other Precompiled (C++, Rust, etc)", + }) return extName, extensions.ExtTemplateType(extTmplType), err } var flagType string diff --git a/pkg/cmd/extension/command_test.go b/pkg/cmd/extension/command_test.go index fdf7588ef..fb4707320 100644 --- a/pkg/cmd/extension/command_test.go +++ b/pkg/cmd/extension/command_test.go @@ -12,11 +12,11 @@ import ( "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/internal/config" "github.com/cli/cli/v2/internal/ghrepo" + "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/extensions" "github.com/cli/cli/v2/pkg/httpmock" "github.com/cli/cli/v2/pkg/iostreams" - "github.com/cli/cli/v2/pkg/prompt" "github.com/spf13/cobra" "github.com/stretchr/testify/assert" ) @@ -28,15 +28,15 @@ func TestNewCmdExtension(t *testing.T) { t.Cleanup(func() { _ = os.Chdir(oldWd) }) tests := []struct { - name string - args []string - managerStubs func(em *extensions.ExtensionManagerMock) func(*testing.T) - askStubs func(as *prompt.AskStubber) - isTTY bool - wantErr bool - errMsg string - wantStdout string - wantStderr string + name string + args []string + managerStubs func(em *extensions.ExtensionManagerMock) func(*testing.T) + prompterStubs func(pm *prompter.PrompterMock) + isTTY bool + wantErr bool + errMsg string + wantStdout string + wantStderr string }{ { name: "install an extension", @@ -387,11 +387,16 @@ func TestNewCmdExtension(t *testing.T) { } }, isTTY: true, - askStubs: func(as *prompt.AskStubber) { - as.StubPrompt("Extension name:").AnswerWith("test") - as.StubPrompt("What kind of extension?"). - AssertOptions([]string{"Script (Bash, Ruby, Python, etc)", "Go", "Other Precompiled (C++, Rust, etc)"}). - AnswerDefault() + prompterStubs: func(pm *prompter.PrompterMock) { + pm.InputFunc = func(prompt, defVal string) (string, error) { + if prompt == "Extension name:" { + return "test", nil + } + return "", nil + } + pm.SelectFunc = func(prompt, defVal string, opts []string) (int, error) { + return prompter.IndexFor(opts, "Script (Bash, Ruby, Python, etc)") + } }, wantStdout: heredoc.Doc(` ✓ Created directory gh-test @@ -562,9 +567,9 @@ func TestNewCmdExtension(t *testing.T) { assertFunc = tt.managerStubs(em) } - as := prompt.NewAskStubber(t) - if tt.askStubs != nil { - tt.askStubs(as) + pm := &prompter.PrompterMock{} + if tt.prompterStubs != nil { + tt.prompterStubs(pm) } reg := httpmock.Registry{} @@ -577,6 +582,7 @@ func TestNewCmdExtension(t *testing.T) { }, IOStreams: ios, ExtensionManager: em, + Prompter: pm, HttpClient: func() (*http.Client, error) { return &client, nil }, diff --git a/pkg/cmd/factory/default.go b/pkg/cmd/factory/default.go index d315cb772..e5be3dd0c 100644 --- a/pkg/cmd/factory/default.go +++ b/pkg/cmd/factory/default.go @@ -12,6 +12,7 @@ import ( "github.com/cli/cli/v2/git" "github.com/cli/cli/v2/internal/config" "github.com/cli/cli/v2/internal/ghrepo" + "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/pkg/cmd/extension" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" @@ -31,6 +32,7 @@ func New(appVersion string) *cmdutil.Factory { f.HttpClient = httpClientFunc(f, appVersion) // Depends on Config, IOStreams, and appVersion f.Remotes = remotesFunc(f) // Depends on Config f.BaseRepo = BaseRepoFunc(f) // Depends on Remotes + f.Prompter = newPrompter(f) // Depends on Config and IOStreams f.Browser = browser(f) // Depends on Config, and IOStreams f.ExtensionManager = extensionManager(f) // Depends on Config, HttpClient, and IOStreams @@ -108,6 +110,12 @@ func browser(f *cmdutil.Factory) cmdutil.Browser { return cmdutil.NewBrowser(browserLauncher(f), io.Out, io.ErrOut) } +func newPrompter(f *cmdutil.Factory) prompter.Prompter { + editor, _ := cmdutil.DetermineEditor(f.Config) + io := f.IOStreams + return prompter.New(editor, io.In, io.Out, io.ErrOut) +} + // Browser precedence // 1. GH_BROWSER // 2. browser from config diff --git a/pkg/cmd/gist/edit/edit.go b/pkg/cmd/gist/edit/edit.go index 47b122c27..a96b3feb7 100644 --- a/pkg/cmd/gist/edit/edit.go +++ b/pkg/cmd/gist/edit/edit.go @@ -189,6 +189,7 @@ func editRun(opts *EditOptions) error { if !opts.IO.CanPrompt() { return errors.New("unsure what file to edit; either specify --filename or run interactively") } + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(&survey.Select{ Message: "Edit which file?", Options: candidates, @@ -249,6 +250,7 @@ func editRun(opts *EditOptions) error { choice := "" + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(&survey.Select{ Message: "What next?", Options: []string{ diff --git a/pkg/cmd/gist/edit/edit_test.go b/pkg/cmd/gist/edit/edit_test.go index 7318f26f8..33223ddee 100644 --- a/pkg/cmd/gist/edit/edit_test.go +++ b/pkg/cmd/gist/edit/edit_test.go @@ -486,6 +486,7 @@ func Test_editRun(t *testing.T) { } t.Run(tt.name, func(t *testing.T) { + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) if tt.askStubs != nil { tt.askStubs(as) diff --git a/pkg/cmd/gist/view/view.go b/pkg/cmd/gist/view/view.go index 1c4e618df..014ba2bdf 100644 --- a/pkg/cmd/gist/view/view.go +++ b/pkg/cmd/gist/view/view.go @@ -246,6 +246,7 @@ func promptGists(client *http.Client, host string, cs *iostreams.ColorScheme) (g Options: opts, } + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(questions, &result) if err != nil { diff --git a/pkg/cmd/gist/view/view_test.go b/pkg/cmd/gist/view/view_test.go index 42ec91fef..8dbc9a748 100644 --- a/pkg/cmd/gist/view/view_test.go +++ b/pkg/cmd/gist/view/view_test.go @@ -355,6 +355,7 @@ func Test_viewRun(t *testing.T) { )), ) + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) as.StubPrompt("Select a gist").AnswerDefault() } @@ -469,6 +470,7 @@ func Test_promptGists(t *testing.T) { client := &http.Client{Transport: reg} t.Run(tt.name, func(t *testing.T) { + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) if tt.askStubs != nil { tt.askStubs(as) diff --git a/pkg/cmd/issue/create/create_test.go b/pkg/cmd/issue/create/create_test.go index ae98535c5..6d1928f0d 100644 --- a/pkg/cmd/issue/create/create_test.go +++ b/pkg/cmd/issue/create/create_test.go @@ -402,6 +402,7 @@ func TestIssueCreate_recover(t *testing.T) { assert.Equal(t, []interface{}{"BUGID", "TODOID"}, inputs["labelIds"]) })) + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) as.StubPrompt("Title").AnswerDefault() @@ -469,6 +470,7 @@ func TestIssueCreate_nonLegacyTemplate(t *testing.T) { }), ) + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) as.StubPrompt("Choose a template").AnswerWith("Submit a request") @@ -499,6 +501,7 @@ func TestIssueCreate_continueInBrowser(t *testing.T) { } } }`), ) + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) as.StubPrompt("Title").AnswerWith("hello") diff --git a/pkg/cmd/issue/delete/delete.go b/pkg/cmd/issue/delete/delete.go index bf62dafac..b4a8752da 100644 --- a/pkg/cmd/issue/delete/delete.go +++ b/pkg/cmd/issue/delete/delete.go @@ -79,6 +79,7 @@ func deleteRun(opts *DeleteOptions) error { // already provided. Otherwise skip confirmation. if opts.IO.CanPrompt() && !opts.Confirmed { answer := "" + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne( &survey.Input{ Message: fmt.Sprintf("You're going to delete issue #%d. This action cannot be reversed. To confirm, type the issue number:", issue.Number), diff --git a/pkg/cmd/issue/delete/delete_test.go b/pkg/cmd/issue/delete/delete_test.go index 2cf323e56..e3b2613ce 100644 --- a/pkg/cmd/issue/delete/delete_test.go +++ b/pkg/cmd/issue/delete/delete_test.go @@ -76,6 +76,7 @@ func TestIssueDelete(t *testing.T) { }), ) + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) as.StubPrompt("You're going to delete issue #13. This action cannot be reversed. To confirm, type the issue number:").AnswerWith("13") @@ -136,6 +137,7 @@ func TestIssueDelete_cancel(t *testing.T) { } } }`), ) + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) as.StubPrompt("You're going to delete issue #13. This action cannot be reversed. To confirm, type the issue number:").AnswerWith("14") diff --git a/pkg/cmd/label/delete.go b/pkg/cmd/label/delete.go index 3b5aa1ce7..3d51cf4c4 100644 --- a/pkg/cmd/label/delete.go +++ b/pkg/cmd/label/delete.go @@ -67,6 +67,7 @@ func deleteRun(opts *deleteOptions) error { if !opts.Confirmed { var valid string + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err := prompt.SurveyAskOne( &survey.Input{Message: fmt.Sprintf("Type %s to confirm deletion:", opts.Name)}, &valid, diff --git a/pkg/cmd/label/delete_test.go b/pkg/cmd/label/delete_test.go index 48fba81c5..d5d0ac195 100644 --- a/pkg/cmd/label/delete_test.go +++ b/pkg/cmd/label/delete_test.go @@ -153,6 +153,7 @@ func TestDeleteRun(t *testing.T) { return &http.Client{Transport: reg}, nil } + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) if tt.askStubs != nil { tt.askStubs(as) diff --git a/pkg/cmd/pr/create/create.go b/pkg/cmd/pr/create/create.go index 9f8e34fd4..31ff9c25c 100644 --- a/pkg/cmd/pr/create/create.go +++ b/pkg/cmd/pr/create/create.go @@ -569,6 +569,7 @@ func NewCreateContext(opts *CreateOptions) (*CreateContext, error) { pushOptions = append(pushOptions, "Cancel") var selectedOption int + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(&survey.Select{ Message: fmt.Sprintf("Where should we push the '%s' branch?", headBranch), Options: pushOptions, diff --git a/pkg/cmd/pr/merge/merge.go b/pkg/cmd/pr/merge/merge.go index 5d9eb5ab9..635945572 100644 --- a/pkg/cmd/pr/merge/merge.go +++ b/pkg/cmd/pr/merge/merge.go @@ -370,6 +370,7 @@ func (m *mergeContext) deleteLocalBranch() error { if m.merged { // prompt for delete if m.opts.IO.CanPrompt() && !m.opts.IsDeleteBranchIndicated { + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err := prompt.SurveyAskOne(&survey.Confirm{ Message: fmt.Sprintf("Pull request #%d was already merged. Delete the branch locally?", m.pr.Number), Default: false, @@ -571,6 +572,7 @@ func mergeMethodSurvey(baseRepo *api.Repository) (PullRequestMergeMethod, error) } var result int + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err := prompt.SurveyAskOne(mergeQuestion, &result) return mergeOpts[result].method, err } @@ -589,6 +591,7 @@ func deleteBranchSurvey(opts *MergeOptions, crossRepoPR, localBranchExists bool) Message: message, Default: false, } + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err := prompt.SurveyAskOne(submit, &result) return result, err } @@ -615,6 +618,7 @@ func confirmSurvey(allowEditMsg bool) (shared.Action, error) { Message: "What's next?", Options: options, } + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err := prompt.SurveyAskOne(submit, &result) if err != nil { return shared.CancelAction, fmt.Errorf("could not prompt: %w", err) diff --git a/pkg/cmd/pr/merge/merge_test.go b/pkg/cmd/pr/merge/merge_test.go index 4afa03ab4..9428fd3e6 100644 --- a/pkg/cmd/pr/merge/merge_test.go +++ b/pkg/cmd/pr/merge/merge_test.go @@ -960,6 +960,7 @@ func TestPrMerge_alreadyMerged(t *testing.T) { cs.Register(`git branch -D blueberries`, 0, "") cs.Register(`git pull --ff-only`, 0, "") + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) as.StubPrompt("Pull request #4 was already merged. Delete the branch locally?").AnswerWith(true) @@ -1021,6 +1022,7 @@ func TestPrMerge_alreadyMerged_withMergeStrategy_TTY(t *testing.T) { cs.Register(`git rev-parse --verify refs/heads/`, 0, "") cs.Register(`git branch -D `, 0, "") + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) as.StubPrompt("Pull request #4 was already merged. Delete the branch locally?").AnswerWith(true) @@ -1100,6 +1102,7 @@ func TestPRMergeTTY(t *testing.T) { cs.Register(`git rev-parse --verify refs/heads/blueberries`, 0, "") + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) as.StubPrompt("What merge method would you like to use?").AnswerDefault() as.StubPrompt("Delete the branch locally and on GitHub?").AnswerDefault() @@ -1161,6 +1164,7 @@ func TestPRMergeTTY_withDeleteBranch(t *testing.T) { cs.Register(`git branch -D blueberries`, 0, "") cs.Register(`git pull --ff-only`, 0, "") + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) as.StubPrompt("What merge method would you like to use?").AnswerDefault() as.StubPrompt("What's next?").AnswerWith("Submit") @@ -1220,6 +1224,7 @@ func TestPRMergeTTY_squashEditCommitMsgAndSubject(t *testing.T) { _, cmdTeardown := run.Stub() defer cmdTeardown(t) + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) as.StubPrompt("What merge method would you like to use?").AnswerWith("Squash and merge") as.StubPrompt("Delete the branch on GitHub?").AnswerDefault() @@ -1298,6 +1303,7 @@ func TestPRTTY_cancelled(t *testing.T) { cs.Register(`git rev-parse --verify refs/heads/`, 0, "") + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) as.StubPrompt("What merge method would you like to use?").AnswerDefault() as.StubPrompt("Delete the branch locally and on GitHub?").AnswerDefault() @@ -1317,6 +1323,7 @@ func Test_mergeMethodSurvey(t *testing.T) { RebaseMergeAllowed: true, SquashMergeAllowed: true, } + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) as.StubPrompt("What merge method would you like to use?").AnswerWith("Rebase and merge") @@ -1624,6 +1631,7 @@ func TestPrAddToMergeQueueAdmin(t *testing.T) { defer cmdTeardown(t) cs.Register(`git rev-parse --verify refs/heads/`, 0, "") + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) as.StubPrompt("What merge method would you like to use?").AnswerDefault() as.StubPrompt("Delete the branch locally and on GitHub?").AnswerDefault() diff --git a/pkg/cmd/pr/review/review.go b/pkg/cmd/pr/review/review.go index 07593d747..0d65cffa2 100644 --- a/pkg/cmd/pr/review/review.go +++ b/pkg/cmd/pr/review/review.go @@ -5,16 +5,14 @@ import ( "fmt" "net/http" - "github.com/AlecAivazis/survey/v2" "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/config" + "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/pkg/cmd/pr/shared" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" "github.com/cli/cli/v2/pkg/markdown" - "github.com/cli/cli/v2/pkg/prompt" - "github.com/cli/cli/v2/pkg/surveyext" "github.com/spf13/cobra" ) @@ -22,6 +20,7 @@ type ReviewOptions struct { HttpClient func() (*http.Client, error) Config func() (config.Config, error) IO *iostreams.IOStreams + Prompter prompter.Prompter Finder shared.PRFinder @@ -36,6 +35,7 @@ func NewCmdReview(f *cmdutil.Factory, runF func(*ReviewOptions) error) *cobra.Co IO: f.IOStreams, HttpClient: f.HttpClient, Config: f.Config, + Prompter: f.Prompter, } var ( @@ -160,7 +160,7 @@ func reviewRun(opts *ReviewOptions) error { if err != nil { return err } - reviewData, err = reviewSurvey(opts.IO, editorCommand) + reviewData, err = reviewSurvey(opts, editorCommand) if err != nil { return err } @@ -204,95 +204,51 @@ func reviewRun(opts *ReviewOptions) error { return nil } -func reviewSurvey(io *iostreams.IOStreams, editorCommand string) (*api.PullRequestReviewInput, error) { - typeAnswers := struct { - ReviewType string - }{} - typeQs := []*survey.Question{ - { - Name: "reviewType", - Prompt: &survey.Select{ - Message: "What kind of review do you want to give?", - Options: []string{ - "Comment", - "Approve", - "Request changes", - }, - }, - }, - } - - err := prompt.SurveyAsk(typeQs, &typeAnswers) +func reviewSurvey(opts *ReviewOptions, editorCommand string) (*api.PullRequestReviewInput, error) { + reviewType, err := opts.Prompter.Select( + "What kind of review do you want to give?", "", + []string{"Comment", "Approve", "Request Changes"}) if err != nil { return nil, err } var reviewState api.PullRequestReviewState - switch typeAnswers.ReviewType { - case "Approve": - reviewState = api.ReviewApprove - case "Request changes": - reviewState = api.ReviewRequestChanges - case "Comment": + switch reviewType { + case 0: reviewState = api.ReviewComment + case 1: + reviewState = api.ReviewApprove + case 2: + reviewState = api.ReviewRequestChanges default: panic("unreachable state") } - bodyAnswers := struct { - Body string - }{} - blankAllowed := false if reviewState == api.ReviewApprove { blankAllowed = true } - bodyQs := []*survey.Question{ - { - Name: "body", - Prompt: &surveyext.GhEditor{ - BlankAllowed: blankAllowed, - EditorCommand: editorCommand, - Editor: &survey.Editor{ - Message: "Review body", - FileName: "*.md", - }, - }, - }, - } - - err = prompt.SurveyAsk(bodyQs, &bodyAnswers) + body, err := opts.Prompter.MarkdownEditor("Review body", "", blankAllowed) if err != nil { return nil, err } - if bodyAnswers.Body == "" && (reviewState == api.ReviewComment || reviewState == api.ReviewRequestChanges) { + if body == "" && (reviewState == api.ReviewComment || reviewState == api.ReviewRequestChanges) { return nil, errors.New("this type of review cannot be blank") } - if len(bodyAnswers.Body) > 0 { - renderedBody, err := markdown.Render(bodyAnswers.Body, markdown.WithIO(io)) + if len(body) > 0 { + renderedBody, err := markdown.Render(body, markdown.WithIO(opts.IO)) if err != nil { return nil, err } - fmt.Fprintf(io.Out, "Got:\n%s", renderedBody) + fmt.Fprintf(opts.IO.Out, "Got:\n%s", renderedBody) } - confirm := false - confirmQs := []*survey.Question{ - { - Name: "confirm", - Prompt: &survey.Confirm{ - Message: "Submit?", - Default: true, - }, - }, - } - - err = prompt.SurveyAsk(confirmQs, &confirm) + confirm, err := opts.Prompter.Confirm("Submit?", true) if err != nil { return nil, err } @@ -302,7 +258,7 @@ func reviewSurvey(io *iostreams.IOStreams, editorCommand string) (*api.PullReque } return &api.PullRequestReviewInput{ - Body: bodyAnswers.Body, + Body: body, State: reviewState, }, nil } diff --git a/pkg/cmd/pr/review/review_test.go b/pkg/cmd/pr/review/review_test.go index 13c9aab1a..9d1ad6ff9 100644 --- a/pkg/cmd/pr/review/review_test.go +++ b/pkg/cmd/pr/review/review_test.go @@ -14,11 +14,11 @@ import ( "github.com/cli/cli/v2/context" "github.com/cli/cli/v2/internal/config" "github.com/cli/cli/v2/internal/ghrepo" + "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/pkg/cmd/pr/shared" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/httpmock" "github.com/cli/cli/v2/pkg/iostreams" - "github.com/cli/cli/v2/pkg/prompt" "github.com/cli/cli/v2/test" "github.com/google/shlex" "github.com/stretchr/testify/assert" @@ -166,7 +166,7 @@ func Test_NewCmdReview(t *testing.T) { } } -func runCommand(rt http.RoundTripper, remotes context.Remotes, isTTY bool, cli string) (*test.CmdOut, error) { +func runCommand(rt http.RoundTripper, prompter prompter.Prompter, remotes context.Remotes, isTTY bool, cli string) (*test.CmdOut, error) { ios, _, stdout, stderr := iostreams.Test() ios.SetStdoutTTY(isTTY) ios.SetStdinTTY(isTTY) @@ -180,6 +180,7 @@ func runCommand(rt http.RoundTripper, remotes context.Remotes, isTTY bool, cli s Config: func() (config.Config, error) { return config.NewBlankConfig(), nil }, + Prompter: prompter, } cmd := NewCmdReview(factory, nil) @@ -248,7 +249,7 @@ func TestPRReview(t *testing.T) { }), ) - output, err := runCommand(http, nil, false, tt.args) + output, err := runCommand(http, nil, nil, false, tt.args) assert.NoError(t, err) assert.Equal(t, "", output.String()) assert.Equal(t, "", output.Stderr()) @@ -271,33 +272,13 @@ func TestPRReview_interactive(t *testing.T) { }), ) - //nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber - as, teardown := prompt.InitAskStubber() - defer teardown() + pm := &prompter.PrompterMock{ + SelectFunc: func(_, _ string, _ []string) (int, error) { return 1, nil }, + MarkdownEditorFunc: func(_, _ string, _ bool) (string, error) { return "cool story", nil }, + ConfirmFunc: func(_ string, _ bool) (bool, error) { return true, nil }, + } - //nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt - as.Stub([]*prompt.QuestionStub{ - { - Name: "reviewType", - Value: "Approve", - }, - }) - //nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt - as.Stub([]*prompt.QuestionStub{ - { - Name: "body", - Value: "cool story", - }, - }) - //nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt - as.Stub([]*prompt.QuestionStub{ - { - Name: "confirm", - Value: true, - }, - }) - - output, err := runCommand(http, nil, true, "") + output, err := runCommand(http, pm, nil, true, "") assert.NoError(t, err) assert.Equal(t, heredoc.Doc(` Got: @@ -314,12 +295,12 @@ func TestPRReview_interactive_no_body(t *testing.T) { shared.RunCommandFinder("", &api.PullRequest{ID: "THE-ID", Number: 123}, ghrepo.New("OWNER", "REPO")) - as := prompt.NewAskStubber(t) + pm := &prompter.PrompterMock{ + SelectFunc: func(_, _ string, _ []string) (int, error) { return 2, nil }, + MarkdownEditorFunc: func(_, _ string, _ bool) (string, error) { return "", nil }, + } - as.StubPrompt("What kind of review do you want to give?").AnswerWith("Request changes") - as.StubPrompt("Review body").AnswerWith("") - - _, err := runCommand(http, nil, true, "") + _, err := runCommand(http, pm, nil, true, "") assert.EqualError(t, err, "this type of review cannot be blank") } @@ -338,33 +319,13 @@ func TestPRReview_interactive_blank_approve(t *testing.T) { }), ) - //nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber - as, teardown := prompt.InitAskStubber() - defer teardown() + pm := &prompter.PrompterMock{ + SelectFunc: func(_, _ string, _ []string) (int, error) { return 1, nil }, + MarkdownEditorFunc: func(_, defVal string, _ bool) (string, error) { return defVal, nil }, + ConfirmFunc: func(_ string, _ bool) (bool, error) { return true, nil }, + } - //nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt - as.Stub([]*prompt.QuestionStub{ - { - Name: "reviewType", - Value: "Approve", - }, - }) - //nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt - as.Stub([]*prompt.QuestionStub{ - { - Name: "body", - Default: true, - }, - }) - //nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt - as.Stub([]*prompt.QuestionStub{ - { - Name: "confirm", - Value: true, - }, - }) - - output, err := runCommand(http, nil, true, "") + output, err := runCommand(http, pm, nil, true, "") assert.NoError(t, err) assert.Equal(t, "", output.String()) assert.Equal(t, "✓ Approved pull request #123\n", output.Stderr()) diff --git a/pkg/cmd/pr/shared/survey.go b/pkg/cmd/pr/shared/survey.go index 0285e22ff..31942c6a0 100644 --- a/pkg/cmd/pr/shared/survey.go +++ b/pkg/cmd/pr/shared/survey.go @@ -71,6 +71,7 @@ func confirmSubmission(allowPreview, allowMetadata, allowDraft, isDraft bool) (A }, } + //nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter err := prompt.SurveyAsk(confirmQs, &confirmAnswers) if err != nil { return -1, fmt.Errorf("could not prompt: %w", err) @@ -122,6 +123,7 @@ func BodySurvey(state *IssueMetadataState, templateContent, editorCommand string }, } + //nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter err := prompt.SurveyAsk(qs, state) if err != nil { return err @@ -148,6 +150,7 @@ func TitleSurvey(state *IssueMetadataState) error { }, } + //nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter err := prompt.SurveyAsk(qs, state) if err != nil { return err @@ -197,6 +200,7 @@ func MetadataSurvey(io *iostreams.IOStreams, baseRepo ghrepo.Interface, fetcher } extraFieldsOptions = append(extraFieldsOptions, "Assignees", "Labels", "Projects", "Milestone") + //nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter err := prompt.SurveyAsk([]*survey.Question{ { Name: "metadata", @@ -327,6 +331,7 @@ func MetadataSurvey(io *iostreams.IOStreams, baseRepo ghrepo.Interface, fetcher Milestone string }{} + //nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter err = prompt.SurveyAsk(mqs, &values) if err != nil { return fmt.Errorf("could not prompt: %w", err) diff --git a/pkg/cmd/pr/shared/templates.go b/pkg/cmd/pr/shared/templates.go index 45c8ec0a7..2eac86ab7 100644 --- a/pkg/cmd/pr/shared/templates.go +++ b/pkg/cmd/pr/shared/templates.go @@ -189,6 +189,7 @@ func (m *templateManager) Choose() (Template, error) { } var selectedOption int + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err := prompt.SurveyAskOne(&survey.Select{ Message: "Choose a template", Options: append(names, blankOption), diff --git a/pkg/cmd/pr/shared/templates_test.go b/pkg/cmd/pr/shared/templates_test.go index 752de47c1..cce39ad65 100644 --- a/pkg/cmd/pr/shared/templates_test.go +++ b/pkg/cmd/pr/shared/templates_test.go @@ -47,6 +47,7 @@ func TestTemplateManager_hasAPI(t *testing.T) { assert.Equal(t, "LEGACY", string(m.LegacyBody())) + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) as.StubPrompt("Choose a template"). AssertOptions([]string{"Bug report", "Feature request", "Open a blank issue"}). @@ -93,6 +94,7 @@ func TestTemplateManager_hasAPI_PullRequest(t *testing.T) { assert.Equal(t, "LEGACY", string(m.LegacyBody())) + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) as.StubPrompt("Choose a template"). AssertOptions([]string{"bug_pr.md", "feature_pr.md", "Open a blank pull request"}). diff --git a/pkg/cmd/release/create/create.go b/pkg/cmd/release/create/create.go index 7af34638f..6b692fc61 100644 --- a/pkg/cmd/release/create/create.go +++ b/pkg/cmd/release/create/create.go @@ -195,6 +195,7 @@ func createRun(opts *CreateOptions) error { Options: options, Default: options[0], } + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err := prompt.SurveyAskOne(q, &tag) if err != nil { return fmt.Errorf("could not prompt: %w", err) @@ -209,6 +210,7 @@ func createRun(opts *CreateOptions) error { q := &survey.Input{ Message: "Tag name", } + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err := prompt.SurveyAskOne(q, &opts.TagName) if err != nil { return fmt.Errorf("could not prompt: %w", err) @@ -309,6 +311,7 @@ func createRun(opts *CreateOptions) error { }, }, } + //nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter err = prompt.SurveyAsk(qs, opts) if err != nil { return fmt.Errorf("could not prompt: %w", err) @@ -373,6 +376,7 @@ func createRun(opts *CreateOptions) error { }, } + //nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter err = prompt.SurveyAsk(qs, opts) if err != nil { return fmt.Errorf("could not prompt: %w", err) diff --git a/pkg/cmd/release/create/create_test.go b/pkg/cmd/release/create/create_test.go index b4a95e1e6..494d29ab5 100644 --- a/pkg/cmd/release/create/create_test.go +++ b/pkg/cmd/release/create/create_test.go @@ -851,6 +851,7 @@ func Test_createRun_interactive(t *testing.T) { } t.Run(tt.name, func(t *testing.T) { + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) if tt.askStubs != nil { tt.askStubs(as) diff --git a/pkg/cmd/release/delete-asset/delete_asset.go b/pkg/cmd/release/delete-asset/delete_asset.go index f50659021..1309729d5 100644 --- a/pkg/cmd/release/delete-asset/delete_asset.go +++ b/pkg/cmd/release/delete-asset/delete_asset.go @@ -69,6 +69,7 @@ func deleteAssetRun(opts *DeleteAssetOptions) error { if !opts.SkipConfirm && opts.IO.CanPrompt() { var confirmed bool + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err := prompt.SurveyAskOne(&survey.Confirm{ Message: fmt.Sprintf("Delete asset %s in release %s in %s?", opts.AssetName, release.TagName, ghrepo.FullName(baseRepo)), Default: true, diff --git a/pkg/cmd/release/delete/delete.go b/pkg/cmd/release/delete/delete.go index 944320ec2..bb0ce0083 100644 --- a/pkg/cmd/release/delete/delete.go +++ b/pkg/cmd/release/delete/delete.go @@ -69,6 +69,7 @@ func deleteRun(opts *DeleteOptions) error { if !opts.SkipConfirm && opts.IO.CanPrompt() { var confirmed bool + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err := prompt.SurveyAskOne(&survey.Confirm{ Message: fmt.Sprintf("Delete release %s in %s?", release.TagName, ghrepo.FullName(baseRepo)), Default: true, diff --git a/pkg/cmd/repo/archive/archive.go b/pkg/cmd/repo/archive/archive.go index 2c4487af6..9e18ea1c1 100644 --- a/pkg/cmd/repo/archive/archive.go +++ b/pkg/cmd/repo/archive/archive.go @@ -115,6 +115,7 @@ func archiveRun(opts *ArchiveOptions) error { Message: fmt.Sprintf("Archive %s?", fullName), Default: false, } + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(p, &opts.Confirmed) if err != nil { return fmt.Errorf("failed to prompt: %w", err) diff --git a/pkg/cmd/repo/create/create.go b/pkg/cmd/repo/create/create.go index 241453ed1..7857d90e8 100644 --- a/pkg/cmd/repo/create/create.go +++ b/pkg/cmd/repo/create/create.go @@ -232,6 +232,7 @@ func createRun(opts *CreateOptions) error { "Create a new repository on GitHub from scratch", "Push an existing local repository to GitHub", } + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter if err := prompt.SurveyAskOne(&survey.Select{ Message: "What would you like to do?", Options: modeOptions, @@ -353,6 +354,7 @@ func createFromScratch(opts *CreateOptions) error { Message: "Clone the new repository locally?", Default: true, } + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(cloneQuestion, &opts.Clone) if err != nil { return err @@ -507,6 +509,7 @@ func createFromLocal(opts *CreateOptions) error { Message: `Add a remote?`, Default: true, } + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(remoteQuesiton, &addRemote) if err != nil { return err @@ -520,6 +523,7 @@ func createFromLocal(opts *CreateOptions) error { Message: "What should the new remote be called?", Default: "origin", } + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(pushQuestion, &baseRemote) if err != nil { return err @@ -536,6 +540,7 @@ func createFromLocal(opts *CreateOptions) error { Message: fmt.Sprintf(`Would you like to push commits from the current branch to %q?`, baseRemote), Default: true, } + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(pushQuestion, &opts.Push) if err != nil { return err @@ -684,6 +689,7 @@ func interactiveGitIgnore(client *http.Client, hostname string) (string, error) } addGitIgnoreSurvey = append(addGitIgnoreSurvey, addGitIgnoreQuestion) + //nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter err := prompt.SurveyAsk(addGitIgnoreSurvey, &addGitIgnore) if err != nil { return "", err @@ -706,6 +712,7 @@ func interactiveGitIgnore(client *http.Client, hostname string) (string, error) }, } gitIg = append(gitIg, gitIgnoreQuestion) + //nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter err = prompt.SurveyAsk(gitIg, &wantedIgnoreTemplate) if err != nil { return "", err @@ -730,6 +737,7 @@ func interactiveLicense(client *http.Client, hostname string) (string, error) { } addLicenseSurvey = append(addLicenseSurvey, addLicenseQuestion) + //nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter err := prompt.SurveyAsk(addLicenseSurvey, &addLicense) if err != nil { return "", err @@ -757,6 +765,7 @@ func interactiveLicense(client *http.Client, hostname string) (string, error) { }, } licenseQs = append(licenseQs, licenseQuestion) + //nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter err = prompt.SurveyAsk(licenseQs, &wantedLicense) if err != nil { return "", err @@ -794,6 +803,7 @@ func interactiveRepoInfo(defaultName string) (string, string, string, error) { RepoVisibility string }{} + //nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter err := prompt.SurveyAsk(qs, &answer) if err != nil { return "", "", "", err @@ -808,6 +818,7 @@ func interactiveSource() (string, error) { Message: "Path to local repository", Default: "."} + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err := prompt.SurveyAskOne(sourcePrompt, &sourcePath) if err != nil { return "", err @@ -823,6 +834,7 @@ func confirmSubmission(repoWithOwner, visibility string) error { var answer struct { ConfirmSubmit bool } + //nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter err := prompt.SurveyAsk([]*survey.Question{{ Name: "confirmSubmit", Prompt: &survey.Confirm{ diff --git a/pkg/cmd/repo/delete/delete.go b/pkg/cmd/repo/delete/delete.go index 5fd5a5340..9e1d6d1d8 100644 --- a/pkg/cmd/repo/delete/delete.go +++ b/pkg/cmd/repo/delete/delete.go @@ -8,10 +8,9 @@ import ( "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/ghinstance" "github.com/cli/cli/v2/internal/ghrepo" + "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/pkg/cmdutil" - "github.com/cli/cli/v2/pkg/prompt" - "github.com/AlecAivazis/survey/v2" "github.com/cli/cli/v2/pkg/iostreams" "github.com/spf13/cobra" ) @@ -19,6 +18,7 @@ import ( type DeleteOptions struct { HttpClient func() (*http.Client, error) BaseRepo func() (ghrepo.Interface, error) + Prompter prompter.Prompter IO *iostreams.IOStreams RepoArg string Confirmed bool @@ -29,6 +29,7 @@ func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Co IO: f.IOStreams, HttpClient: f.HttpClient, BaseRepo: f.BaseRepo, + Prompter: f.Prompter, } cmd := &cobra.Command{ @@ -91,19 +92,13 @@ func deleteRun(opts *DeleteOptions) error { fullName := ghrepo.FullName(toDelete) if !opts.Confirmed { - var valid string - err := prompt.SurveyAskOne( - &survey.Input{Message: fmt.Sprintf("Type %s to confirm deletion:", fullName)}, - &valid, - survey.WithValidator( - func(val interface{}) error { - if str := val.(string); !strings.EqualFold(str, fullName) { - return fmt.Errorf("You entered %s", str) - } - return nil - })) + result, err := opts.Prompter.Input( + fmt.Sprintf("Type %s to confirm deletion:", fullName), "") if err != nil { - return fmt.Errorf("could not prompt: %w", err) + return err + } + if !strings.EqualFold(result, fullName) { + return fmt.Errorf("You entered %s", result) } } diff --git a/pkg/cmd/repo/delete/delete_test.go b/pkg/cmd/repo/delete/delete_test.go index beb66c183..a49bbff4f 100644 --- a/pkg/cmd/repo/delete/delete_test.go +++ b/pkg/cmd/repo/delete/delete_test.go @@ -6,10 +6,10 @@ import ( "testing" "github.com/cli/cli/v2/internal/ghrepo" + "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/httpmock" "github.com/cli/cli/v2/pkg/iostreams" - "github.com/cli/cli/v2/pkg/prompt" "github.com/google/shlex" "github.com/stretchr/testify/assert" ) @@ -77,25 +77,24 @@ func TestNewCmdDelete(t *testing.T) { func Test_deleteRun(t *testing.T) { tests := []struct { - name string - tty bool - opts *DeleteOptions - httpStubs func(*httpmock.Registry) - askStubs func(*prompt.AskStubber) - wantStdout string - wantErr bool - errMsg string + name string + tty bool + opts *DeleteOptions + httpStubs func(*httpmock.Registry) + prompterStubs func(*prompter.PrompterMock) + wantStdout string + wantErr bool + errMsg string }{ { name: "prompting confirmation tty", tty: true, opts: &DeleteOptions{RepoArg: "OWNER/REPO"}, wantStdout: "✓ Deleted repository OWNER/REPO\n", - askStubs: func(q *prompt.AskStubber) { - // TODO: survey stubber doesn't have WithValidator support - // so this always passes regardless of prompt input - //nolint:staticcheck // SA1019: q.StubOne is deprecated: use StubPrompt - q.StubOne("OWNER/REPO") + prompterStubs: func(p *prompter.PrompterMock) { + p.InputFunc = func(_, _ string) (string, error) { + return "OWNER/REPO", nil + } }, httpStubs: func(reg *httpmock.Registry) { reg.Register( @@ -108,9 +107,10 @@ func Test_deleteRun(t *testing.T) { tty: true, opts: &DeleteOptions{}, wantStdout: "✓ Deleted repository OWNER/REPO\n", - askStubs: func(q *prompt.AskStubber) { - //nolint:staticcheck // SA1019: q.StubOne is deprecated: use StubPrompt - q.StubOne("OWNER/REPO") + prompterStubs: func(p *prompter.PrompterMock) { + p.InputFunc = func(_, _ string) (string, error) { + return "OWNER/REPO", nil + } }, httpStubs: func(reg *httpmock.Registry) { reg.Register( @@ -135,9 +135,10 @@ func Test_deleteRun(t *testing.T) { opts: &DeleteOptions{RepoArg: "REPO"}, wantStdout: "✓ Deleted repository OWNER/REPO\n", tty: true, - askStubs: func(q *prompt.AskStubber) { - //nolint:staticcheck // SA1019: q.StubOne is deprecated: use StubPrompt - q.StubOne("OWNER/REPO") + prompterStubs: func(p *prompter.PrompterMock) { + p.InputFunc = func(_, _ string) (string, error) { + return "OWNER/REPO", nil + } }, httpStubs: func(reg *httpmock.Registry) { reg.Register( @@ -150,12 +151,11 @@ func Test_deleteRun(t *testing.T) { }, } for _, tt := range tests { - //nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber - q, teardown := prompt.InitAskStubber() - defer teardown() - if tt.askStubs != nil { - tt.askStubs(q) + pm := &prompter.PrompterMock{} + if tt.prompterStubs != nil { + tt.prompterStubs(pm) } + tt.opts.Prompter = pm tt.opts.BaseRepo = func() (ghrepo.Interface, error) { return ghrepo.New("OWNER", "REPO"), nil diff --git a/pkg/cmd/repo/edit/edit.go b/pkg/cmd/repo/edit/edit.go index 538a507db..81ebee963 100644 --- a/pkg/cmd/repo/edit/edit.go +++ b/pkg/cmd/repo/edit/edit.go @@ -282,6 +282,7 @@ func interactiveChoice(r *api.Repository) ([]string, error) { options = append(options, optionAllowForking) } var answers []string + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err := prompt.SurveyAskOne(&survey.MultiSelect{ Message: "What do you want to edit?", Options: options, @@ -301,6 +302,7 @@ func interactiveRepoEdit(opts *EditOptions, r *api.Repository) error { switch c { case optionDescription: opts.Edits.Description = &r.Description + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(&survey.Input{ Message: "Description of the repository", Default: r.Description, @@ -310,6 +312,7 @@ func interactiveRepoEdit(opts *EditOptions, r *api.Repository) error { } case optionHomePageURL: opts.Edits.Homepage = &r.HomepageURL + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(&survey.Input{ Message: "Repository home page URL", Default: r.HomepageURL, @@ -319,6 +322,7 @@ func interactiveRepoEdit(opts *EditOptions, r *api.Repository) error { } case optionTopics: var addTopics string + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(&survey.Input{ Message: "Add topics?(csv format)", }, &addTopics) @@ -330,6 +334,7 @@ func interactiveRepoEdit(opts *EditOptions, r *api.Repository) error { } if len(opts.topicsCache) > 0 { + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(&survey.MultiSelect{ Message: "Remove Topics", Options: opts.topicsCache, @@ -340,6 +345,7 @@ func interactiveRepoEdit(opts *EditOptions, r *api.Repository) error { } case optionDefaultBranchName: opts.Edits.DefaultBranch = &r.DefaultBranchRef.Name + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(&survey.Input{ Message: "Default branch name", Default: r.DefaultBranchRef.Name, @@ -349,6 +355,7 @@ func interactiveRepoEdit(opts *EditOptions, r *api.Repository) error { } case optionWikis: opts.Edits.EnableWiki = &r.HasWikiEnabled + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(&survey.Confirm{ Message: "Enable Wikis?", Default: r.HasWikiEnabled, @@ -358,6 +365,7 @@ func interactiveRepoEdit(opts *EditOptions, r *api.Repository) error { } case optionIssues: opts.Edits.EnableIssues = &r.HasIssuesEnabled + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(&survey.Confirm{ Message: "Enable Issues?", Default: r.HasIssuesEnabled, @@ -367,6 +375,7 @@ func interactiveRepoEdit(opts *EditOptions, r *api.Repository) error { } case optionProjects: opts.Edits.EnableProjects = &r.HasProjectsEnabled + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(&survey.Confirm{ Message: "Enable Projects?", Default: r.HasProjectsEnabled, @@ -376,6 +385,7 @@ func interactiveRepoEdit(opts *EditOptions, r *api.Repository) error { } case optionVisibility: opts.Edits.Visibility = &r.Visibility + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(&survey.Select{ Message: "Visibility", Options: []string{"public", "private", "internal"}, @@ -396,6 +406,7 @@ func interactiveRepoEdit(opts *EditOptions, r *api.Repository) error { if r.RebaseMergeAllowed { defaultMergeOptions = append(defaultMergeOptions, allowRebaseMerge) } + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(&survey.MultiSelect{ Message: "Allowed merge strategies", Default: defaultMergeOptions, @@ -415,6 +426,7 @@ func interactiveRepoEdit(opts *EditOptions, r *api.Repository) error { } opts.Edits.EnableAutoMerge = &r.AutoMergeAllowed + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(&survey.Confirm{ Message: "Enable Auto Merge?", Default: r.AutoMergeAllowed, @@ -424,6 +436,7 @@ func interactiveRepoEdit(opts *EditOptions, r *api.Repository) error { } opts.Edits.DeleteBranchOnMerge = &r.DeleteBranchOnMerge + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(&survey.Confirm{ Message: "Automatically delete head branches after merging?", Default: r.DeleteBranchOnMerge, @@ -433,6 +446,7 @@ func interactiveRepoEdit(opts *EditOptions, r *api.Repository) error { } case optionTemplateRepo: opts.Edits.IsTemplate = &r.IsTemplate + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(&survey.Confirm{ Message: "Convert into a template repository?", Default: r.IsTemplate, @@ -442,6 +456,7 @@ func interactiveRepoEdit(opts *EditOptions, r *api.Repository) error { } case optionAllowForking: opts.Edits.AllowForking = &r.ForkingAllowed + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(&survey.Confirm{ Message: "Allow forking (of an organization repository)?", Default: r.ForkingAllowed, diff --git a/pkg/cmd/repo/edit/edit_test.go b/pkg/cmd/repo/edit/edit_test.go index dc4198581..41193b057 100644 --- a/pkg/cmd/repo/edit/edit_test.go +++ b/pkg/cmd/repo/edit/edit_test.go @@ -319,6 +319,7 @@ func Test_editRun_interactive(t *testing.T) { opts := &tt.opts opts.HTTPClient = &http.Client{Transport: httpReg} opts.IO = ios + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) if tt.askStubs != nil { tt.askStubs(as) diff --git a/pkg/cmd/repo/fork/fork.go b/pkg/cmd/repo/fork/fork.go index d0b0f9c7a..324f8fb1b 100644 --- a/pkg/cmd/repo/fork/fork.go +++ b/pkg/cmd/repo/fork/fork.go @@ -260,6 +260,7 @@ func forkRun(opts *ForkOptions) error { remoteDesired := opts.Remote if opts.PromptRemote { + //nolint:staticcheck // SA1019: prompt.Confirm is deprecated: use Prompter err = prompt.Confirm("Would you like to add a remote for the fork?", &remoteDesired) if err != nil { return fmt.Errorf("failed to prompt: %w", err) @@ -302,6 +303,7 @@ func forkRun(opts *ForkOptions) error { } else { cloneDesired := opts.Clone if opts.PromptClone { + //nolint:staticcheck // SA1019: prompt.Confirm is deprecated: use Prompter err = prompt.Confirm("Would you like to clone the fork?", &cloneDesired) if err != nil { return fmt.Errorf("failed to prompt: %w", err) diff --git a/pkg/cmd/repo/rename/rename.go b/pkg/cmd/repo/rename/rename.go index baf376140..0b256d1d3 100644 --- a/pkg/cmd/repo/rename/rename.go +++ b/pkg/cmd/repo/rename/rename.go @@ -89,6 +89,7 @@ func renameRun(opts *RenameOptions) error { } if newRepoName == "" { + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne( &survey.Input{ Message: fmt.Sprintf("Rename %s to: ", ghrepo.FullName(currRepo)), @@ -106,6 +107,7 @@ func renameRun(opts *RenameOptions) error { Message: fmt.Sprintf("Rename %s to %s?", ghrepo.FullName(currRepo), newRepoName), Default: false, } + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err = prompt.SurveyAskOne(p, &confirmed) if err != nil { return fmt.Errorf("failed to prompt: %w", err) diff --git a/pkg/cmd/run/download/download.go b/pkg/cmd/run/download/download.go index 86e612793..25eec5d62 100644 --- a/pkg/cmd/run/download/download.go +++ b/pkg/cmd/run/download/download.go @@ -198,6 +198,7 @@ func matchAnyPattern(patterns []string, name string) bool { type surveyPrompter struct{} func (sp *surveyPrompter) Prompt(message string, options []string, result interface{}) error { + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter return prompt.SurveyAskOne(&survey.MultiSelect{ Message: message, Options: options, diff --git a/pkg/cmd/run/shared/shared.go b/pkg/cmd/run/shared/shared.go index b2b17fbb1..6eb2f3300 100644 --- a/pkg/cmd/run/shared/shared.go +++ b/pkg/cmd/run/shared/shared.go @@ -356,6 +356,7 @@ func PromptForRun(cs *iostreams.ColorScheme, runs []Run) (string, error) { // TODO consider custom filter so it's fuzzier. right now matches start anywhere in string but // become contiguous + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err := prompt.SurveyAskOne(&survey.Select{ Message: "Select a workflow run", Options: candidates, diff --git a/pkg/cmd/run/view/view.go b/pkg/cmd/run/view/view.go index 03c7c017c..973a67897 100644 --- a/pkg/cmd/run/view/view.go +++ b/pkg/cmd/run/view/view.go @@ -446,6 +446,7 @@ func promptForJob(cs *iostreams.ColorScheme, jobs []shared.Job) (*shared.Job, er } var selected int + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err := prompt.SurveyAskOne(&survey.Select{ Message: "View a specific job in this run?", Options: candidates, diff --git a/pkg/cmd/run/watch/watch_test.go b/pkg/cmd/run/watch/watch_test.go index bdb08a58d..cabf680ac 100644 --- a/pkg/cmd/run/watch/watch_test.go +++ b/pkg/cmd/run/watch/watch_test.go @@ -277,6 +277,7 @@ func TestWatchRun(t *testing.T) { } t.Run(tt.name, func(t *testing.T) { + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) if tt.askStubs != nil { tt.askStubs(as) diff --git a/pkg/cmd/secret/set/set.go b/pkg/cmd/secret/set/set.go index 9be244772..2ba90b888 100644 --- a/pkg/cmd/secret/set/set.go +++ b/pkg/cmd/secret/set/set.go @@ -373,6 +373,7 @@ func getBody(opts *SetOptions) ([]byte, error) { if opts.IO.CanPrompt() { var bodyInput string + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err := prompt.SurveyAskOne(&survey.Password{ Message: "Paste your secret", }, &bodyInput) diff --git a/pkg/cmd/secret/set/set_test.go b/pkg/cmd/secret/set/set_test.go index c9843a5a9..ee86f1d10 100644 --- a/pkg/cmd/secret/set/set_test.go +++ b/pkg/cmd/secret/set/set_test.go @@ -560,6 +560,7 @@ func Test_getBodyPrompt(t *testing.T) { ios.SetStdinTTY(true) ios.SetStdoutTTY(true) + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) as.StubPrompt("Paste your secret").AnswerWith("cool secret") diff --git a/pkg/cmd/workflow/disable/disable_test.go b/pkg/cmd/workflow/disable/disable_test.go index bc46212ed..82d0bba2f 100644 --- a/pkg/cmd/workflow/disable/disable_test.go +++ b/pkg/cmd/workflow/disable/disable_test.go @@ -278,6 +278,7 @@ func TestDisableRun(t *testing.T) { } t.Run(tt.name, func(t *testing.T) { + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) if tt.askStubs != nil { tt.askStubs(as) diff --git a/pkg/cmd/workflow/enable/enable_test.go b/pkg/cmd/workflow/enable/enable_test.go index ffc679a93..2c3db074d 100644 --- a/pkg/cmd/workflow/enable/enable_test.go +++ b/pkg/cmd/workflow/enable/enable_test.go @@ -327,6 +327,7 @@ func TestEnableRun(t *testing.T) { } t.Run(tt.name, func(t *testing.T) { + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) if tt.askStubs != nil { tt.askStubs(as) diff --git a/pkg/cmd/workflow/run/run.go b/pkg/cmd/workflow/run/run.go index 23bf632e8..847683148 100644 --- a/pkg/cmd/workflow/run/run.go +++ b/pkg/cmd/workflow/run/run.go @@ -230,6 +230,7 @@ func collectInputs(yamlContent []byte) (map[string]string, error) { inputAnswer := InputAnswer{ providedInputs: providedInputs, } + //nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter err = prompt.SurveyAsk(qs, &inputAnswer) if err != nil { return nil, err diff --git a/pkg/cmd/workflow/run/run_test.go b/pkg/cmd/workflow/run/run_test.go index 125e3fcfb..42fc6e962 100644 --- a/pkg/cmd/workflow/run/run_test.go +++ b/pkg/cmd/workflow/run/run_test.go @@ -632,6 +632,7 @@ jobs: } t.Run(tt.name, func(t *testing.T) { + //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock as := prompt.NewAskStubber(t) if tt.askStubs != nil { tt.askStubs(as) diff --git a/pkg/cmd/workflow/shared/shared.go b/pkg/cmd/workflow/shared/shared.go index c3f0b6a93..efef97e1c 100644 --- a/pkg/cmd/workflow/shared/shared.go +++ b/pkg/cmd/workflow/shared/shared.go @@ -104,6 +104,7 @@ func SelectWorkflow(workflows []Workflow, promptMsg string, states []WorkflowSta var selected int + //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter err := prompt.SurveyAskOne(&survey.Select{ Message: promptMsg, Options: candidates, diff --git a/pkg/cmdutil/factory.go b/pkg/cmdutil/factory.go index 668cd637c..3f3f0de0e 100644 --- a/pkg/cmdutil/factory.go +++ b/pkg/cmdutil/factory.go @@ -9,6 +9,7 @@ import ( "github.com/cli/cli/v2/context" "github.com/cli/cli/v2/internal/config" "github.com/cli/cli/v2/internal/ghrepo" + "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/pkg/extensions" "github.com/cli/cli/v2/pkg/iostreams" ) @@ -20,6 +21,7 @@ type Browser interface { type Factory struct { IOStreams *iostreams.IOStreams Browser Browser + Prompter prompter.Prompter HttpClient func() (*http.Client, error) BaseRepo func() (ghrepo.Interface, error) diff --git a/pkg/prompt/prompt.go b/pkg/prompt/prompt.go index 1c6fc7a61..683fad913 100644 --- a/pkg/prompt/prompt.go +++ b/pkg/prompt/prompt.go @@ -2,6 +2,7 @@ package prompt import "github.com/AlecAivazis/survey/v2" +// Deprecated: use PrompterMock func StubConfirm(result bool) func() { orig := Confirm Confirm = func(_ string, r *bool) error { @@ -13,6 +14,7 @@ func StubConfirm(result bool) func() { } } +// Deprecated: use Prompter var Confirm = func(prompt string, result *bool) error { p := &survey.Confirm{ Message: prompt, @@ -21,10 +23,12 @@ var Confirm = func(prompt string, result *bool) error { return SurveyAskOne(p, result) } +// Deprecated: use Prompter var SurveyAskOne = func(p survey.Prompt, response interface{}, opts ...survey.AskOpt) error { return survey.AskOne(p, response, opts...) } +// Deprecated: use Prompter var SurveyAsk = func(qs []*survey.Question, response interface{}, opts ...survey.AskOpt) error { return survey.Ask(qs, response, opts...) } diff --git a/pkg/prompt/stubber.go b/pkg/prompt/stubber.go index 8d9e22520..8011c5924 100644 --- a/pkg/prompt/stubber.go +++ b/pkg/prompt/stubber.go @@ -18,6 +18,7 @@ type testing interface { Cleanup(func()) } +// Deprecated: use PrompterMock func NewAskStubber(t testing) *AskStubber { as, teardown := InitAskStubber() t.Cleanup(func() {