diff --git a/pnpm/crates/resolving-deps-resolver/src/resolve_peers/cache.rs b/pnpm/crates/resolving-deps-resolver/src/resolve_peers/cache.rs index 656e24f1f3..73666883d9 100644 --- a/pnpm/crates/resolving-deps-resolver/src/resolve_peers/cache.rs +++ b/pnpm/crates/resolving-deps-resolver/src/resolve_peers/cache.rs @@ -416,12 +416,10 @@ impl Walker<'_> { parent_refs: &ParentRefs, pkg_id: &str, ) -> DeferredChildResolution { - if self - .pure_pkgs - .get(pkg_id) - .is_some_and(|_| self.tree.packages[pkg_id].peer_dependencies.is_empty()) + if let Some(dep_path) = self.pure_pkgs.get(pkg_id) + && self.tree.packages[pkg_id].peer_dependencies.is_empty() { - return DeferredChildResolution::Pure(self.pure_pkgs[pkg_id].clone()); + return DeferredChildResolution::Pure(dep_path.clone()); } if let Some(cached) = self .find_fast_hit_for_lazy(parent_ids, parent_refs, pkg_id) diff --git a/pnpm/crates/resolving-deps-resolver/src/resolve_peers/finalize.rs b/pnpm/crates/resolving-deps-resolver/src/resolve_peers/finalize.rs index 2b40c312b7..23b5421365 100644 --- a/pnpm/crates/resolving-deps-resolver/src/resolve_peers/finalize.rs +++ b/pnpm/crates/resolving-deps-resolver/src/resolve_peers/finalize.rs @@ -1,17 +1,20 @@ -//! The post-walk passes: pending-edge repair, SCC-based cycle -//! detection, the final depPath recomputation that gives every resolved -//! peer its full suffix, and the [`DependenciesGraph`] built from the -//! per-node records keyed by those depPaths. +//! The graph-entry capture each walked node performs, and the post-walk +//! passes it feeds: pending-edge repair, SCC-based cycle detection, the +//! final depPath recomputation that gives every resolved peer its full +//! suffix, and the [`DependenciesGraph`] built from the per-node records +//! keyed by those depPaths. use crate::{ dependencies_graph::{DependenciesGraph, DependenciesGraphNode}, node_id::NodeId, resolve_peers::{ context::{ - link_node_id_as_dep_path, node_id_sort_key, peer_segment_names, pkg_name_version, + SharedChain, link_node_id_as_dep_path, node_id_sort_key, peer_segment_names, + pkg_name_version, }, - walker::Walker, + walker::{MissingPeerInfo, Walker}, }, + resolved_tree::ResolvedPackage, }; use pacquet_deps_path::{DepPath, PeerId, create_peer_dep_graph_hash, link_path_to_peer_version}; use pacquet_resolving_resolver_base::ResolveResult; @@ -45,6 +48,30 @@ pub(super) struct NodeRecord { pub(super) order: u64, } +/// One walked node's contribution to the graph, as +/// [`Walker::record_walked_node`] receives it: the node's identity, the +/// depPath the walk gave it, and the edge and peer sets its graph entry +/// and [`NodeRecord`] are built from. +pub(super) struct WalkedNode<'a> { + pub(super) node_id: &'a NodeId, + pub(super) pkg: &'a ResolvedPackage, + pub(super) dep_path: &'a DepPath, + pub(super) parent_node_ids: &'a SharedChain, + pub(super) parent_pkg_ids_chain: &'a SharedChain, + /// This node's realized `alias → NodeId` children. + pub(super) children: &'a BTreeMap, + /// The depPaths those same children resolved to. + pub(super) child_dep_paths: BTreeMap, + /// Every peer resolved anywhere in this node's subtree. + pub(super) all_resolved_peers: &'a HashMap, + pub(super) all_missing_peers: &'a HashMap, + /// The subset of the above this node declares itself. + pub(super) own_resolved_peers: &'a HashMap, + pub(super) depth: i32, + pub(super) installable: bool, + pub(super) is_pure: bool, +} + /// One `parent → child` edge whose target wasn't walked yet at the /// time the parent's `graph_children` was built. Patched up by /// [`Walker::patch_pending_peer_edges`] after the main walk completes. @@ -61,6 +88,123 @@ struct FinalPeerContext<'a> { } impl Walker<'_> { + /// Record one walked node: its entry in the provisional + /// depPath-keyed graph, and the [`NodeRecord`] the post-walk rebuild + /// consumes. A discovery pass runs neither of the passes that read + /// these, so it never calls this. + pub(super) fn record_walked_node(&mut self, node: WalkedNode<'_>) { + let WalkedNode { + node_id, + pkg, + dep_path, + parent_node_ids, + parent_pkg_ids_chain, + children, + child_dep_paths, + all_resolved_peers, + all_missing_peers, + own_resolved_peers, + depth, + installable, + is_pure, + } = node; + + // The children's depPath edges become this node's graph children. + // Resolved peers become extra edges, aliased by peer name. If a + // peer's depPath isn't known yet — typically a later sibling + // direct dep — defer the edge to the post-walk patch pass; the + // install layer drives off `graph_children`, so skipping the + // edge entirely would leave the peer un-symlinked in the + // parent's slot. + let mut graph_children = BTreeMap::new(); + for (alias, child_node_id) in + self.previously_resolved_children(parent_node_ids, parent_pkg_ids_chain, &pkg.id) + { + self.add_graph_child_or_pending(&mut graph_children, dep_path, alias, child_node_id); + } + for (alias, child_dep_path) in child_dep_paths { + graph_children.insert(alias, child_dep_path); + } + for (peer_alias, peer_node_id) in all_resolved_peers { + self.add_graph_child_or_pending( + &mut graph_children, + dep_path, + peer_alias.clone(), + peer_node_id.clone(), + ); + } + + // Compute transitive peer set: peers visible in this subtree + // that are NOT declared in this package's own peerDependencies. + let mut transitive_peer_dependencies: HashSet = HashSet::default(); + for peer_alias in all_resolved_peers.keys() { + if !pkg.peer_dependencies.contains_key(peer_alias) { + transitive_peer_dependencies.insert(peer_alias.clone()); + } + } + for peer_alias in all_missing_peers.keys() { + if !pkg.peer_dependencies.contains_key(peer_alias) { + transitive_peer_dependencies.insert(peer_alias.clone()); + } + } + + // Capture this node's NodeId-level edges + metadata for the + // post-walk [`Walker::build_final_dep_paths`] rebuild. Edges are + // the node's regular children overlaid with its *own* resolved + // peers — this node's own peer resolution, not the descendants' + // peers bubbled up for the suffix. A peer a descendant resolved + // (e.g. `debug`'s optional `supports-color`) is symlinked at the + // descendant that declares it, so it must not appear in this + // node's dependencies. Carries NodeIds so the rebuild can + // resolve each to its corrected final depPath. + let mut record_edges = + self.previously_resolved_children(parent_node_ids, parent_pkg_ids_chain, &pkg.id); + record_edges.extend(children.clone()); + for (peer_alias, peer_node_id) in own_resolved_peers { + record_edges.insert(peer_alias.clone(), peer_node_id.clone()); + } + let optional_child_aliases = self.optional_child_aliases(&pkg.id, &record_edges); + let record_order = self.next_record_order; + self.next_record_order += 1; + self.node_records.insert( + node_id.clone(), + NodeRecord { + edges: record_edges, + optional_child_aliases: optional_child_aliases.clone(), + transitive_peer_dependencies: transitive_peer_dependencies.clone(), + depth, + installable, + is_pure, + order: record_order, + }, + ); + + // Multiple visits with the same depPath collapse onto the same + // graph entry. On a conflict, keep the entry with the smallest + // `depth` so install order matches. + self.graph + .entry(dep_path.clone()) + .and_modify(|node| { + if node.depth > depth { + node.depth = depth; + } + }) + .or_insert(DependenciesGraphNode { + dep_path: dep_path.clone(), + resolved_package_id: pkg.id.clone(), + resolve_result: Arc::clone(&pkg.result), + children: graph_children, + optional_children: optional_child_aliases, + peer_dependencies: pkg.peer_dependencies.clone(), + transitive_peer_dependencies, + resolved_peer_names: all_resolved_peers.keys().cloned().collect(), + depth, + installable, + is_pure, + optional: pkg.optional, + }); + } + /// Fill in `graph_children` edges that were skipped during the main /// walk because the peer target's `DepPath` hadn't been computed /// yet. Each direct dep's subtree is fully walked by the time diff --git a/pnpm/crates/resolving-deps-resolver/src/resolve_peers/walker.rs b/pnpm/crates/resolving-deps-resolver/src/resolve_peers/walker.rs index bd8fe9d31d..41c894abee 100644 --- a/pnpm/crates/resolving-deps-resolver/src/resolve_peers/walker.rs +++ b/pnpm/crates/resolving-deps-resolver/src/resolve_peers/walker.rs @@ -5,8 +5,7 @@ use crate::{ dependencies_graph::{ - DependenciesGraph, DependenciesGraphNode, MissingPeer, ParentPackageRef, - PeerDependencyIssue, PeerDependencyIssues, + DependenciesGraph, MissingPeer, ParentPackageRef, PeerDependencyIssue, PeerDependencyIssues, }, node_id::NodeId, resolve_peers::{ @@ -22,7 +21,7 @@ use crate::{ scoped_hoisted_optional_parent_refs, }, discovery::PeerDiscoveryCaches, - finalize::{NodeRecord, PendingPeerEdge}, + finalize::{NodeRecord, PendingPeerEdge, WalkedNode}, }, resolved_tree::{ChildEdge, DirectDep, PeerDep, ResolvedPackage, ResolvedTree, TreeChildren}, }; @@ -107,7 +106,7 @@ pub(super) struct Walker<'tree> { /// the post-walk [`Walker::build_final_dep_paths`] / /// [`Walker::build_final_graph`] pass. See [`NodeRecord`]. pub(super) node_records: HashMap, - next_record_order: u64, + pub(super) next_record_order: u64, /// Reverse index over the tree nodes' `previous_dep_path`, built /// only when [`ResolvePeersOptions::resolved_peer_provider_paths`] /// is set. The upstream `nodeIdsByPreviousDepPath`. @@ -325,6 +324,46 @@ impl ChildOutputs { } } +/// The [`ParentRefs`] view a node hands down to its descendants, +/// as [`Walker::build_child_parent_refs`] computes it. +struct ChildParentRefs { + refs: Arc, + /// Only what this node itself contributed: its peer-relevant + /// children. + own: ParentRefs, + /// Whether `refs` says anything the caller's map didn't. `false` + /// lets the caller pass its own parent-context snapshot down + /// instead of rebuilding an identical one. + changed: bool, +} + +/// The peers of one node, as [`Walker::resolve_node_peers`] resolves +/// them, and the depPath the combined set renders to. +struct NodePeers { + /// The node's own `peerDependencies`, resolved. + own_resolved: HashMap, + /// The above folded with what the subtree resolved against + /// ancestors, minus the node's own name — the set `dep_path`'s + /// suffix renders. + all_resolved: HashMap, + all_missing: HashMap, + dep_path: DepPath, +} + +struct NodePeersContext<'a> { + pkg: &'a ResolvedPackage, + pkg_name: &'a str, + /// The augmented refs visible at this node, including its own + /// peer-relevant children. + parent_refs: &'a ParentRefs, + chain_names: &'a SharedChain, + ancestor_pkg_ids: &'a SharedChain, + /// Taken by value: it is the base the combined resolved-peer map is + /// built on, so folding into it costs no extra map. + external_from_children: HashMap, + missing_from_children: &'a HashMap, +} + impl Walker<'_> { pub(super) fn walk(mut self) -> ResolvePeersResult { let importer_parents = Arc::new(self.build_importer_parents()); @@ -577,78 +616,19 @@ impl Walker<'_> { }; let pkg = self.owned_package(&pkg_id); let (provider_children, preview_undo) = self.preview_peer_provider_children(node_id); - let mut refs_changed = locked_peer_names.is_some(); - let parent_parent_refs = if let Some(locked_peer_names) = &locked_peer_names { - Arc::new(scoped_hoisted_optional_parent_refs( - parent_parent_refs, - locked_peer_names, - &self.opts.hoisted_optional_peer_node_ids, - )) - } else { - Arc::clone(parent_parent_refs) - }; let (pkg_name, _pkg_version) = pkg_name_version(&pkg.result); - - // Build the ParentRefs map that descendants of this node see: - // parent's view + this node's own peer-relevant children. Kept - // behind `Arc` copy-on-write: most nodes contribute nothing, so - // they pass the parent's map down by refcount instead of cloning - // it — the per-node map clones dominated the walker's CPU time on - // peer-heavy workspaces. - let mut child_parent_refs = parent_parent_refs; - let mut new_parent_refs = ParentRefs::default(); - for (alias, child_node_id) in &provider_children { - let Some(child_tree) = self.tree.dependencies_tree.get(child_node_id) else { continue }; - let Some(child_pkg) = self.tree.packages.get(&child_tree.resolved_package_id) else { - continue; - }; - insert_parent_ref( - &mut new_parent_refs, - alias, - child_node_id.clone(), - child_pkg, - child_tree.depth, - ); - } - if !new_parent_refs.is_empty() { - refs_changed = true; - let refs = Arc::make_mut(&mut child_parent_refs); - // Built only when a name collision actually consults it — the - // common no-collision node never pays for the extra map clone. - let mut refs_with_new: Option = None; - for (name, mut new_parent_ref) in new_parent_refs.clone() { - if let Some(existing) = refs.get(&name) { - let with_new = refs_with_new.get_or_insert_with(|| { - let mut with_new = refs.clone(); - with_new.extend(new_parent_refs.clone()); - with_new - }); - if !self.parent_refs_match(existing, &new_parent_ref) - || self.inherited_parent_pkg_breaks_peer_diamond( - with_new, - existing, - &new_parent_ref, - node_id, - ) - { - new_parent_ref.occurrence = existing.occurrence + 1; - refs.insert(name, new_parent_ref); - } - } else { - refs.insert(name, new_parent_ref); - } - } - } - - let locked_pins = - self.locked_peer_context_pins(node_id, &pkg, &child_parent_refs, parent_node_ids); - if !locked_pins.is_empty() { - refs_changed = true; - let refs = Arc::make_mut(&mut child_parent_refs); - for (name, parent_ref) in locked_pins { - refs.insert(name, parent_ref); - } - } + let ChildParentRefs { + refs: child_parent_refs, + own: new_parent_refs, + changed: refs_changed, + } = self.build_child_parent_refs( + node_id, + &pkg, + parent_parent_refs, + locked_peer_names.as_deref(), + &provider_children, + parent_node_ids, + ); // Record this node's parent context for the descendants' // [`peers_cache`] lookups. We compute and store the snapshot @@ -788,53 +768,25 @@ impl Walker<'_> { mut missing_summaries, } = child_outputs; - // Resolve this node's own peer requirements against the augmented - // ParentRefs visible at this node, including peer-relevant children. - let mut own_resolved_peers: HashMap = HashMap::default(); - let mut own_missing_peers: HashMap = HashMap::default(); - for (peer_name, peer_dep) in &pkg.peer_dependencies { - self.resolve_one_peer( - &pkg_name, - peer_name, - peer_dep, - &child_parent_refs, - &child_chain_names, - parent_pkg_ids_chain, - &mut own_resolved_peers, - &mut own_missing_peers, - ); - if let Some(peer_node_id) = own_resolved_peers.get(peer_name) { - auto_install_resolved_peers.insert(peer_name.clone(), peer_node_id.clone()); - } - } - - // Combine all resolved peers (this node's own + descendants'). - // Filter out the node's own name (a package doesn't peer-depend - // on itself). - let mut all_resolved_peers = external_from_children; - for (peer_alias, peer_node_id) in &own_resolved_peers { - all_resolved_peers.insert(peer_alias.clone(), peer_node_id.clone()); - } - all_resolved_peers.remove(&pkg_name); - - // Same for missing peers (children + own). - let mut all_missing_peers = missing_from_children.clone(); - for (peer_alias, info) in &own_missing_peers { - all_missing_peers.insert(peer_alias.clone(), info.clone()); - } - - // Construct the depPath. Empty resolved-peers ⇒ pure node: - // depPath = pkgIdWithPatchHash. - let dep_path = if all_resolved_peers.is_empty() { - DepPath::from(pkg.id.clone()) - } else { - let peer_ids: Vec = all_resolved_peers + let NodePeers { + own_resolved: own_resolved_peers, + all_resolved: all_resolved_peers, + all_missing: all_missing_peers, + dep_path, + } = self.resolve_node_peers(NodePeersContext { + pkg: &pkg, + pkg_name: &pkg_name, + parent_refs: &child_parent_refs, + chain_names: &child_chain_names, + ancestor_pkg_ids: parent_pkg_ids_chain, + external_from_children, + missing_from_children: &missing_from_children, + }); + auto_install_resolved_peers.extend( + own_resolved_peers .iter() - .map(|(peer_alias, peer_node_id)| self.build_peer_id(peer_alias, peer_node_id)) - .collect(); - let suffix = create_peer_dep_graph_hash(&peer_ids, self.opts.peers_suffix_max_length); - DepPath::from(format!("{}{}", pkg.id, suffix)) - }; + .map(|(peer_name, peer_node_id)| (peer_name.clone(), peer_node_id.clone())), + ); // Register the depPath ↔ NodeId mapping and per-node // propagated state before inserting into the graph (so any @@ -895,105 +847,21 @@ impl Walker<'_> { } if !self.discovery { - // The children's depPath edges become this node's graph children. - // Resolved peers become extra edges, aliased by peer name. If a - // peer's depPath isn't known yet — typically a later sibling - // direct dep — defer the edge to the post-walk patch pass; the - // install layer drives off `graph_children`, so skipping the - // edge entirely would leave the peer un-symlinked in the - // parent's slot. - let mut graph_children = BTreeMap::new(); - for (alias, child_node_id) in - self.previously_resolved_children(parent_node_ids, parent_pkg_ids_chain, &pkg.id) - { - self.add_graph_child_or_pending( - &mut graph_children, - &dep_path, - alias, - child_node_id, - ); - } - for (alias, child_dep_path) in child_dep_paths { - graph_children.insert(alias, child_dep_path); - } - for (peer_alias, peer_node_id) in all_resolved_peers.iter() { - self.add_graph_child_or_pending( - &mut graph_children, - &dep_path, - peer_alias.clone(), - peer_node_id.clone(), - ); - } - - // Compute transitive peer set: peers visible in this subtree - // that are NOT declared in this package's own peerDependencies. - let mut transitive_peer_dependencies: HashSet = HashSet::default(); - for peer_alias in all_resolved_peers.keys() { - if !pkg.peer_dependencies.contains_key(peer_alias) { - transitive_peer_dependencies.insert(peer_alias.clone()); - } - } - for peer_alias in all_missing_peers.keys() { - if !pkg.peer_dependencies.contains_key(peer_alias) { - transitive_peer_dependencies.insert(peer_alias.clone()); - } - } - - // Capture this node's NodeId-level edges + metadata for the - // post-walk [`Walker::build_final_dep_paths`] rebuild. Edges are - // the node's regular children overlaid with its *own* resolved - // peers — this node's own peer resolution, not the descendants' - // peers bubbled up for the suffix. A peer a descendant resolved - // (e.g. `debug`'s optional `supports-color`) is symlinked at the - // descendant that declares it, so it must not appear in this - // node's dependencies. Carries NodeIds so the rebuild can - // resolve each to its corrected final depPath. - let mut record_edges = - self.previously_resolved_children(parent_node_ids, parent_pkg_ids_chain, &pkg.id); - record_edges.extend(children_map.clone()); - for (peer_alias, peer_node_id) in &own_resolved_peers { - record_edges.insert(peer_alias.clone(), peer_node_id.clone()); - } - let optional_child_aliases = self.optional_child_aliases(&pkg.id, &record_edges); - let record_order = self.next_record_order; - self.next_record_order += 1; - self.node_records.insert( - node_id.clone(), - NodeRecord { - edges: record_edges, - optional_child_aliases: optional_child_aliases.clone(), - transitive_peer_dependencies: transitive_peer_dependencies.clone(), - depth: tree_node_depth, - installable: tree_node_installable, - is_pure, - order: record_order, - }, - ); - - // Multiple visits with the same depPath collapse onto the same - // graph entry. On a conflict, keep the entry with the smallest - // `depth` so install order matches. - self.graph - .entry(dep_path.clone()) - .and_modify(|node| { - if node.depth > tree_node_depth { - node.depth = tree_node_depth; - } - }) - .or_insert(DependenciesGraphNode { - dep_path: dep_path.clone(), - resolved_package_id: pkg.id.clone(), - resolve_result: Arc::clone(&pkg.result), - children: graph_children, - optional_children: optional_child_aliases, - peer_dependencies: pkg.peer_dependencies.clone(), - transitive_peer_dependencies, - resolved_peer_names: all_resolved_peers.keys().cloned().collect(), - depth: tree_node_depth, - installable: tree_node_installable, - is_pure, - optional: pkg.optional, - }); + self.record_walked_node(WalkedNode { + node_id, + pkg: &pkg, + dep_path: &dep_path, + parent_node_ids, + parent_pkg_ids_chain, + children: &children_map, + child_dep_paths, + all_resolved_peers: &all_resolved_peers, + all_missing_peers: &all_missing_peers, + own_resolved_peers: &own_resolved_peers, + depth: tree_node_depth, + installable: tree_node_installable, + is_pure, + }); } self.in_progress.remove(node_id); @@ -1022,6 +890,148 @@ impl Walker<'_> { output } + /// Build the [`ParentRefs`] map that descendants of this node see: + /// the parent's view (scoped down when the node's locked peer names + /// exclude hoisted optional providers), plus the node's own + /// peer-relevant children, plus the pins the wanted lockfile locked + /// in. Kept behind `Arc` copy-on-write: most nodes contribute + /// nothing, so they pass the parent's map down by refcount instead + /// of cloning it — the per-node map clones dominated the walker's + /// CPU time on peer-heavy workspaces. + fn build_child_parent_refs( + &self, + node_id: &NodeId, + pkg: &ResolvedPackage, + parent_parent_refs: &Arc, + locked_peer_names: Option<&HashSet>, + provider_children: &BTreeMap, + parent_node_ids: &SharedChain, + ) -> ChildParentRefs { + let mut refs_changed = locked_peer_names.is_some(); + let mut child_parent_refs = if let Some(locked_peer_names) = locked_peer_names { + Arc::new(scoped_hoisted_optional_parent_refs( + parent_parent_refs, + locked_peer_names, + &self.opts.hoisted_optional_peer_node_ids, + )) + } else { + Arc::clone(parent_parent_refs) + }; + + let mut new_parent_refs = ParentRefs::default(); + for (alias, child_node_id) in provider_children { + let Some(child_tree) = self.tree.dependencies_tree.get(child_node_id) else { continue }; + let Some(child_pkg) = self.tree.packages.get(&child_tree.resolved_package_id) else { + continue; + }; + insert_parent_ref( + &mut new_parent_refs, + alias, + child_node_id.clone(), + child_pkg, + child_tree.depth, + ); + } + if !new_parent_refs.is_empty() { + refs_changed = true; + let refs = Arc::make_mut(&mut child_parent_refs); + // Built only when a name collision actually consults it — the + // common no-collision node never pays for the extra map clone. + let mut refs_with_new: Option = None; + for (name, mut new_parent_ref) in new_parent_refs.clone() { + if let Some(existing) = refs.get(&name) { + let with_new = refs_with_new.get_or_insert_with(|| { + let mut with_new = refs.clone(); + with_new.extend(new_parent_refs.clone()); + with_new + }); + if !self.parent_refs_match(existing, &new_parent_ref) + || self.inherited_parent_pkg_breaks_peer_diamond( + with_new, + existing, + &new_parent_ref, + node_id, + ) + { + new_parent_ref.occurrence = existing.occurrence + 1; + refs.insert(name, new_parent_ref); + } + } else { + refs.insert(name, new_parent_ref); + } + } + } + + let locked_pins = + self.locked_peer_context_pins(node_id, pkg, &child_parent_refs, parent_node_ids); + if !locked_pins.is_empty() { + refs_changed = true; + let refs = Arc::make_mut(&mut child_parent_refs); + for (name, parent_ref) in locked_pins { + refs.insert(name, parent_ref); + } + } + + ChildParentRefs { refs: child_parent_refs, own: new_parent_refs, changed: refs_changed } + } + + /// Resolve this node's own peer requirements against the augmented + /// [`ParentRefs`] visible at it, fold the result with what its + /// children reported, and render the depPath. Empty resolved-peers + /// ⇒ pure node: depPath = `pkgIdWithPatchHash`. + fn resolve_node_peers(&mut self, context: NodePeersContext<'_>) -> NodePeers { + let NodePeersContext { + pkg, + pkg_name, + parent_refs, + chain_names, + ancestor_pkg_ids, + external_from_children, + missing_from_children, + } = context; + + let mut own_resolved: HashMap = HashMap::default(); + let mut own_missing: HashMap = HashMap::default(); + for (peer_name, peer_dep) in &pkg.peer_dependencies { + self.resolve_one_peer( + pkg_name, + peer_name, + peer_dep, + parent_refs, + chain_names, + ancestor_pkg_ids, + &mut own_resolved, + &mut own_missing, + ); + } + + // A package doesn't peer-depend on itself, so its own name never + // enters its suffix. + let mut all_resolved = external_from_children; + for (peer_alias, peer_node_id) in &own_resolved { + all_resolved.insert(peer_alias.clone(), peer_node_id.clone()); + } + all_resolved.remove(pkg_name); + + let mut all_missing = missing_from_children.clone(); + for (peer_alias, info) in &own_missing { + all_missing.insert(peer_alias.clone(), info.clone()); + } + + let dep_path = if all_resolved.is_empty() { + DepPath::from(pkg.id.clone()) + } else { + let peer_ids: Vec = all_resolved + .iter() + .map(|(peer_alias, peer_node_id)| self.build_peer_id(peer_alias, peer_node_id)) + .collect(); + let suffix = create_peer_dep_graph_hash(&peer_ids, self.opts.peers_suffix_max_length); + DepPath::from(format!("{}{}", pkg.id, suffix)) + }; + + NodePeers { own_resolved, all_resolved, all_missing, dep_path } + } + /// The upstream locked-peer-provider reuse block /// (`resolvePeers.ts:594`): for each `peer name → provider DepPath` /// the wanted lockfile recorded on this node, re-pin the provider