@npmcli/arborist 9.0.1 → 9.1.0
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 +13 -47
- package/lib/arborist/build-ideal-tree.js +48 -10
- package/lib/arborist/isolated-reifier.js +3 -3
- package/lib/arborist/load-virtual.js +13 -0
- package/lib/arborist/reify.js +86 -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
package/README.md
CHANGED
|
@@ -267,53 +267,19 @@ are updated by arborist when necessary whenever the tree is modified in
|
|
|
267
267
|
such a way that the dependency graph can change, and are relevant when
|
|
268
268
|
pruning nodes from the tree.
|
|
269
269
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
|
274
|
-
|
|
275
|
-
| X
|
|
276
|
-
| | | |
|
|
277
|
-
|
|
278
|
-
| |
|
|
279
|
-
| |
|
|
280
|
-
| |
|
|
281
|
-
|
|
282
|
-
| |
|
|
283
|
-
| | | | | not in lock | or only depended on | optional |
|
|
284
|
-
| | | | | | by optionalDeps | |
|
|
285
|
-
|------------+------+-----+----------+-------------+---------------------+-------------------|
|
|
286
|
-
| | | X | X | X | Optional dependency | if pruning EITHER |
|
|
287
|
-
| | | | | not in lock | of dep(s) in the | dev OR optional |
|
|
288
|
-
| | | | | | dev hierarchy | |
|
|
289
|
-
|------------+------+-----+----------+-------------+---------------------+-------------------|
|
|
290
|
-
| | | | | X | BOTH a non-optional | if pruning BOTH |
|
|
291
|
-
| | | | | in lock | dep within the dev | dev AND optional |
|
|
292
|
-
| | | | | | hierarchy, AND a | |
|
|
293
|
-
| | | | | | dep within the | |
|
|
294
|
-
| | | | | | optional hierarchy | |
|
|
295
|
-
|------------+------+-----+----------+-------------+---------------------+-------------------|
|
|
296
|
-
| | X | | | | peer dependency, or | if pruning peers |
|
|
297
|
-
| | | | | | only depended on by | |
|
|
298
|
-
| | | | | | peer dependencies | |
|
|
299
|
-
|------------+------+-----+----------+-------------+---------------------+-------------------|
|
|
300
|
-
| | X | X | | X | peer dependency of | if pruning peer |
|
|
301
|
-
| | | | | not in lock | dev node hierarchy | OR dev deps |
|
|
302
|
-
|------------+------+-----+----------+-------------+---------------------+-------------------|
|
|
303
|
-
| | X | | X | X | peer dependency of | if pruning peer |
|
|
304
|
-
| | | | | not in lock | optional nodes, or | OR optional deps |
|
|
305
|
-
| | | | | | peerOptional dep | |
|
|
306
|
-
|------------+------+-----+----------+-------------+---------------------+-------------------|
|
|
307
|
-
| | X | X | X | X | peer optional deps | if pruning peer |
|
|
308
|
-
| | | | | not in lock | of the dev dep | OR optional OR |
|
|
309
|
-
| | | | | | hierarchy | dev |
|
|
310
|
-
|------------+------+-----+----------+-------------+---------------------+-------------------|
|
|
311
|
-
| | X | | | X | BOTH a non-optional | if pruning peers |
|
|
312
|
-
| | | | | in lock | peer dep within the | OR: |
|
|
313
|
-
| | | | | | dev hierarchy, AND | BOTH optional |
|
|
314
|
-
| | | | | | a peer optional dep | AND dev deps |
|
|
315
|
-
+------------+------+-----+----------+-------------+---------------------+-------------------+
|
|
316
|
-
```
|
|
270
|
+
| extraneous | peer | dev | optional | devOptional | meaning | prune? |
|
|
271
|
+
|:----------:|:----:|:---:|:--------:|:----------------:|:-------------------------------------------------------------------------------------------------|:-------------------------------------------------------|
|
|
272
|
+
| | | | | | production dep | never |
|
|
273
|
+
| X | N/A | N/A | N/A | N/A | nothing depends on this, it is trash | always |
|
|
274
|
+
| | | X | | X<br>not in lock | devDependency, or only depended<br>on by devDependencies | if pruning dev |
|
|
275
|
+
| | | | X | X<br>not in lock | optionalDependency, or only depended<br>on by optionalDeps | if pruning optional |
|
|
276
|
+
| | | X | X | X<br>not in lock | Optional dependency of dep(s) in the<br>dev hierarchy | if pruning EITHER<br>dev OR optional |
|
|
277
|
+
| | | | | X<br>in lock | BOTH a non-optional dep within the<br>dev hierarchy, AND a dep within<br>the optional hierarchy | if pruning BOTH<br>dev AND optional |
|
|
278
|
+
| | X | | | | peer dependency, or only depended<br>on by peer dependencies | if pruning peers |
|
|
279
|
+
| | X | X | | X<br>not in lock | peer dependency of dev node hierarchy | if pruning peer OR<br>dev deps |
|
|
280
|
+
| | X | | X | X<br>not in lock | peer dependency of optional nodes, or<br>peerOptional dep | if pruning peer OR<br>optional deps |
|
|
281
|
+
| | X | X | X | X<br>not in lock | peer optional deps of the dev dep hierarchy | if pruning peer OR<br>optional OR dev |
|
|
282
|
+
| | X | | | X<br>in lock | BOTH a non-optional peer dep within the<br>dev hierarchy, AND a peer optional dep | if pruning peer deps OR:<br>BOTH optional AND dev deps |
|
|
317
283
|
|
|
318
284
|
* If none of these flags are set, then the node is required by the
|
|
319
285
|
dependency and/or peerDependency hierarchy. It should not be pruned.
|
|
@@ -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,
|
|
@@ -200,6 +200,18 @@ module.exports = cls => class VirtualLoader extends cls {
|
|
|
200
200
|
const targetPath = resolve(this.path, meta.resolved)
|
|
201
201
|
const targetLoc = relpath(this.path, targetPath)
|
|
202
202
|
const target = nodes.get(targetLoc)
|
|
203
|
+
|
|
204
|
+
if (!target) {
|
|
205
|
+
const err = new Error(
|
|
206
|
+
`Missing target in lock file: "${targetLoc}" is referenced by "${location}" but does not exist.
|
|
207
|
+
To fix:
|
|
208
|
+
1. rm package-lock.json
|
|
209
|
+
2. npm install`
|
|
210
|
+
)
|
|
211
|
+
err.code = 'EMISSINGTARGET'
|
|
212
|
+
throw err
|
|
213
|
+
}
|
|
214
|
+
|
|
203
215
|
const link = this.#loadLink(location, targetLoc, target, meta)
|
|
204
216
|
nodes.set(location, link)
|
|
205
217
|
nodes.set(targetLoc, link.target)
|
|
@@ -267,6 +279,7 @@ module.exports = cls => class VirtualLoader extends cls {
|
|
|
267
279
|
integrity: sw.integrity,
|
|
268
280
|
resolved: consistentResolve(sw.resolved, this.path, path),
|
|
269
281
|
pkg: sw,
|
|
282
|
+
ideallyInert: sw.ideallyInert,
|
|
270
283
|
hasShrinkwrap: sw.hasShrinkwrap,
|
|
271
284
|
dev,
|
|
272
285
|
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
|
|
|
@@ -807,7 +825,14 @@ module.exports = cls => class Reifier extends cls {
|
|
|
807
825
|
// symlink
|
|
808
826
|
const dir = dirname(node.path)
|
|
809
827
|
const target = node.realpath
|
|
810
|
-
|
|
828
|
+
|
|
829
|
+
let rel
|
|
830
|
+
if (node.resolved?.startsWith('file:')) {
|
|
831
|
+
rel = this.#calculateRelativePath(node, dir, target, nm)
|
|
832
|
+
} else {
|
|
833
|
+
rel = relative(dir, target)
|
|
834
|
+
}
|
|
835
|
+
|
|
811
836
|
await mkdir(dir, { recursive: true })
|
|
812
837
|
return symlink(rel, node.path, 'junction')
|
|
813
838
|
}
|
|
@@ -819,11 +844,42 @@ module.exports = cls => class Reifier extends cls {
|
|
|
819
844
|
const set = optionalSet(node)
|
|
820
845
|
for (node of set) {
|
|
821
846
|
log.verbose('reify', 'failed optional dependency', node.path)
|
|
847
|
+
node.ideallyInert = true
|
|
822
848
|
this[_addNodeToTrashList](node)
|
|
823
849
|
}
|
|
824
850
|
}) : p).then(() => node)
|
|
825
851
|
}
|
|
826
852
|
|
|
853
|
+
#calculateRelativePath (node, dir, target) {
|
|
854
|
+
// Check if the node is affected by a root override
|
|
855
|
+
let hasRootOverride = [...node.edgesIn].some(edge => edge.from.isRoot && edge.overrides)
|
|
856
|
+
// If not set via edges, see if the root package.json explicitly lists an override
|
|
857
|
+
if (!hasRootOverride && node.root) {
|
|
858
|
+
const rootPackage = node.root.target
|
|
859
|
+
hasRootOverride = !!(rootPackage &&
|
|
860
|
+
rootPackage.package.overrides &&
|
|
861
|
+
rootPackage.package.overrides[node.name])
|
|
862
|
+
}
|
|
863
|
+
if (!hasRootOverride) {
|
|
864
|
+
return relative(dir, target)
|
|
865
|
+
}
|
|
866
|
+
// If an override is detected, attempt to retrieve the override spec from the root package.json
|
|
867
|
+
const overrideSpec = node.root?.target?.package?.overrides?.[node.name]
|
|
868
|
+
if (typeof overrideSpec === 'string' && overrideSpec.startsWith('file:')) {
|
|
869
|
+
const overridePath = overrideSpec.replace(/^file:/, '')
|
|
870
|
+
const rootDir = node.root.target.path
|
|
871
|
+
return relative(dir, resolve(rootDir, overridePath))
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Fallback: derive the package name from node.resolved in a platform-agnostic way
|
|
875
|
+
const filePath = node.resolved.replace(/^file:/, '')
|
|
876
|
+
// A node.package.name could be different than the folder name
|
|
877
|
+
const pathParts = filePath.split(/[\\/]/)
|
|
878
|
+
const packageName = pathParts[pathParts.length - 1]
|
|
879
|
+
|
|
880
|
+
return join('..', packageName)
|
|
881
|
+
}
|
|
882
|
+
|
|
827
883
|
#registryResolved (resolved) {
|
|
828
884
|
// the default registry url is a magic value meaning "the currently
|
|
829
885
|
// configured registry".
|
|
@@ -833,21 +889,33 @@ module.exports = cls => class Reifier extends cls {
|
|
|
833
889
|
// ${REGISTRY} or something. This has to be threaded through the
|
|
834
890
|
// Shrinkwrap and Node classes carefully, so for now, just treat
|
|
835
891
|
// the default reg as the magical animal that it has been.
|
|
836
|
-
|
|
892
|
+
try {
|
|
893
|
+
const resolvedURL = new URL(resolved)
|
|
837
894
|
|
|
838
|
-
|
|
895
|
+
if ((this.options.replaceRegistryHost === resolvedURL.hostname) ||
|
|
896
|
+
this.options.replaceRegistryHost === 'always') {
|
|
897
|
+
const registryURL = new URL(this.registry)
|
|
898
|
+
|
|
899
|
+
// Replace the host with the registry host while keeping the path intact
|
|
900
|
+
resolvedURL.hostname = registryURL.hostname
|
|
901
|
+
resolvedURL.port = registryURL.port
|
|
902
|
+
|
|
903
|
+
// Make sure we don't double-include the path if it's already there
|
|
904
|
+
const registryPath = registryURL.pathname.replace(/\/$/, '')
|
|
905
|
+
|
|
906
|
+
if (registryPath && registryPath !== '/' && !resolvedURL.pathname.startsWith(registryPath)) {
|
|
907
|
+
// Since hostname is changed, we need to ensure the registry path is included
|
|
908
|
+
resolvedURL.pathname = registryPath + resolvedURL.pathname
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
return resolvedURL.toString()
|
|
912
|
+
}
|
|
913
|
+
return resolved
|
|
914
|
+
} catch (e) {
|
|
839
915
|
// if we could not parse the url at all then returning nothing
|
|
840
916
|
// here means it will get removed from the tree in the next step
|
|
841
|
-
return
|
|
842
|
-
}
|
|
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}`
|
|
917
|
+
return undefined
|
|
848
918
|
}
|
|
849
|
-
|
|
850
|
-
return resolved
|
|
851
919
|
}
|
|
852
920
|
|
|
853
921
|
// bundles are *sort of* like shrinkwraps, in that the branch is defined
|
|
@@ -1151,6 +1219,9 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1151
1219
|
|
|
1152
1220
|
this.#retiredUnchanged[retireFolder] = []
|
|
1153
1221
|
return promiseAllRejectLate(diff.unchanged.map(node => {
|
|
1222
|
+
if (node.ideallyInert) {
|
|
1223
|
+
return
|
|
1224
|
+
}
|
|
1154
1225
|
// no need to roll back links, since we'll just delete them anyway
|
|
1155
1226
|
if (node.isLink) {
|
|
1156
1227
|
return mkdir(dirname(node.path), { recursive: true, force: true })
|
|
@@ -1230,7 +1301,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1230
1301
|
// skip links that only live within node_modules as they are most
|
|
1231
1302
|
// likely managed by packages we installed, we only want to rebuild
|
|
1232
1303
|
// unchanged links we directly manage
|
|
1233
|
-
const linkedFromRoot = node.parent === tree || node.target.fsTop === tree
|
|
1304
|
+
const linkedFromRoot = (node.parent === tree && !node.ideallyInert) || node.target.fsTop === tree
|
|
1234
1305
|
if (node.isLink && linkedFromRoot) {
|
|
1235
1306
|
nodes.push(node)
|
|
1236
1307
|
}
|
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
|