Skip to content

Commit 265d8a9

Browse files
mwbrookszimeg
andauthored
feat: update 'app link' to not change the app manifest source (#396)
* 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. * feat: update remote manifest warnings for link command and manifest overwrite prompt * test: update tests for local and remote manifest source * feat: fix preserving app manifest source when template sets it * chore: remove redundant default for manifest source * Update cmd/app/link.go Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com> --------- Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
1 parent 3744b76 commit 265d8a9

5 files changed

Lines changed: 40 additions & 223 deletions

File tree

cmd/app/link.go

Lines changed: 14 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ package app
1616

1717
import (
1818
"context"
19-
"fmt"
2019
"path/filepath"
2120
"strings"
2221

@@ -36,9 +35,6 @@ import (
3635
// LinkAppConfirmPromptText is displayed when prompting to add an existing app
3736
const LinkAppConfirmPromptText = "Do you want to add an existing app?"
3837

39-
// LinkAppManifestSourceConfirmPromptText is displayed before updating the manifest source
40-
const LinkAppManifestSourceConfirmPromptText = "Do you want to update the manifest source to remote?"
41-
4238
// appLinkFlagSet contains flag values to reference
4339
type appLinkFlagSet struct {
4440
environmentFlag string
@@ -131,11 +127,9 @@ func LinkAppHeaderSection(ctx context.Context, clients *shared.ClientFactory, sh
131127
}
132128

133129
// LinkExistingApp prompts for an existing App ID and saves the details to the project.
134-
// When shouldConfirm is true, a confirmation prompt will ask the user is they want to
130+
// When shouldConfirm is true, a confirmation prompt will ask the user if they want to
135131
// link an existing app and additional information is included in the header.
136132
// The shouldConfirm option is encouraged for third-party callers.
137-
// The link command requires manifest source to be remote. When it is not, a
138-
// confirmation prompt is displayed before updating the manifest source value.
139133
func LinkExistingApp(ctx context.Context, clients *shared.ClientFactory, app *types.App, shouldConfirm bool) (err error) {
140134
// Header section
141135
LinkAppHeaderSection(ctx, clients, shouldConfirm)
@@ -156,68 +150,22 @@ func LinkExistingApp(ctx context.Context, clients *shared.ClientFactory, app *ty
156150
}
157151
}
158152

159-
// Confirm to update manifest source to remote.
160-
// - Update the manifest source to remote when its a GBP project with a local manifest.
161-
// - Do not update manifest source for ROSI projects, because they can only be local manifests.
153+
// App Manifest section
162154
manifestSource, err := clients.Config.ProjectConfig.GetManifestSource(ctx)
163-
isManifestSourceRemote := manifestSource.Equals(config.ManifestSourceRemote)
164-
isSlackHostedProject := cmdutil.IsSlackHostedProject(ctx, clients) == nil
165-
166-
if err != nil || (!isManifestSourceRemote && !isSlackHostedProject) {
167-
// When undefined, the default is config.ManifestSourceLocal
168-
if !manifestSource.Exists() {
169-
manifestSource = config.ManifestSourceLocal
170-
}
171-
172-
clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{
173-
Emoji: "warning",
174-
Text: "Warning",
175-
Secondary: []string{
176-
"Linking an existing app requires the app manifest source to be managed by",
177-
fmt.Sprintf("%s.", config.ManifestSourceRemote.Human()),
178-
" ",
179-
fmt.Sprintf(`App manifest source can be %s or %s:`, config.ManifestSourceLocal.Human(), config.ManifestSourceRemote.Human()),
180-
fmt.Sprintf("- %s: uses manifest from your project's source code for all apps", config.ManifestSourceLocal.String()),
181-
fmt.Sprintf("- %s: uses manifest from app settings for each app", config.ManifestSourceRemote.String()),
182-
" ",
183-
fmt.Sprintf(style.Highlight(`Your manifest source is set to %s.`), manifestSource.Human()),
184-
" ",
185-
fmt.Sprintf("Current manifest source in %s:", style.Highlight(filepath.Join(config.ProjectConfigDirName, config.ProjectConfigJSONFilename))),
186-
fmt.Sprintf(style.Highlight(` %s: "%s"`), "manifest.source", manifestSource.String()),
187-
" ",
188-
fmt.Sprintf("Updating manifest source will be changed in %s:", style.Highlight(filepath.Join(config.ProjectConfigDirName, config.ProjectConfigJSONFilename))),
189-
fmt.Sprintf(style.Highlight(` %s: "%s"`), "manifest.source", config.ManifestSourceRemote),
190-
},
191-
}))
192-
193-
proceed, err := clients.IO.ConfirmPrompt(ctx, LinkAppManifestSourceConfirmPromptText, false)
194-
if err != nil {
195-
clients.IO.PrintDebug(ctx, "Error prompting to update the manifest source to %s: %s", config.ManifestSourceRemote, err)
196-
return err
197-
}
198-
199-
if !proceed {
200-
// Add newline to match the trailing newline inserted from the footer section
201-
clients.IO.PrintInfo(ctx, false, "")
202-
return nil
203-
}
204-
205-
if err := config.SetManifestSource(ctx, clients.Fs, clients.Os, config.ManifestSourceRemote); err != nil {
206-
// Log the error to the verbose output
207-
clients.IO.PrintDebug(ctx, "Error setting manifest source in project-level config: %s", err)
208-
// Display a user-friendly error with a workaround
209-
slackErr := slackerror.New(slackerror.ErrProjectConfigManifestSource).
210-
WithMessage("Failed to update the manifest source to %s", config.ManifestSourceRemote).
211-
WithRemediation(
212-
"You can manually update the manifest source by setting the following\nproperty in %s:\n %s",
213-
filepath.Join(config.ProjectConfigDirName, config.ProjectConfigJSONFilename),
214-
fmt.Sprintf(`manifest.source: "%s"`, config.ManifestSourceRemote),
215-
).
216-
WithRootCause(err)
217-
clients.IO.PrintError(ctx, "%s", slackErr.Error())
218-
}
155+
if err != nil {
156+
return err
219157
}
220158

159+
configPath := filepath.Join(config.ProjectConfigDirName, config.ProjectConfigJSONFilename)
160+
clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{
161+
Emoji: "books",
162+
Text: "App Manifest",
163+
Secondary: []string{
164+
"Manifest source is gathered from " + style.Highlight(manifestSource.Human()),
165+
"Manifest source is configured in " + style.Highlight(configPath),
166+
},
167+
}))
168+
221169
// Prompt to get app details
222170
var auth *types.SlackAuth
223171
*app, auth, err = promptExistingApp(ctx, clients)

cmd/app/link_test.go

Lines changed: 21 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -431,135 +431,18 @@ func Test_Apps_Link(t *testing.T) {
431431
CmdArgs: []string{},
432432
ExpectedError: slackerror.New(slackerror.ErrAppNotFound),
433433
},
434-
"accepting manifest source prompt should save information about the provided deployed app": {
434+
"links app when manifest source is local": {
435435
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
436436
cm.Auth.On("Auths", mock.Anything).Return([]types.SlackAuth{
437437
mockLinkSlackAuth2,
438438
mockLinkSlackAuth1,
439439
}, nil)
440440
cm.AddDefaultMocks()
441441
setupAppLinkCommandMocks(t, ctx, cm, cf)
442-
// Set manifest source to project to trigger confirmation prompt
443-
if err := config.SetManifestSource(ctx, cm.Fs, cm.Os, config.ManifestSourceLocal); err != nil {
444-
require.FailNow(t, fmt.Sprintf("Failed to set the manifest source in the memory-based file system: %s", err))
445-
}
446-
// Accept manifest source confirmation prompt
447-
cm.IO.On("ConfirmPrompt",
448-
mock.Anything,
449-
LinkAppManifestSourceConfirmPromptText,
450-
mock.Anything,
451-
).Return(true, nil)
452-
cm.IO.On("SelectPrompt",
453-
mock.Anything,
454-
"Select the existing app team",
455-
mock.Anything,
456-
mock.Anything,
457-
mock.Anything,
458-
).Return(iostreams.SelectPromptResponse{
459-
Flag: true,
460-
Option: mockLinkSlackAuth1.TeamDomain,
461-
}, nil)
462-
cm.IO.On("InputPrompt",
463-
mock.Anything,
464-
"Enter the existing app ID",
465-
mock.Anything,
466-
).Return(mockLinkAppID1, nil)
467-
cm.IO.On("SelectPrompt",
468-
mock.Anything,
469-
"Choose the app environment",
470-
mock.Anything,
471-
mock.Anything,
472-
mock.Anything,
473-
).Return(iostreams.SelectPromptResponse{
474-
Flag: true,
475-
Option: "deployed",
476-
}, nil)
477-
cm.API.On(
478-
"GetAppStatus",
479-
mock.Anything,
480-
mockLinkSlackAuth1.Token,
481-
[]string{mockLinkAppID1},
482-
mockLinkSlackAuth1.TeamID,
483-
).Return(api.GetAppStatusResult{}, nil)
484-
},
485-
CmdArgs: []string{},
486-
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
487-
expectedApp := types.App{
488-
AppID: mockLinkAppID1,
489-
TeamDomain: mockLinkSlackAuth1.TeamDomain,
490-
TeamID: mockLinkSlackAuth1.TeamID,
491-
EnterpriseID: mockLinkSlackAuth1.EnterpriseID,
492-
}
493-
actualApp, err := cm.AppClient.GetDeployed(
494-
ctx,
495-
mockLinkSlackAuth1.TeamID,
496-
)
497-
require.NoError(t, err)
498-
assert.Equal(t, expectedApp, actualApp)
499-
// Assert manifest confirmation prompt accepted
500-
cm.IO.AssertCalled(t, "ConfirmPrompt",
501-
mock.Anything,
502-
LinkAppManifestSourceConfirmPromptText,
503-
mock.Anything,
504-
)
505-
},
506-
},
507-
"declining manifest source prompt should not link app": {
508-
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
509-
cm.AddDefaultMocks()
510-
setupAppLinkCommandMocks(t, ctx, cm, cf)
511-
// Set manifest source to project to trigger confirmation prompt
512-
if err := config.SetManifestSource(ctx, cm.Fs, cm.Os, config.ManifestSourceLocal); err != nil {
513-
require.FailNow(t, fmt.Sprintf("Failed to set the manifest source in the memory-based file system: %s", err))
514-
}
515-
// Decline manifest source confirmation prompt
516-
cm.IO.On("ConfirmPrompt",
517-
mock.Anything,
518-
LinkAppManifestSourceConfirmPromptText,
519-
mock.Anything,
520-
).Return(false, nil)
521-
},
522-
CmdArgs: []string{},
523-
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
524-
// Assert manifest confirmation prompt accepted
525-
cm.IO.AssertCalled(t, "ConfirmPrompt",
526-
mock.Anything,
527-
LinkAppManifestSourceConfirmPromptText,
528-
mock.Anything,
529-
)
530-
531-
// Assert no apps saved
532-
apps, _, err := cm.AppClient.GetDeployedAll(ctx)
533-
require.NoError(t, err)
534-
require.Len(t, apps, 0)
535-
536-
apps, err = cm.AppClient.GetLocalAll(ctx)
537-
require.NoError(t, err)
538-
require.Len(t, apps, 0)
539-
},
540-
},
541-
"manifest source prompt should not display for Run-on-Slack apps with local manifest source": {
542-
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
543-
cm.Auth.On("Auths", mock.Anything).Return([]types.SlackAuth{
544-
mockLinkSlackAuth1,
545-
mockLinkSlackAuth2,
546-
}, nil)
547-
cm.AddDefaultMocks()
548-
setupAppLinkCommandMocks(t, ctx, cm, cf)
549442
// Set manifest source to local
550443
if err := config.SetManifestSource(ctx, cm.Fs, cm.Os, config.ManifestSourceLocal); err != nil {
551444
require.FailNow(t, fmt.Sprintf("Failed to set the manifest source in the memory-based file system: %s", err))
552445
}
553-
// Mock manifest for Run-on-Slack app
554-
manifestMock := &app.ManifestMockObject{}
555-
manifestMock.On("GetManifestLocal", mock.Anything, mock.Anything, mock.Anything).Return(types.SlackYaml{
556-
AppManifest: types.AppManifest{
557-
Settings: &types.AppSettings{
558-
FunctionRuntime: types.SlackHosted,
559-
},
560-
},
561-
}, nil)
562-
cf.AppClient().Manifest = manifestMock
563446
cm.IO.On("SelectPrompt",
564447
mock.Anything,
565448
"Select the existing app team",
@@ -583,7 +466,7 @@ func Test_Apps_Link(t *testing.T) {
583466
mock.Anything,
584467
).Return(iostreams.SelectPromptResponse{
585468
Flag: true,
586-
Option: "deployed",
469+
Option: "local",
587470
}, nil)
588471
cm.API.On(
589472
"GetAppStatus",
@@ -600,42 +483,33 @@ func Test_Apps_Link(t *testing.T) {
600483
TeamDomain: mockLinkSlackAuth1.TeamDomain,
601484
TeamID: mockLinkSlackAuth1.TeamID,
602485
EnterpriseID: mockLinkSlackAuth1.EnterpriseID,
486+
UserID: mockLinkSlackAuth1.UserID,
487+
IsDev: true,
603488
}
604-
actualApp, err := cm.AppClient.GetDeployed(
489+
actualApp, err := cm.AppClient.GetLocal(
605490
ctx,
606491
mockLinkSlackAuth1.TeamID,
607492
)
608493
require.NoError(t, err)
609494
assert.Equal(t, expectedApp, actualApp)
610-
// Assert manifest confirmation prompt was not displayed
611-
cm.IO.AssertNotCalled(t, "ConfirmPrompt",
612-
mock.Anything,
613-
LinkAppManifestSourceConfirmPromptText,
614-
mock.Anything,
615-
)
495+
// Assert manifest source info is displayed
496+
output := cm.GetCombinedOutput()
497+
assert.Contains(t, output, "App Manifest")
498+
assert.Contains(t, output, `"project" (local)`)
616499
},
617500
},
618-
"manifest source prompt should display for GBP apps with local manifest source": {
501+
"links app when manifest source is remote": {
619502
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
620503
cm.Auth.On("Auths", mock.Anything).Return([]types.SlackAuth{
621-
mockLinkSlackAuth1,
622504
mockLinkSlackAuth2,
505+
mockLinkSlackAuth1,
623506
}, nil)
624507
cm.AddDefaultMocks()
625508
setupAppLinkCommandMocks(t, ctx, cm, cf)
626-
// Set manifest source to local
627-
if err := config.SetManifestSource(ctx, cm.Fs, cm.Os, config.ManifestSourceLocal); err != nil {
509+
// Set manifest source to remote
510+
if err := config.SetManifestSource(ctx, cm.Fs, cm.Os, config.ManifestSourceRemote); err != nil {
628511
require.FailNow(t, fmt.Sprintf("Failed to set the manifest source in the memory-based file system: %s", err))
629512
}
630-
// Mock manifest for Run-on-Slack app
631-
manifestMock := &app.ManifestMockObject{}
632-
manifestMock.On("GetManifestLocal", mock.Anything, mock.Anything, mock.Anything).Return(types.SlackYaml{}, nil)
633-
cf.AppClient().Manifest = manifestMock
634-
cm.IO.On("ConfirmPrompt",
635-
mock.Anything,
636-
LinkAppManifestSourceConfirmPromptText,
637-
mock.Anything,
638-
).Return(true, nil)
639513
cm.IO.On("SelectPrompt",
640514
mock.Anything,
641515
"Select the existing app team",
@@ -659,7 +533,7 @@ func Test_Apps_Link(t *testing.T) {
659533
mock.Anything,
660534
).Return(iostreams.SelectPromptResponse{
661535
Flag: true,
662-
Option: "deployed",
536+
Option: "local",
663537
}, nil)
664538
cm.API.On(
665539
"GetAppStatus",
@@ -676,19 +550,19 @@ func Test_Apps_Link(t *testing.T) {
676550
TeamDomain: mockLinkSlackAuth1.TeamDomain,
677551
TeamID: mockLinkSlackAuth1.TeamID,
678552
EnterpriseID: mockLinkSlackAuth1.EnterpriseID,
553+
UserID: mockLinkSlackAuth1.UserID,
554+
IsDev: true,
679555
}
680-
actualApp, err := cm.AppClient.GetDeployed(
556+
actualApp, err := cm.AppClient.GetLocal(
681557
ctx,
682558
mockLinkSlackAuth1.TeamID,
683559
)
684560
require.NoError(t, err)
685561
assert.Equal(t, expectedApp, actualApp)
686-
// Assert manifest confirmation prompt was displayed
687-
cm.IO.AssertCalled(t, "ConfirmPrompt",
688-
mock.Anything,
689-
LinkAppManifestSourceConfirmPromptText,
690-
mock.Anything,
691-
)
562+
// Assert manifest source info is displayed
563+
output := cm.GetCombinedOutput()
564+
assert.Contains(t, output, "App Manifest")
565+
assert.Contains(t, output, `"app settings" (remote)`)
692566
},
693567
},
694568
}, func(clients *shared.ClientFactory) *cobra.Command {

cmd/project/init_test.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,8 @@ func Test_Project_InitCommand(t *testing.T) {
108108
}, nil)
109109
// Default setup
110110
setupProjectInitCommandMocks(t, ctx, cm, cf)
111-
// Do not link an existing app
111+
// Link an existing app
112112
cm.IO.On("ConfirmPrompt", mock.Anything, app.LinkAppConfirmPromptText, mock.Anything).Return(true, nil)
113-
// Mock prompt to link an existing app
114-
cm.IO.On("ConfirmPrompt", mock.Anything, app.LinkAppManifestSourceConfirmPromptText, mock.Anything).Return(true, nil)
115113
// Mock prompt for team
116114
cm.IO.On("SelectPrompt",
117115
mock.Anything,

internal/pkg/apps/install.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,7 @@ func shouldUpdateManifest(ctx context.Context, clients *shared.ClientFactory, ap
752752
case saved.Equals(""):
753753
notice = "Manifest values for this app are overwritten on reinstall"
754754
default:
755-
notice = "The manifest on app settings has been changed since last update!"
755+
notice = style.Yellow("The manifest on app settings has been changed since last update")
756756
}
757757
clients.IO.PrintInfo(ctx, false, "\n%s", style.Sectionf(style.TextSection{
758758
Emoji: "books",
@@ -764,10 +764,7 @@ func shouldUpdateManifest(ctx context.Context, clients *shared.ClientFactory, ap
764764
}
765765
continues, err := clients.IO.ConfirmPrompt(
766766
ctx,
767-
fmt.Sprintf(
768-
"Update app settings with changes to the %s manifest?",
769-
config.ManifestSourceLocal.String(),
770-
),
767+
"Overwrite manifest on app settings with the project's manifest file?",
771768
false,
772769
)
773770
if err != nil {

internal/pkg/apps/install_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,7 @@ func TestInstall(t *testing.T) {
574574
clientsMock.IO.On(
575575
"ConfirmPrompt",
576576
mock.Anything,
577-
"Update app settings with changes to the local manifest?",
577+
"Overwrite manifest on app settings with the project's manifest file?",
578578
false,
579579
).Return(
580580
tc.mockConfirmPrompt,
@@ -1396,7 +1396,7 @@ func TestInstallLocalApp(t *testing.T) {
13961396
clientsMock.IO.On(
13971397
"ConfirmPrompt",
13981398
mock.Anything,
1399-
"Update app settings with changes to the local manifest?",
1399+
"Overwrite manifest on app settings with the project's manifest file?",
14001400
false,
14011401
).Return(
14021402
tc.mockConfirmPrompt,

0 commit comments

Comments
 (0)