diff --git a/pkg/cmd/codespace/select.go b/pkg/cmd/codespace/select.go index 88c01e211..c5800826a 100644 --- a/pkg/cmd/codespace/select.go +++ b/pkg/cmd/codespace/select.go @@ -3,31 +3,66 @@ package codespace import ( "context" "fmt" + "os" "github.com/spf13/cobra" ) +type selectOptions struct { + filePath string +} + func newSelectCmd(app *App) *cobra.Command { - codeCmd := &cobra.Command{ + opts := selectOptions{} + + selectCmd := &cobra.Command{ Use: "select", Short: "Select a codespace", Hidden: true, Args: noArgsConstraint, RunE: func(cmd *cobra.Command, args []string) error { - return app.Select(cmd.Context(), "") + return app.Select(cmd.Context(), "", opts) }, } - return codeCmd + selectCmd.Flags().StringVarP(&opts.filePath, "file", "f", "", "Output file path") + return selectCmd } -func (a *App) Select(ctx context.Context, name string) error { +// Hidden codespace `select` command allows to reuse the common codespace selection +// dialog by external GH CLI extensions. By default output selected codespace name +// into `stdout`. Pass `--file`(`-f`) flag along with a file path to output selected +// codespace into a file instead. +// +// ## Examples +// +// With `stdout` output: +// +// ```shell +// gh codespace select +// ``` +// +// With `into-a-file`` output: +// +// ```shell +// gh codespace select --file /tmp/selected_codespace.txt +// ``` +func (a *App) Select(ctx context.Context, name string, opts selectOptions) error { codespace, err := getOrChooseCodespace(ctx, a.apiClient, name) if err != nil { return err } - a.io.Out.Write([]byte(fmt.Sprintf("%s\n", codespace.Name))); + if opts.filePath != "" { + f, err := os.Create(opts.filePath) + if err != nil { + return err + } + f.WriteString(codespace.Name); + return nil + } + + a.io.Out.Write([]byte(fmt.Sprintf("%s\n", codespace.Name))); return nil } diff --git a/pkg/cmd/codespace/select_test.go b/pkg/cmd/codespace/select_test.go index 09c162096..cdae1d619 100644 --- a/pkg/cmd/codespace/select_test.go +++ b/pkg/cmd/codespace/select_test.go @@ -3,6 +3,7 @@ package codespace import ( "context" "errors" + "os" "fmt" "testing" @@ -11,14 +12,17 @@ import ( ) const CODESPACE_NAME = "monalisa-cli-cli-abcdef" +const OUTPUT_FILE_PATH = "../../../bin/codespace-selection-test.log" func TestApp_Select(t *testing.T) { tests := []struct { name string arg string + opts selectOptions wantErr bool wantStdout string wantStderr string + wantFileContents string }{ { name: "Select a codespace", @@ -31,6 +35,13 @@ func TestApp_Select(t *testing.T) { arg: "non-existent-codespace-name", wantErr: true, }, + { + name: "Select a codespace", + arg: CODESPACE_NAME, + wantErr: false, + wantFileContents: CODESPACE_NAME, + opts: selectOptions { filePath: OUTPUT_FILE_PATH }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -39,7 +50,7 @@ func TestApp_Select(t *testing.T) { io.SetStdoutTTY(true) a := NewApp(io, nil, testSelectApiMock(), nil) - if err := a.Select(context.Background(), tt.arg); (err != nil) != tt.wantErr { + if err := a.Select(context.Background(), tt.arg, tt.opts); (err != nil) != tt.wantErr { t.Errorf("App.Select() error = %v, wantErr %v", err, tt.wantErr) } @@ -49,6 +60,21 @@ func TestApp_Select(t *testing.T) { if out := sortLines(stderr.String()); out != tt.wantStderr { t.Errorf("stderr = %q, want %q", out, tt.wantStderr) } + + if tt.wantFileContents != "" { + if tt.opts.filePath == "" { + t.Errorf("wantFileContents is set but opts.filePath is not") + } + + dat, err := os.ReadFile(tt.opts.filePath) + if err != nil { + panic(err) + } + + if string(dat) != tt.wantFileContents { + t.Errorf("file contents = %q, want %q", string(dat), CODESPACE_NAME) + } + } }) } }