Skip to content

Commit 3cc50cb

Browse files
authored
Merge branch 'main' into fix/rc-no-changes-commit
2 parents ad3d90b + 8373513 commit 3cc50cb

24 files changed

Lines changed: 642 additions & 412 deletions

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ jobs:
109109
default: "dev-build"
110110
docker: # run the steps with Docker
111111
# CircleCI Go images available at: https://hub.docker.com/r/circleci/golang/
112-
- image: cimg/go:1.26.3
112+
- image: cimg/go:1.26.4
113113
steps: # steps that comprise the `build` job
114114
- checkout # check out source code to working directory
115115
- restore_cache: # restores saved cache if no changes are detected since last run

.github/workflows/release.yml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,19 @@ jobs:
117117
PR_BODY=$(cat <<EOF
118118
### Summary
119119
120-
Release v${RELEASE_VERSION}. After merging, create a [GitHub Release](https://github.com/${{ github.repository }}/releases/new?tag=v${RELEASE_VERSION}&title=v${RELEASE_VERSION}) to tag and trigger the release pipeline.
120+
Release v${RELEASE_VERSION}.
121121
122122
### Testing
123123
124-
Install the [dev-build](https://github.com/slackapi/slack-cli/releases/tag/dev-build) and verify changes.
124+
- [ ] Confirm unit tests and E2E tests are passing on the [`main`](https://github.com/slackapi/slack-cli/commits/main) branch.
125+
- [ ] Confirm unit tests and E2E tests are passing on this PR.
126+
- [ ] Confirm the [dev-build](https://github.com/slackapi/slack-cli/releases/tag/dev-build) includes all commits since last release.
127+
- [ ] Review open issues or PRs on the "[Next Release](https://github.com/slackapi/slack-cli/milestones)" milestone.
128+
- [ ] Confirm the new version matches the expected next version.
129+
130+
### Reviewers
131+
132+
After merging, create a [GitHub Release](https://github.com/${{ github.repository }}/releases/new?tag=v${RELEASE_VERSION}&title=v${RELEASE_VERSION}) to tag the new version.
125133
126134
### Requirements
127135
@@ -138,6 +146,6 @@ jobs:
138146
gh pr edit "$EXISTING_PR" --title "$PR_TITLE" --body "$PR_BODY" --milestone "Next Release" --add-label "release" --add-label "semver:${SEMVER}"
139147
echo "Updated PR #$EXISTING_PR"
140148
else
141-
gh pr create --draft --head rc --base main --title "$PR_TITLE" --body "$PR_BODY" --milestone "Next Release" --add-label "release" --add-label "semver:${SEMVER}"
149+
gh pr create --draft --head rc --base main --title "$PR_TITLE" --body "$PR_BODY" --milestone "Next Release" --label "release" --label "semver:${SEMVER}"
142150
echo "Created new release PR"
143151
fi

.github/workflows/tests.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ jobs:
2020
- name: Set up Go
2121
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
2222
with:
23-
go-version: "1.26.3"
23+
go-version: "1.26.4"
2424
- name: Lint
2525
run: go tool golangci-lint run --timeout=5m
2626
- name: Unit Tests
2727
run: make test
2828
- name: Install Tests
2929
run: make test-install
3030
- name: Upload coverage to Codecov
31-
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
31+
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
3232
with:
3333
token: ${{ secrets.CODECOV_TOKEN }}
3434
files: ./coverage.out
@@ -49,7 +49,7 @@ jobs:
4949
- name: Set up Go
5050
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
5151
with:
52-
go-version: "1.26.3"
52+
go-version: "1.26.4"
5353
- name: Report health score
5454
uses: slackapi/slack-health-score@d58a419f15cdaff97e9aa7f09f95772830ab66f7 # v0.1.1
5555
with:

cmd/docgen/docgen.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ func runDocGenCommandFunc(clients *shared.ClientFactory, cmd *cobra.Command, arg
108108
return slackerror.New(slackerror.ErrDocumentationGenerationFailed).WithRootCause(err)
109109
}
110110

111-
// Generate errors reference
111+
// Generate errors reference sorted alphabetically by error code
112112
file, err := clients.Fs.Create(filepath.Join(docsDirPath, "errors.md"))
113113
if err != nil {
114114
return err
@@ -118,7 +118,14 @@ func runDocGenCommandFunc(clients *shared.ClientFactory, cmd *cobra.Command, arg
118118
if err != nil {
119119
return err
120120
}
121-
err = tmpl.Execute(file, slackerror.ErrorCodeMap)
121+
errors := make([]slackerror.Error, 0, len(slackerror.ErrorCodeMap))
122+
for _, e := range slackerror.ErrorCodeMap {
123+
errors = append(errors, e)
124+
}
125+
slices.SortFunc(errors, func(a, b slackerror.Error) int {
126+
return strings.Compare(a.Code, b.Code)
127+
})
128+
err = tmpl.Execute(file, errors)
122129
if err != nil {
123130
return err
124131
}

cmd/docgen/errors.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ ones, as well as a list of the errors the Slack CLI may raise, what they mean,
66
and some ways to remediate them.
77

88
## Slack CLI errors list
9-
{{range $key, $err := . }}
9+
{{range $err := . }}
1010
### {{ $err.Code }} {#{{ $err.Code }}}
1111
{{ if $err.Message }}
1212
**Message**: {{ $err.Message }}

cmd/externalauth/add_secret.go

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,6 @@ func NewAddClientSecretCommand(clients *shared.ClientFactory) *cobra.Command {
3838
"Add the client secret for an external provider of a workflow app.",
3939
"",
4040
"This secret will be used when initiating the OAuth2 flow.",
41-
"",
42-
"This command is supported for apps deployed to Slack managed infrastructure but",
43-
"other apps can attempt to run the command with the --force flag.",
4441
}, "\n"),
4542
Example: style.ExampleCommandsf([]style.ExampleCommand{
4643
{
@@ -67,18 +64,10 @@ func NewAddClientSecretCommand(clients *shared.ClientFactory) *cobra.Command {
6764
return cmd
6865
}
6966

70-
// preRunAddClientSecretCommand determines if the command is supported for a
71-
// project and configures flags
72-
func preRunAddClientSecretCommand(ctx context.Context, clients *shared.ClientFactory, cmd *cobra.Command) error {
67+
// preRunAddClientSecretCommand configures flags and validates the project directory
68+
func preRunAddClientSecretCommand(_ context.Context, clients *shared.ClientFactory, cmd *cobra.Command) error {
7369
clients.Config.SetFlags(cmd)
74-
err := cmdutil.IsValidProjectDirectory(clients)
75-
if err != nil {
76-
return err
77-
}
78-
if clients.Config.ForceFlag {
79-
return nil
80-
}
81-
return cmdutil.IsSlackHostedProject(ctx, clients)
70+
return cmdutil.IsValidProjectDirectory(clients)
8271
}
8372

8473
// runAddClientSecretCommand adds a client secret to an authentication provider

cmd/externalauth/add_secret_test.go

Lines changed: 1 addition & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ import (
1919
"errors"
2020
"testing"
2121

22-
"github.com/slackapi/slack-cli/internal/app"
23-
"github.com/slackapi/slack-cli/internal/config"
2422
"github.com/slackapi/slack-cli/internal/iostreams"
2523
"github.com/slackapi/slack-cli/internal/shared"
2624
"github.com/slackapi/slack-cli/internal/shared/types"
@@ -34,88 +32,22 @@ import (
3432

3533
func TestExternalAuthAddClientSecretCommandPreRun(t *testing.T) {
3634
tests := map[string]struct {
37-
mockFlagForce bool
38-
mockManifestResponse types.SlackYaml
39-
mockManifestError error
40-
mockManifestSource config.ManifestSource
4135
mockWorkingDirectory string
4236
expectedError error
4337
}{
44-
"continues if the application is hosted on slack": {
45-
mockManifestResponse: types.SlackYaml{
46-
AppManifest: types.AppManifest{
47-
Settings: &types.AppSettings{
48-
FunctionRuntime: types.SlackHosted,
49-
},
50-
},
51-
},
52-
mockManifestError: nil,
38+
"continues if the command is run in a valid project directory": {
5339
mockWorkingDirectory: "/slack/path/to/project",
5440
expectedError: nil,
5541
},
56-
"errors if the application is not hosted on slack": {
57-
mockManifestResponse: types.SlackYaml{
58-
AppManifest: types.AppManifest{
59-
Settings: &types.AppSettings{
60-
FunctionRuntime: types.Remote,
61-
},
62-
},
63-
},
64-
mockManifestError: nil,
65-
mockManifestSource: config.ManifestSourceLocal,
66-
mockWorkingDirectory: "/slack/path/to/project",
67-
expectedError: slackerror.New(slackerror.ErrAppNotHosted),
68-
},
69-
"continues if the force flag is used in a project": {
70-
mockFlagForce: true,
71-
mockWorkingDirectory: "/slack/path/to/project",
72-
expectedError: nil,
73-
},
74-
"errors if the project manifest cannot be retrieved": {
75-
mockManifestResponse: types.SlackYaml{},
76-
mockManifestError: slackerror.New(slackerror.ErrSDKHookInvocationFailed),
77-
mockManifestSource: config.ManifestSourceLocal,
78-
mockWorkingDirectory: "/slack/path/to/project",
79-
expectedError: slackerror.New(slackerror.ErrSDKHookInvocationFailed),
80-
},
8142
"errors if the command is not run in a project": {
82-
mockManifestResponse: types.SlackYaml{},
83-
mockManifestError: slackerror.New(slackerror.ErrSDKHookNotFound),
84-
mockManifestSource: config.ManifestSourceLocal,
8543
mockWorkingDirectory: "",
8644
expectedError: slackerror.New(slackerror.ErrInvalidAppDirectory),
8745
},
88-
"errors if the manifest source is set to remote": {
89-
mockManifestSource: config.ManifestSourceRemote,
90-
mockWorkingDirectory: "/slack/path/to/project",
91-
expectedError: slackerror.New(slackerror.ErrAppNotHosted),
92-
},
9346
}
9447
for name, tc := range tests {
9548
t.Run(name, func(t *testing.T) {
9649
clientsMock := shared.NewClientsMock()
97-
manifestMock := &app.ManifestMockObject{}
98-
manifestMock.On(
99-
"GetManifestLocal",
100-
mock.Anything,
101-
mock.Anything,
102-
mock.Anything,
103-
).Return(
104-
tc.mockManifestResponse,
105-
tc.mockManifestError,
106-
)
107-
clientsMock.AppClient.Manifest = manifestMock
108-
projectConfigMock := config.NewProjectConfigMock()
109-
projectConfigMock.On(
110-
"GetManifestSource",
111-
mock.Anything,
112-
).Return(
113-
tc.mockManifestSource,
114-
nil,
115-
)
116-
clientsMock.Config.ProjectConfig = projectConfigMock
11750
clients := shared.NewClientFactory(clientsMock.MockClientFactory(), func(cf *shared.ClientFactory) {
118-
cf.Config.ForceFlag = tc.mockFlagForce
11951
cf.SDKConfig.WorkingDirectory = tc.mockWorkingDirectory
12052
})
12153
cmd := NewAddClientSecretCommand(clients)

cmd/manifest/validate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
"github.com/slackapi/slack-cli/internal/app"
2424
"github.com/slackapi/slack-cli/internal/cmdutil"
2525
"github.com/slackapi/slack-cli/internal/iostreams"
26-
"github.com/slackapi/slack-cli/internal/pkg/manifest"
26+
"github.com/slackapi/slack-cli/internal/manifest"
2727
"github.com/slackapi/slack-cli/internal/prompts"
2828
"github.com/slackapi/slack-cli/internal/shared"
2929
"github.com/slackapi/slack-cli/internal/shared/types"

cmd/project/create.go

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,29 @@ import (
1818
"context"
1919
"fmt"
2020
"math/rand"
21+
"os"
2122
"path/filepath"
2223
"strings"
2324
"time"
2425

26+
"github.com/slackapi/slack-cli/cmd/app"
2527
"github.com/slackapi/slack-cli/internal/iostreams"
2628
"github.com/slackapi/slack-cli/internal/pkg/create"
2729
"github.com/slackapi/slack-cli/internal/shared"
30+
"github.com/slackapi/slack-cli/internal/shared/types"
2831
"github.com/slackapi/slack-cli/internal/slackerror"
2932
"github.com/slackapi/slack-cli/internal/slacktrace"
3033
"github.com/slackapi/slack-cli/internal/style"
3134
"github.com/spf13/cobra"
3235
)
3336

3437
// Flags
35-
var createTemplateURLFlag string
36-
var createGitBranchFlag string
3738
var createAppNameFlag string
39+
var createEnvironmentFlag string
40+
var createGitBranchFlag string
3841
var createListFlag bool
3942
var createSubdirFlag string
43+
var createTemplateURLFlag string
4044

4145
// Handle to client's create function used for testing
4246
// TODO - Find best practice, such as using an Interface and Struct to create a client
@@ -67,6 +71,7 @@ name your app 'agent' (not create an AI Agent), use the --name flag instead.`,
6771
{Command: "create my-project -t slack-samples/deno-hello-world", Meaning: "Start a new project from a specific template"},
6872
{Command: "create --name my-project", Meaning: "Create a project named 'my-project'"},
6973
{Command: "create my-project -t org/monorepo --subdir apps/my-app", Meaning: "Create from a subdirectory of a template"},
74+
{Command: "create my-project -t slack-samples/bolt-js-starter-template --app A0123456789 --environment local", Meaning: "Create from template and link to an existing app"},
7075
}),
7176
Args: cobra.MaximumNArgs(2),
7277
RunE: func(cmd *cobra.Command, args []string) error {
@@ -81,6 +86,7 @@ name your app 'agent' (not create an AI Agent), use the --name flag instead.`,
8186
cmd.Flags().StringVarP(&createAppNameFlag, "name", "n", "", "name for your app (overrides the name argument)")
8287
cmd.Flags().BoolVar(&createListFlag, "list", false, "list available app templates")
8388
cmd.Flags().StringVar(&createSubdirFlag, "subdir", "", "subdirectory in the template to use as project")
89+
cmd.Flags().StringVarP(&createEnvironmentFlag, "environment", "E", "", "environment to save existing app (local, deployed)")
8490

8591
return cmd
8692
}
@@ -127,6 +133,31 @@ func runCreateCommand(clients *shared.ClientFactory, cmd *cobra.Command, args []
127133
WithMessage("The --subdir flag requires the --template flag")
128134
}
129135

136+
// --app must be an app ID when used with create
137+
appFlagProvided := clients.Config.AppFlag != ""
138+
if appFlagProvided && !types.IsAppID(clients.Config.AppFlag) {
139+
return slackerror.New(slackerror.ErrInvalidAppFlag).
140+
WithMessage("The --app flag requires an app ID when used with create")
141+
}
142+
143+
// --app requires --template
144+
if appFlagProvided && !templateFlagProvided {
145+
return slackerror.New(slackerror.ErrMismatchedFlags).
146+
WithMessage("The --app flag requires the --template flag when used with create")
147+
}
148+
149+
// --environment requires --app and must be "local" or "deployed"
150+
if cmd.Flags().Changed("environment") {
151+
if !appFlagProvided {
152+
return slackerror.New(slackerror.ErrMismatchedFlags).
153+
WithMessage("The --environment flag requires the --app flag when used with create")
154+
}
155+
if !types.IsAppFlagEnvironment(createEnvironmentFlag) {
156+
return slackerror.New(slackerror.ErrMismatchedFlags).
157+
WithMessage("The --environment flag must be either 'local' or 'deployed'")
158+
}
159+
}
160+
130161
// Collect the template URL or select a starting template
131162
template, err := promptTemplateSelection(cmd, clients, categoryShortcut)
132163
if err != nil {
@@ -183,6 +214,26 @@ func runCreateCommand(clients *shared.ClientFactory, cmd *cobra.Command, args []
183214
return err
184215
}
185216

217+
if appFlagProvided {
218+
absProjectPath, err := filepath.Abs(appDirPath)
219+
if err != nil {
220+
return slackerror.Wrap(err, slackerror.ErrAppDirectoryAccess)
221+
}
222+
originalDir, err := clients.Os.Getwd()
223+
if err != nil {
224+
return slackerror.Wrap(err, slackerror.ErrAppDirectoryAccess)
225+
}
226+
if err := os.Chdir(absProjectPath); err != nil {
227+
return slackerror.Wrap(err, slackerror.ErrAppDirectoryAccess)
228+
}
229+
defer func() {
230+
_ = os.Chdir(originalDir)
231+
}()
232+
if err := app.LinkExistingApp(ctx, clients, &types.App{}, false); err != nil {
233+
return err
234+
}
235+
}
236+
186237
printCreateSuccess(ctx, clients, appDirPath)
187238
return nil
188239
}

0 commit comments

Comments
 (0)