feat: add slack manifest diff command#591
Draft
mwbrooks wants to merge 16 commits into
Draft
Conversation
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.
Codecov Report❌ Patch coverage is 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. 🚀 New features to boost your workflow:
|
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changelog
Summary
This pull request adds a read-only
slack manifest diffcommand that compares the project manifest against the app settings on Slack and prints any differences.internal/manifest.slack manifest synccommand will build on these primitives.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 changevalidateanddifftogether.Preview
2026-06-15-manifest-diff.mov
Testing
Testing outside of a project will error:
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:
Setup a new App ID for testing:
$ ../bin/slack install -E localTesting a new install should return no differences:
Testing different values for project and app settings:
Testing project missing a value:
Testing app settings missing a value:
Clean up:
$ lack delete -f $ cd ../ $ rm -rf my-app/Requirements