@npmcli/arborist 7.3.0 → 7.4.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.
@@ -4,7 +4,7 @@ const rpj = require('read-package-json-fast')
4
4
  const npa = require('npm-package-arg')
5
5
  const pacote = require('pacote')
6
6
  const cacache = require('cacache')
7
- const promiseCallLimit = require('promise-call-limit')
7
+ const { callLimit: promiseCallLimit } = require('promise-call-limit')
8
8
  const realpath = require('../../lib/realpath.js')
9
9
  const { resolve, dirname } = require('path')
10
10
  const treeCheck = require('../tree-check.js')
@@ -38,48 +38,63 @@ const resetDepFlags = require('../reset-dep-flags.js')
38
38
  // them with unit tests and reuse them across mixins
39
39
  const _updateAll = Symbol.for('updateAll')
40
40
  const _flagsSuspect = Symbol.for('flagsSuspect')
41
- const _workspaces = Symbol.for('workspaces')
42
41
  const _setWorkspaces = Symbol.for('setWorkspaces')
43
42
  const _updateNames = Symbol.for('updateNames')
44
43
  const _resolvedAdd = Symbol.for('resolvedAdd')
45
44
  const _usePackageLock = Symbol.for('usePackageLock')
46
45
  const _rpcache = Symbol.for('realpathCache')
47
46
  const _stcache = Symbol.for('statCache')
48
- const _includeWorkspaceRoot = Symbol.for('includeWorkspaceRoot')
49
-
50
- // exposed symbol for unit testing the placeDep method directly
51
- const _peerSetSource = Symbol.for('peerSetSource')
52
47
 
53
48
  // used by Reify mixin
54
- const _force = Symbol.for('force')
55
- const _global = Symbol.for('global')
56
- const _idealTreePrune = Symbol.for('idealTreePrune')
49
+ const _addNodeToTrashList = Symbol.for('addNodeToTrashList')
57
50
 
58
51
  // Push items in, pop them sorted by depth and then path
52
+ // Sorts physically shallower deps up to the front of the queue, because
53
+ // they'll affect things deeper in, then alphabetical for consistency between
54
+ // installs
59
55
  class DepsQueue {
56
+ // [{ sorted, items }] indexed by depth
60
57
  #deps = []
61
58
  #sorted = true
59
+ #minDepth = 0
60
+ #length = 0
62
61
 
63
62
  get length () {
64
- return this.#deps.length
63
+ return this.#length
65
64
  }
66
65
 
67
66
  push (item) {
68
- if (!this.#deps.includes(item)) {
69
- this.#sorted = false
70
- this.#deps.push(item)
67
+ if (!this.#deps[item.depth]) {
68
+ this.#length++
69
+ this.#deps[item.depth] = { sorted: true, items: [item] }
70
+ // no minDepth check needed, this branch is only reached when we are in
71
+ // the middle of a shallower depth and creating a new one
72
+ return
73
+ }
74
+ if (!this.#deps[item.depth].items.includes(item)) {
75
+ this.#length++
76
+ this.#deps[item.depth].sorted = false
77
+ this.#deps[item.depth].items.push(item)
78
+ if (item.depth < this.#minDepth) {
79
+ this.#minDepth = item.depth
80
+ }
71
81
  }
72
82
  }
73
83
 
74
84
  pop () {
75
- if (!this.#sorted) {
76
- // sort physically shallower deps up to the front of the queue, because
77
- // they'll affect things deeper in, then alphabetical
78
- this.#deps.sort((a, b) =>
79
- (a.depth - b.depth) || localeCompare(a.path, b.path))
80
- this.#sorted = true
85
+ let depth
86
+ while (!depth?.items.length) {
87
+ depth = this.#deps[this.#minDepth]
88
+ if (!depth?.items.length) {
89
+ this.#minDepth++
90
+ }
81
91
  }
82
- return this.#deps.shift()
92
+ if (!depth.sorted) {
93
+ depth.items.sort((a, b) => localeCompare(a.path, b.path))
94
+ depth.sorted = true
95
+ }
96
+ this.#length--
97
+ return depth.items.shift()
83
98
  }
84
99
  }
85
100
 
@@ -95,6 +110,10 @@ module.exports = cls => class IdealTreeBuilder extends cls {
95
110
  #loadFailures = new Set()
96
111
  #manifests = new Map()
97
112
  #mutateTree = false
113
+ // a map of each module in a peer set to the thing that depended on
114
+ // that set of peers in the first place. Use a WeakMap so that we
115
+ // don't hold onto references for nodes that are garbage collected.
116
+ #peerSetSource = new WeakMap()
98
117
  #preferDedupe = false
99
118
  #prune
100
119
  #strictPeerDeps
@@ -109,20 +128,16 @@ module.exports = cls => class IdealTreeBuilder extends cls {
109
128
 
110
129
  const {
111
130
  follow = false,
112
- force = false,
113
- global = false,
114
131
  installStrategy = 'hoisted',
115
132
  idealTree = null,
116
- includeWorkspaceRoot = false,
117
133
  installLinks = false,
118
134
  legacyPeerDeps = false,
119
135
  packageLock = true,
120
136
  strictPeerDeps = false,
121
- workspaces = [],
137
+ workspaces,
138
+ global,
122
139
  } = options
123
140
 
124
- this[_workspaces] = workspaces || []
125
- this[_force] = !!force
126
141
  this.#strictPeerDeps = !!strictPeerDeps
127
142
 
128
143
  this.idealTree = idealTree
@@ -130,24 +145,16 @@ module.exports = cls => class IdealTreeBuilder extends cls {
130
145
  this.legacyPeerDeps = legacyPeerDeps
131
146
 
132
147
  this[_usePackageLock] = packageLock
133
- this[_global] = !!global
134
148
  this.#installStrategy = global ? 'shallow' : installStrategy
135
149
  this.#follow = !!follow
136
150
 
137
- if (this[_workspaces].length && this[_global]) {
151
+ if (workspaces?.length && global) {
138
152
  throw new Error('Cannot operate on workspaces in global mode')
139
153
  }
140
154
 
141
155
  this[_updateAll] = false
142
156
  this[_updateNames] = []
143
157
  this[_resolvedAdd] = []
144
-
145
- // a map of each module in a peer set to the thing that depended on
146
- // that set of peers in the first place. Use a WeakMap so that we
147
- // don't hold onto references for nodes that are garbage collected.
148
- this[_peerSetSource] = new WeakMap()
149
-
150
- this[_includeWorkspaceRoot] = includeWorkspaceRoot
151
158
  }
152
159
 
153
160
  get explicitRequests () {
@@ -174,7 +181,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
174
181
 
175
182
  process.emit('time', 'idealTree')
176
183
 
177
- if (!options.add && !options.rm && !options.update && this[_global]) {
184
+ if (!options.add && !options.rm && !options.update && this.options.global) {
178
185
  throw new Error('global requires add, rm, or update option')
179
186
  }
180
187
 
@@ -210,7 +217,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
210
217
  for (const node of this.idealTree.inventory.values()) {
211
218
  if (!node.optional) {
212
219
  try {
213
- checkEngine(node.package, npmVersion, nodeVersion, this[_force])
220
+ checkEngine(node.package, npmVersion, nodeVersion, this.options.force)
214
221
  } catch (err) {
215
222
  if (engineStrict) {
216
223
  throw err
@@ -221,7 +228,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
221
228
  current: err.current,
222
229
  })
223
230
  }
224
- checkPlatform(node.package, this[_force])
231
+ checkPlatform(node.package, this.options.force)
225
232
  }
226
233
  }
227
234
  }
@@ -273,7 +280,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
273
280
  async #initTree () {
274
281
  process.emit('time', 'idealTree:init')
275
282
  let root
276
- if (this[_global]) {
283
+ if (this.options.global) {
277
284
  root = await this.#globalRootNode()
278
285
  } else {
279
286
  try {
@@ -291,7 +298,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
291
298
  // When updating all, we load the shrinkwrap, but don't bother
292
299
  // to build out the full virtual tree from it, since we'll be
293
300
  // reconstructing it anyway.
294
- .then(root => this[_global] ? root
301
+ .then(root => this.options.global ? root
295
302
  : !this[_usePackageLock] || this[_updateAll]
296
303
  ? Shrinkwrap.reset({
297
304
  path: this.path,
@@ -307,7 +314,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
307
314
  // Load on a new Arborist object, so the Nodes aren't the same,
308
315
  // or else it'll get super confusing when we change them!
309
316
  .then(async root => {
310
- if ((!this[_updateAll] && !this[_global] && !root.meta.loadedFromDisk) || (this[_global] && this[_updateNames].length)) {
317
+ if ((!this[_updateAll] && !this.options.global && !root.meta.loadedFromDisk) || (this.options.global && this[_updateNames].length)) {
311
318
  await new this.constructor(this.options).loadActual({ root })
312
319
  const tree = root.target
313
320
  // even though we didn't load it from a package-lock.json FILE,
@@ -386,7 +393,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
386
393
  devOptional: false,
387
394
  peer: false,
388
395
  optional: false,
389
- global: this[_global],
396
+ global: this.options.global,
390
397
  installLinks: this.installLinks,
391
398
  legacyPeerDeps: this.legacyPeerDeps,
392
399
  loadOverrides: true,
@@ -401,7 +408,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
401
408
  devOptional: false,
402
409
  peer: false,
403
410
  optional: false,
404
- global: this[_global],
411
+ global: this.options.global,
405
412
  installLinks: this.installLinks,
406
413
  legacyPeerDeps: this.legacyPeerDeps,
407
414
  root,
@@ -416,11 +423,11 @@ module.exports = cls => class IdealTreeBuilder extends cls {
416
423
  process.emit('time', 'idealTree:userRequests')
417
424
  const tree = this.idealTree.target
418
425
 
419
- if (!this[_workspaces].length) {
426
+ if (!this.options.workspaces.length) {
420
427
  await this.#applyUserRequestsToNode(tree, options)
421
428
  } else {
422
- const nodes = this.workspaceNodes(tree, this[_workspaces])
423
- if (this[_includeWorkspaceRoot]) {
429
+ const nodes = this.workspaceNodes(tree, this.options.workspaces)
430
+ if (this.options.includeWorkspaceRoot) {
424
431
  nodes.push(tree)
425
432
  }
426
433
  const appliedRequests = nodes.map(
@@ -436,14 +443,14 @@ module.exports = cls => class IdealTreeBuilder extends cls {
436
443
  // If we have a list of package names to update, and we know it's
437
444
  // going to update them wherever they are, add any paths into those
438
445
  // named nodes to the buildIdealTree queue.
439
- if (!this[_global] && this[_updateNames].length) {
446
+ if (!this.options.global && this[_updateNames].length) {
440
447
  this.#queueNamedUpdates()
441
448
  }
442
449
 
443
450
  // global updates only update the globalTop nodes, but we need to know
444
451
  // that they're there, and not reinstall the world unnecessarily.
445
452
  const globalExplicitUpdateNames = []
446
- if (this[_global] && (this[_updateAll] || this[_updateNames].length)) {
453
+ if (this.options.global && (this[_updateAll] || this[_updateNames].length)) {
447
454
  const nm = resolve(this.path, 'node_modules')
448
455
  const paths = await readdirScoped(nm).catch(() => [])
449
456
  for (const p of paths) {
@@ -488,7 +495,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
488
495
  // triggers a refresh of all edgesOut. this has to be done BEFORE
489
496
  // adding the edges to explicitRequests, because the package setter
490
497
  // resets all edgesOut.
491
- if (add && add.length || rm && rm.length || this[_global]) {
498
+ if (add && add.length || rm && rm.length || this.options.global) {
492
499
  tree.package = tree.package
493
500
  }
494
501
 
@@ -594,7 +601,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
594
601
  //
595
602
  // XXX: how to handle top nodes that aren't the root? Maybe the report
596
603
  // just tells the user to cd into that directory and fix it?
597
- if (this[_force] && this.auditReport && this.auditReport.topVulns.size) {
604
+ if (this.options.force && this.auditReport && this.auditReport.topVulns.size) {
598
605
  options.add = options.add || []
599
606
  options.rm = options.rm || []
600
607
  const nodesTouched = new Set()
@@ -878,7 +885,7 @@ This is a one-time fix-up, please be patient...
878
885
  // dep if allowed.
879
886
 
880
887
  const tasks = []
881
- const peerSource = this[_peerSetSource].get(node) || node
888
+ const peerSource = this.#peerSetSource.get(node) || node
882
889
  for (const edge of this.#problemEdges(node)) {
883
890
  if (edge.peerConflicted) {
884
891
  continue
@@ -936,7 +943,7 @@ This is a one-time fix-up, please be patient...
936
943
 
937
944
  auditReport: this.auditReport,
938
945
  explicitRequest: this.#explicitRequests.has(edge),
939
- force: this[_force],
946
+ force: this.options.force,
940
947
  installLinks: this.installLinks,
941
948
  installStrategy: this.#installStrategy,
942
949
  legacyPeerDeps: this.legacyPeerDeps,
@@ -1016,7 +1023,7 @@ This is a one-time fix-up, please be patient...
1016
1023
  // may well be an optional dep that has gone missing. it'll
1017
1024
  // fail later anyway.
1018
1025
  for (const e of this.#problemEdges(placed)) {
1019
- promises.push(
1026
+ promises.push(() =>
1020
1027
  this.#fetchManifest(npa.resolve(e.name, e.spec, fromPath(placed, e)))
1021
1028
  .catch(er => null)
1022
1029
  )
@@ -1031,7 +1038,7 @@ This is a one-time fix-up, please be patient...
1031
1038
  }
1032
1039
  }
1033
1040
 
1034
- await Promise.all(promises)
1041
+ await promiseCallLimit(promises)
1035
1042
  return this.#buildDepStep()
1036
1043
  }
1037
1044
 
@@ -1077,13 +1084,13 @@ This is a one-time fix-up, please be patient...
1077
1084
 
1078
1085
  // keep track of the thing that caused this node to be included.
1079
1086
  const src = parent.sourceReference
1080
- this[_peerSetSource].set(node, src)
1087
+ this.#peerSetSource.set(node, src)
1081
1088
 
1082
1089
  // do not load the peers along with the set if this is a global top pkg
1083
1090
  // otherwise we'll be tempted to put peers as other top-level installed
1084
1091
  // things, potentially clobbering what's there already, which is not
1085
1092
  // what we want. the missing edges will be picked up on the next pass.
1086
- if (this[_global] && edge.from.isProjectRoot) {
1093
+ if (this.options.global && edge.from.isProjectRoot) {
1087
1094
  return node
1088
1095
  }
1089
1096
 
@@ -1208,8 +1215,12 @@ This is a one-time fix-up, please be patient...
1208
1215
  } else {
1209
1216
  const cleanRawSpec = cleanUrl(spec.rawSpec)
1210
1217
  log.silly('fetch manifest', spec.raw.replace(spec.rawSpec, cleanRawSpec))
1211
- const p = pacote.manifest(spec, options)
1212
- .then(mani => {
1218
+ const o = {
1219
+ ...options,
1220
+ fullMetadata: true,
1221
+ }
1222
+ const p = pacote.manifest(spec, o)
1223
+ .then(({ license, ...mani }) => {
1213
1224
  this.#manifests.set(spec.raw, mani)
1214
1225
  return mani
1215
1226
  })
@@ -1302,7 +1313,7 @@ This is a one-time fix-up, please be patient...
1302
1313
  const parentEdge = node.parent.edgesOut.get(edge.name)
1303
1314
  const { isProjectRoot, isWorkspace } = node.parent.sourceReference
1304
1315
  const isMine = isProjectRoot || isWorkspace
1305
- const conflictOK = this[_force] || !isMine && !this.#strictPeerDeps
1316
+ const conflictOK = this.options.force || !isMine && !this.#strictPeerDeps
1306
1317
 
1307
1318
  if (!edge.to) {
1308
1319
  if (!parentEdge) {
@@ -1389,7 +1400,7 @@ This is a one-time fix-up, please be patient...
1389
1400
  currentEdge: currentEdge ? currentEdge.explain() : null,
1390
1401
  edge: edge.explain(),
1391
1402
  strictPeerDeps: this.#strictPeerDeps,
1392
- force: this[_force],
1403
+ force: this.options.force,
1393
1404
  }
1394
1405
  }
1395
1406
 
@@ -1477,7 +1488,7 @@ This is a one-time fix-up, please be patient...
1477
1488
  // otherwise, don't bother.
1478
1489
  const needPrune = metaFromDisk && (mutateTree || flagsSuspect)
1479
1490
  if (this.#prune && needPrune) {
1480
- this[_idealTreePrune]()
1491
+ this.#idealTreePrune()
1481
1492
  for (const node of this.idealTree.inventory.values()) {
1482
1493
  if (node.extraneous) {
1483
1494
  node.parent = null
@@ -1488,7 +1499,7 @@ This is a one-time fix-up, please be patient...
1488
1499
  process.emit('timeEnd', 'idealTree:fixDepFlags')
1489
1500
  }
1490
1501
 
1491
- [_idealTreePrune] () {
1502
+ #idealTreePrune () {
1492
1503
  for (const node of this.idealTree.inventory.values()) {
1493
1504
  if (node.extraneous) {
1494
1505
  node.parent = null
@@ -1508,4 +1519,29 @@ This is a one-time fix-up, please be patient...
1508
1519
  }
1509
1520
  }
1510
1521
  }
1522
+
1523
+ async prune (options = {}) {
1524
+ // allow the user to set options on the ctor as well.
1525
+ // XXX: deprecate separate method options objects.
1526
+ options = { ...this.options, ...options }
1527
+
1528
+ await this.buildIdealTree(options)
1529
+
1530
+ this.#idealTreePrune()
1531
+
1532
+ if (!this.options.workspacesEnabled) {
1533
+ const excludeNodes = this.excludeWorkspacesDependencySet(this.idealTree)
1534
+ for (const node of this.idealTree.inventory.values()) {
1535
+ if (
1536
+ node.parent !== null
1537
+ && !node.isProjectRoot
1538
+ && !excludeNodes.has(node)
1539
+ ) {
1540
+ this[_addNodeToTrashList](node)
1541
+ }
1542
+ }
1543
+ }
1544
+
1545
+ return this.reify(options)
1546
+ }
1511
1547
  }
@@ -29,15 +29,16 @@
29
29
  const { resolve } = require('path')
30
30
  const { homedir } = require('os')
31
31
  const { depth } = require('treeverse')
32
+ const mapWorkspaces = require('@npmcli/map-workspaces')
33
+ const log = require('proc-log')
34
+
32
35
  const { saveTypeMap } = require('../add-rm-pkg-deps.js')
36
+ const AuditReport = require('../audit-report.js')
37
+ const relpath = require('../relpath.js')
33
38
 
34
39
  const mixins = [
35
40
  require('../tracker.js'),
36
- require('./pruner.js'),
37
- require('./deduper.js'),
38
- require('./audit.js'),
39
41
  require('./build-ideal-tree.js'),
40
- require('./set-workspaces.js'),
41
42
  require('./load-actual.js'),
42
43
  require('./load-virtual.js'),
43
44
  require('./rebuild.js'),
@@ -45,9 +46,8 @@ const mixins = [
45
46
  require('./isolated-reifier.js'),
46
47
  ]
47
48
 
48
- const _workspacesEnabled = Symbol.for('workspacesEnabled')
49
+ const _setWorkspaces = Symbol.for('setWorkspaces')
49
50
  const Base = mixins.reduce((a, b) => b(a), require('events'))
50
- const getWorkspaceNodes = require('../get-workspace-nodes.js')
51
51
 
52
52
  // if it's 1, 2, or 3, set it explicitly that.
53
53
  // if undefined or null, set it null
@@ -72,20 +72,26 @@ class Arborist extends Base {
72
72
  nodeVersion: process.version,
73
73
  ...options,
74
74
  Arborist: this.constructor,
75
- path: options.path || '.',
75
+ binLinks: 'binLinks' in options ? !!options.binLinks : true,
76
76
  cache: options.cache || `${homedir()}/.npm/_cacache`,
77
+ force: !!options.force,
78
+ global: !!options.global,
79
+ ignoreScripts: !!options.ignoreScripts,
80
+ installStrategy: options.global ? 'shallow' : (options.installStrategy ? options.installStrategy : 'hoisted'),
81
+ lockfileVersion: lockfileVersion(options.lockfileVersion),
77
82
  packumentCache: options.packumentCache || new Map(),
78
- workspacesEnabled: options.workspacesEnabled !== false,
83
+ path: options.path || '.',
84
+ rebuildBundle: 'rebuildBundle' in options ? !!options.rebuildBundle : true,
79
85
  replaceRegistryHost: options.replaceRegistryHost,
80
- lockfileVersion: lockfileVersion(options.lockfileVersion),
81
- installStrategy: options.global ? 'shallow' : (options.installStrategy ? options.installStrategy : 'hoisted'),
86
+ scriptShell: options.scriptShell,
87
+ workspaces: options.workspaces || [],
88
+ workspacesEnabled: options.workspacesEnabled !== false,
82
89
  }
90
+ // TODO is this even used? If not is that a bug?
83
91
  this.replaceRegistryHost = this.options.replaceRegistryHost =
84
92
  (!this.options.replaceRegistryHost || this.options.replaceRegistryHost === 'npmjs') ?
85
93
  'registry.npmjs.org' : this.options.replaceRegistryHost
86
94
 
87
- this[_workspacesEnabled] = this.options.workspacesEnabled
88
-
89
95
  if (options.saveType && !saveTypeMap.get(options.saveType)) {
90
96
  throw new Error(`Invalid saveType ${options.saveType}`)
91
97
  }
@@ -97,12 +103,40 @@ class Arborist extends Base {
97
103
  // TODO: We should change these to static functions instead
98
104
  // of methods for the next major version
99
105
 
100
- // returns an array of the actual nodes for all the workspaces
106
+ // Get the actual nodes corresponding to a root node's child workspaces,
107
+ // given a list of workspace names.
101
108
  workspaceNodes (tree, workspaces) {
102
- return getWorkspaceNodes(tree, workspaces)
109
+ const wsMap = tree.workspaces
110
+ if (!wsMap) {
111
+ log.warn('workspaces', 'filter set, but no workspaces present')
112
+ return []
113
+ }
114
+
115
+ const nodes = []
116
+ for (const name of workspaces) {
117
+ const path = wsMap.get(name)
118
+ if (!path) {
119
+ log.warn('workspaces', `${name} in filter set, but not in workspaces`)
120
+ continue
121
+ }
122
+
123
+ const loc = relpath(tree.realpath, path)
124
+ const node = tree.inventory.get(loc)
125
+
126
+ if (!node) {
127
+ log.warn('workspaces', `${name} in filter set, but no workspace folder present`)
128
+ continue
129
+ }
130
+
131
+ nodes.push(node)
132
+ }
133
+
134
+ return nodes
103
135
  }
104
136
 
105
137
  // returns a set of workspace nodes and all their deps
138
+ // TODO why is includeWorkspaceRoot a param?
139
+ // TODO why is workspaces a param?
106
140
  workspaceDependencySet (tree, workspaces, includeWorkspaceRoot) {
107
141
  const wsNodes = this.workspaceNodes(tree, workspaces)
108
142
  if (includeWorkspaceRoot) {
@@ -162,6 +196,60 @@ class Arborist extends Base {
162
196
  })
163
197
  return rootDepSet
164
198
  }
199
+
200
+ async [_setWorkspaces] (node) {
201
+ const workspaces = await mapWorkspaces({
202
+ cwd: node.path,
203
+ pkg: node.package,
204
+ })
205
+
206
+ if (node && workspaces.size) {
207
+ node.workspaces = workspaces
208
+ }
209
+
210
+ return node
211
+ }
212
+
213
+ async audit (options = {}) {
214
+ this.addTracker('audit')
215
+ if (this.options.global) {
216
+ throw Object.assign(
217
+ new Error('`npm audit` does not support testing globals'),
218
+ { code: 'EAUDITGLOBAL' }
219
+ )
220
+ }
221
+
222
+ // allow the user to set options on the ctor as well.
223
+ // XXX: deprecate separate method options objects.
224
+ options = { ...this.options, ...options }
225
+
226
+ process.emit('time', 'audit')
227
+ let tree
228
+ if (options.packageLock === false) {
229
+ // build ideal tree
230
+ await this.loadActual(options)
231
+ await this.buildIdealTree()
232
+ tree = this.idealTree
233
+ } else {
234
+ tree = await this.loadVirtual()
235
+ }
236
+ if (this.options.workspaces.length) {
237
+ options.filterSet = this.workspaceDependencySet(
238
+ tree,
239
+ this.options.workspaces,
240
+ this.options.includeWorkspaceRoot
241
+ )
242
+ }
243
+ if (!options.workspacesEnabled) {
244
+ options.filterSet =
245
+ this.excludeWorkspacesDependencySet(tree)
246
+ }
247
+ this.auditReport = await AuditReport.load(tree, options)
248
+ const ret = options.fix ? this.reify(options) : this.auditReport
249
+ process.emit('timeEnd', 'audit')
250
+ this.finishTracker('audit')
251
+ return ret
252
+ }
165
253
  }
166
254
 
167
255
  module.exports = Arborist
@@ -16,7 +16,6 @@ const realpath = require('../realpath.js')
16
16
 
17
17
  // public symbols
18
18
  const _changePath = Symbol.for('_changePath')
19
- const _global = Symbol.for('global')
20
19
  const _setWorkspaces = Symbol.for('setWorkspaces')
21
20
  const _rpcache = Symbol.for('realpathCache')
22
21
  const _stcache = Symbol.for('statCache')
@@ -45,8 +44,6 @@ module.exports = cls => class ActualLoader extends cls {
45
44
  constructor (options) {
46
45
  super(options)
47
46
 
48
- this[_global] = !!options.global
49
-
50
47
  // the tree of nodes on disk
51
48
  this.actualTree = options.actualTree
52
49
 
@@ -58,6 +55,7 @@ module.exports = cls => class ActualLoader extends cls {
58
55
  }
59
56
 
60
57
  // public method
58
+ // TODO remove options param in next semver major
61
59
  async loadActual (options = {}) {
62
60
  // In the past this.actualTree was set as a promise that eventually
63
61
  // resolved, and overwrite this.actualTree with the resolved value. This
@@ -100,7 +98,7 @@ module.exports = cls => class ActualLoader extends cls {
100
98
  async #loadActual (options) {
101
99
  // mostly realpath to throw if the root doesn't exist
102
100
  const {
103
- global = false,
101
+ global,
104
102
  filter = () => true,
105
103
  root = null,
106
104
  transplantFilter = () => true,