Skip to content

Commit 8c51eff

Browse files
committed
refactor: pet
1 parent 2c8e938 commit 8c51eff

File tree

16 files changed

+1991
-1899
lines changed

16 files changed

+1991
-1899
lines changed
Lines changed: 399 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,399 @@
1+
use anyhow::Result;
2+
use petgraph::graph::{EdgeIndex, NodeIndex};
3+
4+
use crate::model::graph::{DependencyGraph, FindResult, PackageNode};
5+
use crate::model::node::EdgeType;
6+
use crate::util::config::get_legacy_peer_deps;
7+
use crate::util::logger::{PROGRESS_BAR, log_progress};
8+
use crate::util::registry::{ResolvedPackage, resolve_dependency};
9+
10+
/// Represents dependency edge information extracted from the graph
11+
/// This structure improves readability compared to using tuples
12+
#[derive(Debug, Clone)]
13+
struct DependencyEdgeInfo {
14+
edge_id: EdgeIndex,
15+
name: String,
16+
spec: String,
17+
edge_type: EdgeType,
18+
is_valid: bool,
19+
target: Option<NodeIndex>,
20+
}
21+
22+
/// Represents node type information for dependency propagation
23+
/// Used to avoid borrowing conflicts when updating node types
24+
#[derive(Debug, Clone, Copy)]
25+
struct NodeTypeInfo {
26+
is_root: bool,
27+
is_prod: bool,
28+
is_dev: bool,
29+
is_optional: bool,
30+
}
31+
32+
/// Collect dependency edges from a node and convert to structured info
33+
/// This helper function improves code readability by avoiding complex tuple destructuring
34+
fn collect_dependency_edges(
35+
graph: &DependencyGraph,
36+
node_index: NodeIndex,
37+
) -> Vec<DependencyEdgeInfo> {
38+
let dep_edges = graph.get_dependency_edges(node_index);
39+
dep_edges
40+
.into_iter()
41+
.map(|(edge_id, dep)| DependencyEdgeInfo {
42+
edge_id,
43+
name: dep.name.clone(),
44+
spec: dep.spec.clone(),
45+
edge_type: dep.edge_type.clone(),
46+
is_valid: dep.valid,
47+
target: dep.to,
48+
})
49+
.collect()
50+
}
51+
52+
/// Build dependency tree using single-threaded BFS traversal
53+
pub async fn build_deps(graph: &mut DependencyGraph) -> Result<()> {
54+
let legacy_peer_deps = get_legacy_peer_deps().await;
55+
tracing::debug!("going to build deps for root, legacy_peer_deps: {legacy_peer_deps}");
56+
57+
let mut current_level = vec![graph.root_index];
58+
59+
while !current_level.is_empty() {
60+
let level_count = current_level.len();
61+
tracing::debug!("Starting new dependency level with {level_count} nodes");
62+
63+
let mut next_level = Vec::new();
64+
65+
for node_index in current_level {
66+
// Collect all dependency edges from this node (both valid and invalid)
67+
// We extract edge info into a dedicated struct to avoid borrow conflicts
68+
// and improve code readability
69+
let dependency_edges = collect_dependency_edges(graph, node_index);
70+
71+
// Count unresolved edges for progress bar tracking
72+
let unresolved_count = dependency_edges
73+
.iter()
74+
.filter(|edge| !edge.is_valid)
75+
.count();
76+
PROGRESS_BAR.inc_length(unresolved_count as u64);
77+
log_progress(&format!(
78+
"resolving {}",
79+
graph.get_node(node_index).unwrap().name
80+
));
81+
82+
for edge_info in dependency_edges {
83+
// Handle already resolved edges (e.g., workspace edges)
84+
if edge_info.is_valid {
85+
if let Some(target_idx) = edge_info.target {
86+
let target_node = graph.get_node(target_idx).unwrap();
87+
// Only add workspace nodes to next level when from root
88+
if target_node.is_workspace() && node_index == graph.root_index {
89+
next_level.push(target_idx);
90+
}
91+
}
92+
continue;
93+
}
94+
95+
tracing::debug!(
96+
"going to build deps {}@{} from [{:?}]",
97+
edge_info.name,
98+
edge_info.spec,
99+
node_index
100+
);
101+
102+
let start_time = std::time::Instant::now();
103+
104+
// Find installation location
105+
match graph.find_compatible_node(node_index, &edge_info.name, &edge_info.spec) {
106+
FindResult::Reuse(existing_index) => {
107+
tracing::debug!(
108+
"resolved deps {}@{} => {} (reuse) took {:?}",
109+
edge_info.name,
110+
edge_info.spec,
111+
graph.get_node(existing_index).unwrap().version,
112+
start_time.elapsed()
113+
);
114+
115+
// Mark edge as resolved
116+
graph.mark_dependency_resolved(edge_info.edge_id, existing_index);
117+
118+
// Update target node type based on edge type
119+
update_node_type_from_edge(
120+
graph,
121+
node_index,
122+
existing_index,
123+
&edge_info.edge_type,
124+
);
125+
}
126+
FindResult::Conflict(conflict_parent) | FindResult::New(conflict_parent) => {
127+
tracing::debug!(
128+
"Conflict/New found for {}@{}, resolving...",
129+
edge_info.name,
130+
edge_info.spec
131+
);
132+
133+
// Check if there's an override for this dependency
134+
let effective_spec = graph
135+
.check_override(node_index, &edge_info.name, &edge_info.spec)
136+
.unwrap_or_else(|| edge_info.spec.clone());
137+
138+
// Resolve dependency from registry with effective spec
139+
let resolved = match resolve_dependency(
140+
&edge_info.name,
141+
&effective_spec,
142+
&edge_info.edge_type,
143+
)
144+
.await?
145+
{
146+
Some(resolved) => {
147+
tracing::debug!(
148+
"Resolved dependency {}@{} => {}",
149+
edge_info.name,
150+
edge_info.spec,
151+
resolved.version
152+
);
153+
resolved
154+
}
155+
None => {
156+
tracing::debug!(
157+
"No resolution found for {}@{}",
158+
edge_info.name,
159+
edge_info.spec
160+
);
161+
PROGRESS_BAR.inc(1);
162+
continue;
163+
}
164+
};
165+
166+
PROGRESS_BAR.inc(1);
167+
tracing::debug!(
168+
"resolved deps {}@{} => {} (conflict/new) took {:?}",
169+
edge_info.name,
170+
edge_info.spec,
171+
resolved.version,
172+
start_time.elapsed()
173+
);
174+
175+
// Create new node
176+
let new_node = place_deps(
177+
edge_info.name.clone(),
178+
resolved.clone(),
179+
conflict_parent,
180+
graph,
181+
);
182+
let new_index = graph.add_node(new_node);
183+
184+
// Add physical edge
185+
graph.add_physical_edge(conflict_parent, new_index);
186+
187+
// Mark edge as resolved
188+
graph.mark_dependency_resolved(edge_info.edge_id, new_index);
189+
190+
// Update target node type based on edge type
191+
update_node_type_from_edge(
192+
graph,
193+
node_index,
194+
new_index,
195+
&edge_info.edge_type,
196+
);
197+
198+
// Add dependencies of the new node
199+
add_dependency_edges(
200+
graph,
201+
new_index,
202+
&resolved.manifest,
203+
legacy_peer_deps,
204+
)
205+
.await;
206+
207+
// Add to next level for processing
208+
next_level.push(new_index);
209+
}
210+
}
211+
}
212+
}
213+
214+
tracing::debug!("Level completed, next level has {} nodes", next_level.len());
215+
current_level = next_level;
216+
}
217+
218+
Ok(())
219+
}
220+
221+
/// Create a new package node
222+
fn place_deps(
223+
name: String,
224+
pkg: ResolvedPackage,
225+
parent: NodeIndex,
226+
graph: &DependencyGraph,
227+
) -> PackageNode {
228+
let parent_node = graph.get_node(parent).unwrap();
229+
let path = if parent_node.path.to_string_lossy().is_empty()
230+
|| parent_node.path == std::path::Path::new(".")
231+
{
232+
std::path::PathBuf::from(format!("node_modules/{name}"))
233+
} else {
234+
parent_node.path.join(format!("node_modules/{name}"))
235+
};
236+
237+
let new_node = PackageNode::new(name.clone(), path, pkg.manifest);
238+
239+
tracing::debug!(
240+
"\nInstalling {}@{} under parent {:?}",
241+
new_node.name,
242+
new_node.version,
243+
parent
244+
);
245+
246+
new_node
247+
}
248+
249+
/// Add dependency edges for a node
250+
async fn add_dependency_edges(
251+
graph: &mut DependencyGraph,
252+
node_index: NodeIndex,
253+
manifest: &serde_json::Value,
254+
legacy_peer_deps: bool,
255+
) {
256+
let dep_types = if legacy_peer_deps {
257+
vec![
258+
("dependencies", EdgeType::Prod),
259+
("optionalDependencies", EdgeType::Optional),
260+
]
261+
} else {
262+
vec![
263+
("dependencies", EdgeType::Prod),
264+
("peerDependencies", EdgeType::Peer),
265+
("optionalDependencies", EdgeType::Optional),
266+
]
267+
};
268+
269+
for (field, edge_type) in dep_types {
270+
if let Some(deps) = manifest.get(field).and_then(|v| v.as_object()) {
271+
tracing::debug!(
272+
"Processing {} dependencies for {}",
273+
field,
274+
graph.get_node(node_index).unwrap().name
275+
);
276+
for (name, version) in deps {
277+
let version_spec = version.as_str().unwrap_or("").to_string();
278+
graph.add_dependency_edge(
279+
node_index,
280+
name.clone(),
281+
version_spec,
282+
edge_type.clone(),
283+
);
284+
tracing::debug!(
285+
"add edge {}@{} for {}",
286+
name,
287+
version,
288+
graph.get_node(node_index).unwrap().name
289+
);
290+
}
291+
tracing::debug!(
292+
"Finished processing {} dependencies for {}",
293+
field,
294+
graph.get_node(node_index).unwrap().name
295+
);
296+
}
297+
}
298+
}
299+
300+
/// Update target node type based on source node and edge type
301+
///
302+
/// This function propagates dependency types through the graph according to npm rules:
303+
/// - Root dependencies directly set the target node type
304+
/// - Prod dependencies propagate through prod edges
305+
/// - Dev/Optional flags propagate only when appropriate
306+
fn update_node_type_from_edge(
307+
graph: &mut DependencyGraph,
308+
from_index: NodeIndex,
309+
to_index: NodeIndex,
310+
edge_type: &EdgeType,
311+
) {
312+
// Extract source node information to avoid borrowing conflicts
313+
// We need these flags to determine how to mark the target node
314+
let source_node_info = {
315+
let from_node = graph.get_node(from_index).unwrap();
316+
NodeTypeInfo {
317+
is_root: from_node.is_root(),
318+
is_prod: from_node.is_prod,
319+
is_dev: from_node.is_dev,
320+
is_optional: from_node.is_optional,
321+
}
322+
};
323+
324+
// Update target node based on source node type and edge type
325+
let to_node = graph.get_node_mut(to_index).unwrap();
326+
327+
// Root node dependencies directly determine target type
328+
if source_node_info.is_root {
329+
match edge_type {
330+
EdgeType::Prod => {
331+
to_node.is_prod = true;
332+
to_node.is_dev = false;
333+
to_node.is_optional = false;
334+
to_node.is_peer = false;
335+
}
336+
EdgeType::Dev => {
337+
if !to_node.is_prod {
338+
to_node.is_dev = true;
339+
to_node.is_optional = false;
340+
}
341+
}
342+
EdgeType::Optional => {
343+
if !to_node.is_prod && !to_node.is_dev {
344+
to_node.is_optional = true;
345+
}
346+
}
347+
EdgeType::Peer => {
348+
if !to_node.is_prod && !to_node.is_dev {
349+
to_node.is_peer = true;
350+
}
351+
}
352+
}
353+
} else {
354+
// If source is prod, and edge is prod, target must be prod
355+
if source_node_info.is_prod && *edge_type == EdgeType::Prod {
356+
to_node.is_prod = true;
357+
to_node.is_dev = false;
358+
to_node.is_optional = false;
359+
to_node.is_peer = false;
360+
}
361+
// Propagate dev
362+
else if source_node_info.is_dev && *edge_type == EdgeType::Dev && !to_node.is_prod {
363+
to_node.is_dev = true;
364+
}
365+
// Propagate optional
366+
else if source_node_info.is_optional
367+
&& *edge_type == EdgeType::Optional
368+
&& !to_node.is_prod
369+
&& !to_node.is_dev
370+
{
371+
to_node.is_optional = true;
372+
}
373+
}
374+
}
375+
376+
#[cfg(test)]
377+
mod tests {
378+
use super::*;
379+
use serde_json::json;
380+
use std::path::PathBuf;
381+
382+
#[test]
383+
fn test_place_deps() {
384+
let pkg = json!({"name": "root", "version": "1.0.0"});
385+
let graph = DependencyGraph::new(PathBuf::from("."), pkg);
386+
387+
let resolved = ResolvedPackage {
388+
name: "lodash".to_string(),
389+
version: "4.17.21".to_string(),
390+
manifest: json!({"name": "lodash", "version": "4.17.21"}),
391+
};
392+
393+
let new_node = place_deps("lodash".to_string(), resolved, graph.root_index, &graph);
394+
395+
assert_eq!(new_node.name, "lodash");
396+
assert_eq!(new_node.version, "4.17.21");
397+
assert_eq!(new_node.path, PathBuf::from("node_modules/lodash"));
398+
}
399+
}

0 commit comments

Comments
 (0)