diff --git a/cmd/app/link.go b/cmd/app/link.go index 044f56c3..c8aa45d2 100644 --- a/cmd/app/link.go +++ b/cmd/app/link.go @@ -97,58 +97,8 @@ func LinkCommandRunE(ctx context.Context, clients *shared.ClientFactory, app *ty // Add empty line between executed command and first output clients.IO.PrintInfo(ctx, false, "") - err = LinkExistingApp(ctx, clients, app, false) - if err != nil { - return err - } - - return nil -} - -// LinkAppHeaderSection displays a section explaining how to find existing apps. -// External callers can use extraSecondaryText to show additional information. -// When shouldConfirm is true, additional information is included in the header -// explaining how to link apps, in case the user declines. -func LinkAppHeaderSection(ctx context.Context, clients *shared.ClientFactory, shouldConfirm bool) { - var secondaryText = []string{ - "Add an existing app from app settings", - "Find your existing apps at: " + style.Underline("https://api.slack.com/apps"), - } - - if shouldConfirm { - secondaryText = append(secondaryText, "Manually add apps later with "+style.Commandf("app link", true)) - } - - clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{ - Emoji: "house", - Text: "App Link", - Secondary: secondaryText, - })) -} - -// LinkExistingApp prompts for an existing App ID and saves the details to the project. -// When shouldConfirm is true, a confirmation prompt will ask the user if they want to -// link an existing app and additional information is included in the header. -// The shouldConfirm option is encouraged for third-party callers. -func LinkExistingApp(ctx context.Context, clients *shared.ClientFactory, app *types.App, shouldConfirm bool) (err error) { // Header section - LinkAppHeaderSection(ctx, clients, shouldConfirm) - - // Confirm to add an existing app; useful for third-party callers - if shouldConfirm { - proceed, err := clients.IO.ConfirmPrompt(ctx, LinkAppConfirmPromptText, true) - if err != nil { - clients.IO.PrintDebug(ctx, "Error prompting to add an existing app: %s", err) - return err - } - - // Add newline to match the trailing newline inserted from the footer section - clients.IO.PrintInfo(ctx, false, "") - - if !proceed { - return nil - } - } + LinkAppHeaderSection(ctx, clients) // App Manifest section manifestSource, err := clients.Config.ProjectConfig.GetManifestSource(ctx) @@ -166,6 +116,45 @@ func LinkExistingApp(ctx context.Context, clients *shared.ClientFactory, app *ty }, })) + err = LinkExistingApp(ctx, clients, app) + if err != nil { + return err + } + + // App summary section + clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{ + Emoji: "house", + Text: "App", + Secondary: FormatListSuccess([]types.App{*app}), + })) + + // Footer section + clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{ + Emoji: "house_with_garden", + Text: "App Link", + Secondary: []string{ + "Added existing app to project", + }, + })) + + return nil +} + +// LinkAppHeaderSection displays a section explaining how to find existing apps. +func LinkAppHeaderSection(ctx context.Context, clients *shared.ClientFactory) { + clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{ + Emoji: "house", + Text: "App Link", + Secondary: []string{ + "Add an existing app from app settings", + "Find your existing apps at: " + style.Underline("https://api.slack.com/apps"), + }, + })) +} + +// LinkExistingApp resolves app details, validates the app, and saves it to the +// project. It produces no output — callers handle their own display. +func LinkExistingApp(ctx context.Context, clients *shared.ClientFactory, app *types.App) (err error) { // Prompt to get app details var auth *types.SlackAuth *app, auth, err = promptExistingApp(ctx, clients) @@ -186,28 +175,9 @@ func LinkExistingApp(ctx context.Context, clients *shared.ClientFactory, app *ty return err } - // Footer section - LinkAppFooterSection(ctx, clients, app) - return nil } -// LinkAppFooterSection displays the details of app that was added to the project. -func LinkAppFooterSection(ctx context.Context, clients *shared.ClientFactory, app *types.App) { - clients.IO.PrintInfo(ctx, false, "\n%s", style.Sectionf(style.TextSection{ - Emoji: "house", - Text: "App", - Secondary: formatListSuccess([]types.App{*app}), - })) - clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{ - Emoji: "house_with_garden", - Text: "App Link", - Secondary: []string{ - "Added existing app to project", - }, - })) -} - // promptExistingApp gathers details to represent app information func promptExistingApp(ctx context.Context, clients *shared.ClientFactory) (types.App, *types.SlackAuth, error) { slackAuth, err := prompts.PromptTeamSlackAuth(ctx, clients, "Select the existing app team", nil) diff --git a/cmd/app/link_test.go b/cmd/app/link_test.go index 598037ac..915d21ef 100644 --- a/cmd/app/link_test.go +++ b/cmd/app/link_test.go @@ -572,53 +572,16 @@ func Test_Apps_Link(t *testing.T) { } func Test_Apps_LinkAppHeaderSection(t *testing.T) { - tests := map[string]struct { - shouldConfirm bool - expectedOutputs []string - unexpectedOutputs []string - }{ - "When shouldConfirm is false": { - shouldConfirm: false, - expectedOutputs: []string{ - "Add an existing app from app settings", - "Find your existing apps at: https://api.slack.com/apps", - }, - unexpectedOutputs: []string{ - "Manually add apps later with", - }, - }, - "When shouldConfirm is true": { - shouldConfirm: true, - expectedOutputs: []string{ - "Add an existing app from app settings", - "Find your existing apps at: https://api.slack.com/apps", - "Manually add apps later with", - }, - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // Create mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.AddDefaultMocks() + ctx := slackcontext.MockContext(t.Context()) + clientsMock := shared.NewClientsMock() + clientsMock.AddDefaultMocks() + clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - // Create clients that is mocked for testing - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) + LinkAppHeaderSection(ctx, clients) - // Run the test - LinkAppHeaderSection(ctx, clients, tc.shouldConfirm) - - // Assertions - output := clientsMock.GetCombinedOutput() - for _, expectedOutput := range tc.expectedOutputs { - require.Contains(t, output, expectedOutput) - } - for _, unexpectedOutput := range tc.unexpectedOutputs { - require.NotContains(t, output, unexpectedOutput) - } - }) - } + output := clientsMock.GetCombinedOutput() + require.Contains(t, output, "Add an existing app from app settings") + require.Contains(t, output, "Find your existing apps at: https://api.slack.com/apps") } func setupAppLinkCommandMocks(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { diff --git a/cmd/app/list.go b/cmd/app/list.go index 290eeb6e..216988c7 100644 --- a/cmd/app/list.go +++ b/cmd/app/list.go @@ -73,13 +73,13 @@ func runListCommand(cmd *cobra.Command, clients *shared.ClientFactory) error { clients.IO.PrintInfo(ctx, false, "\n%s", style.Sectionf(style.TextSection{ Emoji: "house_buildings", Text: "Apps", - Secondary: formatListSuccess(envs), + Secondary: FormatListSuccess(envs), })) return nil } -// formatListSuccess formats details about the list of project apps -func formatListSuccess(apps []types.App) (secondaryText []string) { +// FormatListSuccess formats details about the list of project apps +func FormatListSuccess(apps []types.App) (secondaryText []string) { for _, app := range apps { if app.AppID == "" { continue diff --git a/cmd/app/list_test.go b/cmd/app/list_test.go index caffcf6d..04d7546b 100644 --- a/cmd/app/list_test.go +++ b/cmd/app/list_test.go @@ -218,7 +218,7 @@ func TestAppsListFormat(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { listFlags = tc.Flags - formattedList := formatListSuccess(tc.Apps) + formattedList := FormatListSuccess(tc.Apps) for ii, value := range formattedList { formattedList[ii] = strings.TrimRight(value, ":") } diff --git a/cmd/project/create.go b/cmd/project/create.go index 1adff451..18f66d1d 100644 --- a/cmd/project/create.go +++ b/cmd/project/create.go @@ -229,9 +229,15 @@ func runCreateCommand(clients *shared.ClientFactory, cmd *cobra.Command, args [] defer func() { _ = os.Chdir(originalDir) }() - if err := app.LinkExistingApp(ctx, clients, &types.App{}, false); err != nil { + linkedApp := &types.App{} + if err := app.LinkExistingApp(ctx, clients, linkedApp); err != nil { return err } + clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{ + Emoji: "house", + Text: "App", + Secondary: app.FormatListSuccess([]types.App{*linkedApp}), + })) } printCreateSuccess(ctx, clients, appDirPath) diff --git a/cmd/project/init.go b/cmd/project/init.go index 9f9d5f90..c23eccc6 100644 --- a/cmd/project/init.go +++ b/cmd/project/init.go @@ -109,11 +109,26 @@ func projectInitCommandRunE(clients *shared.ClientFactory, cmd *cobra.Command, a // Existing projects initialized always default to config.ManifestSourceLocal. _ = create.InstallProjectDependencies(ctx, clients, projectDirPath) - // Add an existing app to the project - err = app.LinkExistingApp(ctx, clients, &types.App{}, true) + // Prompt to add an existing app to the project + app.LinkAppHeaderSection(ctx, clients) + clients.IO.PrintInfo(ctx, false, " %s\n", "Manually add apps later with "+style.Commandf("app link", true)) + proceed, err := clients.IO.ConfirmPrompt(ctx, app.LinkAppConfirmPromptText, true) if err != nil { - // Display the error but continue to init - clients.IO.PrintError(ctx, "%s", err.Error()) + clients.IO.PrintDebug(ctx, "Error prompting to add an existing app: %s", err) + } + if proceed { + linkedApp := &types.App{} + err = app.LinkExistingApp(ctx, clients, linkedApp) + if err != nil { + // Display the error but continue to init + clients.IO.PrintError(ctx, "%s", err.Error()) + } else { + clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{ + Emoji: "house", + Text: "App", + Secondary: app.FormatListSuccess([]types.App{*linkedApp}), + })) + } } printNextStepSection(ctx, clients, projectDirPath)