@npmcli/arborist 2.2.9 → 2.4.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.
@@ -115,10 +115,6 @@ module.exports = cls => class Builder extends cls {
115
115
  await this[_runScripts]('preinstall')
116
116
  if (this[_binLinks] && type !== 'links')
117
117
  await this[_linkAllBins]()
118
- if (!this[_ignoreScripts]) {
119
- await this[_runScripts]('install')
120
- await this[_runScripts]('postinstall')
121
- }
122
118
 
123
119
  // links should also run prepare scripts and only link bins after that
124
120
  if (type === 'links') {
@@ -128,6 +124,11 @@ module.exports = cls => class Builder extends cls {
128
124
  await this[_linkAllBins]()
129
125
  }
130
126
 
127
+ if (!this[_ignoreScripts]) {
128
+ await this[_runScripts]('install')
129
+ await this[_runScripts]('postinstall')
130
+ }
131
+
131
132
  process.emit('timeEnd', `build:${type}`)
132
133
  }
133
134
 
@@ -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')
@@ -14,6 +13,7 @@ const fs = require('fs')
14
13
  const {promisify} = require('util')
15
14
  const symlink = promisify(fs.symlink)
16
15
  const mkdirp = require('mkdirp-infer-owner')
16
+ const justMkdirp = require('mkdirp')
17
17
  const moveFile = require('@npmcli/move-file')
18
18
  const rimraf = promisify(require('rimraf'))
19
19
  const packageContents = require('@npmcli/installed-package-contents')
@@ -26,6 +26,8 @@ const retirePath = require('../retire-path.js')
26
26
  const promiseAllRejectLate = require('promise-all-reject-late')
27
27
  const optionalSet = require('../optional-set.js')
28
28
  const updateRootPackageJson = require('../update-root-package-json.js')
29
+ const calcDepFlags = require('../calc-dep-flags.js')
30
+ const { saveTypeMap, hasSubKey } = require('../add-rm-pkg-deps.js')
29
31
 
30
32
  const _retiredPaths = Symbol('retiredPaths')
31
33
  const _retiredUnchanged = Symbol('retiredUnchanged')
@@ -36,6 +38,8 @@ const _retireShallowNodes = Symbol.for('retireShallowNodes')
36
38
  const _getBundlesByDepth = Symbol('getBundlesByDepth')
37
39
  const _registryResolved = Symbol('registryResolved')
38
40
  const _addNodeToTrashList = Symbol('addNodeToTrashList')
41
+ const _workspaces = Symbol.for('workspaces')
42
+
39
43
  // shared by rebuild mixin
40
44
  const _trashList = Symbol.for('trashList')
41
45
  const _handleOptionalFailure = Symbol.for('handleOptionalFailure')
@@ -45,7 +49,8 @@ const _loadTrees = Symbol.for('loadTrees')
45
49
  const _diffTrees = Symbol.for('diffTrees')
46
50
  const _createSparseTree = Symbol.for('createSparseTree')
47
51
  const _loadShrinkwrapsAndUpdateTrees = Symbol.for('loadShrinkwrapsAndUpdateTrees')
48
- const _shrinkwrapUnpacked = Symbol('shrinkwrapUnpacked')
52
+ const _shrinkwrapInflated = Symbol('shrinkwrapInflated')
53
+ const _bundleUnpacked = Symbol('bundleUnpacked')
49
54
  const _reifyNode = Symbol.for('reifyNode')
50
55
  const _extractOrLink = Symbol('extractOrLink')
51
56
  // defined by rebuild mixin
@@ -82,7 +87,6 @@ const _global = Symbol.for('global')
82
87
 
83
88
  // defined by Ideal mixin
84
89
  const _pruneBundledMetadeps = Symbol.for('pruneBundledMetadeps')
85
- const _explicitRequests = Symbol.for('explicitRequests')
86
90
  const _resolvedAdd = Symbol.for('resolvedAdd')
87
91
  const _usePackageLock = Symbol.for('usePackageLock')
88
92
  const _formatPackageLock = Symbol.for('formatPackageLock')
@@ -105,7 +109,7 @@ module.exports = cls => class Reifier extends cls {
105
109
 
106
110
  this.diff = null
107
111
  this[_retiredPaths] = {}
108
- this[_shrinkwrapUnpacked] = new Set()
112
+ this[_shrinkwrapInflated] = new Set()
109
113
  this[_retiredUnchanged] = {}
110
114
  this[_sparseTreeDirs] = new Set()
111
115
  this[_sparseTreeRoots] = new Set()
@@ -146,7 +150,10 @@ module.exports = cls => class Reifier extends cls {
146
150
  if (this[_packageLockOnly] || this[_dryRun])
147
151
  return
148
152
 
149
- await mkdirp(resolve(this.path))
153
+ // we do NOT want to set ownership on this folder, especially
154
+ // recursively, because it can have other side effects to do that
155
+ // in a project directory. We just want to make it if it's missing.
156
+ await justMkdirp(resolve(this.path))
150
157
  }
151
158
 
152
159
  async [_reifyPackages] () {
@@ -237,9 +244,25 @@ module.exports = cls => class Reifier extends cls {
237
244
  const actualOpt = this[_global] ? {
238
245
  ignoreMissing: true,
239
246
  global: true,
240
- filter: (node, kid) =>
241
- this[_explicitRequests].size === 0 || !node.isProjectRoot ? true
242
- : (this.idealTree.edgesOut.has(kid) || this[_explicitRequests].has(kid)),
247
+ filter: (node, kid) => {
248
+ // if it's not the project root, and we have no explicit requests,
249
+ // then we're already into a nested dep, so we keep it
250
+ if (this.explicitRequests.size === 0 || !node.isProjectRoot)
251
+ return true
252
+
253
+ // if we added it as an edgeOut, then we want it
254
+ if (this.idealTree.edgesOut.has(kid))
255
+ return true
256
+
257
+ // if it's an explicit request, then we want it
258
+ const hasExplicit = [...this.explicitRequests]
259
+ .some(edge => edge.name === kid)
260
+ if (hasExplicit)
261
+ return true
262
+
263
+ // ignore the rest of the global install folder
264
+ return false
265
+ },
243
266
  } : { ignoreMissing: true }
244
267
 
245
268
  if (!this[_global]) {
@@ -266,9 +289,36 @@ module.exports = cls => class Reifier extends cls {
266
289
  // to just invalidate the parts that changed, but avoid walking the
267
290
  // whole tree again.
268
291
 
292
+ const filterNodes = []
293
+ if (this[_global] && this.explicitRequests.size) {
294
+ const idealTree = this.idealTree.target || this.idealTree
295
+ const actualTree = this.actualTree.target || this.actualTree
296
+ // we ONLY are allowed to make changes in the global top-level
297
+ // children where there's an explicit request.
298
+ for (const { name } of this.explicitRequests) {
299
+ const ideal = idealTree.children.get(name)
300
+ if (ideal)
301
+ filterNodes.push(ideal)
302
+ const actual = actualTree.children.get(name)
303
+ if (actual)
304
+ filterNodes.push(actual)
305
+ }
306
+ } else {
307
+ for (const ws of this[_workspaces]) {
308
+ const ideal = this.idealTree.children.get(ws)
309
+ if (ideal)
310
+ filterNodes.push(ideal)
311
+ const actual = this.actualTree.children.get(ws)
312
+ if (actual)
313
+ filterNodes.push(actual)
314
+ }
315
+ }
316
+
269
317
  // find all the nodes that need to change between the actual
270
318
  // and ideal trees.
271
319
  this.diff = Diff.calculate({
320
+ shrinkwrapInflated: this[_shrinkwrapInflated],
321
+ filterNodes,
272
322
  actual: this.actualTree,
273
323
  ideal: this.idealTree,
274
324
  })
@@ -356,11 +406,14 @@ module.exports = cls => class Reifier extends cls {
356
406
  return
357
407
 
358
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
359
411
  const filter = node =>
360
- node.peer && this[_omitPeer] ||
361
- node.dev && this[_omitDev] ||
362
- node.optional && this[_omitOptional] ||
363
- 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])
364
417
 
365
418
  for (const node of this.idealTree.inventory.filter(filter))
366
419
  this[_addNodeToTrashList](node)
@@ -375,7 +428,8 @@ module.exports = cls => class Reifier extends cls {
375
428
  const dirs = this.diff.leaves
376
429
  .filter(diff => {
377
430
  return (diff.action === 'ADD' || diff.action === 'CHANGE') &&
378
- !this[_sparseTreeDirs].has(diff.ideal.path)
431
+ !this[_sparseTreeDirs].has(diff.ideal.path) &&
432
+ !diff.ideal.isLink
379
433
  })
380
434
  .map(diff => diff.ideal.path)
381
435
 
@@ -409,9 +463,9 @@ module.exports = cls => class Reifier extends cls {
409
463
  // we need to unpack them, read that shrinkwrap file, and then update
410
464
  // the tree by calling loadVirtual with the node as the root.
411
465
  [_loadShrinkwrapsAndUpdateTrees] () {
412
- const seen = this[_shrinkwrapUnpacked]
466
+ const seen = this[_shrinkwrapInflated]
413
467
  const shrinkwraps = this.diff.leaves
414
- .filter(d => (d.action === 'CHANGE' || d.action === 'ADD') &&
468
+ .filter(d => (d.action === 'CHANGE' || d.action === 'ADD' || !d.action) &&
415
469
  d.ideal.hasShrinkwrap && !seen.has(d.ideal) &&
416
470
  !this[_trashList].has(d.ideal.path))
417
471
 
@@ -424,7 +478,7 @@ module.exports = cls => class Reifier extends cls {
424
478
  return promiseAllRejectLate(shrinkwraps.map(diff => {
425
479
  const node = diff.ideal
426
480
  seen.add(node)
427
- return this[_reifyNode](node)
481
+ return diff.action ? this[_reifyNode](node) : node
428
482
  }))
429
483
  .then(nodes => promiseAllRejectLate(nodes.map(node => new Arborist({
430
484
  ...this.options,
@@ -455,7 +509,7 @@ module.exports = cls => class Reifier extends cls {
455
509
 
456
510
  const { npmVersion, nodeVersion } = this.options
457
511
  const p = Promise.resolve()
458
- .then(() => {
512
+ .then(async () => {
459
513
  // when we reify an optional node, check the engine and platform
460
514
  // first. be sure to ignore the --force and --engine-strict flags,
461
515
  // since we always want to skip any optional packages we can't install.
@@ -465,11 +519,11 @@ module.exports = cls => class Reifier extends cls {
465
519
  checkEngine(node.package, npmVersion, nodeVersion, false)
466
520
  checkPlatform(node.package, false)
467
521
  }
522
+ await this[_checkBins](node)
523
+ await this[_extractOrLink](node)
524
+ await this[_warnDeprecated](node)
525
+ await this[_loadAncientPackageDetails](node)
468
526
  })
469
- .then(() => this[_checkBins](node))
470
- .then(() => this[_extractOrLink](node))
471
- .then(() => this[_warnDeprecated](node))
472
- .then(() => this[_loadAncientPackageDetails](node))
473
527
 
474
528
  return this[_handleOptionalFailure](node, p)
475
529
  .then(() => {
@@ -488,8 +542,8 @@ module.exports = cls => class Reifier extends cls {
488
542
  // Do the best with what we have, or else remove it from the tree
489
543
  // entirely, since we can't possibly reify it.
490
544
  const res = node.resolved ? `${node.name}@${this[_registryResolved](node.resolved)}`
491
- : node.package.name && node.version
492
- ? `${node.package.name}@${node.version}`
545
+ : node.packageName && node.version
546
+ ? `${node.packageName}@${node.version}`
493
547
  : null
494
548
 
495
549
  // no idea what this thing is. remove it from the tree.
@@ -515,10 +569,11 @@ module.exports = cls => class Reifier extends cls {
515
569
  })
516
570
  }
517
571
 
518
- [_symlink] (node) {
572
+ async [_symlink] (node) {
519
573
  const dir = dirname(node.path)
520
574
  const target = node.realpath
521
575
  const rel = relative(dir, target)
576
+ await mkdirp(dir)
522
577
  return symlink(rel, node.path, 'junction')
523
578
  }
524
579
 
@@ -585,8 +640,10 @@ module.exports = cls => class Reifier extends cls {
585
640
  [_loadBundlesAndUpdateTrees] (
586
641
  depth = 0, bundlesByDepth = this[_getBundlesByDepth]()
587
642
  ) {
588
- if (depth === 0)
643
+ if (depth === 0) {
644
+ this[_bundleUnpacked] = new Set()
589
645
  process.emit('time', 'reify:loadBundles')
646
+ }
590
647
  const maxBundleDepth = bundlesByDepth.get('maxBundleDepth')
591
648
  if (depth > maxBundleDepth) {
592
649
  // if we did something, then prune the tree and update the diffs
@@ -602,13 +659,17 @@ module.exports = cls => class Reifier extends cls {
602
659
  // shallower bundle overwriting them with a bundled meta-dep.
603
660
  const set = (bundlesByDepth.get(depth) || [])
604
661
  .filter(node => node.root === this.idealTree &&
662
+ node.target !== node.root &&
605
663
  !this[_trashList].has(node.path))
606
664
 
607
665
  if (!set.length)
608
666
  return this[_loadBundlesAndUpdateTrees](depth + 1, bundlesByDepth)
609
667
 
610
668
  // extract all the nodes with bundles
611
- return promiseAllRejectLate(set.map(node => this[_reifyNode](node)))
669
+ return promiseAllRejectLate(set.map(node => {
670
+ this[_bundleUnpacked].add(node)
671
+ return this[_reifyNode](node)
672
+ }))
612
673
  // then load their unpacked children and move into the ideal tree
613
674
  .then(nodes =>
614
675
  promiseAllRejectLate(nodes.map(node => new this.constructor({
@@ -630,8 +691,13 @@ module.exports = cls => class Reifier extends cls {
630
691
  tree: this.diff,
631
692
  visit: diff => {
632
693
  const node = diff.ideal
633
- if (node && !node.isProjectRoot && node.package.bundleDependencies &&
634
- node.package.bundleDependencies.length) {
694
+ if (!node)
695
+ return
696
+ if (node.isProjectRoot || (node.target && node.target.isProjectRoot))
697
+ return
698
+
699
+ const { bundleDependencies } = node.package
700
+ if (bundleDependencies && bundleDependencies.length) {
635
701
  maxBundleDepth = Math.max(maxBundleDepth, node.depth)
636
702
  if (!bundlesByDepth.has(node.depth))
637
703
  bundlesByDepth.set(node.depth, [node])
@@ -736,14 +802,14 @@ module.exports = cls => class Reifier extends cls {
736
802
  return
737
803
 
738
804
  const node = diff.ideal
739
- const bd = node.package.bundleDependencies
740
- const sw = this[_shrinkwrapUnpacked].has(node)
805
+ const bd = this[_bundleUnpacked].has(node)
806
+ const sw = this[_shrinkwrapInflated].has(node)
741
807
 
742
808
  // check whether we still need to unpack this one.
743
809
  // test the inDepBundle last, since that's potentially a tree walk.
744
810
  const doUnpack = node && // can't unpack if removed!
745
811
  !node.isRoot && // root node already exists
746
- !(bd && bd.length) && // already unpacked to read bundle
812
+ !bd && // already unpacked to read bundle
747
813
  !sw && // already unpacked to read sw
748
814
  !node.inDepBundle // already unpacked by another dep's bundle
749
815
 
@@ -886,7 +952,7 @@ module.exports = cls => class Reifier extends cls {
886
952
  // to things like git repos and tarball file/urls. However, if the
887
953
  // user requested 'foo@', and we have a foo@file:../foo, then we should
888
954
  // end up saving the spec we actually used, not whatever they gave us.
889
- if (this[_resolvedAdd]) {
955
+ if (this[_resolvedAdd].length) {
890
956
  const root = this.idealTree
891
957
  const pkg = root.package
892
958
  for (const { name } of this[_resolvedAdd]) {
@@ -896,6 +962,7 @@ module.exports = cls => class Reifier extends cls {
896
962
  const spec = subSpec ? subSpec.rawSpec : rawSpec
897
963
  const child = root.children.get(name)
898
964
 
965
+ let newSpec
899
966
  if (req.registry) {
900
967
  const version = child.version
901
968
  const prefixRange = version ? this[_savePrefix] + version : '*'
@@ -907,16 +974,17 @@ module.exports = cls => class Reifier extends cls {
907
974
  const isRange = (subSpec || req).type === 'range'
908
975
  const range = !isRange || subset(prefixRange, spec, { loose: true })
909
976
  ? prefixRange : spec
910
- const pname = child.package.name
977
+ const pname = child.packageName
911
978
  const alias = name !== pname
912
- updateDepSpec(pkg, name, (alias ? `npm:${pname}@` : '') + range)
979
+ newSpec = alias ? `npm:${pname}@${range}` : range
913
980
  } else if (req.hosted) {
914
981
  // save the git+https url if it has auth, otherwise shortcut
915
982
  const h = req.hosted
916
983
  const opt = { noCommittish: false }
917
- const save = h.https && h.auth ? `git+${h.https(opt)}`
918
- : h.shortcut(opt)
919
- updateDepSpec(pkg, name, save)
984
+ if (h.https && h.auth)
985
+ newSpec = `git+${h.https(opt)}`
986
+ else
987
+ newSpec = h.shortcut(opt)
920
988
  } else if (req.type === 'directory' || req.type === 'file') {
921
989
  // save the relative path in package.json
922
990
  // Normally saveSpec is updated with the proper relative
@@ -925,9 +993,37 @@ module.exports = cls => class Reifier extends cls {
925
993
  // thing, so just get the ultimate fetchSpec and relativize it.
926
994
  const p = req.fetchSpec.replace(/^file:/, '')
927
995
  const rel = relpath(root.realpath, p)
928
- updateDepSpec(pkg, name, `file:${rel}`)
996
+ newSpec = `file:${rel}`
929
997
  } else
930
- updateDepSpec(pkg, name, req.saveSpec)
998
+ newSpec = req.saveSpec
999
+
1000
+ if (options.saveType) {
1001
+ const depType = saveTypeMap.get(options.saveType)
1002
+ pkg[depType][name] = newSpec
1003
+ // rpj will have moved it here if it was in both
1004
+ // if it is empty it will be deleted later
1005
+ if (options.saveType === 'prod' && pkg.optionalDependencies)
1006
+ delete pkg.optionalDependencies[name]
1007
+ } else {
1008
+ if (hasSubKey(pkg, 'dependencies', name))
1009
+ pkg.dependencies[name] = newSpec
1010
+
1011
+ if (hasSubKey(pkg, 'devDependencies', name)) {
1012
+ pkg.devDependencies[name] = newSpec
1013
+ // don't update peer or optional if we don't have to
1014
+ if (hasSubKey(pkg, 'peerDependencies', name) && !intersects(newSpec, pkg.peerDependencies[name]))
1015
+ pkg.peerDependencies[name] = newSpec
1016
+
1017
+ if (hasSubKey(pkg, 'optionalDependencies', name) && !intersects(newSpec, pkg.optionalDependencies[name]))
1018
+ pkg.optionalDependencies[name] = newSpec
1019
+ } else {
1020
+ if (hasSubKey(pkg, 'peerDependencies', name))
1021
+ pkg.peerDependencies[name] = newSpec
1022
+
1023
+ if (hasSubKey(pkg, 'optionalDependencies', name))
1024
+ pkg.optionalDependencies[name] = newSpec
1025
+ }
1026
+ }
931
1027
  }
932
1028
 
933
1029
  // refresh the edges so they have the correct specs
@@ -966,20 +1062,85 @@ module.exports = cls => class Reifier extends cls {
966
1062
  return meta.save(saveOpt)
967
1063
  }
968
1064
 
969
- [_copyIdealToActual] () {
1065
+ async [_copyIdealToActual] () {
1066
+ // clean up any trash that is still in the tree
1067
+ for (const path of this[_trashList]) {
1068
+ const loc = relpath(this.idealTree.realpath, path)
1069
+ const node = this.idealTree.inventory.get(loc)
1070
+ if (node && node.root === this.idealTree)
1071
+ node.parent = null
1072
+ }
1073
+
1074
+ // if we filtered to only certain nodes, then anything ELSE needs
1075
+ // to be untouched in the resulting actual tree, even if it differs
1076
+ // in the idealTree. Copy over anything that was in the actual and
1077
+ // was not changed, delete anything in the ideal and not actual.
1078
+ // Then we move the entire idealTree over to this.actualTree, and
1079
+ // save the hidden lockfile.
1080
+ if (this.diff && this.diff.filterSet.size) {
1081
+ const { filterSet } = this.diff
1082
+ const seen = new Set()
1083
+ for (const [loc, ideal] of this.idealTree.inventory.entries()) {
1084
+ if (seen.has(loc))
1085
+ continue
1086
+ seen.add(loc)
1087
+
1088
+ // if it's an ideal node from the filter set, then skip it
1089
+ // because we already made whatever changes were necessary
1090
+ if (filterSet.has(ideal))
1091
+ continue
1092
+
1093
+ // otherwise, if it's not in the actualTree, then it's not a thing
1094
+ // that we actually added. And if it IS in the actualTree, then
1095
+ // it's something that we left untouched, so we need to record
1096
+ // that.
1097
+ const actual = this.actualTree.inventory.get(loc)
1098
+ if (!actual)
1099
+ ideal.root = null
1100
+ else {
1101
+ if ([...actual.linksIn].some(link => filterSet.has(link))) {
1102
+ seen.add(actual.location)
1103
+ continue
1104
+ }
1105
+ const { realpath, isLink } = actual
1106
+ if (isLink && ideal.isLink && ideal.realpath === realpath)
1107
+ continue
1108
+ else
1109
+ actual.root = this.idealTree
1110
+ }
1111
+ }
1112
+
1113
+ // now find any actual nodes that may not be present in the ideal
1114
+ // tree, but were left behind by virtue of not being in the filter
1115
+ for (const [loc, actual] of this.actualTree.inventory.entries()) {
1116
+ if (seen.has(loc))
1117
+ continue
1118
+ seen.add(loc)
1119
+ if (filterSet.has(actual))
1120
+ continue
1121
+ actual.root = this.idealTree
1122
+ }
1123
+
1124
+ // prune out any tops that lack a linkIn
1125
+ for (const top of this.idealTree.tops) {
1126
+ if (top.linksIn.size === 0)
1127
+ top.root = null
1128
+ }
1129
+
1130
+ // need to calculate dep flags, since nodes may have been marked
1131
+ // as extraneous or otherwise incorrect during transit.
1132
+ calcDepFlags(this.idealTree)
1133
+ }
1134
+
970
1135
  // save the ideal's meta as a hidden lockfile after we actualize it
971
1136
  this.idealTree.meta.filename =
972
- this.path + '/node_modules/.package-lock.json'
1137
+ this.idealTree.realpath + '/node_modules/.package-lock.json'
973
1138
  this.idealTree.meta.hiddenLockfile = true
1139
+
974
1140
  this.actualTree = this.idealTree
975
1141
  this.idealTree = null
976
- for (const path of this[_trashList]) {
977
- const loc = relpath(this.path, path)
978
- const node = this.actualTree.inventory.get(loc)
979
- if (node && node.root === this.actualTree)
980
- node.parent = null
981
- }
982
1142
 
983
- return !this[_global] && this.actualTree.meta.save()
1143
+ if (!this[_global])
1144
+ await this.actualTree.meta.save()
984
1145
  }
985
1146
  }
@@ -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/debug.js CHANGED
@@ -12,6 +12,7 @@
12
12
 
13
13
  // run in debug mode if explicitly requested, running arborist tests,
14
14
  // or working in the arborist project directory.
15
+
15
16
  const debug = process.env.ARBORIST_DEBUG !== '0' && (
16
17
  process.env.ARBORIST_DEBUG === '1' ||
17
18
  /\barborist\b/.test(process.env.NODE_DEBUG || '') ||
@@ -21,4 +22,10 @@ const debug = process.env.ARBORIST_DEBUG !== '0' && (
21
22
  )
22
23
 
23
24
  module.exports = debug ? fn => fn() : () => {}
24
- module.exports.log = (...msg) => module.exports(() => console.error(...msg))
25
+ const red = process.stderr.isTTY ? msg => `\x1B[31m${msg}\x1B[39m` : m => m
26
+ module.exports.log = (...msg) => module.exports(() => {
27
+ const { format } = require('util')
28
+ const prefix = `\n${process.pid} ${red(format(msg.shift()))} `
29
+ msg = (prefix + format(...msg).trim().split('\n').join(prefix)).trim()
30
+ console.error(msg)
31
+ })