Skip to content

feat: add two-way manifest sync between project and app settings#543

Draft
mwbrooks wants to merge 15 commits into
mainfrom
mwbrooks-2-way-manifest-sync
Draft

feat: add two-way manifest sync between project and app settings#543
mwbrooks wants to merge 15 commits into
mainfrom
mwbrooks-2-way-manifest-sync

Conversation

@mwbrooks

Copy link
Copy Markdown
Member

Changelog

Added slack manifest sync command (and slack sync alias) that detects per-field differences between the local manifest.json and remote app settings, prompts users to resolve conflicts, and writes the merged result to both sides. Gated behind --experiment=manifest-sync.

Summary

This pull request adds two-way manifest sync so developers can resolve differences between their local manifest.json and the remote app settings on api.slack.com.

Today, when manifest.source=local, the local manifest silently overwrites the API on next install — discarding changes made on the web. This feature replaces the binary "Overwrite?" prompt with a per-field resolution flow when divergence is detected.

Key additions:

  • internal/pkg/manifest/ — flatten, diff, merge, display, writeback, and sync orchestrator
  • cmd/manifest/sync.go — standalone slack manifest sync command
  • slack sync alias in cmd/root.go
  • manifest-sync experiment flag for gated rollout
  • Integration into shouldUpdateManifest() in the install flow

Preview

📚 App Manifest
   Found 2 difference(s) between project and app settings

  display_information.description
    Project:      "A cool app for teams"
    App settings: "A cool app for collaboration"

  functions.new_func (only in project)
    Value: {"title":"New Function","description":"Greets"}

? How would you like to resolve these differences?
  > Use all project values
    Use all app settings values
    Choose for each difference

  Syncing manifest...
  ✓ Updated app settings
  ✓ Updated manifest.json

📚 App Manifest
   Finished manifest sync for "My App"

Testing

  1. Build the CLI: make build
  2. In a project with manifest.json, install an app: ./bin/slack install -e manifest-sync
  3. No differences: Run ./bin/slack manifest sync -e manifest-sync — should report "in sync"
  4. Remote change: Edit the manifest on https://app.slack.com/app-settings, then run ./bin/slack manifest sync -e manifest-sync — should show the difference, choose "Use all app settings values", verify manifest.json updated
  5. Local change: Edit manifest.json, run ./bin/slack manifest sync -e manifest-sync — choose "Use all project values", verify app settings updated
  6. Both sides changed: Edit manifest.json and app settings (different fields), run sync, choose "Choose for each difference" — verify per-field resolution works and both sides reflect the merged result
  7. Key order preserved: After sync, git diff manifest.json should only show value changes, not key reordering
  8. Triggered during install: Make a remote change, run ./bin/slack install -e manifest-sync — sync flow triggers instead of old "Overwrite?" prompt
  9. Force flag: Make a remote change, run ./bin/slack install -e manifest-sync --force — old behavior, local overwrites remote
  10. Without experiment: Run ./bin/slack install with a remote change — old "Overwrite?" prompt (unchanged behavior)

Notes

  • The set-manifest hook for code-generated manifests (e.g., manifest.ts) is not included in this initial implementation. For those projects, the merged manifest is pushed to the API only, with a warning that the local project was not updated.
  • Uses a two-way (local vs remote) comparison model rather than three-way merge. This avoids stale cached base issues in multi-developer scenarios.
  • The --force-remote flag (remote overwrites local) is a follow-up item.

Requirements

When the manifest-sync experiment is enabled, the CLI detects per-field
differences between the local manifest.json and the remote app settings,
lets the user resolve them interactively, and writes the merged result to
both sides. This replaces the binary "overwrite?" prompt when divergence
is detected during install/deploy.

Adds `slack manifest sync` command and `slack sync` alias.
Gated behind --experiment=manifest-sync.
@codecov

codecov Bot commented May 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 46.69339% with 266 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.05%. Comparing base (727c6c6) to head (22e3a29).

Files with missing lines Patch % Lines
internal/manifest/display.go 0.00% 106 Missing ⚠️
internal/manifest/sync.go 0.00% 82 Missing ⚠️
internal/manifest/writeback.go 62.10% 19 Missing and 17 partials ⚠️
internal/manifest/merge.go 88.88% 6 Missing and 5 partials ⚠️
cmd/manifest/sync.go 65.51% 10 Missing ⚠️
internal/manifest/diff.go 80.00% 5 Missing and 5 partials ⚠️
internal/pkg/apps/install.go 0.00% 4 Missing and 3 partials ⚠️
internal/manifest/flatten.go 86.66% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #543      +/-   ##
==========================================
- Coverage   71.66%   71.05%   -0.62%     
==========================================
  Files         226      233       +7     
  Lines       19176    19673     +497     
==========================================
+ Hits        13743    13978     +235     
- Misses       4222     4454     +232     
- Partials     1211     1241      +30     

☔ 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 11 commits June 11, 2026 15:29
Manifest sync would write to manifest.json regardless of the project's
configured manifest source. Projects with ManifestSourceRemote treat
app settings as the source of truth, so writing locally creates
divergence. Refuse early with a clear remediation pointing at
slack app settings.
Hides the slack manifest sync subcommand and the slack sync alias
from --help, and refuses execution unless the manifest-sync experiment
is enabled. Adds ErrExperimentRequired so the user gets a clear
remediation pointing at the --experiment flag.
setNestedValue used an unchecked type assertion that panicked when
flattened paths disagreed about whether a node is a scalar or an
object (e.g. "a.b" alongside "a.b.c"). Replace the assertion with
the comma-ok form and propagate ErrInvalidManifest. Also catch the
mirror case where a leaf would silently overwrite an existing
nested object.
afero.WriteFile truncates the destination before writing, so an
interrupted write could leave manifest.json empty or half-written
right after the API had already been updated. Switch to a sibling
temp file plus rename so the destination is only ever the old
contents or the new contents.
After Sync's UpdateApp succeeds, the cached manifest hash still
reflected the pre-sync upstream, so a follow-up install or deploy
would treat the merged result as drift and prompt the user to sync
again. Persist the new hash right after the API push so the cache
matches the current upstream state.
The non-TTY remediation pointed users at --force, but the flag was
ignored. --force is a global persistent flag, so the message was
quietly misleading. Treat --force as MergeAllLocal so CI flows can
push their project manifest to app settings non-interactively.
marshalPreservingOrder discarded errors from json.Marshal and
json.MarshalIndent with _, so a marshal failure on a malformed
key or value would write malformed JSON like '"  : "' to disk and
return success. Propagate the errors so the user sees a real
failure and the file is not overwritten.
marshalPreservingOrder silently fell back to default key ordering
when the original file could not be parsed (BOM, trailing comma,
malformed JSON), so users had no idea their carefully-ordered
manifest had been reshuffled. Return a fellBack signal and surface
it on WriteBackResult.Warning so the user is informed.
…nequality

valuesEqual returned false when json.Marshal failed, so equal values
with a marshal hiccup got flagged as a phantom diff that the user
was then prompted to resolve. Surface the error up through Diff so
the caller sees a real failure instead of fabricated differences.
Manifest function and workflow IDs may contain literal dots (e.g.
"slack.users.lookup"). The original flatten/splitPath used dot as
both delimiter and a valid key character, so a key like that round-
tripped to a 4-level nested map and silently corrupted the merge.
Backslash-escape dots and backslashes in path segments and honor
the escape in splitPath.
@mwbrooks mwbrooks self-assigned this Jun 12, 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 area:deno-sdk Related to github.com/slackapi/deno-slack-sdk area:bolt-js Related to github.com/slackapi/bolt-js area:bolt-python Related to github.com/slackapi/bolt-python labels Jun 12, 2026
mwbrooks added 3 commits June 12, 2026 13:20
Required by the license_check workflow.
internal/pkg is a generic bucket the project is migrating away from.
Move the existing internal/pkg/manifest package (validate plus the
new sync/diff/merge/flatten/writeback/display logic) up to
internal/manifest and update its three importers.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:bolt-js Related to github.com/slackapi/bolt-js area:bolt-python Related to github.com/slackapi/bolt-python area:deno-sdk Related to github.com/slackapi/deno-slack-sdk 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