Skip to content

v1.17.0

Choose a tag to compare

@KirillCustom KirillCustom released this 04 Sep 12:37
· 46 commits to main since this release
abf42bc

Six open complaints about patch-package were reproduced against this code and the four that reproduced here are now closed. All four belong to one class — the tool reporting success without doing the job — which this project treats as worse than any refusal.

Fixed

  • apply no longer lays a rewritten patch on top of the version already in the tree. Switch branches and the patch file under one name becomes a different file, while node_modules stays patched by the old one. An appending hunk fits a second time, so both versions ended up in the file while the run printed a green tick and 1 applied — measured on ms@2.1.2, both // FIX VERSION ONE and // FIX VERSION TWO in one file, with status calling it in the tree. patch-package reports the same on #487 and #557, and there it at least ends in a refusal.

    The record now carries the hashes of the files each patch left behind, which turns "the patch file changed" into a statement about the tree: when not one of those files has moved since, the previous version is provably still there, and apply refuses instead of stacking — naming the file to look at and the package to reinstall. When the tree has moved on, because the package was reinstalled or someone edited it, there is nothing to prove and it warns instead, so a clean install does not fail over a record. A refusal keeps the record it was proved from; without that the state file was rewritten without the refused patch and the next bun install knew nothing.

    The record costs +2 ms per 90 patches in the case that runs on every install — 17 ms against 19 ms on a stand of 30 packages with three sequenced patches each, files of 800 lines, median of five.

  • create no longer overwrites the patch of a neighbouring directory. An alias exists to hold two versions of one package, and both directories answer to one manifest name. The patch file took its name from the manifest, so with matching versions the second create replaced the first patch and printed ✅ Patch created; the change was simply gone. With differing versions the names differed only by version, and rebase is-number 0 took off the patches of both directories while announcing it was rebasing mynum.

  • rebase understands a package installed under an npm alias. rebase mynum 0 answered No patches found for mynum — the only one of six commands that could not resolve the name. Worse, rebase is-number 0 did work and then printed advice that did not: Now edit node_modules/is-number for a directory that does not exist, and create is-number --append, which exits 1. The directory in the hints now comes from the paths inside the patch, which is where it is actually written.

  • The record survives a patch file that is gone. Delete or rename one and its entry stays behind — it is the only thing that remembers those changes are probably still in node_modules, and status says so. But rebase and reverse rebuilt the record after the rollback and read every file named in it, so a missing one threw ENOENT: the work was done and the command reported a stack instead. apply did not throw; it dropped the entry silently, so the warning lived exactly until the next install — and apply runs from postinstall. Measured: one run erased it while the deleted patch's change sat in the tree untouched.

  • retarget stopped being a dead end. It refused a set carrying more than one version with "sort that out first" — no way out, at the very place apply sends you. It still refuses, because moving them all would give two files the same name, but now it says that and lists the files written for a version that is not installed.

Changed

  • A patch file is named after the directory it patches, not after the package manifest. For everything except an npm alias these are the same name and nothing changes. For an alias, create mynum now writes patches/mynum+7.0.0.patch — which is also what patch-package writes, checked by running it. Patches created by earlier versions carry the manifest name and are still read: the key is looked up by directory first and by manifest name second, and that order matters, since starting from the manifest name would hand one directory the patches of its neighbour. The next create moves such a file onto the directory name and says so, and never touches one that patches a different directory.

  • Two patches for one package are explained instead of merely counted. Upgrade a package, run create again, and patches/ holds two files for one package; both are applied and the tree ends up with the changes of two versions at once (patch-package #480). What was said about it was version mismatch (patch: 2.1.2, installed: 2.1.3) on the stale file — true, and no help, because the trouble is the set. apply now names the other file when one of the package's patches is written for exactly the installed version, and create says it the moment the second file appears, asking the tree whether the new patch already carries the old one's changes rather than guessing. The applying itself is unchanged: measured, our tree matches patch-package byte for byte here, and it warns and applies too.

Verified

  • 272 tests, tsc --noEmit, on Linux, macOS and Windows, plus smoke jobs on bun 1.0.36, 1.1.45 and 1.2.23. The suite runs twice locally, with Apple diff and with GNU diff.
  • The corpus of 292 real patches ran on every change touching parsing, applying or un-applying: 291 compared, all matching patch-package and the pre-change code, 0 NOT-idempotent, and round-trip down to the two known cases — react-native-launch-navigator+1.0.8, whose newline cannot be recovered, and @radix-ui+react-dialog+1.1.6, which patch-package refuses as well.
  • 23 mutations across the five changes. Four survived a run, and none of them meant "spare code" by default: two were unreachable second locks and were removed, and two were holes in the suite — nothing covered separating two directories that share an old-style name, and nothing covered the order the patch key is looked up in. Both are covered now.
  • Two of the defects above were found by comparing against main on a live tree rather than by the suite, which is now written down as a step of its own: a change that narrows something is invisible to tests written for the new behaviour.

Full Changelog: v1.16.0...v1.17.0