|
| 1 | +# yarn-lockfile-surgeon |
| 2 | + |
| 3 | +Surgically bump packages in a Yarn Berry (v3+) lockfile to their **minimum |
| 4 | +satisfying versions**, without re-resolving unrelated transitive dependencies. |
| 5 | + |
| 6 | +## Why |
| 7 | + |
| 8 | +Yarn's built-in `yarn up` and `yarn install` always resolve new dependency |
| 9 | +ranges to the **latest** matching version. On an LTS branch where lockfile |
| 10 | +stability matters (e.g. security patches), this pulls in far more changes than |
| 11 | +necessary. |
| 12 | + |
| 13 | +This tool resolves to the **lowest** version that satisfies each range, |
| 14 | +keeping the lockfile diff as small as possible. |
| 15 | + |
| 16 | +## How it works |
| 17 | + |
| 18 | +1. Removes old lockfile entries for the target packages (including any `patch:` entries) |
| 19 | +2. Fetches the new version's metadata from the npm registry |
| 20 | +3. For each new dependency range not satisfied by an existing lockfile entry, |
| 21 | + resolves to the **minimum satisfying version** |
| 22 | +4. Walks transitive dependencies to catch cascading range bumps |
| 23 | +5. Writes the updated lockfile with empty checksums — |
| 24 | + `yarn install --mode=update-lockfile` fills these in |
| 25 | + |
| 26 | +## Usage |
| 27 | + |
| 28 | +The tool only modifies `yarn.lock`. Before running it, you need to: |
| 29 | + |
| 30 | +1. Update direct dependency versions in your `package.json` files |
| 31 | +2. Remove any `patch:` resolutions from `package.json` for the packages being upgraded |
| 32 | +3. Delete the corresponding `.yarn/patches/` files |
| 33 | + |
| 34 | +Then run: |
| 35 | + |
| 36 | +```bash |
| 37 | +cd scripts/yarn-lockfile-surgeon |
| 38 | +npm install # first time only |
| 39 | + |
| 40 | +yarn-lockfile-surgeon yarn.lock \ |
| 41 | + @scope/package-a@1.2.3 \ |
| 42 | + @scope/package-b@4.5.6 |
| 43 | + |
| 44 | +# Then, from the directory containing yarn.lock |
| 45 | +yarn install --mode=update-lockfile |
| 46 | +``` |
| 47 | + |
| 48 | +## Comparison with `yarn up` |
| 49 | + |
| 50 | +Given `@backstage/backend-defaults@0.12.2` which declares |
| 51 | +`@backstage/config: ^1.3.4` (current lockfile has 1.3.3): |
| 52 | + |
| 53 | +| Tool | Resolves `^1.3.4` to | Result | |
| 54 | +| ---------------------- | -------------------- | ------------------------------- | |
| 55 | +| `yarn up` | 1.3.7 (latest) | Cascading transitive upgrades | |
| 56 | +| `yarn-lockfile-surgeon` | 1.3.4 (minimum) | Only the required patch version | |
| 57 | + |
| 58 | +## Known limitations |
| 59 | + |
| 60 | +**Extra entries from `yarn install`**: When `yarn install --mode=update-lockfile` |
| 61 | +fills in checksums, it may also add new lockfile entries for dependency ranges |
| 62 | +introduced by second-order transitive dependencies. These resolve to the |
| 63 | +**latest** version since they go through Yarn's standard resolver. The extra |
| 64 | +entries are additive and harmless, but make the diff slightly larger than the |
| 65 | +theoretical minimum. |
| 66 | + |
| 67 | +**Unresolvable ranges**: If no published version satisfies a transitive |
| 68 | +dependency range, the tool logs a warning and skips it. The range will be left |
| 69 | +for `yarn install` to resolve using its default (latest) strategy. |
| 70 | + |
| 71 | +## Prior art |
| 72 | + |
| 73 | +pnpm has a built-in [`resolution-mode: lowest-direct`](https://pnpm.io/settings#resolution-mode) |
| 74 | +setting that resolves direct dependencies to their lowest matching version. |
| 75 | +Yarn Berry has no equivalent — this tool fills that gap for lockfile-level |
| 76 | +bumps. |
0 commit comments