-
Notifications
You must be signed in to change notification settings - Fork 32
Expand file tree
/
Copy pathcreate.go
More file actions
241 lines (212 loc) · 8.43 KB
/
create.go
File metadata and controls
241 lines (212 loc) · 8.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// Copyright 2022-2026 Salesforce, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package project
import (
"context"
"fmt"
"math/rand"
"path/filepath"
"strings"
"time"
"github.com/slackapi/slack-cli/internal/iostreams"
"github.com/slackapi/slack-cli/internal/pkg/create"
"github.com/slackapi/slack-cli/internal/shared"
"github.com/slackapi/slack-cli/internal/slackerror"
"github.com/slackapi/slack-cli/internal/slacktrace"
"github.com/slackapi/slack-cli/internal/style"
"github.com/spf13/cobra"
)
// Flags
var createTemplateURLFlag string
var createGitBranchFlag string
var createAppNameFlag string
var createListFlag bool
var createSubdirFlag 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
var CreateFunc = create.Create
// promptObject describes the Github app template
type promptObject struct {
Title string // "Reverse string"
Repository string // "slack-samples/reverse-string"
Description string // "A function that reverses a given string"
Subdir string // "agents/hello-world" - subdirectory within the repository
}
const viewMoreSamples = "slack-cli#view-more-samples"
func NewCreateCommand(clients *shared.ClientFactory) *cobra.Command {
cmd := &cobra.Command{
SuggestFor: []string{"new"},
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.
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'"},
{Command: "create my-project -t org/monorepo --subdir apps/my-app", Meaning: "Create from a subdirectory of a template"},
}),
Args: cobra.MaximumNArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
clients.Config.SetFlags(cmd)
return runCreateCommand(clients, cmd, args)
},
}
// 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)")
cmd.Flags().BoolVar(&createListFlag, "list", false, "list available app templates")
cmd.Flags().StringVar(&createSubdirFlag, "subdir", "", "subdirectory in the template to use as project")
return cmd
}
func runCreateCommand(clients *shared.ClientFactory, cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
// Get optional app name passed as an arg and check for category shortcuts
appNameArg := ""
categoryShortcut := ""
templateFlagProvided := cmd.Flags().Changed("template")
nameFlagProvided := cmd.Flags().Changed("name")
if len(args) > 0 {
switch args[0] {
case "samples", "create":
// These are special commands, not app names
case "agent":
// Only treat as shortcut if --template flag is not provided
if !templateFlagProvided {
// Shortcut to AI apps category
categoryShortcut = "agent"
// Check if a second argument was provided as the app name
if len(args) > 1 {
appNameArg = args[1]
}
} else {
// When --template is provided, "agent" is the app name
appNameArg = args[0]
}
default:
appNameArg = args[0]
}
}
// --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
}
// List templates and exit early if the --list flag is set
if createListFlag {
return listTemplates(ctx, clients, categoryShortcut)
}
// --subdir requires --template
if cmd.Flags().Changed("subdir") && !templateFlagProvided {
return slackerror.New(slackerror.ErrMismatchedFlags).
WithMessage("The --subdir flag requires the --template flag")
}
// Collect the template URL or select a starting template
template, err := promptTemplateSelection(cmd, clients, categoryShortcut)
if err != nil {
return err
}
// Prompt for app name if not provided via flag or argument
if appNameArg == "" {
if clients.IO.IsTTY() {
defaultName := generateRandomAppName()
name, err := clients.IO.InputPrompt(ctx, "Name your app:", iostreams.InputPromptConfig{
Placeholder: defaultName,
})
if err != nil {
return err
}
if name != "" {
appNameArg = name
} else {
appNameArg = defaultName
}
} else {
appNameArg = generateRandomAppName()
}
}
subdir := createSubdirFlag
if subdir == "" {
subdir = template.GetSubdir()
}
createArgs := create.CreateArgs{
AppName: appNameArg,
Template: template,
GitBranch: createGitBranchFlag,
Subdir: subdir,
}
clients.EventTracker.SetAppTemplate(template.GetTemplatePath())
appDirPath, err := CreateFunc(ctx, clients, createArgs)
if err != nil {
return err
}
printCreateSuccess(ctx, clients, appDirPath)
return nil
}
// printCreateSuccess outputs an informative message after creating a new app
func printCreateSuccess(ctx context.Context, clients *shared.ClientFactory, appPath string) {
// Check if this is a Deno project to conditionally enable some features
var isDenoProject = false
if clients.Runtime != nil {
isDenoProject = strings.Contains(strings.ToLower(clients.Runtime.Name()), "deno")
}
// Include documentation and information about ROSI for deno apps
if isDenoProject {
clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{
Emoji: "compass",
Text: "Explore the documentation to learn more",
Secondary: []string{
"Read the README.md or peruse the docs over at " + style.Highlight("https://docs.slack.dev/tools/deno-slack-sdk"),
"Find available commands and usage info with " + style.Commandf("help", false),
},
}))
clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{
Emoji: "clipboard",
Text: "Follow the steps below to begin development",
Secondary: []string{
"Change into your project directory with " + style.CommandText(fmt.Sprintf("cd %s%s", appPath, string(filepath.Separator))),
"Develop locally and see changes in real-time with " + style.Commandf("run", true),
"When you're ready to deploy for production with " + style.Commandf("deploy", true),
},
}))
} else {
var secondaryOutput []string
// Output about the README.md
if _, err := clients.Fs.Stat(filepath.Join(appPath, "README.md")); !clients.Os.IsNotExist(err) {
secondaryOutput = append(secondaryOutput, "Learn more about the project in the "+style.Highlight("README.md"))
}
// Output about general usage
secondaryOutput = append(secondaryOutput,
"Change into your project with "+style.CommandText(fmt.Sprintf("cd %s%s", appPath, string(filepath.Separator))),
"Start developing and see changes in real-time with "+style.Commandf("run", true),
)
clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{
Emoji: "clipboard",
Text: "Next steps to begin development",
Secondary: secondaryOutput,
}))
}
clients.IO.PrintTrace(ctx, slacktrace.CreateSuccess)
}
// 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(create.Adjectives))
var secondRandomNum = rand.Intn(len(create.Animals))
var randomName = fmt.Sprintf("%s-%s-%d", create.Adjectives[firstRandomNum], create.Animals[secondRandomNum], rand.Intn(1000))
return randomName
}