feat(cli): structured merge for package.json/.env.example in bfs update#313
Conversation
Implements the deferred follow-up in scaffold-upgrade's isStructuredMergeFile: instead of always routing package.json and .env.example to manual review, `bfs update` now folds template-side changes into the user's file, reusing stack-update's mergePackageJson / mergeEnvExample (now exported). - bts.lock.json gains optional `baselines`: the pure-template render content of package.json / *.env.example, recorded at create (CLI + MCP), by `update --record-baseline`, and advanced on every reconciling apply (scaffold upgrade + stack update). - Merge semantics match stack-update: template additions/changes to deps, scripts, and env keys are applied when the user (or create-time post-processing) didn't touch the key; user-changed keys always win — if the template also changed one, the whole file becomes a conflict naming the blocked keys. Manifests without baselines fall back to manual review. - Lockfiles and .env (secrets) stay conservative: manual review, never patched. - New "merged" plan category is actionable, reported by `update`, and written on --apply with hashes + baselines refreshed. - fix(template-generator): later processCatalogs passes now resolve existing "catalog:" references when counting duplicates, so graph-mode db-package deps get catalogued like solo mode; previously create/update renders disagreed and a fresh project reported spurious package.json changes.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4c60e77cda
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…hanges, env baseline Addresses three review findings on the bfs update structured merge: - diffJsonSection: a user-side deletion of a key the baseline had now blocks the key like any other user edit when the template also changed it, instead of silently re-adding it (applies to stack-update merges too). - package.json: template changes mergePackageJson cannot express — section key removals and changed top-level fields (exports, workspaces, ...) — route the file to manual review naming the changes, instead of being silently dropped under a user-edited label or partially merged. Comparison is key-order insensitive so legitimate render reordering does not false-positive. - *.env.example without a recorded content baseline now falls back to manual review like package.json; merging with an undefined previous side would mistake every proposed key for a template addition and re-append keys the user deliberately removed.
…/Marve10s/Better-Fullstack into feat/bfs-update-structured-merge
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bed781ed11
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| await refreshScaffoldManifestFiles( | ||
| plan.projectDir, | ||
| plan.operations.map((operation) => operation.path), | ||
| collectStructuredBaselines(proposedTree), |
There was a problem hiding this comment.
Don't advance baselines for untouched structured files
When a stack update changes a generated package.json in a way mergePackageJson does not write (for example removing a frontend so the generated root package drops scripts.dev:web, or changing top-level fields), the plan produces no operation for that file and leaves the on-disk package untouched. This call still records proposed structured baselines for every package.json / *.env.example, so the scaffold baseline moves past an unapplied template change; subsequent bfs update treats the stale disk content as a local edit instead of surfacing the pending template change for manual review. Limit the baseline update to structured files that were actually written or proven unchanged.
Useful? React with 👍 / 👎.
| } | ||
|
|
||
| // *.env.example: append template-added keys; existing keys are never touched. | ||
| const merged = mergeEnvExample(existingContent, baselineContent, proposedContent); |
There was a problem hiding this comment.
Surface unmerged env.example key changes
When a template changes or removes an existing key in *.env.example and the user's file still matches the recorded baseline for that key, mergeEnvExample ignores it because it only appends keys that were absent from the baseline. This path then reports the file as merged/user-edited (and --apply can advance the content baseline), so the template-side env change is never surfaced for manual review unlike the previous conservative handling.
Useful? React with 👍 / 👎.
| } | ||
| for (const key of new Set([...Object.keys(previous), ...Object.keys(proposed)])) { | ||
| if (mergedSections.has(key)) continue; | ||
| if (!deepEqualUnordered(previous[key], proposed[key])) changes.push(key); |
There was a problem hiding this comment.
Ignore generated packageManager bumps during package merges
When the bundled virtual package-manager version changes, this top-level scan reports packageManager as an unmergeable template change, even though the create path rewrites that field to the user's local package-manager version after generation. In that scenario otherwise mergeable dependency/script updates in every existing package.json fall back to manual review for a value the user should not apply, so structured package merges stop working until the baseline is hand-adjusted.
Useful? React with 👍 / 👎.
Summary
Closes the deferred TODO in
scaffold-upgrade.ts:package.jsonand*.env.exampleare no longer always routed to manual review duringbfs update— they get a 3-way structured merge reusing stack-update's existingmergePackageJson/mergeEnvExample(exported, not forked).conflictentry naming the blocked keys (stack-update's blocker semantics).env(secrets) stay conservative — never auto-patchedbts.lock.jsonnow records optionalbaselines(pure-template render of structured files) at create time, viaupdate --record-baseline, and advances them on apply; old hash-only manifests gracefully fall back to manual reviewBonus root-cause fix
processCatalogsskipped deps whose peers were alreadycatalog:, so graph-mode's repeated post-process passes left db-package deps at literal versions — every fresh graph project would have reported a spuriouspackage.jsonmerge. Later passes now resolve existing catalog references when counting duplicates.Verification
.envconservative), stack-update 68/68, template-generator 140/140, fullbun run test:releasegreenactionable: []); simulated older baseline + user edits →update --applyproduced the union (template dep+script restored, user dep+script kept); re-plan stableGenerated with Claude Code (Fable 5 implementation via background agent)