-
Notifications
You must be signed in to change notification settings - Fork 42
feat: add --app flag support to slack create for linking existing apps #565
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
0d8bfd3
da2eb75
e57f7d5
aceb7a4
906346a
8128d5e
315c9bb
dc7d9f7
7386119
818c87a
60d222a
8ef2b29
c9f5bbf
7564232
6533815
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,29 +18,34 @@ import ( | |
| "context" | ||
| "fmt" | ||
| "math/rand" | ||
| "os" | ||
| "path/filepath" | ||
| "strings" | ||
| "time" | ||
|
|
||
| "github.com/slackapi/slack-cli/cmd/app" | ||
| "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/shared/types" | ||
| "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 createEnvironmentFlag string | ||
| var createGitBranchFlag string | ||
| var createListFlag bool | ||
| var createSubdirFlag string | ||
| var createTemplateURLFlag 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 LinkFunc = app.LinkExistingApp | ||
|
|
||
| // promptObject describes the Github app template | ||
| type promptObject struct { | ||
|
|
@@ -67,6 +72,7 @@ name your app 'agent' (not create an AI Agent), use the --name flag instead.`, | |
| {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"}, | ||
| {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"}, | ||
| }), | ||
| Args: cobra.MaximumNArgs(2), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
|
|
@@ -81,6 +87,7 @@ name your app 'agent' (not create an AI Agent), use the --name flag instead.`, | |
| 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") | ||
| cmd.Flags().StringVarP(&createEnvironmentFlag, "environment", "E", "", "environment to save existing app (local, deployed)") | ||
|
|
||
| return cmd | ||
| } | ||
|
|
@@ -127,6 +134,25 @@ func runCreateCommand(clients *shared.ClientFactory, cmd *cobra.Command, args [] | |
| WithMessage("The --subdir flag requires the --template flag") | ||
| } | ||
|
|
||
| // --app requires --template | ||
| appFlagProvided := clients.Config.AppFlag != "" && types.IsAppID(clients.Config.AppFlag) | ||
| if appFlagProvided && !templateFlagProvided { | ||
| return slackerror.New(slackerror.ErrMismatchedFlags). | ||
| WithMessage("The --app flag requires the --template flag when used with create") | ||
| } | ||
|
|
||
| // --environment requires --app and must be "local" or "deployed" | ||
| if cmd.Flags().Changed("environment") { | ||
| if !appFlagProvided { | ||
| return slackerror.New(slackerror.ErrMismatchedFlags). | ||
| WithMessage("The --environment flag requires the --app flag when used with create") | ||
| } | ||
| if !types.IsAppFlagEnvironment(createEnvironmentFlag) { | ||
| return slackerror.New(slackerror.ErrMismatchedFlags). | ||
| WithMessage("The --environment flag must be either 'local' or 'deployed'") | ||
| } | ||
| } | ||
|
|
||
| // Collect the template URL or select a starting template | ||
| template, err := promptTemplateSelection(cmd, clients, categoryShortcut) | ||
| if err != nil { | ||
|
|
@@ -183,6 +209,22 @@ func runCreateCommand(clients *shared.ClientFactory, cmd *cobra.Command, args [] | |
| return err | ||
| } | ||
|
|
||
| if appFlagProvided { | ||
| absProjectPath, err := filepath.Abs(appDirPath) | ||
| if err != nil { | ||
| return slackerror.Wrap(err, slackerror.ErrAppDirectoryAccess) | ||
| } | ||
| originalDir, _ := clients.Os.Getwd() | ||
| if err := os.Chdir(absProjectPath); err != nil { | ||
| return slackerror.Wrap(err, slackerror.ErrAppDirectoryAccess) | ||
| } | ||
| linkErr := LinkFunc(ctx, clients, &types.App{}, false) | ||
| _ = os.Chdir(originalDir) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minor: We should handle the error returned by
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Commit 7564232 now handles errors when getting or changing the working directory. It also defers restoring the original working directory, so that it's restored safely during errors. |
||
| if linkErr != nil { | ||
| return linkErr | ||
| } | ||
| } | ||
|
zimeg marked this conversation as resolved.
|
||
|
|
||
| printCreateSuccess(ctx, clients, appDirPath) | ||
| return nil | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ import ( | |
| "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/shared/types" | ||
| "github.com/slackapi/slack-cli/internal/slackerror" | ||
| "github.com/slackapi/slack-cli/test/testutil" | ||
| "github.com/spf13/cobra" | ||
|
|
@@ -853,3 +854,92 @@ func TestCreateCommand_confirmExternalTemplateSelection(t *testing.T) { | |
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestCreateCommand_AppFlag(t *testing.T) { | ||
| var createClientMock *CreateClientMock | ||
|
|
||
| testutil.TableTestCommand(t, testutil.CommandTests{ | ||
| "app flag without template flag returns error": { | ||
| CmdArgs: []string{"my-app", "--app", "A0123456789"}, | ||
| Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { | ||
| createClientMock = new(CreateClientMock) | ||
| CreateFunc = createClientMock.Create | ||
| }, | ||
| ExpectedErrorStrings: []string{"The --app flag requires the --template flag when used with create"}, | ||
| ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) { | ||
| createClientMock.AssertNotCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything) | ||
| }, | ||
| }, | ||
| "environment flag without app flag returns error": { | ||
| CmdArgs: []string{"my-app", "--template", "slack-samples/bolt-js-starter-template", "--environment", "deployed"}, | ||
| Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { | ||
| createClientMock = new(CreateClientMock) | ||
| CreateFunc = createClientMock.Create | ||
| }, | ||
| ExpectedErrorStrings: []string{"The --environment flag requires the --app flag when used with create"}, | ||
| ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) { | ||
| createClientMock.AssertNotCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything) | ||
| }, | ||
| }, | ||
| "invalid environment flag returns error": { | ||
| CmdArgs: []string{"my-app", "--template", "slack-samples/bolt-js-starter-template", "--app", "A0123456789", "--environment", "invalid"}, | ||
| Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { | ||
| createClientMock = new(CreateClientMock) | ||
| CreateFunc = createClientMock.Create | ||
| }, | ||
| ExpectedErrorStrings: []string{"The --environment flag must be either 'local' or 'deployed'"}, | ||
| ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) { | ||
| createClientMock.AssertNotCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything) | ||
| }, | ||
| }, | ||
| "app flag with template creates project then calls link": { | ||
| CmdArgs: []string{"my-app", "--template", "slack-samples/bolt-js-starter-template", "--app", "A0123456789", "--environment", "local"}, | ||
| Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { | ||
| cm.IO.On("SelectPrompt", mock.Anything, mock.Anything, mock.Anything, mock.Anything). | ||
| Return(iostreams.SelectPromptResponse{Flag: true, Option: "slack-samples/bolt-js-starter-template"}, nil).Maybe() | ||
|
|
||
| createClientMock = new(CreateClientMock) | ||
| createClientMock.On("Create", mock.Anything, mock.Anything, mock.Anything).Return(t.TempDir(), nil) | ||
| CreateFunc = createClientMock.Create | ||
|
|
||
| linkCalled := false | ||
| LinkFunc = func(ctx context.Context, clients *shared.ClientFactory, a *types.App, shouldConfirm bool) error { | ||
| linkCalled = true | ||
| assert.False(t, shouldConfirm) | ||
| return nil | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note: We should be backing up the original
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Commit 6533815 removes this code and now uses the real link function. |
||
| t.Cleanup(func() { | ||
| assert.True(t, linkCalled, "LinkFunc should have been called") | ||
| }) | ||
| }, | ||
| ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) { | ||
| createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything) | ||
| }, | ||
| }, | ||
| "app flag without environment still calls link": { | ||
| CmdArgs: []string{"my-app", "--template", "slack-samples/bolt-js-starter-template", "--app", "A0123456789"}, | ||
| Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { | ||
| cm.IO.On("SelectPrompt", mock.Anything, mock.Anything, mock.Anything, mock.Anything). | ||
| Return(iostreams.SelectPromptResponse{Flag: true, Option: "slack-samples/bolt-js-starter-template"}, nil).Maybe() | ||
|
|
||
| createClientMock = new(CreateClientMock) | ||
| createClientMock.On("Create", mock.Anything, mock.Anything, mock.Anything).Return(t.TempDir(), nil) | ||
| CreateFunc = createClientMock.Create | ||
|
|
||
| linkCalled := false | ||
| LinkFunc = func(ctx context.Context, clients *shared.ClientFactory, a *types.App, shouldConfirm bool) error { | ||
| linkCalled = true | ||
| return nil | ||
| } | ||
| t.Cleanup(func() { | ||
| assert.True(t, linkCalled, "LinkFunc should have been called") | ||
| }) | ||
| }, | ||
| ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) { | ||
| createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.Anything) | ||
| }, | ||
| }, | ||
| }, func(cf *shared.ClientFactory) *cobra.Command { | ||
| return NewCreateCommand(cf) | ||
| }) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧪 suggestion: Adding cases to cover the expected happy paths might be meaningful to guarantee the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I understand correctly, Eden would prefer that we don't mock the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Commit 6533815 removes the |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor:
--app localwill silently skip linking because it's not an App ID. While it's correct that we don't want to support--app localand--app deployedwhen creating an app, we should display an error immediately when it's not an App ID.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commit c9f5bbf now displays an error during the creation if a non-App-ID is provided. For example,
--app localor--app deployed.