From a87561b40c3980636a92af590f07630e3c2b0cc0 Mon Sep 17 00:00:00 2001 From: Michael Brooks Date: Thu, 12 Mar 2026 15:25:34 -0700 Subject: [PATCH 1/6] feat: default manifest source to local for non-Deno apps Change the default manifest source for non-Deno (Bolt) projects from ManifestSourceRemote to ManifestSourceLocal, aligning their behavior with Deno projects. --- cmd/project/init.go | 1 - internal/pkg/create/create.go | 4 ++-- internal/pkg/create/create_test.go | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cmd/project/init.go b/cmd/project/init.go index 946663d0..cd062abf 100644 --- a/cmd/project/init.go +++ b/cmd/project/init.go @@ -108,7 +108,6 @@ func projectInitCommandRunE(clients *shared.ClientFactory, cmd *cobra.Command, a // Install the project dependencies, such as .slack/ and runtime packages // Existing projects initialized always default to config.ManifestSourceLocal. - // The link command will switch it to config.ManifestSourceRemote _ = create.InstallProjectDependencies(ctx, clients, projectDirPath, config.ManifestSourceLocal) // Add an existing app to the project diff --git a/internal/pkg/create/create.go b/internal/pkg/create/create.go index 09a85b7e..1ec3e11c 100644 --- a/internal/pkg/create/create.go +++ b/internal/pkg/create/create.go @@ -521,12 +521,12 @@ func InstallProjectDependencies( manifestSource = config.ManifestSourceLocal } - // Set non-Deno (non-ROSI) projects to ManifestSourceRemote. + // Set non-Deno (non-ROSI) projects to ManifestSourceLocal. // TODO: should check if Slack hosted project, but the SDKConfig has not been initialized yet. if clients.Runtime != nil { isDenoProject := strings.Contains(strings.ToLower(clients.Runtime.Name()), "deno") if !isDenoProject { - manifestSource = config.ManifestSourceRemote + manifestSource = config.ManifestSourceLocal } } diff --git a/internal/pkg/create/create_test.go b/internal/pkg/create/create_test.go index 2e8c561e..5dbb3560 100644 --- a/internal/pkg/create/create_test.go +++ b/internal/pkg/create/create_test.go @@ -432,10 +432,10 @@ func Test_Create_installProjectDependencies(t *testing.T) { `Updated config.json manifest source to "project" (local)`, }, }, - "When non-Deno project, should set manifest source to app settings (remote)": { + "When non-Deno project, should set manifest source to project (local)": { runtime: "node", expectedOutputs: []string{ - `Updated config.json manifest source to "app settings" (remote)`, + `Updated config.json manifest source to "project" (local)`, }, }, } From f5ab1ef077b0db4505db538a78a92fc42bfb59c7 Mon Sep 17 00:00:00 2001 From: Michael Brooks Date: Thu, 12 Mar 2026 21:55:56 -0700 Subject: [PATCH 2/6] feat: update remote manifest warnings for link command and manifest overwrite prompt --- cmd/app/link.go | 81 +++++------------------------ cmd/app/link_test.go | 85 +++++++------------------------ cmd/project/init_test.go | 4 +- internal/pkg/apps/install.go | 7 +-- internal/pkg/apps/install_test.go | 4 +- 5 files changed, 36 insertions(+), 145 deletions(-) diff --git a/cmd/app/link.go b/cmd/app/link.go index 82efa163..0bef7216 100644 --- a/cmd/app/link.go +++ b/cmd/app/link.go @@ -16,7 +16,6 @@ package app import ( "context" - "fmt" "path/filepath" "strings" @@ -36,9 +35,6 @@ import ( // LinkAppConfirmPromptText is displayed when prompting to add an existing app const LinkAppConfirmPromptText = "Do you want to add an existing app?" -// LinkAppManifestSourceConfirmPromptText is displayed before updating the manifest source -const LinkAppManifestSourceConfirmPromptText = "Do you want to update the manifest source to remote?" - // appLinkFlagSet contains flag values to reference type appLinkFlagSet struct { environmentFlag string @@ -131,11 +127,9 @@ func LinkAppHeaderSection(ctx context.Context, clients *shared.ClientFactory, sh } // 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 is they want to +// 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. -// The link command requires manifest source to be remote. When it is not, a -// confirmation prompt is displayed before updating the manifest source value. func LinkExistingApp(ctx context.Context, clients *shared.ClientFactory, app *types.App, shouldConfirm bool) (err error) { // Header section LinkAppHeaderSection(ctx, clients, shouldConfirm) @@ -156,67 +150,20 @@ func LinkExistingApp(ctx context.Context, clients *shared.ClientFactory, app *ty } } - // Confirm to update manifest source to remote. - // - Update the manifest source to remote when its a GBP project with a local manifest. - // - Do not update manifest source for ROSI projects, because they can only be local manifests. - manifestSource, err := clients.Config.ProjectConfig.GetManifestSource(ctx) - isManifestSourceRemote := manifestSource.Equals(config.ManifestSourceRemote) - isSlackHostedProject := cmdutil.IsSlackHostedProject(ctx, clients) == nil - - if err != nil || (!isManifestSourceRemote && !isSlackHostedProject) { - // When undefined, the default is config.ManifestSourceLocal - if !manifestSource.Exists() { - manifestSource = config.ManifestSourceLocal - } - - clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{ - Emoji: "warning", - Text: "Warning", - Secondary: []string{ - "Linking an existing app requires the app manifest source to be managed by", - fmt.Sprintf("%s.", config.ManifestSourceRemote.Human()), - " ", - fmt.Sprintf(`App manifest source can be %s or %s:`, config.ManifestSourceLocal.Human(), config.ManifestSourceRemote.Human()), - fmt.Sprintf("- %s: uses manifest from your project's source code for all apps", config.ManifestSourceLocal.String()), - fmt.Sprintf("- %s: uses manifest from app settings for each app", config.ManifestSourceRemote.String()), - " ", - fmt.Sprintf(style.Highlight(`Your manifest source is set to %s.`), manifestSource.Human()), - " ", - fmt.Sprintf("Current manifest source in %s:", style.Highlight(filepath.Join(config.ProjectConfigDirName, config.ProjectConfigJSONFilename))), - fmt.Sprintf(style.Highlight(` %s: "%s"`), "manifest.source", manifestSource.String()), - " ", - fmt.Sprintf("Updating manifest source will be changed in %s:", style.Highlight(filepath.Join(config.ProjectConfigDirName, config.ProjectConfigJSONFilename))), - fmt.Sprintf(style.Highlight(` %s: "%s"`), "manifest.source", config.ManifestSourceRemote), - }, - })) - - proceed, err := clients.IO.ConfirmPrompt(ctx, LinkAppManifestSourceConfirmPromptText, false) - if err != nil { - clients.IO.PrintDebug(ctx, "Error prompting to update the manifest source to %s: %s", config.ManifestSourceRemote, err) - return err - } - - if !proceed { - // Add newline to match the trailing newline inserted from the footer section - clients.IO.PrintInfo(ctx, false, "") - return nil - } - - if err := config.SetManifestSource(ctx, clients.Fs, clients.Os, config.ManifestSourceRemote); err != nil { - // Log the error to the verbose output - clients.IO.PrintDebug(ctx, "Error setting manifest source in project-level config: %s", err) - // Display a user-friendly error with a workaround - slackErr := slackerror.New(slackerror.ErrProjectConfigManifestSource). - WithMessage("Failed to update the manifest source to %s", config.ManifestSourceRemote). - WithRemediation( - "You can manually update the manifest source by setting the following\nproperty in %s:\n %s", - filepath.Join(config.ProjectConfigDirName, config.ProjectConfigJSONFilename), - fmt.Sprintf(`manifest.source: "%s"`, config.ManifestSourceRemote), - ). - WithRootCause(err) - clients.IO.PrintError(ctx, "%s", slackErr.Error()) - } + // App Manifest section + manifestSource, _ := clients.Config.ProjectConfig.GetManifestSource(ctx) + if !manifestSource.Exists() { + manifestSource = config.ManifestSourceLocal } + configPath := filepath.Join(config.ProjectConfigDirName, config.ProjectConfigJSONFilename) + clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{ + Emoji: "books", + Text: "App Manifest", + Secondary: []string{ + "Manifest source is " + style.Highlight(manifestSource.Human()), + "Manifest source is configured in " + style.Highlight(configPath), + }, + })) // Prompt to get app details var auth *types.SlackAuth diff --git a/cmd/app/link_test.go b/cmd/app/link_test.go index d212d750..82f4e154 100644 --- a/cmd/app/link_test.go +++ b/cmd/app/link_test.go @@ -431,7 +431,7 @@ func Test_Apps_Link(t *testing.T) { CmdArgs: []string{}, ExpectedError: slackerror.New(slackerror.ErrAppNotFound), }, - "accepting manifest source prompt should save information about the provided deployed app": { + "links app when manifest source is local": { Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { cm.Auth.On("Auths", mock.Anything).Return([]types.SlackAuth{ mockLinkSlackAuth2, @@ -439,16 +439,10 @@ func Test_Apps_Link(t *testing.T) { }, nil) cm.AddDefaultMocks() setupAppLinkCommandMocks(t, ctx, cm, cf) - // Set manifest source to project to trigger confirmation prompt + // Set manifest source to local if err := config.SetManifestSource(ctx, cm.Fs, cm.Os, config.ManifestSourceLocal); err != nil { require.FailNow(t, fmt.Sprintf("Failed to set the manifest source in the memory-based file system: %s", err)) } - // Accept manifest source confirmation prompt - cm.IO.On("ConfirmPrompt", - mock.Anything, - LinkAppManifestSourceConfirmPromptText, - mock.Anything, - ).Return(true, nil) cm.IO.On("SelectPrompt", mock.Anything, "Select the existing app team", @@ -496,49 +490,13 @@ func Test_Apps_Link(t *testing.T) { ) require.NoError(t, err) assert.Equal(t, expectedApp, actualApp) - // Assert manifest confirmation prompt accepted - cm.IO.AssertCalled(t, "ConfirmPrompt", - mock.Anything, - LinkAppManifestSourceConfirmPromptText, - mock.Anything, - ) + // Assert manifest source info is displayed + output := cm.GetCombinedOutput() + assert.Contains(t, output, "App Manifest") + assert.Contains(t, output, `"project" (local)`) }, }, - "declining manifest source prompt should not link app": { - Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { - cm.AddDefaultMocks() - setupAppLinkCommandMocks(t, ctx, cm, cf) - // Set manifest source to project to trigger confirmation prompt - if err := config.SetManifestSource(ctx, cm.Fs, cm.Os, config.ManifestSourceLocal); err != nil { - require.FailNow(t, fmt.Sprintf("Failed to set the manifest source in the memory-based file system: %s", err)) - } - // Decline manifest source confirmation prompt - cm.IO.On("ConfirmPrompt", - mock.Anything, - LinkAppManifestSourceConfirmPromptText, - mock.Anything, - ).Return(false, nil) - }, - CmdArgs: []string{}, - ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) { - // Assert manifest confirmation prompt accepted - cm.IO.AssertCalled(t, "ConfirmPrompt", - mock.Anything, - LinkAppManifestSourceConfirmPromptText, - mock.Anything, - ) - - // Assert no apps saved - apps, _, err := cm.AppClient.GetDeployedAll(ctx) - require.NoError(t, err) - require.Len(t, apps, 0) - - apps, err = cm.AppClient.GetLocalAll(ctx) - require.NoError(t, err) - require.Len(t, apps, 0) - }, - }, - "manifest source prompt should not display for Run-on-Slack apps with local manifest source": { + "displays manifest info for Run-on-Slack apps with local manifest source": { Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { cm.Auth.On("Auths", mock.Anything).Return([]types.SlackAuth{ mockLinkSlackAuth1, @@ -607,15 +565,13 @@ func Test_Apps_Link(t *testing.T) { ) require.NoError(t, err) assert.Equal(t, expectedApp, actualApp) - // Assert manifest confirmation prompt was not displayed - cm.IO.AssertNotCalled(t, "ConfirmPrompt", - mock.Anything, - LinkAppManifestSourceConfirmPromptText, - mock.Anything, - ) + // Assert manifest source info is displayed + output := cm.GetCombinedOutput() + assert.Contains(t, output, "App Manifest") + assert.Contains(t, output, `"project" (local)`) }, }, - "manifest source prompt should display for GBP apps with local manifest source": { + "displays manifest info for GBP apps with local manifest source": { Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { cm.Auth.On("Auths", mock.Anything).Return([]types.SlackAuth{ mockLinkSlackAuth1, @@ -627,15 +583,10 @@ func Test_Apps_Link(t *testing.T) { if err := config.SetManifestSource(ctx, cm.Fs, cm.Os, config.ManifestSourceLocal); err != nil { require.FailNow(t, fmt.Sprintf("Failed to set the manifest source in the memory-based file system: %s", err)) } - // Mock manifest for Run-on-Slack app + // Mock manifest for GBP app manifestMock := &app.ManifestMockObject{} manifestMock.On("GetManifestLocal", mock.Anything, mock.Anything, mock.Anything).Return(types.SlackYaml{}, nil) cf.AppClient().Manifest = manifestMock - cm.IO.On("ConfirmPrompt", - mock.Anything, - LinkAppManifestSourceConfirmPromptText, - mock.Anything, - ).Return(true, nil) cm.IO.On("SelectPrompt", mock.Anything, "Select the existing app team", @@ -683,12 +634,10 @@ func Test_Apps_Link(t *testing.T) { ) require.NoError(t, err) assert.Equal(t, expectedApp, actualApp) - // Assert manifest confirmation prompt was displayed - cm.IO.AssertCalled(t, "ConfirmPrompt", - mock.Anything, - LinkAppManifestSourceConfirmPromptText, - mock.Anything, - ) + // Assert manifest source info is displayed + output := cm.GetCombinedOutput() + assert.Contains(t, output, "App Manifest") + assert.Contains(t, output, `"project" (local)`) }, }, }, func(clients *shared.ClientFactory) *cobra.Command { diff --git a/cmd/project/init_test.go b/cmd/project/init_test.go index 2af500f4..e04d6a28 100644 --- a/cmd/project/init_test.go +++ b/cmd/project/init_test.go @@ -108,10 +108,8 @@ func Test_Project_InitCommand(t *testing.T) { }, nil) // Default setup setupProjectInitCommandMocks(t, ctx, cm, cf) - // Do not link an existing app + // Link an existing app cm.IO.On("ConfirmPrompt", mock.Anything, app.LinkAppConfirmPromptText, mock.Anything).Return(true, nil) - // Mock prompt to link an existing app - cm.IO.On("ConfirmPrompt", mock.Anything, app.LinkAppManifestSourceConfirmPromptText, mock.Anything).Return(true, nil) // Mock prompt for team cm.IO.On("SelectPrompt", mock.Anything, diff --git a/internal/pkg/apps/install.go b/internal/pkg/apps/install.go index 3b312bb1..7cf505b9 100644 --- a/internal/pkg/apps/install.go +++ b/internal/pkg/apps/install.go @@ -752,7 +752,7 @@ func shouldUpdateManifest(ctx context.Context, clients *shared.ClientFactory, ap case saved.Equals(""): notice = "Manifest values for this app are overwritten on reinstall" default: - notice = "The manifest on app settings has been changed since last update!" + notice = style.Yellow("The manifest on app settings has been changed since last update") } clients.IO.PrintInfo(ctx, false, "\n%s", style.Sectionf(style.TextSection{ Emoji: "books", @@ -764,10 +764,7 @@ func shouldUpdateManifest(ctx context.Context, clients *shared.ClientFactory, ap } continues, err := clients.IO.ConfirmPrompt( ctx, - fmt.Sprintf( - "Update app settings with changes to the %s manifest?", - config.ManifestSourceLocal.String(), - ), + "Overwrite manifest on app settings with the project's manifest file?", false, ) if err != nil { diff --git a/internal/pkg/apps/install_test.go b/internal/pkg/apps/install_test.go index f9bc6d2b..2c44dc94 100644 --- a/internal/pkg/apps/install_test.go +++ b/internal/pkg/apps/install_test.go @@ -574,7 +574,7 @@ func TestInstall(t *testing.T) { clientsMock.IO.On( "ConfirmPrompt", mock.Anything, - "Update app settings with changes to the local manifest?", + "Overwrite manifest on app settings with the project's manifest file?", false, ).Return( tc.mockConfirmPrompt, @@ -1396,7 +1396,7 @@ func TestInstallLocalApp(t *testing.T) { clientsMock.IO.On( "ConfirmPrompt", mock.Anything, - "Update app settings with changes to the local manifest?", + "Overwrite manifest on app settings with the project's manifest file?", false, ).Return( tc.mockConfirmPrompt, From 3632f26c3a8b0a2d7e4aa9bec96fd296a93d0ef4 Mon Sep 17 00:00:00 2001 From: Michael Brooks Date: Thu, 12 Mar 2026 22:42:16 -0700 Subject: [PATCH 3/6] test: update tests for local and remote manifest source --- cmd/app/link_test.go | 99 ++++++-------------------------------------- 1 file changed, 12 insertions(+), 87 deletions(-) diff --git a/cmd/app/link_test.go b/cmd/app/link_test.go index 82f4e154..598037ac 100644 --- a/cmd/app/link_test.go +++ b/cmd/app/link_test.go @@ -466,7 +466,7 @@ func Test_Apps_Link(t *testing.T) { mock.Anything, ).Return(iostreams.SelectPromptResponse{ Flag: true, - Option: "deployed", + Option: "local", }, nil) cm.API.On( "GetAppStatus", @@ -483,8 +483,10 @@ func Test_Apps_Link(t *testing.T) { TeamDomain: mockLinkSlackAuth1.TeamDomain, TeamID: mockLinkSlackAuth1.TeamID, EnterpriseID: mockLinkSlackAuth1.EnterpriseID, + UserID: mockLinkSlackAuth1.UserID, + IsDev: true, } - actualApp, err := cm.AppClient.GetDeployed( + actualApp, err := cm.AppClient.GetLocal( ctx, mockLinkSlackAuth1.TeamID, ) @@ -496,97 +498,18 @@ func Test_Apps_Link(t *testing.T) { assert.Contains(t, output, `"project" (local)`) }, }, - "displays manifest info for Run-on-Slack apps with local manifest source": { + "links app when manifest source is remote": { Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { cm.Auth.On("Auths", mock.Anything).Return([]types.SlackAuth{ - mockLinkSlackAuth1, mockLinkSlackAuth2, - }, nil) - cm.AddDefaultMocks() - setupAppLinkCommandMocks(t, ctx, cm, cf) - // Set manifest source to local - if err := config.SetManifestSource(ctx, cm.Fs, cm.Os, config.ManifestSourceLocal); err != nil { - require.FailNow(t, fmt.Sprintf("Failed to set the manifest source in the memory-based file system: %s", err)) - } - // Mock manifest for Run-on-Slack app - manifestMock := &app.ManifestMockObject{} - manifestMock.On("GetManifestLocal", mock.Anything, mock.Anything, mock.Anything).Return(types.SlackYaml{ - AppManifest: types.AppManifest{ - Settings: &types.AppSettings{ - FunctionRuntime: types.SlackHosted, - }, - }, - }, nil) - cf.AppClient().Manifest = manifestMock - cm.IO.On("SelectPrompt", - mock.Anything, - "Select the existing app team", - mock.Anything, - mock.Anything, - mock.Anything, - ).Return(iostreams.SelectPromptResponse{ - Flag: true, - Option: mockLinkSlackAuth1.TeamDomain, - }, nil) - cm.IO.On("InputPrompt", - mock.Anything, - "Enter the existing app ID", - mock.Anything, - ).Return(mockLinkAppID1, nil) - cm.IO.On("SelectPrompt", - mock.Anything, - "Choose the app environment", - mock.Anything, - mock.Anything, - mock.Anything, - ).Return(iostreams.SelectPromptResponse{ - Flag: true, - Option: "deployed", - }, nil) - cm.API.On( - "GetAppStatus", - mock.Anything, - mockLinkSlackAuth1.Token, - []string{mockLinkAppID1}, - mockLinkSlackAuth1.TeamID, - ).Return(api.GetAppStatusResult{}, nil) - }, - CmdArgs: []string{}, - ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) { - expectedApp := types.App{ - AppID: mockLinkAppID1, - TeamDomain: mockLinkSlackAuth1.TeamDomain, - TeamID: mockLinkSlackAuth1.TeamID, - EnterpriseID: mockLinkSlackAuth1.EnterpriseID, - } - actualApp, err := cm.AppClient.GetDeployed( - ctx, - mockLinkSlackAuth1.TeamID, - ) - require.NoError(t, err) - assert.Equal(t, expectedApp, actualApp) - // Assert manifest source info is displayed - output := cm.GetCombinedOutput() - assert.Contains(t, output, "App Manifest") - assert.Contains(t, output, `"project" (local)`) - }, - }, - "displays manifest info for GBP apps with local manifest source": { - Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { - cm.Auth.On("Auths", mock.Anything).Return([]types.SlackAuth{ mockLinkSlackAuth1, - mockLinkSlackAuth2, }, nil) cm.AddDefaultMocks() setupAppLinkCommandMocks(t, ctx, cm, cf) - // Set manifest source to local - if err := config.SetManifestSource(ctx, cm.Fs, cm.Os, config.ManifestSourceLocal); err != nil { + // Set manifest source to remote + if err := config.SetManifestSource(ctx, cm.Fs, cm.Os, config.ManifestSourceRemote); err != nil { require.FailNow(t, fmt.Sprintf("Failed to set the manifest source in the memory-based file system: %s", err)) } - // Mock manifest for GBP app - manifestMock := &app.ManifestMockObject{} - manifestMock.On("GetManifestLocal", mock.Anything, mock.Anything, mock.Anything).Return(types.SlackYaml{}, nil) - cf.AppClient().Manifest = manifestMock cm.IO.On("SelectPrompt", mock.Anything, "Select the existing app team", @@ -610,7 +533,7 @@ func Test_Apps_Link(t *testing.T) { mock.Anything, ).Return(iostreams.SelectPromptResponse{ Flag: true, - Option: "deployed", + Option: "local", }, nil) cm.API.On( "GetAppStatus", @@ -627,8 +550,10 @@ func Test_Apps_Link(t *testing.T) { TeamDomain: mockLinkSlackAuth1.TeamDomain, TeamID: mockLinkSlackAuth1.TeamID, EnterpriseID: mockLinkSlackAuth1.EnterpriseID, + UserID: mockLinkSlackAuth1.UserID, + IsDev: true, } - actualApp, err := cm.AppClient.GetDeployed( + actualApp, err := cm.AppClient.GetLocal( ctx, mockLinkSlackAuth1.TeamID, ) @@ -637,7 +562,7 @@ func Test_Apps_Link(t *testing.T) { // Assert manifest source info is displayed output := cm.GetCombinedOutput() assert.Contains(t, output, "App Manifest") - assert.Contains(t, output, `"project" (local)`) + assert.Contains(t, output, `"app settings" (remote)`) }, }, }, func(clients *shared.ClientFactory) *cobra.Command { From b1b26788c5cb005af3f28e9aa6ed336acfaf39a6 Mon Sep 17 00:00:00 2001 From: Michael Brooks Date: Fri, 13 Mar 2026 14:01:59 -0700 Subject: [PATCH 4/6] feat: fix preserving app manifest source when template sets it --- cmd/project/init.go | 3 +-- cmd/project/init_test.go | 2 +- internal/pkg/create/create.go | 23 +++++----------- internal/pkg/create/create_test.go | 43 +++++++++++++++++++++++++----- 4 files changed, 44 insertions(+), 27 deletions(-) diff --git a/cmd/project/init.go b/cmd/project/init.go index cd062abf..f8a80702 100644 --- a/cmd/project/init.go +++ b/cmd/project/init.go @@ -20,7 +20,6 @@ import ( "strings" "github.com/slackapi/slack-cli/cmd/app" - "github.com/slackapi/slack-cli/internal/config" "github.com/slackapi/slack-cli/internal/pkg/create" "github.com/slackapi/slack-cli/internal/shared" "github.com/slackapi/slack-cli/internal/shared/types" @@ -108,7 +107,7 @@ func projectInitCommandRunE(clients *shared.ClientFactory, cmd *cobra.Command, a // Install the project dependencies, such as .slack/ and runtime packages // Existing projects initialized always default to config.ManifestSourceLocal. - _ = create.InstallProjectDependencies(ctx, clients, projectDirPath, config.ManifestSourceLocal) + _ = create.InstallProjectDependencies(ctx, clients, projectDirPath) // Add an existing app to the project err = app.LinkExistingApp(ctx, clients, &types.App{}, true) diff --git a/cmd/project/init_test.go b/cmd/project/init_test.go index 2af500f4..beb31b80 100644 --- a/cmd/project/init_test.go +++ b/cmd/project/init_test.go @@ -72,7 +72,7 @@ func Test_Project_InitCommand(t *testing.T) { require.Contains(t, output, "Added "+filepath.Join("project-name", ".slack")) require.Contains(t, output, "Added "+filepath.Join("project-name", ".slack", ".gitignore")) require.Contains(t, output, "Added "+filepath.Join("project-name", ".slack", "hooks.json")) - require.Contains(t, output, `Updated config.json manifest source to "project" (local)`) + require.Contains(t, output, `Updated app manifest source to "project" (local)`) // Assert prompt to add existing apps was called cm.IO.AssertCalled( t, diff --git a/internal/pkg/create/create.go b/internal/pkg/create/create.go index 1ec3e11c..f10e07ec 100644 --- a/internal/pkg/create/create.go +++ b/internal/pkg/create/create.go @@ -156,7 +156,7 @@ func Create(ctx context.Context, clients *shared.ClientFactory, createArgs Creat // Install project dependencies to add CLI support and cache dev dependencies. // CLI created projects always default to config.ManifestSourceLocal. - InstallProjectDependencies(ctx, clients, projectDirPath, config.ManifestSourceLocal) + InstallProjectDependencies(ctx, clients, projectDirPath) clients.IO.PrintTrace(ctx, slacktrace.CreateDependenciesSuccess) return appDirPath, nil @@ -413,7 +413,6 @@ func InstallProjectDependencies( ctx context.Context, clients *shared.ClientFactory, projectDirPath string, - manifestSource config.ManifestSource, ) []string { var outputs []string @@ -516,30 +515,20 @@ func InstallProjectDependencies( } } - // Default manifest source to ManifestSourceLocal - if !manifestSource.Exists() { - manifestSource = config.ManifestSourceLocal - } - - // Set non-Deno (non-ROSI) projects to ManifestSourceLocal. - // TODO: should check if Slack hosted project, but the SDKConfig has not been initialized yet. - if clients.Runtime != nil { - isDenoProject := strings.Contains(strings.ToLower(clients.Runtime.Name()), "deno") - if !isDenoProject { - manifestSource = config.ManifestSourceLocal - } + // Get the manifest source from the project-level config + manifestSource, err := clients.Config.ProjectConfig.GetManifestSource(ctx) + if err != nil { + clients.IO.PrintDebug(ctx, "Error getting manifest source: %s", err) } // Set "manifest.source" in .slack/config.json if err := config.SetManifestSource(ctx, clients.Fs, clients.Os, manifestSource); err != nil { clients.IO.PrintDebug(ctx, "Error setting manifest source in project-level config: %s", err) } else { - configJSONFilename := config.ProjectConfigJSONFilename manifestSourceStyled := style.Highlight(manifestSource.Human()) outputs = append(outputs, fmt.Sprintf( - "Updated %s manifest source to %s", - configJSONFilename, + "Updated app manifest source to %s", manifestSourceStyled, )) } diff --git a/internal/pkg/create/create_test.go b/internal/pkg/create/create_test.go index 5dbb3560..1362d30e 100644 --- a/internal/pkg/create/create_test.go +++ b/internal/pkg/create/create_test.go @@ -366,6 +366,7 @@ func Test_Create_installProjectDependencies(t *testing.T) { experiments []string runtime string manifestSource config.ManifestSource + expectedManifestSource config.ManifestSource existingFiles map[string]string expectedOutputs []string unexpectedOutputs []string @@ -418,24 +419,38 @@ func Test_Create_installProjectDependencies(t *testing.T) { }, "When no manifest source, default to project (local)": { expectedOutputs: []string{ - `Updated config.json manifest source to "project" (local)`, + `Updated app manifest source to "project" (local)`, }, }, - "When manifest source is provided, should set it": { - manifestSource: config.ManifestSourceRemote, + "When remote manifest source is provided, should use it": { + manifestSource: config.ManifestSourceRemote, + expectedManifestSource: config.ManifestSourceRemote, + existingFiles: map[string]string{ + ".slack/hooks.json": "{}", + }, + expectedOutputs: []string{ + `Updated app manifest source to "app settings" (remote)`, + }, + }, + "When local manifest source is provided, should use it": { + manifestSource: config.ManifestSourceLocal, + expectedManifestSource: config.ManifestSourceLocal, + existingFiles: map[string]string{ + ".slack/hooks.json": "{}", + }, expectedOutputs: []string{ - `Updated config.json manifest source to "app settings" (remote)`, + `Updated app manifest source to "project" (local)`, }, }, "When Deno project, should set manifest source to project (local)": { expectedOutputs: []string{ - `Updated config.json manifest source to "project" (local)`, + `Updated app manifest source to "project" (local)`, }, }, "When non-Deno project, should set manifest source to project (local)": { runtime: "node", expectedOutputs: []string{ - `Updated config.json manifest source to "project" (local)`, + `Updated app manifest source to "project" (local)`, }, }, } @@ -490,8 +505,15 @@ func Test_Create_installProjectDependencies(t *testing.T) { } } + // Set manifest source + if tc.manifestSource != "" { + if err := config.SetManifestSource(ctx, clientsMock.Fs, clientsMock.Os, tc.manifestSource); err != nil { + require.FailNow(t, fmt.Sprintf("Failed to set the manifest source in the memory-based file system: %s", err)) + } + } + // Run the test - outputs := InstallProjectDependencies(ctx, clients, projectDirPath, tc.manifestSource) + outputs := InstallProjectDependencies(ctx, clients, projectDirPath) // Assertions for _, expectedOutput := range tc.expectedOutputs { @@ -503,6 +525,13 @@ func Test_Create_installProjectDependencies(t *testing.T) { for _, expectedVerboseOutput := range tc.expectedVerboseOutputs { clientsMock.IO.AssertCalled(t, "PrintDebug", mock.Anything, expectedVerboseOutput, tc.expectedVerboseArgs) } + if tc.expectedManifestSource != "" { + actualManifestSource, err := clients.Config.ProjectConfig.GetManifestSource(ctx) + if err != nil { + require.FailNow(t, fmt.Sprintf("Failed to get the manifest source: %s", err)) + } + assert.Equal(t, tc.expectedManifestSource, actualManifestSource) + } assert.NotEmpty(t, clients.Config.ProjectID, "config.project_id") // output := clientsMock.GetCombinedOutput() // assert.Contains(t, output, tc.expectedOutputs) From 0f2e6e1659b24a5b754c512de301334f88cc518f Mon Sep 17 00:00:00 2001 From: Michael Brooks Date: Fri, 13 Mar 2026 14:15:13 -0700 Subject: [PATCH 5/6] chore: remove redundant default for manifest source --- cmd/app/link.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/app/link.go b/cmd/app/link.go index 0bef7216..f7b75034 100644 --- a/cmd/app/link.go +++ b/cmd/app/link.go @@ -151,10 +151,11 @@ func LinkExistingApp(ctx context.Context, clients *shared.ClientFactory, app *ty } // App Manifest section - manifestSource, _ := clients.Config.ProjectConfig.GetManifestSource(ctx) - if !manifestSource.Exists() { - manifestSource = config.ManifestSourceLocal + manifestSource, err := clients.Config.ProjectConfig.GetManifestSource(ctx) + if err != nil { + return err } + configPath := filepath.Join(config.ProjectConfigDirName, config.ProjectConfigJSONFilename) clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{ Emoji: "books", From 8bea8ec26a39955f8467056df0338441a3fdd779 Mon Sep 17 00:00:00 2001 From: Michael Brooks Date: Fri, 13 Mar 2026 14:20:01 -0700 Subject: [PATCH 6/6] Update cmd/app/link.go Co-authored-by: Eden Zimbelman --- cmd/app/link.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/app/link.go b/cmd/app/link.go index f7b75034..a1f3945c 100644 --- a/cmd/app/link.go +++ b/cmd/app/link.go @@ -161,7 +161,7 @@ func LinkExistingApp(ctx context.Context, clients *shared.ClientFactory, app *ty Emoji: "books", Text: "App Manifest", Secondary: []string{ - "Manifest source is " + style.Highlight(manifestSource.Human()), + "Manifest source is gathered from " + style.Highlight(manifestSource.Human()), "Manifest source is configured in " + style.Highlight(configPath), }, }))