@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 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))
@@ -1,60 +1,60 @@
1
1
  // add and remove dependency specs to/from pkg manifest
2
2
 
3
- const removeFromOthers = (name, type, pkg) => {
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
- const addSingle = ({pkg, spec, saveBundle, saveType}) => {
44
- if (!saveType)
45
- saveType = getSaveType(pkg, spec)
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
- pkg[type] = pkg[type] || {}
56
- if (rawSpec !== '' || pkg[type][name] === undefined)
57
- pkg[type][name] = rawSpec || '*'
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
- const getSaveType = (pkg, spec) => {
83
- const {name} = spec
84
- const {
85
- // these names are so lonnnnngggg
86
- devDependencies: devDeps,
87
- optionalDependencies: optDeps,
88
- peerDependencies: peerDeps,
89
- peerDependenciesMeta: peerDepsMeta,
90
- } = pkg
91
-
92
- if (peerDeps && peerDeps[name] !== undefined) {
93
- if (peerDepsMeta && peerDepsMeta[name] && peerDepsMeta[name].optional)
94
- return 'peerOptional'
95
- else
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 deleteSubKey = (obj, k, sk) => {
106
- if (obj[k]) {
107
- delete obj[k][sk]
108
- if (!Object.keys(obj[k]).length)
109
- delete obj[k]
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 type of [
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, type, name)
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.parent !== target && newDep.canReplace(edge.to))
1401
- edge.to.parent = null
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 !== newDep &&
1418
- node.isDescendantOf(target) &&
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 peerSet = getPeerSet(current)
1626
- OUTER: for (const p of peerSet) {
1627
- // if any have a non-peer dep from the target, or a peer dep if
1628
- // the target is root, then cannot safely replace and dupe deeper.
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
- if (peerSet.has(edge.from))
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
- // only respect valid edges, however, since we're likely trying
1634
- // to fix the very one that's currently broken! If the virtual
1635
- // root's replacement is ok, and doesn't have any invalid edges
1636
- // indicating that it was an overridden peer, then ignore the
1637
- // conflict and continue. If it WAS an override, then we need
1638
- // to get the conflict here so that we can decide whether to
1639
- // accept the current dep node, clobber it, or fail the install.
1640
- if (edge.from === target && edge.valid) {
1641
- const rep = dep.parent.children.get(edge.name)
1642
- const override = rep && ([...rep.edgesIn].some(e => !e.valid))
1643
- if (!rep || !rep.satisfies(edge) || override) {
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 OUTER
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
@@ -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
@@ -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')
@@ -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.peer && this[_omitPeer] ||
411
- node.dev && this[_omitDev] ||
412
- node.optional && this[_omitOptional] ||
413
- node.devOptional && this[_omitOptional] && this[_omitDev]
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.package.name && node.version
543
- ? `${node.package.name}@${node.version}`
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
- const range = !isRange || subset(prefixRange, spec, { loose: true })
972
- ? prefixRange : spec
973
- const pname = child.package.name
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
- updateDepSpec(pkg, name, (alias ? `npm:${pname}@` : '') + range)
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
- const save = h.https && h.auth ? `git+${h.https(opt)}`
981
- : h.shortcut(opt)
982
- updateDepSpec(pkg, name, save)
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
- updateDepSpec(pkg, name, `file:${rel}`)
1003
+ newSpec = `file:${rel}`
992
1004
  } else
993
- updateDepSpec(pkg, name, req.saveSpec)
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
@@ -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.package.name)
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('name', name)) {
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.name, advisory).then(meta => {
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('name')) {
348
+ for (const name of tree.inventory.query('packageName')) {
342
349
  const set = new Set()
343
- for (const node of tree.inventory.query('name', name)) {
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.package.name : this.name,
357
+ name: this.isProjectRoot ? this.packageName : this.name,
353
358
  version: this.package.version,
354
359
  }
355
- if (this.errors.length || !this.package.name || !this.package.version) {
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.package.name) || {}
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
- // packages in optionalDependencies and prod/peer/dev are
733
- // optional. Packages in both deps and devDeps are required.
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] (obj, type) {
765
- const from = this
769
+ [_loadDepType] (deps, type) {
766
770
  const ad = this.package.acceptDependencies || {}
767
- for (const [name, spec] of Object.entries(obj || {})) {
768
- const accept = ad[name]
769
- // if it's already set, then we keep the existing edge
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.dev && type === 'prod')
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.package.name && node.package.name &&
931
- this.package.name === node.package.name &&
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.package.name && tree.package.name !== this.name)
11
- this.packageName = tree.package.name
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.package.name
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.package.name || node.name
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.package.name &&
874
- node.package.name !== node.name)
875
- lock.version = `npm:${node.package.name}@${node.version}`
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.0",
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.2",
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
- "mutate-fs": "^2.1.1",
44
- "tap": "^15.0.4",
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
- }