-
Notifications
You must be signed in to change notification settings - Fork 31
Expand file tree
/
Copy pathcreate.go
More file actions
294 lines (253 loc) · 9.79 KB
/
create.go
File metadata and controls
294 lines (253 loc) · 9.79 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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
// 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"
"path/filepath"
"strings"
"github.com/slackapi/slack-cli/internal/logger"
"github.com/slackapi/slack-cli/internal/pkg/create"
"github.com/slackapi/slack-cli/internal/shared"
"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
var appCreateSpinner *style.Spinner
const copyTemplate = "Copying"
const cloneTemplate = "Cloning"
// 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"
}
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 within the template to use as project root")
return cmd
}
func runCreateCommand(clients *shared.ClientFactory, cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
// Set up event logger
log := newCreateLogger(clients, cmd)
// 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)
}
// Collect the template URL or select a starting template
template, err := promptTemplateSelection(cmd, clients, categoryShortcut)
if err != nil {
return err
}
// Set up spinners
appCreateSpinner = style.NewSpinner(cmd.OutOrStdout())
createArgs := create.CreateArgs{
AppName: appNameArg,
Template: template,
GitBranch: createGitBranchFlag,
Subdir: createSubdirFlag,
}
clients.EventTracker.SetAppTemplate(template.GetTemplatePath())
appDirPath, err := CreateFunc(ctx, clients, log, createArgs)
if err != nil {
printAppCreateError(clients, cmd, err)
return err
}
printCreateSuccess(ctx, clients, appDirPath)
return nil
}
/*
App creation is setting up local project directory
Events: on_app_create_completion
*/
// newCreateLogger creates a logger instance to receive event notifications
func newCreateLogger(clients *shared.ClientFactory, cmd *cobra.Command) *logger.Logger {
return logger.New(
// OnEvent
func(event *logger.LogEvent) {
switch event.Name {
case "on_app_create_template_default":
printAppCreateDefaultemplate(cmd, event)
case "on_app_create_template_custom":
printAppCreateCustomTemplate(cmd, event)
case "on_app_create_completion":
printProjectCreateCompletion(clients, cmd, event)
default:
// Ignore the event
}
},
)
}
/*
App creation (not Create command) is cloning the template and creating the project directory
Events: on_app_create_template_custom, on_app_create_completion
*/
func printAppCreateDefaultemplate(cmd *cobra.Command, event *logger.LogEvent) {
startAppCreateSpinner(copyTemplate)
}
// Print template URL if using custom app template
func printAppCreateCustomTemplate(cmd *cobra.Command, event *logger.LogEvent) {
var verb string
templatePath := event.DataToString("templatePath")
isGit := event.DataToBool("isGit")
gitBranch := event.DataToString("gitBranch")
if isGit {
verb = cloneTemplate
} else {
verb = copyTemplate
}
templateText := fmt.Sprintf(
"%s template from %s",
verb,
templatePath,
)
if gitBranch != "" {
templateText = fmt.Sprintf("%s (branch: %s)", templateText, gitBranch)
}
cmd.Print(style.Secondary(templateText), "\n\n")
startAppCreateSpinner(verb)
}
func startAppCreateSpinner(verb string) {
appCreateSpinner.Update(verb+" app template", "").Start()
}
// Display message and added files at completion of app creation
func printProjectCreateCompletion(clients *shared.ClientFactory, cmd *cobra.Command, event *logger.LogEvent) {
createCompletionText := style.Sectionf(style.TextSection{
Emoji: "gear",
Text: "Created project directory",
})
appCreateSpinner.Update(createCompletionText, "").Stop()
}
// 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, 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, 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, style.Sectionf(style.TextSection{
Emoji: "clipboard",
Text: "Next steps to begin development",
Secondary: secondaryOutput,
}))
}
clients.IO.PrintTrace(ctx, slacktrace.CreateSuccess)
}
// printAppCreateError stops the creation spinners and displays the returned error message
func printAppCreateError(clients *shared.ClientFactory, cmd *cobra.Command, err error) {
ctx := cmd.Context()
switch {
case appCreateSpinner.Active():
errorText := fmt.Sprintf("Error creating project directory: %s", err)
appCreateSpinner.Update(errorText, "warning").Stop()
default:
}
clients.IO.PrintTrace(ctx, slacktrace.CreateError)
}