@npmcli/arborist 2.2.8 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/lib/logging.js +10 -2
- package/bin/lib/options.js +7 -1
- package/bin/lib/timers.js +6 -2
- package/bin/virtual.js +2 -1
- package/lib/add-rm-pkg-deps.js +1 -1
- package/lib/arborist/build-ideal-tree.js +260 -73
- package/lib/arborist/index.js +1 -1
- package/lib/arborist/load-actual.js +28 -2
- package/lib/arborist/load-virtual.js +9 -6
- package/lib/arborist/rebuild.js +5 -4
- package/lib/arborist/reify.js +163 -35
- package/lib/debug.js +8 -1
- package/lib/diff.js +70 -11
- package/lib/index.js +1 -0
- package/lib/link.js +13 -7
- package/lib/node.js +43 -2
- package/lib/printable.js +18 -0
- package/lib/tree-check.js +46 -1
- package/package.json +6 -9
|
@@ -32,6 +32,7 @@ const _loadActual = Symbol('loadActual')
|
|
|
32
32
|
const _loadActualVirtually = Symbol('loadActualVirtually')
|
|
33
33
|
const _loadActualActually = Symbol('loadActualActually')
|
|
34
34
|
const _loadWorkspaces = Symbol.for('loadWorkspaces')
|
|
35
|
+
const _loadWorkspaceTargets = Symbol('loadWorkspaceTargets')
|
|
35
36
|
const _actualTreePromise = Symbol('actualTreePromise')
|
|
36
37
|
const _actualTree = Symbol('actualTree')
|
|
37
38
|
const _transplant = Symbol('transplant')
|
|
@@ -150,18 +151,22 @@ module.exports = cls => class ActualLoader extends cls {
|
|
|
150
151
|
await new this.constructor({...this.options}).loadVirtual({
|
|
151
152
|
root: this[_actualTree],
|
|
152
153
|
})
|
|
154
|
+
await this[_loadWorkspaces](this[_actualTree])
|
|
155
|
+
if (this[_actualTree].workspaces && this[_actualTree].workspaces.size)
|
|
156
|
+
calcDepFlags(this[_actualTree], !root)
|
|
153
157
|
this[_transplant](root)
|
|
154
158
|
return this[_actualTree]
|
|
155
159
|
}
|
|
156
160
|
|
|
157
161
|
async [_loadActualActually] ({ root, ignoreMissing, global }) {
|
|
158
162
|
await this[_loadFSTree](this[_actualTree])
|
|
163
|
+
await this[_loadWorkspaces](this[_actualTree])
|
|
164
|
+
await this[_loadWorkspaceTargets](this[_actualTree])
|
|
159
165
|
if (!ignoreMissing)
|
|
160
166
|
await this[_findMissingEdges]()
|
|
161
167
|
this[_findFSParents]()
|
|
162
168
|
this[_transplant](root)
|
|
163
169
|
|
|
164
|
-
await this[_loadWorkspaces](this[_actualTree])
|
|
165
170
|
if (global) {
|
|
166
171
|
// need to depend on the children, or else all of them
|
|
167
172
|
// will end up being flagged as extraneous, since the
|
|
@@ -178,16 +183,37 @@ module.exports = cls => class ActualLoader extends cls {
|
|
|
178
183
|
return this[_actualTree]
|
|
179
184
|
}
|
|
180
185
|
|
|
186
|
+
// if there are workspace targets without Link nodes created, load
|
|
187
|
+
// the targets, so that we know what they are.
|
|
188
|
+
async [_loadWorkspaceTargets] (tree) {
|
|
189
|
+
if (!tree.workspaces || !tree.workspaces.size)
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
const promises = []
|
|
193
|
+
for (const path of tree.workspaces.values()) {
|
|
194
|
+
if (!this[_cache].has(path)) {
|
|
195
|
+
const p = this[_loadFSNode]({ path, root: this[_actualTree] })
|
|
196
|
+
.then(node => this[_loadFSTree](node))
|
|
197
|
+
promises.push(p)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
await Promise.all(promises)
|
|
201
|
+
}
|
|
202
|
+
|
|
181
203
|
[_transplant] (root) {
|
|
182
204
|
if (!root || root === this[_actualTree])
|
|
183
205
|
return
|
|
206
|
+
|
|
184
207
|
this[_actualTree][_changePath](root.path)
|
|
185
208
|
for (const node of this[_actualTree].children.values()) {
|
|
186
209
|
if (!this[_transplantFilter](node))
|
|
187
|
-
node.
|
|
210
|
+
node.root = null
|
|
188
211
|
}
|
|
189
212
|
|
|
190
213
|
root.replace(this[_actualTree])
|
|
214
|
+
for (const node of this[_actualTree].fsChildren)
|
|
215
|
+
node.root = this[_transplantFilter](node) ? root : null
|
|
216
|
+
|
|
191
217
|
this[_actualTree] = root
|
|
192
218
|
}
|
|
193
219
|
|
|
@@ -93,7 +93,8 @@ module.exports = cls => class VirtualLoader extends cls {
|
|
|
93
93
|
this.virtualTree = root
|
|
94
94
|
const {links, nodes} = this[resolveNodes](s, root)
|
|
95
95
|
await this[resolveLinks](links, nodes)
|
|
96
|
-
|
|
96
|
+
if (!(s.originalLockfileVersion >= 2))
|
|
97
|
+
this[assignBundles](nodes)
|
|
97
98
|
if (this[flagsSuspect])
|
|
98
99
|
this[reCalcDepFlags](nodes.values())
|
|
99
100
|
return root
|
|
@@ -220,22 +221,24 @@ module.exports = cls => class VirtualLoader extends cls {
|
|
|
220
221
|
[assignBundles] (nodes) {
|
|
221
222
|
for (const [location, node] of nodes) {
|
|
222
223
|
// Skip assignment of parentage for the root package
|
|
223
|
-
if (!location)
|
|
224
|
+
if (!location || node.target && !node.target.location)
|
|
224
225
|
continue
|
|
225
226
|
const { name, parent, package: { inBundle }} = node
|
|
227
|
+
|
|
226
228
|
if (!parent)
|
|
227
229
|
continue
|
|
228
230
|
|
|
229
231
|
// read inBundle from package because 'package' here is
|
|
230
232
|
// actually a v2 lockfile metadata entry.
|
|
231
|
-
// If the *parent* is also bundled, though,
|
|
232
|
-
// that it's being pulled in
|
|
233
|
+
// If the *parent* is also bundled, though, or if the parent has
|
|
234
|
+
// no dependency on it, then we assume that it's being pulled in
|
|
235
|
+
// just by virtue of its parent or a transitive dep being bundled.
|
|
233
236
|
const { package: ppkg } = parent
|
|
234
237
|
const { inBundle: parentBundled } = ppkg
|
|
235
|
-
if (inBundle && !parentBundled) {
|
|
238
|
+
if (inBundle && !parentBundled && parent.edgesOut.has(node.name)) {
|
|
236
239
|
if (!ppkg.bundleDependencies)
|
|
237
240
|
ppkg.bundleDependencies = [name]
|
|
238
|
-
else
|
|
241
|
+
else
|
|
239
242
|
ppkg.bundleDependencies.push(name)
|
|
240
243
|
}
|
|
241
244
|
}
|
package/lib/arborist/rebuild.js
CHANGED
|
@@ -115,10 +115,6 @@ module.exports = cls => class Builder extends cls {
|
|
|
115
115
|
await this[_runScripts]('preinstall')
|
|
116
116
|
if (this[_binLinks] && type !== 'links')
|
|
117
117
|
await this[_linkAllBins]()
|
|
118
|
-
if (!this[_ignoreScripts]) {
|
|
119
|
-
await this[_runScripts]('install')
|
|
120
|
-
await this[_runScripts]('postinstall')
|
|
121
|
-
}
|
|
122
118
|
|
|
123
119
|
// links should also run prepare scripts and only link bins after that
|
|
124
120
|
if (type === 'links') {
|
|
@@ -128,6 +124,11 @@ module.exports = cls => class Builder extends cls {
|
|
|
128
124
|
await this[_linkAllBins]()
|
|
129
125
|
}
|
|
130
126
|
|
|
127
|
+
if (!this[_ignoreScripts]) {
|
|
128
|
+
await this[_runScripts]('install')
|
|
129
|
+
await this[_runScripts]('postinstall')
|
|
130
|
+
}
|
|
131
|
+
|
|
131
132
|
process.emit('timeEnd', `build:${type}`)
|
|
132
133
|
}
|
|
133
134
|
|
package/lib/arborist/reify.js
CHANGED
|
@@ -14,6 +14,7 @@ const fs = require('fs')
|
|
|
14
14
|
const {promisify} = require('util')
|
|
15
15
|
const symlink = promisify(fs.symlink)
|
|
16
16
|
const mkdirp = require('mkdirp-infer-owner')
|
|
17
|
+
const justMkdirp = require('mkdirp')
|
|
17
18
|
const moveFile = require('@npmcli/move-file')
|
|
18
19
|
const rimraf = promisify(require('rimraf'))
|
|
19
20
|
const packageContents = require('@npmcli/installed-package-contents')
|
|
@@ -26,6 +27,7 @@ const retirePath = require('../retire-path.js')
|
|
|
26
27
|
const promiseAllRejectLate = require('promise-all-reject-late')
|
|
27
28
|
const optionalSet = require('../optional-set.js')
|
|
28
29
|
const updateRootPackageJson = require('../update-root-package-json.js')
|
|
30
|
+
const calcDepFlags = require('../calc-dep-flags.js')
|
|
29
31
|
|
|
30
32
|
const _retiredPaths = Symbol('retiredPaths')
|
|
31
33
|
const _retiredUnchanged = Symbol('retiredUnchanged')
|
|
@@ -36,6 +38,8 @@ const _retireShallowNodes = Symbol.for('retireShallowNodes')
|
|
|
36
38
|
const _getBundlesByDepth = Symbol('getBundlesByDepth')
|
|
37
39
|
const _registryResolved = Symbol('registryResolved')
|
|
38
40
|
const _addNodeToTrashList = Symbol('addNodeToTrashList')
|
|
41
|
+
const _workspaces = Symbol.for('workspaces')
|
|
42
|
+
|
|
39
43
|
// shared by rebuild mixin
|
|
40
44
|
const _trashList = Symbol.for('trashList')
|
|
41
45
|
const _handleOptionalFailure = Symbol.for('handleOptionalFailure')
|
|
@@ -45,7 +49,8 @@ const _loadTrees = Symbol.for('loadTrees')
|
|
|
45
49
|
const _diffTrees = Symbol.for('diffTrees')
|
|
46
50
|
const _createSparseTree = Symbol.for('createSparseTree')
|
|
47
51
|
const _loadShrinkwrapsAndUpdateTrees = Symbol.for('loadShrinkwrapsAndUpdateTrees')
|
|
48
|
-
const
|
|
52
|
+
const _shrinkwrapInflated = Symbol('shrinkwrapInflated')
|
|
53
|
+
const _bundleUnpacked = Symbol('bundleUnpacked')
|
|
49
54
|
const _reifyNode = Symbol.for('reifyNode')
|
|
50
55
|
const _extractOrLink = Symbol('extractOrLink')
|
|
51
56
|
// defined by rebuild mixin
|
|
@@ -82,7 +87,6 @@ const _global = Symbol.for('global')
|
|
|
82
87
|
|
|
83
88
|
// defined by Ideal mixin
|
|
84
89
|
const _pruneBundledMetadeps = Symbol.for('pruneBundledMetadeps')
|
|
85
|
-
const _explicitRequests = Symbol.for('explicitRequests')
|
|
86
90
|
const _resolvedAdd = Symbol.for('resolvedAdd')
|
|
87
91
|
const _usePackageLock = Symbol.for('usePackageLock')
|
|
88
92
|
const _formatPackageLock = Symbol.for('formatPackageLock')
|
|
@@ -105,7 +109,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
105
109
|
|
|
106
110
|
this.diff = null
|
|
107
111
|
this[_retiredPaths] = {}
|
|
108
|
-
this[
|
|
112
|
+
this[_shrinkwrapInflated] = new Set()
|
|
109
113
|
this[_retiredUnchanged] = {}
|
|
110
114
|
this[_sparseTreeDirs] = new Set()
|
|
111
115
|
this[_sparseTreeRoots] = new Set()
|
|
@@ -146,7 +150,10 @@ module.exports = cls => class Reifier extends cls {
|
|
|
146
150
|
if (this[_packageLockOnly] || this[_dryRun])
|
|
147
151
|
return
|
|
148
152
|
|
|
149
|
-
|
|
153
|
+
// we do NOT want to set ownership on this folder, especially
|
|
154
|
+
// recursively, because it can have other side effects to do that
|
|
155
|
+
// in a project directory. We just want to make it if it's missing.
|
|
156
|
+
await justMkdirp(resolve(this.path))
|
|
150
157
|
}
|
|
151
158
|
|
|
152
159
|
async [_reifyPackages] () {
|
|
@@ -237,9 +244,25 @@ module.exports = cls => class Reifier extends cls {
|
|
|
237
244
|
const actualOpt = this[_global] ? {
|
|
238
245
|
ignoreMissing: true,
|
|
239
246
|
global: true,
|
|
240
|
-
filter: (node, kid) =>
|
|
241
|
-
|
|
242
|
-
|
|
247
|
+
filter: (node, kid) => {
|
|
248
|
+
// if it's not the project root, and we have no explicit requests,
|
|
249
|
+
// then we're already into a nested dep, so we keep it
|
|
250
|
+
if (this.explicitRequests.size === 0 || !node.isProjectRoot)
|
|
251
|
+
return true
|
|
252
|
+
|
|
253
|
+
// if we added it as an edgeOut, then we want it
|
|
254
|
+
if (this.idealTree.edgesOut.has(kid))
|
|
255
|
+
return true
|
|
256
|
+
|
|
257
|
+
// if it's an explicit request, then we want it
|
|
258
|
+
const hasExplicit = [...this.explicitRequests]
|
|
259
|
+
.some(edge => edge.name === kid)
|
|
260
|
+
if (hasExplicit)
|
|
261
|
+
return true
|
|
262
|
+
|
|
263
|
+
// ignore the rest of the global install folder
|
|
264
|
+
return false
|
|
265
|
+
},
|
|
243
266
|
} : { ignoreMissing: true }
|
|
244
267
|
|
|
245
268
|
if (!this[_global]) {
|
|
@@ -266,9 +289,36 @@ module.exports = cls => class Reifier extends cls {
|
|
|
266
289
|
// to just invalidate the parts that changed, but avoid walking the
|
|
267
290
|
// whole tree again.
|
|
268
291
|
|
|
292
|
+
const filterNodes = []
|
|
293
|
+
if (this[_global] && this.explicitRequests.size) {
|
|
294
|
+
const idealTree = this.idealTree.target || this.idealTree
|
|
295
|
+
const actualTree = this.actualTree.target || this.actualTree
|
|
296
|
+
// we ONLY are allowed to make changes in the global top-level
|
|
297
|
+
// children where there's an explicit request.
|
|
298
|
+
for (const { name } of this.explicitRequests) {
|
|
299
|
+
const ideal = idealTree.children.get(name)
|
|
300
|
+
if (ideal)
|
|
301
|
+
filterNodes.push(ideal)
|
|
302
|
+
const actual = actualTree.children.get(name)
|
|
303
|
+
if (actual)
|
|
304
|
+
filterNodes.push(actual)
|
|
305
|
+
}
|
|
306
|
+
} else {
|
|
307
|
+
for (const ws of this[_workspaces]) {
|
|
308
|
+
const ideal = this.idealTree.children.get(ws)
|
|
309
|
+
if (ideal)
|
|
310
|
+
filterNodes.push(ideal)
|
|
311
|
+
const actual = this.actualTree.children.get(ws)
|
|
312
|
+
if (actual)
|
|
313
|
+
filterNodes.push(actual)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
269
317
|
// find all the nodes that need to change between the actual
|
|
270
318
|
// and ideal trees.
|
|
271
319
|
this.diff = Diff.calculate({
|
|
320
|
+
shrinkwrapInflated: this[_shrinkwrapInflated],
|
|
321
|
+
filterNodes,
|
|
272
322
|
actual: this.actualTree,
|
|
273
323
|
ideal: this.idealTree,
|
|
274
324
|
})
|
|
@@ -375,7 +425,8 @@ module.exports = cls => class Reifier extends cls {
|
|
|
375
425
|
const dirs = this.diff.leaves
|
|
376
426
|
.filter(diff => {
|
|
377
427
|
return (diff.action === 'ADD' || diff.action === 'CHANGE') &&
|
|
378
|
-
!this[_sparseTreeDirs].has(diff.ideal.path)
|
|
428
|
+
!this[_sparseTreeDirs].has(diff.ideal.path) &&
|
|
429
|
+
!diff.ideal.isLink
|
|
379
430
|
})
|
|
380
431
|
.map(diff => diff.ideal.path)
|
|
381
432
|
|
|
@@ -409,9 +460,9 @@ module.exports = cls => class Reifier extends cls {
|
|
|
409
460
|
// we need to unpack them, read that shrinkwrap file, and then update
|
|
410
461
|
// the tree by calling loadVirtual with the node as the root.
|
|
411
462
|
[_loadShrinkwrapsAndUpdateTrees] () {
|
|
412
|
-
const seen = this[
|
|
463
|
+
const seen = this[_shrinkwrapInflated]
|
|
413
464
|
const shrinkwraps = this.diff.leaves
|
|
414
|
-
.filter(d => (d.action === 'CHANGE' || d.action === 'ADD') &&
|
|
465
|
+
.filter(d => (d.action === 'CHANGE' || d.action === 'ADD' || !d.action) &&
|
|
415
466
|
d.ideal.hasShrinkwrap && !seen.has(d.ideal) &&
|
|
416
467
|
!this[_trashList].has(d.ideal.path))
|
|
417
468
|
|
|
@@ -424,7 +475,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
424
475
|
return promiseAllRejectLate(shrinkwraps.map(diff => {
|
|
425
476
|
const node = diff.ideal
|
|
426
477
|
seen.add(node)
|
|
427
|
-
return this[_reifyNode](node)
|
|
478
|
+
return diff.action ? this[_reifyNode](node) : node
|
|
428
479
|
}))
|
|
429
480
|
.then(nodes => promiseAllRejectLate(nodes.map(node => new Arborist({
|
|
430
481
|
...this.options,
|
|
@@ -455,7 +506,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
455
506
|
|
|
456
507
|
const { npmVersion, nodeVersion } = this.options
|
|
457
508
|
const p = Promise.resolve()
|
|
458
|
-
.then(() => {
|
|
509
|
+
.then(async () => {
|
|
459
510
|
// when we reify an optional node, check the engine and platform
|
|
460
511
|
// first. be sure to ignore the --force and --engine-strict flags,
|
|
461
512
|
// since we always want to skip any optional packages we can't install.
|
|
@@ -465,11 +516,11 @@ module.exports = cls => class Reifier extends cls {
|
|
|
465
516
|
checkEngine(node.package, npmVersion, nodeVersion, false)
|
|
466
517
|
checkPlatform(node.package, false)
|
|
467
518
|
}
|
|
519
|
+
await this[_checkBins](node)
|
|
520
|
+
await this[_extractOrLink](node)
|
|
521
|
+
await this[_warnDeprecated](node)
|
|
522
|
+
await this[_loadAncientPackageDetails](node)
|
|
468
523
|
})
|
|
469
|
-
.then(() => this[_checkBins](node))
|
|
470
|
-
.then(() => this[_extractOrLink](node))
|
|
471
|
-
.then(() => this[_warnDeprecated](node))
|
|
472
|
-
.then(() => this[_loadAncientPackageDetails](node))
|
|
473
524
|
|
|
474
525
|
return this[_handleOptionalFailure](node, p)
|
|
475
526
|
.then(() => {
|
|
@@ -515,10 +566,11 @@ module.exports = cls => class Reifier extends cls {
|
|
|
515
566
|
})
|
|
516
567
|
}
|
|
517
568
|
|
|
518
|
-
[_symlink] (node) {
|
|
569
|
+
async [_symlink] (node) {
|
|
519
570
|
const dir = dirname(node.path)
|
|
520
571
|
const target = node.realpath
|
|
521
572
|
const rel = relative(dir, target)
|
|
573
|
+
await mkdirp(dir)
|
|
522
574
|
return symlink(rel, node.path, 'junction')
|
|
523
575
|
}
|
|
524
576
|
|
|
@@ -585,8 +637,10 @@ module.exports = cls => class Reifier extends cls {
|
|
|
585
637
|
[_loadBundlesAndUpdateTrees] (
|
|
586
638
|
depth = 0, bundlesByDepth = this[_getBundlesByDepth]()
|
|
587
639
|
) {
|
|
588
|
-
if (depth === 0)
|
|
640
|
+
if (depth === 0) {
|
|
641
|
+
this[_bundleUnpacked] = new Set()
|
|
589
642
|
process.emit('time', 'reify:loadBundles')
|
|
643
|
+
}
|
|
590
644
|
const maxBundleDepth = bundlesByDepth.get('maxBundleDepth')
|
|
591
645
|
if (depth > maxBundleDepth) {
|
|
592
646
|
// if we did something, then prune the tree and update the diffs
|
|
@@ -602,13 +656,17 @@ module.exports = cls => class Reifier extends cls {
|
|
|
602
656
|
// shallower bundle overwriting them with a bundled meta-dep.
|
|
603
657
|
const set = (bundlesByDepth.get(depth) || [])
|
|
604
658
|
.filter(node => node.root === this.idealTree &&
|
|
659
|
+
node.target !== node.root &&
|
|
605
660
|
!this[_trashList].has(node.path))
|
|
606
661
|
|
|
607
662
|
if (!set.length)
|
|
608
663
|
return this[_loadBundlesAndUpdateTrees](depth + 1, bundlesByDepth)
|
|
609
664
|
|
|
610
665
|
// extract all the nodes with bundles
|
|
611
|
-
return promiseAllRejectLate(set.map(node =>
|
|
666
|
+
return promiseAllRejectLate(set.map(node => {
|
|
667
|
+
this[_bundleUnpacked].add(node)
|
|
668
|
+
return this[_reifyNode](node)
|
|
669
|
+
}))
|
|
612
670
|
// then load their unpacked children and move into the ideal tree
|
|
613
671
|
.then(nodes =>
|
|
614
672
|
promiseAllRejectLate(nodes.map(node => new this.constructor({
|
|
@@ -630,8 +688,13 @@ module.exports = cls => class Reifier extends cls {
|
|
|
630
688
|
tree: this.diff,
|
|
631
689
|
visit: diff => {
|
|
632
690
|
const node = diff.ideal
|
|
633
|
-
if (
|
|
634
|
-
|
|
691
|
+
if (!node)
|
|
692
|
+
return
|
|
693
|
+
if (node.isProjectRoot || (node.target && node.target.isProjectRoot))
|
|
694
|
+
return
|
|
695
|
+
|
|
696
|
+
const { bundleDependencies } = node.package
|
|
697
|
+
if (bundleDependencies && bundleDependencies.length) {
|
|
635
698
|
maxBundleDepth = Math.max(maxBundleDepth, node.depth)
|
|
636
699
|
if (!bundlesByDepth.has(node.depth))
|
|
637
700
|
bundlesByDepth.set(node.depth, [node])
|
|
@@ -736,14 +799,14 @@ module.exports = cls => class Reifier extends cls {
|
|
|
736
799
|
return
|
|
737
800
|
|
|
738
801
|
const node = diff.ideal
|
|
739
|
-
const bd = node
|
|
740
|
-
const sw = this[
|
|
802
|
+
const bd = this[_bundleUnpacked].has(node)
|
|
803
|
+
const sw = this[_shrinkwrapInflated].has(node)
|
|
741
804
|
|
|
742
805
|
// check whether we still need to unpack this one.
|
|
743
806
|
// test the inDepBundle last, since that's potentially a tree walk.
|
|
744
807
|
const doUnpack = node && // can't unpack if removed!
|
|
745
808
|
!node.isRoot && // root node already exists
|
|
746
|
-
!
|
|
809
|
+
!bd && // already unpacked to read bundle
|
|
747
810
|
!sw && // already unpacked to read sw
|
|
748
811
|
!node.inDepBundle // already unpacked by another dep's bundle
|
|
749
812
|
|
|
@@ -886,11 +949,11 @@ module.exports = cls => class Reifier extends cls {
|
|
|
886
949
|
// to things like git repos and tarball file/urls. However, if the
|
|
887
950
|
// user requested 'foo@', and we have a foo@file:../foo, then we should
|
|
888
951
|
// end up saving the spec we actually used, not whatever they gave us.
|
|
889
|
-
if (this[_resolvedAdd]) {
|
|
952
|
+
if (this[_resolvedAdd].length) {
|
|
890
953
|
const root = this.idealTree
|
|
891
954
|
const pkg = root.package
|
|
892
955
|
for (const { name } of this[_resolvedAdd]) {
|
|
893
|
-
const req = npa(root.edgesOut.get(name).spec, root.realpath)
|
|
956
|
+
const req = npa.resolve(name, root.edgesOut.get(name).spec, root.realpath)
|
|
894
957
|
const {rawSpec, subSpec} = req
|
|
895
958
|
|
|
896
959
|
const spec = subSpec ? subSpec.rawSpec : rawSpec
|
|
@@ -966,20 +1029,85 @@ module.exports = cls => class Reifier extends cls {
|
|
|
966
1029
|
return meta.save(saveOpt)
|
|
967
1030
|
}
|
|
968
1031
|
|
|
969
|
-
[_copyIdealToActual] () {
|
|
1032
|
+
async [_copyIdealToActual] () {
|
|
1033
|
+
// clean up any trash that is still in the tree
|
|
1034
|
+
for (const path of this[_trashList]) {
|
|
1035
|
+
const loc = relpath(this.idealTree.realpath, path)
|
|
1036
|
+
const node = this.idealTree.inventory.get(loc)
|
|
1037
|
+
if (node && node.root === this.idealTree)
|
|
1038
|
+
node.parent = null
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// if we filtered to only certain nodes, then anything ELSE needs
|
|
1042
|
+
// to be untouched in the resulting actual tree, even if it differs
|
|
1043
|
+
// in the idealTree. Copy over anything that was in the actual and
|
|
1044
|
+
// was not changed, delete anything in the ideal and not actual.
|
|
1045
|
+
// Then we move the entire idealTree over to this.actualTree, and
|
|
1046
|
+
// save the hidden lockfile.
|
|
1047
|
+
if (this.diff && this.diff.filterSet.size) {
|
|
1048
|
+
const { filterSet } = this.diff
|
|
1049
|
+
const seen = new Set()
|
|
1050
|
+
for (const [loc, ideal] of this.idealTree.inventory.entries()) {
|
|
1051
|
+
if (seen.has(loc))
|
|
1052
|
+
continue
|
|
1053
|
+
seen.add(loc)
|
|
1054
|
+
|
|
1055
|
+
// if it's an ideal node from the filter set, then skip it
|
|
1056
|
+
// because we already made whatever changes were necessary
|
|
1057
|
+
if (filterSet.has(ideal))
|
|
1058
|
+
continue
|
|
1059
|
+
|
|
1060
|
+
// otherwise, if it's not in the actualTree, then it's not a thing
|
|
1061
|
+
// that we actually added. And if it IS in the actualTree, then
|
|
1062
|
+
// it's something that we left untouched, so we need to record
|
|
1063
|
+
// that.
|
|
1064
|
+
const actual = this.actualTree.inventory.get(loc)
|
|
1065
|
+
if (!actual)
|
|
1066
|
+
ideal.root = null
|
|
1067
|
+
else {
|
|
1068
|
+
if ([...actual.linksIn].some(link => filterSet.has(link))) {
|
|
1069
|
+
seen.add(actual.location)
|
|
1070
|
+
continue
|
|
1071
|
+
}
|
|
1072
|
+
const { realpath, isLink } = actual
|
|
1073
|
+
if (isLink && ideal.isLink && ideal.realpath === realpath)
|
|
1074
|
+
continue
|
|
1075
|
+
else
|
|
1076
|
+
actual.root = this.idealTree
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// now find any actual nodes that may not be present in the ideal
|
|
1081
|
+
// tree, but were left behind by virtue of not being in the filter
|
|
1082
|
+
for (const [loc, actual] of this.actualTree.inventory.entries()) {
|
|
1083
|
+
if (seen.has(loc))
|
|
1084
|
+
continue
|
|
1085
|
+
seen.add(loc)
|
|
1086
|
+
if (filterSet.has(actual))
|
|
1087
|
+
continue
|
|
1088
|
+
actual.root = this.idealTree
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// prune out any tops that lack a linkIn
|
|
1092
|
+
for (const top of this.idealTree.tops) {
|
|
1093
|
+
if (top.linksIn.size === 0)
|
|
1094
|
+
top.root = null
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// need to calculate dep flags, since nodes may have been marked
|
|
1098
|
+
// as extraneous or otherwise incorrect during transit.
|
|
1099
|
+
calcDepFlags(this.idealTree)
|
|
1100
|
+
}
|
|
1101
|
+
|
|
970
1102
|
// save the ideal's meta as a hidden lockfile after we actualize it
|
|
971
1103
|
this.idealTree.meta.filename =
|
|
972
|
-
this.
|
|
1104
|
+
this.idealTree.realpath + '/node_modules/.package-lock.json'
|
|
973
1105
|
this.idealTree.meta.hiddenLockfile = true
|
|
1106
|
+
|
|
974
1107
|
this.actualTree = this.idealTree
|
|
975
1108
|
this.idealTree = null
|
|
976
|
-
for (const path of this[_trashList]) {
|
|
977
|
-
const loc = relpath(this.path, path)
|
|
978
|
-
const node = this.actualTree.inventory.get(loc)
|
|
979
|
-
if (node && node.root === this.actualTree)
|
|
980
|
-
node.parent = null
|
|
981
|
-
}
|
|
982
1109
|
|
|
983
|
-
|
|
1110
|
+
if (!this[_global])
|
|
1111
|
+
await this.actualTree.meta.save()
|
|
984
1112
|
}
|
|
985
1113
|
}
|
package/lib/debug.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
// run in debug mode if explicitly requested, running arborist tests,
|
|
14
14
|
// or working in the arborist project directory.
|
|
15
|
+
|
|
15
16
|
const debug = process.env.ARBORIST_DEBUG !== '0' && (
|
|
16
17
|
process.env.ARBORIST_DEBUG === '1' ||
|
|
17
18
|
/\barborist\b/.test(process.env.NODE_DEBUG || '') ||
|
|
@@ -21,4 +22,10 @@ const debug = process.env.ARBORIST_DEBUG !== '0' && (
|
|
|
21
22
|
)
|
|
22
23
|
|
|
23
24
|
module.exports = debug ? fn => fn() : () => {}
|
|
24
|
-
|
|
25
|
+
const red = process.stderr.isTTY ? msg => `\x1B[31m${msg}\x1B[39m` : m => m
|
|
26
|
+
module.exports.log = (...msg) => module.exports(() => {
|
|
27
|
+
const { format } = require('util')
|
|
28
|
+
const prefix = `\n${process.pid} ${red(format(msg.shift()))} `
|
|
29
|
+
msg = (prefix + format(...msg).trim().split('\n').join(prefix)).trim()
|
|
30
|
+
console.error(msg)
|
|
31
|
+
})
|
package/lib/diff.js
CHANGED
|
@@ -11,7 +11,9 @@ const {existsSync} = require('fs')
|
|
|
11
11
|
const ssri = require('ssri')
|
|
12
12
|
|
|
13
13
|
class Diff {
|
|
14
|
-
constructor ({actual, ideal}) {
|
|
14
|
+
constructor ({actual, ideal, filterSet, shrinkwrapInflated}) {
|
|
15
|
+
this.filterSet = filterSet
|
|
16
|
+
this.shrinkwrapInflated = shrinkwrapInflated
|
|
15
17
|
this.children = []
|
|
16
18
|
this.actual = actual
|
|
17
19
|
this.ideal = ideal
|
|
@@ -29,9 +31,54 @@ class Diff {
|
|
|
29
31
|
this.removed = []
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
static calculate ({actual, ideal}) {
|
|
34
|
+
static calculate ({actual, ideal, filterNodes = [], shrinkwrapInflated = new Set()}) {
|
|
35
|
+
// if there's a filterNode, then:
|
|
36
|
+
// - get the path from the root to the filterNode. The root or
|
|
37
|
+
// root.target should have an edge either to the filterNode or
|
|
38
|
+
// a link to the filterNode. If not, abort. Add the path to the
|
|
39
|
+
// filterSet.
|
|
40
|
+
// - Add set of Nodes depended on by the filterNode to filterSet.
|
|
41
|
+
// - Anything outside of that set should be ignored by getChildren
|
|
42
|
+
const filterSet = new Set()
|
|
43
|
+
for (const filterNode of filterNodes) {
|
|
44
|
+
const { root } = filterNode
|
|
45
|
+
if (root !== ideal && root !== actual)
|
|
46
|
+
throw new Error('invalid filterNode: outside idealTree/actualTree')
|
|
47
|
+
const { target } = root
|
|
48
|
+
const rootTarget = target || root
|
|
49
|
+
const edge = [...rootTarget.edgesOut.values()].filter(e => {
|
|
50
|
+
return e.to && (e.to === filterNode || e.to.target === filterNode)
|
|
51
|
+
})[0]
|
|
52
|
+
filterSet.add(root)
|
|
53
|
+
filterSet.add(rootTarget)
|
|
54
|
+
filterSet.add(ideal)
|
|
55
|
+
filterSet.add(actual)
|
|
56
|
+
if (edge && edge.to) {
|
|
57
|
+
filterSet.add(edge.to)
|
|
58
|
+
if (edge.to.target)
|
|
59
|
+
filterSet.add(edge.to.target)
|
|
60
|
+
}
|
|
61
|
+
filterSet.add(filterNode)
|
|
62
|
+
|
|
63
|
+
depth({
|
|
64
|
+
tree: filterNode,
|
|
65
|
+
visit: node => filterSet.add(node),
|
|
66
|
+
getChildren: node => {
|
|
67
|
+
node = node.target || node
|
|
68
|
+
const loc = node.location
|
|
69
|
+
const idealNode = ideal.inventory.get(loc)
|
|
70
|
+
const ideals = !idealNode ? []
|
|
71
|
+
: [...idealNode.edgesOut.values()].filter(e => e.to).map(e => e.to)
|
|
72
|
+
const actualNode = actual.inventory.get(loc)
|
|
73
|
+
const actuals = !actualNode ? []
|
|
74
|
+
: [...actualNode.edgesOut.values()].filter(e => e.to).map(e => e.to)
|
|
75
|
+
return ideals.concat(actuals)
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
33
80
|
return depth({
|
|
34
|
-
tree: new Diff({actual, ideal}),
|
|
81
|
+
tree: new Diff({actual, ideal, filterSet, shrinkwrapInflated}),
|
|
35
82
|
getChildren,
|
|
36
83
|
leave,
|
|
37
84
|
})
|
|
@@ -89,20 +136,29 @@ const allChildren = node => {
|
|
|
89
136
|
// to create the diff tree
|
|
90
137
|
const getChildren = diff => {
|
|
91
138
|
const children = []
|
|
92
|
-
const {unchanged, removed} = diff
|
|
139
|
+
const {actual, ideal, unchanged, removed, filterSet, shrinkwrapInflated} = diff
|
|
93
140
|
|
|
94
141
|
// Note: we DON'T diff fsChildren themselves, because they are either
|
|
95
142
|
// included in the package contents, or part of some other project, and
|
|
96
143
|
// will never appear in legacy shrinkwraps anyway. but we _do_ include the
|
|
97
144
|
// child nodes of fsChildren, because those are nodes that we are typically
|
|
98
145
|
// responsible for installing.
|
|
99
|
-
const actualKids = allChildren(
|
|
100
|
-
const idealKids = allChildren(
|
|
146
|
+
const actualKids = allChildren(actual)
|
|
147
|
+
const idealKids = allChildren(ideal)
|
|
148
|
+
|
|
149
|
+
if (ideal && ideal.hasShrinkwrap && !shrinkwrapInflated.has(ideal)) {
|
|
150
|
+
// Guaranteed to get a diff.leaves here, because we always
|
|
151
|
+
// be called with a proper Diff object when ideal has a shrinkwrap
|
|
152
|
+
// that has not been inflated.
|
|
153
|
+
diff.leaves.push(diff)
|
|
154
|
+
return children
|
|
155
|
+
}
|
|
156
|
+
|
|
101
157
|
const paths = new Set([...actualKids.keys(), ...idealKids.keys()])
|
|
102
158
|
for (const path of paths) {
|
|
103
159
|
const actual = actualKids.get(path)
|
|
104
160
|
const ideal = idealKids.get(path)
|
|
105
|
-
diffNode(actual, ideal, children, unchanged, removed)
|
|
161
|
+
diffNode(actual, ideal, children, unchanged, removed, filterSet, shrinkwrapInflated)
|
|
106
162
|
}
|
|
107
163
|
|
|
108
164
|
if (diff.leaves && !children.length)
|
|
@@ -111,15 +167,18 @@ const getChildren = diff => {
|
|
|
111
167
|
return children
|
|
112
168
|
}
|
|
113
169
|
|
|
114
|
-
const diffNode = (actual, ideal, children, unchanged, removed) => {
|
|
170
|
+
const diffNode = (actual, ideal, children, unchanged, removed, filterSet, shrinkwrapInflated) => {
|
|
171
|
+
if (filterSet.size && !(filterSet.has(ideal) || filterSet.has(actual)))
|
|
172
|
+
return
|
|
173
|
+
|
|
115
174
|
const action = getAction({actual, ideal})
|
|
116
175
|
|
|
117
176
|
// if it's a match, then get its children
|
|
118
177
|
// otherwise, this is the child diff node
|
|
119
|
-
if (action) {
|
|
178
|
+
if (action || (!shrinkwrapInflated.has(ideal) && ideal.hasShrinkwrap)) {
|
|
120
179
|
if (action === 'REMOVE')
|
|
121
180
|
removed.push(actual)
|
|
122
|
-
children.push(new Diff({actual, ideal}))
|
|
181
|
+
children.push(new Diff({actual, ideal, filterSet, shrinkwrapInflated}))
|
|
123
182
|
} else {
|
|
124
183
|
unchanged.push(ideal)
|
|
125
184
|
// !*! Weird dirty hack warning !*!
|
|
@@ -150,7 +209,7 @@ const diffNode = (actual, ideal, children, unchanged, removed) => {
|
|
|
150
209
|
for (const node of bundledChildren)
|
|
151
210
|
node.parent = ideal
|
|
152
211
|
}
|
|
153
|
-
children.push(...getChildren({actual, ideal, unchanged, removed}))
|
|
212
|
+
children.push(...getChildren({actual, ideal, unchanged, removed, filterSet, shrinkwrapInflated}))
|
|
154
213
|
}
|
|
155
214
|
}
|
|
156
215
|
|
package/lib/index.js
CHANGED
|
@@ -3,5 +3,6 @@ module.exports.Arborist = module.exports
|
|
|
3
3
|
module.exports.Node = require('./node.js')
|
|
4
4
|
module.exports.Link = require('./link.js')
|
|
5
5
|
module.exports.Edge = require('./edge.js')
|
|
6
|
+
module.exports.Shrinkwrap = require('./shrinkwrap.js')
|
|
6
7
|
// XXX export the other classes, too. shrinkwrap, diff, etc.
|
|
7
8
|
// they're handy!
|