mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
+52
-45
@@ -5,11 +5,11 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/atomic"
|
||||
|
||||
"github.com/coder/coder/cli/clibase"
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/pty/ptytest"
|
||||
@@ -24,9 +24,9 @@ func TestAgent(t *testing.T) {
|
||||
|
||||
var disconnected atomic.Bool
|
||||
ptty := ptytest.New(t)
|
||||
cmd := &cobra.Command{
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
err := cliui.Agent(cmd.Context(), cmd.OutOrStdout(), cliui.AgentOptions{
|
||||
cmd := &clibase.Cmd{
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
err := cliui.Agent(inv.Context(), inv.Stdout, cliui.AgentOptions{
|
||||
WorkspaceName: "example",
|
||||
Fetch: func(_ context.Context) (codersdk.WorkspaceAgent, error) {
|
||||
agent := codersdk.WorkspaceAgent{
|
||||
@@ -44,12 +44,13 @@ func TestAgent(t *testing.T) {
|
||||
return err
|
||||
},
|
||||
}
|
||||
cmd.SetOutput(ptty.Output())
|
||||
cmd.SetIn(ptty.Input())
|
||||
|
||||
inv := cmd.Invoke()
|
||||
ptty.Attach(inv)
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
err := cmd.Execute()
|
||||
err := inv.Run()
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
ptty.ExpectMatchContext(ctx, "lost connection")
|
||||
@@ -66,9 +67,9 @@ func TestAgent_TimeoutWithTroubleshootingURL(t *testing.T) {
|
||||
wantURL := "https://coder.com/troubleshoot"
|
||||
|
||||
var connected, timeout atomic.Bool
|
||||
cmd := &cobra.Command{
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
err := cliui.Agent(cmd.Context(), cmd.OutOrStdout(), cliui.AgentOptions{
|
||||
cmd := &clibase.Cmd{
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
err := cliui.Agent(inv.Context(), inv.Stdout, cliui.AgentOptions{
|
||||
WorkspaceName: "example",
|
||||
Fetch: func(_ context.Context) (codersdk.WorkspaceAgent, error) {
|
||||
agent := codersdk.WorkspaceAgent{
|
||||
@@ -91,11 +92,12 @@ func TestAgent_TimeoutWithTroubleshootingURL(t *testing.T) {
|
||||
},
|
||||
}
|
||||
ptty := ptytest.New(t)
|
||||
cmd.SetOutput(ptty.Output())
|
||||
cmd.SetIn(ptty.Input())
|
||||
|
||||
inv := cmd.Invoke()
|
||||
ptty.Attach(inv)
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- cmd.ExecuteContext(ctx)
|
||||
done <- inv.WithContext(ctx).Run()
|
||||
}()
|
||||
ptty.ExpectMatchContext(ctx, "Don't panic, your workspace is booting")
|
||||
timeout.Store(true)
|
||||
@@ -115,9 +117,10 @@ func TestAgent_StartupTimeout(t *testing.T) {
|
||||
var status, state atomic.String
|
||||
setStatus := func(s codersdk.WorkspaceAgentStatus) { status.Store(string(s)) }
|
||||
setState := func(s codersdk.WorkspaceAgentLifecycle) { state.Store(string(s)) }
|
||||
cmd := &cobra.Command{
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
err := cliui.Agent(cmd.Context(), cmd.OutOrStdout(), cliui.AgentOptions{
|
||||
|
||||
cmd := &clibase.Cmd{
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
err := cliui.Agent(inv.Context(), inv.Stdout, cliui.AgentOptions{
|
||||
WorkspaceName: "example",
|
||||
Fetch: func(_ context.Context) (codersdk.WorkspaceAgent, error) {
|
||||
agent := codersdk.WorkspaceAgent{
|
||||
@@ -144,11 +147,12 @@ func TestAgent_StartupTimeout(t *testing.T) {
|
||||
}
|
||||
|
||||
ptty := ptytest.New(t)
|
||||
cmd.SetOutput(ptty.Output())
|
||||
cmd.SetIn(ptty.Input())
|
||||
|
||||
inv := cmd.Invoke()
|
||||
ptty.Attach(inv)
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- cmd.ExecuteContext(ctx)
|
||||
done <- inv.WithContext(ctx).Run()
|
||||
}()
|
||||
setStatus(codersdk.WorkspaceAgentConnecting)
|
||||
ptty.ExpectMatchContext(ctx, "Don't panic, your workspace is booting")
|
||||
@@ -173,9 +177,9 @@ func TestAgent_StartErrorExit(t *testing.T) {
|
||||
var status, state atomic.String
|
||||
setStatus := func(s codersdk.WorkspaceAgentStatus) { status.Store(string(s)) }
|
||||
setState := func(s codersdk.WorkspaceAgentLifecycle) { state.Store(string(s)) }
|
||||
cmd := &cobra.Command{
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
err := cliui.Agent(cmd.Context(), cmd.OutOrStdout(), cliui.AgentOptions{
|
||||
cmd := &clibase.Cmd{
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
err := cliui.Agent(inv.Context(), inv.Stdout, cliui.AgentOptions{
|
||||
WorkspaceName: "example",
|
||||
Fetch: func(_ context.Context) (codersdk.WorkspaceAgent, error) {
|
||||
agent := codersdk.WorkspaceAgent{
|
||||
@@ -202,11 +206,12 @@ func TestAgent_StartErrorExit(t *testing.T) {
|
||||
}
|
||||
|
||||
ptty := ptytest.New(t)
|
||||
cmd.SetOutput(ptty.Output())
|
||||
cmd.SetIn(ptty.Input())
|
||||
|
||||
inv := cmd.Invoke()
|
||||
ptty.Attach(inv)
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- cmd.ExecuteContext(ctx)
|
||||
done <- inv.WithContext(ctx).Run()
|
||||
}()
|
||||
setStatus(codersdk.WorkspaceAgentConnected)
|
||||
setState(codersdk.WorkspaceAgentLifecycleStarting)
|
||||
@@ -228,9 +233,9 @@ func TestAgent_NoWait(t *testing.T) {
|
||||
var status, state atomic.String
|
||||
setStatus := func(s codersdk.WorkspaceAgentStatus) { status.Store(string(s)) }
|
||||
setState := func(s codersdk.WorkspaceAgentLifecycle) { state.Store(string(s)) }
|
||||
cmd := &cobra.Command{
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
err := cliui.Agent(cmd.Context(), cmd.OutOrStdout(), cliui.AgentOptions{
|
||||
cmd := &clibase.Cmd{
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
err := cliui.Agent(inv.Context(), inv.Stdout, cliui.AgentOptions{
|
||||
WorkspaceName: "example",
|
||||
Fetch: func(_ context.Context) (codersdk.WorkspaceAgent, error) {
|
||||
agent := codersdk.WorkspaceAgent{
|
||||
@@ -257,11 +262,12 @@ func TestAgent_NoWait(t *testing.T) {
|
||||
}
|
||||
|
||||
ptty := ptytest.New(t)
|
||||
cmd.SetOutput(ptty.Output())
|
||||
cmd.SetIn(ptty.Input())
|
||||
|
||||
inv := cmd.Invoke()
|
||||
ptty.Attach(inv)
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- cmd.ExecuteContext(ctx)
|
||||
done <- inv.WithContext(ctx).Run()
|
||||
}()
|
||||
setStatus(codersdk.WorkspaceAgentConnecting)
|
||||
ptty.ExpectMatchContext(ctx, "Don't panic, your workspace is booting")
|
||||
@@ -270,19 +276,19 @@ func TestAgent_NoWait(t *testing.T) {
|
||||
require.NoError(t, <-done, "created - should exit early")
|
||||
|
||||
setState(codersdk.WorkspaceAgentLifecycleStarting)
|
||||
go func() { done <- cmd.ExecuteContext(ctx) }()
|
||||
go func() { done <- inv.WithContext(ctx).Run() }()
|
||||
require.NoError(t, <-done, "starting - should exit early")
|
||||
|
||||
setState(codersdk.WorkspaceAgentLifecycleStartTimeout)
|
||||
go func() { done <- cmd.ExecuteContext(ctx) }()
|
||||
go func() { done <- inv.WithContext(ctx).Run() }()
|
||||
require.NoError(t, <-done, "start timeout - should exit early")
|
||||
|
||||
setState(codersdk.WorkspaceAgentLifecycleStartError)
|
||||
go func() { done <- cmd.ExecuteContext(ctx) }()
|
||||
go func() { done <- inv.WithContext(ctx).Run() }()
|
||||
require.NoError(t, <-done, "start error - should exit early")
|
||||
|
||||
setState(codersdk.WorkspaceAgentLifecycleReady)
|
||||
go func() { done <- cmd.ExecuteContext(ctx) }()
|
||||
go func() { done <- inv.WithContext(ctx).Run() }()
|
||||
require.NoError(t, <-done, "ready - should exit early")
|
||||
}
|
||||
|
||||
@@ -297,9 +303,9 @@ func TestAgent_LoginBeforeReadyEnabled(t *testing.T) {
|
||||
var status, state atomic.String
|
||||
setStatus := func(s codersdk.WorkspaceAgentStatus) { status.Store(string(s)) }
|
||||
setState := func(s codersdk.WorkspaceAgentLifecycle) { state.Store(string(s)) }
|
||||
cmd := &cobra.Command{
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
err := cliui.Agent(cmd.Context(), cmd.OutOrStdout(), cliui.AgentOptions{
|
||||
cmd := &clibase.Cmd{
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
err := cliui.Agent(inv.Context(), inv.Stdout, cliui.AgentOptions{
|
||||
WorkspaceName: "example",
|
||||
Fetch: func(_ context.Context) (codersdk.WorkspaceAgent, error) {
|
||||
agent := codersdk.WorkspaceAgent{
|
||||
@@ -325,12 +331,13 @@ func TestAgent_LoginBeforeReadyEnabled(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
inv := cmd.Invoke()
|
||||
|
||||
ptty := ptytest.New(t)
|
||||
cmd.SetOutput(ptty.Output())
|
||||
cmd.SetIn(ptty.Input())
|
||||
ptty.Attach(inv)
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- cmd.ExecuteContext(ctx)
|
||||
done <- inv.WithContext(ctx).Run()
|
||||
}()
|
||||
setStatus(codersdk.WorkspaceAgentConnecting)
|
||||
ptty.ExpectMatchContext(ctx, "Don't panic, your workspace is booting")
|
||||
@@ -339,18 +346,18 @@ func TestAgent_LoginBeforeReadyEnabled(t *testing.T) {
|
||||
require.NoError(t, <-done, "created - should exit early")
|
||||
|
||||
setState(codersdk.WorkspaceAgentLifecycleStarting)
|
||||
go func() { done <- cmd.ExecuteContext(ctx) }()
|
||||
go func() { done <- inv.WithContext(ctx).Run() }()
|
||||
require.NoError(t, <-done, "starting - should exit early")
|
||||
|
||||
setState(codersdk.WorkspaceAgentLifecycleStartTimeout)
|
||||
go func() { done <- cmd.ExecuteContext(ctx) }()
|
||||
go func() { done <- inv.WithContext(ctx).Run() }()
|
||||
require.NoError(t, <-done, "start timeout - should exit early")
|
||||
|
||||
setState(codersdk.WorkspaceAgentLifecycleStartError)
|
||||
go func() { done <- cmd.ExecuteContext(ctx) }()
|
||||
go func() { done <- inv.WithContext(ctx).Run() }()
|
||||
require.NoError(t, <-done, "start error - should exit early")
|
||||
|
||||
setState(codersdk.WorkspaceAgentLifecycleReady)
|
||||
go func() { done <- cmd.ExecuteContext(ctx) }()
|
||||
go func() { done <- inv.WithContext(ctx).Run() }()
|
||||
require.NoError(t, <-done, "ready - should exit early")
|
||||
}
|
||||
|
||||
+4
-2
@@ -53,6 +53,8 @@ var Styles = struct {
|
||||
FocusedPrompt: defaultStyles.FocusedPrompt.Foreground(lipgloss.Color("#651fff")),
|
||||
Fuchsia: defaultStyles.SelectedMenuItem.Copy(),
|
||||
Logo: defaultStyles.Logo.SetString("Coder"),
|
||||
Warn: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#04B575", Dark: "#ECFD65"}),
|
||||
Wrap: lipgloss.NewStyle().Width(80),
|
||||
Warn: lipgloss.NewStyle().Foreground(
|
||||
lipgloss.AdaptiveColor{Light: "#04B575", Dark: "#ECFD65"},
|
||||
),
|
||||
Wrap: lipgloss.NewStyle().Width(80),
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/coder/coder/cli/clibase"
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/pty/ptytest"
|
||||
@@ -23,10 +23,10 @@ func TestGitAuth(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
ptty := ptytest.New(t)
|
||||
cmd := &cobra.Command{
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cmd := &clibase.Cmd{
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
var fetched atomic.Bool
|
||||
return cliui.GitAuth(cmd.Context(), cmd.OutOrStdout(), cliui.GitAuthOptions{
|
||||
return cliui.GitAuth(inv.Context(), inv.Stdout, cliui.GitAuthOptions{
|
||||
Fetch: func(ctx context.Context) ([]codersdk.TemplateVersionGitAuth, error) {
|
||||
defer fetched.Store(true)
|
||||
return []codersdk.TemplateVersionGitAuth{{
|
||||
@@ -40,12 +40,14 @@ func TestGitAuth(t *testing.T) {
|
||||
})
|
||||
},
|
||||
}
|
||||
cmd.SetOutput(ptty.Output())
|
||||
cmd.SetIn(ptty.Input())
|
||||
|
||||
inv := cmd.Invoke().WithContext(ctx)
|
||||
|
||||
ptty.Attach(inv)
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
err := cmd.Execute()
|
||||
err := inv.Run()
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
ptty.ExpectMatchContext(ctx, "You must authenticate with")
|
||||
|
||||
+42
-4
@@ -10,17 +10,22 @@ import (
|
||||
|
||||
// cliMessage provides a human-readable message for CLI errors and messages.
|
||||
type cliMessage struct {
|
||||
Level string
|
||||
Style lipgloss.Style
|
||||
Header string
|
||||
Prefix string
|
||||
Lines []string
|
||||
}
|
||||
|
||||
// String formats the CLI message for consumption by a human.
|
||||
func (m cliMessage) String() string {
|
||||
var str strings.Builder
|
||||
_, _ = fmt.Fprintf(&str, "%s\r\n",
|
||||
Styles.Bold.Render(m.Header))
|
||||
|
||||
if m.Prefix != "" {
|
||||
_, _ = str.WriteString(m.Style.Bold(true).Render(m.Prefix))
|
||||
}
|
||||
|
||||
_, _ = str.WriteString(m.Style.Bold(false).Render(m.Header))
|
||||
_, _ = str.WriteString("\r\n")
|
||||
for _, line := range m.Lines {
|
||||
_, _ = fmt.Fprintf(&str, " %s %s\r\n", m.Style.Render("|"), line)
|
||||
}
|
||||
@@ -30,9 +35,42 @@ func (m cliMessage) String() string {
|
||||
// Warn writes a log to the writer provided.
|
||||
func Warn(wtr io.Writer, header string, lines ...string) {
|
||||
_, _ = fmt.Fprint(wtr, cliMessage{
|
||||
Level: "warning",
|
||||
Style: Styles.Warn,
|
||||
Prefix: "WARN: ",
|
||||
Header: header,
|
||||
Lines: lines,
|
||||
}.String())
|
||||
}
|
||||
|
||||
// Warn writes a formatted log to the writer provided.
|
||||
func Warnf(wtr io.Writer, fmtStr string, args ...interface{}) {
|
||||
Warn(wtr, fmt.Sprintf(fmtStr, args...))
|
||||
}
|
||||
|
||||
// Info writes a log to the writer provided.
|
||||
func Info(wtr io.Writer, header string, lines ...string) {
|
||||
_, _ = fmt.Fprint(wtr, cliMessage{
|
||||
Header: header,
|
||||
Lines: lines,
|
||||
}.String())
|
||||
}
|
||||
|
||||
// Infof writes a formatted log to the writer provided.
|
||||
func Infof(wtr io.Writer, fmtStr string, args ...interface{}) {
|
||||
Info(wtr, fmt.Sprintf(fmtStr, args...))
|
||||
}
|
||||
|
||||
// Error writes a log to the writer provided.
|
||||
func Error(wtr io.Writer, header string, lines ...string) {
|
||||
_, _ = fmt.Fprint(wtr, cliMessage{
|
||||
Style: Styles.Error,
|
||||
Prefix: "ERROR: ",
|
||||
Header: header,
|
||||
Lines: lines,
|
||||
}.String())
|
||||
}
|
||||
|
||||
// Errorf writes a formatted log to the writer provided.
|
||||
func Errorf(wtr io.Writer, fmtStr string, args ...interface{}) {
|
||||
Error(wtr, fmt.Sprintf(fmtStr, args...))
|
||||
}
|
||||
|
||||
+28
-11
@@ -6,13 +6,14 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/cli/clibase"
|
||||
)
|
||||
|
||||
type OutputFormat interface {
|
||||
ID() string
|
||||
AttachFlags(cmd *cobra.Command)
|
||||
AttachOptions(opts *clibase.OptionSet)
|
||||
Format(ctx context.Context, data any) (string, error)
|
||||
}
|
||||
|
||||
@@ -45,11 +46,11 @@ func NewOutputFormatter(formats ...OutputFormat) *OutputFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
// AttachFlags attaches the --output flag to the given command, and any
|
||||
// AttachOptions attaches the --output flag to the given command, and any
|
||||
// additional flags required by the output formatters.
|
||||
func (f *OutputFormatter) AttachFlags(cmd *cobra.Command) {
|
||||
func (f *OutputFormatter) AttachOptions(opts *clibase.OptionSet) {
|
||||
for _, format := range f.formats {
|
||||
format.AttachFlags(cmd)
|
||||
format.AttachOptions(opts)
|
||||
}
|
||||
|
||||
formatNames := make([]string, 0, len(f.formats))
|
||||
@@ -57,7 +58,15 @@ func (f *OutputFormatter) AttachFlags(cmd *cobra.Command) {
|
||||
formatNames = append(formatNames, format.ID())
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&f.formatID, "output", "o", f.formats[0].ID(), "Output format. Available formats: "+strings.Join(formatNames, ", "))
|
||||
*opts = append(*opts,
|
||||
clibase.Option{
|
||||
Flag: "output",
|
||||
FlagShorthand: "o",
|
||||
Default: f.formats[0].ID(),
|
||||
Value: clibase.StringOf(&f.formatID),
|
||||
Description: "Output format. Available formats: " + strings.Join(formatNames, ", ") + ".",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Format formats the given data using the format specified by the --output
|
||||
@@ -118,9 +127,17 @@ func (*tableFormat) ID() string {
|
||||
return "table"
|
||||
}
|
||||
|
||||
// AttachFlags implements OutputFormat.
|
||||
func (f *tableFormat) AttachFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().StringSliceVarP(&f.columns, "column", "c", f.defaultColumns, "Columns to display in table output. Available columns: "+strings.Join(f.allColumns, ", "))
|
||||
// AttachOptions implements OutputFormat.
|
||||
func (f *tableFormat) AttachOptions(opts *clibase.OptionSet) {
|
||||
*opts = append(*opts,
|
||||
clibase.Option{
|
||||
Flag: "column",
|
||||
FlagShorthand: "c",
|
||||
Default: strings.Join(f.defaultColumns, ","),
|
||||
Value: clibase.StringArrayOf(&f.columns),
|
||||
Description: "Columns to display in table output. Available columns: " + strings.Join(f.allColumns, ", ") + ".",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Format implements OutputFormat.
|
||||
@@ -142,8 +159,8 @@ func (jsonFormat) ID() string {
|
||||
return "json"
|
||||
}
|
||||
|
||||
// AttachFlags implements OutputFormat.
|
||||
func (jsonFormat) AttachFlags(_ *cobra.Command) {}
|
||||
// AttachOptions implements OutputFormat.
|
||||
func (jsonFormat) AttachOptions(_ *clibase.OptionSet) {}
|
||||
|
||||
// Format implements OutputFormat.
|
||||
func (jsonFormat) Format(_ context.Context, data any) (string, error) {
|
||||
|
||||
+23
-15
@@ -6,16 +6,16 @@ import (
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/cli/clibase"
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
)
|
||||
|
||||
type format struct {
|
||||
id string
|
||||
attachFlagsFn func(cmd *cobra.Command)
|
||||
formatFn func(ctx context.Context, data any) (string, error)
|
||||
id string
|
||||
attachOptionsFn func(opts *clibase.OptionSet)
|
||||
formatFn func(ctx context.Context, data any) (string, error)
|
||||
}
|
||||
|
||||
var _ cliui.OutputFormat = &format{}
|
||||
@@ -24,9 +24,9 @@ func (f *format) ID() string {
|
||||
return f.id
|
||||
}
|
||||
|
||||
func (f *format) AttachFlags(cmd *cobra.Command) {
|
||||
if f.attachFlagsFn != nil {
|
||||
f.attachFlagsFn(cmd)
|
||||
func (f *format) AttachOptions(opts *clibase.OptionSet) {
|
||||
if f.attachOptionsFn != nil {
|
||||
f.attachOptionsFn(opts)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,8 +82,14 @@ func Test_OutputFormatter(t *testing.T) {
|
||||
cliui.JSONFormat(),
|
||||
&format{
|
||||
id: "foo",
|
||||
attachFlagsFn: func(cmd *cobra.Command) {
|
||||
cmd.Flags().StringP("foo", "f", "", "foo flag 1234")
|
||||
attachOptionsFn: func(opts *clibase.OptionSet) {
|
||||
opts.Add(clibase.Option{
|
||||
Name: "foo",
|
||||
Flag: "foo",
|
||||
FlagShorthand: "f",
|
||||
Value: clibase.DiscardValue,
|
||||
Description: "foo flag 1234",
|
||||
})
|
||||
},
|
||||
formatFn: func(_ context.Context, _ any) (string, error) {
|
||||
atomic.AddInt64(&called, 1)
|
||||
@@ -92,13 +98,15 @@ func Test_OutputFormatter(t *testing.T) {
|
||||
},
|
||||
)
|
||||
|
||||
cmd := &cobra.Command{}
|
||||
f.AttachFlags(cmd)
|
||||
cmd := &clibase.Cmd{}
|
||||
f.AttachOptions(&cmd.Options)
|
||||
|
||||
selected, err := cmd.Flags().GetString("output")
|
||||
fs := cmd.Options.FlagSet()
|
||||
|
||||
selected, err := fs.GetString("output")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "json", selected)
|
||||
usage := cmd.Flags().FlagUsages()
|
||||
usage := fs.FlagUsages()
|
||||
require.Contains(t, usage, "Available formats: json, foo")
|
||||
require.Contains(t, usage, "foo flag 1234")
|
||||
|
||||
@@ -112,13 +120,13 @@ func Test_OutputFormatter(t *testing.T) {
|
||||
require.Equal(t, data, got)
|
||||
require.EqualValues(t, 0, atomic.LoadInt64(&called))
|
||||
|
||||
require.NoError(t, cmd.Flags().Set("output", "foo"))
|
||||
require.NoError(t, fs.Set("output", "foo"))
|
||||
out, err = f.Format(ctx, data)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "foo", out)
|
||||
require.EqualValues(t, 1, atomic.LoadInt64(&called))
|
||||
|
||||
require.NoError(t, cmd.Flags().Set("output", "bar"))
|
||||
require.NoError(t, fs.Set("output", "bar"))
|
||||
out, err = f.Format(ctx, data)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "bar")
|
||||
|
||||
+21
-22
@@ -5,16 +5,15 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coder/coder/cli/clibase"
|
||||
"github.com/coder/coder/coderd/parameter"
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
func ParameterSchema(cmd *cobra.Command, parameterSchema codersdk.ParameterSchema) (string, error) {
|
||||
_, _ = fmt.Fprintln(cmd.OutOrStdout(), Styles.Bold.Render("var."+parameterSchema.Name))
|
||||
func ParameterSchema(inv *clibase.Invocation, parameterSchema codersdk.ParameterSchema) (string, error) {
|
||||
_, _ = fmt.Fprintln(inv.Stdout, Styles.Bold.Render("var."+parameterSchema.Name))
|
||||
if parameterSchema.Description != "" {
|
||||
_, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+strings.TrimSpace(strings.Join(strings.Split(parameterSchema.Description, "\n"), "\n "))+"\n")
|
||||
_, _ = fmt.Fprintln(inv.Stdout, " "+strings.TrimSpace(strings.Join(strings.Split(parameterSchema.Description, "\n"), "\n "))+"\n")
|
||||
}
|
||||
|
||||
var err error
|
||||
@@ -28,15 +27,15 @@ func ParameterSchema(cmd *cobra.Command, parameterSchema codersdk.ParameterSchem
|
||||
var value string
|
||||
if len(options) > 0 {
|
||||
// Move the cursor up a single line for nicer display!
|
||||
_, _ = fmt.Fprint(cmd.OutOrStdout(), "\033[1A")
|
||||
value, err = Select(cmd, SelectOptions{
|
||||
_, _ = fmt.Fprint(inv.Stdout, "\033[1A")
|
||||
value, err = Select(inv, SelectOptions{
|
||||
Options: options,
|
||||
Default: parameterSchema.DefaultSourceValue,
|
||||
HideSearch: true,
|
||||
})
|
||||
if err == nil {
|
||||
_, _ = fmt.Fprintln(cmd.OutOrStdout())
|
||||
_, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+Styles.Prompt.String()+Styles.Field.Render(value))
|
||||
_, _ = fmt.Fprintln(inv.Stdout)
|
||||
_, _ = fmt.Fprintln(inv.Stdout, " "+Styles.Prompt.String()+Styles.Field.Render(value))
|
||||
}
|
||||
} else {
|
||||
text := "Enter a value"
|
||||
@@ -45,7 +44,7 @@ func ParameterSchema(cmd *cobra.Command, parameterSchema codersdk.ParameterSchem
|
||||
}
|
||||
text += ":"
|
||||
|
||||
value, err = Prompt(cmd, PromptOptions{
|
||||
value, err = Prompt(inv, PromptOptions{
|
||||
Text: Styles.Bold.Render(text),
|
||||
})
|
||||
value = strings.TrimSpace(value)
|
||||
@@ -62,17 +61,17 @@ func ParameterSchema(cmd *cobra.Command, parameterSchema codersdk.ParameterSchem
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func RichParameter(cmd *cobra.Command, templateVersionParameter codersdk.TemplateVersionParameter) (string, error) {
|
||||
_, _ = fmt.Fprintln(cmd.OutOrStdout(), Styles.Bold.Render(templateVersionParameter.Name))
|
||||
func RichParameter(inv *clibase.Invocation, templateVersionParameter codersdk.TemplateVersionParameter) (string, error) {
|
||||
_, _ = fmt.Fprintln(inv.Stdout, Styles.Bold.Render(templateVersionParameter.Name))
|
||||
if templateVersionParameter.DescriptionPlaintext != "" {
|
||||
_, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+strings.TrimSpace(strings.Join(strings.Split(templateVersionParameter.DescriptionPlaintext, "\n"), "\n "))+"\n")
|
||||
_, _ = fmt.Fprintln(inv.Stdout, " "+strings.TrimSpace(strings.Join(strings.Split(templateVersionParameter.DescriptionPlaintext, "\n"), "\n "))+"\n")
|
||||
}
|
||||
|
||||
var err error
|
||||
var value string
|
||||
if templateVersionParameter.Type == "list(string)" {
|
||||
// Move the cursor up a single line for nicer display!
|
||||
_, _ = fmt.Fprint(cmd.OutOrStdout(), "\033[1A")
|
||||
_, _ = fmt.Fprint(inv.Stdout, "\033[1A")
|
||||
|
||||
var options []string
|
||||
err = json.Unmarshal([]byte(templateVersionParameter.DefaultValue), &options)
|
||||
@@ -80,29 +79,29 @@ func RichParameter(cmd *cobra.Command, templateVersionParameter codersdk.Templat
|
||||
return "", err
|
||||
}
|
||||
|
||||
values, err := MultiSelect(cmd, options)
|
||||
values, err := MultiSelect(inv, options)
|
||||
if err == nil {
|
||||
v, err := json.Marshal(&values)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(cmd.OutOrStdout())
|
||||
_, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+Styles.Prompt.String()+Styles.Field.Render(strings.Join(values, ", ")))
|
||||
_, _ = fmt.Fprintln(inv.Stdout)
|
||||
_, _ = fmt.Fprintln(inv.Stdout, " "+Styles.Prompt.String()+Styles.Field.Render(strings.Join(values, ", ")))
|
||||
value = string(v)
|
||||
}
|
||||
} else if len(templateVersionParameter.Options) > 0 {
|
||||
// Move the cursor up a single line for nicer display!
|
||||
_, _ = fmt.Fprint(cmd.OutOrStdout(), "\033[1A")
|
||||
_, _ = fmt.Fprint(inv.Stdout, "\033[1A")
|
||||
var richParameterOption *codersdk.TemplateVersionParameterOption
|
||||
richParameterOption, err = RichSelect(cmd, RichSelectOptions{
|
||||
richParameterOption, err = RichSelect(inv, RichSelectOptions{
|
||||
Options: templateVersionParameter.Options,
|
||||
Default: templateVersionParameter.DefaultValue,
|
||||
HideSearch: true,
|
||||
})
|
||||
if err == nil {
|
||||
_, _ = fmt.Fprintln(cmd.OutOrStdout())
|
||||
_, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+Styles.Prompt.String()+Styles.Field.Render(richParameterOption.Name))
|
||||
_, _ = fmt.Fprintln(inv.Stdout)
|
||||
_, _ = fmt.Fprintln(inv.Stdout, " "+Styles.Prompt.String()+Styles.Field.Render(richParameterOption.Name))
|
||||
value = richParameterOption.Value
|
||||
}
|
||||
} else {
|
||||
@@ -112,7 +111,7 @@ func RichParameter(cmd *cobra.Command, templateVersionParameter codersdk.Templat
|
||||
}
|
||||
text += ":"
|
||||
|
||||
value, err = Prompt(cmd, PromptOptions{
|
||||
value, err = Prompt(inv, PromptOptions{
|
||||
Text: Styles.Bold.Render(text),
|
||||
Validate: func(value string) error {
|
||||
return validateRichPrompt(value, templateVersionParameter)
|
||||
|
||||
+34
-17
@@ -11,8 +11,9 @@ import (
|
||||
|
||||
"github.com/bgentry/speakeasy"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/cli/clibase"
|
||||
)
|
||||
|
||||
// PromptOptions supply a set of options to the prompt.
|
||||
@@ -26,8 +27,16 @@ type PromptOptions struct {
|
||||
|
||||
const skipPromptFlag = "yes"
|
||||
|
||||
func AllowSkipPrompt(cmd *cobra.Command) {
|
||||
cmd.Flags().BoolP(skipPromptFlag, "y", false, "Bypass prompts")
|
||||
// SkipPromptOption adds a "--yes/-y" flag to the cmd that can be used to skip
|
||||
// prompts.
|
||||
func SkipPromptOption() clibase.Option {
|
||||
return clibase.Option{
|
||||
Flag: skipPromptFlag,
|
||||
FlagShorthand: "y",
|
||||
Description: "Bypass prompts.",
|
||||
// Discard
|
||||
Value: clibase.BoolOf(new(bool)),
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -36,17 +45,17 @@ const (
|
||||
)
|
||||
|
||||
// Prompt asks the user for input.
|
||||
func Prompt(cmd *cobra.Command, opts PromptOptions) (string, error) {
|
||||
func Prompt(inv *clibase.Invocation, opts PromptOptions) (string, error) {
|
||||
// If the cmd has a "yes" flag for skipping confirm prompts, honor it.
|
||||
// If it's not a "Confirm" prompt, then don't skip. As the default value of
|
||||
// "yes" makes no sense.
|
||||
if opts.IsConfirm && cmd.Flags().Lookup(skipPromptFlag) != nil {
|
||||
if skip, _ := cmd.Flags().GetBool(skipPromptFlag); skip {
|
||||
if opts.IsConfirm && inv.ParsedFlags().Lookup(skipPromptFlag) != nil {
|
||||
if skip, _ := inv.ParsedFlags().GetBool(skipPromptFlag); skip {
|
||||
return ConfirmYes, nil
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprint(cmd.OutOrStdout(), Styles.FocusedPrompt.String()+opts.Text+" ")
|
||||
_, _ = fmt.Fprint(inv.Stdout, Styles.FocusedPrompt.String()+opts.Text+" ")
|
||||
if opts.IsConfirm {
|
||||
if len(opts.Default) == 0 {
|
||||
opts.Default = ConfirmYes
|
||||
@@ -58,19 +67,24 @@ func Prompt(cmd *cobra.Command, opts PromptOptions) (string, error) {
|
||||
} else {
|
||||
renderedNo = Styles.Bold.Render(ConfirmNo)
|
||||
}
|
||||
_, _ = fmt.Fprint(cmd.OutOrStdout(), Styles.Placeholder.Render("("+renderedYes+Styles.Placeholder.Render("/"+renderedNo+Styles.Placeholder.Render(") "))))
|
||||
_, _ = fmt.Fprint(inv.Stdout, Styles.Placeholder.Render("("+renderedYes+Styles.Placeholder.Render("/"+renderedNo+Styles.Placeholder.Render(") "))))
|
||||
} else if opts.Default != "" {
|
||||
_, _ = fmt.Fprint(cmd.OutOrStdout(), Styles.Placeholder.Render("("+opts.Default+") "))
|
||||
_, _ = fmt.Fprint(inv.Stdout, Styles.Placeholder.Render("("+opts.Default+") "))
|
||||
}
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
|
||||
if inv.Stdin == nil {
|
||||
panic("inv.Stdin is nil")
|
||||
}
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
lineCh := make(chan string)
|
||||
|
||||
go func() {
|
||||
var line string
|
||||
var err error
|
||||
|
||||
inFile, isInputFile := cmd.InOrStdin().(*os.File)
|
||||
inFile, isInputFile := inv.Stdin.(*os.File)
|
||||
if opts.Secret && isInputFile && isatty.IsTerminal(inFile.Fd()) {
|
||||
// we don't install a signal handler here because speakeasy has its own
|
||||
line, err = speakeasy.Ask("")
|
||||
@@ -78,7 +92,7 @@ func Prompt(cmd *cobra.Command, opts PromptOptions) (string, error) {
|
||||
signal.Notify(interrupt, os.Interrupt)
|
||||
defer signal.Stop(interrupt)
|
||||
|
||||
reader := bufio.NewReader(cmd.InOrStdin())
|
||||
reader := bufio.NewReader(inv.Stdin)
|
||||
line, err = reader.ReadString('\n')
|
||||
|
||||
// Check if the first line beings with JSON object or array chars.
|
||||
@@ -96,7 +110,10 @@ func Prompt(cmd *cobra.Command, opts PromptOptions) (string, error) {
|
||||
if line == "" {
|
||||
line = opts.Default
|
||||
}
|
||||
lineCh <- line
|
||||
select {
|
||||
case <-inv.Context().Done():
|
||||
case lineCh <- line:
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
@@ -109,16 +126,16 @@ func Prompt(cmd *cobra.Command, opts PromptOptions) (string, error) {
|
||||
if opts.Validate != nil {
|
||||
err := opts.Validate(line)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintln(cmd.OutOrStdout(), defaultStyles.Error.Render(err.Error()))
|
||||
return Prompt(cmd, opts)
|
||||
_, _ = fmt.Fprintln(inv.Stdout, defaultStyles.Error.Render(err.Error()))
|
||||
return Prompt(inv, opts)
|
||||
}
|
||||
}
|
||||
return line, nil
|
||||
case <-cmd.Context().Done():
|
||||
return "", cmd.Context().Err()
|
||||
case <-inv.Context().Done():
|
||||
return "", inv.Context().Err()
|
||||
case <-interrupt:
|
||||
// Print a newline so that any further output starts properly on a new line.
|
||||
_, _ = fmt.Fprintln(cmd.OutOrStdout())
|
||||
_, _ = fmt.Fprintln(inv.Stdout)
|
||||
return "", Canceled
|
||||
}
|
||||
}
|
||||
|
||||
+24
-18
@@ -8,10 +8,10 @@ import (
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/cli/clibase"
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
"github.com/coder/coder/pty"
|
||||
"github.com/coder/coder/pty/ptytest"
|
||||
@@ -77,9 +77,9 @@ func TestPrompt(t *testing.T) {
|
||||
resp, err := newPrompt(ptty, cliui.PromptOptions{
|
||||
Text: "ShouldNotSeeThis",
|
||||
IsConfirm: true,
|
||||
}, func(cmd *cobra.Command) {
|
||||
cliui.AllowSkipPrompt(cmd)
|
||||
cmd.SetArgs([]string{"-y"})
|
||||
}, func(inv *clibase.Invocation) {
|
||||
inv.Command.Options = append(inv.Command.Options, cliui.SkipPromptOption())
|
||||
inv.Args = []string{"-y"}
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
doneChan <- resp
|
||||
@@ -145,23 +145,25 @@ func TestPrompt(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func newPrompt(ptty *ptytest.PTY, opts cliui.PromptOptions, cmdOpt func(cmd *cobra.Command)) (string, error) {
|
||||
func newPrompt(ptty *ptytest.PTY, opts cliui.PromptOptions, invOpt func(inv *clibase.Invocation)) (string, error) {
|
||||
value := ""
|
||||
cmd := &cobra.Command{
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cmd := &clibase.Cmd{
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
var err error
|
||||
value, err = cliui.Prompt(cmd, opts)
|
||||
value, err = cliui.Prompt(inv, opts)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
inv := cmd.Invoke()
|
||||
// Optionally modify the cmd
|
||||
if cmdOpt != nil {
|
||||
cmdOpt(cmd)
|
||||
if invOpt != nil {
|
||||
invOpt(inv)
|
||||
}
|
||||
cmd.SetOut(ptty.Output())
|
||||
cmd.SetErr(ptty.Output())
|
||||
cmd.SetIn(ptty.Input())
|
||||
return value, cmd.ExecuteContext(context.Background())
|
||||
inv.Stdout = ptty.Output()
|
||||
inv.Stderr = ptty.Output()
|
||||
inv.Stdin = ptty.Input()
|
||||
return value, inv.WithContext(context.Background()).Run()
|
||||
}
|
||||
|
||||
func TestPasswordTerminalState(t *testing.T) {
|
||||
@@ -208,13 +210,17 @@ func TestPasswordTerminalState(t *testing.T) {
|
||||
|
||||
// nolint:unused
|
||||
func passwordHelper() {
|
||||
cmd := &cobra.Command{
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cliui.Prompt(cmd, cliui.PromptOptions{
|
||||
cmd := &clibase.Cmd{
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
cliui.Prompt(inv, cliui.PromptOptions{
|
||||
Text: "Password:",
|
||||
Secret: true,
|
||||
})
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.ExecuteContext(context.Background())
|
||||
err := cmd.Invoke().WithOS().Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/coder/coder/cli/clibase"
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/codersdk"
|
||||
@@ -125,9 +125,9 @@ func newProvisionerJob(t *testing.T) provisionerJobTest {
|
||||
}
|
||||
jobLock := sync.Mutex{}
|
||||
logs := make(chan codersdk.ProvisionerJobLog, 1)
|
||||
cmd := &cobra.Command{
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return cliui.ProvisionerJob(cmd.Context(), cmd.OutOrStdout(), cliui.ProvisionerJobOptions{
|
||||
cmd := &clibase.Cmd{
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
return cliui.ProvisionerJob(inv.Context(), inv.Stdout, cliui.ProvisionerJobOptions{
|
||||
FetchInterval: time.Millisecond,
|
||||
Fetch: func() (codersdk.ProvisionerJob, error) {
|
||||
jobLock.Lock()
|
||||
@@ -145,13 +145,14 @@ func newProvisionerJob(t *testing.T) provisionerJobTest {
|
||||
})
|
||||
},
|
||||
}
|
||||
inv := cmd.Invoke()
|
||||
|
||||
ptty := ptytest.New(t)
|
||||
cmd.SetOutput(ptty.Output())
|
||||
cmd.SetIn(ptty.Input())
|
||||
ptty.Attach(inv)
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
err := cmd.ExecuteContext(context.Background())
|
||||
err := inv.WithContext(context.Background()).Run()
|
||||
if err != nil {
|
||||
assert.ErrorIs(t, err, cliui.Canceled)
|
||||
}
|
||||
|
||||
+11
-11
@@ -8,9 +8,9 @@ import (
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/cli/clibase"
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
@@ -68,7 +68,7 @@ type RichSelectOptions struct {
|
||||
}
|
||||
|
||||
// RichSelect displays a list of user options including name and description.
|
||||
func RichSelect(cmd *cobra.Command, richOptions RichSelectOptions) (*codersdk.TemplateVersionParameterOption, error) {
|
||||
func RichSelect(inv *clibase.Invocation, richOptions RichSelectOptions) (*codersdk.TemplateVersionParameterOption, error) {
|
||||
opts := make([]string, len(richOptions.Options))
|
||||
for i, option := range richOptions.Options {
|
||||
line := option.Name
|
||||
@@ -78,7 +78,7 @@ func RichSelect(cmd *cobra.Command, richOptions RichSelectOptions) (*codersdk.Te
|
||||
opts[i] = line
|
||||
}
|
||||
|
||||
selected, err := Select(cmd, SelectOptions{
|
||||
selected, err := Select(inv, SelectOptions{
|
||||
Options: opts,
|
||||
Default: richOptions.Default,
|
||||
Size: richOptions.Size,
|
||||
@@ -97,7 +97,7 @@ func RichSelect(cmd *cobra.Command, richOptions RichSelectOptions) (*codersdk.Te
|
||||
}
|
||||
|
||||
// Select displays a list of user options.
|
||||
func Select(cmd *cobra.Command, opts SelectOptions) (string, error) {
|
||||
func Select(inv *clibase.Invocation, opts SelectOptions) (string, error) {
|
||||
// The survey library used *always* fails when testing on Windows,
|
||||
// as it requires a live TTY (can't be a conpty). We should fork
|
||||
// this library to add a dummy fallback, that simply reads/writes
|
||||
@@ -123,17 +123,17 @@ func Select(cmd *cobra.Command, opts SelectOptions) (string, error) {
|
||||
is.Help.Text = ""
|
||||
}
|
||||
}), survey.WithStdio(fileReadWriter{
|
||||
Reader: cmd.InOrStdin(),
|
||||
Reader: inv.Stdin,
|
||||
}, fileReadWriter{
|
||||
Writer: cmd.OutOrStdout(),
|
||||
}, cmd.OutOrStdout()))
|
||||
Writer: inv.Stdout,
|
||||
}, inv.Stdout))
|
||||
if errors.Is(err, terminal.InterruptErr) {
|
||||
return value, Canceled
|
||||
}
|
||||
return value, err
|
||||
}
|
||||
|
||||
func MultiSelect(cmd *cobra.Command, items []string) ([]string, error) {
|
||||
func MultiSelect(inv *clibase.Invocation, items []string) ([]string, error) {
|
||||
// Similar hack is applied to Select()
|
||||
if flag.Lookup("test.v") != nil {
|
||||
return items, nil
|
||||
@@ -146,10 +146,10 @@ func MultiSelect(cmd *cobra.Command, items []string) ([]string, error) {
|
||||
|
||||
var values []string
|
||||
err := survey.AskOne(prompt, &values, survey.WithStdio(fileReadWriter{
|
||||
Reader: cmd.InOrStdin(),
|
||||
Reader: inv.Stdin,
|
||||
}, fileReadWriter{
|
||||
Writer: cmd.OutOrStdout(),
|
||||
}, cmd.OutOrStdout()))
|
||||
Writer: inv.Stdout,
|
||||
}, inv.Stdout))
|
||||
if errors.Is(err, terminal.InterruptErr) {
|
||||
return nil, Canceled
|
||||
}
|
||||
|
||||
+21
-22
@@ -1,13 +1,12 @@
|
||||
package cliui_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/cli/clibase"
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/pty/ptytest"
|
||||
@@ -32,16 +31,16 @@ func TestSelect(t *testing.T) {
|
||||
|
||||
func newSelect(ptty *ptytest.PTY, opts cliui.SelectOptions) (string, error) {
|
||||
value := ""
|
||||
cmd := &cobra.Command{
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cmd := &clibase.Cmd{
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
var err error
|
||||
value, err = cliui.Select(cmd, opts)
|
||||
value, err = cliui.Select(inv, opts)
|
||||
return err
|
||||
},
|
||||
}
|
||||
cmd.SetOutput(ptty.Output())
|
||||
cmd.SetIn(ptty.Input())
|
||||
return value, cmd.ExecuteContext(context.Background())
|
||||
inv := cmd.Invoke()
|
||||
ptty.Attach(inv)
|
||||
return value, inv.Run()
|
||||
}
|
||||
|
||||
func TestRichSelect(t *testing.T) {
|
||||
@@ -56,11 +55,11 @@ func TestRichSelect(t *testing.T) {
|
||||
{
|
||||
Name: "A-Name",
|
||||
Value: "A-Value",
|
||||
Description: "A-Description",
|
||||
Description: "A-Description.",
|
||||
}, {
|
||||
Name: "B-Name",
|
||||
Value: "B-Value",
|
||||
Description: "B-Description",
|
||||
Description: "B-Description.",
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -73,18 +72,18 @@ func TestRichSelect(t *testing.T) {
|
||||
|
||||
func newRichSelect(ptty *ptytest.PTY, opts cliui.RichSelectOptions) (string, error) {
|
||||
value := ""
|
||||
cmd := &cobra.Command{
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
richOption, err := cliui.RichSelect(cmd, opts)
|
||||
cmd := &clibase.Cmd{
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
richOption, err := cliui.RichSelect(inv, opts)
|
||||
if err == nil {
|
||||
value = richOption.Value
|
||||
}
|
||||
return err
|
||||
},
|
||||
}
|
||||
cmd.SetOutput(ptty.Output())
|
||||
cmd.SetIn(ptty.Input())
|
||||
return value, cmd.ExecuteContext(context.Background())
|
||||
inv := cmd.Invoke()
|
||||
ptty.Attach(inv)
|
||||
return value, inv.Run()
|
||||
}
|
||||
|
||||
func TestMultiSelect(t *testing.T) {
|
||||
@@ -106,16 +105,16 @@ func TestMultiSelect(t *testing.T) {
|
||||
|
||||
func newMultiSelect(ptty *ptytest.PTY, items []string) ([]string, error) {
|
||||
var values []string
|
||||
cmd := &cobra.Command{
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
selectedItems, err := cliui.MultiSelect(cmd, items)
|
||||
cmd := &clibase.Cmd{
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
selectedItems, err := cliui.MultiSelect(inv, items)
|
||||
if err == nil {
|
||||
values = selectedItems
|
||||
}
|
||||
return err
|
||||
},
|
||||
}
|
||||
cmd.SetOutput(ptty.Output())
|
||||
cmd.SetIn(ptty.Input())
|
||||
return values, cmd.ExecuteContext(context.Background())
|
||||
inv := cmd.Invoke()
|
||||
ptty.Attach(inv)
|
||||
return values, inv.Run()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user