Skip to content

feat: add --app flag support to slack create for linking existing apps#565

Open
srtaalej wants to merge 1 commit into
mainfrom
ale-app-id-flag
Open

feat: add --app flag support to slack create for linking existing apps#565
srtaalej wants to merge 1 commit into
mainfrom
ale-app-id-flag

Conversation

@srtaalej
Copy link
Copy Markdown
Contributor

Changelog

slack create now accepts the --app flag alongside --template to scaffold a project and automatically link it to an existing app by fetching its manifest from App Settings.

Summary

This PR adds --app [ID] support to slack create. When used with --template, the CLI will:

  1. Resolve an authenticated workspace that has access to the specified app
  2. Fetch the app's manifest from the platform (apps.manifest.export)
  3. Scaffold the project from the template (existing behavior)
  4. Write the remote manifest to the project's manifest.json
  5. Link the app to the project (defaults to local/dev unless the manifest uses slack-hosted runtime)

The --name flag takes precedence over the remote manifest's display name when both are provided.
Using --app without --template returns an error with guidance.

Example:

slack create my-project -t slack-samples/bolt-js-starter-template --app A0123456789

Preview

  📂 Project Create
     Cloning template slack-samples/bolt-js-starter-template
     To path ~/programming/slack-cli/my-project

  📦 Project Dependencies
     Added my-project/.slack
     Added my-project/.slack/.gitignore
     Added my-project/.slack/config.json
     Added my-project/.slack/hooks.json
     Updated app manifest source to "project" (local)
     Added package @slack/cli-hooks@1.3.2
     Installed dependencies using npm install

  📋 Next Steps
     Learn more about the project in the README.md
     Change into your project with cd my-project/
     Start developing and see changes in real-time with slack run

Testing

make test testdir=cmd/project testname=TestCreateCommand_AppFlag  # — 8 integration tests
make test testdir=cmd/project testname=Test_resolveAuthForApp  # — auth resolution unit tests
make test testdir=cmd/project testname=Test_writeManifestToProject  # — manifest write unit test
make test testdir=cmd/project testname=Test_linkAppToProject   # — app linking unit tests

Manual verification:

  1. ./bin/slack create my-project -t slack-samples/bolt-js-starter-template --app <real-app-id>
  2. Confirmed .slack/apps.dev.json contains linked app with correct team/app IDs
  3. Confirmed manifest.json matches remote app's manifest
  4. Confirmed --app without --template returns helpful error
  5. Confirmed --name overrides manifest display name when provided

Notes

  • Follow-up: manifest merging — Currently the remote manifest fully overwrites the template's
    manifest.json. A future PR will implement git-style merging where template and remote manifests
    are combined.

Requirements

@srtaalej srtaalej self-assigned this May 28, 2026
@srtaalej srtaalej added enhancement M-T: A feature request for new functionality semver:minor Use on pull requests to describe the release version increment labels May 28, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 28, 2026

Codecov Report

❌ Patch coverage is 65.88235% with 29 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.63%. Comparing base (c693443) to head (0d8bfd3).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
cmd/project/create_app.go 61.11% 16 Missing and 5 partials ⚠️
cmd/project/create.go 74.19% 4 Missing and 4 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #565      +/-   ##
==========================================
- Coverage   71.66%   71.63%   -0.04%     
==========================================
  Files         226      227       +1     
  Lines       19115    19200      +85     
==========================================
+ Hits        13699    13754      +55     
- Misses       4209     4230      +21     
- Partials     1207     1216       +9     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@srtaalej srtaalej marked this pull request as ready for review May 28, 2026 15:26
@srtaalej srtaalej requested a review from a team as a code owner May 28, 2026 15:26
Copy link
Copy Markdown
Member

@zimeg zimeg left a comment

Choose a reason for hiding this comment

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

@srtaalej Awesome changes going on here! 🎁

I'm leaving a handful of comments around refactoring logic into adjacent places. Hoping that we can compose commands overall and avoid adding too much to create for ongoing iteration.

Two notable changes to the experience that I'll call out here include:

  • Accepting the environment flag alongside a default
  • Skipping the name prompt when an existing app ID is provided

Quite excited for what this hopes to unlock 🔏

Comment thread cmd/project/create_app.go
Comment on lines +74 to +82
// fetchRemoteManifest retrieves the app manifest from the platform via apps.manifest.export.
func fetchRemoteManifest(ctx context.Context, clients *shared.ClientFactory, token string, appID string) (types.SlackYaml, error) {
manifest, err := clients.AppClient().Manifest.GetManifestRemote(ctx, token, appID)
if err != nil {
return types.SlackYaml{}, slackerror.New(slackerror.ErrInvalidManifest).
WithMessage("Failed to fetch manifest for app %s", appID)
}
return manifest, nil
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
// fetchRemoteManifest retrieves the app manifest from the platform via apps.manifest.export.
func fetchRemoteManifest(ctx context.Context, clients *shared.ClientFactory, token string, appID string) (types.SlackYaml, error) {
manifest, err := clients.AppClient().Manifest.GetManifestRemote(ctx, token, appID)
if err != nil {
return types.SlackYaml{}, slackerror.New(slackerror.ErrInvalidManifest).
WithMessage("Failed to fetch manifest for app %s", appID)
}
return manifest, nil
}

🪓 suggestion: Let's inline this! I think the error returned might sometimes be different from invalid manifest that we might want to surface

Comment thread cmd/project/create_app.go
Comment on lines +84 to +98
// writeManifestToProject writes the fetched manifest JSON to the project directory.
func writeManifestToProject(fs afero.Fs, projectPath string, manifest types.SlackYaml) error {
manifestData, err := json.MarshalIndent(manifest.AppManifest, "", " ")
if err != nil {
return slackerror.Wrap(err, slackerror.ErrProjectFileUpdate).
WithMessage("Failed to serialize app manifest")
}

manifestPath := filepath.Join(projectPath, "manifest.json")
if err := afero.WriteFile(fs, manifestPath, append(manifestData, '\n'), 0644); err != nil {
return slackerror.Wrap(err, slackerror.ErrProjectFileUpdate).
WithMessage("Failed to write manifest to project")
}
return nil
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🛻 suggestion: Let's move this logic to internal/app/manifest for adjacent changes of #543

Comment thread cmd/project/create.go
Comment on lines +213 to +235
if appFlagProvided {
absProjectPath, err := filepath.Abs(appDirPath)
if err != nil {
return slackerror.Wrap(err, slackerror.ErrAppDirectoryAccess)
}
if nameFlagProvided {
remoteManifest.DisplayInformation.Name = displayName
}
if err := writeManifestToProject(clients.Fs, absProjectPath, remoteManifest); err != nil {
return err
}
// linkAppToProject requires the working directory to be the project
// because SaveDeployed/SaveLocal use os.Getwd() to find .slack/
originalDir, _ := clients.Os.Getwd()
if err := os.Chdir(absProjectPath); err != nil {
return slackerror.Wrap(err, slackerror.ErrAppDirectoryAccess)
}
linkErr := linkAppToProject(ctx, clients, appAuth, clients.Config.AppFlag, remoteManifest)
_ = os.Chdir(originalDir)
if linkErr != nil {
return linkErr
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🧊 suggestion: We might want to instead share existing logic of internal package here and add an AppID to the "CreateArgs" above? I'd keep the cmd portion limited to input and output perhaps-

// Change into the project directory to configure defaults and dependencies
// then return to the starting directory
if err = os.Chdir(projectDirPath); err != nil {
return "", slackerror.Wrap(err, slackerror.ErrAppDirectoryAccess)
}
defer func() {
_ = os.Chdir(workingDirPath)
}()
// Update default project files' app name, bot name, etc
if err := app.UpdateDefaultProjectFiles(clients.Fs, projectDirPath, appDirName, displayName); err != nil {
return "", slackerror.Wrap(err, slackerror.ErrProjectFileUpdate)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

you're right, this should def go in internal!

Comment thread cmd/project/create_app.go
Comment on lines +100 to +116
// linkAppToProject saves the app to the project's apps JSON file.
// Defaults to local/dev unless the manifest explicitly uses a hosted runtime.
func linkAppToProject(ctx context.Context, clients *shared.ClientFactory, auth types.SlackAuth, appID string, manifest types.SlackYaml) error {
app := types.App{
AppID: appID,
TeamID: auth.TeamID,
TeamDomain: auth.TeamDomain,
EnterpriseID: auth.EnterpriseID,
}

if manifest.IsFunctionRuntimeSlackHosted() {
return clients.AppClient().SaveDeployed(ctx, app)
}
app.IsDev = true
app.UserID = auth.UserID
return clients.AppClient().SaveLocal(ctx, app)
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🔭 question: Can we reuse logic of the app link command? We perhaps might change outputs but I'm hoping we move toward focused and atomic commands that perhaps compose!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

👾 issue: I'm concerned of the forced default local app here. The same CI example I share earlier is for a production app and I don't have immediate option to "deploy" the right app after using this:

$ slack create --app A0582JYKGB1 --template zimeg/slacks --branch snaek --name snaek --force

🌠 suggestion: We might want to use the --environment flag to decide this? I still think a default "local" makes sense - CI should be explicit!

Comment thread cmd/project/create.go
WithMessage("The --app flag requires the --template flag when used with create")
}

// Fail fast: resolve auth and fetch manifest before creating the project
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🪬 thought: Related to comments of logic moved to internal I'm curious if we can move this check too? I understand a mismatched app ID will cause error but I don't think we should prompt for name when the --app flag is used...

🐮 ramble: For example a minimal example seems excessive for CI use case:

$ slack create --app A0582JYKGB1 --template zimeg/slacks --branch snaek --name snaek --force

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

true! 🫡

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement M-T: A feature request for new functionality semver:minor Use on pull requests to describe the release version increment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants