@npmcli/arborist 7.4.2 → 7.5.1

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.
@@ -7,7 +7,7 @@ const npa = require('npm-package-arg')
7
7
  const semver = require('semver')
8
8
  const debug = require('../debug.js')
9
9
  const { walkUp } = require('walk-up-path')
10
- const log = require('proc-log')
10
+ const { log, time } = require('proc-log')
11
11
  const hgi = require('hosted-git-info')
12
12
  const rpj = require('read-package-json-fast')
13
13
 
@@ -38,119 +38,96 @@ const { saveTypeMap, hasSubKey } = require('../add-rm-pkg-deps.js')
38
38
  const Shrinkwrap = require('../shrinkwrap.js')
39
39
  const { defaultLockfileVersion } = Shrinkwrap
40
40
 
41
- const _retiredPaths = Symbol('retiredPaths')
42
- const _retiredUnchanged = Symbol('retiredUnchanged')
43
- const _sparseTreeDirs = Symbol('sparseTreeDirs')
44
- const _sparseTreeRoots = Symbol('sparseTreeRoots')
45
- const _savePrefix = Symbol('savePrefix')
41
+ // Part of steps (steps need refactoring before we can do anything about these)
46
42
  const _retireShallowNodes = Symbol.for('retireShallowNodes')
47
- const _getBundlesByDepth = Symbol('getBundlesByDepth')
48
- const _registryResolved = Symbol('registryResolved')
49
- const _addNodeToTrashList = Symbol.for('addNodeToTrashList')
43
+ const _loadBundlesAndUpdateTrees = Symbol.for('loadBundlesAndUpdateTrees')
44
+ const _submitQuickAudit = Symbol('submitQuickAudit')
45
+ const _addOmitsToTrashList = Symbol('addOmitsToTrashList')
46
+ const _unpackNewModules = Symbol.for('unpackNewModules')
47
+ const _build = Symbol.for('build')
50
48
 
51
49
  // shared by rebuild mixin
52
50
  const _trashList = Symbol.for('trashList')
53
51
  const _handleOptionalFailure = Symbol.for('handleOptionalFailure')
54
52
  const _loadTrees = Symbol.for('loadTrees')
53
+ // defined by rebuild mixin
54
+ const _checkBins = Symbol.for('checkBins')
55
55
 
56
56
  // shared symbols for swapping out when testing
57
+ // TODO tests should not be this deep into internals
57
58
  const _diffTrees = Symbol.for('diffTrees')
58
59
  const _createSparseTree = Symbol.for('createSparseTree')
59
60
  const _loadShrinkwrapsAndUpdateTrees = Symbol.for('loadShrinkwrapsAndUpdateTrees')
60
- const _shrinkwrapInflated = Symbol('shrinkwrapInflated')
61
- const _bundleUnpacked = Symbol('bundleUnpacked')
62
- const _bundleMissing = Symbol('bundleMissing')
63
61
  const _reifyNode = Symbol.for('reifyNode')
64
- const _extractOrLink = Symbol('extractOrLink')
65
62
  const _updateAll = Symbol.for('updateAll')
66
63
  const _updateNames = Symbol.for('updateNames')
67
- // defined by rebuild mixin
68
- const _checkBins = Symbol.for('checkBins')
69
- const _symlink = Symbol('symlink')
70
- const _warnDeprecated = Symbol('warnDeprecated')
71
- const _loadBundlesAndUpdateTrees = Symbol.for('loadBundlesAndUpdateTrees')
72
- const _submitQuickAudit = Symbol('submitQuickAudit')
73
- const _unpackNewModules = Symbol.for('unpackNewModules')
74
64
  const _moveContents = Symbol.for('moveContents')
75
65
  const _moveBackRetiredUnchanged = Symbol.for('moveBackRetiredUnchanged')
76
- const _build = Symbol.for('build')
77
66
  const _removeTrash = Symbol.for('removeTrash')
78
67
  const _renamePath = Symbol.for('renamePath')
79
68
  const _rollbackRetireShallowNodes = Symbol.for('rollbackRetireShallowNodes')
80
69
  const _rollbackCreateSparseTree = Symbol.for('rollbackCreateSparseTree')
81
70
  const _rollbackMoveBackRetiredUnchanged = Symbol.for('rollbackMoveBackRetiredUnchanged')
82
71
  const _saveIdealTree = Symbol.for('saveIdealTree')
83
- const _copyIdealToActual = Symbol('copyIdealToActual')
84
- const _addOmitsToTrashList = Symbol('addOmitsToTrashList')
85
- const _packageLockOnly = Symbol('packageLockOnly')
86
- const _dryRun = Symbol('dryRun')
87
- const _validateNodeModules = Symbol('validateNodeModules')
88
- const _nmValidated = Symbol('nmValidated')
89
- const _validatePath = Symbol('validatePath')
90
72
  const _reifyPackages = Symbol.for('reifyPackages')
91
73
 
92
- const _omitDev = Symbol('omitDev')
93
- const _omitOptional = Symbol('omitOptional')
94
- const _omitPeer = Symbol('omitPeer')
95
-
96
- const _pruneBundledMetadeps = Symbol('pruneBundledMetadeps')
97
-
98
- // defined by Ideal mixin
74
+ // defined by build-ideal-tree mixin
99
75
  const _resolvedAdd = Symbol.for('resolvedAdd')
100
76
  const _usePackageLock = Symbol.for('usePackageLock')
101
- const _formatPackageLock = Symbol.for('formatPackageLock')
77
+ // used by build-ideal-tree mixin
78
+ const _addNodeToTrashList = Symbol.for('addNodeToTrashList')
102
79
 
103
80
  const _createIsolatedTree = Symbol.for('createIsolatedTree')
104
81
 
105
82
  module.exports = cls => class Reifier extends cls {
83
+ #bundleMissing = new Set() // child nodes we'd EXPECT to be included in a bundle, but aren't
84
+ #bundleUnpacked = new Set() // the nodes we unpack to read their bundles
85
+ #dryRun
86
+ #nmValidated = new Set()
87
+ #omitDev
88
+ #omitPeer
89
+ #omitOptional
90
+ #retiredPaths = {}
91
+ #retiredUnchanged = {}
92
+ #savePrefix
93
+ #shrinkwrapInflated = new Set()
94
+ #sparseTreeDirs = new Set()
95
+ #sparseTreeRoots = new Set()
96
+
106
97
  constructor (options) {
107
98
  super(options)
108
99
 
109
- const {
110
- savePrefix = '^',
111
- packageLockOnly = false,
112
- dryRun = false,
113
- formatPackageLock = true,
114
- } = options
115
-
116
- this[_dryRun] = !!dryRun
117
- this[_packageLockOnly] = !!packageLockOnly
118
- this[_savePrefix] = savePrefix
119
- this[_formatPackageLock] = !!formatPackageLock
120
-
121
- this.diff = null
122
- this[_retiredPaths] = {}
123
- this[_shrinkwrapInflated] = new Set()
124
- this[_retiredUnchanged] = {}
125
- this[_sparseTreeDirs] = new Set()
126
- this[_sparseTreeRoots] = new Set()
127
100
  this[_trashList] = new Set()
128
- // the nodes we unpack to read their bundles
129
- this[_bundleUnpacked] = new Set()
130
- // child nodes we'd EXPECT to be included in a bundle, but aren't
131
- this[_bundleMissing] = new Set()
132
- this[_nmValidated] = new Set()
133
101
  }
134
102
 
135
103
  // public method
136
104
  async reify (options = {}) {
137
105
  const linked = (options.installStrategy || this.options.installStrategy) === 'linked'
138
106
 
139
- if (this[_packageLockOnly] && this.options.global) {
107
+ if (this.options.packageLockOnly && this.options.global) {
140
108
  const er = new Error('cannot generate lockfile for global packages')
141
109
  er.code = 'ESHRINKWRAPGLOBAL'
142
110
  throw er
143
111
  }
144
112
 
145
113
  const omit = new Set(options.omit || [])
146
- this[_omitDev] = omit.has('dev')
147
- this[_omitOptional] = omit.has('optional')
148
- this[_omitPeer] = omit.has('peer')
114
+ this.#omitDev = omit.has('dev')
115
+ this.#omitOptional = omit.has('optional')
116
+ this.#omitPeer = omit.has('peer')
149
117
 
150
118
  // start tracker block
151
119
  this.addTracker('reify')
152
- process.emit('time', 'reify')
153
- await this[_validatePath]()
120
+ const timeEnd = time.start('reify')
121
+ // don't create missing dirs on dry runs
122
+ if (!this.options.packageLockOnly && !this.options.dryRun) {
123
+ // we do NOT want to set ownership on this folder, especially
124
+ // recursively, because it can have other side effects to do that
125
+ // in a project directory. We just want to make it if it's missing.
126
+ await mkdir(resolve(this.path), { recursive: true })
127
+
128
+ // do not allow the top-level node_modules to be a symlink
129
+ await this.#validateNodeModules(resolve(this.path, 'node_modules'))
130
+ }
154
131
  await this[_loadTrees](options)
155
132
 
156
133
  const oldTree = this.idealTree
@@ -159,7 +136,7 @@ module.exports = cls => class Reifier extends cls {
159
136
  // this is currently technical debt which will be resolved in a refactor
160
137
  // of Node/Link trees
161
138
  log.warn('reify', 'The "linked" install strategy is EXPERIMENTAL and may contain bugs.')
162
- this.idealTree = await this[_createIsolatedTree](this.idealTree)
139
+ this.idealTree = await this[_createIsolatedTree]()
163
140
  }
164
141
  await this[_diffTrees]()
165
142
  await this[_reifyPackages]()
@@ -169,37 +146,139 @@ module.exports = cls => class Reifier extends cls {
169
146
  this.idealTree = oldTree
170
147
  }
171
148
  await this[_saveIdealTree](options)
172
- await this[_copyIdealToActual]()
173
- // This is a very bad pattern and I can't wait to stop doing it
174
- this.auditReport = await this.auditReport
149
+ // clean up any trash that is still in the tree
150
+ for (const path of this[_trashList]) {
151
+ const loc = relpath(this.idealTree.realpath, path)
152
+ const node = this.idealTree.inventory.get(loc)
153
+ if (node && node.root === this.idealTree) {
154
+ node.parent = null
155
+ }
156
+ }
175
157
 
176
- this.finishTracker('reify')
177
- process.emit('timeEnd', 'reify')
178
- return treeCheck(this.actualTree)
179
- }
158
+ // if we filtered to only certain nodes, then anything ELSE needs
159
+ // to be untouched in the resulting actual tree, even if it differs
160
+ // in the idealTree. Copy over anything that was in the actual and
161
+ // was not changed, delete anything in the ideal and not actual.
162
+ // Then we move the entire idealTree over to this.actualTree, and
163
+ // save the hidden lockfile.
164
+ if (this.diff && this.diff.filterSet.size) {
165
+ const reroot = new Set()
180
166
 
181
- async [_validatePath] () {
182
- // don't create missing dirs on dry runs
183
- if (this[_packageLockOnly] || this[_dryRun]) {
184
- return
167
+ const { filterSet } = this.diff
168
+ const seen = new Set()
169
+ for (const [loc, ideal] of this.idealTree.inventory.entries()) {
170
+ seen.add(loc)
171
+
172
+ // if it's an ideal node from the filter set, then skip it
173
+ // because we already made whatever changes were necessary
174
+ if (filterSet.has(ideal)) {
175
+ continue
176
+ }
177
+
178
+ // otherwise, if it's not in the actualTree, then it's not a thing
179
+ // that we actually added. And if it IS in the actualTree, then
180
+ // it's something that we left untouched, so we need to record
181
+ // that.
182
+ const actual = this.actualTree.inventory.get(loc)
183
+ if (!actual) {
184
+ ideal.root = null
185
+ } else {
186
+ if ([...actual.linksIn].some(link => filterSet.has(link))) {
187
+ seen.add(actual.location)
188
+ continue
189
+ }
190
+ const { realpath, isLink } = actual
191
+ if (isLink && ideal.isLink && ideal.realpath === realpath) {
192
+ continue
193
+ } else {
194
+ reroot.add(actual)
195
+ }
196
+ }
197
+ }
198
+
199
+ // now find any actual nodes that may not be present in the ideal
200
+ // tree, but were left behind by virtue of not being in the filter
201
+ for (const [loc, actual] of this.actualTree.inventory.entries()) {
202
+ if (seen.has(loc)) {
203
+ continue
204
+ }
205
+ seen.add(loc)
206
+
207
+ // we know that this is something that ISN'T in the idealTree,
208
+ // or else we will have addressed it in the previous loop.
209
+ // If it's in the filterSet, that means we intentionally removed
210
+ // it, so nothing to do here.
211
+ if (filterSet.has(actual)) {
212
+ continue
213
+ }
214
+
215
+ reroot.add(actual)
216
+ }
217
+
218
+ // go through the rerooted actual nodes, and move them over.
219
+ for (const actual of reroot) {
220
+ actual.root = this.idealTree
221
+ }
222
+
223
+ // prune out any tops that lack a linkIn, they are no longer relevant.
224
+ for (const top of this.idealTree.tops) {
225
+ if (top.linksIn.size === 0) {
226
+ top.root = null
227
+ }
228
+ }
229
+
230
+ // need to calculate dep flags, since nodes may have been marked
231
+ // as extraneous or otherwise incorrect during transit.
232
+ calcDepFlags(this.idealTree)
185
233
  }
186
234
 
187
- // we do NOT want to set ownership on this folder, especially
188
- // recursively, because it can have other side effects to do that
189
- // in a project directory. We just want to make it if it's missing.
190
- await mkdir(resolve(this.path), { recursive: true })
235
+ // save the ideal's meta as a hidden lockfile after we actualize it
236
+ this.idealTree.meta.filename =
237
+ this.idealTree.realpath + '/node_modules/.package-lock.json'
238
+ this.idealTree.meta.hiddenLockfile = true
239
+ this.idealTree.meta.lockfileVersion = defaultLockfileVersion
240
+
241
+ this.actualTree = this.idealTree
242
+ this.idealTree = null
243
+
244
+ if (!this.options.global) {
245
+ await this.actualTree.meta.save()
246
+ const ignoreScripts = !!this.options.ignoreScripts
247
+ // if we aren't doing a dry run or ignoring scripts and we actually made changes to the dep
248
+ // tree, then run the dependencies scripts
249
+ if (!this.options.dryRun && !ignoreScripts && this.diff && this.diff.children.length) {
250
+ const { path, package: pkg } = this.actualTree.target
251
+ const stdio = this.options.foregroundScripts ? 'inherit' : 'pipe'
252
+ const { scripts = {} } = pkg
253
+ for (const event of ['predependencies', 'dependencies', 'postdependencies']) {
254
+ if (Object.prototype.hasOwnProperty.call(scripts, event)) {
255
+ log.info('run', pkg._id, event, scripts[event])
256
+ await time.start(`reify:run:${event}`, () => runScript({
257
+ event,
258
+ path,
259
+ pkg,
260
+ stdio,
261
+ scriptShell: this.options.scriptShell,
262
+ }))
263
+ }
264
+ }
265
+ }
266
+ }
267
+ // This is a very bad pattern and I can't wait to stop doing it
268
+ this.auditReport = await this.auditReport
191
269
 
192
- // do not allow the top-level node_modules to be a symlink
193
- await this[_validateNodeModules](resolve(this.path, 'node_modules'))
270
+ this.finishTracker('reify')
271
+ timeEnd()
272
+ return treeCheck(this.actualTree)
194
273
  }
195
274
 
196
275
  async [_reifyPackages] () {
197
276
  // we don't submit the audit report or write to disk on dry runs
198
- if (this[_dryRun]) {
277
+ if (this.options.dryRun) {
199
278
  return
200
279
  }
201
280
 
202
- if (this[_packageLockOnly]) {
281
+ if (this.options.packageLockOnly) {
203
282
  // we already have the complete tree, so just audit it now,
204
283
  // and that's all we have to do here.
205
284
  return this[_submitQuickAudit]()
@@ -248,6 +327,7 @@ module.exports = cls => class Reifier extends cls {
248
327
  throw reifyTerminated
249
328
  }
250
329
  } catch (er) {
330
+ // TODO rollbacks shouldn't be relied on to throw err
251
331
  await this[rollback](er)
252
332
  /* istanbul ignore next - rollback throws, should never hit this */
253
333
  throw er
@@ -269,16 +349,15 @@ module.exports = cls => class Reifier extends cls {
269
349
  // when doing a local install, we load everything and figure it all out.
270
350
  // when doing a global install, we *only* care about the explicit requests.
271
351
  [_loadTrees] (options) {
272
- process.emit('time', 'reify:loadTrees')
352
+ const timeEnd = time.start('reify:loadTrees')
273
353
  const bitOpt = {
274
354
  ...options,
275
- complete: this[_packageLockOnly] || this[_dryRun],
355
+ complete: this.options.packageLockOnly || this.options.dryRun,
276
356
  }
277
357
 
278
358
  // if we're only writing a package lock, then it doesn't matter what's here
279
- if (this[_packageLockOnly]) {
280
- return this.buildIdealTree(bitOpt)
281
- .then(() => process.emit('timeEnd', 'reify:loadTrees'))
359
+ if (this.options.packageLockOnly) {
360
+ return this.buildIdealTree(bitOpt).then(timeEnd)
282
361
  }
283
362
 
284
363
  const actualOpt = this.options.global ? {
@@ -312,7 +391,7 @@ module.exports = cls => class Reifier extends cls {
312
391
  return Promise.all([
313
392
  this.loadActual(actualOpt),
314
393
  this.buildIdealTree(bitOpt),
315
- ]).then(() => process.emit('timeEnd', 'reify:loadTrees'))
394
+ ]).then(timeEnd)
316
395
  }
317
396
 
318
397
  // the global install space tends to have a lot of stuff in it. don't
@@ -322,15 +401,15 @@ module.exports = cls => class Reifier extends cls {
322
401
  // explicitRequests which is set during buildIdealTree
323
402
  return this.buildIdealTree(bitOpt)
324
403
  .then(() => this.loadActual(actualOpt))
325
- .then(() => process.emit('timeEnd', 'reify:loadTrees'))
404
+ .then(timeEnd)
326
405
  }
327
406
 
328
407
  [_diffTrees] () {
329
- if (this[_packageLockOnly]) {
408
+ if (this.options.packageLockOnly) {
330
409
  return
331
410
  }
332
411
 
333
- process.emit('time', 'reify:diffTrees')
412
+ const timeEnd = time.start('reify:diffTrees')
334
413
  // XXX if we have an existing diff already, there should be a way
335
414
  // to just invalidate the parts that changed, but avoid walking the
336
415
  // whole tree again.
@@ -384,7 +463,7 @@ module.exports = cls => class Reifier extends cls {
384
463
  // find all the nodes that need to change between the actual
385
464
  // and ideal trees.
386
465
  this.diff = Diff.calculate({
387
- shrinkwrapInflated: this[_shrinkwrapInflated],
466
+ shrinkwrapInflated: this.#shrinkwrapInflated,
388
467
  filterNodes,
389
468
  actual: this.actualTree,
390
469
  ideal: this.idealTree,
@@ -397,7 +476,7 @@ module.exports = cls => class Reifier extends cls {
397
476
  // because if we remove node_modules/FOO on case-insensitive systems,
398
477
  // it will remove the dep that we *want* at node_modules/foo.
399
478
 
400
- process.emit('timeEnd', 'reify:diffTrees')
479
+ timeEnd()
401
480
  }
402
481
 
403
482
  // add the node and all its bins to the list of things to be
@@ -406,7 +485,7 @@ module.exports = cls => class Reifier extends cls {
406
485
  // replace them when rolling back on failure.
407
486
  [_addNodeToTrashList] (node, retire = false) {
408
487
  const paths = [node.path, ...node.binPaths]
409
- const moves = this[_retiredPaths]
488
+ const moves = this.#retiredPaths
410
489
  log.silly('reify', 'mark', retire ? 'retired' : 'deleted', paths)
411
490
  for (const path of paths) {
412
491
  if (retire) {
@@ -422,8 +501,8 @@ module.exports = cls => class Reifier extends cls {
422
501
  // move aside the shallowest nodes in the tree that have to be
423
502
  // changed or removed, so that we can rollback if necessary.
424
503
  [_retireShallowNodes] () {
425
- process.emit('time', 'reify:retireShallow')
426
- const moves = this[_retiredPaths] = {}
504
+ const timeEnd = time.start('reify:retireShallow')
505
+ const moves = this.#retiredPaths = {}
427
506
  for (const diff of this.diff.children) {
428
507
  if (diff.action === 'CHANGE' || diff.action === 'REMOVE') {
429
508
  // we'll have to clean these up at the end, so add them to the list
@@ -433,8 +512,7 @@ module.exports = cls => class Reifier extends cls {
433
512
  log.silly('reify', 'moves', moves)
434
513
  const movePromises = Object.entries(moves)
435
514
  .map(([from, to]) => this[_renamePath](from, to))
436
- return promiseAllRejectLate(movePromises)
437
- .then(() => process.emit('timeEnd', 'reify:retireShallow'))
515
+ return promiseAllRejectLate(movePromises).then(timeEnd)
438
516
  }
439
517
 
440
518
  [_renamePath] (from, to, didMkdirp = false) {
@@ -456,14 +534,14 @@ module.exports = cls => class Reifier extends cls {
456
534
  }
457
535
 
458
536
  [_rollbackRetireShallowNodes] (er) {
459
- process.emit('time', 'reify:rollback:retireShallow')
460
- const moves = this[_retiredPaths]
537
+ const timeEnd = time.start('reify:rollback:retireShallow')
538
+ const moves = this.#retiredPaths
461
539
  const movePromises = Object.entries(moves)
462
540
  .map(([from, to]) => this[_renamePath](to, from))
463
541
  return promiseAllRejectLate(movePromises)
464
542
  // ignore subsequent rollback errors
465
- .catch(er => {})
466
- .then(() => process.emit('timeEnd', 'reify:rollback:retireShallow'))
543
+ .catch(() => {})
544
+ .then(timeEnd)
467
545
  .then(() => {
468
546
  throw er
469
547
  })
@@ -472,11 +550,11 @@ module.exports = cls => class Reifier extends cls {
472
550
  // adding to the trash list will skip reifying, and delete them
473
551
  // if they are currently in the tree and otherwise untouched.
474
552
  [_addOmitsToTrashList] () {
475
- if (!this[_omitDev] && !this[_omitOptional] && !this[_omitPeer]) {
553
+ if (!this.#omitDev && !this.#omitOptional && !this.#omitPeer) {
476
554
  return
477
555
  }
478
556
 
479
- process.emit('time', 'reify:trashOmits')
557
+ const timeEnd = time.start('reify:trashOmits')
480
558
 
481
559
  for (const node of this.idealTree.inventory.values()) {
482
560
  const { top } = node
@@ -494,26 +572,26 @@ module.exports = cls => class Reifier extends cls {
494
572
 
495
573
  // omit node if the dep type matches any omit flags that were set
496
574
  if (
497
- node.peer && this[_omitPeer] ||
498
- node.dev && this[_omitDev] ||
499
- node.optional && this[_omitOptional] ||
500
- node.devOptional && this[_omitOptional] && this[_omitDev]
575
+ node.peer && this.#omitPeer ||
576
+ node.dev && this.#omitDev ||
577
+ node.optional && this.#omitOptional ||
578
+ node.devOptional && this.#omitOptional && this.#omitDev
501
579
  ) {
502
580
  this[_addNodeToTrashList](node)
503
581
  }
504
582
  }
505
583
 
506
- process.emit('timeEnd', 'reify:trashOmits')
584
+ timeEnd()
507
585
  }
508
586
 
509
587
  [_createSparseTree] () {
510
- process.emit('time', 'reify:createSparse')
588
+ const timeEnd = time.start('reify:createSparse')
511
589
  // if we call this fn again, we look for the previous list
512
590
  // so that we can avoid making the same directory multiple times
513
591
  const leaves = this.diff.leaves
514
592
  .filter(diff => {
515
593
  return (diff.action === 'ADD' || diff.action === 'CHANGE') &&
516
- !this[_sparseTreeDirs].has(diff.ideal.path) &&
594
+ !this.#sparseTreeDirs.has(diff.ideal.path) &&
517
595
  !diff.ideal.isLink
518
596
  })
519
597
  .map(diff => diff.ideal)
@@ -530,37 +608,36 @@ module.exports = cls => class Reifier extends cls {
530
608
  continue
531
609
  }
532
610
  dirsChecked.add(d)
533
- const st = await lstat(d).catch(er => null)
611
+ const st = await lstat(d).catch(() => null)
534
612
  // this can happen if we have a link to a package with a name
535
613
  // that the filesystem treats as if it is the same thing.
536
614
  // would be nice to have conditional istanbul ignores here...
537
615
  /* istanbul ignore next - defense in depth */
538
616
  if (st && !st.isDirectory()) {
539
617
  const retired = retirePath(d)
540
- this[_retiredPaths][d] = retired
618
+ this.#retiredPaths[d] = retired
541
619
  this[_trashList].add(retired)
542
620
  await this[_renamePath](d, retired)
543
621
  }
544
622
  }
545
- this[_sparseTreeDirs].add(node.path)
623
+ this.#sparseTreeDirs.add(node.path)
546
624
  const made = await mkdir(node.path, { recursive: true })
547
625
  // if the directory already exists, made will be undefined. if that's the case
548
626
  // we don't want to remove it because we aren't the ones who created it so we
549
- // omit it from the _sparseTreeRoots
627
+ // omit it from the #sparseTreeRoots
550
628
  if (made) {
551
- this[_sparseTreeRoots].add(made)
629
+ this.#sparseTreeRoots.add(made)
552
630
  }
553
- }))
554
- .then(() => process.emit('timeEnd', 'reify:createSparse'))
631
+ })).then(timeEnd)
555
632
  }
556
633
 
557
634
  [_rollbackCreateSparseTree] (er) {
558
- process.emit('time', 'reify:rollback:createSparse')
635
+ const timeEnd = time.start('reify:rollback:createSparse')
559
636
  // cut the roots of the sparse tree that were created, not the leaves
560
- const roots = this[_sparseTreeRoots]
637
+ const roots = this.#sparseTreeRoots
561
638
  // also delete the moves that we retired, so that we can move them back
562
639
  const failures = []
563
- const targets = [...roots, ...Object.keys(this[_retiredPaths])]
640
+ const targets = [...roots, ...Object.keys(this.#retiredPaths)]
564
641
  const unlinks = targets
565
642
  .map(path => rm(path, { recursive: true, force: true }).catch(er => failures.push([path, er])))
566
643
  return promiseAllRejectLate(unlinks).then(() => {
@@ -569,7 +646,7 @@ module.exports = cls => class Reifier extends cls {
569
646
  log.warn('cleanup', 'Failed to remove some directories', failures)
570
647
  }
571
648
  })
572
- .then(() => process.emit('timeEnd', 'reify:rollback:createSparse'))
649
+ .then(timeEnd)
573
650
  .then(() => this[_rollbackRetireShallowNodes](er))
574
651
  }
575
652
 
@@ -577,7 +654,7 @@ module.exports = cls => class Reifier extends cls {
577
654
  // we need to unpack them, read that shrinkwrap file, and then update
578
655
  // the tree by calling loadVirtual with the node as the root.
579
656
  [_loadShrinkwrapsAndUpdateTrees] () {
580
- const seen = this[_shrinkwrapInflated]
657
+ const seen = this.#shrinkwrapInflated
581
658
  const shrinkwraps = this.diff.leaves
582
659
  .filter(d => (d.action === 'CHANGE' || d.action === 'ADD' || !d.action) &&
583
660
  d.ideal.hasShrinkwrap && !seen.has(d.ideal) &&
@@ -587,7 +664,7 @@ module.exports = cls => class Reifier extends cls {
587
664
  return
588
665
  }
589
666
 
590
- process.emit('time', 'reify:loadShrinkwraps')
667
+ const timeEnd = time.start('reify:loadShrinkwraps')
591
668
 
592
669
  const Arborist = this.constructor
593
670
  return promiseAllRejectLate(shrinkwraps.map(diff => {
@@ -604,7 +681,7 @@ module.exports = cls => class Reifier extends cls {
604
681
  .then(() => this[_createSparseTree]())
605
682
  .then(() => this[_addOmitsToTrashList]())
606
683
  .then(() => this[_loadShrinkwrapsAndUpdateTrees]())
607
- .then(() => process.emit('timeEnd', 'reify:loadShrinkwraps'))
684
+ .then(timeEnd)
608
685
  }
609
686
 
610
687
  // create a symlink for Links, extract for Nodes
@@ -619,8 +696,7 @@ module.exports = cls => class Reifier extends cls {
619
696
  return node
620
697
  }
621
698
 
622
- const timer = `reifyNode:${node.location}`
623
- process.emit('time', timer)
699
+ const timeEnd = time.start(`reifyNode:${node.location}`)
624
700
  this.addTracker('reify', node.name, node.location)
625
701
 
626
702
  const { npmVersion, nodeVersion, cpu, os, libc } = this.options
@@ -636,35 +712,40 @@ module.exports = cls => class Reifier extends cls {
636
712
  checkPlatform(node.package, false, { cpu, os, libc })
637
713
  }
638
714
  await this[_checkBins](node)
639
- await this[_extractOrLink](node)
640
- await this[_warnDeprecated](node)
715
+ await this.#extractOrLink(node)
716
+ const { _id, deprecated } = node.package
717
+ // The .catch is in _handleOptionalFailure. Not ideal, this should be cleaned up.
718
+ // eslint-disable-next-line promise/always-return
719
+ if (deprecated) {
720
+ log.warn('deprecated', `${_id}: ${deprecated}`)
721
+ }
641
722
  })
642
723
 
643
724
  return this[_handleOptionalFailure](node, p)
644
725
  .then(() => {
645
726
  this.finishTracker('reify', node.name, node.location)
646
- process.emit('timeEnd', timer)
727
+ timeEnd()
647
728
  return node
648
729
  })
649
730
  }
650
731
 
651
732
  // do not allow node_modules to be a symlink
652
- async [_validateNodeModules] (nm) {
653
- if (this.options.force || this[_nmValidated].has(nm)) {
733
+ async #validateNodeModules (nm) {
734
+ if (this.options.force || this.#nmValidated.has(nm)) {
654
735
  return
655
736
  }
656
737
  const st = await lstat(nm).catch(() => null)
657
738
  if (!st || st.isDirectory()) {
658
- this[_nmValidated].add(nm)
739
+ this.#nmValidated.add(nm)
659
740
  return
660
741
  }
661
742
  log.warn('reify', 'Removing non-directory', nm)
662
743
  await rm(nm, { recursive: true, force: true })
663
744
  }
664
745
 
665
- async [_extractOrLink] (node) {
746
+ async #extractOrLink (node) {
666
747
  const nm = resolve(node.parent.path, 'node_modules')
667
- await this[_validateNodeModules](nm)
748
+ await this.#validateNodeModules(nm)
668
749
 
669
750
  if (!node.isLink) {
670
751
  // in normal cases, node.resolved should *always* be set by now.
@@ -676,7 +757,7 @@ module.exports = cls => class Reifier extends cls {
676
757
  // entirely, since we can't possibly reify it.
677
758
  let res = null
678
759
  if (node.resolved) {
679
- const registryResolved = this[_registryResolved](node.resolved)
760
+ const registryResolved = this.#registryResolved(node.resolved)
680
761
  if (registryResolved) {
681
762
  res = `${node.name}@${registryResolved}`
682
763
  }
@@ -698,7 +779,7 @@ module.exports = cls => class Reifier extends cls {
698
779
  return
699
780
  }
700
781
  await debug(async () => {
701
- const st = await lstat(node.path).catch(e => null)
782
+ const st = await lstat(node.path).catch(() => null)
702
783
  if (st && !st.isDirectory()) {
703
784
  debug.log('unpacking into a non-directory', node)
704
785
  throw Object.assign(new Error('ENOTDIR: not a directory'), {
@@ -722,10 +803,8 @@ module.exports = cls => class Reifier extends cls {
722
803
 
723
804
  // node.isLink
724
805
  await rm(node.path, { recursive: true, force: true })
725
- await this[_symlink](node)
726
- }
727
806
 
728
- async [_symlink] (node) {
807
+ // symlink
729
808
  const dir = dirname(node.path)
730
809
  const target = node.realpath
731
810
  const rel = relative(dir, target)
@@ -733,17 +812,10 @@ module.exports = cls => class Reifier extends cls {
733
812
  return symlink(rel, node.path, 'junction')
734
813
  }
735
814
 
736
- [_warnDeprecated] (node) {
737
- const { _id, deprecated } = node.package
738
- if (deprecated) {
739
- log.warn('deprecated', `${_id}: ${deprecated}`)
740
- }
741
- }
742
-
743
815
  // if the node is optional, then the failure of the promise is nonfatal
744
816
  // just add it and its optional set to the trash list.
745
817
  [_handleOptionalFailure] (node, p) {
746
- return (node.optional ? p.catch(er => {
818
+ return (node.optional ? p.catch(() => {
747
819
  const set = optionalSet(node)
748
820
  for (node of set) {
749
821
  log.verbose('reify', 'failed optional dependency', node.path)
@@ -752,7 +824,7 @@ module.exports = cls => class Reifier extends cls {
752
824
  }) : p).then(() => node)
753
825
  }
754
826
 
755
- [_registryResolved] (resolved) {
827
+ #registryResolved (resolved) {
756
828
  // the default registry url is a magic value meaning "the currently
757
829
  // configured registry".
758
830
  // `resolved` must never be falsey.
@@ -782,21 +854,51 @@ module.exports = cls => class Reifier extends cls {
782
854
  // by the contents of the package. however, in their case, rather than
783
855
  // shipping a virtual tree that must be reified, they ship an entire
784
856
  // reified actual tree that must be unpacked and not modified.
785
- [_loadBundlesAndUpdateTrees] (
786
- depth = 0, bundlesByDepth = this[_getBundlesByDepth]()
787
- ) {
857
+ [_loadBundlesAndUpdateTrees] (depth = 0, bundlesByDepth) {
858
+ let maxBundleDepth
859
+ if (!bundlesByDepth) {
860
+ bundlesByDepth = new Map()
861
+ maxBundleDepth = -1
862
+ dfwalk({
863
+ tree: this.diff,
864
+ visit: diff => {
865
+ const node = diff.ideal
866
+ if (!node) {
867
+ return
868
+ }
869
+ if (node.isProjectRoot) {
870
+ return
871
+ }
872
+
873
+ const { bundleDependencies } = node.package
874
+ if (bundleDependencies && bundleDependencies.length) {
875
+ maxBundleDepth = Math.max(maxBundleDepth, node.depth)
876
+ if (!bundlesByDepth.has(node.depth)) {
877
+ bundlesByDepth.set(node.depth, [node])
878
+ } else {
879
+ bundlesByDepth.get(node.depth).push(node)
880
+ }
881
+ }
882
+ },
883
+ getChildren: diff => diff.children,
884
+ })
885
+
886
+ bundlesByDepth.set('maxBundleDepth', maxBundleDepth)
887
+ } else {
888
+ maxBundleDepth = bundlesByDepth.get('maxBundleDepth')
889
+ }
890
+
788
891
  if (depth === 0) {
789
- process.emit('time', 'reify:loadBundles')
892
+ time.start('reify:loadBundles')
790
893
  }
791
894
 
792
- const maxBundleDepth = bundlesByDepth.get('maxBundleDepth')
793
895
  if (depth > maxBundleDepth) {
794
896
  // if we did something, then prune the tree and update the diffs
795
897
  if (maxBundleDepth !== -1) {
796
- this[_pruneBundledMetadeps](bundlesByDepth)
898
+ this.#pruneBundledMetadeps(bundlesByDepth)
797
899
  this[_diffTrees]()
798
900
  }
799
- process.emit('timeEnd', 'reify:loadBundles')
901
+ time.end('reify:loadBundles')
800
902
  return
801
903
  }
802
904
 
@@ -814,7 +916,7 @@ module.exports = cls => class Reifier extends cls {
814
916
  // extract all the nodes with bundles
815
917
  return promiseCallLimit(set.map(node => {
816
918
  return () => {
817
- this[_bundleUnpacked].add(node)
919
+ this.#bundleUnpacked.add(node)
818
920
  return this[_reifyNode](node)
819
921
  }
820
922
  }), { rejectLate: true })
@@ -843,46 +945,15 @@ module.exports = cls => class Reifier extends cls {
843
945
  },
844
946
  })
845
947
  for (const name of notTransplanted) {
846
- this[_bundleMissing].add(node.children.get(name))
948
+ this.#bundleMissing.add(node.children.get(name))
847
949
  }
848
950
  })))
849
951
  // move onto the next level of bundled items
850
952
  .then(() => this[_loadBundlesAndUpdateTrees](depth + 1, bundlesByDepth))
851
953
  }
852
954
 
853
- [_getBundlesByDepth] () {
854
- const bundlesByDepth = new Map()
855
- let maxBundleDepth = -1
856
- dfwalk({
857
- tree: this.diff,
858
- visit: diff => {
859
- const node = diff.ideal
860
- if (!node) {
861
- return
862
- }
863
- if (node.isProjectRoot) {
864
- return
865
- }
866
-
867
- const { bundleDependencies } = node.package
868
- if (bundleDependencies && bundleDependencies.length) {
869
- maxBundleDepth = Math.max(maxBundleDepth, node.depth)
870
- if (!bundlesByDepth.has(node.depth)) {
871
- bundlesByDepth.set(node.depth, [node])
872
- } else {
873
- bundlesByDepth.get(node.depth).push(node)
874
- }
875
- }
876
- },
877
- getChildren: diff => diff.children,
878
- })
879
-
880
- bundlesByDepth.set('maxBundleDepth', maxBundleDepth)
881
- return bundlesByDepth
882
- }
883
-
884
955
  // https://github.com/npm/cli/issues/1597#issuecomment-667639545
885
- [_pruneBundledMetadeps] (bundlesByDepth) {
956
+ #pruneBundledMetadeps (bundlesByDepth) {
886
957
  const bundleShadowed = new Set()
887
958
 
888
959
  // Example dep graph:
@@ -981,7 +1052,7 @@ module.exports = cls => class Reifier extends cls {
981
1052
  // before finishing the reify() and returning the tree. Thus, we do
982
1053
  // NOT return the promise, as the intent is for this to run in parallel
983
1054
  // with the reification, and be resolved at a later time.
984
- process.emit('time', 'reify:audit')
1055
+ const timeEnd = time.start('reify:audit')
985
1056
  const options = { ...this.options }
986
1057
  const tree = this.idealTree
987
1058
 
@@ -995,7 +1066,7 @@ module.exports = cls => class Reifier extends cls {
995
1066
  }
996
1067
 
997
1068
  this.auditReport = AuditReport.load(tree, options).then(res => {
998
- process.emit('timeEnd', 'reify:audit')
1069
+ timeEnd()
999
1070
  return res
1000
1071
  })
1001
1072
  }
@@ -1005,7 +1076,7 @@ module.exports = cls => class Reifier extends cls {
1005
1076
  // kicking off each unpack job. If any fail, we rm the sparse
1006
1077
  // tree entirely and try to put everything back where it was.
1007
1078
  [_unpackNewModules] () {
1008
- process.emit('time', 'reify:unpack')
1079
+ const timeEnd = time.start('reify:unpack')
1009
1080
  const unpacks = []
1010
1081
  dfwalk({
1011
1082
  tree: this.diff,
@@ -1016,9 +1087,9 @@ module.exports = cls => class Reifier extends cls {
1016
1087
  }
1017
1088
 
1018
1089
  const node = diff.ideal
1019
- const bd = this[_bundleUnpacked].has(node)
1020
- const sw = this[_shrinkwrapInflated].has(node)
1021
- const bundleMissing = this[_bundleMissing].has(node)
1090
+ const bd = this.#bundleUnpacked.has(node)
1091
+ const sw = this.#shrinkwrapInflated.has(node)
1092
+ const bundleMissing = this.#bundleMissing.has(node)
1022
1093
 
1023
1094
  // check whether we still need to unpack this one.
1024
1095
  // test the inDepBundle last, since that's potentially a tree walk.
@@ -1038,8 +1109,7 @@ module.exports = cls => class Reifier extends cls {
1038
1109
  },
1039
1110
  getChildren: diff => diff.children,
1040
1111
  })
1041
- return promiseAllRejectLate(unpacks)
1042
- .then(() => process.emit('timeEnd', 'reify:unpack'))
1112
+ return promiseAllRejectLate(unpacks).then(timeEnd)
1043
1113
  }
1044
1114
 
1045
1115
  // This is the part where we move back the unchanging nodes that were
@@ -1054,9 +1124,9 @@ module.exports = cls => class Reifier extends cls {
1054
1124
  // This is sort of an inverse diff tree, of all the nodes where
1055
1125
  // the actualTree and idealTree _don't_ differ, starting from the
1056
1126
  // shallowest nodes that we moved aside in the first place.
1057
- process.emit('time', 'reify:unretire')
1058
- const moves = this[_retiredPaths]
1059
- this[_retiredUnchanged] = {}
1127
+ const timeEnd = time.start('reify:unretire')
1128
+ const moves = this.#retiredPaths
1129
+ this.#retiredUnchanged = {}
1060
1130
  return promiseAllRejectLate(this.diff.children.map(diff => {
1061
1131
  // skip if nothing was retired
1062
1132
  if (diff.action !== 'CHANGE' && diff.action !== 'REMOVE') {
@@ -1079,7 +1149,7 @@ module.exports = cls => class Reifier extends cls {
1079
1149
  }
1080
1150
  })
1081
1151
 
1082
- this[_retiredUnchanged][retireFolder] = []
1152
+ this.#retiredUnchanged[retireFolder] = []
1083
1153
  return promiseAllRejectLate(diff.unchanged.map(node => {
1084
1154
  // no need to roll back links, since we'll just delete them anyway
1085
1155
  if (node.isLink) {
@@ -1088,11 +1158,11 @@ module.exports = cls => class Reifier extends cls {
1088
1158
  }
1089
1159
 
1090
1160
  // will have been moved/unpacked along with bundler
1091
- if (node.inDepBundle && !this[_bundleMissing].has(node)) {
1161
+ if (node.inDepBundle && !this.#bundleMissing.has(node)) {
1092
1162
  return
1093
1163
  }
1094
1164
 
1095
- this[_retiredUnchanged][retireFolder].push(node)
1165
+ this.#retiredUnchanged[retireFolder].push(node)
1096
1166
 
1097
1167
  const rel = relative(realFolder, node.path)
1098
1168
  const fromPath = resolve(retireFolder, rel)
@@ -1102,8 +1172,7 @@ module.exports = cls => class Reifier extends cls {
1102
1172
  const dir = bd && bd.length ? node.path + '/node_modules' : node.path
1103
1173
  return mkdir(dir, { recursive: true }).then(() => this[_moveContents](node, fromPath))
1104
1174
  }))
1105
- }))
1106
- .then(() => process.emit('timeEnd', 'reify:unretire'))
1175
+ })).then(timeEnd)
1107
1176
  }
1108
1177
 
1109
1178
  // move the contents from the fromPath to the node.path
@@ -1120,10 +1189,10 @@ module.exports = cls => class Reifier extends cls {
1120
1189
  }
1121
1190
 
1122
1191
  [_rollbackMoveBackRetiredUnchanged] (er) {
1123
- const moves = this[_retiredPaths]
1192
+ const moves = this.#retiredPaths
1124
1193
  // flip the mapping around to go back
1125
1194
  const realFolders = new Map(Object.entries(moves).map(([k, v]) => [v, k]))
1126
- const promises = Object.entries(this[_retiredUnchanged])
1195
+ const promises = Object.entries(this.#retiredUnchanged)
1127
1196
  .map(([retireFolder, nodes]) => promiseAllRejectLate(nodes.map(node => {
1128
1197
  const realFolder = realFolders.get(retireFolder)
1129
1198
  const rel = relative(realFolder, node.path)
@@ -1135,7 +1204,7 @@ module.exports = cls => class Reifier extends cls {
1135
1204
  }
1136
1205
 
1137
1206
  [_build] () {
1138
- process.emit('time', 'reify:build')
1207
+ const timeEnd = time.start('reify:build')
1139
1208
 
1140
1209
  // for all the things being installed, run their appropriate scripts
1141
1210
  // run in tip->root order, so as to be more likely to build a node's
@@ -1167,8 +1236,7 @@ module.exports = cls => class Reifier extends cls {
1167
1236
  }
1168
1237
  }
1169
1238
 
1170
- return this.rebuild({ nodes, handleOptionalFailure: true })
1171
- .then(() => process.emit('timeEnd', 'reify:build'))
1239
+ return this.rebuild({ nodes, handleOptionalFailure: true }).then(timeEnd)
1172
1240
  }
1173
1241
 
1174
1242
  // the tree is pretty much built now, so it's cleanup time.
@@ -1176,7 +1244,7 @@ module.exports = cls => class Reifier extends cls {
1176
1244
  // If this fails, there isn't much we can do but tell the user about it.
1177
1245
  // Thankfully, it's pretty unlikely that it'll fail, since rm is a node builtin.
1178
1246
  async [_removeTrash] () {
1179
- process.emit('time', 'reify:trash')
1247
+ const timeEnd = time.start('reify:trash')
1180
1248
  const promises = []
1181
1249
  const failures = []
1182
1250
  const _rm = path => rm(path, { recursive: true, force: true }).catch(er => failures.push([path, er]))
@@ -1189,7 +1257,8 @@ module.exports = cls => class Reifier extends cls {
1189
1257
  if (failures.length) {
1190
1258
  log.warn('cleanup', 'Failed to remove some directories', failures)
1191
1259
  }
1192
- process.emit('timeEnd', 'reify:trash')
1260
+
1261
+ timeEnd()
1193
1262
  }
1194
1263
 
1195
1264
  // last but not least, we save the ideal tree metadata to the package-lock
@@ -1215,14 +1284,14 @@ module.exports = cls => class Reifier extends cls {
1215
1284
  const saveIdealTree = !(
1216
1285
  (!save && !hasUpdates)
1217
1286
  || this.options.global
1218
- || this[_dryRun]
1287
+ || this.options.dryRun
1219
1288
  )
1220
1289
 
1221
1290
  if (!saveIdealTree) {
1222
1291
  return false
1223
1292
  }
1224
1293
 
1225
- process.emit('time', 'reify:save')
1294
+ const timeEnd = time.start('reify:save')
1226
1295
 
1227
1296
  const updatedTrees = new Set()
1228
1297
  const updateNodes = nodes => {
@@ -1251,7 +1320,7 @@ module.exports = cls => class Reifier extends cls {
1251
1320
  const isLocalDep = req.type === 'directory' || req.type === 'file'
1252
1321
  if (req.registry) {
1253
1322
  const version = child.version
1254
- const prefixRange = version ? this[_savePrefix] + version : '*'
1323
+ const prefixRange = version ? this.options.savePrefix + version : '*'
1255
1324
  // if we installed a range, then we save the range specified
1256
1325
  // if it is not a subset of the ^x.y.z. eg, installing a range
1257
1326
  // of `1.x <1.2.3` will not be saved as `^1.2.0`, because that
@@ -1286,7 +1355,7 @@ module.exports = cls => class Reifier extends cls {
1286
1355
  // using their relative path
1287
1356
  if (edge.type === 'workspace') {
1288
1357
  const { version } = edge.to.target
1289
- const prefixRange = version ? this[_savePrefix] + version : '*'
1358
+ const prefixRange = version ? this.options.savePrefix + version : '*'
1290
1359
  newSpec = prefixRange
1291
1360
  } else {
1292
1361
  // save the relative path in package.json
@@ -1455,154 +1524,12 @@ module.exports = cls => class Reifier extends cls {
1455
1524
 
1456
1525
  // TODO this ignores options.save
1457
1526
  await this.idealTree.meta.save({
1458
- format: (this[_formatPackageLock] && format) ? format
1459
- : this[_formatPackageLock],
1527
+ format: (this.options.formatPackageLock && format) ? format
1528
+ : this.options.formatPackageLock,
1460
1529
  })
1461
1530
  }
1462
1531
 
1463
- process.emit('timeEnd', 'reify:save')
1532
+ timeEnd()
1464
1533
  return true
1465
1534
  }
1466
-
1467
- async [_copyIdealToActual] () {
1468
- // clean up any trash that is still in the tree
1469
- for (const path of this[_trashList]) {
1470
- const loc = relpath(this.idealTree.realpath, path)
1471
- const node = this.idealTree.inventory.get(loc)
1472
- if (node && node.root === this.idealTree) {
1473
- node.parent = null
1474
- }
1475
- }
1476
-
1477
- // if we filtered to only certain nodes, then anything ELSE needs
1478
- // to be untouched in the resulting actual tree, even if it differs
1479
- // in the idealTree. Copy over anything that was in the actual and
1480
- // was not changed, delete anything in the ideal and not actual.
1481
- // Then we move the entire idealTree over to this.actualTree, and
1482
- // save the hidden lockfile.
1483
- if (this.diff && this.diff.filterSet.size) {
1484
- const reroot = new Set()
1485
-
1486
- const { filterSet } = this.diff
1487
- const seen = new Set()
1488
- for (const [loc, ideal] of this.idealTree.inventory.entries()) {
1489
- seen.add(loc)
1490
-
1491
- // if it's an ideal node from the filter set, then skip it
1492
- // because we already made whatever changes were necessary
1493
- if (filterSet.has(ideal)) {
1494
- continue
1495
- }
1496
-
1497
- // otherwise, if it's not in the actualTree, then it's not a thing
1498
- // that we actually added. And if it IS in the actualTree, then
1499
- // it's something that we left untouched, so we need to record
1500
- // that.
1501
- const actual = this.actualTree.inventory.get(loc)
1502
- if (!actual) {
1503
- ideal.root = null
1504
- } else {
1505
- if ([...actual.linksIn].some(link => filterSet.has(link))) {
1506
- seen.add(actual.location)
1507
- continue
1508
- }
1509
- const { realpath, isLink } = actual
1510
- if (isLink && ideal.isLink && ideal.realpath === realpath) {
1511
- continue
1512
- } else {
1513
- reroot.add(actual)
1514
- }
1515
- }
1516
- }
1517
-
1518
- // now find any actual nodes that may not be present in the ideal
1519
- // tree, but were left behind by virtue of not being in the filter
1520
- for (const [loc, actual] of this.actualTree.inventory.entries()) {
1521
- if (seen.has(loc)) {
1522
- continue
1523
- }
1524
- seen.add(loc)
1525
-
1526
- // we know that this is something that ISN'T in the idealTree,
1527
- // or else we will have addressed it in the previous loop.
1528
- // If it's in the filterSet, that means we intentionally removed
1529
- // it, so nothing to do here.
1530
- if (filterSet.has(actual)) {
1531
- continue
1532
- }
1533
-
1534
- reroot.add(actual)
1535
- }
1536
-
1537
- // go through the rerooted actual nodes, and move them over.
1538
- for (const actual of reroot) {
1539
- actual.root = this.idealTree
1540
- }
1541
-
1542
- // prune out any tops that lack a linkIn, they are no longer relevant.
1543
- for (const top of this.idealTree.tops) {
1544
- if (top.linksIn.size === 0) {
1545
- top.root = null
1546
- }
1547
- }
1548
-
1549
- // need to calculate dep flags, since nodes may have been marked
1550
- // as extraneous or otherwise incorrect during transit.
1551
- calcDepFlags(this.idealTree)
1552
- }
1553
-
1554
- // save the ideal's meta as a hidden lockfile after we actualize it
1555
- this.idealTree.meta.filename =
1556
- this.idealTree.realpath + '/node_modules/.package-lock.json'
1557
- this.idealTree.meta.hiddenLockfile = true
1558
- this.idealTree.meta.lockfileVersion = defaultLockfileVersion
1559
-
1560
- this.actualTree = this.idealTree
1561
- this.idealTree = null
1562
-
1563
- if (!this.options.global) {
1564
- await this.actualTree.meta.save()
1565
- const ignoreScripts = !!this.options.ignoreScripts
1566
- // if we aren't doing a dry run or ignoring scripts and we actually made changes to the dep
1567
- // tree, then run the dependencies scripts
1568
- if (!this[_dryRun] && !ignoreScripts && this.diff && this.diff.children.length) {
1569
- const { path, package: pkg } = this.actualTree.target
1570
- const stdio = this.options.foregroundScripts ? 'inherit' : 'pipe'
1571
- const { scripts = {} } = pkg
1572
- for (const event of ['predependencies', 'dependencies', 'postdependencies']) {
1573
- if (Object.prototype.hasOwnProperty.call(scripts, event)) {
1574
- const timer = `reify:run:${event}`
1575
- process.emit('time', timer)
1576
- log.info('run', pkg._id, event, scripts[event])
1577
- await runScript({
1578
- event,
1579
- path,
1580
- pkg,
1581
- stdio,
1582
- scriptShell: this.options.scriptShell,
1583
- })
1584
- process.emit('timeEnd', timer)
1585
- }
1586
- }
1587
- }
1588
- }
1589
- }
1590
-
1591
- async dedupe (options = {}) {
1592
- // allow the user to set options on the ctor as well.
1593
- // XXX: deprecate separate method options objects.
1594
- options = { ...this.options, ...options }
1595
- const tree = await this.loadVirtual().catch(() => this.loadActual())
1596
- const names = []
1597
- for (const name of tree.inventory.query('name')) {
1598
- if (tree.inventory.query('name', name).size > 1) {
1599
- names.push(name)
1600
- }
1601
- }
1602
- return this.reify({
1603
- ...options,
1604
- preferDedupe: true,
1605
- update: { names },
1606
- })
1607
- }
1608
1535
  }