Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions cmd/project/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"path/filepath"
"strings"

"github.com/slackapi/slack-cli/internal/iostreams"
"github.com/slackapi/slack-cli/internal/logger"
"github.com/slackapi/slack-cli/internal/pkg/create"
"github.com/slackapi/slack-cli/internal/shared"
Expand Down Expand Up @@ -127,6 +128,21 @@ func runCreateCommand(clients *shared.ClientFactory, cmd *cobra.Command, args []
return err
}

// Prompt for app name if not provided via flag or argument
if appNameArg == "" {
defaultName := create.GenerateRandomAppName()
cmd.Print(style.Secondary(fmt.Sprintf(" Press Enter to use the generated name: %s", defaultName)), "\n")
name, err := clients.IO.InputPrompt(ctx, "Name your app:", iostreams.InputPromptConfig{})
if err != nil {
return err
}
if name != "" {
appNameArg = name
} else {
appNameArg = defaultName
}
}

// Set up spinners
appCreateSpinner = style.NewSpinner(cmd.OutOrStdout())

Expand Down
90 changes: 90 additions & 0 deletions cmd/project/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ func TestCreateCommand(t *testing.T) {
},
nil,
)
cm.IO.On("InputPrompt", mock.Anything, "Name your app:", mock.Anything).
Return("my-app", nil)
createClientMock = new(CreateClientMock)
createClientMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", nil)
CreateFunc = createClientMock.Create
Expand All @@ -70,9 +72,11 @@ func TestCreateCommand(t *testing.T) {
template, err := create.ResolveTemplateURL("slack-samples/bolt-js-starter-template")
require.NoError(t, err)
expected := create.CreateArgs{
AppName: "my-app",
Template: template,
}
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected)
cm.IO.AssertCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything)
},
},
"creates a deno application from flags": {
Expand All @@ -94,6 +98,8 @@ func TestCreateCommand(t *testing.T) {
},
nil,
)
cm.IO.On("InputPrompt", mock.Anything, "Name your app:", mock.Anything).
Return("my-deno-app", nil)
createClientMock = new(CreateClientMock)
createClientMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", nil)
CreateFunc = createClientMock.Create
Expand All @@ -102,9 +108,11 @@ func TestCreateCommand(t *testing.T) {
template, err := create.ResolveTemplateURL("slack-samples/deno-starter-template")
require.NoError(t, err)
expected := create.CreateArgs{
AppName: "my-deno-app",
Template: template,
}
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected)
cm.IO.AssertCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything)
},
},
"creates an agent app using agent argument shortcut": {
Expand All @@ -119,6 +127,8 @@ func TestCreateCommand(t *testing.T) {
},
nil,
)
cm.IO.On("InputPrompt", mock.Anything, "Name your app:", mock.Anything).
Return("my-agent", nil)
createClientMock = new(CreateClientMock)
createClientMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", nil)
CreateFunc = createClientMock.Create
Expand All @@ -127,11 +137,13 @@ func TestCreateCommand(t *testing.T) {
template, err := create.ResolveTemplateURL("slack-samples/bolt-js-assistant-template")
require.NoError(t, err)
expected := create.CreateArgs{
AppName: "my-agent",
Template: template,
}
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected)
// Verify that category prompt was NOT called
cm.IO.AssertNotCalled(t, "SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything)
cm.IO.AssertCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything)
},
},
"creates an agent app with app name using agent argument": {
Expand Down Expand Up @@ -160,6 +172,8 @@ func TestCreateCommand(t *testing.T) {
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected)
// Verify that category prompt was NOT called
cm.IO.AssertNotCalled(t, "SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything)
// Verify that name prompt was NOT called since name was provided as arg
cm.IO.AssertNotCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything)
},
},
"creates an app named agent when template flag is provided": {
Expand Down Expand Up @@ -193,6 +207,8 @@ func TestCreateCommand(t *testing.T) {
Template: template,
}
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected)
// Verify that name prompt was NOT called since name was provided as arg
cm.IO.AssertNotCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything)
},
},
"creates an app named agent using name flag without triggering shortcut": {
Expand Down Expand Up @@ -229,6 +245,8 @@ func TestCreateCommand(t *testing.T) {
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected)
// Verify that category prompt WAS called (shortcut was not triggered)
cm.IO.AssertCalled(t, "SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything)
// Verify that name prompt was NOT called since --name flag was provided
cm.IO.AssertNotCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything)
},
},
"creates an agent app with name flag overriding positional arg": {
Expand Down Expand Up @@ -290,6 +308,8 @@ func TestCreateCommand(t *testing.T) {
Template: template,
}
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected)
// Verify that name prompt was NOT called since --name flag was provided
cm.IO.AssertNotCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything)
},
},
"name flag overrides positional app name argument with agent shortcut": {
Expand Down Expand Up @@ -318,6 +338,76 @@ func TestCreateCommand(t *testing.T) {
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected)
// Verify that category prompt was NOT called (agent shortcut was triggered)
cm.IO.AssertNotCalled(t, "SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything)
// Verify that name prompt was NOT called since --name flag was provided
cm.IO.AssertNotCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything)
},
},
"user accepts default name from prompt": {
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
cm.IO.On("SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything).
Return(
iostreams.SelectPromptResponse{
Prompt: true,
Index: 0,
},
nil,
)
cm.IO.On("SelectPrompt", mock.Anything, "Select a language:", mock.Anything, mock.Anything).
Return(
iostreams.SelectPromptResponse{
Prompt: true,
Index: 0,
},
nil,
)
// Return empty string to simulate pressing Enter (accepting default)
cm.IO.On("InputPrompt", mock.Anything, "Name your app:", mock.Anything).
Return("", nil)
createClientMock = new(CreateClientMock)
createClientMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", nil)
CreateFunc = createClientMock.Create
},
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
cm.IO.AssertCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything)
// When the user accepts the default (empty return), the generated name is used
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, mock.MatchedBy(func(args create.CreateArgs) bool {
return args.AppName != ""
}))
},
},
"positional arg skips name prompt": {
CmdArgs: []string{"my-project"},
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
cm.IO.On("SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything).
Return(
iostreams.SelectPromptResponse{
Prompt: true,
Index: 0,
},
nil,
)
cm.IO.On("SelectPrompt", mock.Anything, "Select a language:", mock.Anything, mock.Anything).
Return(
iostreams.SelectPromptResponse{
Prompt: true,
Index: 0,
},
nil,
)
createClientMock = new(CreateClientMock)
createClientMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", nil)
CreateFunc = createClientMock.Create
},
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
template, err := create.ResolveTemplateURL("slack-samples/bolt-js-starter-template")
require.NoError(t, err)
expected := create.CreateArgs{
AppName: "my-project",
Template: template,
}
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected)
// Verify that name prompt was NOT called since name was provided as positional arg
cm.IO.AssertNotCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything)
},
},
}, func(cf *shared.ClientFactory) *cobra.Command {
Expand Down
4 changes: 3 additions & 1 deletion internal/iostreams/survey.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ var InputQuestionTemplate = fmt.Sprintf(`

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

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

if err != nil {
Expand Down
6 changes: 3 additions & 3 deletions internal/pkg/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ func Create(ctx context.Context, clients *shared.ClientFactory, log *logger.Logg
return appDirPath, nil
}

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

// trim whitespace
Expand Down
Loading