@npmcli/arborist 0.0.30 → 1.0.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/lib/arborist/build-ideal-tree.js +351 -212
- package/lib/arborist/index.js +1 -0
- package/lib/arborist/load-actual.js +2 -15
- package/lib/arborist/load-virtual.js +3 -13
- package/lib/arborist/load-workspaces.js +31 -0
- package/lib/arborist/rebuild.js +22 -16
- package/lib/edge.js +24 -0
- package/lib/node.js +11 -15
- package/lib/reset-dep-flags.js +15 -0
- package/package.json +2 -2
|
@@ -4,7 +4,6 @@ const npa = require('npm-package-arg')
|
|
|
4
4
|
const pacote = require('pacote')
|
|
5
5
|
const cacache = require('cacache')
|
|
6
6
|
const semver = require('semver')
|
|
7
|
-
const mapWorkspaces = require('@npmcli/map-workspaces')
|
|
8
7
|
const promiseCallLimit = require('promise-call-limit')
|
|
9
8
|
const getPeerSet = require('../peer-set.js')
|
|
10
9
|
const realpath = require('../../lib/realpath.js')
|
|
@@ -54,7 +53,7 @@ const _nodeFromSpec = Symbol('nodeFromSpec')
|
|
|
54
53
|
const _fetchManifest = Symbol('fetchManifest')
|
|
55
54
|
const _problemEdges = Symbol('problemEdges')
|
|
56
55
|
const _manifests = Symbol('manifests')
|
|
57
|
-
const
|
|
56
|
+
const _loadWorkspaces = Symbol.for('loadWorkspaces')
|
|
58
57
|
const _linkFromSpec = Symbol('linkFromSpec')
|
|
59
58
|
const _loadPeerSet = Symbol('loadPeerSet')
|
|
60
59
|
const _updateNames = Symbol.for('updateNames')
|
|
@@ -71,7 +70,7 @@ const _queueNamedUpdates = Symbol('queueNamedUpdates')
|
|
|
71
70
|
const _queueVulnDependents = Symbol('queueVulnDependents')
|
|
72
71
|
const _avoidRange = Symbol('avoidRange')
|
|
73
72
|
const _shouldUpdateNode = Symbol('shouldUpdateNode')
|
|
74
|
-
const
|
|
73
|
+
const resetDepFlags = require('../reset-dep-flags.js')
|
|
75
74
|
const _loadFailures = Symbol('loadFailures')
|
|
76
75
|
const _pruneFailedOptional = Symbol('pruneFailedOptional')
|
|
77
76
|
const _linkNodes = Symbol('linkNodes')
|
|
@@ -90,10 +89,19 @@ const _strictPeerDeps = Symbol('strictPeerDeps')
|
|
|
90
89
|
const _checkEngineAndPlatform = Symbol('checkEngineAndPlatform')
|
|
91
90
|
const _checkEngine = Symbol('checkEngine')
|
|
92
91
|
const _checkPlatform = Symbol('checkPlatform')
|
|
92
|
+
const _virtualRoots = Symbol('virtualRoots')
|
|
93
|
+
const _virtualRoot = Symbol('virtualRoot')
|
|
93
94
|
|
|
94
95
|
// used for the ERESOLVE error to show the last peer conflict encountered
|
|
95
96
|
const _peerConflict = Symbol('peerConflict')
|
|
96
97
|
|
|
98
|
+
const _failPeerConflict = Symbol('failPeerConflict')
|
|
99
|
+
const _explainPeerConflict = Symbol('explainPeerConflict')
|
|
100
|
+
const _warnPeerConflict = Symbol('warnPeerConflict')
|
|
101
|
+
const _edgesOverridden = Symbol('edgesOverridden')
|
|
102
|
+
// exposed symbol for unit testing the placeDep method directly
|
|
103
|
+
const _peerSetSource = Symbol.for('peerSetSource')
|
|
104
|
+
|
|
97
105
|
// used by Reify mixin
|
|
98
106
|
const _force = Symbol.for('force')
|
|
99
107
|
const _explicitRequests = Symbol.for('explicitRequests')
|
|
@@ -143,6 +151,13 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
143
151
|
this[_linkNodes] = new Set()
|
|
144
152
|
this[_manifests] = new Map()
|
|
145
153
|
this[_peerConflict] = null
|
|
154
|
+
this[_edgesOverridden] = new Set()
|
|
155
|
+
|
|
156
|
+
// a map of each module in a peer set to the thing that depended on
|
|
157
|
+
// that set of peers in the first place. Use a WeakMap so that we
|
|
158
|
+
// don't hold onto references for nodes that are garbage collected.
|
|
159
|
+
this[_peerSetSource] = new WeakMap()
|
|
160
|
+
this[_virtualRoots] = new Map()
|
|
146
161
|
}
|
|
147
162
|
|
|
148
163
|
get explicitRequests () {
|
|
@@ -266,8 +281,13 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
266
281
|
this[_global] ? this[_globalRootNode]()
|
|
267
282
|
: rpj(this.path + '/package.json').then(
|
|
268
283
|
pkg => this[_rootNodeFromPackage](pkg),
|
|
269
|
-
er =>
|
|
284
|
+
er => {
|
|
285
|
+
if (er.code === 'EJSONPARSE')
|
|
286
|
+
throw er
|
|
287
|
+
return this[_rootNodeFromPackage]({})
|
|
288
|
+
}
|
|
270
289
|
))
|
|
290
|
+
.then(root => this[_loadWorkspaces](root))
|
|
271
291
|
// ok to not have a virtual tree. probably initial install.
|
|
272
292
|
// When updating all, we load the shrinkwrap, but don't bother
|
|
273
293
|
// to build out the full virtual tree from it, since we'll be
|
|
@@ -288,8 +308,6 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
288
308
|
.then(async root => {
|
|
289
309
|
if (!this[_updateAll] && !this[_global] && !root.meta.loadedFromDisk)
|
|
290
310
|
await new this.constructor(this.options).loadActual({ root })
|
|
291
|
-
else if (!this[_global] && (!this[_usePackageLock] || this[_updateAll]))
|
|
292
|
-
await this[_mapWorkspaces](root)
|
|
293
311
|
return root
|
|
294
312
|
})
|
|
295
313
|
|
|
@@ -327,15 +345,6 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
327
345
|
})
|
|
328
346
|
}
|
|
329
347
|
|
|
330
|
-
[_mapWorkspaces] (node) {
|
|
331
|
-
return mapWorkspaces({ cwd: node.path, pkg: node.package })
|
|
332
|
-
.then(workspaces => {
|
|
333
|
-
if (workspaces.size)
|
|
334
|
-
node.workspaces = workspaces
|
|
335
|
-
return node
|
|
336
|
-
})
|
|
337
|
-
}
|
|
338
|
-
|
|
339
348
|
// process the add/rm requests by modifying the root node, and the
|
|
340
349
|
// update.names request by queueing nodes dependent on those named.
|
|
341
350
|
async [_applyUserRequests] (options) {
|
|
@@ -545,7 +554,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
545
554
|
[_shouldUpdateNode] (node) {
|
|
546
555
|
return this[_updateNames].includes(node.name) &&
|
|
547
556
|
!node.isTop &&
|
|
548
|
-
!node.
|
|
557
|
+
!node.inDepBundle &&
|
|
549
558
|
!node.inShrinkwrap
|
|
550
559
|
}
|
|
551
560
|
|
|
@@ -715,16 +724,42 @@ This is a one-time fix-up, please be patient...
|
|
|
715
724
|
// Set `preferDedupe: true` in the options to replace the shallower
|
|
716
725
|
// dep if allowed.
|
|
717
726
|
|
|
718
|
-
const tasks =
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
//
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
727
|
+
const tasks = []
|
|
728
|
+
const peerSource = this[_peerSetSource].get(node) || node
|
|
729
|
+
for (const edge of this[_problemEdges](node)) {
|
|
730
|
+
if (this[_edgesOverridden].has(edge))
|
|
731
|
+
continue
|
|
732
|
+
|
|
733
|
+
// peerSetSource is only relevant when we have a peerEntryEdge
|
|
734
|
+
// otherwise we're setting regular non-peer deps as if they have
|
|
735
|
+
// a virtual root of whatever brought in THIS node.
|
|
736
|
+
// so we VR the node itself if the edge is not a peer
|
|
737
|
+
const source = edge.peer ? peerSource : node
|
|
738
|
+
const virtualRoot = this[_virtualRoot](source, true)
|
|
739
|
+
// reuse virtual root if we already have one, but don't
|
|
740
|
+
// try to do the override ahead of time, since we MAY be able
|
|
741
|
+
// to create a more correct tree than the virtual root could.
|
|
742
|
+
const vrEdge = virtualRoot && virtualRoot.edgesOut.get(edge.name)
|
|
743
|
+
const vrDep = vrEdge && vrEdge.valid && vrEdge.to
|
|
744
|
+
// only re-use the virtualRoot if it's a peer edge we're placing.
|
|
745
|
+
// otherwise, we end up in situations where we override peer deps that
|
|
746
|
+
// we could have otherwise found homes for. Eg:
|
|
747
|
+
// xy -> (x, y)
|
|
748
|
+
// x -> PEER(z@1)
|
|
749
|
+
// y -> PEER(z@2)
|
|
750
|
+
// If xy is a dependency, we can resolve this like:
|
|
751
|
+
// project
|
|
752
|
+
// +-- xy
|
|
753
|
+
// | +-- y
|
|
754
|
+
// | +-- z@2
|
|
755
|
+
// +-- x
|
|
756
|
+
// +-- z@1
|
|
757
|
+
// But if x and y are loaded in the same virtual root, then they will
|
|
758
|
+
// be forced to agree on a version of z.
|
|
759
|
+
const dep = vrDep && vrDep.satisfies(edge) ? vrDep
|
|
760
|
+
: await this[_nodeFromEdge](edge, edge.peer ? virtualRoot : null)
|
|
761
|
+
tasks.push({edge, dep})
|
|
762
|
+
}
|
|
728
763
|
|
|
729
764
|
const placed = tasks
|
|
730
765
|
.sort((a, b) => a.edge.name.localeCompare(b.edge.name))
|
|
@@ -754,33 +789,45 @@ This is a one-time fix-up, please be patient...
|
|
|
754
789
|
|
|
755
790
|
// loads a node from an edge, and then loads its peer deps (and their
|
|
756
791
|
// peer deps, on down the line) into a virtual root parent.
|
|
757
|
-
[_nodeFromEdge] (edge,
|
|
792
|
+
[_nodeFromEdge] (edge, parent_) {
|
|
758
793
|
// create a virtual root node with the same deps as the node that
|
|
759
794
|
// is requesting this one, so that we can get all the peer deps in
|
|
760
795
|
// a context where they're likely to be resolvable.
|
|
761
|
-
const
|
|
762
|
-
parent = parent || new Node({
|
|
763
|
-
path: '/virtual-root',
|
|
764
|
-
sourceReference: edge.from,
|
|
765
|
-
legacyPeerDeps,
|
|
766
|
-
})
|
|
796
|
+
const parent = parent_ || this[_virtualRoot](edge.from)
|
|
767
797
|
|
|
768
798
|
const spec = npa.resolve(edge.name, edge.spec, edge.from.path)
|
|
769
799
|
return this[_nodeFromSpec](edge.name, spec, parent, edge)
|
|
770
800
|
.then(node => {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
801
|
+
// handle otherwise unresolvable dependency nesting loops by
|
|
802
|
+
// creating a symbolic link
|
|
803
|
+
// a1 -> b1 -> a2 -> b2 -> a1 -> ...
|
|
804
|
+
// instead of nesting forever, when the loop occurs, create
|
|
805
|
+
// a symbolic link to the earlier instance
|
|
776
806
|
for (let p = edge.from.resolveParent; p; p = p.resolveParent) {
|
|
777
807
|
if (p.matches(node) && !p.isRoot)
|
|
778
808
|
return new Link({ parent, target: p })
|
|
779
809
|
}
|
|
810
|
+
// keep track of the thing that caused this node to be included.
|
|
811
|
+
const src = parent.sourceReference
|
|
812
|
+
this[_peerSetSource].set(node, src)
|
|
780
813
|
return this[_loadPeerSet](node)
|
|
781
814
|
})
|
|
782
815
|
}
|
|
783
816
|
|
|
817
|
+
[_virtualRoot] (node, reuse = false) {
|
|
818
|
+
if (reuse && this[_virtualRoots].has(node))
|
|
819
|
+
return this[_virtualRoots].get(node)
|
|
820
|
+
|
|
821
|
+
const vr = new Node({
|
|
822
|
+
path: '/virtual-root',
|
|
823
|
+
sourceReference: node,
|
|
824
|
+
legacyPeerDeps: this.legacyPeerDeps,
|
|
825
|
+
})
|
|
826
|
+
|
|
827
|
+
this[_virtualRoots].set(node, vr)
|
|
828
|
+
return vr
|
|
829
|
+
}
|
|
830
|
+
|
|
784
831
|
[_problemEdges] (node) {
|
|
785
832
|
// skip over any bundled deps, they're not our problem.
|
|
786
833
|
// Note that this WILL fetch bundled meta-deps which are also dependencies
|
|
@@ -809,14 +856,14 @@ This is a one-time fix-up, please be patient...
|
|
|
809
856
|
if (edge.to && edge.to.inShrinkwrap)
|
|
810
857
|
return false
|
|
811
858
|
|
|
812
|
-
// If the edge has an error, there's a problem.
|
|
813
|
-
if (!edge.valid)
|
|
814
|
-
return true
|
|
815
|
-
|
|
816
859
|
// If the edge has no destination, that's a problem.
|
|
817
860
|
if (!edge.to)
|
|
818
861
|
return edge.type !== 'peerOptional'
|
|
819
862
|
|
|
863
|
+
// If the edge has an error, there's a problem.
|
|
864
|
+
if (!edge.valid)
|
|
865
|
+
return true
|
|
866
|
+
|
|
820
867
|
// If user has explicitly asked to update this package by name, it's a problem.
|
|
821
868
|
if (this[_updateNames].includes(edge.name))
|
|
822
869
|
return true
|
|
@@ -899,19 +946,100 @@ This is a one-time fix-up, please be patient...
|
|
|
899
946
|
// We prefer to get peer deps that meet the requiring node's dependency,
|
|
900
947
|
// if possible, since that almost certainly works (since that package was
|
|
901
948
|
// developed with this set of deps) and will typically be more restrictive.
|
|
902
|
-
|
|
949
|
+
// Note that the peers in the set can conflict either with each other,
|
|
950
|
+
// or with a direct dependency from the virtual root parent! In strict
|
|
951
|
+
// mode, this is always an error. In force mode, it never is, and we
|
|
952
|
+
// prefer the parent's non-peer dep over a peer dep, or the version that
|
|
953
|
+
// gets placed first. In non-strict mode, we behave strictly if the
|
|
954
|
+
// virtual root is based on the root project, and allow non-peer parent
|
|
955
|
+
// deps to override, but throw if no preference can be determined.
|
|
956
|
+
async [_loadPeerSet] (node) {
|
|
903
957
|
const peerEdges = [...node.edgesOut.values()]
|
|
904
|
-
|
|
905
|
-
.
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
958
|
+
// we only care about peers here, and don't install peerOptionals
|
|
959
|
+
.filter(e => e.peer && !e.valid && !e.optional)
|
|
960
|
+
.sort(({name: a}, {name: b}) => a.localeCompare(b))
|
|
961
|
+
|
|
962
|
+
for (const edge of peerEdges) {
|
|
963
|
+
// already placed this one, and we're happy with it.
|
|
964
|
+
if (edge.valid)
|
|
965
|
+
continue
|
|
966
|
+
|
|
967
|
+
const parentEdge = node.parent.edgesOut.get(edge.name)
|
|
968
|
+
const {isRoot, isWorkspace} = node.parent.sourceReference
|
|
969
|
+
const isMine = isRoot || isWorkspace
|
|
970
|
+
if (edge.missing) {
|
|
971
|
+
if (!parentEdge) {
|
|
972
|
+
// easy, just put the thing there
|
|
973
|
+
await this[_nodeFromEdge](edge, node.parent)
|
|
974
|
+
continue
|
|
975
|
+
} else {
|
|
976
|
+
// try to put the parent's preference, and make sure that satisfies.
|
|
977
|
+
// if so, we're good.
|
|
978
|
+
// if it does not, then we have a problem in strict mode, no problem
|
|
979
|
+
// in force mode, and a problem in non-strict mode if this isn't
|
|
980
|
+
// on behalf of the root node. In all such cases, we warn at least.
|
|
981
|
+
await this[_nodeFromEdge](parentEdge, node.parent)
|
|
982
|
+
|
|
983
|
+
// hooray! that worked!
|
|
984
|
+
if (edge.valid)
|
|
985
|
+
continue
|
|
986
|
+
|
|
987
|
+
// allow it
|
|
988
|
+
if (this[_force] || !isMine && !this[_strictPeerDeps])
|
|
989
|
+
continue
|
|
990
|
+
else
|
|
991
|
+
this[_failPeerConflict](edge)
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// at this point we know that there is a dep there, and
|
|
996
|
+
// we don't like it. always fail strictly, always allow forcibly or
|
|
997
|
+
// in non-strict mode if it's not our fault. don't warn here, because
|
|
998
|
+
// we are going to warn again when we place the deps, if we end up
|
|
999
|
+
// overriding for something else.
|
|
1000
|
+
if (this[_force] || !isMine && !this[_strictPeerDeps])
|
|
1001
|
+
continue
|
|
1002
|
+
|
|
1003
|
+
// ok, it's the root, or we're in unforced strict mode, so this is bad
|
|
1004
|
+
this[_failPeerConflict](edge)
|
|
1005
|
+
}
|
|
1006
|
+
return node
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
[_failPeerConflict] (edge) {
|
|
1010
|
+
const expl = this[_explainPeerConflict](edge)
|
|
1011
|
+
throw Object.assign(new Error('unable to resolve dependency tree'), expl)
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
[_explainPeerConflict] (edge) {
|
|
1015
|
+
const node = edge.from
|
|
1016
|
+
const curNode = node.resolve(edge.name)
|
|
1017
|
+
const pc = this[_peerConflict] || { peer: null, current: null }
|
|
1018
|
+
const current = curNode ? curNode.explain() : pc.current
|
|
1019
|
+
const peerConflict = pc.peer
|
|
1020
|
+
return {
|
|
1021
|
+
code: 'ERESOLVE',
|
|
1022
|
+
current,
|
|
1023
|
+
edge: edge.explain(),
|
|
1024
|
+
peerConflict,
|
|
1025
|
+
strictPeerDeps: this[_strictPeerDeps],
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
[_warnPeerConflict] (edge) {
|
|
1030
|
+
// track that we've overridden this edge, so that we don't keep trying
|
|
1031
|
+
// to re-resolve it in an infinite loop.
|
|
1032
|
+
this[_edgesOverridden].add(edge)
|
|
1033
|
+
const expl = this[_explainPeerConflict](edge)
|
|
1034
|
+
this.log.warn('ERESOLVE', 'overriding peer dependency', expl)
|
|
909
1035
|
}
|
|
910
1036
|
|
|
911
1037
|
// starting from either node, or in the case of non-root peer deps,
|
|
912
1038
|
// the node's parent, walk up the tree until we find the first spot
|
|
913
1039
|
// where this dep cannot be placed, and use the one right before that.
|
|
914
1040
|
// place dep, requested by node, to satisfy edge
|
|
1041
|
+
// XXX split this out into a separate method or mixin? It's quite a lot
|
|
1042
|
+
// of functionality that ought to have its own unit tests more conveniently.
|
|
915
1043
|
[_placeDep] (dep, node, edge, peerEntryEdge = null) {
|
|
916
1044
|
if (edge.to &&
|
|
917
1045
|
!edge.error &&
|
|
@@ -919,16 +1047,15 @@ This is a one-time fix-up, please be patient...
|
|
|
919
1047
|
!this[_isVulnerable](edge.to))
|
|
920
1048
|
return []
|
|
921
1049
|
|
|
922
|
-
// top nodes should still get peer deps from their
|
|
923
|
-
//
|
|
924
|
-
//
|
|
1050
|
+
// top nodes should still get peer deps from their fsParent if possible,
|
|
1051
|
+
// and only install locally if there's no other option, eg for a link
|
|
1052
|
+
// outside of the project root, or for a conflicted dep.
|
|
925
1053
|
const start = edge.peer && !node.isRoot
|
|
926
1054
|
? node.resolveParent || node
|
|
927
1055
|
: node
|
|
928
1056
|
|
|
929
1057
|
let target
|
|
930
1058
|
let canPlace = null
|
|
931
|
-
let warnPeer = false
|
|
932
1059
|
for (let check = start; check; check = check.resolveParent) {
|
|
933
1060
|
const cp = this[_canPlaceDep](dep, check, edge, peerEntryEdge)
|
|
934
1061
|
|
|
@@ -936,17 +1063,8 @@ This is a one-time fix-up, please be patient...
|
|
|
936
1063
|
if (cp !== CONFLICT) {
|
|
937
1064
|
canPlace = cp
|
|
938
1065
|
target = check
|
|
939
|
-
} else
|
|
940
|
-
if (check === start) {
|
|
941
|
-
// if it's a peer dep, and the first place we're putting it conflicts
|
|
942
|
-
// because the node has a direct dependency on the pkg in question,
|
|
943
|
-
// then we treat that as an override when --force is applied, and
|
|
944
|
-
// just warn about it.
|
|
945
|
-
const checkEdge = check.edgesOut.get(edge.name)
|
|
946
|
-
warnPeer = check === start && edge.peer && checkEdge
|
|
947
|
-
}
|
|
1066
|
+
} else
|
|
948
1067
|
break
|
|
949
|
-
}
|
|
950
1068
|
|
|
951
1069
|
// nest packages like npm v1 and v2
|
|
952
1070
|
// very disk-inefficient
|
|
@@ -959,38 +1077,16 @@ This is a one-time fix-up, please be patient...
|
|
|
959
1077
|
break
|
|
960
1078
|
}
|
|
961
1079
|
|
|
962
|
-
if (!target)
|
|
963
|
-
|
|
964
|
-
const pc = this[_peerConflict] || { peer: null, current: null }
|
|
965
|
-
// we'll only get one of these
|
|
966
|
-
const current = curNode ? curNode.explain() : pc.current
|
|
967
|
-
const peerConflict = pc.peer
|
|
968
|
-
const expl = {
|
|
969
|
-
code: 'ERESOLVE',
|
|
970
|
-
dep: dep.explain(edge),
|
|
971
|
-
current,
|
|
972
|
-
peerConflict,
|
|
973
|
-
fixWithForce: edge.peer && !!warnPeer,
|
|
974
|
-
type: edge.type,
|
|
975
|
-
isPeer: edge.peer,
|
|
976
|
-
}
|
|
977
|
-
const override = this[_force] || !this[_strictPeerDeps]
|
|
978
|
-
|
|
979
|
-
if (override && expl.fixWithForce) {
|
|
980
|
-
this.log.warn('ERESOLVE', 'overriding peer dependency', expl)
|
|
981
|
-
return []
|
|
982
|
-
} else {
|
|
983
|
-
const er = new Error('unable to resolve dependency tree')
|
|
984
|
-
throw Object.assign(er, expl)
|
|
985
|
-
}
|
|
986
|
-
}
|
|
1080
|
+
if (!target)
|
|
1081
|
+
this[_failPeerConflict](edge)
|
|
987
1082
|
|
|
988
1083
|
this.log.silly(
|
|
989
1084
|
'placeDep',
|
|
990
1085
|
target.location || 'ROOT',
|
|
991
|
-
`${
|
|
992
|
-
canPlace,
|
|
993
|
-
`for: ${node.package._id || node.location}
|
|
1086
|
+
`${dep.name}@${dep.version}`,
|
|
1087
|
+
canPlace.description || /* istanbul ignore next */ canPlace,
|
|
1088
|
+
`for: ${node.package._id || node.location}`,
|
|
1089
|
+
`want: ${edge.spec || '*'}`
|
|
994
1090
|
)
|
|
995
1091
|
|
|
996
1092
|
// it worked, so we clearly have no peer conflicts at this point.
|
|
@@ -1042,10 +1138,12 @@ This is a one-time fix-up, please be patient...
|
|
|
1042
1138
|
edge.to.parent = null
|
|
1043
1139
|
|
|
1044
1140
|
// visit any dependents who are upset by this change
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1141
|
+
// if it's an angry overridden peer edge, however, make sure we
|
|
1142
|
+
// skip over it!
|
|
1143
|
+
for (const edgeIn of dep.edgesIn) {
|
|
1144
|
+
if (edgeIn !== edge && !edgeIn.valid && !this[_depsSeen].has(edge.from)) {
|
|
1145
|
+
this.addTracker('idealTree', edgeIn.from.name, edgeIn.from.location)
|
|
1146
|
+
this[_depsQueue].push(edgeIn.from)
|
|
1049
1147
|
}
|
|
1050
1148
|
}
|
|
1051
1149
|
|
|
@@ -1081,28 +1179,30 @@ This is a one-time fix-up, please be patient...
|
|
|
1081
1179
|
// also place its unmet or invalid peer deps at this location
|
|
1082
1180
|
// note that dep has now been removed from the virtualRoot set
|
|
1083
1181
|
// by virtue of being placed in the target's node_modules.
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1182
|
+
const peers = []
|
|
1183
|
+
// double loop so that we don't yank things out and then fail to find
|
|
1184
|
+
// them in the virtualRoot's children.
|
|
1185
|
+
for (const peerEdge of dep.edgesOut.values()) {
|
|
1186
|
+
if (!peerEdge.peer || peerEdge.valid)
|
|
1187
|
+
continue
|
|
1188
|
+
const peer = virtualRoot.children.get(peerEdge.name)
|
|
1189
|
+
// since we re-use virtualRoots, it's possible that the node was
|
|
1190
|
+
// already placed somewhere in the tree, and thus plucked off the
|
|
1191
|
+
// virtual root. however, in that case, it should have been no
|
|
1192
|
+
// longer a missing/invalid peer dep, so something is messed up.
|
|
1193
|
+
if (peer)
|
|
1194
|
+
peers.push([peer, peerEdge])
|
|
1195
|
+
}
|
|
1098
1196
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
}
|
|
1197
|
+
for (const [peer, peerEdge] of peers) {
|
|
1198
|
+
const peerPlaced = this[_placeDep](
|
|
1199
|
+
peer, dep, peerEdge, peerEntryEdge || edge)
|
|
1200
|
+
placed.push(...peerPlaced)
|
|
1104
1201
|
}
|
|
1105
1202
|
|
|
1203
|
+
// we're done with this now, clean it up.
|
|
1204
|
+
this[_virtualRoots].delete(virtualRoot.sourceReference)
|
|
1205
|
+
|
|
1106
1206
|
return placed
|
|
1107
1207
|
}
|
|
1108
1208
|
|
|
@@ -1146,43 +1246,41 @@ This is a one-time fix-up, please be patient...
|
|
|
1146
1246
|
// When we check peers, we pass along the peerEntryEdge to track the
|
|
1147
1247
|
// original edge that caused us to load the family of peer dependencies.
|
|
1148
1248
|
[_canPlaceDep] (dep, target, edge, peerEntryEdge = null) {
|
|
1149
|
-
// peer deps of root deps are effectively root deps
|
|
1150
|
-
const isRootDep = target.isRoot && (
|
|
1151
|
-
// a direct dependency from the root node
|
|
1152
|
-
edge.from === target ||
|
|
1153
|
-
// a member of the peer set of a direct root dependency
|
|
1154
|
-
peerEntryEdge && peerEntryEdge.from === target
|
|
1155
|
-
)
|
|
1156
|
-
|
|
1157
1249
|
const entryEdge = peerEntryEdge || edge
|
|
1250
|
+
const source = this[_peerSetSource].get(dep)
|
|
1251
|
+
const virtualRoot = dep.parent
|
|
1252
|
+
const vrEdge = virtualRoot.edgesOut.get(edge.name)
|
|
1158
1253
|
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
return KEEP
|
|
1254
|
+
const isSource = target === source
|
|
1255
|
+
const { isRoot, isWorkspace } = source || {}
|
|
1256
|
+
const isMine = isRoot || isWorkspace
|
|
1257
|
+
|
|
1258
|
+
if (target.children.has(edge.name)) {
|
|
1259
|
+
const current = target.children.get(edge.name)
|
|
1166
1260
|
|
|
1167
|
-
//
|
|
1168
|
-
if (
|
|
1169
|
-
return
|
|
1261
|
+
// same thing = keep
|
|
1262
|
+
if (dep.matches(current))
|
|
1263
|
+
return KEEP
|
|
1170
1264
|
|
|
1171
|
-
|
|
1172
|
-
const
|
|
1173
|
-
const newVer = dep.version
|
|
1174
|
-
// always try to replace if the version is greater
|
|
1265
|
+
const { version: curVer } = current
|
|
1266
|
+
const { version: newVer } = dep
|
|
1175
1267
|
const tryReplace = curVer && newVer && semver.gte(newVer, curVer)
|
|
1176
|
-
if (tryReplace &&
|
|
1177
|
-
|
|
1268
|
+
if (tryReplace && dep.canReplace(current)) {
|
|
1269
|
+
const res = this[_canPlacePeers](dep, target, edge, REPLACE, peerEntryEdge)
|
|
1270
|
+
/* istanbul ignore else - It's extremely rare that a replaceable
|
|
1271
|
+
* node would be a conflict, if the current one wasn't a conflict,
|
|
1272
|
+
* but it is theoretically possible if peer deps are pinned. In
|
|
1273
|
+
* that case we treat it like any other conflict, and keep trying */
|
|
1274
|
+
if (res !== CONFLICT)
|
|
1275
|
+
return res
|
|
1276
|
+
}
|
|
1178
1277
|
|
|
1179
|
-
// ok,
|
|
1278
|
+
// ok, can't replace the current with new one, but maybe current is ok?
|
|
1180
1279
|
if (edge.satisfiedBy(current))
|
|
1181
1280
|
return KEEP
|
|
1182
1281
|
|
|
1183
|
-
//
|
|
1184
|
-
|
|
1185
|
-
if (this[_preferDedupe] && current.canReplaceWith(dep)) {
|
|
1282
|
+
// if we prefer deduping, then try replacing newer with older
|
|
1283
|
+
if (this[_preferDedupe] && !tryReplace && dep.canReplace(current)) {
|
|
1186
1284
|
const res = this[_canPlacePeers](dep, target, edge, REPLACE, peerEntryEdge)
|
|
1187
1285
|
/* istanbul ignore else - It's extremely rare that a replaceable
|
|
1188
1286
|
* node would be a conflict, if the current one wasn't a conflict,
|
|
@@ -1192,50 +1290,90 @@ This is a one-time fix-up, please be patient...
|
|
|
1192
1290
|
return res
|
|
1193
1291
|
}
|
|
1194
1292
|
|
|
1195
|
-
//
|
|
1196
|
-
//
|
|
1197
|
-
//
|
|
1198
|
-
//
|
|
1199
|
-
//
|
|
1200
|
-
//
|
|
1201
|
-
//
|
|
1202
|
-
|
|
1293
|
+
// check for conflict override cases.
|
|
1294
|
+
// first: is this the only place this thing can go? If the target is
|
|
1295
|
+
// the source, then one of these things are true.
|
|
1296
|
+
//
|
|
1297
|
+
// 1. the conflicted dep was deduped up to here from a lower dependency
|
|
1298
|
+
// w -> (x,y)
|
|
1299
|
+
// x -> (z)
|
|
1300
|
+
// y -> PEER(p@1)
|
|
1301
|
+
// z -> (q)
|
|
1302
|
+
// q -> (p@2)
|
|
1303
|
+
//
|
|
1304
|
+
// When building, let's say that x is fully placed, with all of its
|
|
1305
|
+
// deps, and we're _adding_ y. Since the peer on p@1 was not initially
|
|
1306
|
+
// present, it's been deduped up to w, and now needs to be pushed out.
|
|
1307
|
+
// Replace it, and potentially also replace its peer set (though that'll
|
|
1308
|
+
// be accomplished by making the same determination when we call
|
|
1309
|
+
// _canPlacePeers)
|
|
1310
|
+
//
|
|
1311
|
+
// 2. the dep we're TRYING to place here ought to be overridden by the
|
|
1312
|
+
// one that's here now, because current is (a) a direct dep of the
|
|
1313
|
+
// source, or (b) an already-placed peer in a conflicted peer set, or
|
|
1314
|
+
// (c) an already-placed peer in a different peer set at the same level.
|
|
1315
|
+
// If strict or ours, conflict. Otherwise, keep.
|
|
1316
|
+
if (isSource) {
|
|
1317
|
+
// check to see if the current module could go deeper in the tree
|
|
1203
1318
|
const peerSet = getPeerSet(current)
|
|
1204
|
-
|
|
1319
|
+
let canReplace = true
|
|
1320
|
+
OUTER: for (const p of peerSet) {
|
|
1205
1321
|
// if any have a non-peer dep from the target, or a peer dep if
|
|
1206
|
-
// the target is root, then
|
|
1322
|
+
// the target is root, then cannot safely replace and dupe deeper.
|
|
1207
1323
|
for (const edge of p.edgesIn) {
|
|
1208
|
-
if (edge.
|
|
1209
|
-
// root deps take precedence always.
|
|
1210
|
-
// in case this is an edge coming from a link it's also
|
|
1211
|
-
// going to conflict since deps are effectively relative
|
|
1212
|
-
// to its link node parent
|
|
1213
|
-
if (edge.from.isTop)
|
|
1214
|
-
return CONFLICT
|
|
1215
|
-
|
|
1216
|
-
// other peer deps on this node are irrelevant though.
|
|
1324
|
+
if (peerSet.has(edge.from))
|
|
1217
1325
|
continue
|
|
1326
|
+
|
|
1327
|
+
// only respect valid edges, however, since we're likely trying
|
|
1328
|
+
// to fix the very one that's currently broken!
|
|
1329
|
+
if (edge.from === target && edge.valid) {
|
|
1330
|
+
canReplace = false
|
|
1331
|
+
break OUTER
|
|
1218
1332
|
}
|
|
1219
|
-
// note that we MAY resolve this conflict by using the target's
|
|
1220
|
-
// conflicting dep on the peer, if --force is set.
|
|
1221
|
-
if (edge.from === target)
|
|
1222
|
-
return CONFLICT
|
|
1223
1333
|
}
|
|
1224
1334
|
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1335
|
+
if (canReplace) {
|
|
1336
|
+
const ret = this[_canPlacePeers](dep, target, edge, REPLACE, peerEntryEdge)
|
|
1337
|
+
/* istanbul ignore else - extremely rare that the peer set would
|
|
1338
|
+
* conflict if we can replace the node in question, but theoretically
|
|
1339
|
+
* possible, if peer deps are pinned aggressively. */
|
|
1340
|
+
if (ret !== CONFLICT)
|
|
1341
|
+
return ret
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// so it's not a deeper dep that's been deduped. That means that the
|
|
1345
|
+
// only way it could have ended up here is if it's a conflicted peer.
|
|
1346
|
+
/* istanbul ignore else - would have already crashed if not forced,
|
|
1347
|
+
* and either mine or strict, when creating the peerSet. Keeping this
|
|
1348
|
+
* check so that we're not only relying on action at a distance. */
|
|
1349
|
+
if (!this[_strictPeerDeps] && !isMine || this[_force]) {
|
|
1350
|
+
this[_warnPeerConflict](edge, dep)
|
|
1351
|
+
return KEEP
|
|
1352
|
+
}
|
|
1228
1353
|
}
|
|
1229
1354
|
|
|
1230
|
-
|
|
1355
|
+
if (vrEdge && vrEdge.satisfiedBy(current)) {
|
|
1356
|
+
/* istanbul ignore else - If the virtual root was satisfied, in
|
|
1357
|
+
* such a way that it was an override, and it's NOT forced, and is
|
|
1358
|
+
* ours, or is in strict mode, then it would have crashed during
|
|
1359
|
+
* the creation of the peerSet. Nevertheless, this is a good check
|
|
1360
|
+
* to ensure we're not warning when we should be conflicting. */
|
|
1361
|
+
if (this[_force] || !isMine && !this[_strictPeerDeps]) {
|
|
1362
|
+
this[_warnPeerConflict](edge)
|
|
1363
|
+
return KEEP
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
// no justification for overriding, and no agreement possible.
|
|
1231
1368
|
return CONFLICT
|
|
1232
1369
|
}
|
|
1233
1370
|
|
|
1234
|
-
//
|
|
1235
|
-
//
|
|
1236
|
-
//
|
|
1371
|
+
// no existing node at this location!
|
|
1372
|
+
// check to see if the target doesn't have a child by that name,
|
|
1373
|
+
// but WANTS one, and won't be happy with this one. if this is the
|
|
1374
|
+
// edge we're looking to resolve, then not relevant, of course.
|
|
1237
1375
|
if (target !== entryEdge.from && target.edgesOut.has(dep.name)) {
|
|
1238
|
-
const
|
|
1376
|
+
const targetEdge = target.edgesOut.get(dep.name)
|
|
1239
1377
|
// It might be that the dep would not be valid here, BUT some other
|
|
1240
1378
|
// version would. Could to try to resolve that, but that makes this no
|
|
1241
1379
|
// longer a pure synchronous function. ugh.
|
|
@@ -1248,15 +1386,28 @@ This is a one-time fix-up, please be patient...
|
|
|
1248
1386
|
// a specific name, however, or if a dep makes an incompatible change
|
|
1249
1387
|
// to its peer dep in a non-semver-major version bump, or if the parent
|
|
1250
1388
|
// is unbounded in its dependency list.
|
|
1251
|
-
if (!
|
|
1389
|
+
if (!targetEdge.satisfiedBy(dep)) {
|
|
1390
|
+
if (isSource) {
|
|
1391
|
+
// conflicted peer dep. accept what's there, if overriding
|
|
1392
|
+
/* istanbul ignore else - If it's the source, and the source's edge
|
|
1393
|
+
* is not valid, then either we crashed when creating the peer set,
|
|
1394
|
+
* or it's forced, or it's not ours and not strict. Keep this check
|
|
1395
|
+
* to avoid relying on action at a distance, however. */
|
|
1396
|
+
if (this[_force] || !isMine && !this[_strictPeerDeps]) {
|
|
1397
|
+
this[_warnPeerConflict](edge)
|
|
1398
|
+
return KEEP
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1252
1402
|
return CONFLICT
|
|
1403
|
+
}
|
|
1253
1404
|
}
|
|
1254
1405
|
|
|
1255
|
-
// check to see what
|
|
1256
|
-
//
|
|
1257
|
-
// at this point that it's not the target's direct child node.
|
|
1258
|
-
//
|
|
1259
|
-
//
|
|
1406
|
+
// check to see what that name resolves to here, and who may depend on
|
|
1407
|
+
// being able to reach it by crawling up past this parent. we know
|
|
1408
|
+
// at this point that it's not the target's direct child node. if it's
|
|
1409
|
+
// a direct dep of the target, we just make the invalid edge and
|
|
1410
|
+
// resolve it later.
|
|
1260
1411
|
const current = target !== entryEdge.from && target.resolve(dep.name)
|
|
1261
1412
|
if (current) {
|
|
1262
1413
|
for (const edge of current.edgesIn.values()) {
|
|
@@ -1267,6 +1418,7 @@ This is a one-time fix-up, please be patient...
|
|
|
1267
1418
|
}
|
|
1268
1419
|
}
|
|
1269
1420
|
|
|
1421
|
+
// no objections! ok to place here
|
|
1270
1422
|
return this[_canPlacePeers](dep, target, edge, OK, peerEntryEdge)
|
|
1271
1423
|
}
|
|
1272
1424
|
|
|
@@ -1279,25 +1431,28 @@ This is a one-time fix-up, please be patient...
|
|
|
1279
1431
|
return ret
|
|
1280
1432
|
|
|
1281
1433
|
for (const peer of dep.parent.children.values()) {
|
|
1282
|
-
if (peer
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1434
|
+
if (peer === dep)
|
|
1435
|
+
continue
|
|
1436
|
+
|
|
1437
|
+
const peerEdge = dep.edgesOut.get(peer.name) ||
|
|
1438
|
+
[...peer.edgesIn].find(e => e.peer)
|
|
1439
|
+
|
|
1440
|
+
/* istanbul ignore if - pretty sure this is impossible, but just
|
|
1441
|
+
being cautious */
|
|
1442
|
+
if (!peerEdge)
|
|
1443
|
+
continue
|
|
1444
|
+
|
|
1445
|
+
const canPlacePeer = this[_canPlaceDep](peer, target, peerEdge, edge)
|
|
1446
|
+
if (canPlacePeer !== CONFLICT)
|
|
1447
|
+
continue
|
|
1448
|
+
|
|
1449
|
+
const current = target.resolve(peer.name)
|
|
1450
|
+
this[_peerConflict] = {
|
|
1451
|
+
peer: peer.explain(peerEdge),
|
|
1452
|
+
current: current && current.explain(),
|
|
1298
1453
|
}
|
|
1454
|
+
return CONFLICT
|
|
1299
1455
|
}
|
|
1300
|
-
|
|
1301
1456
|
return ret
|
|
1302
1457
|
}
|
|
1303
1458
|
|
|
@@ -1370,7 +1525,7 @@ This is a one-time fix-up, please be patient...
|
|
|
1370
1525
|
// nothing to prune, because we built it from scratch. if we didn't
|
|
1371
1526
|
// add or remove anything, then also nothing to do.
|
|
1372
1527
|
if (metaFromDisk && mutateTree)
|
|
1373
|
-
this
|
|
1528
|
+
resetDepFlags(this.idealTree)
|
|
1374
1529
|
|
|
1375
1530
|
// update all the dev/optional/etc flags in the tree
|
|
1376
1531
|
// either we started with a fresh tree, or we
|
|
@@ -1405,22 +1560,6 @@ This is a one-time fix-up, please be patient...
|
|
|
1405
1560
|
node.parent = null
|
|
1406
1561
|
}
|
|
1407
1562
|
|
|
1408
|
-
// we'll need to actually do a walk from the root, because you can have
|
|
1409
|
-
// a cycle of deps that all depend on each other, but no path from root.
|
|
1410
|
-
// Also, since the ideal tree is loaded from the shrinkwrap, it had
|
|
1411
|
-
// extraneous flags set false that might now be actually extraneous, and
|
|
1412
|
-
// dev/optional flags that are also now incorrect. This method sets
|
|
1413
|
-
// all flags to true, so we can find the set that is actually extraneous.
|
|
1414
|
-
[_resetDepFlags] () {
|
|
1415
|
-
for (const node of this.idealTree.inventory.values()) {
|
|
1416
|
-
node.extraneous = true
|
|
1417
|
-
node.dev = true
|
|
1418
|
-
node.devOptional = true
|
|
1419
|
-
node.peer = true
|
|
1420
|
-
node.optional = true
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
1563
|
[_pruneFailedOptional] () {
|
|
1425
1564
|
for (const node of this[_loadFailures]) {
|
|
1426
1565
|
if (!node.optional)
|
package/lib/arborist/index.js
CHANGED
|
@@ -7,7 +7,6 @@ const {promisify} = require('util')
|
|
|
7
7
|
const readdir = promisify(require('readdir-scoped-modules'))
|
|
8
8
|
const walkUp = require('walk-up-path')
|
|
9
9
|
const ancestorPath = require('common-ancestor-path')
|
|
10
|
-
const mapWorkspaces = require('@npmcli/map-workspaces')
|
|
11
10
|
|
|
12
11
|
const Shrinkwrap = require('../shrinkwrap.js')
|
|
13
12
|
const calcDepFlags = require('../calc-dep-flags.js')
|
|
@@ -31,7 +30,7 @@ const _cache = Symbol('nodeLoadingCache')
|
|
|
31
30
|
const _loadActual = Symbol('loadActual')
|
|
32
31
|
const _loadActualVirtually = Symbol('loadActualVirtually')
|
|
33
32
|
const _loadActualActually = Symbol('loadActualActually')
|
|
34
|
-
const
|
|
33
|
+
const _loadWorkspaces = Symbol.for('loadWorkspaces')
|
|
35
34
|
const _actualTreePromise = Symbol('actualTreePromise')
|
|
36
35
|
const _actualTree = Symbol('actualTree')
|
|
37
36
|
const _transplant = Symbol('transplant')
|
|
@@ -162,7 +161,7 @@ module.exports = cls => class ActualLoader extends cls {
|
|
|
162
161
|
this[_findFSParents]()
|
|
163
162
|
this[_transplant](root)
|
|
164
163
|
|
|
165
|
-
await this[
|
|
164
|
+
await this[_loadWorkspaces](this[_actualTree])
|
|
166
165
|
// only reset root flags if we're not re-rooting, otherwise leave as-is
|
|
167
166
|
calcDepFlags(this[_actualTree], !root)
|
|
168
167
|
return this[_actualTree]
|
|
@@ -309,18 +308,6 @@ module.exports = cls => class ActualLoader extends cls {
|
|
|
309
308
|
() => {})
|
|
310
309
|
}
|
|
311
310
|
|
|
312
|
-
async [_mapWorkspaces] (node) {
|
|
313
|
-
const workspaces = await mapWorkspaces({
|
|
314
|
-
cwd: node.path,
|
|
315
|
-
pkg: node.package,
|
|
316
|
-
})
|
|
317
|
-
|
|
318
|
-
if (workspaces.size)
|
|
319
|
-
node.workspaces = workspaces
|
|
320
|
-
|
|
321
|
-
return node
|
|
322
|
-
}
|
|
323
|
-
|
|
324
311
|
async [_findMissingEdges] () {
|
|
325
312
|
// try to resolve any missing edges by walking up the directory tree,
|
|
326
313
|
// checking for the package in each node_modules folder. stop at the
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// mixin providing the loadVirtual method
|
|
2
2
|
|
|
3
3
|
const {dirname, resolve} = require('path')
|
|
4
|
-
const mapWorkspaces = require('@npmcli/map-workspaces')
|
|
5
4
|
const walkUp = require('walk-up-path')
|
|
6
5
|
|
|
7
6
|
const nameFromFolder = require('@npmcli/name-from-folder')
|
|
@@ -20,7 +19,8 @@ const assignParentage = Symbol('assignParentage')
|
|
|
20
19
|
const loadRoot = Symbol('loadRoot')
|
|
21
20
|
const loadNode = Symbol('loadVirtualNode')
|
|
22
21
|
const loadLink = Symbol('loadVirtualLink')
|
|
23
|
-
const loadWorkspaces = Symbol('loadWorkspaces')
|
|
22
|
+
const loadWorkspaces = Symbol.for('loadWorkspaces')
|
|
23
|
+
const loadWorkspacesVirtual = Symbol.for('loadWorkspacesVirtual')
|
|
24
24
|
const flagsSuspect = Symbol.for('flagsSuspect')
|
|
25
25
|
const reCalcDepFlags = Symbol('reCalcDepFlags')
|
|
26
26
|
const checkRootEdges = Symbol('checkRootEdges')
|
|
@@ -131,7 +131,7 @@ module.exports = cls => class VirtualLoader extends cls {
|
|
|
131
131
|
delete prod[name]
|
|
132
132
|
|
|
133
133
|
const lockWS = []
|
|
134
|
-
const workspaces =
|
|
134
|
+
const workspaces = this[loadWorkspacesVirtual]({
|
|
135
135
|
cwd: this.path,
|
|
136
136
|
lockfile: s.data,
|
|
137
137
|
})
|
|
@@ -270,16 +270,6 @@ module.exports = cls => class VirtualLoader extends cls {
|
|
|
270
270
|
return node
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
async [loadWorkspaces] (node) {
|
|
274
|
-
const workspaces = await mapWorkspaces({
|
|
275
|
-
cwd: node.path,
|
|
276
|
-
pkg: node.package,
|
|
277
|
-
})
|
|
278
|
-
if (workspaces.size)
|
|
279
|
-
node.workspaces = workspaces
|
|
280
|
-
return node
|
|
281
|
-
}
|
|
282
|
-
|
|
283
273
|
[loadLink] (location, targetLoc, target, meta) {
|
|
284
274
|
const path = resolve(this.path, location)
|
|
285
275
|
const link = new Link({
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const mapWorkspaces = require('@npmcli/map-workspaces')
|
|
2
|
+
|
|
3
|
+
const _appendWorkspaces = Symbol('appendWorkspaces')
|
|
4
|
+
// shared ref used by other mixins/Arborist
|
|
5
|
+
const _loadWorkspaces = Symbol.for('loadWorkspaces')
|
|
6
|
+
const _loadWorkspacesVirtual = Symbol.for('loadWorkspacesVirtual')
|
|
7
|
+
|
|
8
|
+
module.exports = cls => class MapWorkspaces extends cls {
|
|
9
|
+
[_appendWorkspaces] (node, workspaces) {
|
|
10
|
+
if (node && workspaces.size)
|
|
11
|
+
node.workspaces = workspaces
|
|
12
|
+
|
|
13
|
+
return node
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async [_loadWorkspaces] (node) {
|
|
17
|
+
if (node.workspaces)
|
|
18
|
+
return node
|
|
19
|
+
|
|
20
|
+
const workspaces = await mapWorkspaces({
|
|
21
|
+
cwd: node.path,
|
|
22
|
+
pkg: node.package,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return this[_appendWorkspaces](node, workspaces)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
[_loadWorkspacesVirtual] (opts) {
|
|
29
|
+
return mapWorkspaces.virtual(opts)
|
|
30
|
+
}
|
|
31
|
+
}
|
package/lib/arborist/rebuild.js
CHANGED
|
@@ -8,7 +8,10 @@ const binLinks = require('bin-links')
|
|
|
8
8
|
const runScript = require('@npmcli/run-script')
|
|
9
9
|
const promiseCallLimit = require('promise-call-limit')
|
|
10
10
|
const {resolve} = require('path')
|
|
11
|
-
const {
|
|
11
|
+
const {
|
|
12
|
+
isNodeGypPackage,
|
|
13
|
+
defaultGypInstallScript,
|
|
14
|
+
} = require('@npmcli/node-gyp')
|
|
12
15
|
|
|
13
16
|
const boolEnv = b => b ? '1' : ''
|
|
14
17
|
const sortNodes = (a, b) => (a.depth - b.depth) || a.path.localeCompare(b.path)
|
|
@@ -140,7 +143,7 @@ module.exports = cls => class Builder extends cls {
|
|
|
140
143
|
await binLinks.checkBins({ pkg, path, top: true, global: true })
|
|
141
144
|
}
|
|
142
145
|
|
|
143
|
-
async [_addToBuildSet] (node, set) {
|
|
146
|
+
async [_addToBuildSet] (node, set, refreshed = false) {
|
|
144
147
|
if (set.has(node))
|
|
145
148
|
return
|
|
146
149
|
|
|
@@ -151,12 +154,11 @@ module.exports = cls => class Builder extends cls {
|
|
|
151
154
|
}
|
|
152
155
|
|
|
153
156
|
const { package: pkg, hasInstallScript } = node
|
|
154
|
-
const { bin, scripts = {} } = pkg
|
|
157
|
+
const { gypfile, bin, scripts = {} } = pkg
|
|
155
158
|
|
|
156
159
|
const { preinstall, install, postinstall } = scripts
|
|
157
160
|
const anyScript = preinstall || install || postinstall
|
|
158
|
-
|
|
159
|
-
if (!anyScript && (hasInstallScript || this[_oldMeta])) {
|
|
161
|
+
if (!refreshed && !anyScript && (hasInstallScript || this[_oldMeta])) {
|
|
160
162
|
// we either have an old metadata (and thus might have scripts)
|
|
161
163
|
// or we have an indication that there's install scripts (but
|
|
162
164
|
// don't yet know what they are) so we have to load the package.json
|
|
@@ -169,21 +171,25 @@ module.exports = cls => class Builder extends cls {
|
|
|
169
171
|
set.delete(node)
|
|
170
172
|
|
|
171
173
|
const {scripts = {}} = pkg
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return this[_addToBuildSet](node, set)
|
|
175
|
-
}
|
|
174
|
+
node.package.scripts = scripts
|
|
175
|
+
return this[_addToBuildSet](node, set, true)
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
// Rebuild node-gyp dependencies lacking an install or preinstall script
|
|
179
|
+
// note that 'scripts' might be missing entirely, and the package may
|
|
180
|
+
// set gypfile:false to avoid this automatic detection.
|
|
181
|
+
const isGyp = gypfile !== false &&
|
|
182
|
+
!install &&
|
|
183
|
+
!preinstall &&
|
|
184
|
+
await isNodeGypPackage(node.path)
|
|
185
|
+
|
|
186
|
+
if (bin || preinstall || install || postinstall || isGyp) {
|
|
179
187
|
if (bin)
|
|
180
188
|
await this[_checkBins](node)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
scripts.install = 'node-gyp rebuild'
|
|
186
|
-
node.package.scripts = scripts
|
|
189
|
+
if (isGyp) {
|
|
190
|
+
scripts.install = defaultGypInstallScript
|
|
191
|
+
node.package.scripts = scripts
|
|
192
|
+
}
|
|
187
193
|
set.add(node)
|
|
188
194
|
}
|
|
189
195
|
}
|
package/lib/edge.js
CHANGED
|
@@ -12,6 +12,8 @@ const _name = Symbol('_name')
|
|
|
12
12
|
const _error = Symbol('_error')
|
|
13
13
|
const _loadError = Symbol('_loadError')
|
|
14
14
|
const _setFrom = Symbol('_setFrom')
|
|
15
|
+
const _explain = Symbol('_explain')
|
|
16
|
+
const _explanation = Symbol('_explanation')
|
|
15
17
|
|
|
16
18
|
const types = new Set([
|
|
17
19
|
'prod',
|
|
@@ -60,6 +62,25 @@ class Edge {
|
|
|
60
62
|
return depValid(node, this.spec, this.accept, this.from)
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
explain (seen = []) {
|
|
66
|
+
if (this[_explanation])
|
|
67
|
+
return this[_explanation]
|
|
68
|
+
|
|
69
|
+
return this[_explanation] = this[_explain](seen)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// return the edge data, and an explanation of how that edge came to be here
|
|
73
|
+
[_explain] (seen) {
|
|
74
|
+
const { error, from } = this
|
|
75
|
+
return {
|
|
76
|
+
type: this.type,
|
|
77
|
+
name: this.name,
|
|
78
|
+
spec: this.spec,
|
|
79
|
+
...(error ? { error } : {}),
|
|
80
|
+
...(from ? { from: from.explain(null, seen) } : {}),
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
63
84
|
get workspace () {
|
|
64
85
|
return this[_type] === 'workspace'
|
|
65
86
|
}
|
|
@@ -125,6 +146,7 @@ class Edge {
|
|
|
125
146
|
}
|
|
126
147
|
|
|
127
148
|
reload (hard = false) {
|
|
149
|
+
this[_explanation] = null
|
|
128
150
|
const newTo = this[_from].resolve(this.name)
|
|
129
151
|
if (newTo !== this[_to]) {
|
|
130
152
|
if (this[_to])
|
|
@@ -138,6 +160,7 @@ class Edge {
|
|
|
138
160
|
}
|
|
139
161
|
|
|
140
162
|
detach () {
|
|
163
|
+
this[_explanation] = null
|
|
141
164
|
if (this[_to])
|
|
142
165
|
this[_to].edgesIn.delete(this)
|
|
143
166
|
this[_from].edgesOut.delete(this.name)
|
|
@@ -147,6 +170,7 @@ class Edge {
|
|
|
147
170
|
}
|
|
148
171
|
|
|
149
172
|
[_setFrom] (node) {
|
|
173
|
+
this[_explanation] = null
|
|
150
174
|
this[_from] = node
|
|
151
175
|
if (node.edgesOut.has(this.name))
|
|
152
176
|
node.edgesOut.get(this.name).detach()
|
package/lib/node.js
CHANGED
|
@@ -57,7 +57,6 @@ const _delistFromMeta = Symbol('_delistFromMeta')
|
|
|
57
57
|
const _global = Symbol.for('global')
|
|
58
58
|
const _workspaces = Symbol('_workspaces')
|
|
59
59
|
const _explain = Symbol('_explain')
|
|
60
|
-
const _explainEdge = Symbol('_explainEdge')
|
|
61
60
|
const _explanation = Symbol('_explanation')
|
|
62
61
|
|
|
63
62
|
const relpath = require('./relpath.js')
|
|
@@ -340,9 +339,8 @@ class Node {
|
|
|
340
339
|
why.whileInstalling = {
|
|
341
340
|
name,
|
|
342
341
|
version,
|
|
342
|
+
path: this.root.sourceReference.path,
|
|
343
343
|
}
|
|
344
|
-
if (edge)
|
|
345
|
-
this[_explainEdge](edge, seen)
|
|
346
344
|
}
|
|
347
345
|
|
|
348
346
|
if (this.sourceReference)
|
|
@@ -358,7 +356,7 @@ class Node {
|
|
|
358
356
|
|
|
359
357
|
why.dependents = []
|
|
360
358
|
if (edge)
|
|
361
|
-
why.dependents.push(
|
|
359
|
+
why.dependents.push(edge.explain(seen))
|
|
362
360
|
else {
|
|
363
361
|
// if we have an edge from the root, just show that, and stop there
|
|
364
362
|
// no need to go deeper, because it doesn't provide much more value.
|
|
@@ -376,21 +374,11 @@ class Node {
|
|
|
376
374
|
edges.push(edge)
|
|
377
375
|
}
|
|
378
376
|
for (const edge of edges)
|
|
379
|
-
why.dependents.push(
|
|
377
|
+
why.dependents.push(edge.explain(seen))
|
|
380
378
|
}
|
|
381
379
|
return why
|
|
382
380
|
}
|
|
383
381
|
|
|
384
|
-
// return the edge data, and an explanation of how that edge came to be here
|
|
385
|
-
[_explainEdge] (edge, seen) {
|
|
386
|
-
return {
|
|
387
|
-
type: edge.type,
|
|
388
|
-
spec: edge.spec,
|
|
389
|
-
...(edge.error ? { error: edge.error } : {}),
|
|
390
|
-
from: edge.from.explain(null, seen),
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
382
|
isDescendantOf (node) {
|
|
395
383
|
for (let p = this; p; p = p.parent) {
|
|
396
384
|
if (p === node)
|
|
@@ -448,6 +436,14 @@ class Node {
|
|
|
448
436
|
return !!bundler && bundler !== this.root
|
|
449
437
|
}
|
|
450
438
|
|
|
439
|
+
get isWorkspace () {
|
|
440
|
+
if (this.isRoot)
|
|
441
|
+
return false
|
|
442
|
+
const { root } = this
|
|
443
|
+
const { type, to } = root.edgesOut.get(this.package.name) || {}
|
|
444
|
+
return type === 'workspace' && to && (to.target === this || to === this)
|
|
445
|
+
}
|
|
446
|
+
|
|
451
447
|
get isRoot () {
|
|
452
448
|
return this === this.root
|
|
453
449
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Sometimes we need to actually do a walk from the root, because you can
|
|
2
|
+
// have a cycle of deps that all depend on each other, but no path from root.
|
|
3
|
+
// Also, since the ideal tree is loaded from the shrinkwrap, it had extraneous
|
|
4
|
+
// flags set false that might now be actually extraneous, and dev/optional
|
|
5
|
+
// flags that are also now incorrect. This method sets all flags to true, so
|
|
6
|
+
// we can find the set that is actually extraneous.
|
|
7
|
+
module.exports = tree => {
|
|
8
|
+
for (const node of tree.inventory.values()) {
|
|
9
|
+
node.extraneous = true
|
|
10
|
+
node.dev = true
|
|
11
|
+
node.devOptional = true
|
|
12
|
+
node.peer = true
|
|
13
|
+
node.optional = true
|
|
14
|
+
}
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@npmcli/arborist",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Manage node_modules trees",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@npmcli/installed-package-contents": "^1.0.5",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"@npmcli/metavuln-calculator": "^1.0.0",
|
|
9
9
|
"@npmcli/name-from-folder": "^1.0.1",
|
|
10
10
|
"@npmcli/node-gyp": "^1.0.0",
|
|
11
|
-
"@npmcli/run-script": "^1.7.
|
|
11
|
+
"@npmcli/run-script": "^1.7.2",
|
|
12
12
|
"bin-links": "^2.2.1",
|
|
13
13
|
"cacache": "^15.0.3",
|
|
14
14
|
"common-ancestor-path": "^1.0.1",
|