Commit 076551b
authored
fix(arborist): clean up orphan top-level symlinks in linked strategy (npm#9309)
In continuation of our exploration of using `install-strategy=linked` in
the [Gutenberg
monorepo](WordPress/gutenberg#75814), which
powers the WordPress Block Editor.
When using `install-strategy=linked`, removing a dependency leaves a
dangling symlink at `node_modules/<pkg>` (and
`<workspace>/node_modules/<pkg>` for workspace deps).
The store entry under `node_modules/.store/<pkg>@…` is correctly cleaned
up by `#cleanOrphanedStoreEntries`, but the top-level link pointing into
it is left behind, so `require('<pkg>')` fails with `Cannot find module`
even though the entry still appears in `node_modules/`.
The root cause is the same family of issue as npm#9106.
`#buildLinkedActualForDiff` builds the synthetic actual tree from the
ideal tree, so any dependency that exists on disk but is no longer in
the ideal tree is never compared, and the diff produces no REMOVE action
for its top-level symlink.
Fixed by extending `#cleanOrphanedStoreEntries` to also collect, per
`node_modules` directory (root and each workspace), the set of valid
top-level link names from the ideal tree, then sweeping each directory
and removing any symlink whose name is not in that set.
The root `node_modules` and every workspace's `node_modules` (via
`idealTree.fsChildren`) are always seeded into the sweep, so the case of
removing the last dependency from the project root or from a workspace
still triggers cleanup, including when the workspace itself is declared
as a root dependency and therefore has its self-link at the root rather
than under its own `node_modules`.
The sweep is restricted to symlinks whose target resolves inside the
project root, so it covers both store links (e.g. `node_modules/eslint
-> .store/...`) and workspace self-links that no longer belong (e.g.
`node_modules/a -> ../packages/a` after `a` is undeclared) without
touching symlinks that point outside the project, such as those created
by `npm link <global-pkg>` without `--save`.
Real directories and npm-managed entries (`.bin`, `.store`,
`.package-lock.json`) are left alone.
The workspace self-link inside its own `node_modules` (e.g.
`packages/a/node_modules/a -> ..`) is in the ideal tree as a non-store
link, so it's preserved.
The sweep also respects the install mode:
- It is skipped entirely for `dryRun` and `packageLockOnly` installs,
both of which short-circuit `#reifyPackages` and must not mutate
`node_modules`.
- For workspace-filtered installs (`npm install -w <ws>
--install-strategy=linked`), the set of `node_modules` directories to
sweep is restricted to the workspaces named in `--workspace`, so dropped
dependencies from the in-scope workspace get cleaned up while
out-of-scope workspaces and the project root are left untouched.
`IsolatedNode`/`IsolatedLink` locations are built with `path.join`,
which uses backslashes on Windows; locations are normalized to forward
slashes inside the sweep so the parser works on both POSIX and Windows.
## Trade-off
This aligns the linked strategy with npm's normal
`node_modules`-is-managed model: any in-project symlink that isn't in
the ideal tree is treated as orphaned, matching what happens today under
the default install strategy.
A consequence is that hand-made or unsaved `npm link` symlinks pointing
to other paths inside the project root (e.g. `node_modules/foo ->
../examples/foo`) are also swept, since npm doesn't currently record
which links it owns and they are indistinguishable from workspace
self-links by target alone.
A more discriminating ownership check (recording managed link names in
the hidden lockfile and only sweeping those) is a worthwhile follow-up
but materially larger than this fix.
## References
Fixes npm#9308
Related to npm#91061 parent b8655c7 commit 076551b
2 files changed
Lines changed: 546 additions & 11 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | | - | |
| 14 | + | |
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| |||
124 | 124 | | |
125 | 125 | | |
126 | 126 | | |
127 | | - | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
128 | 132 | | |
129 | 133 | | |
130 | 134 | | |
| |||
1281 | 1285 | | |
1282 | 1286 | | |
1283 | 1287 | | |
| 1288 | + | |
1284 | 1289 | | |
1285 | | - | |
| 1290 | + | |
| 1291 | + | |
| 1292 | + | |
1286 | 1293 | | |
1287 | 1294 | | |
1288 | 1295 | | |
1289 | 1296 | | |
1290 | | - | |
| 1297 | + | |
1291 | 1298 | | |
1292 | 1299 | | |
1293 | | - | |
| 1300 | + | |
| 1301 | + | |
| 1302 | + | |
| 1303 | + | |
1294 | 1304 | | |
| 1305 | + | |
| 1306 | + | |
| 1307 | + | |
1295 | 1308 | | |
| 1309 | + | |
1296 | 1310 | | |
1297 | | - | |
| 1311 | + | |
1298 | 1312 | | |
| 1313 | + | |
| 1314 | + | |
| 1315 | + | |
| 1316 | + | |
| 1317 | + | |
| 1318 | + | |
| 1319 | + | |
| 1320 | + | |
| 1321 | + | |
| 1322 | + | |
| 1323 | + | |
| 1324 | + | |
| 1325 | + | |
| 1326 | + | |
| 1327 | + | |
| 1328 | + | |
| 1329 | + | |
| 1330 | + | |
| 1331 | + | |
| 1332 | + | |
| 1333 | + | |
| 1334 | + | |
| 1335 | + | |
| 1336 | + | |
| 1337 | + | |
| 1338 | + | |
| 1339 | + | |
| 1340 | + | |
| 1341 | + | |
| 1342 | + | |
| 1343 | + | |
| 1344 | + | |
| 1345 | + | |
| 1346 | + | |
| 1347 | + | |
| 1348 | + | |
| 1349 | + | |
| 1350 | + | |
| 1351 | + | |
| 1352 | + | |
| 1353 | + | |
| 1354 | + | |
| 1355 | + | |
| 1356 | + | |
| 1357 | + | |
| 1358 | + | |
| 1359 | + | |
| 1360 | + | |
| 1361 | + | |
| 1362 | + | |
| 1363 | + | |
| 1364 | + | |
| 1365 | + | |
| 1366 | + | |
| 1367 | + | |
| 1368 | + | |
| 1369 | + | |
| 1370 | + | |
| 1371 | + | |
| 1372 | + | |
| 1373 | + | |
| 1374 | + | |
| 1375 | + | |
| 1376 | + | |
| 1377 | + | |
| 1378 | + | |
| 1379 | + | |
| 1380 | + | |
| 1381 | + | |
| 1382 | + | |
| 1383 | + | |
| 1384 | + | |
| 1385 | + | |
| 1386 | + | |
| 1387 | + | |
| 1388 | + | |
| 1389 | + | |
| 1390 | + | |
| 1391 | + | |
| 1392 | + | |
| 1393 | + | |
| 1394 | + | |
| 1395 | + | |
| 1396 | + | |
| 1397 | + | |
| 1398 | + | |
| 1399 | + | |
| 1400 | + | |
| 1401 | + | |
| 1402 | + | |
| 1403 | + | |
| 1404 | + | |
| 1405 | + | |
| 1406 | + | |
| 1407 | + | |
| 1408 | + | |
| 1409 | + | |
| 1410 | + | |
| 1411 | + | |
| 1412 | + | |
| 1413 | + | |
| 1414 | + | |
| 1415 | + | |
| 1416 | + | |
| 1417 | + | |
| 1418 | + | |
| 1419 | + | |
| 1420 | + | |
| 1421 | + | |
| 1422 | + | |
| 1423 | + | |
| 1424 | + | |
| 1425 | + | |
| 1426 | + | |
| 1427 | + | |
| 1428 | + | |
| 1429 | + | |
| 1430 | + | |
| 1431 | + | |
| 1432 | + | |
| 1433 | + | |
| 1434 | + | |
| 1435 | + | |
| 1436 | + | |
| 1437 | + | |
| 1438 | + | |
| 1439 | + | |
| 1440 | + | |
| 1441 | + | |
| 1442 | + | |
| 1443 | + | |
1299 | 1444 | | |
1300 | 1445 | | |
1301 | 1446 | | |
1302 | | - | |
1303 | 1447 | | |
1304 | 1448 | | |
1305 | 1449 | | |
1306 | 1450 | | |
1307 | | - | |
| 1451 | + | |
1308 | 1452 | | |
1309 | | - | |
1310 | | - | |
| 1453 | + | |
| 1454 | + | |
1311 | 1455 | | |
1312 | | - | |
| 1456 | + | |
1313 | 1457 | | |
1314 | 1458 | | |
1315 | 1459 | | |
| |||
0 commit comments