@npmcli/arborist 9.0.1 → 9.0.2
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 +48 -10
- package/lib/arborist/isolated-reifier.js +3 -3
- package/lib/arborist/load-virtual.js +1 -0
- package/lib/arborist/reify.js +39 -15
- package/lib/edge.js +26 -12
- package/lib/node.js +9 -1
- package/lib/place-dep.js +1 -1
- package/lib/shrinkwrap.js +16 -1
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@ const { lstat, readlink } = require('node:fs/promises')
|
|
|
13
13
|
const { depth } = require('treeverse')
|
|
14
14
|
const { log, time } = require('proc-log')
|
|
15
15
|
const { redact } = require('@npmcli/redact')
|
|
16
|
+
const semver = require('semver')
|
|
16
17
|
|
|
17
18
|
const {
|
|
18
19
|
OK,
|
|
@@ -279,14 +280,23 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
279
280
|
// When updating all, we load the shrinkwrap, but don't bother
|
|
280
281
|
// to build out the full virtual tree from it, since we'll be
|
|
281
282
|
// reconstructing it anyway.
|
|
282
|
-
.then(root =>
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
283
|
+
.then(root => {
|
|
284
|
+
if (this.options.global) {
|
|
285
|
+
return root
|
|
286
|
+
} else if (!this[_usePackageLock] || this[_updateAll]) {
|
|
287
|
+
return Shrinkwrap.reset({
|
|
288
|
+
path: this.path,
|
|
289
|
+
lockfileVersion: this.options.lockfileVersion,
|
|
290
|
+
resolveOptions: this.options,
|
|
291
|
+
}).then(meta => Object.assign(root, { meta }))
|
|
292
|
+
} else {
|
|
293
|
+
return this.loadVirtual({ root })
|
|
294
|
+
.then(tree => {
|
|
295
|
+
this.#applyRootOverridesToWorkspaces(tree)
|
|
296
|
+
return tree
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
})
|
|
290
300
|
|
|
291
301
|
// if we don't have a lockfile to go from, then start with the
|
|
292
302
|
// actual tree, so we only make the minimum required changes.
|
|
@@ -799,7 +809,8 @@ This is a one-time fix-up, please be patient...
|
|
|
799
809
|
const crackOpen = this.#complete &&
|
|
800
810
|
node !== this.idealTree &&
|
|
801
811
|
node.resolved &&
|
|
802
|
-
(hasBundle || hasShrinkwrap)
|
|
812
|
+
(hasBundle || hasShrinkwrap) &&
|
|
813
|
+
!node.ideallyInert
|
|
803
814
|
if (crackOpen) {
|
|
804
815
|
const Arborist = this.constructor
|
|
805
816
|
const opt = { ...this.options }
|
|
@@ -1475,6 +1486,32 @@ This is a one-time fix-up, please be patient...
|
|
|
1475
1486
|
timeEnd()
|
|
1476
1487
|
}
|
|
1477
1488
|
|
|
1489
|
+
#applyRootOverridesToWorkspaces (tree) {
|
|
1490
|
+
const rootOverrides = tree.root.package.overrides || {}
|
|
1491
|
+
|
|
1492
|
+
for (const node of tree.root.inventory.values()) {
|
|
1493
|
+
if (!node.isWorkspace) {
|
|
1494
|
+
continue
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
for (const depName of Object.keys(rootOverrides)) {
|
|
1498
|
+
const edge = node.edgesOut.get(depName)
|
|
1499
|
+
const rootNode = tree.root.children.get(depName)
|
|
1500
|
+
|
|
1501
|
+
// safely skip if either edge or rootNode doesn't exist yet
|
|
1502
|
+
if (!edge || !rootNode) {
|
|
1503
|
+
continue
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
const resolvedRootVersion = rootNode.package.version
|
|
1507
|
+
if (!semver.satisfies(resolvedRootVersion, edge.spec)) {
|
|
1508
|
+
edge.detach()
|
|
1509
|
+
node.children.delete(depName)
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1478
1515
|
#idealTreePrune () {
|
|
1479
1516
|
for (const node of this.idealTree.inventory.values()) {
|
|
1480
1517
|
if (node.extraneous) {
|
|
@@ -1491,7 +1528,7 @@ This is a one-time fix-up, please be patient...
|
|
|
1491
1528
|
|
|
1492
1529
|
const set = optionalSet(node)
|
|
1493
1530
|
for (const node of set) {
|
|
1494
|
-
node.
|
|
1531
|
+
node.ideallyInert = true
|
|
1495
1532
|
}
|
|
1496
1533
|
}
|
|
1497
1534
|
}
|
|
@@ -1512,6 +1549,7 @@ This is a one-time fix-up, please be patient...
|
|
|
1512
1549
|
node.parent !== null
|
|
1513
1550
|
&& !node.isProjectRoot
|
|
1514
1551
|
&& !excludeNodes.has(node)
|
|
1552
|
+
&& !node.ideallyInert
|
|
1515
1553
|
) {
|
|
1516
1554
|
this[_addNodeToTrashList](node)
|
|
1517
1555
|
}
|
|
@@ -81,7 +81,7 @@ module.exports = cls => class IsolatedReifier extends cls {
|
|
|
81
81
|
}
|
|
82
82
|
queue.push(e.to)
|
|
83
83
|
})
|
|
84
|
-
if (!next.isProjectRoot && !next.isWorkspace) {
|
|
84
|
+
if (!next.isProjectRoot && !next.isWorkspace && !next.ideallyInert) {
|
|
85
85
|
root.external.push(await this.externalProxyMemo(next))
|
|
86
86
|
}
|
|
87
87
|
}
|
|
@@ -147,8 +147,8 @@ module.exports = cls => class IsolatedReifier extends cls {
|
|
|
147
147
|
const nonOptionalDeps = edges.filter(e => !e.optional).map(e => e.to.target)
|
|
148
148
|
|
|
149
149
|
result.localDependencies = await Promise.all(nonOptionalDeps.filter(n => n.isWorkspace).map(this.workspaceProxyMemo))
|
|
150
|
-
result.externalDependencies = await Promise.all(nonOptionalDeps.filter(n => !n.isWorkspace).map(this.externalProxyMemo))
|
|
151
|
-
result.externalOptionalDependencies = await Promise.all(optionalDeps.map(this.externalProxyMemo))
|
|
150
|
+
result.externalDependencies = await Promise.all(nonOptionalDeps.filter(n => !n.isWorkspace && !n.ideallyInert).map(this.externalProxyMemo))
|
|
151
|
+
result.externalOptionalDependencies = await Promise.all(optionalDeps.filter(n => !n.ideallyInert).map(this.externalProxyMemo))
|
|
152
152
|
result.dependencies = [
|
|
153
153
|
...result.externalDependencies,
|
|
154
154
|
...result.localDependencies,
|
|
@@ -267,6 +267,7 @@ module.exports = cls => class VirtualLoader extends cls {
|
|
|
267
267
|
integrity: sw.integrity,
|
|
268
268
|
resolved: consistentResolve(sw.resolved, this.path, path),
|
|
269
269
|
pkg: sw,
|
|
270
|
+
ideallyInert: sw.ideallyInert,
|
|
270
271
|
hasShrinkwrap: sw.hasShrinkwrap,
|
|
271
272
|
dev,
|
|
272
273
|
optional,
|
package/lib/arborist/reify.js
CHANGED
|
@@ -8,7 +8,6 @@ const semver = require('semver')
|
|
|
8
8
|
const debug = require('../debug.js')
|
|
9
9
|
const { walkUp } = require('walk-up-path')
|
|
10
10
|
const { log, time } = require('proc-log')
|
|
11
|
-
const hgi = require('hosted-git-info')
|
|
12
11
|
const rpj = require('read-package-json-fast')
|
|
13
12
|
|
|
14
13
|
const { dirname, resolve, relative, join } = require('node:path')
|
|
@@ -150,7 +149,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
150
149
|
for (const path of this[_trashList]) {
|
|
151
150
|
const loc = relpath(this.idealTree.realpath, path)
|
|
152
151
|
const node = this.idealTree.inventory.get(loc)
|
|
153
|
-
if (node && node.root === this.idealTree) {
|
|
152
|
+
if (node && node.root === this.idealTree && !node.ideallyInert) {
|
|
154
153
|
node.parent = null
|
|
155
154
|
}
|
|
156
155
|
}
|
|
@@ -238,6 +237,18 @@ module.exports = cls => class Reifier extends cls {
|
|
|
238
237
|
this.idealTree.meta.hiddenLockfile = true
|
|
239
238
|
this.idealTree.meta.lockfileVersion = defaultLockfileVersion
|
|
240
239
|
|
|
240
|
+
// Preserve inertness for failed stuff.
|
|
241
|
+
if (this.actualTree) {
|
|
242
|
+
for (const [loc, actual] of this.actualTree.inventory.entries()) {
|
|
243
|
+
if (actual.ideallyInert) {
|
|
244
|
+
const ideal = this.idealTree.inventory.get(loc)
|
|
245
|
+
if (ideal) {
|
|
246
|
+
ideal.ideallyInert = true
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
241
252
|
this.actualTree = this.idealTree
|
|
242
253
|
this.idealTree = null
|
|
243
254
|
|
|
@@ -600,6 +611,9 @@ module.exports = cls => class Reifier extends cls {
|
|
|
600
611
|
// retire the same path at the same time.
|
|
601
612
|
const dirsChecked = new Set()
|
|
602
613
|
return promiseAllRejectLate(leaves.map(async node => {
|
|
614
|
+
if (node.ideallyInert) {
|
|
615
|
+
return
|
|
616
|
+
}
|
|
603
617
|
for (const d of walkUp(node.path)) {
|
|
604
618
|
if (d === node.top.path) {
|
|
605
619
|
break
|
|
@@ -744,6 +758,10 @@ module.exports = cls => class Reifier extends cls {
|
|
|
744
758
|
}
|
|
745
759
|
|
|
746
760
|
async #extractOrLink (node) {
|
|
761
|
+
if (node.ideallyInert) {
|
|
762
|
+
return
|
|
763
|
+
}
|
|
764
|
+
|
|
747
765
|
const nm = resolve(node.parent.path, 'node_modules')
|
|
748
766
|
await this.#validateNodeModules(nm)
|
|
749
767
|
|
|
@@ -819,6 +837,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
819
837
|
const set = optionalSet(node)
|
|
820
838
|
for (node of set) {
|
|
821
839
|
log.verbose('reify', 'failed optional dependency', node.path)
|
|
840
|
+
node.ideallyInert = true
|
|
822
841
|
this[_addNodeToTrashList](node)
|
|
823
842
|
}
|
|
824
843
|
}) : p).then(() => node)
|
|
@@ -833,21 +852,23 @@ module.exports = cls => class Reifier extends cls {
|
|
|
833
852
|
// ${REGISTRY} or something. This has to be threaded through the
|
|
834
853
|
// Shrinkwrap and Node classes carefully, so for now, just treat
|
|
835
854
|
// the default reg as the magical animal that it has been.
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
855
|
+
try {
|
|
856
|
+
const resolvedURL = new URL(resolved)
|
|
857
|
+
|
|
858
|
+
if ((this.options.replaceRegistryHost === resolvedURL.hostname) ||
|
|
859
|
+
this.options.replaceRegistryHost === 'always') {
|
|
860
|
+
const registryURL = new URL(this.registry)
|
|
861
|
+
// Replace the host with the registry host while keeping the path intact
|
|
862
|
+
resolvedURL.hostname = registryURL.hostname
|
|
863
|
+
resolvedURL.port = registryURL.port
|
|
864
|
+
return resolvedURL.toString()
|
|
865
|
+
}
|
|
866
|
+
return resolved
|
|
867
|
+
} catch (e) {
|
|
839
868
|
// if we could not parse the url at all then returning nothing
|
|
840
869
|
// here means it will get removed from the tree in the next step
|
|
841
|
-
return
|
|
870
|
+
return undefined
|
|
842
871
|
}
|
|
843
|
-
|
|
844
|
-
if ((this.options.replaceRegistryHost === resolvedURL.hostname)
|
|
845
|
-
|| this.options.replaceRegistryHost === 'always') {
|
|
846
|
-
// this.registry always has a trailing slash
|
|
847
|
-
return `${this.registry.slice(0, -1)}${resolvedURL.pathname}${resolvedURL.searchParams}`
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
return resolved
|
|
851
872
|
}
|
|
852
873
|
|
|
853
874
|
// bundles are *sort of* like shrinkwraps, in that the branch is defined
|
|
@@ -1151,6 +1172,9 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1151
1172
|
|
|
1152
1173
|
this.#retiredUnchanged[retireFolder] = []
|
|
1153
1174
|
return promiseAllRejectLate(diff.unchanged.map(node => {
|
|
1175
|
+
if (node.ideallyInert) {
|
|
1176
|
+
return
|
|
1177
|
+
}
|
|
1154
1178
|
// no need to roll back links, since we'll just delete them anyway
|
|
1155
1179
|
if (node.isLink) {
|
|
1156
1180
|
return mkdir(dirname(node.path), { recursive: true, force: true })
|
|
@@ -1230,7 +1254,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1230
1254
|
// skip links that only live within node_modules as they are most
|
|
1231
1255
|
// likely managed by packages we installed, we only want to rebuild
|
|
1232
1256
|
// unchanged links we directly manage
|
|
1233
|
-
const linkedFromRoot = node.parent === tree || node.target.fsTop === tree
|
|
1257
|
+
const linkedFromRoot = (node.parent === tree && !node.ideallyInert) || node.target.fsTop === tree
|
|
1234
1258
|
if (node.isLink && linkedFromRoot) {
|
|
1235
1259
|
nodes.push(node)
|
|
1236
1260
|
}
|
package/lib/edge.js
CHANGED
|
@@ -206,22 +206,21 @@ class Edge {
|
|
|
206
206
|
if (this.overrides?.value && this.overrides.value !== '*' && this.overrides.name === this.#name) {
|
|
207
207
|
if (this.overrides.value.startsWith('$')) {
|
|
208
208
|
const ref = this.overrides.value.slice(1)
|
|
209
|
-
|
|
209
|
+
let pkg = this.#from?.sourceReference
|
|
210
210
|
? this.#from?.sourceReference.root.package
|
|
211
211
|
: this.#from?.root?.package
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
return pkg.dependencies[ref]
|
|
220
|
-
}
|
|
221
|
-
if (pkg.peerDependencies?.[ref]) {
|
|
222
|
-
return pkg.peerDependencies[ref]
|
|
212
|
+
|
|
213
|
+
let specValue = this.#calculateReferentialOverrideSpec(ref, pkg)
|
|
214
|
+
|
|
215
|
+
// If the package isn't found in the root package, fall back to the local package.
|
|
216
|
+
if (!specValue) {
|
|
217
|
+
pkg = this.#from?.package
|
|
218
|
+
specValue = this.#calculateReferentialOverrideSpec(ref, pkg)
|
|
223
219
|
}
|
|
224
220
|
|
|
221
|
+
if (specValue) {
|
|
222
|
+
return specValue
|
|
223
|
+
}
|
|
225
224
|
throw new Error(`Unable to resolve reference ${this.overrides.value}`)
|
|
226
225
|
}
|
|
227
226
|
return this.overrides.value
|
|
@@ -229,6 +228,21 @@ class Edge {
|
|
|
229
228
|
return this.#spec
|
|
230
229
|
}
|
|
231
230
|
|
|
231
|
+
#calculateReferentialOverrideSpec (ref, pkg) {
|
|
232
|
+
if (pkg.devDependencies?.[ref]) {
|
|
233
|
+
return pkg.devDependencies[ref]
|
|
234
|
+
}
|
|
235
|
+
if (pkg.optionalDependencies?.[ref]) {
|
|
236
|
+
return pkg.optionalDependencies[ref]
|
|
237
|
+
}
|
|
238
|
+
if (pkg.dependencies?.[ref]) {
|
|
239
|
+
return pkg.dependencies[ref]
|
|
240
|
+
}
|
|
241
|
+
if (pkg.peerDependencies?.[ref]) {
|
|
242
|
+
return pkg.peerDependencies[ref]
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
232
246
|
get accept () {
|
|
233
247
|
return this.#accept
|
|
234
248
|
}
|
package/lib/node.js
CHANGED
|
@@ -103,6 +103,7 @@ class Node {
|
|
|
103
103
|
global = false,
|
|
104
104
|
dummy = false,
|
|
105
105
|
sourceReference = null,
|
|
106
|
+
ideallyInert = false,
|
|
106
107
|
} = options
|
|
107
108
|
// this object gives querySelectorAll somewhere to stash context about a node
|
|
108
109
|
// while processing a query
|
|
@@ -197,6 +198,8 @@ class Node {
|
|
|
197
198
|
this.extraneous = false
|
|
198
199
|
}
|
|
199
200
|
|
|
201
|
+
this.ideallyInert = ideallyInert
|
|
202
|
+
|
|
200
203
|
this.edgesIn = new Set()
|
|
201
204
|
this.edgesOut = new CaseInsensitiveMap()
|
|
202
205
|
|
|
@@ -1074,7 +1077,7 @@ class Node {
|
|
|
1074
1077
|
// return true if it's safe to remove this node, because anything that
|
|
1075
1078
|
// is depending on it would be fine with the thing that they would resolve
|
|
1076
1079
|
// to if it was removed, or nothing is depending on it in the first place.
|
|
1077
|
-
canDedupe (preferDedupe = false) {
|
|
1080
|
+
canDedupe (preferDedupe = false, explicitRequest = false) {
|
|
1078
1081
|
// not allowed to mess with shrinkwraps or bundles
|
|
1079
1082
|
if (this.inDepBundle || this.inShrinkwrap) {
|
|
1080
1083
|
return false
|
|
@@ -1117,6 +1120,11 @@ class Node {
|
|
|
1117
1120
|
return true
|
|
1118
1121
|
}
|
|
1119
1122
|
|
|
1123
|
+
// if the other version was an explicit request, then prefer to take the other version
|
|
1124
|
+
if (explicitRequest) {
|
|
1125
|
+
return true
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1120
1128
|
return false
|
|
1121
1129
|
}
|
|
1122
1130
|
|
package/lib/place-dep.js
CHANGED
|
@@ -423,7 +423,7 @@ class PlaceDep {
|
|
|
423
423
|
// is another satisfying node further up the tree, and if so, dedupes.
|
|
424
424
|
// Even in installStategy is nested, we do this amount of deduplication.
|
|
425
425
|
pruneDedupable (node, descend = true) {
|
|
426
|
-
if (node.canDedupe(this.preferDedupe)) {
|
|
426
|
+
if (node.canDedupe(this.preferDedupe, this.explicitRequest)) {
|
|
427
427
|
// gather up all deps that have no valid edges in from outside
|
|
428
428
|
// the dep set, except for this node we're deduping, so that we
|
|
429
429
|
// also prune deps that would be made extraneous.
|
package/lib/shrinkwrap.js
CHANGED
|
@@ -109,6 +109,7 @@ const nodeMetaKeys = [
|
|
|
109
109
|
'inBundle',
|
|
110
110
|
'hasShrinkwrap',
|
|
111
111
|
'hasInstallScript',
|
|
112
|
+
'ideallyInert',
|
|
112
113
|
]
|
|
113
114
|
|
|
114
115
|
const metaFieldFromPkg = (pkg, key) => {
|
|
@@ -135,6 +136,10 @@ const assertNoNewer = async (path, data, lockTime, dir, seen) => {
|
|
|
135
136
|
|
|
136
137
|
const parent = isParent ? dir : resolve(dir, 'node_modules')
|
|
137
138
|
const rel = relpath(path, dir)
|
|
139
|
+
const inert = data.packages[rel]?.ideallyInert
|
|
140
|
+
if (inert) {
|
|
141
|
+
return
|
|
142
|
+
}
|
|
138
143
|
seen.add(rel)
|
|
139
144
|
let entries
|
|
140
145
|
if (dir === path) {
|
|
@@ -173,7 +178,7 @@ const assertNoNewer = async (path, data, lockTime, dir, seen) => {
|
|
|
173
178
|
|
|
174
179
|
// assert that all the entries in the lockfile were seen
|
|
175
180
|
for (const loc in data.packages) {
|
|
176
|
-
if (!seen.has(loc)) {
|
|
181
|
+
if (!seen.has(loc) && !data.packages[loc].ideallyInert) {
|
|
177
182
|
throw new Error(`missing from node_modules: ${loc}`)
|
|
178
183
|
}
|
|
179
184
|
}
|
|
@@ -783,6 +788,10 @@ class Shrinkwrap {
|
|
|
783
788
|
// ok, I did my best! good luck!
|
|
784
789
|
}
|
|
785
790
|
|
|
791
|
+
if (lock.ideallyInert) {
|
|
792
|
+
meta.ideallyInert = true
|
|
793
|
+
}
|
|
794
|
+
|
|
786
795
|
if (lock.bundled) {
|
|
787
796
|
meta.inBundle = true
|
|
788
797
|
}
|
|
@@ -953,6 +962,12 @@ class Shrinkwrap {
|
|
|
953
962
|
this.#buildLegacyLockfile(this.tree, this.data)
|
|
954
963
|
}
|
|
955
964
|
|
|
965
|
+
if (!this.hiddenLockfile) {
|
|
966
|
+
for (const node of Object.values(this.data.packages)) {
|
|
967
|
+
delete node.ideallyInert
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
956
971
|
// lf version 1 = dependencies only
|
|
957
972
|
// lf version 2 = dependencies and packages
|
|
958
973
|
// lf version 3 = packages only
|