@npmcli/arborist 2.2.1 → 2.2.2

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/ideal.js CHANGED
@@ -1,59 +1,11 @@
1
1
  const Arborist = require('../')
2
2
 
3
+ const { inspect } = require('util')
3
4
  const options = require('./lib/options.js')
4
5
  const print = require('./lib/print-tree.js')
5
6
  require('./lib/logging.js')
6
7
  require('./lib/timers.js')
7
8
 
8
- const c = require('chalk')
9
-
10
- const whichIsA = (name, dependents, indent = ' ') => {
11
- if (!dependents || dependents.length === 0)
12
- return ''
13
- const str = `\nfor: ` +
14
- dependents.map(dep => {
15
- return dep.more ? `${dep.more} more (${dep.names.join(', ')})`
16
- : `${dep.type} dependency ` +
17
- `${c.bold(name)}@"${c.bold(dep.spec)}"` + `\nfrom:` +
18
- (dep.from.location ? (dep.from.name
19
- ? ` ${c.bold(dep.from.name)}@${c.bold(dep.from.version)} ` +
20
- c.dim(`at ${dep.from.location}`)
21
- : ' the root project')
22
- : ` ${c.bold(dep.from.name)}@${c.bold(dep.from.version)}`) +
23
- whichIsA(dep.from.name, dep.from.dependents, ' ')
24
- }).join('\nand: ')
25
-
26
- return str.split(/\n/).join(`\n${indent}`)
27
- }
28
-
29
- const explainEresolve = ({ dep, current, peerConflict, fixWithForce }) => {
30
- return (!dep.whileInstalling ? '' : `While resolving: ` +
31
- `${c.bold(dep.whileInstalling.name)}@${c.bold(dep.whileInstalling.version)}\n`) +
32
-
33
- `Found: ` +
34
- `${c.bold(current.name)}@${c.bold(current.version)} ` +
35
- c.dim(`at ${current.location}`) +
36
- `${whichIsA(current.name, current.dependents)}` +
37
-
38
- `\n\nCould not add conflicting dependency: ` +
39
- `${c.bold(dep.name)}@${c.bold(dep.version)} ` +
40
- c.dim(`at ${dep.location}`) +
41
- `${whichIsA(dep.name, dep.dependents)}\n` +
42
-
43
- (!peerConflict ? '' :
44
- `\nConflicting peer dependency: ` +
45
- `${c.bold(peerConflict.name)}@${c.bold(peerConflict.version)} ` +
46
- c.dim(`at ${peerConflict.location}`) +
47
- `${whichIsA(peerConflict.name, peerConflict.dependents)}\n`
48
- ) +
49
-
50
- `\nFix the upstream dependency conflict, or
51
- run this command with --legacy-peer-deps${
52
- fixWithForce ? ' or --force' : ''}
53
- to accept an incorrect (and potentially broken) dependency resolution.
54
- `
55
- }
56
-
57
9
  const start = process.hrtime()
58
10
  new Arborist(options).buildIdealTree(options).then(tree => {
59
11
  const end = process.hrtime(start)
@@ -62,7 +14,7 @@ new Arborist(options).buildIdealTree(options).then(tree => {
62
14
  if (tree.meta && options.save)
63
15
  tree.meta.save()
64
16
  }).catch(er => {
65
- console.error(er)
66
- if (er.code === 'ERESOLVE')
67
- console.error(explainEresolve(er))
17
+ const opt = { depth: Infinity, color: true }
18
+ console.error(er.code === 'ERESOLVE' ? inspect(er, opt) : er)
19
+ process.exitCode = 1
68
20
  })
@@ -26,7 +26,7 @@ if (loglevel !== 'silent') {
26
26
  return
27
27
  const pref = `${process.pid} ${level} `
28
28
  if (level === 'warn' && args[0] === 'ERESOLVE')
29
- args[2] = inspect(args[2], { depth: Infinity })
29
+ args[2] = inspect(args[2], { depth: 10 })
30
30
  const msg = pref + format(...args).trim().split('\n').join(`\n${pref}`)
31
31
  console.error(msg)
32
32
  })
@@ -397,7 +397,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
397
397
  // that they're there, and not reinstall the world unnecessarily.
398
398
  if (this[_global] && (this[_updateAll] || this[_updateNames].length)) {
399
399
  const nm = resolve(this.path, 'node_modules')
400
- for (const name of await readdir(nm)) {
400
+ for (const name of await readdir(nm).catch(() => [])) {
401
401
  tree.package.dependencies = tree.package.dependencies || {}
402
402
  if (this[_updateAll] || this[_updateNames].includes(name))
403
403
  tree.package.dependencies[name] = '*'
@@ -491,7 +491,8 @@ module.exports = cls => class IdealTreeBuilder extends cls {
491
491
  /* istanbul ignore else - should also be covered by realpath failure */
492
492
  if (filepath) {
493
493
  const { name } = spec
494
- spec = npa(`file:${relpath(this.path, filepath)}`, this.path)
494
+ const tree = this.idealTree.target || this.idealTree
495
+ spec = npa(`file:${relpath(tree.path, filepath)}`, tree.path)
495
496
  spec.name = name
496
497
  }
497
498
  return spec
@@ -663,6 +664,11 @@ This is a one-time fix-up, please be patient...
663
664
  })
664
665
  }
665
666
  await promiseCallLimit(queue)
667
+
668
+ // have to re-calc dep flags, because the nodes don't have edges
669
+ // until their packages get assigned, so everything looks extraneous
670
+ calcDepFlags(this.idealTree)
671
+
666
672
  // yes, yes, this isn't the "original" version, but now that it's been
667
673
  // upgraded, we need to make sure we don't do the work to upgrade it
668
674
  // again, since it's now as new as can be.
@@ -800,6 +806,7 @@ This is a one-time fix-up, please be patient...
800
806
  // a virtual root of whatever brought in THIS node.
801
807
  // so we VR the node itself if the edge is not a peer
802
808
  const source = edge.peer ? peerSource : node
809
+
803
810
  const virtualRoot = this[_virtualRoot](source, true)
804
811
  // reuse virtual root if we already have one, but don't
805
812
  // try to do the override ahead of time, since we MAY be able
@@ -821,13 +828,17 @@ This is a one-time fix-up, please be patient...
821
828
  // +-- z@1
822
829
  // But if x and y are loaded in the same virtual root, then they will
823
830
  // be forced to agree on a version of z.
831
+ const required = new Set([edge.from])
832
+ const parent = edge.peer ? virtualRoot : null
824
833
  const dep = vrDep && vrDep.satisfies(edge) ? vrDep
825
- : await this[_nodeFromEdge](edge, edge.peer ? virtualRoot : null)
834
+ : await this[_nodeFromEdge](edge, parent, null, required)
835
+
826
836
  /* istanbul ignore next */
827
837
  debug(() => {
828
838
  if (!dep)
829
839
  throw new Error('no dep??')
830
840
  })
841
+
831
842
  tasks.push({edge, dep})
832
843
  }
833
844
 
@@ -864,7 +875,7 @@ This is a one-time fix-up, please be patient...
864
875
 
865
876
  // loads a node from an edge, and then loads its peer deps (and their
866
877
  // peer deps, on down the line) into a virtual root parent.
867
- async [_nodeFromEdge] (edge, parent_, secondEdge = null) {
878
+ async [_nodeFromEdge] (edge, parent_, secondEdge, required) {
868
879
  // create a virtual root node with the same deps as the node that
869
880
  // is requesting this one, so that we can get all the peer deps in
870
881
  // a context where they're likely to be resolvable.
@@ -895,6 +906,11 @@ This is a one-time fix-up, please be patient...
895
906
  // ensure the one we want is the one that's placed
896
907
  node.parent = parent
897
908
 
909
+ if (required.has(edge.from) && edge.type !== 'peerOptional' ||
910
+ secondEdge && (
911
+ required.has(secondEdge.from) && secondEdge.type !== 'peerOptional'))
912
+ required.add(node)
913
+
898
914
  // handle otherwise unresolvable dependency nesting loops by
899
915
  // creating a symbolic link
900
916
  // a1 -> b1 -> a2 -> b2 -> a1 -> ...
@@ -908,7 +924,7 @@ This is a one-time fix-up, please be patient...
908
924
  // keep track of the thing that caused this node to be included.
909
925
  const src = parent.sourceReference
910
926
  this[_peerSetSource].set(node, src)
911
- return this[_loadPeerSet](node)
927
+ return this[_loadPeerSet](node, required)
912
928
  }
913
929
 
914
930
  [_virtualRoot] (node, reuse = false) {
@@ -1053,7 +1069,7 @@ This is a one-time fix-up, please be patient...
1053
1069
  // gets placed first. In non-strict mode, we behave strictly if the
1054
1070
  // virtual root is based on the root project, and allow non-peer parent
1055
1071
  // deps to override, but throw if no preference can be determined.
1056
- async [_loadPeerSet] (node) {
1072
+ async [_loadPeerSet] (node, required) {
1057
1073
  const peerEdges = [...node.edgesOut.values()]
1058
1074
  // we typically only install non-optional peers, but we have to
1059
1075
  // factor them into the peerSet so that we can avoid conflicts
@@ -1068,10 +1084,12 @@ This is a one-time fix-up, please be patient...
1068
1084
  const parentEdge = node.parent.edgesOut.get(edge.name)
1069
1085
  const {isProjectRoot, isWorkspace} = node.parent.sourceReference
1070
1086
  const isMine = isProjectRoot || isWorkspace
1087
+ const conflictOK = this[_force] || !isMine && !this[_strictPeerDeps]
1088
+
1071
1089
  if (!edge.to) {
1072
1090
  if (!parentEdge) {
1073
1091
  // easy, just put the thing there
1074
- await this[_nodeFromEdge](edge, node.parent)
1092
+ await this[_nodeFromEdge](edge, node.parent, null, required)
1075
1093
  continue
1076
1094
  } else {
1077
1095
  // if the parent's edge is very broad like >=1, and the edge in
@@ -1082,14 +1100,16 @@ This is a one-time fix-up, please be patient...
1082
1100
  // a conflict. this is always a problem in strict mode, never
1083
1101
  // in force mode, and a problem in non-strict mode if this isn't
1084
1102
  // on behalf of our project. in all such cases, we warn at least.
1085
- await this[_nodeFromEdge](parentEdge, node.parent, edge)
1103
+ const dep = await this[_nodeFromEdge](parentEdge, node.parent, edge, required)
1086
1104
 
1087
1105
  // hooray! that worked!
1088
1106
  if (edge.valid)
1089
1107
  continue
1090
1108
 
1091
- // allow it
1092
- if (this[_force] || !isMine && !this[_strictPeerDeps])
1109
+ // allow it. either we're overriding, or it's not something
1110
+ // that will be installed by default anyway, and we'll fail when
1111
+ // we get to the point where we need to, if we need to.
1112
+ if (conflictOK || !required.has(dep))
1093
1113
  continue
1094
1114
 
1095
1115
  // problem
@@ -1102,7 +1122,7 @@ This is a one-time fix-up, please be patient...
1102
1122
  // in non-strict mode if it's not our fault. don't warn here, because
1103
1123
  // we are going to warn again when we place the deps, if we end up
1104
1124
  // overriding for something else.
1105
- if (this[_force] || !isMine && !this[_strictPeerDeps])
1125
+ if (conflictOK)
1106
1126
  continue
1107
1127
 
1108
1128
  // ok, it's the root, or we're in unforced strict mode, so this is bad
@@ -1198,8 +1218,25 @@ This is a one-time fix-up, please be patient...
1198
1218
  break
1199
1219
  }
1200
1220
 
1201
- if (!target)
1202
- this[_failPeerConflict](edge)
1221
+ // if we can't find a target, that means that the last placed checked
1222
+ // (and all the places before it) had a copy already. if we're in
1223
+ // --force mode, then the user has explicitly said that they're ok
1224
+ // with conflicts. This can only occur in --force mode in the case
1225
+ // when a node was added to the tree with a peerOptional dep that we
1226
+ // ignored, and then later, that edge became invalid, and we fail to
1227
+ // resolve it. We will warn about it in a moment.
1228
+ if (!target) {
1229
+ if (this[_force]) {
1230
+ // we know that there is a dep (not the root) which is the target
1231
+ // of this edge, or else it wouldn't have been a conflict.
1232
+ target = edge.to.resolveParent
1233
+ canPlace = KEEP
1234
+ } else
1235
+ this[_failPeerConflict](edge)
1236
+ } else {
1237
+ // it worked, so we clearly have no peer conflicts at this point.
1238
+ this[_peerConflict] = null
1239
+ }
1203
1240
 
1204
1241
  this.log.silly(
1205
1242
  'placeDep',
@@ -1210,9 +1247,6 @@ This is a one-time fix-up, please be patient...
1210
1247
  `want: ${edge.spec || '*'}`
1211
1248
  )
1212
1249
 
1213
- // it worked, so we clearly have no peer conflicts at this point.
1214
- this[_peerConflict] = null
1215
-
1216
1250
  // Can only get KEEP here if the original edge was valid,
1217
1251
  // and we're checking for an update but it's already up to date.
1218
1252
  if (canPlace === KEEP) {
@@ -1398,6 +1432,7 @@ This is a one-time fix-up, please be patient...
1398
1432
  })
1399
1433
  const entryEdge = peerEntryEdge || edge
1400
1434
  const source = this[_peerSetSource].get(dep)
1435
+
1401
1436
  isSource = isSource || target === source
1402
1437
  // if we're overriding the source, then we care if the *target* is
1403
1438
  // ours, even if it wasn't actually the original source, since we
@@ -24,6 +24,7 @@ const loadWorkspacesVirtual = Symbol.for('loadWorkspacesVirtual')
24
24
  const flagsSuspect = Symbol.for('flagsSuspect')
25
25
  const reCalcDepFlags = Symbol('reCalcDepFlags')
26
26
  const checkRootEdges = Symbol('checkRootEdges')
27
+ const rootOptionProvided = Symbol('rootOptionProvided')
27
28
 
28
29
  const depsToEdges = (type, deps) =>
29
30
  Object.entries(deps).map(d => [type, ...d])
@@ -63,6 +64,8 @@ module.exports = cls => class VirtualLoader extends cls {
63
64
  root = await this[loadRoot](s),
64
65
  } = options
65
66
 
67
+ this[rootOptionProvided] = options.root
68
+
66
69
  await this[loadFromShrinkwrap](s, root)
67
70
  return treeCheck(this.virtualTree)
68
71
  }
@@ -74,13 +77,17 @@ module.exports = cls => class VirtualLoader extends cls {
74
77
  }
75
78
 
76
79
  async [loadFromShrinkwrap] (s, root) {
77
- // root is never any of these things, but might be a brand new
78
- // baby Node object that never had its dep flags calculated.
79
- root.extraneous = false
80
- root.dev = false
81
- root.optional = false
82
- root.devOptional = false
83
- root.peer = false
80
+ if (!this[rootOptionProvided]) {
81
+ // root is never any of these things, but might be a brand new
82
+ // baby Node object that never had its dep flags calculated.
83
+ root.extraneous = false
84
+ root.dev = false
85
+ root.optional = false
86
+ root.devOptional = false
87
+ root.peer = false
88
+ } else
89
+ this[flagsSuspect] = true
90
+
84
91
  this[checkRootEdges](s, root)
85
92
  root.meta = s
86
93
  this.virtualTree = root
@@ -88,20 +95,23 @@ module.exports = cls => class VirtualLoader extends cls {
88
95
  await this[resolveLinks](links, nodes)
89
96
  this[assignBundles](nodes)
90
97
  if (this[flagsSuspect])
91
- this[reCalcDepFlags]()
98
+ this[reCalcDepFlags](nodes.values())
92
99
  return root
93
100
  }
94
101
 
95
- [reCalcDepFlags] () {
102
+ [reCalcDepFlags] (nodes) {
96
103
  // reset all dep flags
97
- for (const node of this.virtualTree.inventory.values()) {
104
+ // can't use inventory here, because virtualTree might not be root
105
+ for (const node of nodes) {
106
+ if (node.isRoot || node === this[rootOptionProvided])
107
+ continue
98
108
  node.extraneous = true
99
109
  node.dev = true
100
110
  node.optional = true
101
111
  node.devOptional = true
102
112
  node.peer = true
103
113
  }
104
- calcDepFlags(this.virtualTree, true)
114
+ calcDepFlags(this.virtualTree, !this[rootOptionProvided])
105
115
  }
106
116
 
107
117
  // check the lockfile deps, and see if they match. if they do not
@@ -237,6 +247,12 @@ module.exports = cls => class VirtualLoader extends cls {
237
247
  // shrinkwrap doesn't include package name unless necessary
238
248
  if (!sw.name)
239
249
  sw.name = nameFromFolder(path)
250
+
251
+ const dev = sw.dev
252
+ const optional = sw.optional
253
+ const devOptional = dev || optional || sw.devOptional
254
+ const peer = sw.peer
255
+
240
256
  const node = new Node({
241
257
  legacyPeerDeps: this.legacyPeerDeps,
242
258
  root: this.virtualTree,
@@ -246,6 +262,10 @@ module.exports = cls => class VirtualLoader extends cls {
246
262
  resolved: consistentResolve(sw.resolved, this.path, path),
247
263
  pkg: sw,
248
264
  hasShrinkwrap: sw.hasShrinkwrap,
265
+ dev,
266
+ optional,
267
+ devOptional,
268
+ peer,
249
269
  })
250
270
  // cast to boolean because they're undefined in the lock file when false
251
271
  node.extraneous = !!sw.extraneous
@@ -442,7 +442,8 @@ module.exports = cls => class Reifier extends cls {
442
442
  if (this[_trashList].has(node.path))
443
443
  return node
444
444
 
445
- process.emit('time', `reifyNode:${node.location}`)
445
+ const timer = `reifyNode:${node.location}`
446
+ process.emit('time', timer)
446
447
  this.addTracker('reify', node.name, node.location)
447
448
 
448
449
  const p = Promise.resolve()
@@ -454,7 +455,7 @@ module.exports = cls => class Reifier extends cls {
454
455
  return this[_handleOptionalFailure](node, p)
455
456
  .then(() => {
456
457
  this.finishTracker('reify', node.name, node.location)
457
- process.emit('timeEnd', `reifyNode:${node.location}`)
458
+ process.emit('timeEnd', timer)
458
459
  return node
459
460
  })
460
461
  }
@@ -474,9 +475,14 @@ module.exports = cls => class Reifier extends cls {
474
475
 
475
476
  // no idea what this thing is. remove it from the tree.
476
477
  if (!res) {
477
- node.parent = null
478
+ const warning = 'invalid or damaged lockfile detected\n' +
479
+ 'please re-try this operation once it completes\n' +
480
+ 'so that the damage can be corrected, or perform\n' +
481
+ 'a fresh install with no lockfile if the problem persists.'
482
+ this.log.warn('reify', warning)
478
483
  this.log.verbose('reify', 'unrecognized node in tree', node.path)
479
484
  node.parent = null
485
+ node.fsParent = null
480
486
  this[_addNodeToTrashList](node)
481
487
  return
482
488
  }
@@ -11,7 +11,7 @@ const calcDepFlags = (tree, resetRoot = true) => {
11
11
  tree,
12
12
  visit: node => calcDepFlagsStep(node),
13
13
  filter: node => node,
14
- getChildren: node => [...node.edgesOut.values()].map(edge => edge.to),
14
+ getChildren: (node, tree) => [...tree.edgesOut.values()].map(edge => edge.to),
15
15
  })
16
16
  return ret
17
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npmcli/arborist",
3
- "version": "2.2.1",
3
+ "version": "2.2.2",
4
4
  "description": "Manage node_modules trees",
5
5
  "dependencies": {
6
6
  "@npmcli/installed-package-contents": "^1.0.6",