@npmcli/arborist 2.4.0 → 2.4.4
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/bin/license.js +1 -1
- package/lib/add-rm-pkg-deps.js +85 -83
- package/lib/arborist/build-ideal-tree.js +161 -46
- package/lib/arborist/index.js +3 -0
- package/lib/arborist/load-virtual.js +2 -2
- package/lib/arborist/rebuild.js +1 -1
- package/lib/arborist/reify.js +57 -17
- package/lib/audit-report.js +14 -7
- package/lib/inventory.js +1 -1
- package/lib/node.js +61 -22
- package/lib/printable.js +15 -6
- package/lib/shrinkwrap.js +6 -6
- package/lib/update-root-package-json.js +14 -2
- package/lib/vuln.js +6 -3
- package/lib/yarn-lock.js +6 -6
- package/package.json +7 -6
- package/CHANGELOG.md +0 -19
- package/lib/dep-spec.js +0 -43
package/bin/license.js
CHANGED
|
@@ -22,7 +22,7 @@ a.loadVirtual().then(tree => {
|
|
|
22
22
|
set.push([tree.inventory.query('license', license).size, license])
|
|
23
23
|
|
|
24
24
|
for (const [count, license] of set.sort((a, b) =>
|
|
25
|
-
a[1] && b[1] ? b[0] - a[0] || a[1].localeCompare(b[1])
|
|
25
|
+
a[1] && b[1] ? b[0] - a[0] || a[1].localeCompare(b[1], 'en')
|
|
26
26
|
: a[1] ? -1
|
|
27
27
|
: b[1] ? 1
|
|
28
28
|
: 0))
|
package/lib/add-rm-pkg-deps.js
CHANGED
|
@@ -1,60 +1,60 @@
|
|
|
1
1
|
// add and remove dependency specs to/from pkg manifest
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const others = new Set([
|
|
5
|
-
'dependencies',
|
|
6
|
-
'optionalDependencies',
|
|
7
|
-
'devDependencies',
|
|
8
|
-
'peerDependenciesMeta',
|
|
9
|
-
'peerDependencies',
|
|
10
|
-
])
|
|
11
|
-
|
|
12
|
-
switch (type) {
|
|
13
|
-
case 'prod':
|
|
14
|
-
others.delete('dependencies')
|
|
15
|
-
break
|
|
16
|
-
case 'dev':
|
|
17
|
-
others.delete('devDependencies')
|
|
18
|
-
others.delete('peerDependencies')
|
|
19
|
-
others.delete('peerDependenciesMeta')
|
|
20
|
-
break
|
|
21
|
-
case 'optional':
|
|
22
|
-
others.delete('optionalDependencies')
|
|
23
|
-
break
|
|
24
|
-
case 'peer':
|
|
25
|
-
case 'peerOptional':
|
|
26
|
-
others.delete('devDependencies')
|
|
27
|
-
others.delete('peerDependencies')
|
|
28
|
-
others.delete('peerDependenciesMeta')
|
|
29
|
-
break
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
for (const other of others)
|
|
33
|
-
deleteSubKey(pkg, other, name)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const add = ({pkg, add, saveBundle, saveType}) => {
|
|
3
|
+
const add = ({pkg, add, saveBundle, saveType, log}) => {
|
|
37
4
|
for (const spec of add)
|
|
38
|
-
addSingle({pkg, spec, saveBundle, saveType})
|
|
5
|
+
addSingle({pkg, spec, saveBundle, saveType, log})
|
|
39
6
|
|
|
40
7
|
return pkg
|
|
41
8
|
}
|
|
42
9
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
10
|
+
// Canonical source of both the map between saveType and where it correlates to
|
|
11
|
+
// in the package, and the names of all our dependencies attributes
|
|
12
|
+
const saveTypeMap = new Map([
|
|
13
|
+
['dev', 'devDependencies'],
|
|
14
|
+
['optional', 'optionalDependencies'],
|
|
15
|
+
['prod', 'dependencies'],
|
|
16
|
+
['peerOptional', 'peerDependencies'],
|
|
17
|
+
['peer', 'peerDependencies'],
|
|
18
|
+
])
|
|
46
19
|
|
|
20
|
+
const addSingle = ({pkg, spec, saveBundle, saveType, log}) => {
|
|
47
21
|
const { name, rawSpec } = spec
|
|
48
|
-
removeFromOthers(name, saveType, pkg)
|
|
49
|
-
const type = saveType === 'prod' ? 'dependencies'
|
|
50
|
-
: saveType === 'optional' ? 'optionalDependencies'
|
|
51
|
-
: saveType === 'peer' || saveType === 'peerOptional' ? 'peerDependencies'
|
|
52
|
-
: saveType === 'dev' ? 'devDependencies'
|
|
53
|
-
: /* istanbul ignore next */ null
|
|
54
22
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
23
|
+
// if the user does not give us a type, we infer which type(s)
|
|
24
|
+
// to keep based on the same order of priority we do when
|
|
25
|
+
// building the tree as defined in the _loadDeps method of
|
|
26
|
+
// the node class.
|
|
27
|
+
if (!saveType)
|
|
28
|
+
saveType = inferSaveType(pkg, spec.name)
|
|
29
|
+
|
|
30
|
+
if (saveType === 'prod') {
|
|
31
|
+
// a production dependency can only exist as production (rpj ensures it
|
|
32
|
+
// doesn't coexist w/ optional)
|
|
33
|
+
deleteSubKey(pkg, 'devDependencies', name, 'dependencies', log)
|
|
34
|
+
deleteSubKey(pkg, 'peerDependencies', name, 'dependencies', log)
|
|
35
|
+
} else if (saveType === 'dev') {
|
|
36
|
+
// a dev dependency may co-exist as peer, or optional, but not production
|
|
37
|
+
deleteSubKey(pkg, 'dependencies', name, 'devDependencies', log)
|
|
38
|
+
} else if (saveType === 'optional') {
|
|
39
|
+
// an optional dependency may co-exist as dev (rpj ensures it doesn't
|
|
40
|
+
// coexist w/ prod)
|
|
41
|
+
deleteSubKey(pkg, 'peerDependencies', name, 'optionalDependencies', log)
|
|
42
|
+
} else { // peer or peerOptional is all that's left
|
|
43
|
+
// a peer dependency may coexist as dev
|
|
44
|
+
deleteSubKey(pkg, 'dependencies', name, 'peerDependencies', log)
|
|
45
|
+
deleteSubKey(pkg, 'optionalDependencies', name, 'peerDependencies', log)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const depType = saveTypeMap.get(saveType)
|
|
49
|
+
|
|
50
|
+
pkg[depType] = pkg[depType] || {}
|
|
51
|
+
if (rawSpec !== '' || pkg[depType][name] === undefined)
|
|
52
|
+
pkg[depType][name] = rawSpec || '*'
|
|
53
|
+
if (saveType === 'optional') {
|
|
54
|
+
// Affordance for previous npm versions that require this behaviour
|
|
55
|
+
pkg.dependencies = pkg.dependencies || {}
|
|
56
|
+
pkg.dependencies[name] = pkg.optionalDependencies[name]
|
|
57
|
+
}
|
|
58
58
|
|
|
59
59
|
if (saveType === 'peer' || saveType === 'peerOptional') {
|
|
60
60
|
const pdm = pkg.peerDependenciesMeta || {}
|
|
@@ -75,51 +75,53 @@ const addSingle = ({pkg, spec, saveBundle, saveType}) => {
|
|
|
75
75
|
// keep it sorted, keep it unique
|
|
76
76
|
const bd = new Set(pkg.bundleDependencies || [])
|
|
77
77
|
bd.add(spec.name)
|
|
78
|
-
pkg.bundleDependencies = [...bd].sort((a, b) => a.localeCompare(b))
|
|
78
|
+
pkg.bundleDependencies = [...bd].sort((a, b) => a.localeCompare(b, 'en'))
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return 'peer'
|
|
97
|
-
} else if (devDeps && devDeps[name] !== undefined)
|
|
98
|
-
return 'dev'
|
|
99
|
-
else if (optDeps && optDeps[name] !== undefined)
|
|
100
|
-
return 'optional'
|
|
101
|
-
else
|
|
102
|
-
return 'prod'
|
|
82
|
+
// Finds where the package is already in the spec and infers saveType from that
|
|
83
|
+
const inferSaveType = (pkg, name) => {
|
|
84
|
+
for (const saveType of saveTypeMap.keys()) {
|
|
85
|
+
if (hasSubKey(pkg, saveTypeMap.get(saveType), name)) {
|
|
86
|
+
if (
|
|
87
|
+
saveType === 'peerOptional' &&
|
|
88
|
+
(!hasSubKey(pkg, 'peerDependenciesMeta', name) ||
|
|
89
|
+
!pkg.peerDependenciesMeta[name].optional)
|
|
90
|
+
)
|
|
91
|
+
return 'peer'
|
|
92
|
+
return saveType
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return 'prod'
|
|
103
96
|
}
|
|
104
97
|
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
98
|
+
const hasSubKey = (pkg, depType, name) => {
|
|
99
|
+
return pkg[depType] && Object.prototype.hasOwnProperty.call(pkg[depType], name)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Removes a subkey and warns about it if it's being replaced
|
|
103
|
+
const deleteSubKey = (pkg, depType, name, replacedBy, log) => {
|
|
104
|
+
if (hasSubKey(pkg, depType, name)) {
|
|
105
|
+
if (replacedBy && log)
|
|
106
|
+
log.warn('idealTree', `Removing ${depType}.${name} in favor of ${replacedBy}.${name}`)
|
|
107
|
+
delete pkg[depType][name]
|
|
108
|
+
|
|
109
|
+
// clean up peerDependenciesMeta if we are removing something from peerDependencies
|
|
110
|
+
if (depType === 'peerDependencies' && pkg.peerDependenciesMeta) {
|
|
111
|
+
delete pkg.peerDependenciesMeta[name]
|
|
112
|
+
if (!Object.keys(pkg.peerDependenciesMeta).length)
|
|
113
|
+
delete pkg.peerDependenciesMeta
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!Object.keys(pkg[depType]).length)
|
|
117
|
+
delete pkg[depType]
|
|
110
118
|
}
|
|
111
119
|
}
|
|
112
120
|
|
|
113
121
|
const rm = (pkg, rm) => {
|
|
114
|
-
for (const
|
|
115
|
-
'dependencies',
|
|
116
|
-
'optionalDependencies',
|
|
117
|
-
'peerDependencies',
|
|
118
|
-
'peerDependenciesMeta',
|
|
119
|
-
'devDependencies',
|
|
120
|
-
]) {
|
|
122
|
+
for (const depType of new Set(saveTypeMap.values())) {
|
|
121
123
|
for (const name of rm)
|
|
122
|
-
deleteSubKey(pkg,
|
|
124
|
+
deleteSubKey(pkg, depType, name)
|
|
123
125
|
}
|
|
124
126
|
if (pkg.bundleDependencies) {
|
|
125
127
|
pkg.bundleDependencies = pkg.bundleDependencies
|
|
@@ -130,4 +132,4 @@ const rm = (pkg, rm) => {
|
|
|
130
132
|
return pkg
|
|
131
133
|
}
|
|
132
134
|
|
|
133
|
-
module.exports = { add, rm }
|
|
135
|
+
module.exports = { add, rm, saveTypeMap, hasSubKey }
|
|
@@ -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')
|
|
@@ -503,6 +504,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
503
504
|
saveBundle,
|
|
504
505
|
saveType,
|
|
505
506
|
path: this.path,
|
|
507
|
+
log: this.log,
|
|
506
508
|
})
|
|
507
509
|
})
|
|
508
510
|
}
|
|
@@ -762,7 +764,7 @@ This is a one-time fix-up, please be patient...
|
|
|
762
764
|
// sort physically shallower deps up to the front of the queue,
|
|
763
765
|
// because they'll affect things deeper in, then alphabetical
|
|
764
766
|
this[_depsQueue].sort((a, b) =>
|
|
765
|
-
(a.depth - b.depth) || a.path.localeCompare(b.path))
|
|
767
|
+
(a.depth - b.depth) || a.path.localeCompare(b.path, 'en'))
|
|
766
768
|
|
|
767
769
|
const node = this[_depsQueue].shift()
|
|
768
770
|
const bd = node.package.bundleDependencies
|
|
@@ -900,7 +902,7 @@ This is a one-time fix-up, please be patient...
|
|
|
900
902
|
}
|
|
901
903
|
|
|
902
904
|
const placed = tasks
|
|
903
|
-
.sort((a, b) => a.edge.name.localeCompare(b.edge.name))
|
|
905
|
+
.sort((a, b) => a.edge.name.localeCompare(b.edge.name, 'en'))
|
|
904
906
|
.map(({ edge, dep }) => this[_placeDep](dep, node, edge))
|
|
905
907
|
|
|
906
908
|
const promises = []
|
|
@@ -1145,7 +1147,7 @@ This is a one-time fix-up, please be patient...
|
|
|
1145
1147
|
// we typically only install non-optional peers, but we have to
|
|
1146
1148
|
// factor them into the peerSet so that we can avoid conflicts
|
|
1147
1149
|
.filter(e => e.peer && !(e.valid && e.to))
|
|
1148
|
-
.sort(({name: a}, {name: b}) => a.localeCompare(b))
|
|
1150
|
+
.sort(({name: a}, {name: b}) => a.localeCompare(b, 'en'))
|
|
1149
1151
|
|
|
1150
1152
|
for (const edge of peerEdges) {
|
|
1151
1153
|
// already placed this one, and we're happy with it.
|
|
@@ -1342,6 +1344,21 @@ This is a one-time fix-up, please be patient...
|
|
|
1342
1344
|
// this is an overridden peer dep
|
|
1343
1345
|
this[_warnPeerConflict](edge)
|
|
1344
1346
|
}
|
|
1347
|
+
|
|
1348
|
+
// if we get a KEEP in a update scenario, then we MAY have something
|
|
1349
|
+
// already duplicating this unnecessarily! For example:
|
|
1350
|
+
// ```
|
|
1351
|
+
// root
|
|
1352
|
+
// +-- x (dep: y@1.x)
|
|
1353
|
+
// | +-- y@1.0.0
|
|
1354
|
+
// +-- y@1.1.0
|
|
1355
|
+
// ```
|
|
1356
|
+
// Now say we do `reify({update:['y']})`, and the latest version is
|
|
1357
|
+
// 1.1.0, which we already have in the root. We'll try to place y@1.1.0
|
|
1358
|
+
// first in x, then in the root, ending with KEEP, because we already
|
|
1359
|
+
// have it. In that case, we ought to REMOVE the nm/x/nm/y node, because
|
|
1360
|
+
// it is an unnecessary duplicate.
|
|
1361
|
+
this[_pruneDedupable](target, true)
|
|
1345
1362
|
return []
|
|
1346
1363
|
}
|
|
1347
1364
|
|
|
@@ -1397,8 +1414,8 @@ This is a one-time fix-up, please be patient...
|
|
|
1397
1414
|
// MAY end up putting a better/identical node further up the tree in
|
|
1398
1415
|
// a way that causes an unnecessary duplication. If so, remove the
|
|
1399
1416
|
// now-unnecessary node.
|
|
1400
|
-
if (edge.valid && edge.to
|
|
1401
|
-
edge.to
|
|
1417
|
+
if (edge.valid && edge.to && edge.to !== newDep)
|
|
1418
|
+
this[_pruneDedupable](edge.to, false)
|
|
1402
1419
|
|
|
1403
1420
|
// visit any dependents who are upset by this change
|
|
1404
1421
|
// if it's an angry overridden peer edge, however, make sure we
|
|
@@ -1414,30 +1431,8 @@ This is a one-time fix-up, please be patient...
|
|
|
1414
1431
|
// prune anything deeper in the tree that can be replaced by this
|
|
1415
1432
|
if (this.idealTree) {
|
|
1416
1433
|
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
|
-
}
|
|
1434
|
+
if (node.isDescendantOf(target))
|
|
1435
|
+
this[_pruneDedupable](node, false)
|
|
1441
1436
|
}
|
|
1442
1437
|
}
|
|
1443
1438
|
|
|
@@ -1470,6 +1465,21 @@ This is a one-time fix-up, please be patient...
|
|
|
1470
1465
|
return placed
|
|
1471
1466
|
}
|
|
1472
1467
|
|
|
1468
|
+
// prune all the nodes in a branch of the tree that can be safely removed
|
|
1469
|
+
// This is only the most basic duplication detection; it finds if there
|
|
1470
|
+
// is another satisfying node further up the tree, and if so, dedupes.
|
|
1471
|
+
// Even in legacyBundling mode, we do this amount of deduplication.
|
|
1472
|
+
[_pruneDedupable] (node, descend = true) {
|
|
1473
|
+
if (node.canDedupe(this[_preferDedupe])) {
|
|
1474
|
+
node.root = null
|
|
1475
|
+
return
|
|
1476
|
+
}
|
|
1477
|
+
if (descend) {
|
|
1478
|
+
for (const child of node.children.values())
|
|
1479
|
+
this[_pruneDedupable](child)
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1473
1483
|
[_pruneForReplacement] (node, oldDeps) {
|
|
1474
1484
|
// gather up all the invalid edgesOut, and any now-extraneous
|
|
1475
1485
|
// deps that the new node doesn't depend on but the old one did.
|
|
@@ -1622,32 +1632,137 @@ This is a one-time fix-up, please be patient...
|
|
|
1622
1632
|
// placed here as well. the virtualRoot already has the appropriate
|
|
1623
1633
|
// overrides applied.
|
|
1624
1634
|
if (peerEntryEdge) {
|
|
1625
|
-
const
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1635
|
+
const currentPeerSet = getPeerSet(current)
|
|
1636
|
+
|
|
1637
|
+
// We are effectively replacing currentPeerSet with newPeerSet
|
|
1638
|
+
// If there are any non-peer deps coming into the currentPeerSet,
|
|
1639
|
+
// which are currently valid, and are from the target, then that
|
|
1640
|
+
// means that we have to ensure that they're not going to be made
|
|
1641
|
+
// invalid by putting the newPeerSet in place.
|
|
1642
|
+
// If the edge comes from somewhere deeper than the target, then
|
|
1643
|
+
// that's fine, because we'll create an invalid edge, detect it,
|
|
1644
|
+
// and duplicate the node further into the tree.
|
|
1645
|
+
// loop through the currentPeerSet checking for valid edges on
|
|
1646
|
+
// the members of the peer set which will be made invalid.
|
|
1647
|
+
const targetEdges = new Set()
|
|
1648
|
+
for (const p of currentPeerSet) {
|
|
1629
1649
|
for (const edge of p.edgesIn) {
|
|
1630
|
-
|
|
1650
|
+
// edge from within the peerSet, ignore
|
|
1651
|
+
if (currentPeerSet.has(edge.from))
|
|
1652
|
+
continue
|
|
1653
|
+
// only care about valid edges from target.
|
|
1654
|
+
// edges from elsewhere can dupe if offended, invalid edges
|
|
1655
|
+
// are already being fixed or will be later.
|
|
1656
|
+
if (edge.from !== target || !edge.valid)
|
|
1631
1657
|
continue
|
|
1658
|
+
targetEdges.add(edge)
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1632
1661
|
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
//
|
|
1639
|
-
//
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
const
|
|
1643
|
-
if (
|
|
1662
|
+
for (const edge of targetEdges) {
|
|
1663
|
+
// see if we intend to replace this one anyway
|
|
1664
|
+
const rep = dep.parent.children.get(edge.name)
|
|
1665
|
+
const current = edge.to
|
|
1666
|
+
if (!rep) {
|
|
1667
|
+
// this isn't one we're replacing. but it WAS included in the
|
|
1668
|
+
// peerSet for some reason, so make sure that it's still
|
|
1669
|
+
// ok with the replacements in the new peerSet
|
|
1670
|
+
for (const curEdge of current.edgesOut.values()) {
|
|
1671
|
+
const newRepDep = dep.parent.children.get(curEdge.name)
|
|
1672
|
+
if (curEdge.valid && newRepDep && !newRepDep.satisfies(curEdge)) {
|
|
1644
1673
|
canReplace = false
|
|
1645
|
-
break
|
|
1674
|
+
break
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
continue
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
// was this replacement already an override of some sort?
|
|
1681
|
+
const override = [...rep.edgesIn].some(e => !e.valid)
|
|
1682
|
+
// if we have a rep, and it's ok to put in this location, and
|
|
1683
|
+
// it's not already part of an override in the peerSet, then
|
|
1684
|
+
// we can continue with it.
|
|
1685
|
+
if (rep.satisfies(edge) && !override)
|
|
1686
|
+
continue
|
|
1687
|
+
// Otherwise, we cannot replace.
|
|
1688
|
+
canReplace = false
|
|
1689
|
+
break
|
|
1690
|
+
}
|
|
1691
|
+
// if we're going to be replacing the peerSet, we have to remove
|
|
1692
|
+
// and re-resolve any members of the old peerSet that are not
|
|
1693
|
+
// present in the new one, and which will have invalid edges.
|
|
1694
|
+
// We know that they're not depended upon by the target, or else
|
|
1695
|
+
// they would have caused a conflict, so they'll get landed deeper
|
|
1696
|
+
// in the tree, if possible.
|
|
1697
|
+
if (canReplace) {
|
|
1698
|
+
let needNesting = false
|
|
1699
|
+
OUTER: for (const node of currentPeerSet) {
|
|
1700
|
+
const rep = dep.parent.children.get(node.name)
|
|
1701
|
+
// has a replacement, already addressed above
|
|
1702
|
+
if (rep)
|
|
1703
|
+
continue
|
|
1704
|
+
|
|
1705
|
+
// ok, it has been placed here to dedupe, see if it needs to go
|
|
1706
|
+
// back deeper within the tree.
|
|
1707
|
+
for (const edge of node.edgesOut.values()) {
|
|
1708
|
+
const repDep = dep.parent.children.get(edge.name)
|
|
1709
|
+
// not in new peerSet, maybe fine.
|
|
1710
|
+
if (!repDep)
|
|
1711
|
+
continue
|
|
1712
|
+
|
|
1713
|
+
// new thing will be fine, no worries
|
|
1714
|
+
if (repDep.satisfies(edge))
|
|
1715
|
+
continue
|
|
1716
|
+
|
|
1717
|
+
// uhoh, we'll have to nest them.
|
|
1718
|
+
needNesting = true
|
|
1719
|
+
break OUTER
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
// to nest, just delete everything without a target dep
|
|
1724
|
+
// that's in the current peerSet, and add their dependants
|
|
1725
|
+
// to the _depsQueue for evaluation. Some of these MAY end
|
|
1726
|
+
// up in the same location again, and that's fine.
|
|
1727
|
+
if (needNesting) {
|
|
1728
|
+
// avoid mutating the tree while we're examining it
|
|
1729
|
+
const dependants = new Set()
|
|
1730
|
+
const reresolve = new Set()
|
|
1731
|
+
OUTER: for (const node of currentPeerSet) {
|
|
1732
|
+
const rep = dep.parent.children.get(node.name)
|
|
1733
|
+
if (rep)
|
|
1734
|
+
continue
|
|
1735
|
+
// create a separate set for each one, so we can skip any
|
|
1736
|
+
// that might somehow have an incoming target edge
|
|
1737
|
+
const deps = new Set()
|
|
1738
|
+
for (const edge of node.edgesIn) {
|
|
1739
|
+
// a target dep, skip this dep entirely, already addressed
|
|
1740
|
+
// ignoring for coverage, because it really ought to be
|
|
1741
|
+
// impossible, but I can't prove it yet, so this is here
|
|
1742
|
+
// for safety.
|
|
1743
|
+
/* istanbul ignore if - should be impossible */
|
|
1744
|
+
if (edge.from === target)
|
|
1745
|
+
continue OUTER
|
|
1746
|
+
// ignore this edge, it'll either be replaced or re-resolved
|
|
1747
|
+
if (currentPeerSet.has(edge.from))
|
|
1748
|
+
continue
|
|
1749
|
+
// ok, we care about this one.
|
|
1750
|
+
deps.add(edge.from)
|
|
1646
1751
|
}
|
|
1752
|
+
reresolve.add(node)
|
|
1753
|
+
for (const d of deps)
|
|
1754
|
+
dependants.add(d)
|
|
1647
1755
|
}
|
|
1756
|
+
for (const dependant of dependants) {
|
|
1757
|
+
this[_depsQueue].push(dependant)
|
|
1758
|
+
this[_depsSeen].delete(dependant)
|
|
1759
|
+
}
|
|
1760
|
+
for (const node of reresolve)
|
|
1761
|
+
node.root = null
|
|
1648
1762
|
}
|
|
1649
1763
|
}
|
|
1650
1764
|
}
|
|
1765
|
+
|
|
1651
1766
|
if (canReplace) {
|
|
1652
1767
|
const ret = this[_canPlacePeers](dep, target, edge, REPLACE, peerEntryEdge, peerPath, isSource)
|
|
1653
1768
|
/* istanbul ignore else - extremely rare that the peer set would
|
package/lib/arborist/index.js
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
const {resolve} = require('path')
|
|
30
30
|
const {homedir} = require('os')
|
|
31
31
|
const procLog = require('../proc-log.js')
|
|
32
|
+
const { saveTypeMap } = require('../add-rm-pkg-deps.js')
|
|
32
33
|
|
|
33
34
|
const mixins = [
|
|
34
35
|
require('../tracker.js'),
|
|
@@ -57,6 +58,8 @@ class Arborist extends Base {
|
|
|
57
58
|
packumentCache: options.packumentCache || new Map(),
|
|
58
59
|
log: options.log || procLog,
|
|
59
60
|
}
|
|
61
|
+
if (options.saveType && !saveTypeMap.get(options.saveType))
|
|
62
|
+
throw new Error(`Invalid saveType ${options.saveType}`)
|
|
60
63
|
this.cache = resolve(this.options.cache)
|
|
61
64
|
this.path = resolve(this.options.path)
|
|
62
65
|
process.emit('timeEnd', 'arborist:ctor')
|
|
@@ -159,12 +159,12 @@ module.exports = cls => class VirtualLoader extends cls {
|
|
|
159
159
|
...depsToEdges('peerOptional', peerOptional),
|
|
160
160
|
...lockWS,
|
|
161
161
|
].sort(([atype, aname], [btype, bname]) =>
|
|
162
|
-
atype.localeCompare(btype) || aname.localeCompare(bname))
|
|
162
|
+
atype.localeCompare(btype, 'en') || aname.localeCompare(bname, 'en'))
|
|
163
163
|
|
|
164
164
|
const rootEdges = [...root.edgesOut.values()]
|
|
165
165
|
.map(e => [e.type, e.name, e.spec])
|
|
166
166
|
.sort(([atype, aname], [btype, bname]) =>
|
|
167
|
-
atype.localeCompare(btype) || aname.localeCompare(bname))
|
|
167
|
+
atype.localeCompare(btype, 'en') || aname.localeCompare(bname, 'en'))
|
|
168
168
|
|
|
169
169
|
if (rootEdges.length !== lockEdges.length) {
|
|
170
170
|
// something added or removed
|
package/lib/arborist/rebuild.js
CHANGED
|
@@ -14,7 +14,7 @@ const {
|
|
|
14
14
|
} = require('@npmcli/node-gyp')
|
|
15
15
|
|
|
16
16
|
const boolEnv = b => b ? '1' : ''
|
|
17
|
-
const sortNodes = (a, b) => (a.depth - b.depth) || a.path.localeCompare(b.path)
|
|
17
|
+
const sortNodes = (a, b) => (a.depth - b.depth) || a.path.localeCompare(b.path, 'en')
|
|
18
18
|
|
|
19
19
|
const _build = Symbol('build')
|
|
20
20
|
const _resetQueues = Symbol('resetQueues')
|
package/lib/arborist/reify.js
CHANGED
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
const onExit = require('../signal-handling.js')
|
|
4
4
|
const pacote = require('pacote')
|
|
5
5
|
const rpj = require('read-package-json-fast')
|
|
6
|
-
const { updateDepSpec } = require('../dep-spec.js')
|
|
7
6
|
const AuditReport = require('../audit-report.js')
|
|
8
|
-
const {subset} = require('semver')
|
|
7
|
+
const {subset, intersects} = require('semver')
|
|
9
8
|
const npa = require('npm-package-arg')
|
|
10
9
|
|
|
11
10
|
const {dirname, resolve, relative} = require('path')
|
|
@@ -28,6 +27,7 @@ const promiseAllRejectLate = require('promise-all-reject-late')
|
|
|
28
27
|
const optionalSet = require('../optional-set.js')
|
|
29
28
|
const updateRootPackageJson = require('../update-root-package-json.js')
|
|
30
29
|
const calcDepFlags = require('../calc-dep-flags.js')
|
|
30
|
+
const { saveTypeMap, hasSubKey } = require('../add-rm-pkg-deps.js')
|
|
31
31
|
|
|
32
32
|
const _retiredPaths = Symbol('retiredPaths')
|
|
33
33
|
const _retiredUnchanged = Symbol('retiredUnchanged')
|
|
@@ -406,11 +406,14 @@ module.exports = cls => class Reifier extends cls {
|
|
|
406
406
|
return
|
|
407
407
|
|
|
408
408
|
process.emit('time', 'reify:trashOmits')
|
|
409
|
+
// node.parent is checked to make sure this is a node that's in the tree, and
|
|
410
|
+
// not the parent-less top level nodes
|
|
409
411
|
const filter = node =>
|
|
410
|
-
node.
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
412
|
+
node.isDescendantOf(this.idealTree) &&
|
|
413
|
+
(node.peer && this[_omitPeer] ||
|
|
414
|
+
node.dev && this[_omitDev] ||
|
|
415
|
+
node.optional && this[_omitOptional] ||
|
|
416
|
+
node.devOptional && this[_omitOptional] && this[_omitDev])
|
|
414
417
|
|
|
415
418
|
for (const node of this.idealTree.inventory.filter(filter))
|
|
416
419
|
this[_addNodeToTrashList](node)
|
|
@@ -539,8 +542,8 @@ module.exports = cls => class Reifier extends cls {
|
|
|
539
542
|
// Do the best with what we have, or else remove it from the tree
|
|
540
543
|
// entirely, since we can't possibly reify it.
|
|
541
544
|
const res = node.resolved ? `${node.name}@${this[_registryResolved](node.resolved)}`
|
|
542
|
-
: node.
|
|
543
|
-
? `${node.
|
|
545
|
+
: node.packageName && node.version
|
|
546
|
+
? `${node.packageName}@${node.version}`
|
|
544
547
|
: null
|
|
545
548
|
|
|
546
549
|
// no idea what this thing is. remove it from the tree.
|
|
@@ -959,6 +962,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
959
962
|
const spec = subSpec ? subSpec.rawSpec : rawSpec
|
|
960
963
|
const child = root.children.get(name)
|
|
961
964
|
|
|
965
|
+
let newSpec
|
|
962
966
|
if (req.registry) {
|
|
963
967
|
const version = child.version
|
|
964
968
|
const prefixRange = version ? this[_savePrefix] + version : '*'
|
|
@@ -968,18 +972,26 @@ module.exports = cls => class Reifier extends cls {
|
|
|
968
972
|
// would allow versions outside the requested range. Tags and
|
|
969
973
|
// specific versions save with the save-prefix.
|
|
970
974
|
const isRange = (subSpec || req).type === 'range'
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
975
|
+
|
|
976
|
+
let range = spec
|
|
977
|
+
if (
|
|
978
|
+
!isRange ||
|
|
979
|
+
spec === '*' ||
|
|
980
|
+
subset(prefixRange, spec, { loose: true })
|
|
981
|
+
)
|
|
982
|
+
range = prefixRange
|
|
983
|
+
|
|
984
|
+
const pname = child.packageName
|
|
974
985
|
const alias = name !== pname
|
|
975
|
-
|
|
986
|
+
newSpec = alias ? `npm:${pname}@${range}` : range
|
|
976
987
|
} else if (req.hosted) {
|
|
977
988
|
// save the git+https url if it has auth, otherwise shortcut
|
|
978
989
|
const h = req.hosted
|
|
979
990
|
const opt = { noCommittish: false }
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
991
|
+
if (h.https && h.auth)
|
|
992
|
+
newSpec = `git+${h.https(opt)}`
|
|
993
|
+
else
|
|
994
|
+
newSpec = h.shortcut(opt)
|
|
983
995
|
} else if (req.type === 'directory' || req.type === 'file') {
|
|
984
996
|
// save the relative path in package.json
|
|
985
997
|
// Normally saveSpec is updated with the proper relative
|
|
@@ -988,9 +1000,37 @@ module.exports = cls => class Reifier extends cls {
|
|
|
988
1000
|
// thing, so just get the ultimate fetchSpec and relativize it.
|
|
989
1001
|
const p = req.fetchSpec.replace(/^file:/, '')
|
|
990
1002
|
const rel = relpath(root.realpath, p)
|
|
991
|
-
|
|
1003
|
+
newSpec = `file:${rel}`
|
|
992
1004
|
} else
|
|
993
|
-
|
|
1005
|
+
newSpec = req.saveSpec
|
|
1006
|
+
|
|
1007
|
+
if (options.saveType) {
|
|
1008
|
+
const depType = saveTypeMap.get(options.saveType)
|
|
1009
|
+
pkg[depType][name] = newSpec
|
|
1010
|
+
// rpj will have moved it here if it was in both
|
|
1011
|
+
// if it is empty it will be deleted later
|
|
1012
|
+
if (options.saveType === 'prod' && pkg.optionalDependencies)
|
|
1013
|
+
delete pkg.optionalDependencies[name]
|
|
1014
|
+
} else {
|
|
1015
|
+
if (hasSubKey(pkg, 'dependencies', name))
|
|
1016
|
+
pkg.dependencies[name] = newSpec
|
|
1017
|
+
|
|
1018
|
+
if (hasSubKey(pkg, 'devDependencies', name)) {
|
|
1019
|
+
pkg.devDependencies[name] = newSpec
|
|
1020
|
+
// don't update peer or optional if we don't have to
|
|
1021
|
+
if (hasSubKey(pkg, 'peerDependencies', name) && !intersects(newSpec, pkg.peerDependencies[name]))
|
|
1022
|
+
pkg.peerDependencies[name] = newSpec
|
|
1023
|
+
|
|
1024
|
+
if (hasSubKey(pkg, 'optionalDependencies', name) && !intersects(newSpec, pkg.optionalDependencies[name]))
|
|
1025
|
+
pkg.optionalDependencies[name] = newSpec
|
|
1026
|
+
} else {
|
|
1027
|
+
if (hasSubKey(pkg, 'peerDependencies', name))
|
|
1028
|
+
pkg.peerDependencies[name] = newSpec
|
|
1029
|
+
|
|
1030
|
+
if (hasSubKey(pkg, 'optionalDependencies', name))
|
|
1031
|
+
pkg.optionalDependencies[name] = newSpec
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
994
1034
|
}
|
|
995
1035
|
|
|
996
1036
|
// refresh the edges so they have the correct specs
|
package/lib/audit-report.js
CHANGED
|
@@ -78,7 +78,7 @@ class AuditReport extends Map {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
obj.vulnerabilities = vulnerabilities
|
|
81
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
81
|
+
.sort(([a], [b]) => a.localeCompare(b, 'en'))
|
|
82
82
|
.reduce((set, [name, vuln]) => {
|
|
83
83
|
set[name] = vuln
|
|
84
84
|
return set
|
|
@@ -101,13 +101,14 @@ class AuditReport extends Map {
|
|
|
101
101
|
|
|
102
102
|
async run () {
|
|
103
103
|
this.report = await this[_getReport]()
|
|
104
|
+
this.log.silly('audit report', this.report)
|
|
104
105
|
if (this.report)
|
|
105
106
|
await this[_init]()
|
|
106
107
|
return this
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
isVulnerable (node) {
|
|
110
|
-
const vuln = this.get(node.
|
|
111
|
+
const vuln = this.get(node.packageName)
|
|
111
112
|
return !!(vuln && vuln.isVulnerable(node))
|
|
112
113
|
}
|
|
113
114
|
|
|
@@ -144,7 +145,7 @@ class AuditReport extends Map {
|
|
|
144
145
|
super.set(name, vuln)
|
|
145
146
|
|
|
146
147
|
const p = []
|
|
147
|
-
for (const node of this.tree.inventory.query('
|
|
148
|
+
for (const node of this.tree.inventory.query('packageName', name)) {
|
|
148
149
|
if (shouldOmit(node, this[_omit]))
|
|
149
150
|
continue
|
|
150
151
|
|
|
@@ -167,7 +168,7 @@ class AuditReport extends Map {
|
|
|
167
168
|
this[_checkTopNode](dep, vuln, spec)
|
|
168
169
|
else {
|
|
169
170
|
// calculate a metavuln, if necessary
|
|
170
|
-
p.push(this.calculator.calculate(dep.
|
|
171
|
+
p.push(this.calculator.calculate(dep.packageName, advisory).then(meta => {
|
|
171
172
|
if (meta.testVersion(dep.version, spec))
|
|
172
173
|
advisories.add(meta)
|
|
173
174
|
}))
|
|
@@ -228,6 +229,9 @@ class AuditReport extends Map {
|
|
|
228
229
|
if (!specObj.registry)
|
|
229
230
|
return false
|
|
230
231
|
|
|
232
|
+
if (specObj.subSpec)
|
|
233
|
+
spec = specObj.subSpec.rawSpec
|
|
234
|
+
|
|
231
235
|
// We don't provide fixes for top nodes other than root, but we
|
|
232
236
|
// still check to see if the node is fixable with a different version,
|
|
233
237
|
// and if that is a semver major bump.
|
|
@@ -289,6 +293,7 @@ class AuditReport extends Map {
|
|
|
289
293
|
try {
|
|
290
294
|
// first try the super fast bulk advisory listing
|
|
291
295
|
const body = prepareBulkData(this.tree, this[_omit])
|
|
296
|
+
this.log.silly('audit', 'bulk request', body)
|
|
292
297
|
|
|
293
298
|
// no sense asking if we don't have anything to audit,
|
|
294
299
|
// we know it'll be empty
|
|
@@ -304,7 +309,8 @@ class AuditReport extends Map {
|
|
|
304
309
|
})
|
|
305
310
|
|
|
306
311
|
return await res.json()
|
|
307
|
-
} catch (
|
|
312
|
+
} catch (er) {
|
|
313
|
+
this.log.silly('audit', 'bulk request failed', String(er.body))
|
|
308
314
|
// that failed, try the quick audit endpoint
|
|
309
315
|
const body = prepareData(this.tree, this.options)
|
|
310
316
|
const res = await fetch('/-/npm/v1/security/audits/quick', {
|
|
@@ -330,6 +336,7 @@ class AuditReport extends Map {
|
|
|
330
336
|
// return true if we should ignore this one
|
|
331
337
|
const shouldOmit = (node, omit) =>
|
|
332
338
|
!node.version ? true
|
|
339
|
+
: node.isRoot ? true
|
|
333
340
|
: omit.size === 0 ? false
|
|
334
341
|
: node.dev && omit.has('dev') ||
|
|
335
342
|
node.optional && omit.has('optional') ||
|
|
@@ -338,9 +345,9 @@ const shouldOmit = (node, omit) =>
|
|
|
338
345
|
|
|
339
346
|
const prepareBulkData = (tree, omit) => {
|
|
340
347
|
const payload = {}
|
|
341
|
-
for (const name of tree.inventory.query('
|
|
348
|
+
for (const name of tree.inventory.query('packageName')) {
|
|
342
349
|
const set = new Set()
|
|
343
|
-
for (const node of tree.inventory.query('
|
|
350
|
+
for (const node of tree.inventory.query('packageName', name)) {
|
|
344
351
|
if (shouldOmit(node, omit))
|
|
345
352
|
continue
|
|
346
353
|
|
package/lib/inventory.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// keys is the set of fields to be able to query.
|
|
5
5
|
const _primaryKey = Symbol('_primaryKey')
|
|
6
6
|
const _index = Symbol('_index')
|
|
7
|
-
const defaultKeys = ['name', 'license', 'funding', 'realpath']
|
|
7
|
+
const defaultKeys = ['name', 'license', 'funding', 'realpath', 'packageName']
|
|
8
8
|
const { hasOwnProperty } = Object.prototype
|
|
9
9
|
const debug = require('./debug.js')
|
|
10
10
|
class Inventory extends Map {
|
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')
|
|
@@ -290,6 +291,10 @@ class Node {
|
|
|
290
291
|
return this[_package].version || ''
|
|
291
292
|
}
|
|
292
293
|
|
|
294
|
+
get packageName () {
|
|
295
|
+
return this[_package].name || null
|
|
296
|
+
}
|
|
297
|
+
|
|
293
298
|
get pkgid () {
|
|
294
299
|
const { name = '', version = '' } = this.package
|
|
295
300
|
// root package will prefer package name over folder name,
|
|
@@ -349,10 +354,10 @@ class Node {
|
|
|
349
354
|
}
|
|
350
355
|
|
|
351
356
|
const why = {
|
|
352
|
-
name: this.isProjectRoot ? this.
|
|
357
|
+
name: this.isProjectRoot ? this.packageName : this.name,
|
|
353
358
|
version: this.package.version,
|
|
354
359
|
}
|
|
355
|
-
if (this.errors.length || !this.
|
|
360
|
+
if (this.errors.length || !this.packageName || !this.package.version) {
|
|
356
361
|
why.errors = this.errors.length ? this.errors : [
|
|
357
362
|
new Error('invalid package: lacks name and/or version'),
|
|
358
363
|
]
|
|
@@ -459,7 +464,7 @@ class Node {
|
|
|
459
464
|
if (this.isProjectRoot)
|
|
460
465
|
return false
|
|
461
466
|
const { root } = this
|
|
462
|
-
const { type, to } = root.edgesOut.get(this.
|
|
467
|
+
const { type, to } = root.edgesOut.get(this.packageName) || {}
|
|
463
468
|
return type === 'workspace' && to && (to.target === this || to === this)
|
|
464
469
|
}
|
|
465
470
|
|
|
@@ -729,20 +734,14 @@ class Node {
|
|
|
729
734
|
|
|
730
735
|
[_loadDeps] () {
|
|
731
736
|
// Caveat! Order is relevant!
|
|
732
|
-
//
|
|
733
|
-
//
|
|
737
|
+
// Packages in optionalDependencies are optional.
|
|
738
|
+
// Packages in both deps and devDeps are required.
|
|
734
739
|
// Note the subtle breaking change from v6: it is no longer possible
|
|
735
740
|
// to have a different spec for a devDep than production dep.
|
|
736
|
-
this[_loadDepType](this.package.optionalDependencies, 'optional')
|
|
737
741
|
|
|
738
742
|
// Linked targets that are disconnected from the tree are tops,
|
|
739
743
|
// but don't have a 'path' field, only a 'realpath', because we
|
|
740
744
|
// don't know their canonical location. We don't need their devDeps.
|
|
741
|
-
const { isTop, path, sourceReference } = this
|
|
742
|
-
const { isTop: srcTop, path: srcPath } = sourceReference || {}
|
|
743
|
-
if (isTop && path && (!sourceReference || srcTop && srcPath))
|
|
744
|
-
this[_loadDepType](this.package.devDependencies, 'dev')
|
|
745
|
-
|
|
746
745
|
const pd = this.package.peerDependencies
|
|
747
746
|
if (pd && typeof pd === 'object' && !this.legacyPeerDeps) {
|
|
748
747
|
const pm = this.package.peerDependenciesMeta || {}
|
|
@@ -759,19 +758,22 @@ class Node {
|
|
|
759
758
|
}
|
|
760
759
|
|
|
761
760
|
this[_loadDepType](this.package.dependencies, 'prod')
|
|
761
|
+
this[_loadDepType](this.package.optionalDependencies, 'optional')
|
|
762
|
+
|
|
763
|
+
const { isTop, path, sourceReference } = this
|
|
764
|
+
const { isTop: srcTop, path: srcPath } = sourceReference || {}
|
|
765
|
+
if (isTop && path && (!sourceReference || srcTop && srcPath))
|
|
766
|
+
this[_loadDepType](this.package.devDependencies, 'dev')
|
|
762
767
|
}
|
|
763
768
|
|
|
764
|
-
[_loadDepType] (
|
|
765
|
-
const from = this
|
|
769
|
+
[_loadDepType] (deps, type) {
|
|
766
770
|
const ad = this.package.acceptDependencies || {}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
// Prod deps should not be marked as dev, however.
|
|
771
|
-
// NB: the Edge ctor adds itself to from.edgesOut
|
|
771
|
+
// Because of the order in which _loadDeps runs, we always want to
|
|
772
|
+
// prioritize a new edge over an existing one
|
|
773
|
+
for (const [name, spec] of Object.entries(deps || {})) {
|
|
772
774
|
const current = this.edgesOut.get(name)
|
|
773
|
-
if (!current || current.
|
|
774
|
-
new Edge({ from, name, spec, accept, type })
|
|
775
|
+
if (!current || current.type !== 'workspace')
|
|
776
|
+
new Edge({ from: this, name, spec, accept: ad[name], type })
|
|
775
777
|
}
|
|
776
778
|
}
|
|
777
779
|
|
|
@@ -885,6 +887,43 @@ class Node {
|
|
|
885
887
|
return node.canReplaceWith(this)
|
|
886
888
|
}
|
|
887
889
|
|
|
890
|
+
// return true if it's safe to remove this node, because anything that
|
|
891
|
+
// is depending on it would be fine with the thing that they would resolve
|
|
892
|
+
// to if it was removed, or nothing is depending on it in the first place.
|
|
893
|
+
canDedupe (preferDedupe = false) {
|
|
894
|
+
// not allowed to mess with shrinkwraps or bundles
|
|
895
|
+
if (this.inDepBundle || this.inShrinkwrap)
|
|
896
|
+
return false
|
|
897
|
+
|
|
898
|
+
// it's a top level pkg, or a dep of one
|
|
899
|
+
if (!this.parent || !this.parent.parent)
|
|
900
|
+
return false
|
|
901
|
+
|
|
902
|
+
// no one wants it, remove it
|
|
903
|
+
if (this.edgesIn.size === 0)
|
|
904
|
+
return true
|
|
905
|
+
|
|
906
|
+
const other = this.parent.parent.resolve(this.name)
|
|
907
|
+
|
|
908
|
+
// nothing else, need this one
|
|
909
|
+
if (!other)
|
|
910
|
+
return false
|
|
911
|
+
|
|
912
|
+
// if it's the same thing, then always fine to remove
|
|
913
|
+
if (other.matches(this))
|
|
914
|
+
return true
|
|
915
|
+
|
|
916
|
+
// if the other thing can't replace this, then skip it
|
|
917
|
+
if (!other.canReplace(this))
|
|
918
|
+
return false
|
|
919
|
+
|
|
920
|
+
// if we prefer dedupe, or if the version is greater/equal, take the other
|
|
921
|
+
if (preferDedupe || semver.gte(other.version, this.version))
|
|
922
|
+
return true
|
|
923
|
+
|
|
924
|
+
return false
|
|
925
|
+
}
|
|
926
|
+
|
|
888
927
|
satisfies (requested) {
|
|
889
928
|
if (requested instanceof Edge)
|
|
890
929
|
return this.name === requested.name && requested.satisfiedBy(this)
|
|
@@ -927,8 +966,8 @@ class Node {
|
|
|
927
966
|
|
|
928
967
|
// if no resolved, check both package name and version
|
|
929
968
|
// otherwise, conclude that they are different things
|
|
930
|
-
return this.
|
|
931
|
-
this.
|
|
969
|
+
return this.packageName && node.packageName &&
|
|
970
|
+
this.packageName === node.packageName &&
|
|
932
971
|
this.version && node.version &&
|
|
933
972
|
this.version === node.version
|
|
934
973
|
}
|
package/lib/printable.js
CHANGED
|
@@ -7,8 +7,8 @@ const relpath = require('./relpath.js')
|
|
|
7
7
|
class ArboristNode {
|
|
8
8
|
constructor (tree, path) {
|
|
9
9
|
this.name = tree.name
|
|
10
|
-
if (tree.
|
|
11
|
-
this.packageName = tree.
|
|
10
|
+
if (tree.packageName && tree.packageName !== this.name)
|
|
11
|
+
this.packageName = tree.packageName
|
|
12
12
|
if (tree.version)
|
|
13
13
|
this.version = tree.version
|
|
14
14
|
this.location = tree.location
|
|
@@ -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)
|
|
@@ -37,14 +46,14 @@ class ArboristNode {
|
|
|
37
46
|
// edgesOut sorted by name
|
|
38
47
|
if (tree.edgesOut.size) {
|
|
39
48
|
this.edgesOut = new Map([...tree.edgesOut.entries()]
|
|
40
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
49
|
+
.sort(([a], [b]) => a.localeCompare(b, 'en'))
|
|
41
50
|
.map(([name, edge]) => [name, new EdgeOut(edge)]))
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
// edgesIn sorted by location
|
|
45
54
|
if (tree.edgesIn.size) {
|
|
46
55
|
this.edgesIn = new Set([...tree.edgesIn]
|
|
47
|
-
.sort((a, b) => a.from.location.localeCompare(b.from.location))
|
|
56
|
+
.sort((a, b) => a.from.location.localeCompare(b.from.location, 'en'))
|
|
48
57
|
.map(edge => new EdgeIn(edge)))
|
|
49
58
|
}
|
|
50
59
|
|
|
@@ -56,14 +65,14 @@ class ArboristNode {
|
|
|
56
65
|
// fsChildren sorted by path
|
|
57
66
|
if (tree.fsChildren.size) {
|
|
58
67
|
this.fsChildren = new Set([...tree.fsChildren]
|
|
59
|
-
.sort(({path: a}, {path: b}) => a.localeCompare(b))
|
|
68
|
+
.sort(({path: a}, {path: b}) => a.localeCompare(b, 'en'))
|
|
60
69
|
.map(tree => printableTree(tree, path)))
|
|
61
70
|
}
|
|
62
71
|
|
|
63
72
|
// children sorted by name
|
|
64
73
|
if (tree.children.size) {
|
|
65
74
|
this.children = new Map([...tree.children.entries()]
|
|
66
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
75
|
+
.sort(([a], [b]) => a.localeCompare(b, 'en'))
|
|
67
76
|
.map(([name, tree]) => [name, printableTree(tree, path)]))
|
|
68
77
|
}
|
|
69
78
|
}
|
package/lib/shrinkwrap.js
CHANGED
|
@@ -254,7 +254,7 @@ class Shrinkwrap {
|
|
|
254
254
|
meta[key.replace(/^_/, '')] = val
|
|
255
255
|
})
|
|
256
256
|
// we only include name if different from the node path name
|
|
257
|
-
const pname = node.
|
|
257
|
+
const pname = node.packageName
|
|
258
258
|
if (pname && pname !== node.name)
|
|
259
259
|
meta.name = pname
|
|
260
260
|
|
|
@@ -825,7 +825,7 @@ class Shrinkwrap {
|
|
|
825
825
|
[_buildLegacyLockfile] (node, lock, path = []) {
|
|
826
826
|
if (node === this.tree) {
|
|
827
827
|
// the root node
|
|
828
|
-
lock.name = node.
|
|
828
|
+
lock.name = node.packageName || node.name
|
|
829
829
|
if (node.version)
|
|
830
830
|
lock.version = node.version
|
|
831
831
|
}
|
|
@@ -844,7 +844,7 @@ class Shrinkwrap {
|
|
|
844
844
|
/* istanbul ignore next - sort calling order is indeterminate */
|
|
845
845
|
return aloc.length > bloc.length ? 1
|
|
846
846
|
: bloc.length > aloc.length ? -1
|
|
847
|
-
: aloc[aloc.length - 1].localeCompare(bloc[bloc.length - 1])
|
|
847
|
+
: aloc[aloc.length - 1].localeCompare(bloc[bloc.length - 1], 'en')
|
|
848
848
|
})[0]
|
|
849
849
|
|
|
850
850
|
const res = consistentResolve(node.resolved, this.path, this.path, true)
|
|
@@ -870,9 +870,9 @@ class Shrinkwrap {
|
|
|
870
870
|
lock.from = spec.raw
|
|
871
871
|
} else if (!node.isRoot &&
|
|
872
872
|
node.package &&
|
|
873
|
-
node.
|
|
874
|
-
node.
|
|
875
|
-
lock.version = `npm:${node.
|
|
873
|
+
node.packageName &&
|
|
874
|
+
node.packageName !== node.name)
|
|
875
|
+
lock.version = `npm:${node.packageName}@${node.version}`
|
|
876
876
|
else if (node.package && node.version)
|
|
877
877
|
lock.version = node.version
|
|
878
878
|
|
|
@@ -6,8 +6,6 @@ const {resolve} = require('path')
|
|
|
6
6
|
|
|
7
7
|
const parseJSON = require('json-parse-even-better-errors')
|
|
8
8
|
|
|
9
|
-
const { orderDeps } = require('./dep-spec.js')
|
|
10
|
-
|
|
11
9
|
const depTypes = new Set([
|
|
12
10
|
'dependencies',
|
|
13
11
|
'optionalDependencies',
|
|
@@ -15,6 +13,20 @@ const depTypes = new Set([
|
|
|
15
13
|
'peerDependencies',
|
|
16
14
|
])
|
|
17
15
|
|
|
16
|
+
// sort alphabetically all types of deps for a given package
|
|
17
|
+
const orderDeps = (pkg) => {
|
|
18
|
+
for (const type of depTypes) {
|
|
19
|
+
if (pkg && pkg[type]) {
|
|
20
|
+
pkg[type] = Object.keys(pkg[type])
|
|
21
|
+
.sort((a, b) => a.localeCompare(b, 'en'))
|
|
22
|
+
.reduce((res, key) => {
|
|
23
|
+
res[key] = pkg[type][key]
|
|
24
|
+
return res
|
|
25
|
+
}, {})
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return pkg
|
|
29
|
+
}
|
|
18
30
|
const parseJsonSafe = json => {
|
|
19
31
|
try {
|
|
20
32
|
return parseJSON(json)
|
package/lib/vuln.js
CHANGED
|
@@ -83,6 +83,9 @@ class Vuln {
|
|
|
83
83
|
if (!specObj.registry)
|
|
84
84
|
return true
|
|
85
85
|
|
|
86
|
+
if (specObj.subSpec)
|
|
87
|
+
spec = specObj.subSpec.rawSpec
|
|
88
|
+
|
|
86
89
|
for (const v of this.versions) {
|
|
87
90
|
if (satisfies(v, spec) && !satisfies(v, this.range, semverOpt))
|
|
88
91
|
return false
|
|
@@ -103,12 +106,12 @@ class Vuln {
|
|
|
103
106
|
vulnerableVersions: undefined,
|
|
104
107
|
id: undefined,
|
|
105
108
|
}).sort((a, b) =>
|
|
106
|
-
String(a.source || a).localeCompare(String(b.source || b))),
|
|
109
|
+
String(a.source || a).localeCompare(String(b.source || b, 'en'))),
|
|
107
110
|
effects: [...this.effects].map(v => v.name)
|
|
108
|
-
.sort(/* istanbul ignore next */(a, b) => a.localeCompare(b)),
|
|
111
|
+
.sort(/* istanbul ignore next */(a, b) => a.localeCompare(b, 'en')),
|
|
109
112
|
range: this.simpleRange,
|
|
110
113
|
nodes: [...this.nodes].map(n => n.location)
|
|
111
|
-
.sort(/* istanbul ignore next */(a, b) => a.localeCompare(b)),
|
|
114
|
+
.sort(/* istanbul ignore next */(a, b) => a.localeCompare(b, 'en')),
|
|
112
115
|
fixAvailable: this[_fixAvailable],
|
|
113
116
|
}
|
|
114
117
|
}
|
package/lib/yarn-lock.js
CHANGED
|
@@ -34,7 +34,7 @@ const {breadth} = require('treeverse')
|
|
|
34
34
|
|
|
35
35
|
// sort a key/value object into a string of JSON stringified keys and vals
|
|
36
36
|
const sortKV = obj => Object.keys(obj)
|
|
37
|
-
.sort((a, b) => a.localeCompare(b))
|
|
37
|
+
.sort((a, b) => a.localeCompare(b, 'en'))
|
|
38
38
|
.map(k => ` ${JSON.stringify(k)} ${JSON.stringify(obj[k])}`)
|
|
39
39
|
.join('\n')
|
|
40
40
|
|
|
@@ -165,7 +165,7 @@ class YarnLock {
|
|
|
165
165
|
toString () {
|
|
166
166
|
return prefix + [...new Set([...this.entries.values()])]
|
|
167
167
|
.map(e => e.toString())
|
|
168
|
-
.sort((a, b) => a.localeCompare(b)).join('\n\n') + '\n'
|
|
168
|
+
.sort((a, b) => a.localeCompare(b, 'en')).join('\n\n') + '\n'
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
fromTree (tree) {
|
|
@@ -175,7 +175,7 @@ class YarnLock {
|
|
|
175
175
|
tree,
|
|
176
176
|
visit: node => this.addEntryFromNode(node),
|
|
177
177
|
getChildren: node => [...node.children.values(), ...node.fsChildren]
|
|
178
|
-
.sort((a, b) => a.depth - b.depth || a.name.localeCompare(b.name)),
|
|
178
|
+
.sort((a, b) => a.depth - b.depth || a.name.localeCompare(b.name, 'en')),
|
|
179
179
|
})
|
|
180
180
|
return this
|
|
181
181
|
}
|
|
@@ -183,7 +183,7 @@ class YarnLock {
|
|
|
183
183
|
addEntryFromNode (node) {
|
|
184
184
|
const specs = [...node.edgesIn]
|
|
185
185
|
.map(e => `${node.name}@${e.spec}`)
|
|
186
|
-
.sort((a, b) => a.localeCompare(b))
|
|
186
|
+
.sort((a, b) => a.localeCompare(b, 'en'))
|
|
187
187
|
|
|
188
188
|
// Note:
|
|
189
189
|
// yarn will do excessive duplication in a case like this:
|
|
@@ -309,7 +309,7 @@ class YarnLockEntry {
|
|
|
309
309
|
toString () {
|
|
310
310
|
// sort objects to the bottom, then alphabetical
|
|
311
311
|
return ([...this[_specs]]
|
|
312
|
-
.sort((a, b) => a.localeCompare(b))
|
|
312
|
+
.sort((a, b) => a.localeCompare(b, 'en'))
|
|
313
313
|
.map(JSON.stringify).join(', ') +
|
|
314
314
|
':\n' +
|
|
315
315
|
Object.getOwnPropertyNames(this)
|
|
@@ -318,7 +318,7 @@ class YarnLockEntry {
|
|
|
318
318
|
(a, b) =>
|
|
319
319
|
/* istanbul ignore next - sort call order is unpredictable */
|
|
320
320
|
(typeof this[a] === 'object') === (typeof this[b] === 'object')
|
|
321
|
-
? a.localeCompare(b)
|
|
321
|
+
? a.localeCompare(b, 'en')
|
|
322
322
|
: typeof this[a] === 'object' ? 1 : -1)
|
|
323
323
|
.map(prop =>
|
|
324
324
|
typeof this[prop] !== 'object'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@npmcli/arborist",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.4",
|
|
4
4
|
"description": "Manage node_modules trees",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@npmcli/installed-package-contents": "^1.0.7",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"cacache": "^15.0.3",
|
|
15
15
|
"common-ancestor-path": "^1.0.1",
|
|
16
16
|
"json-parse-even-better-errors": "^2.3.1",
|
|
17
|
-
"json-stringify-nice": "^1.1.
|
|
17
|
+
"json-stringify-nice": "^1.1.4",
|
|
18
18
|
"mkdirp-infer-owner": "^2.0.0",
|
|
19
19
|
"npm-install-checks": "^4.0.0",
|
|
20
20
|
"npm-package-arg": "^8.1.0",
|
|
@@ -40,9 +40,8 @@
|
|
|
40
40
|
"eslint-plugin-promise": "^4.2.1",
|
|
41
41
|
"eslint-plugin-standard": "^4.0.1",
|
|
42
42
|
"minify-registry-metadata": "^2.1.0",
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"tcompare": "^3.0.4"
|
|
43
|
+
"tap": "^15.0.9",
|
|
44
|
+
"tcompare": "^5.0.6"
|
|
46
45
|
},
|
|
47
46
|
"scripts": {
|
|
48
47
|
"test": "npm run test-only --",
|
|
@@ -74,11 +73,13 @@
|
|
|
74
73
|
"bin": {
|
|
75
74
|
"arborist": "bin/index.js"
|
|
76
75
|
},
|
|
76
|
+
"//": "sk test-env locale to catch locale-specific sorting",
|
|
77
77
|
"tap": {
|
|
78
78
|
"after": "test/fixtures/cleanup.js",
|
|
79
79
|
"coverage-map": "map.js",
|
|
80
80
|
"test-env": [
|
|
81
|
-
"NODE_OPTIONS=--no-warnings"
|
|
81
|
+
"NODE_OPTIONS=--no-warnings",
|
|
82
|
+
"LC_ALL=sk"
|
|
82
83
|
],
|
|
83
84
|
"node-arg": [
|
|
84
85
|
"--no-warnings",
|
package/CHANGELOG.md
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# CHANGELOG
|
|
2
|
-
|
|
3
|
-
## 2.0
|
|
4
|
-
|
|
5
|
-
* BREAKING CHANGE: root node is now included in inventory
|
|
6
|
-
* All parent/target/fsParent/etc. references set in `root` setter, rather
|
|
7
|
-
than the hodgepodge of setters that existed before.
|
|
8
|
-
* `treeCheck` function added, to enforce strict correctness guarantees when
|
|
9
|
-
`ARBORIST_DEBUG=1` in the environment (on by default in Arborist tests).
|
|
10
|
-
|
|
11
|
-
## 1.0
|
|
12
|
-
|
|
13
|
-
* Release for npm v7 beta
|
|
14
|
-
* Fully functional
|
|
15
|
-
|
|
16
|
-
## 0.0
|
|
17
|
-
|
|
18
|
-
* Proof of concept
|
|
19
|
-
* Before this, it was [`read-package-tree`](http://npm.im/read-package-tree)
|
package/lib/dep-spec.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
const types = [
|
|
2
|
-
'peerDependencies',
|
|
3
|
-
'devDependencies',
|
|
4
|
-
'optionalDependencies',
|
|
5
|
-
'dependencies',
|
|
6
|
-
]
|
|
7
|
-
|
|
8
|
-
const findType = (pkg, name) => {
|
|
9
|
-
for (const t of types) {
|
|
10
|
-
if (pkg[t] && typeof pkg[t] === 'object' && pkg[t][name] !== undefined)
|
|
11
|
-
return t
|
|
12
|
-
}
|
|
13
|
-
return 'dependencies'
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// given a dep name and spec, update it wherever it exists in
|
|
17
|
-
// the manifest, or add the spec to 'dependencies' if not found.
|
|
18
|
-
const updateDepSpec = (pkg, name, newSpec) => {
|
|
19
|
-
const type = findType(pkg, name)
|
|
20
|
-
pkg[type] = pkg[type] || {}
|
|
21
|
-
pkg[type][name] = newSpec
|
|
22
|
-
return pkg
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// sort alphabetically all types of deps for a given package
|
|
26
|
-
const orderDeps = (pkg) => {
|
|
27
|
-
for (const type of types) {
|
|
28
|
-
if (pkg && pkg[type]) {
|
|
29
|
-
pkg[type] = Object.keys(pkg[type])
|
|
30
|
-
.sort((a, b) => a.localeCompare(b))
|
|
31
|
-
.reduce((res, key) => {
|
|
32
|
-
res[key] = pkg[type][key]
|
|
33
|
-
return res
|
|
34
|
-
}, {})
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return pkg
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
module.exports = {
|
|
41
|
-
orderDeps,
|
|
42
|
-
updateDepSpec,
|
|
43
|
-
}
|