Make packkit upgrade --apply non-destructive by default - #33
Merged
Conversation
The upgrade planner shipped in 3.1 auto-applied changed dependency versions and changed scripts — so `--apply` could clobber a user's own customizations (a pinned typescript, a custom build script). Without a stored baseline Packkit can't tell a template change from a user edit, so the safe default must be conservative everywhere. Now `--apply` brings in additions only — new files, scripts, dependencies, and package fields — and preserves anything that already exists but differs. Replacing differing values is opt-in per category via a granular policy, surfaced on the CLI as --replace-files / --update-scripts / --update-deps, with --force meaning all of them. Also: - Dependency changes are keyed by section + name (added optionalDependencies), so `dependencies.react` and `peerDependencies.react` no longer collide. - The plan splits scripts, deps, and protected package fields (exports, bin, main, module, types, files, engines, packageManager) into added vs changed. - Protected fields are assigned whole on replace (no stale nested keys), and package.json is left untouched when there's nothing additive to apply. - DEFAULT_UPGRADE_POLICY + the granular policy are exported from the embedded API; the CLI --force is just a convenience over it, not the only control. Verified end to end: a project with a user-changed build script, a pinned typescript, and an edited file — `--apply` adds the missing files/deps and preserves all three; --update-deps replaces only the dep; --force replaces everything. 104 tests pass (new cases cover section distinctness, protected fields, and every preserve/replace path). README gains the safety table. First of several PRs from the upgrade-safety brief; baseline metadata, upgradeProject(), --json, and service contracts follow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
First PR from the upgrade-safety brief. Fixes a real safety bug in the
packkit upgradefeature (shipped 3.1):--applyauto-applied changed dependency versions and changed scripts, so it could overwrite a user's own customizations — a pinnedtypescript, acustom-company-buildscript. Without a stored baseline Packkit can't tell a template change from a user edit, so the default must be conservative.Behavior
--applynow brings in additions only — new files, scripts, dependencies, and package fields — and preserves anything that already exists but differs. Replacing differing values is opt-in, per category:--apply--replace-files/--force--update-scripts/--force--update-deps/--force--forceAlso
optionalDependencies) —dependencies.reactandpeerDependencies.reactno longer collide.exports,bin,main,module,types,files,engines,packageManager) into added vs changed. Protected fields are assigned whole on replace (no stale nested keys).package.jsonis left untouched when there's nothing additive to apply.DEFAULT_UPGRADE_POLICYand the granularUpgradeApplyPolicyare exported from the embedded API —--forceis a convenience over it, not the only control. The internal API is not limited to a global boolean.Verified (acceptance criteria)
A project with a user-changed
buildscript, a pinnedtypescript@5.8.0, and an edited file:upgrade --apply→ adds.editorconfig+vitest, preserves the build script, the typescript pin, and the edited README; lists everything preserved.--update-deps→ replaces only the dep, keeps the script.--force→ replaces everything.104 tests pass (new cases: section distinctness, protected fields, and every preserve/replace path). No core change, so the web bundle is untouched.
Follows in subsequent PRs: baseline metadata (three-way diff),
upgradeProject()embedded helper,--jsonoutput, partial-upgrade provenance, and formalized service deployment contracts.🤖 Generated with Claude Code