Skip to content

Make packkit upgrade --apply non-destructive by default - #33

Merged
DanMat merged 1 commit into
mainfrom
feat/upgrade-non-destructive
Aug 1, 2026
Merged

Make packkit upgrade --apply non-destructive by default#33
DanMat merged 1 commit into
mainfrom
feat/upgrade-non-destructive

Conversation

@DanMat

@DanMat DanMat commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

First PR from the upgrade-safety brief. Fixes a real safety bug in the packkit upgrade feature (shipped 3.1): --apply auto-applied changed dependency versions and changed scripts, so it could overwrite a user's own customizations — a pinned typescript, a custom-company-build script. Without a stored baseline Packkit can't tell a template change from a user edit, so the default must be conservative.

Behavior

--apply now 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:

Change Default --apply Explicit replacement
New file / script / dep / field Applied Applied
Changed file Preserved --replace-files / --force
Changed script Preserved --update-scripts / --force
Changed dependency Preserved --update-deps / --force
Changed package field Preserved --force
Removed template file Reported No automatic deletion

Also

  • Dependency changes keyed by section + name (added optionalDependencies) — dependencies.react and peerDependencies.react no longer collide.
  • The plan splits scripts, deps, and protected fields (exports, bin, main, module, types, files, engines, packageManager) into added vs changed. Protected fields are assigned whole on replace (no stale nested keys).
  • package.json is left untouched when there's nothing additive to apply.
  • DEFAULT_UPGRADE_POLICY and the granular UpgradeApplyPolicy are exported from the embedded API — --force is 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 build script, a pinned typescript@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, --json output, partial-upgrade provenance, and formalized service deployment contracts.

🤖 Generated with Claude Code

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>
@DanMat
DanMat merged commit 320283a into main Aug 1, 2026
5 checks passed
@DanMat
DanMat deleted the feat/upgrade-non-destructive branch August 1, 2026 21:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant