Merge pull request #5390 from legomushroom/legomushroom/codespaces-add-hidden-select-command
[codespaces]: add hidden select command
This commit is contained in:
commit
f6b8bbe982
3 changed files with 183 additions and 0 deletions
|
|
@ -20,6 +20,7 @@ func NewRootCmd(app *App) *cobra.Command {
|
|||
root.AddCommand(newSSHCmd(app))
|
||||
root.AddCommand(newCpCmd(app))
|
||||
root.AddCommand(newStopCmd(app))
|
||||
root.AddCommand(newSelectCmd(app))
|
||||
|
||||
return root
|
||||
}
|
||||
|
|
|
|||
75
pkg/cmd/codespace/select.go
Normal file
75
pkg/cmd/codespace/select.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package codespace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type selectOptions struct {
|
||||
filePath string
|
||||
}
|
||||
|
||||
func newSelectCmd(app *App) *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(), "", opts)
|
||||
},
|
||||
}
|
||||
|
||||
selectCmd.Flags().StringVarP(&opts.filePath, "file", "f", "", "Output file path")
|
||||
return selectCmd
|
||||
}
|
||||
|
||||
// Hidden codespace `select` command allows to reuse existing 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 name 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) (err error) {
|
||||
codespace, err := getOrChooseCodespace(ctx, a.apiClient, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.filePath != "" {
|
||||
f, err := os.Create(opts.filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create output file: %w", err)
|
||||
}
|
||||
|
||||
defer safeClose(f, &err)
|
||||
|
||||
_, err = f.WriteString(codespace.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write codespace name to output file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Fprintln(a.io.Out, codespace.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
107
pkg/cmd/codespace/select_test.go
Normal file
107
pkg/cmd/codespace/select_test.go
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
package codespace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/v2/internal/codespaces/api"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
)
|
||||
|
||||
const CODESPACE_NAME = "monalisa-cli-cli-abcdef"
|
||||
|
||||
func TestApp_Select(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
arg string
|
||||
wantErr bool
|
||||
outputToFile bool
|
||||
wantStdout string
|
||||
wantStderr string
|
||||
wantFileContents string
|
||||
}{
|
||||
{
|
||||
name: "Select a codespace",
|
||||
arg: CODESPACE_NAME,
|
||||
wantErr: false,
|
||||
wantStdout: fmt.Sprintf("%s\n", CODESPACE_NAME),
|
||||
},
|
||||
{
|
||||
name: "Select a codespace error",
|
||||
arg: "non-existent-codespace-name",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Select a codespace",
|
||||
arg: CODESPACE_NAME,
|
||||
wantErr: false,
|
||||
wantFileContents: CODESPACE_NAME,
|
||||
outputToFile: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
io, _, stdout, stderr := iostreams.Test()
|
||||
io.SetStdinTTY(true)
|
||||
io.SetStdoutTTY(true)
|
||||
a := NewApp(io, nil, testSelectApiMock(), nil)
|
||||
|
||||
opts := selectOptions{}
|
||||
if tt.outputToFile {
|
||||
file, err := ioutil.TempFile("", "codespace-selection-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer os.Remove(file.Name())
|
||||
|
||||
opts = selectOptions{filePath: file.Name()}
|
||||
}
|
||||
|
||||
if err := a.Select(context.Background(), tt.arg, opts); (err != nil) != tt.wantErr {
|
||||
t.Errorf("App.Select() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
|
||||
if out := stdout.String(); out != tt.wantStdout {
|
||||
t.Errorf("stdout = %q, want %q", out, tt.wantStdout)
|
||||
}
|
||||
if out := sortLines(stderr.String()); out != tt.wantStderr {
|
||||
t.Errorf("stderr = %q, want %q", out, tt.wantStderr)
|
||||
}
|
||||
|
||||
if tt.wantFileContents != "" {
|
||||
if opts.filePath == "" {
|
||||
t.Errorf("wantFileContents is set but opts.filePath is not")
|
||||
}
|
||||
|
||||
dat, err := os.ReadFile(opts.filePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(dat) != tt.wantFileContents {
|
||||
t.Errorf("file contents = %q, want %q", string(dat), CODESPACE_NAME)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testSelectApiMock() *apiClientMock {
|
||||
testingCodespace := &api.Codespace{
|
||||
Name: CODESPACE_NAME,
|
||||
}
|
||||
return &apiClientMock{
|
||||
GetCodespaceFunc: func(_ context.Context, name string, includeConnection bool) (*api.Codespace, error) {
|
||||
if name == CODESPACE_NAME {
|
||||
return testingCodespace, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("cannot find codespace")
|
||||
},
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue