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
7 changes: 7 additions & 0 deletions cmd/project/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
var createTemplateURLFlag string
var createGitBranchFlag string
var createAppNameFlag string
var createListFlag bool

// 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 Down Expand Up @@ -77,6 +78,7 @@ name your app 'agent' (not create an AI Agent), use the --name flag instead.`,
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)")
cmd.Flags().BoolVar(&createListFlag, "list", false, "list available app templates")

return cmd
}
Expand Down Expand Up @@ -121,6 +123,11 @@ func runCreateCommand(clients *shared.ClientFactory, cmd *cobra.Command, args []
appNameArg = createAppNameFlag
}

// List templates and exit early if the --list flag is set
if createListFlag {
return listTemplates(ctx, clients, categoryShortcut)
}

// Collect the template URL or select a starting template
template, err := promptTemplateSelection(cmd, clients, categoryShortcut)
if err != nil {
Expand Down
37 changes: 37 additions & 0 deletions cmd/project/create_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package project

import (
"context"
"fmt"
"strings"
"time"
Expand Down Expand Up @@ -240,6 +241,42 @@ func confirmExternalTemplateSelection(cmd *cobra.Command, clients *shared.Client
return true, nil
}

// listTemplates prints available templates for the create command
func listTemplates(ctx context.Context, clients *shared.ClientFactory, categoryShortcut string) error {
type categoryInfo struct {
id string
name string
}

var categories []categoryInfo
if categoryShortcut == "agent" {
categories = []categoryInfo{
{id: "slack-cli#ai-apps", name: "AI Agent apps"},
}
} else {
categories = []categoryInfo{
{id: "slack-cli#getting-started", name: "Getting started"},
{id: "slack-cli#ai-apps", name: "AI Agent apps"},
{id: "slack-cli#automation-apps", name: "Automation apps"},
}
Comment on lines +257 to +261
Copy link
Member

Choose a reason for hiding this comment

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

suggestion(non-blocking): Not a deal breaker, but I don't like that we're duplicating the categories in 2 areas. In getSelectionOptionsForCategory(...) we already list the category titles and now we're duplicating the categories here. We're also slightly changing the titles ("AI Agent app" vs "AI Agent apps" / "Starter app" vs "Getting started"). This will become more error-prone if we add or change categories later on.

We don't need to refactor this if it's complicated, but we should at least add a comment in both sections noting that changes must occur in the other section. And we should try to keep the naming consistent.

}

for _, category := range categories {
templates := getSelectionOptions(clients, category.id)
Copy link
Member

Choose a reason for hiding this comment

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

⭐ praise: This is a nice filter setup!

secondary := make([]string, len(templates))
for i, tmpl := range templates {
secondary[i] = tmpl.Repository
}
clients.IO.PrintInfo(ctx, false, style.Sectionf(style.TextSection{
Emoji: "house_buildings",
Text: style.Bold(category.name),
Secondary: secondary,
}))
}

return nil
}

// getSelectionTemplate returns a custom formatted template used for selecting a
// project template during creation
func getSelectionTemplate(clients *shared.ClientFactory) string {
Expand Down
40 changes: 40 additions & 0 deletions cmd/project/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,46 @@ func TestCreateCommand(t *testing.T) {
cm.IO.AssertNotCalled(t, "SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything)
},
},
"lists all templates with --list flag": {
CmdArgs: []string{"--list"},
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
createClientMock = new(CreateClientMock)
CreateFunc = createClientMock.Create
},
ExpectedOutputs: []string{
"Getting started",
"AI Agent apps",
"Automation apps",
"slack-samples/bolt-js-starter-template",
"slack-samples/bolt-python-starter-template",
"slack-samples/bolt-js-assistant-template",
"slack-samples/bolt-python-assistant-template",
"slack-samples/bolt-js-custom-function-template",
"slack-samples/bolt-python-custom-function-template",
"slack-samples/deno-starter-template",
},
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
createClientMock.AssertNotCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
},
},
"lists agent templates with agent --list flag": {
CmdArgs: []string{"agent", "--list"},
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
createClientMock = new(CreateClientMock)
CreateFunc = createClientMock.Create
},
ExpectedOutputs: []string{
"AI Agent apps",
"slack-samples/bolt-js-assistant-template",
"slack-samples/bolt-python-assistant-template",
},
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
createClientMock.AssertNotCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
output := cm.GetCombinedOutput()
assert.NotContains(t, output, "Getting started")
assert.NotContains(t, output, "Automation apps")
},
},
}, func(cf *shared.ClientFactory) *cobra.Command {
return NewCreateCommand(cf)
})
Expand Down
Loading