Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7578b6e
feat(bolt-install): support using 'install' command with remote manif…
mwbrooks Jul 10, 2025
46be447
feat(bolt-install): support using 'install' command with remote manif…
mwbrooks Jul 11, 2025
7678bb4
feat(bolt-install): default --environment deployed when --team provid…
mwbrooks Jul 11, 2025
9847d3c
Merge branch 'main' into mwbrooks-install-remote-manifest
mwbrooks Jul 11, 2025
10966f7
feat(bolt-install): add PR number
mwbrooks Jul 11, 2025
fda7732
feat(bolt-install): add example of using the --environment flag
mwbrooks Jul 11, 2025
aa42052
feat(bolt-install): fix --environment flag support
mwbrooks Jul 11, 2025
9b04aa7
Merge branch 'main' into mwbrooks-install-remote-manifest
mwbrooks Jul 24, 2025
521ee30
fix: allow mixing-and-matching --team and --environment
mwbrooks Jul 25, 2025
886568f
fix: warning message
mwbrooks Jul 25, 2025
12d89f4
test: fix failing add_test.go
mwbrooks Jul 25, 2025
97fe3f0
fix: refactor code to be tighter
mwbrooks Jul 25, 2025
edbcaf9
fix: remove the UserID comment to avoid unexpected behaviour
mwbrooks Jul 25, 2025
976d285
Merge branch 'main' into mwbrooks-install-remote-manifest
mwbrooks Jul 25, 2025
d62e7c6
Update cmd/app/add.go
mwbrooks Jul 25, 2025
013ad0d
test: refactor mockLocalManifest to mockManifestAppLocal (and Remote)
mwbrooks Jul 25, 2025
1e2bd54
fix: install command to handle --app local and --app deployed
mwbrooks Aug 7, 2025
701a794
Merge branch 'main' into mwbrooks-install-remote-manifest
mwbrooks Aug 7, 2025
9e757b1
fix: install command skips environment prompt when --app is set
mwbrooks Aug 7, 2025
c868e1b
Merge branch 'main' into mwbrooks-install-remote-manifest
mwbrooks Aug 7, 2025
69e3284
Merge branch 'main' into mwbrooks-install-remote-manifest
mwbrooks Aug 8, 2025
0e65092
fix: skip defaulting to deployed environment when app id is provided
mwbrooks Aug 8, 2025
e28a02e
fix: skip defaulting to deployed environment when --team provided
mwbrooks Aug 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 19 additions & 21 deletions cmd/app/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,38 +85,36 @@ func preRunAddCommand(ctx context.Context, clients *shared.ClientFactory) error
if !clients.Config.WithExperimentOn(experiment.BoltFrameworks) {
return nil
}
manifestSource, err := clients.Config.ProjectConfig.GetManifestSource(ctx)
if err != nil {
return err
}
if manifestSource.Equals(config.ManifestSourceRemote) {
return slackerror.New(slackerror.ErrAppInstall).
WithMessage("Apps cannot be installed due to project configurations").
WithRemediation(
"Install an app on app settings: %s\nLink an app to this project with %s\nList apps saved with this project using %s",
style.LinkText("https://api.slack.com/apps"),
style.Commandf("app link", false),
style.Commandf("app list", false),
).
WithDetails(slackerror.ErrorDetails{
slackerror.ErrorDetail{
Code: slackerror.ErrProjectConfigManifestSource,
Message: "Cannot install apps with manifests sourced from app settings",
},
})
}
Comment on lines -88 to -107
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🪓 Removing this section that prevents the slack install command from executing on a remote manifest.

return nil
}

// RunAddCommand executes the workspace install command, prints output, and returns any errors.
func RunAddCommand(ctx context.Context, clients *shared.ClientFactory, selection *prompts.SelectedApp, orgGrantWorkspaceID string) (context.Context, types.InstallState, types.App, error) {
if selection == nil {
selected, err := teamAppSelectPromptFunc(ctx, clients, prompts.ShowHostedOnly, prompts.ShowAllApps)
// Prompt for deployed or local app environment.
isProductionApp, err := promptIsProduction(ctx, clients)
if err != nil {
return ctx, "", types.App{}, err
}

var appEnvironmentType prompts.AppEnvironmentType
if isProductionApp {
appEnvironmentType = prompts.ShowHostedOnly
} else {
appEnvironmentType = prompts.ShowLocalOnly
}

selected, err := teamAppSelectPromptFunc(ctx, clients, appEnvironmentType, prompts.ShowAllApps)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: New code that prompts the developer to choose an environment (local or deployed). Then displays the proper Team Prompt. We cannot prompts.ShowAllEnvironments because there is guard logic to avoid having 2 apps on one team. Unfortunately, the prompt isn't aware of the app's environment, so this was our workaround.

if err != nil {
return ctx, "", types.App{}, err
}
selection = &selected

if !isProductionApp {
selection.App.IsDev = true
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: Normally, we'd also update the UserID for dev apps, but it's already been set.

}

if selection.Auth.TeamDomain == "" {
return ctx, "", types.App{}, slackerror.New(slackerror.ErrCredentialsNotFound)
}
Expand Down
124 changes: 97 additions & 27 deletions cmd/app/add_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ import (
"github.com/slackapi/slack-cli/internal/cmdutil"
"github.com/slackapi/slack-cli/internal/config"
"github.com/slackapi/slack-cli/internal/experiment"
"github.com/slackapi/slack-cli/internal/iostreams"
"github.com/slackapi/slack-cli/internal/prompts"
"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/style"
"github.com/slackapi/slack-cli/test/testutil"
"github.com/spf13/cobra"
"github.com/stretchr/testify/mock"
Expand Down Expand Up @@ -85,40 +85,27 @@ func TestAppAddCommandPreRun(t *testing.T) {
cf.SDKConfig.WorkingDirectory = "."
},
},
"errors if manifest.source is remote with the bolt experiment": {
ExpectedError: slackerror.New(slackerror.ErrAppInstall).
WithMessage("Apps cannot be installed due to project configurations").
WithRemediation(
"Install an app on app settings: %s\nLink an app to this project with %s\nList apps saved with this project using %s",
style.LinkText("https://api.slack.com/apps"),
style.Commandf("app link", false),
style.Commandf("app list", false),
).
WithDetails(slackerror.ErrorDetails{
slackerror.ErrorDetail{
Code: slackerror.ErrProjectConfigManifestSource,
Message: "Cannot install apps with manifests sourced from app settings",
},
}),
Comment on lines -88 to -102
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🪓 We no longer want to error on a remote manifest, so this test is removed and replaced with proceeds if manifest.source is remote with the bolt experiment (below)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🪓 thought: Once the bolt experiment is removed altogether, we can perhaps remove even more cases!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes please!

"proceeds if manifest.source is local with the bolt experiment": {
ExpectedError: nil,
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
cf.SDKConfig.WorkingDirectory = "."
cm.AddDefaultMocks()
cm.Config.ExperimentsFlag = append(cm.Config.ExperimentsFlag, string(experiment.BoltFrameworks))
cm.Config.LoadExperiments(ctx, cm.IO.PrintDebug)
mockProjectConfig := config.NewProjectConfigMock()
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceRemote, nil)
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
cm.Config.ProjectConfig = mockProjectConfig
},
},
"proceeds if manifest.source is local with the bolt experiment": {
"proceeds if manifest.source is remote with the bolt experiment": {
ExpectedError: nil,
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
cf.SDKConfig.WorkingDirectory = "."
cm.AddDefaultMocks()
cm.Config.ExperimentsFlag = append(cm.Config.ExperimentsFlag, string(experiment.BoltFrameworks))
cm.Config.LoadExperiments(ctx, cm.IO.PrintDebug)
mockProjectConfig := config.NewProjectConfigMock()
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceRemote, nil)
cm.Config.ProjectConfig = mockProjectConfig
},
},
Expand All @@ -130,13 +117,84 @@ func TestAppAddCommandPreRun(t *testing.T) {
}

func TestAppAddCommand(t *testing.T) {

testutil.TableTestCommand(t, testutil.CommandTests{
"adds a new local app": {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: Added test for new local apps to complement the existing new deployed app test.

CmdArgs: []string{},
ExpectedOutputs: []string{"Creating app manifest", "Installing"},
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
prepareAddMocks(t, cf, cm, "local")

// Mock TeamSelector prompt to return "team1"
appSelectMock := prompts.NewAppSelectMock()
teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt
appSelectMock.On("TeamAppSelectPrompt").Return(prompts.SelectedApp{Auth: mockAuthTeam1}, nil)

// Mock valid session for team1
cm.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{
UserID: &mockAuthTeam1.UserID,
TeamID: &mockAuthTeam1.TeamID,
TeamName: &mockAuthTeam1.TeamDomain,
}, nil)

// Mock a clean ValidateAppManifest result
cm.API.On("ValidateAppManifest", mock.Anything, mockAuthTeam1.Token, mock.Anything, mock.Anything).Return(
api.ValidateAppManifestResult{
Warnings: slackerror.Warnings{},
}, nil,
)

// Mock Host
cm.API.On("Host").Return("")

// Mock a successful CreateApp call and return our mocked AppID
cm.API.On("CreateApp", mock.Anything, mockAuthTeam1.Token, mock.Anything, mock.Anything).Return(
api.CreateAppResult{
AppID: mockAppTeam1.AppID,
},
nil,
)

// Mock a successful DeveloperAppInstall
cm.API.On("DeveloperAppInstall", mock.Anything, mock.Anything, mockAuthTeam1.Token, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(
api.DeveloperAppInstallResult{
AppID: mockAppTeam1.AppID,
APIAccessTokens: struct {
Bot string "json:\"bot,omitempty\""
AppLevel string "json:\"app_level,omitempty\""
User string "json:\"user,omitempty\""
}{},
},
types.InstallSuccess,
nil,
)

// Mock existing and updated cache
cm.API.On(
"ExportAppManifest",
mock.Anything,
mock.Anything,
mock.Anything,
).Return(
api.ExportAppResult{},
nil,
)
mockProjectCache := cache.NewCacheMock()
mockProjectCache.On("GetManifestHash", mock.Anything, mock.Anything).
Return(cache.Hash(""), nil)
mockProjectCache.On("NewManifestHash", mock.Anything, mock.Anything).
Return(cache.Hash("xoxo"), nil)
mockProjectCache.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).
Return(nil)
mockProjectConfig := config.NewProjectConfigMock()
mockProjectConfig.On("Cache").Return(mockProjectCache)
cm.Config.ProjectConfig = mockProjectConfig
},
},
"adds a new deployed app": {
CmdArgs: []string{},
ExpectedOutputs: []string{"Creating app manifest", "Installing"},
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
prepareAddMocks(t, cf, cm)
prepareAddMocks(t, cf, cm, "deployed")

// Mock TeamSelector prompt to return "team1"
appSelectMock := prompts.NewAppSelectMock()
Expand Down Expand Up @@ -208,7 +266,7 @@ func TestAppAddCommand(t *testing.T) {
CmdArgs: []string{},
ExpectedOutputs: []string{"Updated app manifest"},
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
prepareAddMocks(t, cf, cm)
prepareAddMocks(t, cf, cm, "deployed")

// Mock TeamSelector prompt to return "team1"
appSelectMock := prompts.NewAppSelectMock()
Expand Down Expand Up @@ -290,7 +348,7 @@ func TestAppAddCommand(t *testing.T) {
CmdArgs: []string{},
ExpectedError: slackerror.New(slackerror.ErrCredentialsNotFound),
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
prepareAddMocks(t, cf, cm)
prepareAddMocks(t, cf, cm, "deployed")
appSelectMock := prompts.NewAppSelectMock()
teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt
appSelectMock.On("TeamAppSelectPrompt").Return(prompts.SelectedApp{App: mockAppTeam1}, nil)
Expand All @@ -300,7 +358,7 @@ func TestAppAddCommand(t *testing.T) {
CmdArgs: []string{"--" + cmdutil.OrgGrantWorkspaceFlag, "T123"},
ExpectedOutputs: []string{"Creating app manifest", "Installing"},
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
prepareAddMocks(t, cf, cm)
prepareAddMocks(t, cf, cm, "deployed")
// Select workspace
appSelectMock := prompts.NewAppSelectMock()
teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt
Expand Down Expand Up @@ -360,7 +418,7 @@ func TestAppAddCommand(t *testing.T) {
CmdArgs: []string{"--" + cmdutil.OrgGrantWorkspaceFlag, "T123"},
ExpectedOutputs: []string{"Creating app manifest", "Installing", "Your request to install the app is pending", "complete installation by re-running"},
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
prepareAddMocks(t, cf, cm)
prepareAddMocks(t, cf, cm, "deployed")
// Select workspace
appSelectMock := prompts.NewAppSelectMock()
teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt
Expand Down Expand Up @@ -416,7 +474,7 @@ func TestAppAddCommand(t *testing.T) {
CmdArgs: []string{"--" + cmdutil.OrgGrantWorkspaceFlag, "T123"},
ExpectedOutputs: []string{"Creating app manifest", "Installing", "Your request to install the app has been cancelled"},
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
prepareAddMocks(t, cf, cm)
prepareAddMocks(t, cf, cm, "deployed")
// Select workspace
appSelectMock := prompts.NewAppSelectMock()
teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt
Expand Down Expand Up @@ -475,7 +533,7 @@ func TestAppAddCommand(t *testing.T) {
})
}

func prepareAddMocks(t *testing.T, clients *shared.ClientFactory, clientsMock *shared.ClientsMock) {
func prepareAddMocks(t *testing.T, clients *shared.ClientFactory, clientsMock *shared.ClientsMock, appEnvironment string) {
clientsMock.AddDefaultMocks()

clientsMock.Auth.On("ResolveAPIHost", mock.Anything, mock.Anything, mock.Anything).
Expand All @@ -498,4 +556,16 @@ func prepareAddMocks(t *testing.T, clients *shared.ClientFactory, clientsMock *s
listPkgMock := new(ListPkgMock)
listFunc = listPkgMock.List
listPkgMock.On("List").Return(nil)

// Mock the prompt to select the app environment.
clientsMock.IO.On("SelectPrompt",
mock.Anything,
"Choose the app environment",
mock.Anything,
mock.Anything,
mock.Anything,
).Return(iostreams.SelectPromptResponse{
Flag: true,
Option: appEnvironment,
}, nil)
}
10 changes: 9 additions & 1 deletion cmd/platform/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,16 +163,24 @@ func hasValidDeploymentMethod(
return err
}
switch {
// When the manifest source is local, we can get the manifest from the local project.
case manifestSource.Equals(config.ManifestSourceLocal):
manifest, err = clients.AppClient().Manifest.GetManifestLocal(ctx, clients.SDKConfig, clients.HookExecutor)
if err != nil {
return err
}
case manifestSource.Equals(config.ManifestSourceRemote):
// When the manifest source is remote and the app exists, we can get the manifest from the the API.
case manifestSource.Equals(config.ManifestSourceRemote) && app.AppID != "":
Comment on lines +171 to +172
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: We need to differentiate between a remote manifest with an app (get manifest from app settings) and a remote manifest without an app (get manifest from local file).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👁️‍🗨️ thought: An app.Exists method might be nice here-

manifest, err = clients.AppClient().Manifest.GetManifestRemote(ctx, auth.Token, app.AppID)
if err != nil {
return err
}
// When the app does not exist, we need to get the manifest from the local project.
default:
manifest, err = clients.AppClient().Manifest.GetManifestLocal(ctx, clients.SDKConfig, clients.HookExecutor)
if err != nil {
return err
}
}
if manifest.FunctionRuntime() == types.SlackHosted {
return nil
Expand Down
22 changes: 21 additions & 1 deletion cmd/platform/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ func TestDeployCommand(t *testing.T) {

func TestDeployCommand_HasValidDeploymentMethod(t *testing.T) {
tests := map[string]struct {
app types.App
manifest types.SlackYaml
manifestError error
manifestSource config.ManifestSource
Expand Down Expand Up @@ -144,6 +145,20 @@ func TestDeployCommand_HasValidDeploymentMethod(t *testing.T) {
manifestSource: config.ManifestSourceLocal,
expectedError: slackerror.New(slackerror.ErrSDKHookNotFound),
},
"succeeds if the app exists and the manifest source is remote": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😉 thought: These are clever tests. I think it calls out the importance of error codes in assertions. But seems right here!

app: types.App{
AppID: "A123",
},
manifestSource: config.ManifestSourceRemote,
expectedError: slackerror.New(slackerror.ErrSDKHookNotFound),
},
"succeeds if the app does not exist and the manifest source is remote": {
app: types.App{
AppID: "",
},
manifestSource: config.ManifestSourceRemote,
expectedError: slackerror.New(slackerror.ErrSDKHookNotFound),
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
Expand All @@ -152,6 +167,7 @@ func TestDeployCommand_HasValidDeploymentMethod(t *testing.T) {
clients := shared.NewClientFactory(clientsMock.MockClientFactory(), func(clients *shared.ClientFactory) {
manifestMock := &app.ManifestMockObject{}
manifestMock.On("GetManifestLocal", mock.Anything, mock.Anything, mock.Anything).Return(tt.manifest, tt.manifestError)
manifestMock.On("GetManifestRemote", mock.Anything, mock.Anything, mock.Anything).Return(tt.manifest, tt.manifestError)
clients.AppClient().Manifest = manifestMock
projectConfigMock := config.NewProjectConfigMock()
projectConfigMock.On("GetManifestSource", mock.Anything).
Expand All @@ -160,7 +176,11 @@ func TestDeployCommand_HasValidDeploymentMethod(t *testing.T) {
clients.SDKConfig = hooks.NewSDKConfigMock()
clients.SDKConfig.Hooks.Deploy.Command = tt.deployScript
})
err := hasValidDeploymentMethod(ctx, clients, types.App{}, types.SlackAuth{})
app := types.App{}
if tt.app.AppID != "" {
app = tt.app
}
err := hasValidDeploymentMethod(ctx, clients, app, types.SlackAuth{})
if tt.expectedError != nil {
require.Error(t, err)
assert.Equal(
Expand Down
1 change: 0 additions & 1 deletion cmd/triggers/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ type createCmdFlags struct {

var createFlags createCmdFlags

// TODO(mcodik) figure out a way to mock this more nicely
var createAppSelectPromptFunc = prompts.AppSelectPrompt
var workspaceInstallAppFunc = app.RunAddCommand
var createPromptShouldRetryWithInteractivityFunc = promptShouldRetryCreateWithInteractivity
Expand Down
3 changes: 2 additions & 1 deletion docs/reference/experiments.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ The following is a list of currently available experiments. We'll remove experim

* `bolt-install`: enables creating, installing, and running Bolt projects that manage their app manifest on app settings (remote manifest).
* `slack create` and `slack init` now set manifest source to "app settings" (remote) for Bolt JS & Bolt Python projects ([PR#96](https://github.com/slackapi/slack-cli/pull/96)).
* `slack run` and `slack install` support creating and installing Bolt Framework apps that have the manifest source set to "app settings (remote)" ([PR#111](https://github.com/slackapi/slack-cli/pull/111), [PR#TODO](https://github.com/slackapi/slack-cli/pull/TODO)).
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: I added the slack run in PR #111 but it may have been removed by a rogue docs sync 🥷

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lukegalbraithrussell Don't sweat it! The docs sync has improved a lot since then, thank to you!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* `slack run` and `slack install` support creating and installing Bolt Framework apps that have the manifest source set to "app settings (remote)" ([PR#111](https://github.com/slackapi/slack-cli/pull/111), [PR#TODO](https://github.com/slackapi/slack-cli/pull/TODO)).
* `slack run` and `slack install` support creating and installing Bolt Framework apps that have the manifest source set to "app settings (remote)" ([PR#111](https://github.com/slackapi/slack-cli/pull/111), [PR#TODO](https://github.com/slackapi/slack-cli/pull/TODO)).

assuming this #TODO is still needed to be filled out?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whew! Thank you, @lukegalbraithrussell! Commit 10966f7 adds the PR number.

* `read-only-collaborators`: enables creating and modifying collaborator permissions via the `slack collaborator` commands.

## Experiments changelog

Below is a list of updates related to experiments.

* **June 2025**:
* **June 2025**:
* Updated the `slack run` command to create and install new and existing Bolt framework projects configured with app settings as the source of truth (remote manifest).
* Added support for creating, installing, and running Bolt projects that manage their app manifest on app settings (remote manifest). New Bolt projects are now configured to have apps managed by app settings rather than by project. When running a project for local development, the app and bot tokens are automatically set, and no longer require developers to export them as environment variables. Existing Bolt projects will continue to work with a project (local) manifest, and linking an app from app settings will configure the project to be managed by app settings (remote manifest). In an upcoming release, support for installing and deploying apps managed by app settings will be implemented.
* **May 2025**: Added the experiment `bolt-install` to enable creating, installing, and running Bolt projects that manage their app manifest on app settings (remote manifest).
Expand Down
Loading
Loading