Skip to content

Commit abcbc54

Browse files
authoredApr 30, 2024··
fix(reify): cleanup of Symbols (#7430)
This starts the long process of cleaning reify.js up. All of the remaining symbols are used either elsewhere in arborist, or in tests. A single use function was inlined, and `dedupe` was moved to the main arborist class.
1 parent d679ce8 commit abcbc54

File tree

2 files changed

+275
-314
lines changed

2 files changed

+275
-314
lines changed
 

‎workspaces/arborist/lib/arborist/index.js

+26-1
Original file line numberDiff line numberDiff line change
@@ -74,20 +74,26 @@ class Arborist extends Base {
7474
Arborist: this.constructor,
7575
binLinks: 'binLinks' in options ? !!options.binLinks : true,
7676
cache: options.cache || `${homedir()}/.npm/_cacache`,
77+
dryRun: !!options.dryRun,
78+
formatPackageLock: 'formatPackageLock' in options ? !!options.formatPackageLock : true,
7779
force: !!options.force,
7880
global: !!options.global,
7981
ignoreScripts: !!options.ignoreScripts,
8082
installStrategy: options.global ? 'shallow' : (options.installStrategy ? options.installStrategy : 'hoisted'),
8183
lockfileVersion: lockfileVersion(options.lockfileVersion),
84+
packageLockOnly: !!options.packageLockOnly,
8285
packumentCache: options.packumentCache || new Map(),
8386
path: options.path || '.',
8487
rebuildBundle: 'rebuildBundle' in options ? !!options.rebuildBundle : true,
8588
replaceRegistryHost: options.replaceRegistryHost,
89+
savePrefix: 'savePrefix' in options ? options.savePrefix : '^',
8690
scriptShell: options.scriptShell,
8791
workspaces: options.workspaces || [],
8892
workspacesEnabled: options.workspacesEnabled !== false,
8993
}
90-
// TODO is this even used? If not is that a bug?
94+
// TODO we only ever look at this.options.replaceRegistryHost, not
95+
// this.replaceRegistryHost. Defaulting needs to be written back to
96+
// this.options to work properly
9197
this.replaceRegistryHost = this.options.replaceRegistryHost =
9298
(!this.options.replaceRegistryHost || this.options.replaceRegistryHost === 'npmjs') ?
9399
'registry.npmjs.org' : this.options.replaceRegistryHost
@@ -96,6 +102,7 @@ class Arborist extends Base {
96102
throw new Error(`Invalid saveType ${options.saveType}`)
97103
}
98104
this.cache = resolve(this.options.cache)
105+
this.diff = null
99106
this.path = resolve(this.options.path)
100107
timeEnd()
101108
}
@@ -250,6 +257,24 @@ class Arborist extends Base {
250257
this.finishTracker('audit')
251258
return ret
252259
}
260+
261+
async dedupe (options = {}) {
262+
// allow the user to set options on the ctor as well.
263+
// XXX: deprecate separate method options objects.
264+
options = { ...this.options, ...options }
265+
const tree = await this.loadVirtual().catch(() => this.loadActual())
266+
const names = []
267+
for (const name of tree.inventory.query('name')) {
268+
if (tree.inventory.query('name', name).size > 1) {
269+
names.push(name)
270+
}
271+
}
272+
return this.reify({
273+
...options,
274+
preferDedupe: true,
275+
update: { names },
276+
})
277+
}
253278
}
254279

255280
module.exports = Arborist

‎workspaces/arborist/lib/arborist/reify.js

+249-313
Original file line numberDiff line numberDiff line change
@@ -38,119 +38,96 @@
3838
const Shrinkwrap = require('../shrinkwrap.js')
3939
const { defaultLockfileVersion } = Shrinkwrap
4040

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)
4642
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')
5048

5149
// shared by rebuild mixin
5250
const _trashList = Symbol.for('trashList')
5351
const _handleOptionalFailure = Symbol.for('handleOptionalFailure')
5452
const _loadTrees = Symbol.for('loadTrees')
53+
// defined by rebuild mixin
54+
const _checkBins = Symbol.for('checkBins')
5555

5656
// shared symbols for swapping out when testing
57+
// TODO tests should not be this deep into internals
5758
const _diffTrees = Symbol.for('diffTrees')
5859
const _createSparseTree = Symbol.for('createSparseTree')
5960
const _loadShrinkwrapsAndUpdateTrees = Symbol.for('loadShrinkwrapsAndUpdateTrees')
60-
const _shrinkwrapInflated = Symbol('shrinkwrapInflated')
61-
const _bundleUnpacked = Symbol('bundleUnpacked')
62-
const _bundleMissing = Symbol('bundleMissing')
6361
const _reifyNode = Symbol.for('reifyNode')
64-
const _extractOrLink = Symbol('extractOrLink')
6562
const _updateAll = Symbol.for('updateAll')
6663
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')
7464
const _moveContents = Symbol.for('moveContents')
7565
const _moveBackRetiredUnchanged = Symbol.for('moveBackRetiredUnchanged')
76-
const _build = Symbol.for('build')
7766
const _removeTrash = Symbol.for('removeTrash')
7867
const _renamePath = Symbol.for('renamePath')
7968
const _rollbackRetireShallowNodes = Symbol.for('rollbackRetireShallowNodes')
8069
const _rollbackCreateSparseTree = Symbol.for('rollbackCreateSparseTree')
8170
const _rollbackMoveBackRetiredUnchanged = Symbol.for('rollbackMoveBackRetiredUnchanged')
8271
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')
9072
const _reifyPackages = Symbol.for('reifyPackages')
9173

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
9975
const _resolvedAdd = Symbol.for('resolvedAdd')
10076
const _usePackageLock = Symbol.for('usePackageLock')
101-
const _formatPackageLock = Symbol.for('formatPackageLock')
77+
// used by build-ideal-tree mixin
78+
const _addNodeToTrashList = Symbol.for('addNodeToTrashList')
10279

10380
const _createIsolatedTree = Symbol.for('createIsolatedTree')
10481

10582
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+
10697
constructor (options) {
10798
super(options)
10899

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()
127100
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()
133101
}
134102

135103
// public method
136104
async reify (options = {}) {
137105
const linked = (options.installStrategy || this.options.installStrategy) === 'linked'
138106

139-
if (this[_packageLockOnly] && this.options.global) {
107+
if (this.options.packageLockOnly && this.options.global) {
140108
const er = new Error('cannot generate lockfile for global packages')
141109
er.code = 'ESHRINKWRAPGLOBAL'
142110
throw er
143111
}
144112

145113
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')
149117

150118
// start tracker block
151119
this.addTracker('reify')
152120
const timeEnd = time.start('reify')
153-
await this[_validatePath]()
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+
}
154131
await this[_loadTrees](options)
155132

156133
const oldTree = this.idealTree
@@ -169,7 +146,124 @@
169146
this.idealTree = oldTree
170147
}
171148
await this[_saveIdealTree](options)
172-
await this[_copyIdealToActual]()
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+
}
157+
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()
166+
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)
233+
}
234+
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+
}
173267
// This is a very bad pattern and I can't wait to stop doing it
174268
this.auditReport = await this.auditReport
175269

@@ -178,28 +272,13 @@
178272
return treeCheck(this.actualTree)
179273
}
180274

181-
async [_validatePath] () {
182-
// don't create missing dirs on dry runs
183-
if (this[_packageLockOnly] || this[_dryRun]) {
184-
return
185-
}
186-
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 })
191-
192-
// do not allow the top-level node_modules to be a symlink
193-
await this[_validateNodeModules](resolve(this.path, 'node_modules'))
194-
}
195-
196275
async [_reifyPackages] () {
197276
// we don't submit the audit report or write to disk on dry runs
198-
if (this[_dryRun]) {
277+
if (this.options.dryRun) {
199278
return
200279
}
201280

202-
if (this[_packageLockOnly]) {
281+
if (this.options.packageLockOnly) {
203282
// we already have the complete tree, so just audit it now,
204283
// and that's all we have to do here.
205284
return this[_submitQuickAudit]()
@@ -248,6 +327,7 @@
248327
throw reifyTerminated
249328
}
250329
} catch (er) {
330+
// TODO rollbacks shouldn't be relied on to throw err
251331
await this[rollback](er)
252332
/* istanbul ignore next - rollback throws, should never hit this */
253333
throw er
@@ -272,11 +352,11 @@
272352
const timeEnd = time.start('reify:loadTrees')
273353
const bitOpt = {
274354
...options,
275-
complete: this[_packageLockOnly] || this[_dryRun],
355+
complete: this.options.packageLockOnly || this.options.dryRun,
276356
}
277357

278358
// if we're only writing a package lock, then it doesn't matter what's here
279-
if (this[_packageLockOnly]) {
359+
if (this.options.packageLockOnly) {
280360
return this.buildIdealTree(bitOpt).then(timeEnd)
281361
}
282362

@@ -325,7 +405,7 @@
325405
}
326406

327407
[_diffTrees] () {
328-
if (this[_packageLockOnly]) {
408+
if (this.options.packageLockOnly) {
329409
return
330410
}
331411

@@ -383,7 +463,7 @@
383463
// find all the nodes that need to change between the actual
384464
// and ideal trees.
385465
this.diff = Diff.calculate({
386-
shrinkwrapInflated: this[_shrinkwrapInflated],
466+
shrinkwrapInflated: this.#shrinkwrapInflated,
387467
filterNodes,
388468
actual: this.actualTree,
389469
ideal: this.idealTree,
@@ -405,7 +485,7 @@
405485
// replace them when rolling back on failure.
406486
[_addNodeToTrashList] (node, retire = false) {
407487
const paths = [node.path, ...node.binPaths]
408-
const moves = this[_retiredPaths]
488+
const moves = this.#retiredPaths
409489
log.silly('reify', 'mark', retire ? 'retired' : 'deleted', paths)
410490
for (const path of paths) {
411491
if (retire) {
@@ -422,7 +502,7 @@
422502
// changed or removed, so that we can rollback if necessary.
423503
[_retireShallowNodes] () {
424504
const timeEnd = time.start('reify:retireShallow')
425-
const moves = this[_retiredPaths] = {}
505+
const moves = this.#retiredPaths = {}
426506
for (const diff of this.diff.children) {
427507
if (diff.action === 'CHANGE' || diff.action === 'REMOVE') {
428508
// we'll have to clean these up at the end, so add them to the list
@@ -455,12 +535,12 @@
455535

456536
[_rollbackRetireShallowNodes] (er) {
457537
const timeEnd = time.start('reify:rollback:retireShallow')
458-
const moves = this[_retiredPaths]
538+
const moves = this.#retiredPaths
459539
const movePromises = Object.entries(moves)
460540
.map(([from, to]) => this[_renamePath](to, from))
461541
return promiseAllRejectLate(movePromises)
462542
// ignore subsequent rollback errors
463543
.catch(er => {})
464544
.then(timeEnd)
465545
.then(() => {
466546
throw er
@@ -470,7 +550,7 @@
470550
// adding to the trash list will skip reifying, and delete them
471551
// if they are currently in the tree and otherwise untouched.
472552
[_addOmitsToTrashList] () {
473-
if (!this[_omitDev] && !this[_omitOptional] && !this[_omitPeer]) {
553+
if (!this.#omitDev && !this.#omitOptional && !this.#omitPeer) {
474554
return
475555
}
476556

@@ -492,10 +572,10 @@
492572

493573
// omit node if the dep type matches any omit flags that were set
494574
if (
495-
node.peer && this[_omitPeer] ||
496-
node.dev && this[_omitDev] ||
497-
node.optional && this[_omitOptional] ||
498-
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
499579
) {
500580
this[_addNodeToTrashList](node)
501581
}
@@ -511,7 +591,7 @@
511591
const leaves = this.diff.leaves
512592
.filter(diff => {
513593
return (diff.action === 'ADD' || diff.action === 'CHANGE') &&
514-
!this[_sparseTreeDirs].has(diff.ideal.path) &&
594+
!this.#sparseTreeDirs.has(diff.ideal.path) &&
515595
!diff.ideal.isLink
516596
})
517597
.map(diff => diff.ideal)
@@ -528,36 +608,36 @@
528608
continue
529609
}
530610
dirsChecked.add(d)
531611
const st = await lstat(d).catch(er => null)
532612
// this can happen if we have a link to a package with a name
533613
// that the filesystem treats as if it is the same thing.
534614
// would be nice to have conditional istanbul ignores here...
535615
/* istanbul ignore next - defense in depth */
536616
if (st && !st.isDirectory()) {
537617
const retired = retirePath(d)
538-
this[_retiredPaths][d] = retired
618+
this.#retiredPaths[d] = retired
539619
this[_trashList].add(retired)
540620
await this[_renamePath](d, retired)
541621
}
542622
}
543-
this[_sparseTreeDirs].add(node.path)
623+
this.#sparseTreeDirs.add(node.path)
544624
const made = await mkdir(node.path, { recursive: true })
545625
// if the directory already exists, made will be undefined. if that's the case
546626
// we don't want to remove it because we aren't the ones who created it so we
547-
// omit it from the _sparseTreeRoots
627+
// omit it from the #sparseTreeRoots
548628
if (made) {
549-
this[_sparseTreeRoots].add(made)
629+
this.#sparseTreeRoots.add(made)
550630
}
551631
})).then(timeEnd)
552632
}
553633

554634
[_rollbackCreateSparseTree] (er) {
555635
const timeEnd = time.start('reify:rollback:createSparse')
556636
// cut the roots of the sparse tree that were created, not the leaves
557-
const roots = this[_sparseTreeRoots]
637+
const roots = this.#sparseTreeRoots
558638
// also delete the moves that we retired, so that we can move them back
559639
const failures = []
560-
const targets = [...roots, ...Object.keys(this[_retiredPaths])]
640+
const targets = [...roots, ...Object.keys(this.#retiredPaths)]
561641
const unlinks = targets
562642
.map(path => rm(path, { recursive: true, force: true }).catch(er => failures.push([path, er])))
563643
return promiseAllRejectLate(unlinks).then(() => {
@@ -574,7 +654,7 @@
574654
// we need to unpack them, read that shrinkwrap file, and then update
575655
// the tree by calling loadVirtual with the node as the root.
576656
[_loadShrinkwrapsAndUpdateTrees] () {
577-
const seen = this[_shrinkwrapInflated]
657+
const seen = this.#shrinkwrapInflated
578658
const shrinkwraps = this.diff.leaves
579659
.filter(d => (d.action === 'CHANGE' || d.action === 'ADD' || !d.action) &&
580660
d.ideal.hasShrinkwrap && !seen.has(d.ideal) &&
@@ -632,8 +712,13 @@
632712
checkPlatform(node.package, false, { cpu, os, libc })
633713
}
634714
await this[_checkBins](node)
635-
await this[_extractOrLink](node)
636-
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+
}
637722
})
638723

639724
return this[_handleOptionalFailure](node, p)
@@ -645,22 +730,22 @@
645730
}
646731

647732
// do not allow node_modules to be a symlink
648-
async [_validateNodeModules] (nm) {
649-
if (this.options.force || this[_nmValidated].has(nm)) {
733+
async #validateNodeModules (nm) {
734+
if (this.options.force || this.#nmValidated.has(nm)) {
650735
return
651736
}
652737
const st = await lstat(nm).catch(() => null)
653738
if (!st || st.isDirectory()) {
654-
this[_nmValidated].add(nm)
739+
this.#nmValidated.add(nm)
655740
return
656741
}
657742
log.warn('reify', 'Removing non-directory', nm)
658743
await rm(nm, { recursive: true, force: true })
659744
}
660745

661-
async [_extractOrLink] (node) {
746+
async #extractOrLink (node) {
662747
const nm = resolve(node.parent.path, 'node_modules')
663-
await this[_validateNodeModules](nm)
748+
await this.#validateNodeModules(nm)
664749

665750
if (!node.isLink) {
666751
// in normal cases, node.resolved should *always* be set by now.
@@ -672,7 +757,7 @@
672757
// entirely, since we can't possibly reify it.
673758
let res = null
674759
if (node.resolved) {
675-
const registryResolved = this[_registryResolved](node.resolved)
760+
const registryResolved = this.#registryResolved(node.resolved)
676761
if (registryResolved) {
677762
res = `${node.name}@${registryResolved}`
678763
}
@@ -694,7 +779,7 @@
694779
return
695780
}
696781
await debug(async () => {
697782
const st = await lstat(node.path).catch(e => null)
698783
if (st && !st.isDirectory()) {
699784
debug.log('unpacking into a non-directory', node)
700785
throw Object.assign(new Error('ENOTDIR: not a directory'), {
@@ -718,24 +803,15 @@
718803

719804
// node.isLink
720805
await rm(node.path, { recursive: true, force: true })
721-
await this[_symlink](node)
722-
}
723806

724-
async [_symlink] (node) {
807+
// symlink
725808
const dir = dirname(node.path)
726809
const target = node.realpath
727810
const rel = relative(dir, target)
728811
await mkdir(dir, { recursive: true })
729812
return symlink(rel, node.path, 'junction')
730813
}
731814

732-
[_warnDeprecated] (node) {
733-
const { _id, deprecated } = node.package
734-
if (deprecated) {
735-
log.warn('deprecated', `${_id}: ${deprecated}`)
736-
}
737-
}
738-
739815
// if the node is optional, then the failure of the promise is nonfatal
740816
// just add it and its optional set to the trash list.
741817
[_handleOptionalFailure] (node, p) {
@@ -748,7 +824,7 @@
748824
}) : p).then(() => node)
749825
}
750826

751-
[_registryResolved] (resolved) {
827+
#registryResolved (resolved) {
752828
// the default registry url is a magic value meaning "the currently
753829
// configured registry".
754830
// `resolved` must never be falsey.
@@ -778,18 +854,48 @@
778854
// by the contents of the package. however, in their case, rather than
779855
// shipping a virtual tree that must be reified, they ship an entire
780856
// reified actual tree that must be unpacked and not modified.
781-
[_loadBundlesAndUpdateTrees] (
782-
depth = 0, bundlesByDepth = this[_getBundlesByDepth]()
783-
) {
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+
784891
if (depth === 0) {
785892
time.start('reify:loadBundles')
786893
}
787894

788-
const maxBundleDepth = bundlesByDepth.get('maxBundleDepth')
789895
if (depth > maxBundleDepth) {
790896
// if we did something, then prune the tree and update the diffs
791897
if (maxBundleDepth !== -1) {
792-
this[_pruneBundledMetadeps](bundlesByDepth)
898+
this.#pruneBundledMetadeps(bundlesByDepth)
793899
this[_diffTrees]()
794900
}
795901
time.end('reify:loadBundles')
@@ -810,7 +916,7 @@
810916
// extract all the nodes with bundles
811917
return promiseCallLimit(set.map(node => {
812918
return () => {
813-
this[_bundleUnpacked].add(node)
919+
this.#bundleUnpacked.add(node)
814920
return this[_reifyNode](node)
815921
}
816922
}), { rejectLate: true })
@@ -839,46 +945,15 @@
839945
},
840946
})
841947
for (const name of notTransplanted) {
842-
this[_bundleMissing].add(node.children.get(name))
948+
this.#bundleMissing.add(node.children.get(name))
843949
}
844950
})))
845951
// move onto the next level of bundled items
846952
.then(() => this[_loadBundlesAndUpdateTrees](depth + 1, bundlesByDepth))
847953
}
848954

849-
[_getBundlesByDepth] () {
850-
const bundlesByDepth = new Map()
851-
let maxBundleDepth = -1
852-
dfwalk({
853-
tree: this.diff,
854-
visit: diff => {
855-
const node = diff.ideal
856-
if (!node) {
857-
return
858-
}
859-
if (node.isProjectRoot) {
860-
return
861-
}
862-
863-
const { bundleDependencies } = node.package
864-
if (bundleDependencies && bundleDependencies.length) {
865-
maxBundleDepth = Math.max(maxBundleDepth, node.depth)
866-
if (!bundlesByDepth.has(node.depth)) {
867-
bundlesByDepth.set(node.depth, [node])
868-
} else {
869-
bundlesByDepth.get(node.depth).push(node)
870-
}
871-
}
872-
},
873-
getChildren: diff => diff.children,
874-
})
875-
876-
bundlesByDepth.set('maxBundleDepth', maxBundleDepth)
877-
return bundlesByDepth
878-
}
879-
880955
// https://github.com/npm/cli/issues/1597#issuecomment-667639545
881-
[_pruneBundledMetadeps] (bundlesByDepth) {
956+
#pruneBundledMetadeps (bundlesByDepth) {
882957
const bundleShadowed = new Set()
883958

884959
// Example dep graph:
@@ -1012,9 +1087,9 @@
10121087
}
10131088

10141089
const node = diff.ideal
1015-
const bd = this[_bundleUnpacked].has(node)
1016-
const sw = this[_shrinkwrapInflated].has(node)
1017-
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)
10181093

10191094
// check whether we still need to unpack this one.
10201095
// test the inDepBundle last, since that's potentially a tree walk.
@@ -1050,8 +1125,8 @@
10501125
// the actualTree and idealTree _don't_ differ, starting from the
10511126
// shallowest nodes that we moved aside in the first place.
10521127
const timeEnd = time.start('reify:unretire')
1053-
const moves = this[_retiredPaths]
1054-
this[_retiredUnchanged] = {}
1128+
const moves = this.#retiredPaths
1129+
this.#retiredUnchanged = {}
10551130
return promiseAllRejectLate(this.diff.children.map(diff => {
10561131
// skip if nothing was retired
10571132
if (diff.action !== 'CHANGE' && diff.action !== 'REMOVE') {
@@ -1074,7 +1149,7 @@
10741149
}
10751150
})
10761151

1077-
this[_retiredUnchanged][retireFolder] = []
1152+
this.#retiredUnchanged[retireFolder] = []
10781153
return promiseAllRejectLate(diff.unchanged.map(node => {
10791154
// no need to roll back links, since we'll just delete them anyway
10801155
if (node.isLink) {
@@ -1083,11 +1158,11 @@
10831158
}
10841159

10851160
// will have been moved/unpacked along with bundler
1086-
if (node.inDepBundle && !this[_bundleMissing].has(node)) {
1161+
if (node.inDepBundle && !this.#bundleMissing.has(node)) {
10871162
return
10881163
}
10891164

1090-
this[_retiredUnchanged][retireFolder].push(node)
1165+
this.#retiredUnchanged[retireFolder].push(node)
10911166

10921167
const rel = relative(realFolder, node.path)
10931168
const fromPath = resolve(retireFolder, rel)
@@ -1114,10 +1189,10 @@
11141189
}
11151190

11161191
[_rollbackMoveBackRetiredUnchanged] (er) {
1117-
const moves = this[_retiredPaths]
1192+
const moves = this.#retiredPaths
11181193
// flip the mapping around to go back
11191194
const realFolders = new Map(Object.entries(moves).map(([k, v]) => [v, k]))
1120-
const promises = Object.entries(this[_retiredUnchanged])
1195+
const promises = Object.entries(this.#retiredUnchanged)
11211196
.map(([retireFolder, nodes]) => promiseAllRejectLate(nodes.map(node => {
11221197
const realFolder = realFolders.get(retireFolder)
11231198
const rel = relative(realFolder, node.path)
@@ -1209,7 +1284,7 @@
12091284
const saveIdealTree = !(
12101285
(!save && !hasUpdates)
12111286
|| this.options.global
1212-
|| this[_dryRun]
1287+
|| this.options.dryRun
12131288
)
12141289

12151290
if (!saveIdealTree) {
@@ -1245,7 +1320,7 @@
12451320
const isLocalDep = req.type === 'directory' || req.type === 'file'
12461321
if (req.registry) {
12471322
const version = child.version
1248-
const prefixRange = version ? this[_savePrefix] + version : '*'
1323+
const prefixRange = version ? this.options.savePrefix + version : '*'
12491324
// if we installed a range, then we save the range specified
12501325
// if it is not a subset of the ^x.y.z. eg, installing a range
12511326
// of `1.x <1.2.3` will not be saved as `^1.2.0`, because that
@@ -1280,7 +1355,7 @@
12801355
// using their relative path
12811356
if (edge.type === 'workspace') {
12821357
const { version } = edge.to.target
1283-
const prefixRange = version ? this[_savePrefix] + version : '*'
1358+
const prefixRange = version ? this.options.savePrefix + version : '*'
12841359
newSpec = prefixRange
12851360
} else {
12861361
// save the relative path in package.json
@@ -1449,151 +1524,12 @@
14491524

14501525
// TODO this ignores options.save
14511526
await this.idealTree.meta.save({
1452-
format: (this[_formatPackageLock] && format) ? format
1453-
: this[_formatPackageLock],
1527+
format: (this.options.formatPackageLock && format) ? format
1528+
: this.options.formatPackageLock,
14541529
})
14551530
}
14561531

14571532
timeEnd()
14581533
return true
14591534
}
1460-
1461-
async [_copyIdealToActual] () {
1462-
// clean up any trash that is still in the tree
1463-
for (const path of this[_trashList]) {
1464-
const loc = relpath(this.idealTree.realpath, path)
1465-
const node = this.idealTree.inventory.get(loc)
1466-
if (node && node.root === this.idealTree) {
1467-
node.parent = null
1468-
}
1469-
}
1470-
1471-
// if we filtered to only certain nodes, then anything ELSE needs
1472-
// to be untouched in the resulting actual tree, even if it differs
1473-
// in the idealTree. Copy over anything that was in the actual and
1474-
// was not changed, delete anything in the ideal and not actual.
1475-
// Then we move the entire idealTree over to this.actualTree, and
1476-
// save the hidden lockfile.
1477-
if (this.diff && this.diff.filterSet.size) {
1478-
const reroot = new Set()
1479-
1480-
const { filterSet } = this.diff
1481-
const seen = new Set()
1482-
for (const [loc, ideal] of this.idealTree.inventory.entries()) {
1483-
seen.add(loc)
1484-
1485-
// if it's an ideal node from the filter set, then skip it
1486-
// because we already made whatever changes were necessary
1487-
if (filterSet.has(ideal)) {
1488-
continue
1489-
}
1490-
1491-
// otherwise, if it's not in the actualTree, then it's not a thing
1492-
// that we actually added. And if it IS in the actualTree, then
1493-
// it's something that we left untouched, so we need to record
1494-
// that.
1495-
const actual = this.actualTree.inventory.get(loc)
1496-
if (!actual) {
1497-
ideal.root = null
1498-
} else {
1499-
if ([...actual.linksIn].some(link => filterSet.has(link))) {
1500-
seen.add(actual.location)
1501-
continue
1502-
}
1503-
const { realpath, isLink } = actual
1504-
if (isLink && ideal.isLink && ideal.realpath === realpath) {
1505-
continue
1506-
} else {
1507-
reroot.add(actual)
1508-
}
1509-
}
1510-
}
1511-
1512-
// now find any actual nodes that may not be present in the ideal
1513-
// tree, but were left behind by virtue of not being in the filter
1514-
for (const [loc, actual] of this.actualTree.inventory.entries()) {
1515-
if (seen.has(loc)) {
1516-
continue
1517-
}
1518-
seen.add(loc)
1519-
1520-
// we know that this is something that ISN'T in the idealTree,
1521-
// or else we will have addressed it in the previous loop.
1522-
// If it's in the filterSet, that means we intentionally removed
1523-
// it, so nothing to do here.
1524-
if (filterSet.has(actual)) {
1525-
continue
1526-
}
1527-
1528-
reroot.add(actual)
1529-
}
1530-
1531-
// go through the rerooted actual nodes, and move them over.
1532-
for (const actual of reroot) {
1533-
actual.root = this.idealTree
1534-
}
1535-
1536-
// prune out any tops that lack a linkIn, they are no longer relevant.
1537-
for (const top of this.idealTree.tops) {
1538-
if (top.linksIn.size === 0) {
1539-
top.root = null
1540-
}
1541-
}
1542-
1543-
// need to calculate dep flags, since nodes may have been marked
1544-
// as extraneous or otherwise incorrect during transit.
1545-
calcDepFlags(this.idealTree)
1546-
}
1547-
1548-
// save the ideal's meta as a hidden lockfile after we actualize it
1549-
this.idealTree.meta.filename =
1550-
this.idealTree.realpath + '/node_modules/.package-lock.json'
1551-
this.idealTree.meta.hiddenLockfile = true
1552-
this.idealTree.meta.lockfileVersion = defaultLockfileVersion
1553-
1554-
this.actualTree = this.idealTree
1555-
this.idealTree = null
1556-
1557-
if (!this.options.global) {
1558-
await this.actualTree.meta.save()
1559-
const ignoreScripts = !!this.options.ignoreScripts
1560-
// if we aren't doing a dry run or ignoring scripts and we actually made changes to the dep
1561-
// tree, then run the dependencies scripts
1562-
if (!this[_dryRun] && !ignoreScripts && this.diff && this.diff.children.length) {
1563-
const { path, package: pkg } = this.actualTree.target
1564-
const stdio = this.options.foregroundScripts ? 'inherit' : 'pipe'
1565-
const { scripts = {} } = pkg
1566-
for (const event of ['predependencies', 'dependencies', 'postdependencies']) {
1567-
if (Object.prototype.hasOwnProperty.call(scripts, event)) {
1568-
log.info('run', pkg._id, event, scripts[event])
1569-
await time.start(`reify:run:${event}`, () => runScript({
1570-
event,
1571-
path,
1572-
pkg,
1573-
stdio,
1574-
scriptShell: this.options.scriptShell,
1575-
}))
1576-
}
1577-
}
1578-
}
1579-
}
1580-
}
1581-
1582-
async dedupe (options = {}) {
1583-
// allow the user to set options on the ctor as well.
1584-
// XXX: deprecate separate method options objects.
1585-
options = { ...this.options, ...options }
1586-
const tree = await this.loadVirtual().catch(() => this.loadActual())
1587-
const names = []
1588-
for (const name of tree.inventory.query('name')) {
1589-
if (tree.inventory.query('name', name).size > 1) {
1590-
names.push(name)
1591-
}
1592-
}
1593-
return this.reify({
1594-
...options,
1595-
preferDedupe: true,
1596-
update: { names },
1597-
})
1598-
}
15991535
}

0 commit comments

Comments
 (0)
Please sign in to comment.