Skip to content

feat: add slack manifest diff command#591

Draft
mwbrooks wants to merge 16 commits into
mainfrom
mwbrooks-manifest-diff
Draft

feat: add slack manifest diff command#591
mwbrooks wants to merge 16 commits into
mainfrom
mwbrooks-manifest-diff

Conversation

@mwbrooks

@mwbrooks mwbrooks commented Jun 13, 2026

Copy link
Copy Markdown
Member

Changelog

Added the slack manifest diff command, which prints differences between the project manifest and the app settings.

Summary

This pull request adds a read-only slack manifest diff command that compares the project manifest against the app settings on Slack and prints any differences.

  • Useful for CI/CD guardrails, pre-check before deploys, and debugging manifest conflicts.
  • Read-only - no API mutations, no file writes.
  • 91.5% test coverage of internal/manifest.
  • Carved out from PR feat: add two-way manifest sync between project and app settings #543 to make that PR easier to review. The two-way slack manifest sync command will build on these primitives.
  • Ships without an experiment gate because it's a stand-alone read-only command. Happy to gate it, if reviewers want.
  • Exits 0 even when differences are found, to match slack manifest validate (which exits 0 even when the manifest is invalid). Standardizing exit-code semantics for scripting use-cases is a separate, project-wide conversation that should change validate and diff together.

Preview

2026-06-15-manifest-diff.mov

Testing

Testing outside of a project will error:

$ ./bin/slack manifest diff

🚫 This is an invalid Slack app project directory (invalid_app_directory)

Setup a new a project:

$ ./bin/slack create my-app --template slack-samples/bolt-js-starter-template
$ cd my-app/

Testing inside of a project with no App IDs will error:

$ ../bin/slack manifest diff

🚫 A valid installation of this app is required to take this action (installation_required)

Setup a new App ID for testing:

$ ../bin/slack install -E local

Testing a new install should return no differences:

$ ../bin/slack manifest diff

📚 App Manifest
   Project manifest and app settings match

Testing different values for project and app settings:

$ vim manifest.json
# Set "features.bot_user.always_online" to false

$ ../bin/slack manifest diff

📚 App Manifest
   Found 1 difference between project and app settings

  features.bot_user.always_online
    Project:      false
    App settings: true

Testing project missing a value:

$ vim manifest.json
# Delete the line `"home_tab_enabled": true,`

$ ../bin/slack manifest diff

📚 App Manifest
   Found 2 differences between project and app settings

  features.app_home.home_tab_enabled
    Project:      (not set)
    App settings: true

  features.bot_user.always_online
    Project:      false
    App settings: true

Testing app settings missing a value:

$ vim manifest.json
# Add the following line to the top-level of the manifest object:
# 
# "outgoing_domains": ["example.com"],

$ ../bin/slack manifest diff

📚 App Manifest
   Found 3 differences between project and app settings

  features.app_home.home_tab_enabled
    Project:      (not set)
    App settings: true

  features.bot_user.always_online
    Project:      false
    App settings: true

  outgoing_domains
    Project:      ["example.com"]
    App settings: (not set)

Clean up:

$ lack delete -f
$ cd ../
$ rm -rf my-app/

Requirements

Compares the project manifest against app settings and prints any
differences. Read-only — no API mutations, no file writes, no prompts.
Useful for CI guardrails, pre-flight visibility before deploy, and
debugging manifest drift.

Carved out from the larger two-way manifest sync work (#543) so the
diff capability can land independently with a smaller diff.
@mwbrooks mwbrooks self-assigned this Jun 13, 2026
@mwbrooks mwbrooks added enhancement M-T: A feature request for new functionality semver:minor Use on pull requests to describe the release version increment labels Jun 13, 2026
@mwbrooks mwbrooks added this to the Next Release milestone Jun 13, 2026
@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.23853% with 30 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.86%. Comparing base (d7dfda7) to head (2093f53).

Files with missing lines Patch % Lines
internal/manifest/diff.go 83.50% 8 Missing and 8 partials ⚠️
cmd/manifest/diff.go 79.48% 4 Missing and 4 partials ⚠️
internal/manifest/flatten.go 86.66% 2 Missing and 2 partials ⚠️
internal/manifest/diff_display.go 96.07% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #591      +/-   ##
==========================================
+ Coverage   71.67%   71.86%   +0.18%     
==========================================
  Files         226      230       +4     
  Lines       19176    19394     +218     
==========================================
+ Hits        13744    13937     +193     
- Misses       4221     4233      +12     
- Partials     1211     1224      +13     

☔ View full report in Codecov by Harness.
📢 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.

mwbrooks added 15 commits June 13, 2026 16:25
Shows the non-interactive form using --app and --token, matching the
intended CI guardrail use case.
Replaces "Detect manifest drift in CI" with "Show manifest differences
without prompts" so the language is plain and accessible to developers
who haven't encountered drift terminology before.
Documents flattenRecursive, diffFlat, valuesEqual, and formatValue
to match the project's existing convention (e.g. cmd/manifest/info.go
documents both exported and unexported helpers).
DisplayDiffs printed a leading blank inside the loop, which combined
with the trailing newline from style.Sectionf to produce a double
blank between the header and the first difference. Skip the blank on
the first iteration so spacing only appears between entries.
apps.manifest.export does not echo _metadata back, so projects that
declared it (e.g. SDK schema annotations) saw a noisy "(only in
project)" entry on every diff run. Apply a small ignored-paths
filter inside Diff so these never reach the user, and leave the
list extensible for any future one-sided fields.
formatValue used a byte-based slice (s[:77]) for the truncation,
which could cut a multi-byte UTF-8 character in half and produce an
invalid string in the middle of a localized field value. Switch to a
rune-based truncate helper and cover it with a test that includes
multi-byte input.
The Test_Diff and Test_Flatten loops previously only verified that
expected entries were present, so a regression that produced extra
unrelated entries would not have failed. Add a Len assertion before
the per-entry checks. Also update the function-related cases to
account for ManifestFunction.InputParameters and OutputParameters
(which lack the omitempty tag and therefore always serialize as null).
Slack's apps.manifest.export appends " (local)" to
display_information.name and features.bot_user.display_name when
returning the manifest of a dev-installed app. Fresh installs would
otherwise show two phantom diffs immediately. Suppress these
specifically when removing the suffix would make the values equal,
so genuine renames still surface.
apps.manifest.export emits settings.is_mcp_enabled: false for every
app even when the project never declared the field, so users would
otherwise see a phantom "(only in app settings)" entry on every run.
Suppress it specifically when the value is false; if a user sets it
to true locally, the resulting Modified diff still surfaces.
"In sync" reads as version-control jargon and is awkward when paired
with the diff command's read-only contract. Use plainer language
that any developer can parse without translation.
Local-only and remote-only fields previously printed in a different
shape than modified fields, forcing the reader to mentally translate
"only in app settings" into "Project doesn't have it." Render every
diff with the same Project/App settings layout and use "(not set)"
on the absent side. The header still announces the count and the
direction is conveyed by which side reads "(not set)".
Replaces "Found N difference(s)" with the proper singular/plural form
via the existing style.Pluralize helper.
Adds focused test cases for the remaining uncovered branches:
DisplayDiffs's empty-result early return and multi-diff sort path,
formatValue's nil and long-value paths, and truncateRunes's
short-max early return. The json.Marshal error path inside
formatValue stays uncovered because reproducing it requires a value
that the marshaller cannot encode (channels, funcs); the contortion
is not worth the line of coverage.
The bare "display" name was ambiguous now that the package sits
alongside cmd/manifest/info.go, which also "displays" a manifest.
Prefix the file with the command it belongs to so a reader
navigating the package can place the file at a glance.
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.

1 participant