@npmcli/arborist 6.2.8 → 6.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37,7 +37,7 @@ const mixins = [
37
37
  require('./deduper.js'),
38
38
  require('./audit.js'),
39
39
  require('./build-ideal-tree.js'),
40
- require('./load-workspaces.js'),
40
+ require('./set-workspaces.js'),
41
41
  require('./load-actual.js'),
42
42
  require('./load-virtual.js'),
43
43
  require('./rebuild.js'),
@@ -17,28 +17,31 @@ const realpath = require('../realpath.js')
17
17
  // public symbols
18
18
  const _changePath = Symbol.for('_changePath')
19
19
  const _global = Symbol.for('global')
20
- const _loadWorkspaces = Symbol.for('loadWorkspaces')
20
+ const _setWorkspaces = Symbol.for('setWorkspaces')
21
21
  const _rpcache = Symbol.for('realpathCache')
22
22
  const _stcache = Symbol.for('statCache')
23
23
 
24
- // private symbols
25
- const _actualTree = Symbol('actualTree')
26
- const _actualTreeLoaded = Symbol('actualTreeLoaded')
27
- const _actualTreePromise = Symbol('actualTreePromise')
28
- const _cache = Symbol('nodeLoadingCache')
29
- const _filter = Symbol('filter')
30
- const _findMissingEdges = Symbol('findMissingEdges')
31
- const _loadActual = Symbol('loadActual')
32
- const _loadFSChildren = Symbol('loadFSChildren')
33
- const _loadFSNode = Symbol('loadFSNode')
34
- const _loadFSTree = Symbol('loadFSTree')
35
- const _newLink = Symbol('newLink')
36
- const _newNode = Symbol('newNode')
37
- const _topNodes = Symbol('linkTargets')
38
- const _transplant = Symbol('transplant')
39
- const _transplantFilter = Symbol('transplantFilter')
40
-
41
24
  module.exports = cls => class ActualLoader extends cls {
25
+ #actualTree
26
+ // ensure when walking the tree that we don't call loadTree on the same
27
+ // actual node more than one time.
28
+ #actualTreeLoaded = new Set()
29
+ #actualTreePromise
30
+
31
+ // cache of nodes when loading the actualTree, so that we avoid loaded the
32
+ // same node multiple times when symlinks attack.
33
+ #cache = new Map()
34
+ #filter
35
+
36
+ // cache of link targets for setting fsParent links
37
+ // We don't do fsParent as a magic getter/setter, because it'd be too costly
38
+ // to keep up to date along the walk.
39
+ // And, we know that it can ONLY be relevant when the node is a target of a
40
+ // link, otherwise it'd be in a node_modules folder, so take advantage of
41
+ // that to limit the scans later.
42
+ #topNodes = new Set()
43
+ #transplantFilter
44
+
42
45
  constructor (options) {
43
46
  super(options)
44
47
 
@@ -47,27 +50,11 @@ module.exports = cls => class ActualLoader extends cls {
47
50
  // the tree of nodes on disk
48
51
  this.actualTree = options.actualTree
49
52
 
50
- // ensure when walking the tree that we don't call loadTree on the
51
- // same actual node more than one time.
52
- this[_actualTreeLoaded] = new Set()
53
-
54
53
  // caches for cached realpath calls
55
54
  const cwd = process.cwd()
56
55
  // assume that the cwd is real enough for our purposes
57
56
  this[_rpcache] = new Map([[cwd, cwd]])
58
57
  this[_stcache] = new Map()
59
-
60
- // cache of nodes when loading the actualTree, so that we avoid
61
- // loaded the same node multiple times when symlinks attack.
62
- this[_cache] = new Map()
63
-
64
- // cache of link targets for setting fsParent links
65
- // We don't do fsParent as a magic getter/setter, because
66
- // it'd be too costly to keep up to date along the walk.
67
- // And, we know that it can ONLY be relevant when the node
68
- // is a target of a link, otherwise it'd be in a node_modules
69
- // folder, so take advantage of that to limit the scans later.
70
- this[_topNodes] = new Set()
71
58
  }
72
59
 
73
60
  // public method
@@ -81,12 +68,12 @@ module.exports = cls => class ActualLoader extends cls {
81
68
  if (this.actualTree) {
82
69
  return this.actualTree
83
70
  }
84
- if (!this[_actualTreePromise]) {
71
+ if (!this.#actualTreePromise) {
85
72
  // allow the user to set options on the ctor as well.
86
73
  // XXX: deprecate separate method options objects.
87
74
  options = { ...this.options, ...options }
88
75
 
89
- this[_actualTreePromise] = this[_loadActual](options)
76
+ this.#actualTreePromise = this.#loadActual(options)
90
77
  .then(tree => {
91
78
  // reset all deps to extraneous prior to recalc
92
79
  if (!options.root) {
@@ -102,14 +89,15 @@ module.exports = cls => class ActualLoader extends cls {
102
89
  return this.actualTree
103
90
  })
104
91
  }
105
- return this[_actualTreePromise]
92
+ return this.#actualTreePromise
106
93
  }
94
+
107
95
  // return the promise so that we don't ever have more than one going at the
108
96
  // same time. This is so that buildIdealTree can default to the actualTree
109
97
  // if no shrinkwrap present, but reify() can still call buildIdealTree and
110
98
  // loadActual in parallel safely.
111
99
 
112
- async [_loadActual] (options) {
100
+ async #loadActual (options) {
113
101
  // mostly realpath to throw if the root doesn't exist
114
102
  const {
115
103
  global = false,
@@ -119,8 +107,8 @@ module.exports = cls => class ActualLoader extends cls {
119
107
  ignoreMissing = false,
120
108
  forceActual = false,
121
109
  } = options
122
- this[_filter] = filter
123
- this[_transplantFilter] = transplantFilter
110
+ this.#filter = filter
111
+ this.#transplantFilter = transplantFilter
124
112
 
125
113
  if (global) {
126
114
  const real = await realpath(this.path, this[_rpcache], this[_stcache])
@@ -132,19 +120,19 @@ module.exports = cls => class ActualLoader extends cls {
132
120
  loadOverrides: true,
133
121
  }
134
122
  if (this.path === real) {
135
- this[_actualTree] = this[_newNode](params)
123
+ this.#actualTree = this.#newNode(params)
136
124
  } else {
137
- this[_actualTree] = await this[_newLink](params)
125
+ this.#actualTree = await this.#newLink(params)
138
126
  }
139
127
  } else {
140
128
  // not in global mode, hidden lockfile is allowed, load root pkg too
141
- this[_actualTree] = await this[_loadFSNode]({
129
+ this.#actualTree = await this.#loadFSNode({
142
130
  path: this.path,
143
131
  real: await realpath(this.path, this[_rpcache], this[_stcache]),
144
132
  loadOverrides: true,
145
133
  })
146
134
 
147
- this[_actualTree].assertRootOverrides()
135
+ this.#actualTree.assertRootOverrides()
148
136
 
149
137
  // if forceActual is set, don't even try the hidden lockfile
150
138
  if (!forceActual) {
@@ -152,48 +140,48 @@ module.exports = cls => class ActualLoader extends cls {
152
140
  // in the folder, or if any of the entries in the hidden lockfile are
153
141
  // missing.
154
142
  const meta = await Shrinkwrap.load({
155
- path: this[_actualTree].path,
143
+ path: this.#actualTree.path,
156
144
  hiddenLockfile: true,
157
145
  resolveOptions: this.options,
158
146
  })
159
147
 
160
148
  if (meta.loadedFromDisk) {
161
- this[_actualTree].meta = meta
149
+ this.#actualTree.meta = meta
162
150
  // have to load on a new Arborist object, so we don't assign
163
151
  // the virtualTree on this one! Also, the weird reference is because
164
152
  // we can't easily get a ref to Arborist in this module, without
165
153
  // creating a circular reference, since this class is a mixin used
166
154
  // to build up the Arborist class itself.
167
155
  await new this.constructor({ ...this.options }).loadVirtual({
168
- root: this[_actualTree],
156
+ root: this.#actualTree,
169
157
  })
170
- await this[_loadWorkspaces](this[_actualTree])
158
+ await this[_setWorkspaces](this.#actualTree)
171
159
 
172
- this[_transplant](root)
173
- return this[_actualTree]
160
+ this.#transplant(root)
161
+ return this.#actualTree
174
162
  }
175
163
  }
176
164
 
177
165
  const meta = await Shrinkwrap.load({
178
- path: this[_actualTree].path,
166
+ path: this.#actualTree.path,
179
167
  lockfileVersion: this.options.lockfileVersion,
180
168
  resolveOptions: this.options,
181
169
  })
182
- this[_actualTree].meta = meta
170
+ this.#actualTree.meta = meta
183
171
  }
184
172
 
185
- await this[_loadFSTree](this[_actualTree])
186
- await this[_loadWorkspaces](this[_actualTree])
173
+ await this.#loadFSTree(this.#actualTree)
174
+ await this[_setWorkspaces](this.#actualTree)
187
175
 
188
176
  // if there are workspace targets without Link nodes created, load
189
177
  // the targets, so that we know what they are.
190
- if (this[_actualTree].workspaces && this[_actualTree].workspaces.size) {
178
+ if (this.#actualTree.workspaces && this.#actualTree.workspaces.size) {
191
179
  const promises = []
192
- for (const path of this[_actualTree].workspaces.values()) {
193
- if (!this[_cache].has(path)) {
180
+ for (const path of this.#actualTree.workspaces.values()) {
181
+ if (!this.#cache.has(path)) {
194
182
  // workspace overrides use the root overrides
195
- const p = this[_loadFSNode]({ path, root: this[_actualTree], useRootOverrides: true })
196
- .then(node => this[_loadFSTree](node))
183
+ const p = this.#loadFSNode({ path, root: this.#actualTree, useRootOverrides: true })
184
+ .then(node => this.#loadFSTree(node))
197
185
  promises.push(p)
198
186
  }
199
187
  }
@@ -201,32 +189,32 @@ module.exports = cls => class ActualLoader extends cls {
201
189
  }
202
190
 
203
191
  if (!ignoreMissing) {
204
- await this[_findMissingEdges]()
192
+ await this.#findMissingEdges()
205
193
  }
206
194
 
207
195
  // try to find a node that is the parent in a fs tree sense, but not a
208
196
  // node_modules tree sense, of any link targets. this allows us to
209
197
  // resolve deps that node will find, but a legacy npm view of the
210
198
  // world would not have noticed.
211
- for (const path of this[_topNodes]) {
212
- const node = this[_cache].get(path)
199
+ for (const path of this.#topNodes) {
200
+ const node = this.#cache.get(path)
213
201
  if (node && !node.parent && !node.fsParent) {
214
202
  for (const p of walkUp(dirname(path))) {
215
- if (this[_cache].has(p)) {
216
- node.fsParent = this[_cache].get(p)
203
+ if (this.#cache.has(p)) {
204
+ node.fsParent = this.#cache.get(p)
217
205
  break
218
206
  }
219
207
  }
220
208
  }
221
209
  }
222
210
 
223
- this[_transplant](root)
211
+ this.#transplant(root)
224
212
 
225
213
  if (global) {
226
214
  // need to depend on the children, or else all of them
227
215
  // will end up being flagged as extraneous, since the
228
216
  // global root isn't a "real" project
229
- const tree = this[_actualTree]
217
+ const tree = this.#actualTree
230
218
  const actualRoot = tree.isLink ? tree.target : tree
231
219
  const { dependencies = {} } = actualRoot.package
232
220
  for (const [name, kid] of actualRoot.children.entries()) {
@@ -235,30 +223,30 @@ module.exports = cls => class ActualLoader extends cls {
235
223
  }
236
224
  actualRoot.package = { ...actualRoot.package, dependencies }
237
225
  }
238
- return this[_actualTree]
226
+ return this.#actualTree
239
227
  }
240
228
 
241
- [_transplant] (root) {
242
- if (!root || root === this[_actualTree]) {
229
+ #transplant (root) {
230
+ if (!root || root === this.#actualTree) {
243
231
  return
244
232
  }
245
233
 
246
- this[_actualTree][_changePath](root.path)
247
- for (const node of this[_actualTree].children.values()) {
248
- if (!this[_transplantFilter](node)) {
234
+ this.#actualTree[_changePath](root.path)
235
+ for (const node of this.#actualTree.children.values()) {
236
+ if (!this.#transplantFilter(node)) {
249
237
  node.root = null
250
238
  }
251
239
  }
252
240
 
253
- root.replace(this[_actualTree])
254
- for (const node of this[_actualTree].fsChildren) {
255
- node.root = this[_transplantFilter](node) ? root : null
241
+ root.replace(this.#actualTree)
242
+ for (const node of this.#actualTree.fsChildren) {
243
+ node.root = this.#transplantFilter(node) ? root : null
256
244
  }
257
245
 
258
- this[_actualTree] = root
246
+ this.#actualTree = root
259
247
  }
260
248
 
261
- async [_loadFSNode] ({ path, parent, real, root, loadOverrides, useRootOverrides }) {
249
+ async #loadFSNode ({ path, parent, real, root, loadOverrides, useRootOverrides }) {
262
250
  if (!real) {
263
251
  try {
264
252
  real = await realpath(path, this[_rpcache], this[_stcache])
@@ -275,7 +263,7 @@ module.exports = cls => class ActualLoader extends cls {
275
263
  }
276
264
  }
277
265
 
278
- const cached = this[_cache].get(path)
266
+ const cached = this.#cache.get(path)
279
267
  let node
280
268
  // missing edges get a dummy node, assign the parent and return it
281
269
  if (cached && !cached.dummy) {
@@ -306,67 +294,67 @@ module.exports = cls => class ActualLoader extends cls {
306
294
  // Node which will attach it to its errors array (Link passes it along to
307
295
  // its target node)
308
296
  if (normalize(path) === real) {
309
- node = this[_newNode](params)
297
+ node = this.#newNode(params)
310
298
  } else {
311
- node = await this[_newLink](params)
299
+ node = await this.#newLink(params)
312
300
  }
313
301
  }
314
- this[_cache].set(path, node)
302
+ this.#cache.set(path, node)
315
303
  return node
316
304
  }
317
305
 
318
- [_newNode] (options) {
306
+ #newNode (options) {
319
307
  // check it for an fsParent if it's a tree top. there's a decent chance
320
308
  // it'll get parented later, making the fsParent scan a no-op, but better
321
309
  // safe than sorry, since it's cheap.
322
310
  const { parent, realpath } = options
323
311
  if (!parent) {
324
- this[_topNodes].add(realpath)
312
+ this.#topNodes.add(realpath)
325
313
  }
326
314
  return new Node(options)
327
315
  }
328
316
 
329
- async [_newLink] (options) {
317
+ async #newLink (options) {
330
318
  const { realpath } = options
331
- this[_topNodes].add(realpath)
332
- const target = this[_cache].get(realpath)
319
+ this.#topNodes.add(realpath)
320
+ const target = this.#cache.get(realpath)
333
321
  const link = new Link({ ...options, target })
334
322
 
335
323
  if (!target) {
336
324
  // Link set its target itself in this case
337
- this[_cache].set(realpath, link.target)
325
+ this.#cache.set(realpath, link.target)
338
326
  // if a link target points at a node outside of the root tree's
339
327
  // node_modules hierarchy, then load that node as well.
340
- await this[_loadFSTree](link.target)
328
+ await this.#loadFSTree(link.target)
341
329
  }
342
330
 
343
331
  return link
344
332
  }
345
333
 
346
- async [_loadFSTree] (node) {
347
- const did = this[_actualTreeLoaded]
334
+ async #loadFSTree (node) {
335
+ const did = this.#actualTreeLoaded
348
336
  if (!did.has(node.target.realpath)) {
349
337
  did.add(node.target.realpath)
350
- await this[_loadFSChildren](node.target)
338
+ await this.#loadFSChildren(node.target)
351
339
  return Promise.all(
352
340
  [...node.target.children.entries()]
353
341
  .filter(([name, kid]) => !did.has(kid.realpath))
354
- .map(([name, kid]) => this[_loadFSTree](kid))
342
+ .map(([name, kid]) => this.#loadFSTree(kid))
355
343
  )
356
344
  }
357
345
  }
358
346
 
359
347
  // create child nodes for all the entries in node_modules
360
348
  // and attach them to the node as a parent
361
- async [_loadFSChildren] (node) {
349
+ async #loadFSChildren (node) {
362
350
  const nm = resolve(node.realpath, 'node_modules')
363
351
  try {
364
352
  const kids = await readdirScoped(nm).then(paths => paths.map(p => p.replace(/\\/g, '/')))
365
353
  return Promise.all(
366
354
  // ignore . dirs and retired scoped package folders
367
355
  kids.filter(kid => !/^(@[^/]+\/)?\./.test(kid))
368
- .filter(kid => this[_filter](node, kid))
369
- .map(kid => this[_loadFSNode]({
356
+ .filter(kid => this.#filter(node, kid))
357
+ .map(kid => this.#loadFSNode({
370
358
  parent: node,
371
359
  path: resolve(nm, kid),
372
360
  })))
@@ -375,7 +363,7 @@ module.exports = cls => class ActualLoader extends cls {
375
363
  }
376
364
  }
377
365
 
378
- async [_findMissingEdges] () {
366
+ async #findMissingEdges () {
379
367
  // try to resolve any missing edges by walking up the directory tree,
380
368
  // checking for the package in each node_modules folder. stop at the
381
369
  // root directory.
@@ -385,7 +373,7 @@ module.exports = cls => class ActualLoader extends cls {
385
373
  // because people sometimes develop in ~/projects/node_modules/...
386
374
  // so we'd end up loading a massive tree with lots of unrelated junk.
387
375
  const nmContents = new Map()
388
- const tree = this[_actualTree]
376
+ const tree = this.#actualTree
389
377
  for (const node of tree.inventory.values()) {
390
378
  const ancestor = ancestorPath(node.realpath, this.path)
391
379
 
@@ -410,28 +398,37 @@ module.exports = cls => class ActualLoader extends cls {
410
398
  break
411
399
  }
412
400
 
413
- const entries = nmContents.get(p) || await readdirScoped(p + '/node_modules')
414
- .catch(() => []).then(paths => paths.map(p => p.replace(/\\/g, '/')))
415
- nmContents.set(p, entries)
401
+ let entries
402
+ if (!nmContents.has(p)) {
403
+ entries = await readdirScoped(p + '/node_modules')
404
+ .catch(() => []).then(paths => paths.map(p => p.replace(/\\/g, '/')))
405
+ nmContents.set(p, entries)
406
+ } else {
407
+ entries = nmContents.get(p)
408
+ }
409
+
416
410
  if (!entries.includes(name)) {
417
411
  continue
418
412
  }
419
413
 
420
- const d = this[_cache].has(p) ? await this[_cache].get(p)
421
- : new Node({ path: p, root: node.root, dummy: true })
422
- // not a promise
423
- this[_cache].set(p, d)
414
+ let d
415
+ if (!this.#cache.has(p)) {
416
+ d = new Node({ path: p, root: node.root, dummy: true })
417
+ this.#cache.set(p, d)
418
+ } else {
419
+ d = this.#cache.get(p)
420
+ }
424
421
  if (d.dummy) {
425
422
  // it's a placeholder, so likely would not have loaded this dep,
426
423
  // unless another dep in the tree also needs it.
427
424
  const depPath = normalize(`${p}/node_modules/${name}`)
428
- const cached = this[_cache].get(depPath)
425
+ const cached = this.#cache.get(depPath)
429
426
  if (!cached || cached.dummy) {
430
- depPromises.push(this[_loadFSNode]({
427
+ depPromises.push(this.#loadFSNode({
431
428
  path: depPath,
432
429
  root: node.root,
433
430
  parent: d,
434
- }).then(node => this[_loadFSTree](node)))
431
+ }).then(node => this.#loadFSTree(node)))
435
432
  }
436
433
  }
437
434
  break