@npmcli/arborist 2.4.0 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/arborist/build-ideal-tree.js +157 -43
- package/lib/node.js +38 -0
- package/lib/printable.js +9 -0
- package/package.json +1 -1
|
@@ -47,6 +47,7 @@ const _flagsSuspect = Symbol.for('flagsSuspect')
|
|
|
47
47
|
const _workspaces = Symbol.for('workspaces')
|
|
48
48
|
const _prune = Symbol('prune')
|
|
49
49
|
const _preferDedupe = Symbol('preferDedupe')
|
|
50
|
+
const _pruneDedupable = Symbol('pruneDedupable')
|
|
50
51
|
const _legacyBundling = Symbol('legacyBundling')
|
|
51
52
|
const _parseSettings = Symbol('parseSettings')
|
|
52
53
|
const _initTree = Symbol('initTree')
|
|
@@ -1342,6 +1343,21 @@ This is a one-time fix-up, please be patient...
|
|
|
1342
1343
|
// this is an overridden peer dep
|
|
1343
1344
|
this[_warnPeerConflict](edge)
|
|
1344
1345
|
}
|
|
1346
|
+
|
|
1347
|
+
// if we get a KEEP in a update scenario, then we MAY have something
|
|
1348
|
+
// already duplicating this unnecessarily! For example:
|
|
1349
|
+
// ```
|
|
1350
|
+
// root
|
|
1351
|
+
// +-- x (dep: y@1.x)
|
|
1352
|
+
// | +-- y@1.0.0
|
|
1353
|
+
// +-- y@1.1.0
|
|
1354
|
+
// ```
|
|
1355
|
+
// Now say we do `reify({update:['y']})`, and the latest version is
|
|
1356
|
+
// 1.1.0, which we already have in the root. We'll try to place y@1.1.0
|
|
1357
|
+
// first in x, then in the root, ending with KEEP, because we already
|
|
1358
|
+
// have it. In that case, we ought to REMOVE the nm/x/nm/y node, because
|
|
1359
|
+
// it is an unnecessary duplicate.
|
|
1360
|
+
this[_pruneDedupable](target, true)
|
|
1345
1361
|
return []
|
|
1346
1362
|
}
|
|
1347
1363
|
|
|
@@ -1397,8 +1413,8 @@ This is a one-time fix-up, please be patient...
|
|
|
1397
1413
|
// MAY end up putting a better/identical node further up the tree in
|
|
1398
1414
|
// a way that causes an unnecessary duplication. If so, remove the
|
|
1399
1415
|
// now-unnecessary node.
|
|
1400
|
-
if (edge.valid && edge.to
|
|
1401
|
-
edge.to
|
|
1416
|
+
if (edge.valid && edge.to && edge.to !== newDep)
|
|
1417
|
+
this[_pruneDedupable](edge.to, false)
|
|
1402
1418
|
|
|
1403
1419
|
// visit any dependents who are upset by this change
|
|
1404
1420
|
// if it's an angry overridden peer edge, however, make sure we
|
|
@@ -1414,30 +1430,8 @@ This is a one-time fix-up, please be patient...
|
|
|
1414
1430
|
// prune anything deeper in the tree that can be replaced by this
|
|
1415
1431
|
if (this.idealTree) {
|
|
1416
1432
|
for (const node of this.idealTree.inventory.query('name', newDep.name)) {
|
|
1417
|
-
if (node
|
|
1418
|
-
|
|
1419
|
-
!node.inShrinkwrap &&
|
|
1420
|
-
!node.inBundle &&
|
|
1421
|
-
node.canReplaceWith(newDep)) {
|
|
1422
|
-
// don't prune if the dupe is necessary!
|
|
1423
|
-
// root (a, d)
|
|
1424
|
-
// +-- a (b, c2)
|
|
1425
|
-
// | +-- b (c2) <-- place c2 for b, lands at root
|
|
1426
|
-
// +-- d (e)
|
|
1427
|
-
// +-- e (c1, d)
|
|
1428
|
-
// +-- c1
|
|
1429
|
-
// +-- f (c2)
|
|
1430
|
-
// +-- c2 <-- pruning this would be bad
|
|
1431
|
-
|
|
1432
|
-
const mask = node.parent !== target &&
|
|
1433
|
-
node.parent &&
|
|
1434
|
-
node.parent.parent &&
|
|
1435
|
-
node.parent.parent !== target &&
|
|
1436
|
-
node.parent.parent.resolve(newDep.name)
|
|
1437
|
-
|
|
1438
|
-
if (!mask || mask === newDep || node.canReplaceWith(mask))
|
|
1439
|
-
node.parent = null
|
|
1440
|
-
}
|
|
1433
|
+
if (node.isDescendantOf(target))
|
|
1434
|
+
this[_pruneDedupable](node, false)
|
|
1441
1435
|
}
|
|
1442
1436
|
}
|
|
1443
1437
|
|
|
@@ -1470,6 +1464,21 @@ This is a one-time fix-up, please be patient...
|
|
|
1470
1464
|
return placed
|
|
1471
1465
|
}
|
|
1472
1466
|
|
|
1467
|
+
// prune all the nodes in a branch of the tree that can be safely removed
|
|
1468
|
+
// This is only the most basic duplication detection; it finds if there
|
|
1469
|
+
// is another satisfying node further up the tree, and if so, dedupes.
|
|
1470
|
+
// Even in legacyBundling mode, we do this amount of deduplication.
|
|
1471
|
+
[_pruneDedupable] (node, descend = true) {
|
|
1472
|
+
if (node.canDedupe(this[_preferDedupe])) {
|
|
1473
|
+
node.root = null
|
|
1474
|
+
return
|
|
1475
|
+
}
|
|
1476
|
+
if (descend) {
|
|
1477
|
+
for (const child of node.children.values())
|
|
1478
|
+
this[_pruneDedupable](child)
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1473
1482
|
[_pruneForReplacement] (node, oldDeps) {
|
|
1474
1483
|
// gather up all the invalid edgesOut, and any now-extraneous
|
|
1475
1484
|
// deps that the new node doesn't depend on but the old one did.
|
|
@@ -1622,32 +1631,137 @@ This is a one-time fix-up, please be patient...
|
|
|
1622
1631
|
// placed here as well. the virtualRoot already has the appropriate
|
|
1623
1632
|
// overrides applied.
|
|
1624
1633
|
if (peerEntryEdge) {
|
|
1625
|
-
const
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1634
|
+
const currentPeerSet = getPeerSet(current)
|
|
1635
|
+
|
|
1636
|
+
// We are effectively replacing currentPeerSet with newPeerSet
|
|
1637
|
+
// If there are any non-peer deps coming into the currentPeerSet,
|
|
1638
|
+
// which are currently valid, and are from the target, then that
|
|
1639
|
+
// means that we have to ensure that they're not going to be made
|
|
1640
|
+
// invalid by putting the newPeerSet in place.
|
|
1641
|
+
// If the edge comes from somewhere deeper than the target, then
|
|
1642
|
+
// that's fine, because we'll create an invalid edge, detect it,
|
|
1643
|
+
// and duplicate the node further into the tree.
|
|
1644
|
+
// loop through the currentPeerSet checking for valid edges on
|
|
1645
|
+
// the members of the peer set which will be made invalid.
|
|
1646
|
+
const targetEdges = new Set()
|
|
1647
|
+
for (const p of currentPeerSet) {
|
|
1629
1648
|
for (const edge of p.edgesIn) {
|
|
1630
|
-
|
|
1649
|
+
// edge from within the peerSet, ignore
|
|
1650
|
+
if (currentPeerSet.has(edge.from))
|
|
1651
|
+
continue
|
|
1652
|
+
// only care about valid edges from target.
|
|
1653
|
+
// edges from elsewhere can dupe if offended, invalid edges
|
|
1654
|
+
// are already being fixed or will be later.
|
|
1655
|
+
if (edge.from !== target || !edge.valid)
|
|
1631
1656
|
continue
|
|
1657
|
+
targetEdges.add(edge)
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1632
1660
|
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
//
|
|
1639
|
-
//
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
const
|
|
1643
|
-
if (
|
|
1661
|
+
for (const edge of targetEdges) {
|
|
1662
|
+
// see if we intend to replace this one anyway
|
|
1663
|
+
const rep = dep.parent.children.get(edge.name)
|
|
1664
|
+
const current = edge.to
|
|
1665
|
+
if (!rep) {
|
|
1666
|
+
// this isn't one we're replacing. but it WAS included in the
|
|
1667
|
+
// peerSet for some reason, so make sure that it's still
|
|
1668
|
+
// ok with the replacements in the new peerSet
|
|
1669
|
+
for (const curEdge of current.edgesOut.values()) {
|
|
1670
|
+
const newRepDep = dep.parent.children.get(curEdge.name)
|
|
1671
|
+
if (curEdge.valid && newRepDep && !newRepDep.satisfies(curEdge)) {
|
|
1644
1672
|
canReplace = false
|
|
1645
|
-
break
|
|
1673
|
+
break
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
continue
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
// was this replacement already an override of some sort?
|
|
1680
|
+
const override = [...rep.edgesIn].some(e => !e.valid)
|
|
1681
|
+
// if we have a rep, and it's ok to put in this location, and
|
|
1682
|
+
// it's not already part of an override in the peerSet, then
|
|
1683
|
+
// we can continue with it.
|
|
1684
|
+
if (rep.satisfies(edge) && !override)
|
|
1685
|
+
continue
|
|
1686
|
+
// Otherwise, we cannot replace.
|
|
1687
|
+
canReplace = false
|
|
1688
|
+
break
|
|
1689
|
+
}
|
|
1690
|
+
// if we're going to be replacing the peerSet, we have to remove
|
|
1691
|
+
// and re-resolve any members of the old peerSet that are not
|
|
1692
|
+
// present in the new one, and which will have invalid edges.
|
|
1693
|
+
// We know that they're not depended upon by the target, or else
|
|
1694
|
+
// they would have caused a conflict, so they'll get landed deeper
|
|
1695
|
+
// in the tree, if possible.
|
|
1696
|
+
if (canReplace) {
|
|
1697
|
+
let needNesting = false
|
|
1698
|
+
OUTER: for (const node of currentPeerSet) {
|
|
1699
|
+
const rep = dep.parent.children.get(node.name)
|
|
1700
|
+
// has a replacement, already addressed above
|
|
1701
|
+
if (rep)
|
|
1702
|
+
continue
|
|
1703
|
+
|
|
1704
|
+
// ok, it has been placed here to dedupe, see if it needs to go
|
|
1705
|
+
// back deeper within the tree.
|
|
1706
|
+
for (const edge of node.edgesOut.values()) {
|
|
1707
|
+
const repDep = dep.parent.children.get(edge.name)
|
|
1708
|
+
// not in new peerSet, maybe fine.
|
|
1709
|
+
if (!repDep)
|
|
1710
|
+
continue
|
|
1711
|
+
|
|
1712
|
+
// new thing will be fine, no worries
|
|
1713
|
+
if (repDep.satisfies(edge))
|
|
1714
|
+
continue
|
|
1715
|
+
|
|
1716
|
+
// uhoh, we'll have to nest them.
|
|
1717
|
+
needNesting = true
|
|
1718
|
+
break OUTER
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
// to nest, just delete everything without a target dep
|
|
1723
|
+
// that's in the current peerSet, and add their dependants
|
|
1724
|
+
// to the _depsQueue for evaluation. Some of these MAY end
|
|
1725
|
+
// up in the same location again, and that's fine.
|
|
1726
|
+
if (needNesting) {
|
|
1727
|
+
// avoid mutating the tree while we're examining it
|
|
1728
|
+
const dependants = new Set()
|
|
1729
|
+
const reresolve = new Set()
|
|
1730
|
+
OUTER: for (const node of currentPeerSet) {
|
|
1731
|
+
const rep = dep.parent.children.get(node.name)
|
|
1732
|
+
if (rep)
|
|
1733
|
+
continue
|
|
1734
|
+
// create a separate set for each one, so we can skip any
|
|
1735
|
+
// that might somehow have an incoming target edge
|
|
1736
|
+
const deps = new Set()
|
|
1737
|
+
for (const edge of node.edgesIn) {
|
|
1738
|
+
// a target dep, skip this dep entirely, already addressed
|
|
1739
|
+
// ignoring for coverage, because it really ought to be
|
|
1740
|
+
// impossible, but I can't prove it yet, so this is here
|
|
1741
|
+
// for safety.
|
|
1742
|
+
/* istanbul ignore if - should be impossible */
|
|
1743
|
+
if (edge.from === target)
|
|
1744
|
+
continue OUTER
|
|
1745
|
+
// ignore this edge, it'll either be replaced or re-resolved
|
|
1746
|
+
if (currentPeerSet.has(edge.from))
|
|
1747
|
+
continue
|
|
1748
|
+
// ok, we care about this one.
|
|
1749
|
+
deps.add(edge.from)
|
|
1646
1750
|
}
|
|
1751
|
+
reresolve.add(node)
|
|
1752
|
+
for (const d of deps)
|
|
1753
|
+
dependants.add(d)
|
|
1647
1754
|
}
|
|
1755
|
+
for (const dependant of dependants) {
|
|
1756
|
+
this[_depsQueue].push(dependant)
|
|
1757
|
+
this[_depsSeen].delete(dependant)
|
|
1758
|
+
}
|
|
1759
|
+
for (const node of reresolve)
|
|
1760
|
+
node.root = null
|
|
1648
1761
|
}
|
|
1649
1762
|
}
|
|
1650
1763
|
}
|
|
1764
|
+
|
|
1651
1765
|
if (canReplace) {
|
|
1652
1766
|
const ret = this[_canPlacePeers](dep, target, edge, REPLACE, peerEntryEdge, peerPath, isSource)
|
|
1653
1767
|
/* istanbul ignore else - extremely rare that the peer set would
|
package/lib/node.js
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
// where we need to quickly find all instances of a given package name within a
|
|
29
29
|
// tree.
|
|
30
30
|
|
|
31
|
+
const semver = require('semver')
|
|
31
32
|
const nameFromFolder = require('@npmcli/name-from-folder')
|
|
32
33
|
const Edge = require('./edge.js')
|
|
33
34
|
const Inventory = require('./inventory.js')
|
|
@@ -885,6 +886,43 @@ class Node {
|
|
|
885
886
|
return node.canReplaceWith(this)
|
|
886
887
|
}
|
|
887
888
|
|
|
889
|
+
// return true if it's safe to remove this node, because anything that
|
|
890
|
+
// is depending on it would be fine with the thing that they would resolve
|
|
891
|
+
// to if it was removed, or nothing is depending on it in the first place.
|
|
892
|
+
canDedupe (preferDedupe = false) {
|
|
893
|
+
// not allowed to mess with shrinkwraps or bundles
|
|
894
|
+
if (this.inDepBundle || this.inShrinkwrap)
|
|
895
|
+
return false
|
|
896
|
+
|
|
897
|
+
// it's a top level pkg, or a dep of one
|
|
898
|
+
if (!this.parent || !this.parent.parent)
|
|
899
|
+
return false
|
|
900
|
+
|
|
901
|
+
// no one wants it, remove it
|
|
902
|
+
if (this.edgesIn.size === 0)
|
|
903
|
+
return true
|
|
904
|
+
|
|
905
|
+
const other = this.parent.parent.resolve(this.name)
|
|
906
|
+
|
|
907
|
+
// nothing else, need this one
|
|
908
|
+
if (!other)
|
|
909
|
+
return false
|
|
910
|
+
|
|
911
|
+
// if it's the same thing, then always fine to remove
|
|
912
|
+
if (other.matches(this))
|
|
913
|
+
return true
|
|
914
|
+
|
|
915
|
+
// if the other thing can't replace this, then skip it
|
|
916
|
+
if (!other.canReplace(this))
|
|
917
|
+
return false
|
|
918
|
+
|
|
919
|
+
// if we prefer dedupe, or if the version is greater/equal, take the other
|
|
920
|
+
if (preferDedupe || semver.gte(other.version, this.version))
|
|
921
|
+
return true
|
|
922
|
+
|
|
923
|
+
return false
|
|
924
|
+
}
|
|
925
|
+
|
|
888
926
|
satisfies (requested) {
|
|
889
927
|
if (requested instanceof Edge)
|
|
890
928
|
return this.name === requested.name && requested.satisfiedBy(this)
|
package/lib/printable.js
CHANGED
|
@@ -29,6 +29,15 @@ class ArboristNode {
|
|
|
29
29
|
this.peer = true
|
|
30
30
|
if (tree.inBundle)
|
|
31
31
|
this.bundled = true
|
|
32
|
+
if (tree.inDepBundle)
|
|
33
|
+
this.bundler = tree.getBundler().location
|
|
34
|
+
const bd = tree.package && tree.package.bundleDependencies
|
|
35
|
+
if (bd && bd.length)
|
|
36
|
+
this.bundleDependencies = bd
|
|
37
|
+
if (tree.inShrinkwrap)
|
|
38
|
+
this.inShrinkwrap = true
|
|
39
|
+
else if (tree.hasShrinkwrap)
|
|
40
|
+
this.hasShrinkwrap = true
|
|
32
41
|
if (tree.error)
|
|
33
42
|
this.error = treeError(tree.error)
|
|
34
43
|
if (tree.errors && tree.errors.length)
|