Skip to content
Merged
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
17 changes: 15 additions & 2 deletions cmd/project/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
// Flags
var createTemplateURLFlag string
var createGitBranchFlag string
var createAppNameFlag string

// Handle to client's create function used for testing
// TODO - Find best practice, such as using an Interface and Struct to create a client
Expand All @@ -53,13 +54,17 @@ const viewMoreSamples = "slack-cli#view-more-samples"
func NewCreateCommand(clients *shared.ClientFactory) *cobra.Command {
cmd := &cobra.Command{
SuggestFor: []string{"new"},
Use: "create [name] [flags]",
Use: "create [name | agent <name>] [flags]",
Short: "Create a new Slack project",
Long: `Create a new Slack project on your local machine from an optional template`,
Long: `Create a new Slack project on your local machine from an optional template.

The 'agent' argument is a shortcut to create an AI Agent app. If you want to
name your app 'agent' (not create an AI Agent), use the --name flag instead.`,
Example: style.ExampleCommandsf([]style.ExampleCommand{
{Command: "create my-project", Meaning: "Create a new project from a template"},
{Command: "create agent my-agent-app", Meaning: "Create a new AI Agent app"},
{Command: "create my-project -t slack-samples/deno-hello-world", Meaning: "Start a new project from a specific template"},
{Command: "create --name my-project", Meaning: "Create a project named 'my-project'"},
}),
Args: cobra.MaximumNArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
Expand All @@ -71,6 +76,7 @@ func NewCreateCommand(clients *shared.ClientFactory) *cobra.Command {
// Add flags
cmd.Flags().StringVarP(&createTemplateURLFlag, "template", "t", "", "template URL for your app")
cmd.Flags().StringVarP(&createGitBranchFlag, "branch", "b", "", "name of git branch to checkout")
cmd.Flags().StringVarP(&createAppNameFlag, "name", "n", "", "name for your app (overrides the name argument)")

return cmd
}
Expand All @@ -85,6 +91,7 @@ func runCreateCommand(clients *shared.ClientFactory, cmd *cobra.Command, args []
appNameArg := ""
categoryShortcut := ""
templateFlagProvided := cmd.Flags().Changed("template")
nameFlagProvided := cmd.Flags().Changed("name")

if len(args) > 0 {
switch args[0] {
Expand All @@ -108,6 +115,12 @@ func runCreateCommand(clients *shared.ClientFactory, cmd *cobra.Command, args []
}
}

// --name flag overrides any positional app name argument
// This allows users to name their app "agent" without triggering the AI Agent shortcut
if nameFlagProvided {
appNameArg = createAppNameFlag
}

Comment on lines +118 to +123
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: Love the comment calling out that --name flag overrides any position app name argument.

// Collect the template URL or select a starting template
template, err := promptTemplateSelection(cmd, clients, categoryShortcut)
if err != nil {
Expand Down
125 changes: 125 additions & 0 deletions cmd/project/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,131 @@ func TestCreateCommand(t *testing.T) {
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected)
},
},
"creates an app named agent using name flag without triggering shortcut": {
CmdArgs: []string{"--name", "agent"},
Comment on lines +198 to +199
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Since our intent is for --name flag to override the <name> argument, perhaps we should have test for the combination to guarantee that future code changes don't create a regression in this behaviour:.

It may also be a good idea to test the agent use-case.

The test cases could be:

  1. slack create my-project --name my-name → Expects my-name
  2. slack create agent my-project --name my-name → Expects my-name

Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
// Should prompt for category since agent shortcut is NOT triggered
cm.IO.On("SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything).
Return(
iostreams.SelectPromptResponse{
Prompt: true,
Index: 0, // Select starter app
},
nil,
)
cm.IO.On("SelectPrompt", mock.Anything, "Select a language:", mock.Anything, mock.Anything).
Return(
iostreams.SelectPromptResponse{
Prompt: true,
Index: 0, // Select Node.js
},
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: "agent",
Template: template,
}
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)
},
},
"creates an agent app with name flag overriding positional arg": {
CmdArgs: []string{"agent", "--name", "my-custom-name"},
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
// Should skip category prompt due to agent shortcut
cm.IO.On("SelectPrompt", mock.Anything, "Select a language:", mock.Anything, mock.Anything).
Return(
iostreams.SelectPromptResponse{
Prompt: true,
Index: 0, // Select Node.js
},
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-assistant-template")
require.NoError(t, err)
expected := create.CreateArgs{
AppName: "my-custom-name", // --name flag overrides
Template: template,
}
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected)
// Verify that category prompt was NOT called (shortcut was triggered)
cm.IO.AssertNotCalled(t, "SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything)
},
},
"name flag overrides positional app name argument": {
CmdArgs: []string{"my-project", "--name", "my-name"},
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, // Select starter app
},
nil,
)
cm.IO.On("SelectPrompt", mock.Anything, "Select a language:", mock.Anything, mock.Anything).
Return(
iostreams.SelectPromptResponse{
Prompt: true,
Index: 0, // Select Node.js
},
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-name", // --name flag overrides "my-project" positional arg
Template: template,
}
createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, expected)
},
},
"name flag overrides positional app name argument with agent shortcut": {
CmdArgs: []string{"agent", "my-project", "--name", "my-name"},
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
// Should skip category prompt due to agent shortcut
cm.IO.On("SelectPrompt", mock.Anything, "Select a language:", mock.Anything, mock.Anything).
Return(
iostreams.SelectPromptResponse{
Prompt: true,
Index: 0, // Select Node.js
},
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-assistant-template")
require.NoError(t, err)
expected := create.CreateArgs{
AppName: "my-name", // --name flag overrides "my-project" positional arg
Template: template,
}
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)
},
},
}, func(cf *shared.ClientFactory) *cobra.Command {
return NewCreateCommand(cf)
})
Expand Down
Loading