Skip to content

Commit 7c99f80

Browse files
committed
fix: show generated app name in hint line instead of input default
1 parent ae1d9a0 commit 7c99f80

4 files changed

Lines changed: 112 additions & 4 deletions

File tree

cmd/project/create.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"path/filepath"
2121
"strings"
2222

23+
"github.com/slackapi/slack-cli/internal/iostreams"
2324
"github.com/slackapi/slack-cli/internal/logger"
2425
"github.com/slackapi/slack-cli/internal/pkg/create"
2526
"github.com/slackapi/slack-cli/internal/shared"
@@ -127,6 +128,21 @@ func runCreateCommand(clients *shared.ClientFactory, cmd *cobra.Command, args []
127128
return err
128129
}
129130

131+
// Prompt for app name if not provided via flag or argument
132+
if appNameArg == "" {
133+
defaultName := create.GenerateRandomAppName()
134+
cmd.Print(style.Secondary(fmt.Sprintf(" Press Enter to use the generated name: %s", defaultName)), "\n")
135+
name, err := clients.IO.InputPrompt(ctx, "Name your app:", iostreams.InputPromptConfig{})
136+
if err != nil {
137+
return err
138+
}
139+
if name != "" {
140+
appNameArg = name
141+
} else {
142+
appNameArg = defaultName
143+
}
144+
}
145+
130146
// Set up spinners
131147
appCreateSpinner = style.NewSpinner(cmd.OutOrStdout())
132148

cmd/project/create_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ func TestCreateCommand(t *testing.T) {
6262
},
6363
nil,
6464
)
65+
cm.IO.On("InputPrompt", mock.Anything, "Name your app:", mock.Anything).
66+
Return("my-app", nil)
6567
createClientMock = new(CreateClientMock)
6668
createClientMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", nil)
6769
CreateFunc = createClientMock.Create
@@ -70,9 +72,11 @@ func TestCreateCommand(t *testing.T) {
7072
template, err := create.ResolveTemplateURL("slack-samples/bolt-js-starter-template")
7173
require.NoError(t, err)
7274
expected := create.CreateArgs{
75+
AppName: "my-app",
7376
Template: template,
7477
}
7578
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected)
79+
cm.IO.AssertCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything)
7680
},
7781
},
7882
"creates a deno application from flags": {
@@ -94,6 +98,8 @@ func TestCreateCommand(t *testing.T) {
9498
},
9599
nil,
96100
)
101+
cm.IO.On("InputPrompt", mock.Anything, "Name your app:", mock.Anything).
102+
Return("my-deno-app", nil)
97103
createClientMock = new(CreateClientMock)
98104
createClientMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", nil)
99105
CreateFunc = createClientMock.Create
@@ -102,9 +108,11 @@ func TestCreateCommand(t *testing.T) {
102108
template, err := create.ResolveTemplateURL("slack-samples/deno-starter-template")
103109
require.NoError(t, err)
104110
expected := create.CreateArgs{
111+
AppName: "my-deno-app",
105112
Template: template,
106113
}
107114
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected)
115+
cm.IO.AssertCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything)
108116
},
109117
},
110118
"creates an agent app using agent argument shortcut": {
@@ -119,6 +127,8 @@ func TestCreateCommand(t *testing.T) {
119127
},
120128
nil,
121129
)
130+
cm.IO.On("InputPrompt", mock.Anything, "Name your app:", mock.Anything).
131+
Return("my-agent", nil)
122132
createClientMock = new(CreateClientMock)
123133
createClientMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", nil)
124134
CreateFunc = createClientMock.Create
@@ -127,11 +137,13 @@ func TestCreateCommand(t *testing.T) {
127137
template, err := create.ResolveTemplateURL("slack-samples/bolt-js-assistant-template")
128138
require.NoError(t, err)
129139
expected := create.CreateArgs{
140+
AppName: "my-agent",
130141
Template: template,
131142
}
132143
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected)
133144
// Verify that category prompt was NOT called
134145
cm.IO.AssertNotCalled(t, "SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything)
146+
cm.IO.AssertCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything)
135147
},
136148
},
137149
"creates an agent app with app name using agent argument": {
@@ -160,6 +172,8 @@ func TestCreateCommand(t *testing.T) {
160172
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected)
161173
// Verify that category prompt was NOT called
162174
cm.IO.AssertNotCalled(t, "SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything)
175+
// Verify that name prompt was NOT called since name was provided as arg
176+
cm.IO.AssertNotCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything)
163177
},
164178
},
165179
"creates an app named agent when template flag is provided": {
@@ -193,6 +207,8 @@ func TestCreateCommand(t *testing.T) {
193207
Template: template,
194208
}
195209
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected)
210+
// Verify that name prompt was NOT called since name was provided as arg
211+
cm.IO.AssertNotCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything)
196212
},
197213
},
198214
"creates an app named agent using name flag without triggering shortcut": {
@@ -229,6 +245,8 @@ func TestCreateCommand(t *testing.T) {
229245
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected)
230246
// Verify that category prompt WAS called (shortcut was not triggered)
231247
cm.IO.AssertCalled(t, "SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything)
248+
// Verify that name prompt was NOT called since --name flag was provided
249+
cm.IO.AssertNotCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything)
232250
},
233251
},
234252
"creates an agent app with name flag overriding positional arg": {
@@ -290,6 +308,8 @@ func TestCreateCommand(t *testing.T) {
290308
Template: template,
291309
}
292310
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected)
311+
// Verify that name prompt was NOT called since --name flag was provided
312+
cm.IO.AssertNotCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything)
293313
},
294314
},
295315
"name flag overrides positional app name argument with agent shortcut": {
@@ -318,6 +338,76 @@ func TestCreateCommand(t *testing.T) {
318338
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected)
319339
// Verify that category prompt was NOT called (agent shortcut was triggered)
320340
cm.IO.AssertNotCalled(t, "SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything)
341+
// Verify that name prompt was NOT called since --name flag was provided
342+
cm.IO.AssertNotCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything)
343+
},
344+
},
345+
"user accepts default name from prompt": {
346+
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
347+
cm.IO.On("SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything).
348+
Return(
349+
iostreams.SelectPromptResponse{
350+
Prompt: true,
351+
Index: 0,
352+
},
353+
nil,
354+
)
355+
cm.IO.On("SelectPrompt", mock.Anything, "Select a language:", mock.Anything, mock.Anything).
356+
Return(
357+
iostreams.SelectPromptResponse{
358+
Prompt: true,
359+
Index: 0,
360+
},
361+
nil,
362+
)
363+
// Return empty string to simulate pressing Enter (accepting default)
364+
cm.IO.On("InputPrompt", mock.Anything, "Name your app:", mock.Anything).
365+
Return("", nil)
366+
createClientMock = new(CreateClientMock)
367+
createClientMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", nil)
368+
CreateFunc = createClientMock.Create
369+
},
370+
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
371+
cm.IO.AssertCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything)
372+
// When the user accepts the default (empty return), the generated name is used
373+
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, mock.MatchedBy(func(args create.CreateArgs) bool {
374+
return args.AppName != ""
375+
}))
376+
},
377+
},
378+
"positional arg skips name prompt": {
379+
CmdArgs: []string{"my-project"},
380+
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
381+
cm.IO.On("SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything).
382+
Return(
383+
iostreams.SelectPromptResponse{
384+
Prompt: true,
385+
Index: 0,
386+
},
387+
nil,
388+
)
389+
cm.IO.On("SelectPrompt", mock.Anything, "Select a language:", mock.Anything, mock.Anything).
390+
Return(
391+
iostreams.SelectPromptResponse{
392+
Prompt: true,
393+
Index: 0,
394+
},
395+
nil,
396+
)
397+
createClientMock = new(CreateClientMock)
398+
createClientMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", nil)
399+
CreateFunc = createClientMock.Create
400+
},
401+
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
402+
template, err := create.ResolveTemplateURL("slack-samples/bolt-js-starter-template")
403+
require.NoError(t, err)
404+
expected := create.CreateArgs{
405+
AppName: "my-project",
406+
Template: template,
407+
}
408+
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected)
409+
// Verify that name prompt was NOT called since name was provided as positional arg
410+
cm.IO.AssertNotCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything)
321411
},
322412
},
323413
}, func(cf *shared.ClientFactory) *cobra.Command {

internal/iostreams/survey.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,8 @@ var InputQuestionTemplate = fmt.Sprintf(`
175175

176176
// InputPromptConfig holds additional config for an Input prompt
177177
type InputPromptConfig struct {
178-
Required bool // Whether the input must be non-empty
178+
Required bool // Whether the input must be non-empty
179+
Default string // Default value pre-filled in the prompt
179180
}
180181

181182
// GetFlags returns all flags for the Input prompt
@@ -200,6 +201,7 @@ func (io *IOStreams) InputPrompt(ctx context.Context, message string, cfg InputP
200201
var input string
201202
err := survey.AskOne(&survey.Input{
202203
Message: message,
204+
Default: cfg.Default,
203205
}, &input, SurveyOptions(cfg)...)
204206

205207
if err != nil {

internal/pkg/create/create.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@ func Create(ctx context.Context, clients *shared.ClientFactory, log *logger.Logg
150150
return appDirPath, nil
151151
}
152152

153-
// generateRandomAppName will create a random app name based on two words and a number
154-
func generateRandomAppName() string {
153+
// GenerateRandomAppName will create a random app name based on two words and a number
154+
func GenerateRandomAppName() string {
155155
rand.New(rand.NewSource(time.Now().UnixNano()))
156156
var firstRandomNum = rand.Intn(len(adjectives))
157157
var secondRandomNum = rand.Intn(len(animals))
@@ -162,7 +162,7 @@ func generateRandomAppName() string {
162162
// getAppDirName will validate and return the app's directory name
163163
func getAppDirName(appName string) (string, error) {
164164
if len(appName) <= 0 {
165-
return generateRandomAppName(), nil
165+
return GenerateRandomAppName(), nil
166166
}
167167

168168
// trim whitespace

0 commit comments

Comments
 (0)