@npmcli/arborist 9.1.5 → 9.1.7
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/README.md +1 -1
- package/bin/index.js +1 -1
- package/lib/arborist/build-ideal-tree.js +43 -54
- package/lib/arborist/index.js +30 -0
- package/lib/arborist/isolated-reifier.js +6 -7
- package/lib/arborist/load-actual.js +1 -14
- package/lib/arborist/load-virtual.js +25 -54
- package/lib/arborist/rebuild.js +13 -18
- package/lib/arborist/reify.js +25 -56
- package/lib/calc-dep-flags.js +86 -128
- package/lib/diff.js +17 -21
- package/lib/edge.js +9 -3
- package/lib/gather-dep-set.js +1 -1
- package/lib/node.js +22 -6
- package/lib/optional-set.js +2 -8
- package/lib/override-set.js +76 -2
- package/lib/packument-cache.js +1 -1
- package/lib/place-dep.js +3 -3
- package/lib/query-selector-all.js +1 -1
- package/lib/reset-dep-flags.js +1 -5
- package/lib/shrinkwrap.js +3 -18
- package/package.json +10 -10
package/lib/arborist/reify.js
CHANGED
|
@@ -7,7 +7,6 @@ const pacote = require('pacote')
|
|
|
7
7
|
const promiseAllRejectLate = require('promise-all-reject-late')
|
|
8
8
|
const runScript = require('@npmcli/run-script')
|
|
9
9
|
const { callLimit: promiseCallLimit } = require('promise-call-limit')
|
|
10
|
-
const { checkEngine, checkPlatform } = require('npm-install-checks')
|
|
11
10
|
const { depth: dfwalk } = require('treeverse')
|
|
12
11
|
const { dirname, resolve, relative, join } = require('node:path')
|
|
13
12
|
const { log, time } = require('proc-log')
|
|
@@ -58,11 +57,9 @@ const _rollbackRetireShallowNodes = Symbol.for('rollbackRetireShallowNodes')
|
|
|
58
57
|
const _rollbackCreateSparseTree = Symbol.for('rollbackCreateSparseTree')
|
|
59
58
|
const _rollbackMoveBackRetiredUnchanged = Symbol.for('rollbackMoveBackRetiredUnchanged')
|
|
60
59
|
const _saveIdealTree = Symbol.for('saveIdealTree')
|
|
61
|
-
const _reifyPackages = Symbol.for('reifyPackages')
|
|
62
60
|
|
|
63
61
|
// defined by build-ideal-tree mixin
|
|
64
62
|
const _resolvedAdd = Symbol.for('resolvedAdd')
|
|
65
|
-
const _usePackageLock = Symbol.for('usePackageLock')
|
|
66
63
|
// used by build-ideal-tree mixin
|
|
67
64
|
const _addNodeToTrashList = Symbol.for('addNodeToTrashList')
|
|
68
65
|
|
|
@@ -71,13 +68,10 @@ const _createIsolatedTree = Symbol.for('createIsolatedTree')
|
|
|
71
68
|
module.exports = cls => class Reifier extends cls {
|
|
72
69
|
#bundleMissing = new Set() // child nodes we'd EXPECT to be included in a bundle, but aren't
|
|
73
70
|
#bundleUnpacked = new Set() // the nodes we unpack to read their bundles
|
|
74
|
-
#dryRun
|
|
75
71
|
#nmValidated = new Set()
|
|
76
72
|
#omit
|
|
77
|
-
#omitted
|
|
78
73
|
#retiredPaths = {}
|
|
79
74
|
#retiredUnchanged = {}
|
|
80
|
-
#savePrefix
|
|
81
75
|
#shrinkwrapInflated = new Set()
|
|
82
76
|
#sparseTreeDirs = new Set()
|
|
83
77
|
#sparseTreeRoots = new Set()
|
|
@@ -99,7 +93,6 @@ module.exports = cls => class Reifier extends cls {
|
|
|
99
93
|
}
|
|
100
94
|
|
|
101
95
|
this.#omit = new Set(options.omit)
|
|
102
|
-
this.#omitted = new Set()
|
|
103
96
|
|
|
104
97
|
// start tracker block
|
|
105
98
|
this.addTracker('reify')
|
|
@@ -125,22 +118,24 @@ module.exports = cls => class Reifier extends cls {
|
|
|
125
118
|
this.idealTree = await this[_createIsolatedTree]()
|
|
126
119
|
}
|
|
127
120
|
await this[_diffTrees]()
|
|
128
|
-
await this
|
|
121
|
+
await this.#reifyPackages()
|
|
129
122
|
if (linked) {
|
|
130
123
|
// swap back in the idealTree
|
|
131
124
|
// so that the lockfile is preserved
|
|
132
125
|
this.idealTree = oldTree
|
|
133
126
|
}
|
|
134
127
|
await this[_saveIdealTree](options)
|
|
135
|
-
// clean
|
|
136
|
-
for (const node of this
|
|
137
|
-
node.
|
|
128
|
+
// clean inert
|
|
129
|
+
for (const node of this.idealTree.inventory.values()) {
|
|
130
|
+
if (node.inert) {
|
|
131
|
+
node.parent = null
|
|
132
|
+
}
|
|
138
133
|
}
|
|
139
134
|
// clean up any trash that is still in the tree
|
|
140
135
|
for (const path of this[_trashList]) {
|
|
141
136
|
const loc = relpath(this.idealTree.realpath, path)
|
|
142
137
|
const node = this.idealTree.inventory.get(loc)
|
|
143
|
-
if (node && node.root === this.idealTree
|
|
138
|
+
if (node && node.root === this.idealTree) {
|
|
144
139
|
node.parent = null
|
|
145
140
|
}
|
|
146
141
|
}
|
|
@@ -228,18 +223,6 @@ module.exports = cls => class Reifier extends cls {
|
|
|
228
223
|
this.idealTree.meta.hiddenLockfile = true
|
|
229
224
|
this.idealTree.meta.lockfileVersion = defaultLockfileVersion
|
|
230
225
|
|
|
231
|
-
// Preserve inertness for failed stuff.
|
|
232
|
-
if (this.actualTree) {
|
|
233
|
-
for (const [loc, actual] of this.actualTree.inventory.entries()) {
|
|
234
|
-
if (actual.ideallyInert) {
|
|
235
|
-
const ideal = this.idealTree.inventory.get(loc)
|
|
236
|
-
if (ideal) {
|
|
237
|
-
ideal.ideallyInert = true
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
226
|
this.actualTree = this.idealTree
|
|
244
227
|
this.idealTree = null
|
|
245
228
|
|
|
@@ -274,7 +257,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
274
257
|
return treeCheck(this.actualTree)
|
|
275
258
|
}
|
|
276
259
|
|
|
277
|
-
async
|
|
260
|
+
async #reifyPackages () {
|
|
278
261
|
// we don't submit the audit report or write to disk on dry runs
|
|
279
262
|
if (this.options.dryRun) {
|
|
280
263
|
return
|
|
@@ -465,7 +448,6 @@ module.exports = cls => class Reifier extends cls {
|
|
|
465
448
|
// and ideal trees.
|
|
466
449
|
this.diff = Diff.calculate({
|
|
467
450
|
omit: this.#omit,
|
|
468
|
-
omitted: this.#omitted,
|
|
469
451
|
shrinkwrapInflated: this.#shrinkwrapInflated,
|
|
470
452
|
filterNodes,
|
|
471
453
|
actual: this.actualTree,
|
|
@@ -566,9 +548,6 @@ module.exports = cls => class Reifier extends cls {
|
|
|
566
548
|
// retire the same path at the same time.
|
|
567
549
|
const dirsChecked = new Set()
|
|
568
550
|
return promiseAllRejectLate(leaves.map(async node => {
|
|
569
|
-
if (node.ideallyInert) {
|
|
570
|
-
return
|
|
571
|
-
}
|
|
572
551
|
for (const d of walkUp(node.path)) {
|
|
573
552
|
if (d === node.top.path) {
|
|
574
553
|
break
|
|
@@ -662,18 +641,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
662
641
|
const timeEnd = time.start(`reifyNode:${node.location}`)
|
|
663
642
|
this.addTracker('reify', node.name, node.location)
|
|
664
643
|
|
|
665
|
-
const { npmVersion, nodeVersion, cpu, os, libc } = this.options
|
|
666
644
|
const p = Promise.resolve().then(async () => {
|
|
667
|
-
// when we reify an optional node, check the engine and platform
|
|
668
|
-
// first. be sure to ignore the --force and --engine-strict flags,
|
|
669
|
-
// since we always want to skip any optional packages we can't install.
|
|
670
|
-
// these checks throwing will result in a rollback and removal
|
|
671
|
-
// of the mismatches
|
|
672
|
-
// eslint-disable-next-line promise/always-return
|
|
673
|
-
if (node.optional) {
|
|
674
|
-
checkEngine(node.package, npmVersion, nodeVersion, false)
|
|
675
|
-
checkPlatform(node.package, false, { cpu, os, libc })
|
|
676
|
-
}
|
|
677
645
|
await this[_checkBins](node)
|
|
678
646
|
await this.#extractOrLink(node)
|
|
679
647
|
const { _id, deprecated } = node.package
|
|
@@ -707,10 +675,6 @@ module.exports = cls => class Reifier extends cls {
|
|
|
707
675
|
}
|
|
708
676
|
|
|
709
677
|
async #extractOrLink (node) {
|
|
710
|
-
if (node.ideallyInert) {
|
|
711
|
-
return
|
|
712
|
-
}
|
|
713
|
-
|
|
714
678
|
const nm = resolve(node.parent.path, 'node_modules')
|
|
715
679
|
await this.#validateNodeModules(nm)
|
|
716
680
|
|
|
@@ -791,9 +755,9 @@ module.exports = cls => class Reifier extends cls {
|
|
|
791
755
|
[_handleOptionalFailure] (node, p) {
|
|
792
756
|
return (node.optional ? p.catch(() => {
|
|
793
757
|
const set = optionalSet(node)
|
|
794
|
-
for (node of set) {
|
|
758
|
+
for (const node of set) {
|
|
795
759
|
log.verbose('reify', 'failed optional dependency', node.path)
|
|
796
|
-
node.
|
|
760
|
+
node.inert = true
|
|
797
761
|
this[_addNodeToTrashList](node)
|
|
798
762
|
}
|
|
799
763
|
}) : p).then(() => node)
|
|
@@ -849,9 +813,17 @@ module.exports = cls => class Reifier extends cls {
|
|
|
849
813
|
// Make sure we don't double-include the path if it's already there
|
|
850
814
|
const registryPath = registryURL.pathname.replace(/\/$/, '')
|
|
851
815
|
|
|
852
|
-
if (registryPath && registryPath !== '/'
|
|
853
|
-
//
|
|
854
|
-
|
|
816
|
+
if (registryPath && registryPath !== '/') {
|
|
817
|
+
// Check if the resolved pathname already starts with the registry path
|
|
818
|
+
// We need to ensure it's a proper path prefix, not just a string prefix
|
|
819
|
+
// e.g., registry path '/npm' should not match '/npm-run-path'
|
|
820
|
+
const hasRegistryPath = resolvedURL.pathname === registryPath ||
|
|
821
|
+
resolvedURL.pathname.startsWith(registryPath + '/')
|
|
822
|
+
|
|
823
|
+
if (!hasRegistryPath) {
|
|
824
|
+
// Since hostname is changed, we need to ensure the registry path is included
|
|
825
|
+
resolvedURL.pathname = registryPath + resolvedURL.pathname
|
|
826
|
+
}
|
|
855
827
|
}
|
|
856
828
|
|
|
857
829
|
return resolvedURL.toString()
|
|
@@ -1165,9 +1137,6 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1165
1137
|
|
|
1166
1138
|
this.#retiredUnchanged[retireFolder] = []
|
|
1167
1139
|
return promiseAllRejectLate(diff.unchanged.map(node => {
|
|
1168
|
-
if (node.ideallyInert) {
|
|
1169
|
-
return
|
|
1170
|
-
}
|
|
1171
1140
|
// no need to roll back links, since we'll just delete them anyway
|
|
1172
1141
|
if (node.isLink) {
|
|
1173
1142
|
return mkdir(dirname(node.path), { recursive: true, force: true })
|
|
@@ -1247,7 +1216,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1247
1216
|
// skip links that only live within node_modules as they are most
|
|
1248
1217
|
// likely managed by packages we installed, we only want to rebuild
|
|
1249
1218
|
// unchanged links we directly manage
|
|
1250
|
-
const linkedFromRoot = (node.parent === tree && !node.
|
|
1219
|
+
const linkedFromRoot = (node.parent === tree && !node.inert) || node.target.fsTop === tree
|
|
1251
1220
|
if (node.isLink && linkedFromRoot) {
|
|
1252
1221
|
nodes.push(node)
|
|
1253
1222
|
}
|
|
@@ -1358,7 +1327,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1358
1327
|
const alias = name !== pname
|
|
1359
1328
|
newSpec = alias ? `npm:${pname}@${range}` : range
|
|
1360
1329
|
} else if (req.hosted) {
|
|
1361
|
-
// save the git+https url if it has auth
|
|
1330
|
+
// save the git+https url if it has auth; otherwise, shortcut
|
|
1362
1331
|
const h = req.hosted
|
|
1363
1332
|
const opt = { noCommittish: false }
|
|
1364
1333
|
if (h.https && h.auth) {
|
|
@@ -1427,7 +1396,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1427
1396
|
|
|
1428
1397
|
// Returns true if any of the edges from this node has a semver
|
|
1429
1398
|
// range definition that is an exact match to the version installed
|
|
1430
|
-
// e.g: should return true if for a given
|
|
1399
|
+
// e.g: should return true if for a given and installed version 1.0.0,
|
|
1431
1400
|
// range is either =1.0.0 or 1.0.0
|
|
1432
1401
|
const exactVersion = node => {
|
|
1433
1402
|
for (const edge of node.edgesIn) {
|
|
@@ -1531,7 +1500,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1531
1500
|
|
|
1532
1501
|
// before now edge specs could be changing, affecting the `requires` field
|
|
1533
1502
|
// in the package lock, so we hold off saving to the very last action
|
|
1534
|
-
if (this
|
|
1503
|
+
if (this.options.usePackageLock) {
|
|
1535
1504
|
// preserve indentation, if possible
|
|
1536
1505
|
let format = this.idealTree.package[Symbol.for('indent')]
|
|
1537
1506
|
if (format === undefined) {
|
package/lib/calc-dep-flags.js
CHANGED
|
@@ -1,144 +1,102 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Dep flag (dev, peer, etc.) calculation requires default or reset flags.
|
|
2
|
+
// Flags are true by default and are unset to false as we walk deps.
|
|
3
|
+
// We iterate outward edges looking for dep flags that can
|
|
4
|
+
// be unset based on the current nodes flags and edge type.
|
|
5
|
+
// Examples:
|
|
6
|
+
// - a non-optional node with a non-optional edge out, the edge node should not be optional
|
|
7
|
+
// - a non-peer node with a non-peer edge out, the edge node should not be peer
|
|
8
|
+
// If a node is changed, we add to the queue and continue until no more changes.
|
|
9
|
+
// Flags that remain after all this unsetting should be valid.
|
|
10
|
+
// Examples:
|
|
11
|
+
// - a node still flagged optional must only be reachable via optional edges
|
|
12
|
+
// - a node still flagged peer must only be reachable via peer edges
|
|
3
13
|
const calcDepFlags = (tree, resetRoot = true) => {
|
|
4
14
|
if (resetRoot) {
|
|
5
|
-
tree.
|
|
6
|
-
tree.optional = false
|
|
7
|
-
tree.devOptional = false
|
|
8
|
-
tree.peer = false
|
|
15
|
+
tree.unsetDepFlags()
|
|
9
16
|
}
|
|
10
|
-
const ret = depth({
|
|
11
|
-
tree,
|
|
12
|
-
visit: node => calcDepFlagsStep(node),
|
|
13
|
-
filter: node => node,
|
|
14
|
-
getChildren: (node, tree) =>
|
|
15
|
-
[...tree.edgesOut.values()].map(edge => edge.to),
|
|
16
|
-
})
|
|
17
|
-
return ret
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const calcDepFlagsStep = (node) => {
|
|
21
|
-
// This rewalk is necessary to handle cases where devDep and optional
|
|
22
|
-
// or normal dependency graphs overlap deep in the dep graph.
|
|
23
|
-
// Since we're only walking through deps that are not already flagged
|
|
24
|
-
// as non-dev/non-optional, it's typically a very shallow traversal
|
|
25
|
-
|
|
26
|
-
node.extraneous = false
|
|
27
|
-
resetParents(node, 'extraneous')
|
|
28
|
-
resetParents(node, 'dev')
|
|
29
|
-
resetParents(node, 'peer')
|
|
30
|
-
resetParents(node, 'devOptional')
|
|
31
|
-
resetParents(node, 'optional')
|
|
32
|
-
|
|
33
|
-
// for links, map their hierarchy appropriately
|
|
34
|
-
if (node.isLink) {
|
|
35
|
-
// node.target can be null, we check to ensure it's not null before proceeding
|
|
36
|
-
if (node.target == null) {
|
|
37
|
-
return node
|
|
38
|
-
}
|
|
39
|
-
node.target.dev = node.dev
|
|
40
|
-
node.target.optional = node.optional
|
|
41
|
-
node.target.devOptional = node.devOptional
|
|
42
|
-
node.target.peer = node.peer
|
|
43
|
-
return calcDepFlagsStep(node.target)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
node.edgesOut.forEach(({ peer, optional, dev, to }) => {
|
|
47
|
-
// if the dep is missing, then its flags are already maximally unset
|
|
48
|
-
if (!to) {
|
|
49
|
-
return
|
|
50
|
-
}
|
|
51
|
-
// everything with any kind of edge into it is not extraneous
|
|
52
|
-
to.extraneous = false
|
|
53
|
-
|
|
54
|
-
// If this is a peer edge, mark the target as peer
|
|
55
|
-
if (peer) {
|
|
56
|
-
to.peer = true
|
|
57
|
-
} else if (to.peer && !hasIncomingPeerEdge(to)) {
|
|
58
|
-
unsetFlag(to, 'peer')
|
|
59
|
-
}
|
|
60
17
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// it set when we are in *either* tree, and then omit it from the
|
|
64
|
-
// package-lock if either dev or optional are set.
|
|
65
|
-
const unsetDevOpt = !node.devOptional && !node.dev && !node.optional && !dev && !optional
|
|
18
|
+
const seen = new Set()
|
|
19
|
+
const queue = [tree]
|
|
66
20
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const unsetOpt = unsetDevOpt || !node.optional && !optional
|
|
21
|
+
let node
|
|
22
|
+
while (node = queue.pop()) {
|
|
23
|
+
seen.add(node)
|
|
71
24
|
|
|
72
|
-
|
|
73
|
-
|
|
25
|
+
// Unset extraneous from all parents to avoid removal of children.
|
|
26
|
+
if (!node.extraneous) {
|
|
27
|
+
for (let n = node.resolveParent; n?.extraneous; n = n.resolveParent) {
|
|
28
|
+
n.extraneous = false
|
|
29
|
+
}
|
|
74
30
|
}
|
|
75
31
|
|
|
76
|
-
|
|
77
|
-
|
|
32
|
+
// for links, map their hierarchy appropriately
|
|
33
|
+
if (node.isLink) {
|
|
34
|
+
// node.target can be null, we check to ensure it's not null before proceeding
|
|
35
|
+
if (node.target == null) {
|
|
36
|
+
continue
|
|
37
|
+
}
|
|
38
|
+
node.target.dev = node.dev
|
|
39
|
+
node.target.optional = node.optional
|
|
40
|
+
node.target.devOptional = node.devOptional
|
|
41
|
+
node.target.peer = node.peer
|
|
42
|
+
node.target.extraneous = node.extraneous
|
|
43
|
+
queue.push(node.target)
|
|
44
|
+
continue
|
|
78
45
|
}
|
|
79
46
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
47
|
+
for (const { peer, optional, dev, to } of node.edgesOut.values()) {
|
|
48
|
+
// if the dep is missing, then its flags are already maximally unset
|
|
49
|
+
if (!to) {
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let changed = false
|
|
54
|
+
|
|
55
|
+
// only optional peer dependencies should stay extraneous
|
|
56
|
+
if (to.extraneous && !node.extraneous && !(peer && optional)) {
|
|
57
|
+
to.extraneous = false
|
|
58
|
+
changed = true
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (to.dev && !node.dev && !dev) {
|
|
62
|
+
to.dev = false
|
|
63
|
+
changed = true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (to.optional && !node.optional && !optional) {
|
|
67
|
+
to.optional = false
|
|
68
|
+
changed = true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// devOptional is the *overlap* of the dev and optional tree.
|
|
72
|
+
// A node may be depended on by separate dev and optional nodes.
|
|
73
|
+
// It SHOULD NOT be removed when pruning dev OR optional.
|
|
74
|
+
// It SHOULD be removed when pruning dev AND optional.
|
|
75
|
+
// We only unset here if a node is not dev AND not optional because
|
|
76
|
+
// if we did unset, it would prevent any overlap deeper in the tree.
|
|
77
|
+
// We correct this later by removing if dev OR optional is set.
|
|
78
|
+
if (to.devOptional && !node.devOptional && !node.dev && !node.optional && !dev && !optional) {
|
|
79
|
+
to.devOptional = false
|
|
80
|
+
changed = true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (to.peer && !node.peer && !peer) {
|
|
84
|
+
to.peer = false
|
|
85
|
+
changed = true
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (changed) {
|
|
89
|
+
queue.push(to)
|
|
90
|
+
}
|
|
93
91
|
}
|
|
94
92
|
}
|
|
95
|
-
return false
|
|
96
|
-
}
|
|
97
93
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
p[flag] = false
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// typically a short walk, since it only traverses deps that have the flag set.
|
|
109
|
-
const unsetFlag = (node, flag) => {
|
|
110
|
-
if (node[flag]) {
|
|
111
|
-
node[flag] = false
|
|
112
|
-
depth({
|
|
113
|
-
tree: node,
|
|
114
|
-
visit: node => {
|
|
115
|
-
node.extraneous = node[flag] = false
|
|
116
|
-
if (node.isLink && node.target) {
|
|
117
|
-
node.target.extraneous = node.target[flag] = false
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
getChildren: node => {
|
|
121
|
-
const children = []
|
|
122
|
-
const targetNode = node.isLink && node.target ? node.target : node
|
|
123
|
-
for (const edge of targetNode.edgesOut.values()) {
|
|
124
|
-
if (edge.to?.[flag]) {
|
|
125
|
-
// For the peer flag, only follow peer edges to unset the flag
|
|
126
|
-
// Don't propagate peer flag through prod/dev/optional edges
|
|
127
|
-
if (flag === 'peer') {
|
|
128
|
-
if (edge.type === 'peer') {
|
|
129
|
-
children.push(edge.to)
|
|
130
|
-
}
|
|
131
|
-
} else {
|
|
132
|
-
// For other flags, follow prod edges (and peer edges for non-peer flags)
|
|
133
|
-
if (edge.type === 'prod' || edge.type === 'peer') {
|
|
134
|
-
children.push(edge.to)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return children
|
|
140
|
-
},
|
|
141
|
-
})
|
|
94
|
+
// Remove incorrect devOptional flags now that we have walked all deps.
|
|
95
|
+
seen.delete(tree)
|
|
96
|
+
for (const node of seen.values()) {
|
|
97
|
+
if (node.devOptional && (node.dev || node.optional)) {
|
|
98
|
+
node.devOptional = false
|
|
99
|
+
}
|
|
142
100
|
}
|
|
143
101
|
}
|
|
144
102
|
|
package/lib/diff.js
CHANGED
|
@@ -11,9 +11,8 @@ const { existsSync } = require('node:fs')
|
|
|
11
11
|
const ssri = require('ssri')
|
|
12
12
|
|
|
13
13
|
class Diff {
|
|
14
|
-
constructor ({ actual, ideal, filterSet, shrinkwrapInflated, omit
|
|
14
|
+
constructor ({ actual, ideal, filterSet, shrinkwrapInflated, omit }) {
|
|
15
15
|
this.omit = omit
|
|
16
|
-
this.omitted = omitted
|
|
17
16
|
this.filterSet = filterSet
|
|
18
17
|
this.shrinkwrapInflated = shrinkwrapInflated
|
|
19
18
|
this.children = []
|
|
@@ -39,7 +38,6 @@ class Diff {
|
|
|
39
38
|
filterNodes = [],
|
|
40
39
|
shrinkwrapInflated = new Set(),
|
|
41
40
|
omit = new Set(),
|
|
42
|
-
omitted = new Set(),
|
|
43
41
|
}) {
|
|
44
42
|
// if there's a filterNode, then:
|
|
45
43
|
// - get the path from the root to the filterNode. The root or
|
|
@@ -98,28 +96,18 @@ class Diff {
|
|
|
98
96
|
}
|
|
99
97
|
|
|
100
98
|
return depth({
|
|
101
|
-
tree: new Diff({ actual, ideal, filterSet, shrinkwrapInflated, omit
|
|
99
|
+
tree: new Diff({ actual, ideal, filterSet, shrinkwrapInflated, omit }),
|
|
102
100
|
getChildren,
|
|
103
101
|
leave,
|
|
104
102
|
})
|
|
105
103
|
}
|
|
106
104
|
}
|
|
107
105
|
|
|
108
|
-
const getAction = ({ actual, ideal
|
|
106
|
+
const getAction = ({ actual, ideal }) => {
|
|
109
107
|
if (!ideal) {
|
|
110
108
|
return 'REMOVE'
|
|
111
109
|
}
|
|
112
110
|
|
|
113
|
-
if (ideal.shouldOmit?.(omit)) {
|
|
114
|
-
omitted.add(ideal)
|
|
115
|
-
|
|
116
|
-
if (actual) {
|
|
117
|
-
return 'REMOVE'
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return null
|
|
121
|
-
}
|
|
122
|
-
|
|
123
111
|
// bundled meta-deps are copied over to the ideal tree when we visit it,
|
|
124
112
|
// so they'll appear to be missing here. There's no need to handle them
|
|
125
113
|
// in the diff, though, because they'll be replaced at reify time anyway
|
|
@@ -199,7 +187,6 @@ const getChildren = diff => {
|
|
|
199
187
|
filterSet,
|
|
200
188
|
shrinkwrapInflated,
|
|
201
189
|
omit,
|
|
202
|
-
omitted,
|
|
203
190
|
} = diff
|
|
204
191
|
|
|
205
192
|
// Note: we DON'T diff fsChildren themselves, because they are either
|
|
@@ -231,7 +218,6 @@ const getChildren = diff => {
|
|
|
231
218
|
filterSet,
|
|
232
219
|
shrinkwrapInflated,
|
|
233
220
|
omit,
|
|
234
|
-
omitted,
|
|
235
221
|
})
|
|
236
222
|
}
|
|
237
223
|
|
|
@@ -251,13 +237,24 @@ const diffNode = ({
|
|
|
251
237
|
filterSet,
|
|
252
238
|
shrinkwrapInflated,
|
|
253
239
|
omit,
|
|
254
|
-
omitted,
|
|
255
240
|
}) => {
|
|
256
241
|
if (filterSet.size && !(filterSet.has(ideal) || filterSet.has(actual))) {
|
|
257
242
|
return
|
|
258
243
|
}
|
|
259
244
|
|
|
260
|
-
|
|
245
|
+
if (ideal?.shouldOmit?.(omit)) {
|
|
246
|
+
ideal.inert = true
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Treat inert nodes as undefined for the purposes of diffing.
|
|
250
|
+
if (ideal?.inert) {
|
|
251
|
+
ideal = undefined
|
|
252
|
+
}
|
|
253
|
+
if (!actual && !ideal) {
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const action = getAction({ actual, ideal })
|
|
261
258
|
|
|
262
259
|
// if it's a match, then get its children
|
|
263
260
|
// otherwise, this is the child diff node
|
|
@@ -265,7 +262,7 @@ const diffNode = ({
|
|
|
265
262
|
if (action === 'REMOVE') {
|
|
266
263
|
removed.push(actual)
|
|
267
264
|
}
|
|
268
|
-
children.push(new Diff({ actual, ideal, filterSet, shrinkwrapInflated, omit
|
|
265
|
+
children.push(new Diff({ actual, ideal, filterSet, shrinkwrapInflated, omit }))
|
|
269
266
|
} else {
|
|
270
267
|
unchanged.push(ideal)
|
|
271
268
|
// !*! Weird dirty hack warning !*!
|
|
@@ -306,7 +303,6 @@ const diffNode = ({
|
|
|
306
303
|
filterSet,
|
|
307
304
|
shrinkwrapInflated,
|
|
308
305
|
omit,
|
|
309
|
-
omitted,
|
|
310
306
|
}))
|
|
311
307
|
}
|
|
312
308
|
}
|
package/lib/edge.js
CHANGED
|
@@ -276,9 +276,15 @@ class Edge {
|
|
|
276
276
|
} else if (!this.satisfiedBy(this.#to)) {
|
|
277
277
|
this.#error = 'INVALID'
|
|
278
278
|
} else if (this.overrides && this.#to.edgesOut.size && OverrideSet.doOverrideSetsConflict(this.overrides, this.#to.overrides)) {
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
//
|
|
279
|
+
// Check for conflicts between the edge's override set and the target node's override set.
|
|
280
|
+
// This catches cases where different parts of the tree have genuinely incompatible
|
|
281
|
+
// version requirements for the same package.
|
|
282
|
+
// The improved conflict detection uses semantic comparison (checking for incompatible
|
|
283
|
+
// version ranges) rather than pure structural equality, avoiding false positives from:
|
|
284
|
+
// - Reference overrides ($syntax) that resolve to compatible versions
|
|
285
|
+
// - Peer dependencies with different but compatible override contexts
|
|
286
|
+
// Note: We only check if the target has dependencies (edgesOut.size > 0), since
|
|
287
|
+
// override conflicts are only relevant if the target has its own dependencies.
|
|
282
288
|
this.#error = 'INVALID'
|
|
283
289
|
} else {
|
|
284
290
|
this.#error = 'OK'
|
package/lib/gather-dep-set.js
CHANGED
|
@@ -20,7 +20,7 @@ const gatherDepSet = (set, edgeFilter) => {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
// now remove all nodes in the set that have a
|
|
23
|
+
// now remove all nodes in the set that have a dependent outside the set
|
|
24
24
|
// if any change is made, then re-check
|
|
25
25
|
// continue until no changes made, or deps set evaporates fully.
|
|
26
26
|
let changed = true
|
package/lib/node.js
CHANGED
|
@@ -101,7 +101,7 @@ class Node {
|
|
|
101
101
|
global = false,
|
|
102
102
|
dummy = false,
|
|
103
103
|
sourceReference = null,
|
|
104
|
-
|
|
104
|
+
inert = false,
|
|
105
105
|
} = options
|
|
106
106
|
// this object gives querySelectorAll somewhere to stash context about a node
|
|
107
107
|
// while processing a query
|
|
@@ -207,7 +207,7 @@ class Node {
|
|
|
207
207
|
this.extraneous = false
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
this.
|
|
210
|
+
this.inert = inert
|
|
211
211
|
|
|
212
212
|
this.edgesIn = new Set()
|
|
213
213
|
this.edgesOut = new CaseInsensitiveMap()
|
|
@@ -248,7 +248,7 @@ class Node {
|
|
|
248
248
|
this.fsParent = fsParent || null
|
|
249
249
|
|
|
250
250
|
// see parent/root setters below.
|
|
251
|
-
// root is set to parent's root if we have a parent
|
|
251
|
+
// root is set to parent's root if we have a parent; otherwise, if it's
|
|
252
252
|
// null, then it's set to the node itself.
|
|
253
253
|
if (!parent && !fsParent) {
|
|
254
254
|
this.root = root || null
|
|
@@ -832,7 +832,7 @@ class Node {
|
|
|
832
832
|
edge.reload()
|
|
833
833
|
}
|
|
834
834
|
}
|
|
835
|
-
// reload all edgesOut where root
|
|
835
|
+
// reload all edgesOut where root doesn't match, or is missing, since
|
|
836
836
|
// it might not be missing in the new tree
|
|
837
837
|
for (const edge of this.edgesOut.values()) {
|
|
838
838
|
if (!edge.to || edge.to.root !== root) {
|
|
@@ -1268,7 +1268,7 @@ class Node {
|
|
|
1268
1268
|
// with another by the same name (eg, to update or dedupe).
|
|
1269
1269
|
// This does a couple of walks out on the node_modules tree, recursing
|
|
1270
1270
|
// into child nodes. However, as setting the parent is typically done
|
|
1271
|
-
// with nodes that don't have
|
|
1271
|
+
// with nodes that don't have many children, and (deduped) package
|
|
1272
1272
|
// trees tend to be broad rather than deep, it's not that bad.
|
|
1273
1273
|
// The only walk that starts from the parent rather than this node is
|
|
1274
1274
|
// limited by edge name.
|
|
@@ -1412,7 +1412,7 @@ class Node {
|
|
|
1412
1412
|
}
|
|
1413
1413
|
|
|
1414
1414
|
recalculateOutEdgesOverrides () {
|
|
1415
|
-
// For each edge out
|
|
1415
|
+
// For each edge out propagate the new overrides through.
|
|
1416
1416
|
for (const edge of this.edgesOut.values()) {
|
|
1417
1417
|
edge.reload(true)
|
|
1418
1418
|
if (edge.to) {
|
|
@@ -1613,6 +1613,22 @@ class Node {
|
|
|
1613
1613
|
[util.inspect.custom] () {
|
|
1614
1614
|
return this.toJSON()
|
|
1615
1615
|
}
|
|
1616
|
+
|
|
1617
|
+
resetDepFlags () {
|
|
1618
|
+
this.extraneous = true
|
|
1619
|
+
this.dev = true
|
|
1620
|
+
this.optional = true
|
|
1621
|
+
this.devOptional = true
|
|
1622
|
+
this.peer = true
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
unsetDepFlags () {
|
|
1626
|
+
this.extraneous = false
|
|
1627
|
+
this.dev = false
|
|
1628
|
+
this.optional = false
|
|
1629
|
+
this.devOptional = false
|
|
1630
|
+
this.peer = false
|
|
1631
|
+
}
|
|
1616
1632
|
}
|
|
1617
1633
|
|
|
1618
1634
|
module.exports = Node
|