Skip to content

Commit 6d1789c

Browse files
authoredFeb 28, 2024··
fix: Arborist code cleanup (#7237)
A bunch of tiny tweaks to Arborist, in service of eventually cleaning it up and removing the mixin approach. Most of the fixes are like before: removing symbols in favor of private attributes so we can easily find where state is being shared. There is also some shuffling of single-use methods into larger classes.
1 parent 818957c commit 6d1789c

19 files changed

+791
-876
lines changed
 

‎workspaces/arborist/lib/arborist/audit.js

-51
This file was deleted.

‎workspaces/arborist/lib/arborist/build-ideal-tree.js

+56-46
Original file line numberDiff line numberDiff line change
@@ -38,22 +38,15 @@ const resetDepFlags = require('../reset-dep-flags.js')
3838
// them with unit tests and reuse them across mixins
3939
const _updateAll = Symbol.for('updateAll')
4040
const _flagsSuspect = Symbol.for('flagsSuspect')
41-
const _workspaces = Symbol.for('workspaces')
4241
const _setWorkspaces = Symbol.for('setWorkspaces')
4342
const _updateNames = Symbol.for('updateNames')
4443
const _resolvedAdd = Symbol.for('resolvedAdd')
4544
const _usePackageLock = Symbol.for('usePackageLock')
4645
const _rpcache = Symbol.for('realpathCache')
4746
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')
5247

5348
// 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')
5750

5851
// Push items in, pop them sorted by depth and then path
5952
// Sorts physically shallower deps up to the front of the queue, because
@@ -117,6 +110,10 @@ module.exports = cls => class IdealTreeBuilder extends cls {
117110
#loadFailures = new Set()
118111
#manifests = new Map()
119112
#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()
120117
#preferDedupe = false
121118
#prune
122119
#strictPeerDeps
@@ -131,45 +128,33 @@ module.exports = cls => class IdealTreeBuilder extends cls {
131128

132129
const {
133130
follow = false,
134-
force = false,
135-
global = false,
136131
installStrategy = 'hoisted',
137132
idealTree = null,
138-
includeWorkspaceRoot = false,
139133
installLinks = false,
140134
legacyPeerDeps = false,
141135
packageLock = true,
142136
strictPeerDeps = false,
143-
workspaces = [],
137+
workspaces,
138+
global,
144139
} = options
145140

146-
this[_workspaces] = workspaces || []
147-
this[_force] = !!force
148141
this.#strictPeerDeps = !!strictPeerDeps
149142

150143
this.idealTree = idealTree
151144
this.installLinks = installLinks
152145
this.legacyPeerDeps = legacyPeerDeps
153146

154147
this[_usePackageLock] = packageLock
155-
this[_global] = !!global
156148
this.#installStrategy = global ? 'shallow' : installStrategy
157149
this.#follow = !!follow
158150

159-
if (this[_workspaces].length && this[_global]) {
151+
if (workspaces?.length && global) {
160152
throw new Error('Cannot operate on workspaces in global mode')
161153
}
162154

163155
this[_updateAll] = false
164156
this[_updateNames] = []
165157
this[_resolvedAdd] = []
166-
167-
// a map of each module in a peer set to the thing that depended on
168-
// that set of peers in the first place. Use a WeakMap so that we
169-
// don't hold onto references for nodes that are garbage collected.
170-
this[_peerSetSource] = new WeakMap()
171-
172-
this[_includeWorkspaceRoot] = includeWorkspaceRoot
173158
}
174159

175160
get explicitRequests () {
@@ -196,7 +181,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
196181

197182
process.emit('time', 'idealTree')
198183

199-
if (!options.add && !options.rm && !options.update && this[_global]) {
184+
if (!options.add && !options.rm && !options.update && this.options.global) {
200185
throw new Error('global requires add, rm, or update option')
201186
}
202187

@@ -232,7 +217,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
232217
for (const node of this.idealTree.inventory.values()) {
233218
if (!node.optional) {
234219
try {
235-
checkEngine(node.package, npmVersion, nodeVersion, this[_force])
220+
checkEngine(node.package, npmVersion, nodeVersion, this.options.force)
236221
} catch (err) {
237222
if (engineStrict) {
238223
throw err
@@ -243,7 +228,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
243228
current: err.current,
244229
})
245230
}
246-
checkPlatform(node.package, this[_force])
231+
checkPlatform(node.package, this.options.force)
247232
}
248233
}
249234
}
@@ -295,7 +280,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
295280
async #initTree () {
296281
process.emit('time', 'idealTree:init')
297282
let root
298-
if (this[_global]) {
283+
if (this.options.global) {
299284
root = await this.#globalRootNode()
300285
} else {
301286
try {
@@ -313,7 +298,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
313298
// When updating all, we load the shrinkwrap, but don't bother
314299
// to build out the full virtual tree from it, since we'll be
315300
// reconstructing it anyway.
316-
.then(root => this[_global] ? root
301+
.then(root => this.options.global ? root
317302
: !this[_usePackageLock] || this[_updateAll]
318303
? Shrinkwrap.reset({
319304
path: this.path,
@@ -329,7 +314,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
329314
// Load on a new Arborist object, so the Nodes aren't the same,
330315
// or else it'll get super confusing when we change them!
331316
.then(async root => {
332-
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)) {
333318
await new this.constructor(this.options).loadActual({ root })
334319
const tree = root.target
335320
// even though we didn't load it from a package-lock.json FILE,
@@ -408,7 +393,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
408393
devOptional: false,
409394
peer: false,
410395
optional: false,
411-
global: this[_global],
396+
global: this.options.global,
412397
installLinks: this.installLinks,
413398
legacyPeerDeps: this.legacyPeerDeps,
414399
loadOverrides: true,
@@ -423,7 +408,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
423408
devOptional: false,
424409
peer: false,
425410
optional: false,
426-
global: this[_global],
411+
global: this.options.global,
427412
installLinks: this.installLinks,
428413
legacyPeerDeps: this.legacyPeerDeps,
429414
root,
@@ -438,11 +423,11 @@ module.exports = cls => class IdealTreeBuilder extends cls {
438423
process.emit('time', 'idealTree:userRequests')
439424
const tree = this.idealTree.target
440425

441-
if (!this[_workspaces].length) {
426+
if (!this.options.workspaces.length) {
442427
await this.#applyUserRequestsToNode(tree, options)
443428
} else {
444-
const nodes = this.workspaceNodes(tree, this[_workspaces])
445-
if (this[_includeWorkspaceRoot]) {
429+
const nodes = this.workspaceNodes(tree, this.options.workspaces)
430+
if (this.options.includeWorkspaceRoot) {
446431
nodes.push(tree)
447432
}
448433
const appliedRequests = nodes.map(
@@ -458,14 +443,14 @@ module.exports = cls => class IdealTreeBuilder extends cls {
458443
// If we have a list of package names to update, and we know it's
459444
// going to update them wherever they are, add any paths into those
460445
// named nodes to the buildIdealTree queue.
461-
if (!this[_global] && this[_updateNames].length) {
446+
if (!this.options.global && this[_updateNames].length) {
462447
this.#queueNamedUpdates()
463448
}
464449

465450
// global updates only update the globalTop nodes, but we need to know
466451
// that they're there, and not reinstall the world unnecessarily.
467452
const globalExplicitUpdateNames = []
468-
if (this[_global] && (this[_updateAll] || this[_updateNames].length)) {
453+
if (this.options.global && (this[_updateAll] || this[_updateNames].length)) {
469454
const nm = resolve(this.path, 'node_modules')
470455
const paths = await readdirScoped(nm).catch(() => [])
471456
for (const p of paths) {
@@ -510,7 +495,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
510495
// triggers a refresh of all edgesOut. this has to be done BEFORE
511496
// adding the edges to explicitRequests, because the package setter
512497
// resets all edgesOut.
513-
if (add && add.length || rm && rm.length || this[_global]) {
498+
if (add && add.length || rm && rm.length || this.options.global) {
514499
tree.package = tree.package
515500
}
516501

@@ -616,7 +601,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
616601
//
617602
// XXX: how to handle top nodes that aren't the root? Maybe the report
618603
// just tells the user to cd into that directory and fix it?
619-
if (this[_force] && this.auditReport && this.auditReport.topVulns.size) {
604+
if (this.options.force && this.auditReport && this.auditReport.topVulns.size) {
620605
options.add = options.add || []
621606
options.rm = options.rm || []
622607
const nodesTouched = new Set()
@@ -900,7 +885,7 @@ This is a one-time fix-up, please be patient...
900885
// dep if allowed.
901886

902887
const tasks = []
903-
const peerSource = this[_peerSetSource].get(node) || node
888+
const peerSource = this.#peerSetSource.get(node) || node
904889
for (const edge of this.#problemEdges(node)) {
905890
if (edge.peerConflicted) {
906891
continue
@@ -958,7 +943,7 @@ This is a one-time fix-up, please be patient...
958943

959944
auditReport: this.auditReport,
960945
explicitRequest: this.#explicitRequests.has(edge),
961-
force: this[_force],
946+
force: this.options.force,
962947
installLinks: this.installLinks,
963948
installStrategy: this.#installStrategy,
964949
legacyPeerDeps: this.legacyPeerDeps,
@@ -1099,13 +1084,13 @@ This is a one-time fix-up, please be patient...
10991084

11001085
// keep track of the thing that caused this node to be included.
11011086
const src = parent.sourceReference
1102-
this[_peerSetSource].set(node, src)
1087+
this.#peerSetSource.set(node, src)
11031088

11041089
// do not load the peers along with the set if this is a global top pkg
11051090
// otherwise we'll be tempted to put peers as other top-level installed
11061091
// things, potentially clobbering what's there already, which is not
11071092
// what we want. the missing edges will be picked up on the next pass.
1108-
if (this[_global] && edge.from.isProjectRoot) {
1093+
if (this.options.global && edge.from.isProjectRoot) {
11091094
return node
11101095
}
11111096

@@ -1328,7 +1313,7 @@ This is a one-time fix-up, please be patient...
13281313
const parentEdge = node.parent.edgesOut.get(edge.name)
13291314
const { isProjectRoot, isWorkspace } = node.parent.sourceReference
13301315
const isMine = isProjectRoot || isWorkspace
1331-
const conflictOK = this[_force] || !isMine && !this.#strictPeerDeps
1316+
const conflictOK = this.options.force || !isMine && !this.#strictPeerDeps
13321317

13331318
if (!edge.to) {
13341319
if (!parentEdge) {
@@ -1415,7 +1400,7 @@ This is a one-time fix-up, please be patient...
14151400
currentEdge: currentEdge ? currentEdge.explain() : null,
14161401
edge: edge.explain(),
14171402
strictPeerDeps: this.#strictPeerDeps,
1418-
force: this[_force],
1403+
force: this.options.force,
14191404
}
14201405
}
14211406

@@ -1503,7 +1488,7 @@ This is a one-time fix-up, please be patient...
15031488
// otherwise, don't bother.
15041489
const needPrune = metaFromDisk && (mutateTree || flagsSuspect)
15051490
if (this.#prune && needPrune) {
1506-
this[_idealTreePrune]()
1491+
this.#idealTreePrune()
15071492
for (const node of this.idealTree.inventory.values()) {
15081493
if (node.extraneous) {
15091494
node.parent = null
@@ -1514,7 +1499,7 @@ This is a one-time fix-up, please be patient...
15141499
process.emit('timeEnd', 'idealTree:fixDepFlags')
15151500
}
15161501

1517-
[_idealTreePrune] () {
1502+
#idealTreePrune () {
15181503
for (const node of this.idealTree.inventory.values()) {
15191504
if (node.extraneous) {
15201505
node.parent = null
@@ -1534,4 +1519,29 @@ This is a one-time fix-up, please be patient...
15341519
}
15351520
}
15361521
}
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+
}
15371547
}

‎workspaces/arborist/lib/arborist/deduper.js

-19
This file was deleted.

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

+102-14
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,25 @@
2929
const { resolve } = require('path')
3030
const { homedir } = require('os')
3131
const { depth } = require('treeverse')
32+
const mapWorkspaces = require('@npmcli/map-workspaces')
33+
const log = require('proc-log')
34+
3235
const { saveTypeMap } = require('../add-rm-pkg-deps.js')
36+
const AuditReport = require('../audit-report.js')
37+
const relpath = require('../relpath.js')
3338

3439
const mixins = [
3540
require('../tracker.js'),
36-
require('./pruner.js'),
37-
require('./deduper.js'),
38-
require('./audit.js'),
3941
require('./build-ideal-tree.js'),
40-
require('./set-workspaces.js'),
4142
require('./load-actual.js'),
4243
require('./load-virtual.js'),
4344
require('./rebuild.js'),
4445
require('./reify.js'),
4546
require('./isolated-reifier.js'),
4647
]
4748

48-
const _workspacesEnabled = Symbol.for('workspacesEnabled')
49+
const _setWorkspaces = Symbol.for('setWorkspaces')
4950
const Base = mixins.reduce((a, b) => b(a), require('events'))
50-
const getWorkspaceNodes = require('../get-workspace-nodes.js')
5151

5252
// if it's 1, 2, or 3, set it explicitly that.
5353
// if undefined or null, set it null
@@ -72,20 +72,26 @@ class Arborist extends Base {
7272
nodeVersion: process.version,
7373
...options,
7474
Arborist: this.constructor,
75-
path: options.path || '.',
75+
binLinks: 'binLinks' in options ? !!options.binLinks : true,
7676
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),
7782
packumentCache: options.packumentCache || new Map(),
78-
workspacesEnabled: options.workspacesEnabled !== false,
83+
path: options.path || '.',
84+
rebuildBundle: 'rebuildBundle' in options ? !!options.rebuildBundle : true,
7985
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,
8289
}
90+
// TODO is this even used? If not is that a bug?
8391
this.replaceRegistryHost = this.options.replaceRegistryHost =
8492
(!this.options.replaceRegistryHost || this.options.replaceRegistryHost === 'npmjs') ?
8593
'registry.npmjs.org' : this.options.replaceRegistryHost
8694

87-
this[_workspacesEnabled] = this.options.workspacesEnabled
88-
8995
if (options.saveType && !saveTypeMap.get(options.saveType)) {
9096
throw new Error(`Invalid saveType ${options.saveType}`)
9197
}
@@ -97,12 +103,40 @@ class Arborist extends Base {
97103
// TODO: We should change these to static functions instead
98104
// of methods for the next major version
99105

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.
101108
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
103135
}
104136

105137
// returns a set of workspace nodes and all their deps
138+
// TODO why is includeWorkspaceRoot a param?
139+
// TODO why is workspaces a param?
106140
workspaceDependencySet (tree, workspaces, includeWorkspaceRoot) {
107141
const wsNodes = this.workspaceNodes(tree, workspaces)
108142
if (includeWorkspaceRoot) {
@@ -162,6 +196,60 @@ class Arborist extends Base {
162196
})
163197
return rootDepSet
164198
}
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+
}
165253
}
166254

167255
module.exports = Arborist

‎workspaces/arborist/lib/arborist/load-actual.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ const realpath = require('../realpath.js')
1616

1717
// public symbols
1818
const _changePath = Symbol.for('_changePath')
19-
const _global = Symbol.for('global')
2019
const _setWorkspaces = Symbol.for('setWorkspaces')
2120
const _rpcache = Symbol.for('realpathCache')
2221
const _stcache = Symbol.for('statCache')
@@ -45,8 +44,6 @@ module.exports = cls => class ActualLoader extends cls {
4544
constructor (options) {
4645
super(options)
4746

48-
this[_global] = !!options.global
49-
5047
// the tree of nodes on disk
5148
this.actualTree = options.actualTree
5249

@@ -58,6 +55,7 @@ module.exports = cls => class ActualLoader extends cls {
5855
}
5956

6057
// public method
58+
// TODO remove options param in next semver major
6159
async loadActual (options = {}) {
6260
// In the past this.actualTree was set as a promise that eventually
6361
// resolved, and overwrite this.actualTree with the resolved value. This
@@ -100,7 +98,7 @@ module.exports = cls => class ActualLoader extends cls {
10098
async #loadActual (options) {
10199
// mostly realpath to throw if the root doesn't exist
102100
const {
103-
global = false,
101+
global,
104102
filter = () => true,
105103
root = null,
106104
transplantFilter = () => true,

‎workspaces/arborist/lib/arborist/pruner.js

-30
This file was deleted.

‎workspaces/arborist/lib/arborist/rebuild.js

+53-82
Original file line numberDiff line numberDiff line change
@@ -19,67 +19,37 @@ const boolEnv = b => b ? '1' : ''
1919
const sortNodes = (a, b) =>
2020
(a.depth - b.depth) || localeCompare(a.path, b.path)
2121

22-
const _workspaces = Symbol.for('workspaces')
23-
const _build = Symbol('build')
24-
const _loadDefaultNodes = Symbol('loadDefaultNodes')
25-
const _retrieveNodesByType = Symbol('retrieveNodesByType')
26-
const _resetQueues = Symbol('resetQueues')
27-
const _rebuildBundle = Symbol('rebuildBundle')
28-
const _ignoreScripts = Symbol('ignoreScripts')
29-
const _binLinks = Symbol('binLinks')
30-
const _oldMeta = Symbol('oldMeta')
31-
const _createBinLinks = Symbol('createBinLinks')
32-
const _doHandleOptionalFailure = Symbol('doHandleOptionalFailure')
33-
const _linkAllBins = Symbol('linkAllBins')
34-
const _runScripts = Symbol('runScripts')
35-
const _buildQueues = Symbol('buildQueues')
36-
const _addToBuildSet = Symbol('addToBuildSet')
3722
const _checkBins = Symbol.for('checkBins')
38-
const _queues = Symbol('queues')
39-
const _scriptShell = Symbol('scriptShell')
40-
const _includeWorkspaceRoot = Symbol.for('includeWorkspaceRoot')
41-
const _workspacesEnabled = Symbol.for('workspacesEnabled')
42-
43-
const _force = Symbol.for('force')
44-
const _global = Symbol.for('global')
4523

4624
// defined by reify mixin
4725
const _handleOptionalFailure = Symbol.for('handleOptionalFailure')
4826
const _trashList = Symbol.for('trashList')
4927

5028
module.exports = cls => class Builder extends cls {
29+
#doHandleOptionalFailure
30+
#oldMeta = null
31+
#queues
32+
5133
constructor (options) {
5234
super(options)
5335

54-
const {
55-
ignoreScripts = false,
56-
scriptShell,
57-
binLinks = true,
58-
rebuildBundle = true,
59-
} = options
60-
6136
this.scriptsRun = new Set()
62-
this[_binLinks] = binLinks
63-
this[_ignoreScripts] = !!ignoreScripts
64-
this[_scriptShell] = scriptShell
65-
this[_rebuildBundle] = !!rebuildBundle
66-
this[_resetQueues]()
67-
this[_oldMeta] = null
37+
this.#resetQueues()
6838
}
6939

7040
async rebuild ({ nodes, handleOptionalFailure = false } = {}) {
7141
// nothing to do if we're not building anything!
72-
if (this[_ignoreScripts] && !this[_binLinks]) {
42+
if (this.options.ignoreScripts && !this.options.binLinks) {
7343
return
7444
}
7545

7646
// when building for the first time, as part of reify, we ignore
7747
// failures in optional nodes, and just delete them. however, when
7848
// running JUST a rebuild, we treat optional failures as real fails
79-
this[_doHandleOptionalFailure] = handleOptionalFailure
49+
this.#doHandleOptionalFailure = handleOptionalFailure
8050

8151
if (!nodes) {
82-
nodes = await this[_loadDefaultNodes]()
52+
nodes = await this.#loadDefaultNodes()
8353
}
8454

8555
// separates links nodes so that it can run
@@ -89,36 +59,36 @@ module.exports = cls => class Builder extends cls {
8959
const {
9060
depNodes,
9161
linkNodes,
92-
} = this[_retrieveNodesByType](nodes)
62+
} = this.#retrieveNodesByType(nodes)
9363

9464
// build regular deps
95-
await this[_build](depNodes, {})
65+
await this.#build(depNodes, {})
9666

9767
// build link deps
9868
if (linkNodes.size) {
99-
this[_resetQueues]()
100-
await this[_build](linkNodes, { type: 'links' })
69+
this.#resetQueues()
70+
await this.#build(linkNodes, { type: 'links' })
10171
}
10272

10373
process.emit('timeEnd', 'build')
10474
}
10575

10676
// if we don't have a set of nodes, then just rebuild
10777
// the actual tree on disk.
108-
async [_loadDefaultNodes] () {
78+
async #loadDefaultNodes () {
10979
let nodes
11080
const tree = await this.loadActual()
11181
let filterSet
112-
if (!this[_workspacesEnabled]) {
82+
if (!this.options.workspacesEnabled) {
11383
filterSet = this.excludeWorkspacesDependencySet(tree)
11484
nodes = tree.inventory.filter(node =>
11585
filterSet.has(node) || node.isProjectRoot
11686
)
117-
} else if (this[_workspaces] && this[_workspaces].length) {
87+
} else if (this.options.workspaces.length) {
11888
filterSet = this.workspaceDependencySet(
11989
tree,
120-
this[_workspaces],
121-
this[_includeWorkspaceRoot]
90+
this.options.workspaces,
91+
this.options.includeWorkspaceRoot
12292
)
12393
nodes = tree.inventory.filter(node => filterSet.has(node))
12494
} else {
@@ -127,7 +97,7 @@ module.exports = cls => class Builder extends cls {
12797
return nodes
12898
}
12999

130-
[_retrieveNodesByType] (nodes) {
100+
#retrieveNodesByType (nodes) {
131101
const depNodes = new Set()
132102
const linkNodes = new Set()
133103
const storeNodes = new Set()
@@ -154,7 +124,7 @@ module.exports = cls => class Builder extends cls {
154124
//
155125
// we avoid doing so if global=true since `bin-links` relies
156126
// on having the target nodes available in global mode.
157-
if (!this[_global]) {
127+
if (!this.options.global) {
158128
for (const node of linkNodes) {
159129
depNodes.delete(node.target)
160130
}
@@ -166,8 +136,8 @@ module.exports = cls => class Builder extends cls {
166136
}
167137
}
168138

169-
[_resetQueues] () {
170-
this[_queues] = {
139+
#resetQueues () {
140+
this.#queues = {
171141
preinstall: [],
172142
install: [],
173143
postinstall: [],
@@ -176,46 +146,46 @@ module.exports = cls => class Builder extends cls {
176146
}
177147
}
178148

179-
async [_build] (nodes, { type = 'deps' }) {
149+
async #build (nodes, { type = 'deps' }) {
180150
process.emit('time', `build:${type}`)
181151

182-
await this[_buildQueues](nodes)
152+
await this.#buildQueues(nodes)
183153

184-
if (!this[_ignoreScripts]) {
185-
await this[_runScripts]('preinstall')
154+
if (!this.options.ignoreScripts) {
155+
await this.#runScripts('preinstall')
186156
}
187157

188158
// links should run prepare scripts and only link bins after that
189159
if (type === 'links') {
190-
await this[_runScripts]('prepare')
160+
await this.#runScripts('prepare')
191161
}
192-
if (this[_binLinks]) {
193-
await this[_linkAllBins]()
162+
if (this.options.binLinks) {
163+
await this.#linkAllBins()
194164
}
195165

196-
if (!this[_ignoreScripts]) {
197-
await this[_runScripts]('install')
198-
await this[_runScripts]('postinstall')
166+
if (!this.options.ignoreScripts) {
167+
await this.#runScripts('install')
168+
await this.#runScripts('postinstall')
199169
}
200170

201171
process.emit('timeEnd', `build:${type}`)
202172
}
203173

204-
async [_buildQueues] (nodes) {
174+
async #buildQueues (nodes) {
205175
process.emit('time', 'build:queue')
206176
const set = new Set()
207177

208178
const promises = []
209179
for (const node of nodes) {
210-
promises.push(this[_addToBuildSet](node, set))
180+
promises.push(this.#addToBuildSet(node, set))
211181

212182
// if it has bundle deps, add those too, if rebuildBundle
213-
if (this[_rebuildBundle] !== false) {
183+
if (this.options.rebuildBundle !== false) {
214184
const bd = node.package.bundleDependencies
215185
if (bd && bd.length) {
216186
dfwalk({
217187
tree: node,
218-
leave: node => promises.push(this[_addToBuildSet](node, set)),
188+
leave: node => promises.push(this.#addToBuildSet(node, set)),
219189
getChildren: node => [...node.children.values()],
220190
filter: node => node.inBundle,
221191
})
@@ -236,7 +206,7 @@ module.exports = cls => class Builder extends cls {
236206
const tests = { bin, preinstall, install, postinstall, prepare }
237207
for (const [key, has] of Object.entries(tests)) {
238208
if (has) {
239-
this[_queues][key].push(node)
209+
this.#queues[key].push(node)
240210
}
241211
}
242212
}
@@ -249,21 +219,21 @@ module.exports = cls => class Builder extends cls {
249219
// the node path. Otherwise a package can have a preinstall script
250220
// that unlinks something, to allow them to silently overwrite system
251221
// binaries, which is unsafe and insecure.
252-
if (!node.globalTop || this[_force]) {
222+
if (!node.globalTop || this.options.force) {
253223
return
254224
}
255225
const { path, package: pkg } = node
256226
await binLinks.checkBins({ pkg, path, top: true, global: true })
257227
}
258228

259-
async [_addToBuildSet] (node, set, refreshed = false) {
229+
async #addToBuildSet (node, set, refreshed = false) {
260230
if (set.has(node)) {
261231
return
262232
}
263233

264-
if (this[_oldMeta] === null) {
234+
if (this.#oldMeta === null) {
265235
const { root: { meta } } = node
266-
this[_oldMeta] = meta && meta.loadedFromDisk &&
236+
this.#oldMeta = meta && meta.loadedFromDisk &&
267237
!(meta.originalLockfileVersion >= 2)
268238
}
269239

@@ -272,7 +242,7 @@ module.exports = cls => class Builder extends cls {
272242

273243
const { preinstall, install, postinstall, prepare } = scripts
274244
const anyScript = preinstall || install || postinstall || prepare
275-
if (!refreshed && !anyScript && (hasInstallScript || this[_oldMeta])) {
245+
if (!refreshed && !anyScript && (hasInstallScript || this.#oldMeta)) {
276246
// we either have an old metadata (and thus might have scripts)
277247
// or we have an indication that there's install scripts (but
278248
// don't yet know what they are) so we have to load the package.json
@@ -286,7 +256,7 @@ module.exports = cls => class Builder extends cls {
286256

287257
const { scripts = {} } = pkg
288258
node.package.scripts = scripts
289-
return this[_addToBuildSet](node, set, true)
259+
return this.#addToBuildSet(node, set, true)
290260
}
291261

292262
// Rebuild node-gyp dependencies lacking an install or preinstall script
@@ -309,8 +279,8 @@ module.exports = cls => class Builder extends cls {
309279
}
310280
}
311281

312-
async [_runScripts] (event) {
313-
const queue = this[_queues][event]
282+
async #runScripts (event) {
283+
const queue = this.#queues[event]
314284

315285
if (!queue.length) {
316286
return
@@ -358,7 +328,7 @@ module.exports = cls => class Builder extends cls {
358328
pkg,
359329
stdio,
360330
env,
361-
scriptShell: this[_scriptShell],
331+
scriptShell: this.options.scriptShell,
362332
}
363333
const p = runScript(runOpts).catch(er => {
364334
const { code, signal } = er
@@ -382,7 +352,7 @@ module.exports = cls => class Builder extends cls {
382352
log.info('run', pkg._id, event, { code, signal })
383353
})
384354

385-
await (this[_doHandleOptionalFailure]
355+
await (this.#doHandleOptionalFailure
386356
? this[_handleOptionalFailure](node, p)
387357
: p)
388358

@@ -391,8 +361,8 @@ module.exports = cls => class Builder extends cls {
391361
process.emit('timeEnd', `build:run:${event}`)
392362
}
393363

394-
async [_linkAllBins] () {
395-
const queue = this[_queues].bin
364+
async #linkAllBins () {
365+
const queue = this.#queues.bin
396366
if (!queue.length) {
397367
return
398368
}
@@ -402,14 +372,15 @@ module.exports = cls => class Builder extends cls {
402372
// sort the queue by node path, so that the module-local collision
403373
// detector in bin-links will always resolve the same way.
404374
for (const node of queue.sort(sortNodes)) {
405-
promises.push(this[_createBinLinks](node))
375+
// TODO these run before they're awaited
376+
promises.push(this.#createBinLinks(node))
406377
}
407378

408379
await promiseAllRejectLate(promises)
409380
process.emit('timeEnd', 'build:link')
410381
}
411382

412-
async [_createBinLinks] (node) {
383+
async #createBinLinks (node) {
413384
if (this[_trashList].has(node.path)) {
414385
return
415386
}
@@ -420,11 +391,11 @@ module.exports = cls => class Builder extends cls {
420391
pkg: node.package,
421392
path: node.path,
422393
top: !!(node.isTop || node.globalTop),
423-
force: this[_force],
394+
force: this.options.force,
424395
global: !!node.globalTop,
425396
})
426397

427-
await (this[_doHandleOptionalFailure]
398+
await (this.#doHandleOptionalFailure
428399
? this[_handleOptionalFailure](node, p)
429400
: p)
430401

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

+32-20
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ const PackageJson = require('@npmcli/package-json')
2424
const packageContents = require('@npmcli/installed-package-contents')
2525
const runScript = require('@npmcli/run-script')
2626
const { checkEngine, checkPlatform } = require('npm-install-checks')
27-
const _force = Symbol.for('force')
2827

2928
const treeCheck = require('../tree-check.js')
3029
const relpath = require('../relpath.js')
@@ -48,8 +47,6 @@ const _retireShallowNodes = Symbol.for('retireShallowNodes')
4847
const _getBundlesByDepth = Symbol('getBundlesByDepth')
4948
const _registryResolved = Symbol('registryResolved')
5049
const _addNodeToTrashList = Symbol.for('addNodeToTrashList')
51-
const _workspaces = Symbol.for('workspaces')
52-
const _workspacesEnabled = Symbol.for('workspacesEnabled')
5350

5451
// shared by rebuild mixin
5552
const _trashList = Symbol.for('trashList')
@@ -91,14 +88,11 @@ const _validateNodeModules = Symbol('validateNodeModules')
9188
const _nmValidated = Symbol('nmValidated')
9289
const _validatePath = Symbol('validatePath')
9390
const _reifyPackages = Symbol.for('reifyPackages')
94-
const _includeWorkspaceRoot = Symbol.for('includeWorkspaceRoot')
9591

9692
const _omitDev = Symbol('omitDev')
9793
const _omitOptional = Symbol('omitOptional')
9894
const _omitPeer = Symbol('omitPeer')
9995

100-
const _global = Symbol.for('global')
101-
10296
const _pruneBundledMetadeps = Symbol('pruneBundledMetadeps')
10397

10498
// defined by Ideal mixin
@@ -142,7 +136,7 @@ module.exports = cls => class Reifier extends cls {
142136
async reify (options = {}) {
143137
const linked = (options.installStrategy || this.options.installStrategy) === 'linked'
144138

145-
if (this[_packageLockOnly] && this[_global]) {
139+
if (this[_packageLockOnly] && this.options.global) {
146140
const er = new Error('cannot generate lockfile for global packages')
147141
er.code = 'ESHRINKWRAPGLOBAL'
148142
throw er
@@ -287,7 +281,7 @@ module.exports = cls => class Reifier extends cls {
287281
.then(() => process.emit('timeEnd', 'reify:loadTrees'))
288282
}
289283

290-
const actualOpt = this[_global] ? {
284+
const actualOpt = this.options.global ? {
291285
ignoreMissing: true,
292286
global: true,
293287
filter: (node, kid) => {
@@ -314,7 +308,7 @@ module.exports = cls => class Reifier extends cls {
314308
},
315309
} : { ignoreMissing: true }
316310

317-
if (!this[_global]) {
311+
if (!this.options.global) {
318312
return Promise.all([
319313
this.loadActual(actualOpt),
320314
this.buildIdealTree(bitOpt),
@@ -341,12 +335,12 @@ module.exports = cls => class Reifier extends cls {
341335
// to just invalidate the parts that changed, but avoid walking the
342336
// whole tree again.
343337

344-
const includeWorkspaces = this[_workspacesEnabled]
345-
const includeRootDeps = !this[_workspacesEnabled]
346-
|| this[_includeWorkspaceRoot] && this[_workspaces].length > 0
338+
const includeWorkspaces = this.options.workspacesEnabled
339+
const includeRootDeps = !includeWorkspaces
340+
|| this.options.includeWorkspaceRoot && this.options.workspaces.length > 0
347341

348342
const filterNodes = []
349-
if (this[_global] && this.explicitRequests.size) {
343+
if (this.options.global && this.explicitRequests.size) {
350344
const idealTree = this.idealTree.target
351345
const actualTree = this.actualTree.target
352346
// we ONLY are allowed to make changes in the global top-level
@@ -364,7 +358,7 @@ module.exports = cls => class Reifier extends cls {
364358
} else {
365359
if (includeWorkspaces) {
366360
// add all ws nodes to filterNodes
367-
for (const ws of this[_workspaces]) {
361+
for (const ws of this.options.workspaces) {
368362
const ideal = this.idealTree.children.get(ws)
369363
if (ideal) {
370364
filterNodes.push(ideal)
@@ -656,7 +650,7 @@ module.exports = cls => class Reifier extends cls {
656650

657651
// do not allow node_modules to be a symlink
658652
async [_validateNodeModules] (nm) {
659-
if (this[_force] || this[_nmValidated].has(nm)) {
653+
if (this.options.force || this[_nmValidated].has(nm)) {
660654
return
661655
}
662656
const st = await lstat(nm).catch(() => null)
@@ -992,11 +986,11 @@ module.exports = cls => class Reifier extends cls {
992986
const tree = this.idealTree
993987

994988
// if we're operating on a workspace, only audit the workspace deps
995-
if (this[_workspaces] && this[_workspaces].length) {
989+
if (this.options.workspaces.length) {
996990
options.filterSet = this.workspaceDependencySet(
997991
tree,
998-
this[_workspaces],
999-
this[_includeWorkspaceRoot]
992+
this.options.workspaces,
993+
this.options.includeWorkspaceRoot
1000994
)
1001995
}
1002996

@@ -1220,7 +1214,7 @@ module.exports = cls => class Reifier extends cls {
12201214
// saveIdealTree to be able to write the lockfile by default.
12211215
const saveIdealTree = !(
12221216
(!save && !hasUpdates)
1223-
|| this[_global]
1217+
|| this.options.global
12241218
|| this[_dryRun]
12251219
)
12261220

@@ -1566,7 +1560,7 @@ module.exports = cls => class Reifier extends cls {
15661560
this.actualTree = this.idealTree
15671561
this.idealTree = null
15681562

1569-
if (!this[_global]) {
1563+
if (!this.options.global) {
15701564
await this.actualTree.meta.save()
15711565
const ignoreScripts = !!this.options.ignoreScripts
15721566
// if we aren't doing a dry run or ignoring scripts and we actually made changes to the dep
@@ -1593,4 +1587,22 @@ module.exports = cls => class Reifier extends cls {
15931587
}
15941588
}
15951589
}
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+
}
15961608
}

‎workspaces/arborist/lib/arborist/set-workspaces.js

-19
This file was deleted.

‎workspaces/arborist/lib/case-insensitive-map.js

+20-20
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,49 @@
22
// are case-insensitive and unicode-normalizing, so we need to treat
33
// node.children.get('FOO') and node.children.get('foo') as the same thing.
44

5-
const _keys = Symbol('keys')
6-
const _normKey = Symbol('normKey')
7-
const normalize = s => s.normalize('NFKD').toLowerCase()
8-
const OGMap = Map
9-
module.exports = class Map extends OGMap {
5+
module.exports = class CIMap extends Map {
6+
#keys = new Map()
7+
108
constructor (items = []) {
119
super()
12-
this[_keys] = new OGMap()
1310
for (const [key, val] of items) {
1411
this.set(key, val)
1512
}
1613
}
1714

18-
[_normKey] (key) {
19-
return typeof key === 'string' ? normalize(key) : key
15+
#normKey (key) {
16+
if (typeof key !== 'string') {
17+
return key
18+
}
19+
return key.normalize('NFKD').toLowerCase()
2020
}
2121

2222
get (key) {
23-
const normKey = this[_normKey](key)
24-
return this[_keys].has(normKey) ? super.get(this[_keys].get(normKey))
23+
const normKey = this.#normKey(key)
24+
return this.#keys.has(normKey) ? super.get(this.#keys.get(normKey))
2525
: undefined
2626
}
2727

2828
set (key, val) {
29-
const normKey = this[_normKey](key)
30-
if (this[_keys].has(normKey)) {
31-
super.delete(this[_keys].get(normKey))
29+
const normKey = this.#normKey(key)
30+
if (this.#keys.has(normKey)) {
31+
super.delete(this.#keys.get(normKey))
3232
}
33-
this[_keys].set(normKey, key)
33+
this.#keys.set(normKey, key)
3434
return super.set(key, val)
3535
}
3636

3737
delete (key) {
38-
const normKey = this[_normKey](key)
39-
if (this[_keys].has(normKey)) {
40-
const prevKey = this[_keys].get(normKey)
41-
this[_keys].delete(normKey)
38+
const normKey = this.#normKey(key)
39+
if (this.#keys.has(normKey)) {
40+
const prevKey = this.#keys.get(normKey)
41+
this.#keys.delete(normKey)
4242
return super.delete(prevKey)
4343
}
4444
}
4545

4646
has (key) {
47-
const normKey = this[_normKey](key)
48-
return this[_keys].has(normKey) && super.has(this[_keys].get(normKey))
47+
const normKey = this.#normKey(key)
48+
return this.#keys.has(normKey) && super.has(this.#keys.get(normKey))
4949
}
5050
}

‎workspaces/arborist/lib/get-workspace-nodes.js

-36
This file was deleted.

‎workspaces/arborist/lib/index.js

-2
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,3 @@ module.exports.Node = require('./node.js')
44
module.exports.Link = require('./link.js')
55
module.exports.Edge = require('./edge.js')
66
module.exports.Shrinkwrap = require('./shrinkwrap.js')
7-
// XXX export the other classes, too. shrinkwrap, diff, etc.
8-
// they're handy!

‎workspaces/arborist/lib/query-selector-all.js

-2
Original file line numberDiff line numberDiff line change
@@ -919,8 +919,6 @@ const retrieveNodesFromParsedAst = async (opts) => {
919919
return results.collect(rootAstNode)
920920
}
921921

922-
// We are keeping this async in the event that we do add async operators, we
923-
// won't have to have a breaking change on this function signature.
924922
const querySelectorAll = async (targetNode, query, flatOptions) => {
925923
// This never changes ever we just pass it around. But we can't scope it to
926924
// this whole file if we ever want to support concurrent calls to this

‎workspaces/arborist/lib/tracker.js

+28-29
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,46 @@
1-
const _progress = Symbol('_progress')
2-
const _onError = Symbol('_onError')
3-
const _setProgress = Symbol('_setProgess')
41
const npmlog = require('npmlog')
52

63
module.exports = cls => class Tracker extends cls {
4+
#progress = new Map()
5+
#setProgress
6+
77
constructor (options = {}) {
88
super(options)
9-
this[_setProgress] = !!options.progress
10-
this[_progress] = new Map()
9+
this.#setProgress = !!options.progress
1110
}
1211

1312
addTracker (section, subsection = null, key = null) {
1413
if (section === null || section === undefined) {
15-
this[_onError](`Tracker can't be null or undefined`)
14+
this.#onError(`Tracker can't be null or undefined`)
1615
}
1716

1817
if (key === null) {
1918
key = subsection
2019
}
2120

22-
const hasTracker = this[_progress].has(section)
23-
const hasSubtracker = this[_progress].has(`${section}:${key}`)
21+
const hasTracker = this.#progress.has(section)
22+
const hasSubtracker = this.#progress.has(`${section}:${key}`)
2423

2524
if (hasTracker && subsection === null) {
2625
// 0. existing tracker, no subsection
27-
this[_onError](`Tracker "${section}" already exists`)
26+
this.#onError(`Tracker "${section}" already exists`)
2827
} else if (!hasTracker && subsection === null) {
2928
// 1. no existing tracker, no subsection
3029
// Create a new tracker from npmlog
3130
// starts progress bar
32-
if (this[_setProgress] && this[_progress].size === 0) {
31+
if (this.#setProgress && this.#progress.size === 0) {
3332
npmlog.enableProgress()
3433
}
3534

36-
this[_progress].set(section, npmlog.newGroup(section))
35+
this.#progress.set(section, npmlog.newGroup(section))
3736
} else if (!hasTracker && subsection !== null) {
3837
// 2. no parent tracker and subsection
39-
this[_onError](`Parent tracker "${section}" does not exist`)
38+
this.#onError(`Parent tracker "${section}" does not exist`)
4039
} else if (!hasTracker || !hasSubtracker) {
4140
// 3. existing parent tracker, no subsection tracker
42-
// Create a new subtracker in this[_progress] from parent tracker
43-
this[_progress].set(`${section}:${key}`,
44-
this[_progress].get(section).newGroup(`${section}:${subsection}`)
41+
// Create a new subtracker in this.#progress from parent tracker
42+
this.#progress.set(`${section}:${key}`,
43+
this.#progress.get(section).newGroup(`${section}:${subsection}`)
4544
)
4645
}
4746
// 4. existing parent tracker, existing subsection tracker
@@ -50,51 +49,51 @@ module.exports = cls => class Tracker extends cls {
5049

5150
finishTracker (section, subsection = null, key = null) {
5251
if (section === null || section === undefined) {
53-
this[_onError](`Tracker can't be null or undefined`)
52+
this.#onError(`Tracker can't be null or undefined`)
5453
}
5554

5655
if (key === null) {
5756
key = subsection
5857
}
5958

60-
const hasTracker = this[_progress].has(section)
61-
const hasSubtracker = this[_progress].has(`${section}:${key}`)
59+
const hasTracker = this.#progress.has(section)
60+
const hasSubtracker = this.#progress.has(`${section}:${key}`)
6261

6362
// 0. parent tracker exists, no subsection
64-
// Finish parent tracker and remove from this[_progress]
63+
// Finish parent tracker and remove from this.#progress
6564
if (hasTracker && subsection === null) {
6665
// check if parent tracker does
6766
// not have any remaining children
68-
const keys = this[_progress].keys()
67+
const keys = this.#progress.keys()
6968
for (const key of keys) {
7069
if (key.match(new RegExp(section + ':'))) {
7170
this.finishTracker(section, key)
7271
}
7372
}
7473

7574
// remove parent tracker
76-
this[_progress].get(section).finish()
77-
this[_progress].delete(section)
75+
this.#progress.get(section).finish()
76+
this.#progress.delete(section)
7877

7978
// remove progress bar if all
8079
// trackers are finished
81-
if (this[_setProgress] && this[_progress].size === 0) {
80+
if (this.#setProgress && this.#progress.size === 0) {
8281
npmlog.disableProgress()
8382
}
8483
} else if (!hasTracker && subsection === null) {
8584
// 1. no existing parent tracker, no subsection
86-
this[_onError](`Tracker "${section}" does not exist`)
85+
this.#onError(`Tracker "${section}" does not exist`)
8786
} else if (!hasTracker || hasSubtracker) {
8887
// 2. subtracker exists
89-
// Finish subtracker and remove from this[_progress]
90-
this[_progress].get(`${section}:${key}`).finish()
91-
this[_progress].delete(`${section}:${key}`)
88+
// Finish subtracker and remove from this.#progress
89+
this.#progress.get(`${section}:${key}`).finish()
90+
this.#progress.delete(`${section}:${key}`)
9291
}
9392
// 3. existing parent tracker, no subsection
9493
}
9594

96-
[_onError] (msg) {
97-
if (this[_setProgress]) {
95+
#onError (msg) {
96+
if (this.#setProgress) {
9897
npmlog.disableProgress()
9998
}
10099
throw new Error(msg)

‎workspaces/arborist/lib/version-from-tgz.js

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
1-
/* eslint node/no-deprecated-api: "off" */
21
const semver = require('semver')
32
const { basename } = require('path')
4-
const { parse } = require('url')
3+
const { URL } = require('url')
54
module.exports = (name, tgz) => {
65
const base = basename(tgz)
76
if (!base.endsWith('.tgz')) {
87
return null
98
}
109

11-
const u = parse(tgz)
12-
if (/^https?:/.test(u.protocol)) {
10+
if (tgz.startsWith('http:/') || tgz.startsWith('https:/')) {
11+
const u = new URL(tgz)
1312
// registry url? check for most likely pattern.
1413
// either /@foo/bar/-/bar-1.2.3.tgz or
1514
// /foo/-/foo-1.2.3.tgz, and fall through to
1615
// basename checking. Note that registries can
1716
// be mounted below the root url, so /a/b/-/x/y/foo/-/foo-1.2.3.tgz
1817
// is a potential option.
19-
const tfsplit = u.path.slice(1).split('/-/')
18+
const tfsplit = u.pathname.slice(1).split('/-/')
2019
if (tfsplit.length > 1) {
2120
const afterTF = tfsplit.pop()
2221
if (afterTF === base) {

‎workspaces/arborist/lib/vuln.js

+28-31
Original file line numberDiff line numberDiff line change
@@ -16,48 +16,42 @@ const semverOpt = { loose: true, includePrerelease: true }
1616

1717
const localeCompare = require('@isaacs/string-locale-compare')('en')
1818
const npa = require('npm-package-arg')
19-
const _range = Symbol('_range')
20-
const _simpleRange = Symbol('_simpleRange')
21-
const _fixAvailable = Symbol('_fixAvailable')
2219

2320
const severities = new Map([
24-
['info', 0],
25-
['low', 1],
26-
['moderate', 2],
27-
['high', 3],
28-
['critical', 4],
29-
[null, -1],
21+
['info', 0], [0, 'info'],
22+
['low', 1], [1, 'low'],
23+
['moderate', 2], [2, 'moderate'],
24+
['high', 3], [3, 'high'],
25+
['critical', 4], [4, 'critical'],
26+
[null, -1], [-1, null],
3027
])
3128

32-
for (const [name, val] of severities.entries()) {
33-
severities.set(val, name)
34-
}
35-
3629
class Vuln {
30+
#range = null
31+
#simpleRange = null
32+
// assume a fix is available unless it hits a top node
33+
// that locks it in place, setting this false or {isSemVerMajor, version}.
34+
#fixAvailable = true
35+
3736
constructor ({ name, advisory }) {
3837
this.name = name
3938
this.via = new Set()
4039
this.advisories = new Set()
4140
this.severity = null
4241
this.effects = new Set()
4342
this.topNodes = new Set()
44-
this[_range] = null
45-
this[_simpleRange] = null
4643
this.nodes = new Set()
47-
// assume a fix is available unless it hits a top node
48-
// that locks it in place, setting this false or {isSemVerMajor, version}.
49-
this[_fixAvailable] = true
5044
this.addAdvisory(advisory)
5145
this.packument = advisory.packument
5246
this.versions = advisory.versions
5347
}
5448

5549
get fixAvailable () {
56-
return this[_fixAvailable]
50+
return this.#fixAvailable
5751
}
5852

5953
set fixAvailable (f) {
60-
this[_fixAvailable] = f
54+
this.#fixAvailable = f
6155
// if there's a fix available for this at the top level, it means that
6256
// it will also fix the vulns that led to it being there. to get there,
6357
// we set the vias to the most "strict" of fix availables.
@@ -131,7 +125,7 @@ class Vuln {
131125
effects: [...this.effects].map(v => v.name).sort(localeCompare),
132126
range: this.simpleRange,
133127
nodes: [...this.nodes].map(n => n.location).sort(localeCompare),
134-
fixAvailable: this[_fixAvailable],
128+
fixAvailable: this.#fixAvailable,
135129
}
136130
}
137131

@@ -151,8 +145,8 @@ class Vuln {
151145
this.advisories.delete(advisory)
152146
// make sure we have the max severity of all the vulns causing this one
153147
this.severity = null
154-
this[_range] = null
155-
this[_simpleRange] = null
148+
this.#range = null
149+
this.#simpleRange = null
156150
// refresh severity
157151
for (const advisory of this.advisories) {
158152
this.addAdvisory(advisory)
@@ -170,27 +164,30 @@ class Vuln {
170164
addAdvisory (advisory) {
171165
this.advisories.add(advisory)
172166
const sev = severities.get(advisory.severity)
173-
this[_range] = null
174-
this[_simpleRange] = null
167+
this.#range = null
168+
this.#simpleRange = null
175169
if (sev > severities.get(this.severity)) {
176170
this.severity = advisory.severity
177171
}
178172
}
179173

180174
get range () {
181-
return this[_range] ||
182-
(this[_range] = [...this.advisories].map(v => v.range).join(' || '))
175+
if (!this.#range) {
176+
this.#range = [...this.advisories].map(v => v.range).join(' || ')
177+
}
178+
return this.#range
183179
}
184180

185181
get simpleRange () {
186-
if (this[_simpleRange] && this[_simpleRange] === this[_range]) {
187-
return this[_simpleRange]
182+
if (this.#simpleRange && this.#simpleRange === this.#range) {
183+
return this.#simpleRange
188184
}
189185

190186
const versions = [...this.advisories][0].versions
191187
const range = this.range
192-
const simple = simplifyRange(versions, range, semverOpt)
193-
return this[_simpleRange] = this[_range] = simple
188+
this.#simpleRange = simplifyRange(versions, range, semverOpt)
189+
this.#range = this.#simpleRange
190+
return this.#simpleRange
194191
}
195192

196193
isVulnerable (node) {

‎workspaces/arborist/tap-snapshots/test/link.js.test.cjs

+6-6
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Link {
1111
"devOptional": true,
1212
"dummy": false,
1313
"edgesIn": Set {},
14-
"edgesOut": Map {},
14+
"edgesOut": CIMap {},
1515
"errors": Array [],
1616
"extraneous": true,
1717
"fsChildren": Set {},
@@ -41,7 +41,7 @@ exports[`test/link.js TAP > instantiate without providing target 1`] = `
4141
"devOptional": true,
4242
"dummy": false,
4343
"edgesIn": Set {},
44-
"edgesOut": Map {},
44+
"edgesOut": CIMap {},
4545
"errors": Array [],
4646
"extraneous": true,
4747
"fsChildren": Set {},
@@ -51,12 +51,12 @@ exports[`test/link.js TAP > instantiate without providing target 1`] = `
5151
"inventory": Inventory {
5252
"../../../../../some/other/path" => <*ref_1>,
5353
"" => Node {
54-
"children": Map {},
54+
"children": CIMap {},
5555
"dev": true,
5656
"devOptional": true,
5757
"dummy": false,
5858
"edgesIn": Set {},
59-
"edgesOut": Map {},
59+
"edgesOut": CIMap {},
6060
"errors": Array [],
6161
"extraneous": true,
6262
"fsChildren": Set {},
@@ -95,12 +95,12 @@ exports[`test/link.js TAP > instantiate without providing target 1`] = `
9595
"sourceReference": null,
9696
"tops": Set {
9797
Node {
98-
"children": Map {},
98+
"children": CIMap {},
9999
"dev": true,
100100
"devOptional": true,
101101
"dummy": false,
102102
"edgesIn": Set {},
103-
"edgesOut": Map {},
103+
"edgesOut": CIMap {},
104104
"errors": Array [],
105105
"extraneous": true,
106106
"fsChildren": Set {},

‎workspaces/arborist/tap-snapshots/test/node.js.test.cjs

+454-454
Large diffs are not rendered by default.

‎workspaces/arborist/test/get-workspace-nodes.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
const t = require('tap')
2-
const getWorkspaceNodes = require('../lib/get-workspace-nodes.js')
32
const Arborist = require('../lib/arborist/index.js')
43
const { resolve } = require('path')
54
const path = resolve(__dirname, './fixtures/workspaces-shared-deps-virtual')
5+
const arb = new Arborist({ path })
66

77
const warningTracker = () => {
88
const list = []
@@ -16,12 +16,12 @@ const warningTracker = () => {
1616

1717
let tree
1818
t.before(async () => {
19-
tree = await new Arborist({ path }).loadVirtual()
19+
tree = await arb.loadVirtual()
2020
})
2121

2222
t.test('basic behavior', t => {
2323
const getLogs = warningTracker()
24-
const wsNodes = getWorkspaceNodes(tree, ['a'])
24+
const wsNodes = arb.workspaceNodes(tree, ['a'])
2525
t.equal(wsNodes.length, 1)
2626
t.equal(wsNodes[0], tree.children.get('a').target)
2727
t.same(getLogs(), [])
@@ -30,7 +30,7 @@ t.test('basic behavior', t => {
3030

3131
t.test('filter set, but no workspaces present', t => {
3232
const getLogs = warningTracker()
33-
const wsNodes = getWorkspaceNodes(tree.children.get('b').target, ['xyz'])
33+
const wsNodes = arb.workspaceNodes(tree.children.get('b').target, ['xyz'])
3434
t.same(wsNodes, [])
3535
t.same(getLogs(), [
3636
['warn', 'workspaces', 'filter set, but no workspaces present'],
@@ -40,7 +40,7 @@ t.test('filter set, but no workspaces present', t => {
4040

4141
t.test('name in filter set, but not in workspaces', t => {
4242
const getLogs = warningTracker()
43-
const wsNodes = getWorkspaceNodes(tree, ['xyz'])
43+
const wsNodes = arb.workspaceNodes(tree, ['xyz'])
4444
t.same(wsNodes, [])
4545
t.same(getLogs(), [
4646
['warn', 'workspaces', 'xyz in filter set, but not in workspaces'],
@@ -55,7 +55,7 @@ t.test('name in filter set, but no workspace folder present', t => {
5555
// but if we start moving things around and make a mistake, it's
5656
// possible to get there.
5757
tree.children.get('c').target.root = null
58-
const wsNodes = getWorkspaceNodes(tree, ['c'])
58+
const wsNodes = arb.workspaceNodes(tree, ['c'])
5959
t.same(wsNodes, [])
6060
t.same(getLogs(), [
6161
['warn', 'workspaces', 'c in filter set, but no workspace folder present'],

0 commit comments

Comments
 (0)
Please sign in to comment.