@npmcli/arborist 2.8.2 → 2.9.0

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.
Files changed (49) hide show
  1. package/bin/actual.js +4 -2
  2. package/bin/audit.js +12 -6
  3. package/bin/dedupe.js +6 -3
  4. package/bin/funding.js +4 -2
  5. package/bin/ideal.js +2 -1
  6. package/bin/lib/logging.js +4 -3
  7. package/bin/lib/options.js +14 -12
  8. package/bin/lib/timers.js +6 -3
  9. package/bin/license.js +9 -5
  10. package/bin/prune.js +6 -3
  11. package/bin/reify.js +6 -3
  12. package/bin/virtual.js +4 -2
  13. package/lib/add-rm-pkg-deps.js +28 -15
  14. package/lib/arborist/audit.js +2 -1
  15. package/lib/arborist/build-ideal-tree.js +139 -72
  16. package/lib/arborist/deduper.js +2 -1
  17. package/lib/arborist/index.js +8 -4
  18. package/lib/arborist/load-actual.js +28 -13
  19. package/lib/arborist/load-virtual.js +37 -20
  20. package/lib/arborist/load-workspaces.js +4 -2
  21. package/lib/arborist/rebuild.js +34 -17
  22. package/lib/arborist/reify.js +153 -76
  23. package/lib/audit-report.js +44 -23
  24. package/lib/calc-dep-flags.js +18 -9
  25. package/lib/can-place-dep.js +59 -30
  26. package/lib/case-insensitive-map.js +4 -2
  27. package/lib/consistent-resolve.js +2 -1
  28. package/lib/deepest-nesting-target.js +4 -2
  29. package/lib/dep-valid.js +8 -4
  30. package/lib/diff.js +74 -22
  31. package/lib/edge.js +26 -13
  32. package/lib/gather-dep-set.js +2 -1
  33. package/lib/inventory.js +12 -6
  34. package/lib/link.js +14 -9
  35. package/lib/node.js +216 -113
  36. package/lib/optional-set.js +4 -2
  37. package/lib/peer-entry-sets.js +10 -5
  38. package/lib/place-dep.js +111 -37
  39. package/lib/printable.js +46 -25
  40. package/lib/realpath.js +12 -6
  41. package/lib/shrinkwrap.js +164 -90
  42. package/lib/signal-handling.js +6 -3
  43. package/lib/spec-from-lock.js +7 -4
  44. package/lib/tracker.js +24 -18
  45. package/lib/tree-check.js +12 -6
  46. package/lib/version-from-tgz.js +4 -2
  47. package/lib/vuln.js +44 -22
  48. package/lib/yarn-lock.js +34 -21
  49. package/package.json +8 -10
@@ -78,8 +78,9 @@ module.exports = cls => class ActualLoader extends cls {
78
78
  [_resetDepFlags] (tree, root) {
79
79
  // reset all deps to extraneous prior to recalc
80
80
  if (!root) {
81
- for (const node of tree.inventory.values())
81
+ for (const node of tree.inventory.values()) {
82
82
  node.extraneous = true
83
+ }
83
84
  }
84
85
 
85
86
  // only reset root flags if we're not re-rooting,
@@ -176,8 +177,9 @@ module.exports = cls => class ActualLoader extends cls {
176
177
  await this[_loadFSTree](this[_actualTree])
177
178
  await this[_loadWorkspaces](this[_actualTree])
178
179
  await this[_loadWorkspaceTargets](this[_actualTree])
179
- if (!ignoreMissing)
180
+ if (!ignoreMissing) {
180
181
  await this[_findMissingEdges]()
182
+ }
181
183
  this[_findFSParents]()
182
184
  this[_transplant](root)
183
185
 
@@ -200,8 +202,9 @@ module.exports = cls => class ActualLoader extends cls {
200
202
  // if there are workspace targets without Link nodes created, load
201
203
  // the targets, so that we know what they are.
202
204
  async [_loadWorkspaceTargets] (tree) {
203
- if (!tree.workspaces || !tree.workspaces.size)
205
+ if (!tree.workspaces || !tree.workspaces.size) {
204
206
  return
207
+ }
205
208
 
206
209
  const promises = []
207
210
  for (const path of tree.workspaces.values()) {
@@ -215,18 +218,21 @@ module.exports = cls => class ActualLoader extends cls {
215
218
  }
216
219
 
217
220
  [_transplant] (root) {
218
- if (!root || root === this[_actualTree])
221
+ if (!root || root === this[_actualTree]) {
219
222
  return
223
+ }
220
224
 
221
225
  this[_actualTree][_changePath](root.path)
222
226
  for (const node of this[_actualTree].children.values()) {
223
- if (!this[_transplantFilter](node))
227
+ if (!this[_transplantFilter](node)) {
224
228
  node.root = null
229
+ }
225
230
  }
226
231
 
227
232
  root.replace(this[_actualTree])
228
- for (const node of this[_actualTree].fsChildren)
233
+ for (const node of this[_actualTree].fsChildren) {
229
234
  node.root = this[_transplantFilter](node) ? root : null
235
+ }
230
236
 
231
237
  this[_actualTree] = root
232
238
  }
@@ -291,8 +297,9 @@ module.exports = cls => class ActualLoader extends cls {
291
297
  // it'll get parented later, making the fsParent scan a no-op, but better
292
298
  // safe than sorry, since it's cheap.
293
299
  const { parent, realpath } = options
294
- if (!parent)
300
+ if (!parent) {
295
301
  this[_topNodes].add(realpath)
302
+ }
296
303
  return process.env._TEST_ARBORIST_SLOW_LINK_TARGET_ === '1'
297
304
  ? new Promise(res => setTimeout(() => res(new Node(options)), 100))
298
305
  : new Node(options)
@@ -309,8 +316,9 @@ module.exports = cls => class ActualLoader extends cls {
309
316
  // if a link target points at a node outside of the root tree's
310
317
  // node_modules hierarchy, then load that node as well.
311
318
  return this[_loadFSTree](link.target).then(() => link)
312
- } else if (target.then)
319
+ } else if (target.then) {
313
320
  target.then(node => link.target = node)
321
+ }
314
322
 
315
323
  return link
316
324
  }
@@ -321,13 +329,15 @@ module.exports = cls => class ActualLoader extends cls {
321
329
 
322
330
  // if a Link target has started, but not completed, then
323
331
  // a Promise will be in the cache to indicate this.
324
- if (node.then)
332
+ if (node.then) {
325
333
  return node.then(node => this[_loadFSTree](node))
334
+ }
326
335
 
327
336
  // impossible except in pathological ELOOP cases
328
337
  /* istanbul ignore if */
329
- if (did.has(node.realpath))
338
+ if (did.has(node.realpath)) {
330
339
  return Promise.resolve(node)
340
+ }
331
341
 
332
342
  did.add(node.realpath)
333
343
  return this[_loadFSChildren](node)
@@ -371,8 +381,11 @@ module.exports = cls => class ActualLoader extends cls {
371
381
 
372
382
  const depPromises = []
373
383
  for (const [name, edge] of node.edgesOut.entries()) {
374
- if (!edge.missing && !(edge.to && (edge.to.dummy || edge.to.parent !== node)))
384
+ const notMissing = !edge.missing &&
385
+ !(edge.to && (edge.to.dummy || edge.to.parent !== node))
386
+ if (notMissing) {
375
387
  continue
388
+ }
376
389
 
377
390
  // start the walk from the dirname, because we would have found
378
391
  // the dep in the loadFSTree step already if it was local.
@@ -383,14 +396,16 @@ module.exports = cls => class ActualLoader extends cls {
383
396
  // allows for finding the transitive deps of link targets.
384
397
  // ie, if it has to go up and back out to get to the path
385
398
  // from the nearest common ancestor, we've gone too far.
386
- if (ancestor && /^\.\.(?:[\\/]|$)/.test(relative(ancestor, p)))
399
+ if (ancestor && /^\.\.(?:[\\/]|$)/.test(relative(ancestor, p))) {
387
400
  break
401
+ }
388
402
 
389
403
  const entries = nmContents.get(p) ||
390
404
  await readdir(p + '/node_modules').catch(() => [])
391
405
  nmContents.set(p, entries)
392
- if (!entries.includes(name))
406
+ if (!entries.includes(name)) {
393
407
  continue
408
+ }
394
409
 
395
410
  const d = this[_cache].has(p) ? await this[_cache].get(p)
396
411
  : new Node({ path: p, root: node.root, dummy: true })
@@ -1,4 +1,5 @@
1
1
  // mixin providing the loadVirtual method
2
+ const localeCompare = require('@isaacs/string-locale-compare')('en')
2
3
 
3
4
  const {resolve} = require('path')
4
5
 
@@ -40,8 +41,9 @@ module.exports = cls => class VirtualLoader extends cls {
40
41
 
41
42
  // public method
42
43
  async loadVirtual (options = {}) {
43
- if (this.virtualTree)
44
+ if (this.virtualTree) {
44
45
  return this.virtualTree
46
+ }
45
47
 
46
48
  // allow the user to set reify options on the ctor as well.
47
49
  // XXX: deprecate separate reify() options object.
@@ -85,18 +87,21 @@ module.exports = cls => class VirtualLoader extends cls {
85
87
  root.optional = false
86
88
  root.devOptional = false
87
89
  root.peer = false
88
- } else
90
+ } else {
89
91
  this[flagsSuspect] = true
92
+ }
90
93
 
91
94
  this[checkRootEdges](s, root)
92
95
  root.meta = s
93
96
  this.virtualTree = root
94
97
  const {links, nodes} = this[resolveNodes](s, root)
95
98
  await this[resolveLinks](links, nodes)
96
- if (!(s.originalLockfileVersion >= 2))
99
+ if (!(s.originalLockfileVersion >= 2)) {
97
100
  this[assignBundles](nodes)
98
- if (this[flagsSuspect])
101
+ }
102
+ if (this[flagsSuspect]) {
99
103
  this[reCalcDepFlags](nodes.values())
104
+ }
100
105
  return root
101
106
  }
102
107
 
@@ -104,8 +109,9 @@ module.exports = cls => class VirtualLoader extends cls {
104
109
  // reset all dep flags
105
110
  // can't use inventory here, because virtualTree might not be root
106
111
  for (const node of nodes) {
107
- if (node.isRoot || node === this[rootOptionProvided])
112
+ if (node.isRoot || node === this[rootOptionProvided]) {
108
113
  continue
114
+ }
109
115
  node.extraneous = true
110
116
  node.dev = true
111
117
  node.optional = true
@@ -123,8 +129,9 @@ module.exports = cls => class VirtualLoader extends cls {
123
129
  // loaded virtually from tree, no chance of being out of sync
124
130
  // ancient lockfiles are critically damaged by this process,
125
131
  // so we need to just hope for the best in those cases.
126
- if (!s.loadedFromDisk || s.ancientLockfile)
132
+ if (!s.loadedFromDisk || s.ancientLockfile) {
127
133
  return
134
+ }
128
135
 
129
136
  const lock = s.get('')
130
137
  const prod = lock.dependencies || {}
@@ -140,16 +147,18 @@ module.exports = cls => class VirtualLoader extends cls {
140
147
  }
141
148
  }
142
149
  }
143
- for (const name of Object.keys(optional))
150
+ for (const name of Object.keys(optional)) {
144
151
  delete prod[name]
152
+ }
145
153
 
146
154
  const lockWS = []
147
155
  const workspaces = this[loadWorkspacesVirtual]({
148
156
  cwd: this.path,
149
157
  lockfile: s.data,
150
158
  })
151
- for (const [name, path] of workspaces.entries())
159
+ for (const [name, path] of workspaces.entries()) {
152
160
  lockWS.push(['workspace', name, `file:${path}`])
161
+ }
153
162
 
154
163
  const lockEdges = [
155
164
  ...depsToEdges('prod', prod),
@@ -159,12 +168,12 @@ module.exports = cls => class VirtualLoader extends cls {
159
168
  ...depsToEdges('peerOptional', peerOptional),
160
169
  ...lockWS,
161
170
  ].sort(([atype, aname], [btype, bname]) =>
162
- atype.localeCompare(btype, 'en') || aname.localeCompare(bname, 'en'))
171
+ localeCompare(atype, btype) || localeCompare(aname, bname))
163
172
 
164
173
  const rootEdges = [...root.edgesOut.values()]
165
174
  .map(e => [e.type, e.name, e.spec])
166
175
  .sort(([atype, aname], [btype, bname]) =>
167
- atype.localeCompare(btype, 'en') || aname.localeCompare(bname, 'en'))
176
+ localeCompare(atype, btype) || localeCompare(aname, bname))
168
177
 
169
178
  if (rootEdges.length !== lockEdges.length) {
170
179
  // something added or removed
@@ -174,8 +183,9 @@ module.exports = cls => class VirtualLoader extends cls {
174
183
  for (let i = 0; i < lockEdges.length; i++) {
175
184
  if (rootEdges[i][0] !== lockEdges[i][0] ||
176
185
  rootEdges[i][1] !== lockEdges[i][1] ||
177
- rootEdges[i][2] !== lockEdges[i][2])
186
+ rootEdges[i][2] !== lockEdges[i][2]) {
178
187
  return this[flagsSuspect] = true
188
+ }
179
189
  }
180
190
  }
181
191
 
@@ -185,13 +195,15 @@ module.exports = cls => class VirtualLoader extends cls {
185
195
  const nodes = new Map([['', root]])
186
196
  for (const [location, meta] of Object.entries(s.data.packages)) {
187
197
  // skip the root because we already got it
188
- if (!location)
198
+ if (!location) {
189
199
  continue
200
+ }
190
201
 
191
- if (meta.link)
202
+ if (meta.link) {
192
203
  links.set(location, meta)
193
- else
204
+ } else {
194
205
  nodes.set(location, this[loadNode](location, meta))
206
+ }
195
207
  }
196
208
  return {links, nodes}
197
209
  }
@@ -212,8 +224,9 @@ module.exports = cls => class VirtualLoader extends cls {
212
224
  if (!link.target.parent) {
213
225
  const pj = link.realpath + '/package.json'
214
226
  const pkg = await rpj(pj).catch(() => null)
215
- if (pkg)
227
+ if (pkg) {
216
228
  link.target.package = pkg
229
+ }
217
230
  }
218
231
  }
219
232
  }
@@ -221,12 +234,14 @@ module.exports = cls => class VirtualLoader extends cls {
221
234
  [assignBundles] (nodes) {
222
235
  for (const [location, node] of nodes) {
223
236
  // Skip assignment of parentage for the root package
224
- if (!location || node.isLink && !node.target.location)
237
+ if (!location || node.isLink && !node.target.location) {
225
238
  continue
239
+ }
226
240
  const { name, parent, package: { inBundle }} = node
227
241
 
228
- if (!parent)
242
+ if (!parent) {
229
243
  continue
244
+ }
230
245
 
231
246
  // read inBundle from package because 'package' here is
232
247
  // actually a v2 lockfile metadata entry.
@@ -236,10 +251,11 @@ module.exports = cls => class VirtualLoader extends cls {
236
251
  const { package: ppkg } = parent
237
252
  const { inBundle: parentBundled } = ppkg
238
253
  if (inBundle && !parentBundled && parent.edgesOut.has(node.name)) {
239
- if (!ppkg.bundleDependencies)
254
+ if (!ppkg.bundleDependencies) {
240
255
  ppkg.bundleDependencies = [name]
241
- else
256
+ } else {
242
257
  ppkg.bundleDependencies.push(name)
258
+ }
243
259
  }
244
260
  }
245
261
  }
@@ -248,8 +264,9 @@ module.exports = cls => class VirtualLoader extends cls {
248
264
  const p = this.virtualTree ? this.virtualTree.realpath : this.path
249
265
  const path = resolve(p, location)
250
266
  // shrinkwrap doesn't include package name unless necessary
251
- if (!sw.name)
267
+ if (!sw.name) {
252
268
  sw.name = nameFromFolder(path)
269
+ }
253
270
 
254
271
  const dev = sw.dev
255
272
  const optional = sw.optional
@@ -7,15 +7,17 @@ const _loadWorkspacesVirtual = Symbol.for('loadWorkspacesVirtual')
7
7
 
8
8
  module.exports = cls => class MapWorkspaces extends cls {
9
9
  [_appendWorkspaces] (node, workspaces) {
10
- if (node && workspaces.size)
10
+ if (node && workspaces.size) {
11
11
  node.workspaces = workspaces
12
+ }
12
13
 
13
14
  return node
14
15
  }
15
16
 
16
17
  async [_loadWorkspaces] (node) {
17
- if (node.workspaces)
18
+ if (node.workspaces) {
18
19
  return node
20
+ }
19
21
 
20
22
  const workspaces = await mapWorkspaces({
21
23
  cwd: node.path,
@@ -1,6 +1,7 @@
1
1
  // Arborist.rebuild({path = this.path}) will do all the binlinks and
2
2
  // bundle building needed. Called by reify, and by `npm rebuild`.
3
3
 
4
+ const localeCompare = require('@isaacs/string-locale-compare')('en')
4
5
  const {depth: dfwalk} = require('treeverse')
5
6
  const promiseAllRejectLate = require('promise-all-reject-late')
6
7
  const rpj = require('read-package-json-fast')
@@ -14,7 +15,8 @@ const {
14
15
  } = require('@npmcli/node-gyp')
15
16
 
16
17
  const boolEnv = b => b ? '1' : ''
17
- const sortNodes = (a, b) => (a.depth - b.depth) || a.path.localeCompare(b.path, 'en')
18
+ const sortNodes = (a, b) =>
19
+ (a.depth - b.depth) || localeCompare(a.path, b.path)
18
20
 
19
21
  const _workspaces = Symbol.for('workspaces')
20
22
  const _build = Symbol('build')
@@ -61,8 +63,9 @@ module.exports = cls => class Builder extends cls {
61
63
 
62
64
  async rebuild ({ nodes, handleOptionalFailure = false } = {}) {
63
65
  // nothing to do if we're not building anything!
64
- if (this[_ignoreScripts] && !this[_binLinks])
66
+ if (this[_ignoreScripts] && !this[_binLinks]) {
65
67
  return
68
+ }
66
69
 
67
70
  // when building for the first time, as part of reify, we ignore
68
71
  // failures in optional nodes, and just delete them. however, when
@@ -76,8 +79,9 @@ module.exports = cls => class Builder extends cls {
76
79
  if (this[_workspaces] && this[_workspaces].length) {
77
80
  const filterSet = this.workspaceDependencySet(tree, this[_workspaces])
78
81
  nodes = tree.inventory.filter(node => filterSet.has(node))
79
- } else
82
+ } else {
80
83
  nodes = tree.inventory.values()
84
+ }
81
85
  }
82
86
 
83
87
  // separates links nodes so that it can run
@@ -88,10 +92,11 @@ module.exports = cls => class Builder extends cls {
88
92
  for (const node of nodes) {
89
93
  // we skip the target nodes to that workspace in order to make sure
90
94
  // we only run lifecycle scripts / place bin links once per workspace
91
- if (node.isLink)
95
+ if (node.isLink) {
92
96
  linkNodes.add(node)
93
- else
97
+ } else {
94
98
  depNodes.add(node)
99
+ }
95
100
  }
96
101
 
97
102
  await this[_build](depNodes, {})
@@ -118,17 +123,20 @@ module.exports = cls => class Builder extends cls {
118
123
  process.emit('time', `build:${type}`)
119
124
 
120
125
  await this[_buildQueues](nodes)
121
- if (!this[_ignoreScripts])
126
+ if (!this[_ignoreScripts]) {
122
127
  await this[_runScripts]('preinstall')
123
- if (this[_binLinks] && type !== 'links')
128
+ }
129
+ if (this[_binLinks] && type !== 'links') {
124
130
  await this[_linkAllBins]()
131
+ }
125
132
 
126
133
  // links should also run prepare scripts and only link bins after that
127
134
  if (type === 'links') {
128
135
  await this[_runScripts]('prepare')
129
136
 
130
- if (this[_binLinks])
137
+ if (this[_binLinks]) {
131
138
  await this[_linkAllBins]()
139
+ }
132
140
  }
133
141
 
134
142
  if (!this[_ignoreScripts]) {
@@ -173,8 +181,9 @@ module.exports = cls => class Builder extends cls {
173
181
  const { preinstall, install, postinstall, prepare } = scripts
174
182
  const tests = { bin, preinstall, install, postinstall, prepare }
175
183
  for (const [key, has] of Object.entries(tests)) {
176
- if (has)
184
+ if (has) {
177
185
  this[_queues][key].push(node)
186
+ }
178
187
  }
179
188
  }
180
189
  process.emit('timeEnd', 'build:queue')
@@ -186,15 +195,17 @@ module.exports = cls => class Builder extends cls {
186
195
  // the node path. Otherwise a package can have a preinstall script
187
196
  // that unlinks something, to allow them to silently overwrite system
188
197
  // binaries, which is unsafe and insecure.
189
- if (!node.globalTop || this[_force])
198
+ if (!node.globalTop || this[_force]) {
190
199
  return
200
+ }
191
201
  const { path, package: pkg } = node
192
202
  await binLinks.checkBins({ pkg, path, top: true, global: true })
193
203
  }
194
204
 
195
205
  async [_addToBuildSet] (node, set, refreshed = false) {
196
- if (set.has(node))
206
+ if (set.has(node)) {
197
207
  return
208
+ }
198
209
 
199
210
  if (this[_oldMeta] === null) {
200
211
  const {root: {meta}} = node
@@ -233,8 +244,9 @@ module.exports = cls => class Builder extends cls {
233
244
  await isNodeGypPackage(node.path)
234
245
 
235
246
  if (bin || preinstall || install || postinstall || prepare || isGyp) {
236
- if (bin)
247
+ if (bin) {
237
248
  await this[_checkBins](node)
249
+ }
238
250
  if (isGyp) {
239
251
  scripts.install = defaultGypInstallScript
240
252
  node.package.scripts = scripts
@@ -246,8 +258,9 @@ module.exports = cls => class Builder extends cls {
246
258
  async [_runScripts] (event) {
247
259
  const queue = this[_queues][event]
248
260
 
249
- if (!queue.length)
261
+ if (!queue.length) {
250
262
  return
263
+ }
251
264
 
252
265
  process.emit('time', `build:run:${event}`)
253
266
  const stdio = this.options.foregroundScripts ? 'inherit' : 'pipe'
@@ -266,8 +279,9 @@ module.exports = cls => class Builder extends cls {
266
279
  } = node.target
267
280
 
268
281
  // skip any that we know we'll be deleting
269
- if (this[_trashList].has(path))
282
+ if (this[_trashList].has(path)) {
270
283
  return
284
+ }
271
285
 
272
286
  const timer = `build:run:${event}:${location}`
273
287
  process.emit('time', timer)
@@ -321,23 +335,26 @@ module.exports = cls => class Builder extends cls {
321
335
 
322
336
  async [_linkAllBins] () {
323
337
  const queue = this[_queues].bin
324
- if (!queue.length)
338
+ if (!queue.length) {
325
339
  return
340
+ }
326
341
 
327
342
  process.emit('time', 'build:link')
328
343
  const promises = []
329
344
  // sort the queue by node path, so that the module-local collision
330
345
  // detector in bin-links will always resolve the same way.
331
- for (const node of queue.sort(sortNodes))
346
+ for (const node of queue.sort(sortNodes)) {
332
347
  promises.push(this[_createBinLinks](node))
348
+ }
333
349
 
334
350
  await promiseAllRejectLate(promises)
335
351
  process.emit('timeEnd', 'build:link')
336
352
  }
337
353
 
338
354
  async [_createBinLinks] (node) {
339
- if (this[_trashList].has(node.path))
355
+ if (this[_trashList].has(node.path)) {
340
356
  return
357
+ }
341
358
 
342
359
  process.emit('time', `build:link:${node.location}`)
343
360