Skip to content

Commit 69ddbe7

Browse files
authored
Merge branch 'main' into chore-bump-golang
2 parents 3dc741e + 2e9d76f commit 69ddbe7

10 files changed

Lines changed: 240 additions & 40 deletions

File tree

.github/pull_request_template.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
### Changelog
2+
3+
(Add a note with features or fixes that change the user experience for release notes)
4+
15
### Summary
26

37
(Please describe the goal of this pull request and mention any related issue numbers)
48

59
### Requirements
610

7-
* [ ] I've read and understood the [Contributing Guidelines](/blob/main/.github/contributing.md) and have done my best effort to follow them.
8-
* [ ] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct).
11+
- [ ] I've read and understood the [Contributing Guidelines](https://github.com/slackapi/slack-cli/blob/main/.github/CONTRIBUTING.md) and have done my best effort to follow them.
12+
- [ ] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct).

cmd/platform/deploy.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -316,16 +316,13 @@ func printDeployHostingCompletion(clients *shared.ClientFactory, cmd *cobra.Comm
316316
func errorMissingDeployHook(clients *shared.ClientFactory) error {
317317
if !clients.SDKConfig.Hooks.Deploy.IsAvailable() {
318318
return slackerror.New(slackerror.ErrSDKHookNotFound).
319-
WithMessage("Missing the `deploy` hook from the `%s` file", config.GetProjectHooksJSONFilePath()).
319+
WithMessage("No deploy script found").
320320
WithRemediation("%s", strings.Join([]string{
321-
"Provide a command or script to run with the deploy command by adding a new hook.",
321+
"For deployment options, see:",
322+
" https://docs.slack.dev/tools/slack-cli/reference/hooks/#deploy",
322323
"",
323-
fmt.Sprintf("Example `%s` `deploy` hook:", config.GetProjectHooksJSONFilePath()),
324-
"{",
325-
` "hooks": {`,
326-
` "deploy": "./deploy.sh"`,
327-
" }",
328-
"}",
324+
"To start a local development server, use:",
325+
fmt.Sprintf(" %s", style.Commandf("run", false)),
329326
}, "\n"))
330327
}
331328
return nil

cmd/platform/deploy_test.go

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,14 @@ func TestDeployCommand(t *testing.T) {
111111

112112
func TestDeployCommand_HasValidDeploymentMethod(t *testing.T) {
113113
tests := map[string]struct {
114-
app types.App
115-
manifest types.SlackYaml
116-
manifestError error
117-
manifestSource config.ManifestSource
118-
deployScript string
119-
expectedError error
114+
app types.App
115+
manifest types.SlackYaml
116+
manifestError error
117+
manifestSource config.ManifestSource
118+
deployScript string
119+
expectedError error
120+
expectedMessage string
121+
expectedRemediation []string
120122
}{
121123
"fails when no manifest exists": {
122124
manifestError: slackerror.New(slackerror.ErrInvalidManifest),
@@ -142,8 +144,10 @@ func TestDeployCommand_HasValidDeploymentMethod(t *testing.T) {
142144
deployScript: "sleep 4",
143145
},
144146
"fails if no deploy hook is provided": {
145-
manifestSource: config.ManifestSourceLocal,
146-
expectedError: slackerror.New(slackerror.ErrSDKHookNotFound),
147+
manifestSource: config.ManifestSourceLocal,
148+
expectedError: slackerror.New(slackerror.ErrSDKHookNotFound),
149+
expectedMessage: "No deploy script found",
150+
expectedRemediation: []string{"https://docs.slack.dev/tools/slack-cli/reference/hooks/#deploy", "run"},
147151
},
148152
"succeeds if the app exists and the manifest source is remote": {
149153
app: types.App{
@@ -183,11 +187,14 @@ func TestDeployCommand_HasValidDeploymentMethod(t *testing.T) {
183187
err := hasValidDeploymentMethod(ctx, clients, app, types.SlackAuth{})
184188
if tc.expectedError != nil {
185189
require.Error(t, err)
186-
assert.Equal(
187-
t,
188-
slackerror.ToSlackError(tc.expectedError).Code,
189-
slackerror.ToSlackError(err).Code,
190-
)
190+
slackErr := slackerror.ToSlackError(err)
191+
assert.Equal(t, slackerror.ToSlackError(tc.expectedError).Code, slackErr.Code)
192+
if tc.expectedMessage != "" {
193+
assert.Contains(t, slackErr.Message, tc.expectedMessage)
194+
}
195+
for _, r := range tc.expectedRemediation {
196+
assert.Contains(t, slackErr.Remediation, r)
197+
}
191198
} else {
192199
require.NoError(t, err)
193200
}

cmd/project/create.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
var createTemplateURLFlag string
3333
var createGitBranchFlag string
3434
var createAppNameFlag string
35+
var createListFlag bool
3536

3637
// Handle to client's create function used for testing
3738
// TODO - Find best practice, such as using an Interface and Struct to create a client
@@ -77,6 +78,7 @@ name your app 'agent' (not create an AI Agent), use the --name flag instead.`,
7778
cmd.Flags().StringVarP(&createTemplateURLFlag, "template", "t", "", "template URL for your app")
7879
cmd.Flags().StringVarP(&createGitBranchFlag, "branch", "b", "", "name of git branch to checkout")
7980
cmd.Flags().StringVarP(&createAppNameFlag, "name", "n", "", "name for your app (overrides the name argument)")
81+
cmd.Flags().BoolVar(&createListFlag, "list", false, "list available app templates")
8082

8183
return cmd
8284
}
@@ -121,6 +123,11 @@ func runCreateCommand(clients *shared.ClientFactory, cmd *cobra.Command, args []
121123
appNameArg = createAppNameFlag
122124
}
123125

126+
// List templates and exit early if the --list flag is set
127+
if createListFlag {
128+
return listTemplates(ctx, clients, categoryShortcut)
129+
}
130+
124131
// Collect the template URL or select a starting template
125132
template, err := promptTemplateSelection(cmd, clients, categoryShortcut)
126133
if err != nil {

cmd/project/create_template.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package project
1616

1717
import (
18+
"context"
1819
"fmt"
1920
"strings"
2021
"time"
@@ -240,6 +241,42 @@ func confirmExternalTemplateSelection(cmd *cobra.Command, clients *shared.Client
240241
return true, nil
241242
}
242243

244+
// listTemplates prints available templates for the create command
245+
func listTemplates(ctx context.Context, clients *shared.ClientFactory, categoryShortcut string) error {
246+
type categoryInfo struct {
247+
id string
248+
name string
249+
}
250+
251+
var categories []categoryInfo
252+
if categoryShortcut == "agent" {
253+
categories = []categoryInfo{
254+
{id: "slack-cli#ai-apps", name: "AI Agent apps"},
255+
}
256+
} else {
257+
categories = []categoryInfo{
258+
{id: "slack-cli#getting-started", name: "Getting started"},
259+
{id: "slack-cli#ai-apps", name: "AI Agent apps"},
260+
{id: "slack-cli#automation-apps", name: "Automation apps"},
261+
}
262+
}
263+
264+
for _, category := range categories {
265+
templates := getSelectionOptions(clients, category.id)
266+
secondary := make([]string, len(templates))
267+
for i, tmpl := range templates {
268+
secondary[i] = tmpl.Repository
269+
}
270+
clients.IO.PrintInfo(ctx, false, style.Sectionf(style.TextSection{
271+
Emoji: "house_buildings",
272+
Text: style.Bold(category.name),
273+
Secondary: secondary,
274+
}))
275+
}
276+
277+
return nil
278+
}
279+
243280
// getSelectionTemplate returns a custom formatted template used for selecting a
244281
// project template during creation
245282
func getSelectionTemplate(clients *shared.ClientFactory) string {

cmd/project/create_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,46 @@ func TestCreateCommand(t *testing.T) {
320320
cm.IO.AssertNotCalled(t, "SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything)
321321
},
322322
},
323+
"lists all templates with --list flag": {
324+
CmdArgs: []string{"--list"},
325+
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
326+
createClientMock = new(CreateClientMock)
327+
CreateFunc = createClientMock.Create
328+
},
329+
ExpectedOutputs: []string{
330+
"Getting started",
331+
"AI Agent apps",
332+
"Automation apps",
333+
"slack-samples/bolt-js-starter-template",
334+
"slack-samples/bolt-python-starter-template",
335+
"slack-samples/bolt-js-assistant-template",
336+
"slack-samples/bolt-python-assistant-template",
337+
"slack-samples/bolt-js-custom-function-template",
338+
"slack-samples/bolt-python-custom-function-template",
339+
"slack-samples/deno-starter-template",
340+
},
341+
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
342+
createClientMock.AssertNotCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
343+
},
344+
},
345+
"lists agent templates with agent --list flag": {
346+
CmdArgs: []string{"agent", "--list"},
347+
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
348+
createClientMock = new(CreateClientMock)
349+
CreateFunc = createClientMock.Create
350+
},
351+
ExpectedOutputs: []string{
352+
"AI Agent apps",
353+
"slack-samples/bolt-js-assistant-template",
354+
"slack-samples/bolt-python-assistant-template",
355+
},
356+
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
357+
createClientMock.AssertNotCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
358+
output := cm.GetCombinedOutput()
359+
assert.NotContains(t, output, "Getting started")
360+
assert.NotContains(t, output, "Automation apps")
361+
},
362+
},
323363
}, func(cf *shared.ClientFactory) *cobra.Command {
324364
return NewCreateCommand(cf)
325365
})

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ require (
2323
github.com/spf13/cobra v1.10.2
2424
github.com/stretchr/testify v1.11.1
2525
github.com/uber/jaeger-client-go v2.30.0+incompatible
26-
golang.org/x/mod v0.32.0
27-
golang.org/x/sys v0.40.0
28-
golang.org/x/text v0.33.0
26+
golang.org/x/mod v0.33.0
27+
golang.org/x/sys v0.41.0
28+
golang.org/x/text v0.34.0
2929
gopkg.in/yaml.v2 v2.4.0
3030
)
3131

@@ -59,7 +59,7 @@ require (
5959
require (
6060
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
6161
github.com/fatih/color v1.18.0 // indirect
62-
github.com/go-git/go-git/v5 v5.16.4
62+
github.com/go-git/go-git/v5 v5.16.5
6363
github.com/inconshreveable/mousetrap v1.1.0 // indirect
6464
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
6565
github.com/kubescape/go-git-url v0.0.31

go.sum

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN
5252
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
5353
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
5454
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
55-
github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y=
56-
github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
55+
github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s=
56+
github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M=
5757
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
5858
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
5959
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
@@ -181,8 +181,8 @@ golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+o
181181
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
182182
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
183183
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
184-
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
185-
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
184+
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
185+
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
186186
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
187187
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
188188
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@@ -206,8 +206,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
206206
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
207207
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
208208
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
209-
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
210-
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
209+
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
210+
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
211211
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
212212
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
213213
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
@@ -217,8 +217,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
217217
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
218218
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
219219
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
220-
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
221-
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
220+
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
221+
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
222222
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
223223
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
224224
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

internal/prompts/app_select.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,21 @@ func getAuths(ctx context.Context, clients *shared.ClientFactory) ([]types.Slack
245245
}
246246
}
247247
if len(allAuths) == 0 {
248-
auth := types.SlackAuth{}
249-
err := validateAuth(ctx, clients, &auth)
248+
// No workspaces connected - prompt user to login if interactive
249+
if !clients.IO.IsTTY() {
250+
return nil, slackerror.New(slackerror.ErrNotAuthed).
251+
WithMessage("No workspaces connected").
252+
WithRemediation("Run %s to sign in to a workspace", style.Commandf("login", false))
253+
}
254+
clients.IO.PrintInfo(ctx, false, "\n%s", style.Sectionf(style.TextSection{
255+
Emoji: "wave",
256+
Text: "No workspaces connected. Sign in to get started.",
257+
}))
258+
newAuth, _, err := authpkg.LoginWithClients(ctx, clients, "", false)
250259
if err != nil {
251-
return nil, slackerror.New(slackerror.ErrNotAuthed)
260+
return nil, slackerror.New(slackerror.ErrNotAuthed).WithRootCause(err)
252261
}
253-
allAuths = append(allAuths, auth)
262+
allAuths = append(allAuths, newAuth)
254263
}
255264
return allAuths, nil
256265
}

0 commit comments

Comments
 (0)