This method is used to calculate the idealTree
, which satisfies
dependencies, complies with the lockfile if one exists, and makes the
changes to the tree that the user has requested.
- Init Tree Load the virtualTree or
actualTree as a starting point. (The
actualTree
is used only if no lockfile exists, or if we have been instructed not to use it.) - Inflate Ancient Lockfile If using a lockfile, and we detect that the
lockfile is from npm v6 or npm v5, attempt to load missing metadata from
the npm registry and/or from
package.json
files. Log a warning for any nodes that could not be inflated in this way. - Apply User Requests If an
add
orrm
action was specified, then update the dependencies in the root project (or in specifiedworkspaces
). For any node so modified, add it to theDeps Queue
. - Build Deps Enter the recursive
buildDeps
process described below. - Fix Dep Flags Calculate dependency flags for all Node objects within the tree.
- Prune Failed Optional Any
optional
dependencies that failed to resolve are removed from the tree. - Check Engine and Platform Ensure that any non-
optional
dependencies are compatible with the current environment.
Iterate over the queue of deps, placing them in the tree and resolving all of their missing dependencies, until all have been visited and placed.
- While the
Deps Queue
contains nodes:- Sort
Deps Queue
by depth, and then alphabetically by node name. - Set
node
to next item inDeps Queue
- If
node
has been seen, or has been removed from the tree, loop. - Set
hasShrinkwrap
andhasBundle
based on the node'snode.hasShrinkwrap
andnode.package.bundleDependencies
fields. - If
complete
is true, andhasShrinkwrap
orhasBundle
are set,- Extract node resolved value into a temp directory
- If
hasBundle
, callloadActual
in the temp directory, rooted on the node - Else, if
hasShrinkwrap
, callloadVirtual
in the temp directory, rooted on the node
- Set
peerSource
to thepeerSetSource
for this node, or the node itself if none is found. - Set
tasks
to empty list - For each
Problem Edges
of the node:- If the edge is marked as an overridden
peer
dependency, loop - Set
source
topeerSource
if edge.type ispeer
, otherwise the node being built. - If a
virtual root
for the node exists, then:- set
virtualRoot
to the node's virtual root - set
vrEdge
to the edge from the virtual root by this edge name - set
vrDep
to the resolution of the edge in the virtual root tree, if valid.
- set
- If
vrDep
is set, and satisfies the edge, then setdep
tovrDep
- Else, set
dep
toNode From Edge
, requiringedge.from
- Push edge and dep onto the
tasks
list
- If the edge is marked as an overridden
- Sort tasks alphabetically by edge name.
- Attempt to
Place Dep
each dep/edge in the task list, returning list ofplaced
nodes. - For each
placed
node:- Push node onto
Deps Queue
- Pre-fetch manifests for all of the node's Problem Edges
- Push node onto
- Loop
- Sort
- Resolve Links For every
Link
node placed within the tree,- If the link target is outside of the project root folder, and
follow
is not set, then do nothing. - Else, if the link target was not already visited, push the link target
onto the
Deps Queue
and restart loop.
- If the link target is outside of the project root folder, and
This creates a new Node in a virtual root to satisfy the edge provided,
and the secondEdge if provided, tracking which nodes along the journey are
required (ie, not peerOptional
dependencies or their deps).
The end result is a Node that lives within a Virtual Root, where all of its
peer dependencies and transitive peer dependencies have been resolved as
well, assigned with an appropriate peerSetSource
so that we know why they
are included.
- If
parent
is not set, then set to newVirtual Root
based onedge.from
- Set
first
toNode From Spec
based onedge.spec
- If
secondEdge
is provided, and not satisfied byfirst
, then setsecond
toNode From Spec
based onsecondEdge.spec
- If
second
is set, and the edge is valid, then setnode
tosecond
. Else: setnode
tofirst
- If
required
containsedge.from
, andedge.type
is notpeerOptional
, add node torequired
- If
required
containssecondEdge.from
andsecondEdge.type
is notpeerOptional
, add node torequired
- If a matching node can be found in the tree above the level of
edge.from
, then return aLink
to the matching node, to prevent infinite recursion ina1->b2->a2->b1->...
cases. - Set
parent.sourceReference
as thepeerSetSource
for the node. Load Peer Set
on the node
Load the set of nodes that need to be loaded as peerDependencies for the node being resolved. This builds up the peerSet within a Virtual Root, so that we can know which versions ought to be placed in the idealTree.
Note that this calls Node From Edge
, which in turn calls back into Load Peer Set
with the same parent, building up the set until it is complete.
- Get the list of peerEdges that are not currently resolved and valid, sorted by name.
- For each edge in peerEdges,
- If the edge is valid and has resolution, loop (this can happen because the function is re-entered for each node as it is resolved, and peerDep sets can overlap)
- Set
parentEdge
to the edge fromnode.parent
by this name - Set
isMine
if parent source reference is either the project root or a workspace. - If
force
, orstrictPeerDeps
is not set andisMine
is false, thenConflict is OK
- If no
edge.to
exists:- If there is no parent edge, call
Node From Edge
with the edge, innode.parent
, loop - Else:
- set
dep
toNode From Edge
with the parentEdge asedge
,node.parent
as theparent
, andedge
assecondEdge
- If the
edge
is now valid, loop - Conflict is ok, or if the dep is not found in the
required
set, loop - Throw
ERESOLVE
failure. (We do not warn for the "allowed" failures, because they may be resolvable later.)
- set
- If there is no parent edge, call
- Else (
edge.to
exists):- Set
current
to the currentedge.to
value - Set
dep
toNode From Edge
with theedge
andrequired
list (do not provideparent
orsecondEdge
) - If
dep
can safely replacecurrent
- call
Node From Edge
withedge
,parent
, andrequired
- loop
- call
- If conflict is ok or
edge.from
is not required, loop - Throw ERESOLVE
- Set
Starting with the dependent node location (or its resolveParent
if a peer
dep), walk up the tree finding the shallowest location within the tree
where it can be safely placed without violating any other dependency
contracts.
This function recurses to place peer dependencies at or above the location
where we are placing dep
. When re-entering, peerEntryEdge
is set to
the non-peer edge that caused the inclusion of the peer set.
- If edge is resolved, and valid, and not within explicitRequests, and its name is not within updateNames, and the edge resolution is not a known vulnerability, return an empty list.
- Set
start
tonode.resolveParent
if edge is a peerDep and node is not the project root, otherwise set tonode
- Set
check
to start - Set
source
to thepeerSetSource
fordep
- While
check
is set, settingcheck
tocheck.resolveParent
at each step:- Set
isSource
totrue
ifcheck
issource
- If
check
has an edge by that name, andcheck
is not atop
node, and thecheck
edge is a peerDep, loop - Set
canPlace
toCan Place Dep
with (dep, check, edge, peerEntryEdge, peerPath, isSource) - If
canPlace
is anything other thanCONFLICT
, then settarget
tocheck
. - Else: break loop
- If
legacyBundling
is set, break loop. (Accept first valid placement) - If
globalStyle
is set, andcheck.resolveParent
is the root of the tree, break loop. (Global style only includes direct deps in top level.)
- Set
- If no target was found, then we know there is a conflict
- if
force
is set,- set
target
toedge.to.resolveParent
(Ie, the parent of whichever unsatisfying version was found in the tree) - set
canPlace
toKEEP
(Keep the conflicting dependency)
- set
- Else: throw ERESOLVE
- if
- At this point, a target has been found, and
canPlace
is eitherKEEP
,REPLACE
, orOK
- If
canPlace
isKEEP
,- If edge is a
peerDep
and the placed node does not satisfy the edge, raise a peer dep conflict warning. If we got to this location, it's because eitherforce
was set, or the peerDep conflict comes from a transitive dependency. In either case, we warn and move on. Prune Dedupable
on the target to remove any nodes that will become extraneous.- Return empty list.
- If edge is a
- Set
virtualRoot
todep.parent
- Clone
dep
tonewDep
- Set
placed
to a new list containingnewDep
- If a child by the same name of
dep
exists intarget.children
- Gather set of all deps that are solely depended upon by the current child or other deps within the set.
- Replace current child with newDep
- Prune all nodes in old dep set that are not valid resolutions within the set of dependencies of the new dep
- For each invalid
edgeIn
of newDep, whereedgeIn
is not theedge
, re-evaluate the now-invalid resolution. We may need to re-resolve nodes that were previously visited, in this case.- Add
edgeIn.from
to Deps Queue - Remove
edgeIn.from
from Deps Seen
- Add
- Else: set
newDep.parent
totarget
- If edge is a peerDep, and newDep does not satisfy edge, raise warning on peer conflict.
- If edge is valid, and edge is not resolving to newDep,
Prune Dedupable
onedge.to
, but do not descend - For each invalid
edgeIn
resolving tonewDep
, whereedge.from
is not within Deps Seen, addedgeIn.from
to Deps Queue - For each node by the same name as
newDep
, descending fromtarget
, call Prune Dedupable, not descending - Place missing or invalid peer deps at or above this location as well.
For each
peer
edge ofnewDep.edgesOut
,- If the
edge.to
is not set, then it's a non-required peerOptional, and can be skipped. Loop. - Set
peerPlaced
toPlace Dep
with (peer, newDep, peerEdge, peerEntryEdge (oredge
), peerPath) - Add all
peerPlaced
nodes toplaced
- If the
- Remove virtualRoot.sourceReference from
virtualRoots
map - Return
placed
Answers the question: can we place dep
in target
safely, along with its
peer deps?
This function is re-entered from Can Place Peers
, with a peerEntryEdge
and peerPath
set in order to avoid infinite regress.
Placement return values:
OK
It is ok to put the dep in this location, and there are no conflicting versions present.KEEP
There is a node present in this location, and we ought to keep it and use it to resolve the edge.REPLACE
There is a node present, but we ought to replace it with the new dependency.CONFLICT
There is a node present which does not meet the dependency, and we cannot safely replace it.
- set
entryEdge
topeerEntryEdge
if set, otherwiseedge
- set
source
topeerSetSource
fordep
- If
isSource
is not true, set it to true iftarget
is equal tosource
- If
isSource
is true, then seteffectiveSource
totarget
. Otherwise, seteffectiveSource
tosource
. - Set
isProjectRoot
andisWorkspace
from effectiveSource - Set
isMine
toisProjectRoot
orisWorkspace
- If target has a child by the name of
edge.name
, then- set
current
to target's child - if the dep matches
current
, then we keep it unless the current doesn't match the edge and the new dep does. If current satisfies edge OR dep does NOT satisfy edge, return KEEP - set
curVer
tocurrent.version
- set
newVer
todep.version
- set
tryReplace
to true ifnewVer
is greater thancurVer
- if
tryReplace
is true and dep can replace current, then:- set result to
Can Place Peers
with (dep, target, edge,REPLACE
, peerEntryEdge, peerPath, isSource) - If
res
is notCONFLICT
, return res
- set result to
- if edge satisfied by current, return
KEEP
- if
preferDedupe
is set or if the edge is a peer dep, andtryReplace
is false, and dep can replace current, try to replace it anyway.- set res to
Can Place Peers
with (dep, target, edge,REPLACE
, peerEntryEdge, peerPath, isSource) - if res is not
CONFLICT
return res
- set res to
- If
isSource
is true, then Check for Conflict Override Cases If this is the only place the thing can go, and the current thing there can potentially be nested, we MAY opt to replace it anyway. The complexity arises when we are replacing a dep set, not merely one node.- set
canReplace
totrue
- If
peerEntryEdge
is set, then:- set
currentPeerSet
to the peer set of thecurrent
node - set
targetEdges
to new Set. This will be the set of all edges coming from the target to nodes within the currentPeerSet. - for each peer of currentPeerSet
- for each
edgeIn
of peer.edgesIn- if the currentPeerSet has edgeIn.from, loop
- if edgeIn.from is not the target, or is not valid, loop
- add edgeIn to
targetEdges
- for each
- for each edge of targetEdges
- set rep to
dep.parent.children(edge.name)
(That is, the replacement that will be placed, coming from the virtualRoot tree) - set current to
edge.to
- if rep is not set, then no replacement is intended.
However, it was still included in the peerSet, so we
need to make sure that it is ok with the other nodes
coming in as part of the new peerSet.
- for each edge as
curEdge
ofcurrent
's edgesOut,- set
newRepDep
(new replacement dependency) todep.parent.children.get(curEdge.name)
(Ie, the replacement node from the virtual root - if curEdge is valid, and newRepDep exists, and
does not satisfy curEdge, set
canReplace
to false, and break
- set
- loop
- for each edge as
- Check if the replacement is already an override of some
sort. If any
rep.edgesIn
is not valid, setoverride
totrue
. - If rep satisfies edge, and is not an override, loop
- Else, we cannot replace. Set
canReplace
to false. - break
- set rep to
- If
canReplace
is true, then we need to determine if we need nesting.- set
needNesting
to `false OUTER
: for each node ofcurrentPeerSet
- set rep to
dep.parent.children.get(node.name)
- if rep, continue (already addressed above)
- for each edge of node.edgesOut
- set repDep to dep.parent.children.get(edge.name)
- if no repDep, loop
- if repDep satisfies edge, loop
- Otherwise, nesting is required. Set
needNesting
to true, and breakOUTER
- set rep to
- If
needNesting
is true, then we delete everything in the current peerSet that does not have a corresponding target dep, and add their dependents to the Deps Queue. Some or all of them MAY end up in the same location, which is fine if allowed.- set
dependents
to a new Set - set
reresolve
to a new Set OUTER
: for each node of currentPeerSet- set rep to
dep.parent.children.get(node.name)
- if rep, loop (already addressed)
- set deps to new Set
- for each edge of node.edgesIn
- if edge.from is target, loop
OUTER
(This is not one that we can replace) - If currentPeerSet has edge.from, loop
- add edge.from to deps
- if edge.from is target, loop
- add node to reresolve
- add each dep in deps to dependents
- set rep to
- for each dependent in dependents,
- add dependent to Deps Queue
- remove dependent from Deps Seen
- for each node of reresolve, remove from tree
- set
- set
- set
- If
canReplace
is true, then- set
ret
toCan Place Peers
with (dep, target, edge,REPLACE
, peerEntryEdge, peerPath, isSource) - if ret is not
CONFLICT
, return ret
- set
- At this point, we know that it is not a deeper dependency that was deduped up to this location, and not something we can replace or accept. This is a peer conflict.
- If conflict is ok, then raise peer conflict warning, and return
KEEP
- set
- Conflicting node at this location, and no justification for
overriding. Return
CONFLICT
- set
- No node by that name here. Check to see if the target perhaps doens't have a child by that name, but wants one, and will not be satisfied by this one.
- If target is not entryEdge.from and target has an edge for dep.name,
- if target edge is not satisfied by dep, return
CONFLICT
- if target edge is not satisfied by dep, return
- Check for deduping dependents that may be upset by the placement. If target is not entryEdge.from, then set current to target.resolve(dep.name)
- If current, then for each edge of current.edgesIn
- if edge.from is a descendant of target, and the edge is valid, and
the edge is not satisfied by dep, then return
CONFLICT
- if edge.from is a descendant of target, and the edge is valid, and
the edge is not satisfied by dep, then return
- No objections! Return
Can Place Peers
(dep, target, edge,OK
, peerEntryEdge, peerPath, isSource)
Answers the question: can we place the peers of dep
at or above this
target. If so, return the returnValue
(the original return value of
placing dep
in the target without peers). If not, return CONFLICT
.
This always operates on a dep
in a virtual root, where its peers have
been placed already. We check each one for placement within target
.
Note that they might not actually end up in target
, but this
establishes that they could, and thus have at least one valid resolution.
If they cannot be placed in target
, then it means something else is there
which will prevent the valid placement of dep
anyway.
- If
dep.parent
is not set (because the node was already removed or replaced in the peer set), returnreturnValue
- if a
peerEntryEdge
is set, and thepeerPath
includes the dep (cycle), returnreturnValue
- Set
entryEdge
topeerEntryEdge
if set, otherwiseedge
- Set
peerPath
to the concatenation ofpeerPath
anddep
- For each
peerEdge
of dep.edgesOut- If peerEdge.to is not set, loop
- set
peer
topeerEdge.to
- set
canPlacePeer
toCan Place Dep
with (peer, target, peerEdge, entryEdge, peerPath, isSource). This re-enters theCan Place Dep
flow with the peer, noting that it is part of a peer set, so only its un-checked peers will still be checked for placement. - If
canPlacePeer
isCONFLICT
, returnCONFLICT
- Loop
- Return
returnValue
Figure out which edges of a node have to get some attention paid to them.
Return the set of node.edgesOut
where:
- The edge is not a bundled dependency (unless the bundling node is the project root)
- The edge resolves to a module that is already a known load failure
- The edge does not have a resolution, and either its type is not
peerOptional
or it is in theexplicitRequests
- One of the following are true:
- The edge is not valid
- The
updateNames
includes the edge name - The edge resolution is marked as vulnerable
- The edge is present in
explicitRequests
- If
node.canDedupe()
, setnode.root
to null, and return - If
descend
,- For each
node.children
, sorted bylocation
, callPrune Dedupable
- For each
node.fsChildren
, sorted by location, callPrune Dedupable
- For each
Create a virtual parent node based on the provided node, so that we have a safe space in which to resolve its set of dependencies and their peerDependencies, prior to moving into the idealTree.
This is a bit like growing a small bush that will be grafted onto the tree we are building.
- If reuse, and virtual root exists for
node
, return the virtual root previously created for node - Set
virtualRoot
to a new Node using the source node'srealpath
and setting the source node assourceReference
- For all children of node,
- if child is a link, create a new node at the child's
realpath
, rooted onvirtualRoot
. This ensures that subsequent resolutions offile:
dependencies will be found within the virtual root tree.
- if child is a link, create a new node at the child's
- Save the
virtualRoot
as the virtual root fornode
, and return it