@npmcli/arborist 6.2.7 → 6.2.9
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.
- package/lib/arborist/build-ideal-tree.js +271 -269
- package/lib/arborist/index.js +2 -1
- package/lib/arborist/load-actual.js +105 -108
- package/lib/arborist/load-virtual.js +58 -78
- package/lib/arborist/reify.js +1 -2
- package/lib/arborist/{load-workspaces.js → set-workspaces.js} +2 -2
- package/lib/calc-dep-flags.js +14 -10
- package/lib/dep-valid.js +15 -9
- package/lib/edge.js +168 -161
- package/lib/from-path.js +21 -15
- package/lib/inventory.js +68 -55
- package/lib/node.js +73 -76
- package/lib/query-selector-all.js +1 -1
- package/package.json +5 -5
package/lib/arborist/index.js
CHANGED
|
@@ -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('./
|
|
40
|
+
require('./set-workspaces.js'),
|
|
41
41
|
require('./load-actual.js'),
|
|
42
42
|
require('./load-virtual.js'),
|
|
43
43
|
require('./rebuild.js'),
|
|
@@ -71,6 +71,7 @@ class Arborist extends Base {
|
|
|
71
71
|
this.options = {
|
|
72
72
|
nodeVersion: process.version,
|
|
73
73
|
...options,
|
|
74
|
+
Arborist: this.constructor,
|
|
74
75
|
path: options.path || '.',
|
|
75
76
|
cache: options.cache || `${homedir()}/.npm/_cacache`,
|
|
76
77
|
packumentCache: options.packumentCache || new Map(),
|
|
@@ -4,7 +4,7 @@ const { relative, dirname, resolve, join, normalize } = require('path')
|
|
|
4
4
|
|
|
5
5
|
const rpj = require('read-package-json-fast')
|
|
6
6
|
const { readdirScoped } = require('@npmcli/fs')
|
|
7
|
-
const walkUp = require('walk-up-path')
|
|
7
|
+
const { walkUp } = require('walk-up-path')
|
|
8
8
|
const ancestorPath = require('common-ancestor-path')
|
|
9
9
|
const treeCheck = require('../tree-check.js')
|
|
10
10
|
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
123
|
-
this
|
|
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
|
|
123
|
+
this.#actualTree = this.#newNode(params)
|
|
136
124
|
} else {
|
|
137
|
-
this
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
156
|
+
root: this.#actualTree,
|
|
169
157
|
})
|
|
170
|
-
await this[
|
|
158
|
+
await this[_setWorkspaces](this.#actualTree)
|
|
171
159
|
|
|
172
|
-
this
|
|
173
|
-
return this
|
|
160
|
+
this.#transplant(root)
|
|
161
|
+
return this.#actualTree
|
|
174
162
|
}
|
|
175
163
|
}
|
|
176
164
|
|
|
177
165
|
const meta = await Shrinkwrap.load({
|
|
178
|
-
path: this
|
|
166
|
+
path: this.#actualTree.path,
|
|
179
167
|
lockfileVersion: this.options.lockfileVersion,
|
|
180
168
|
resolveOptions: this.options,
|
|
181
169
|
})
|
|
182
|
-
this
|
|
170
|
+
this.#actualTree.meta = meta
|
|
183
171
|
}
|
|
184
172
|
|
|
185
|
-
await this
|
|
186
|
-
await this[
|
|
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
|
|
178
|
+
if (this.#actualTree.workspaces && this.#actualTree.workspaces.size) {
|
|
191
179
|
const promises = []
|
|
192
|
-
for (const path of this
|
|
193
|
-
if (!this
|
|
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
|
|
196
|
-
.then(node => this
|
|
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
|
|
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
|
|
212
|
-
const node = this
|
|
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
|
|
216
|
-
node.fsParent = this
|
|
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
|
|
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
|
|
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
|
|
226
|
+
return this.#actualTree
|
|
239
227
|
}
|
|
240
228
|
|
|
241
|
-
|
|
242
|
-
if (!root || root === this
|
|
229
|
+
#transplant (root) {
|
|
230
|
+
if (!root || root === this.#actualTree) {
|
|
243
231
|
return
|
|
244
232
|
}
|
|
245
233
|
|
|
246
|
-
this[
|
|
247
|
-
for (const node of this
|
|
248
|
-
if (!this
|
|
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
|
|
254
|
-
for (const node of this
|
|
255
|
-
node.root = this
|
|
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
|
|
246
|
+
this.#actualTree = root
|
|
259
247
|
}
|
|
260
248
|
|
|
261
|
-
async
|
|
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
|
|
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
|
|
297
|
+
node = this.#newNode(params)
|
|
310
298
|
} else {
|
|
311
|
-
node = await this
|
|
299
|
+
node = await this.#newLink(params)
|
|
312
300
|
}
|
|
313
301
|
}
|
|
314
|
-
this
|
|
302
|
+
this.#cache.set(path, node)
|
|
315
303
|
return node
|
|
316
304
|
}
|
|
317
305
|
|
|
318
|
-
|
|
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
|
|
312
|
+
this.#topNodes.add(realpath)
|
|
325
313
|
}
|
|
326
314
|
return new Node(options)
|
|
327
315
|
}
|
|
328
316
|
|
|
329
|
-
async
|
|
317
|
+
async #newLink (options) {
|
|
330
318
|
const { realpath } = options
|
|
331
|
-
this
|
|
332
|
-
const target = this
|
|
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
|
|
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
|
|
328
|
+
await this.#loadFSTree(link.target)
|
|
341
329
|
}
|
|
342
330
|
|
|
343
331
|
return link
|
|
344
332
|
}
|
|
345
333
|
|
|
346
|
-
async
|
|
347
|
-
const did = this
|
|
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
|
|
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
|
|
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
|
|
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
|
|
369
|
-
.map(kid => this
|
|
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
|
|
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
|
|
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
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
|
425
|
+
const cached = this.#cache.get(depPath)
|
|
429
426
|
if (!cached || cached.dummy) {
|
|
430
|
-
depPromises.push(this
|
|
427
|
+
depPromises.push(this.#loadFSNode({
|
|
431
428
|
path: depPath,
|
|
432
429
|
root: node.root,
|
|
433
430
|
parent: d,
|
|
434
|
-
}).then(node => this
|
|
431
|
+
}).then(node => this.#loadFSTree(node)))
|
|
435
432
|
}
|
|
436
433
|
}
|
|
437
434
|
break
|