1use crate::pkg::install::resolver;
2use anyhow::{Result, anyhow};
3use colored::*;
4use std::collections::HashSet;
5
6pub fn run(package_names: &[String]) -> Result<()> {
7 if package_names.is_empty() {
8 println!("{}", "Please specify at least one package name.".yellow());
9 return Ok(());
10 }
11
12 println!("{} Resolving dependency tree...", "::".bold().blue());
13
14 let (graph, non_zoi_deps) =
15 resolver::resolve_dependency_graph(package_names, None, false, true, true, None, true)?;
16
17 if !non_zoi_deps.is_empty() {
18 println!(
19 "\n{} External dependencies (non-Zoi):",
20 "::".bold().yellow()
21 );
22 for dep in non_zoi_deps {
23 println!(" - {}", dep.dimmed());
24 }
25 }
26
27 println!("\n{} Dependency tree:", "::".bold().blue());
28
29 let mut visited = HashSet::new();
30 for source in package_names {
31 if let Some(children) = graph.adj.get("$root") {
32 for pkg_id in children {
33 let Some(node) = graph.nodes.get(pkg_id) else {
34 continue;
35 };
36 if source.contains(&node.pkg.name) {
37 print_node(&graph, pkg_id, "", true, &mut visited)?;
38 }
39 }
40 }
41 }
42
43 Ok(())
44}
45
46fn print_node(
47 graph: &resolver::DependencyGraph,
48 pkg_id: &str,
49 prefix: &str,
50 is_last: bool,
51 visited: &mut HashSet<String>,
52) -> Result<()> {
53 let node = graph
54 .nodes
55 .get(pkg_id)
56 .ok_or_else(|| anyhow!("Package not found in graph: {}", pkg_id))?;
57 let is_repeated = visited.contains(pkg_id);
58
59 let connector = if is_last { "└── " } else { "├── " };
60
61 let pkg_display = if let Some(sub) = &node.sub_package {
62 format!("{}:{}", node.pkg.name.cyan().bold(), sub.yellow())
63 } else {
64 node.pkg.name.cyan().bold().to_string()
65 };
66
67 let version_display = format!("v{}", node.version);
68 let repeated_mark = if is_repeated {
69 " (*)".dimmed()
70 } else {
71 "".normal()
72 };
73
74 println!(
75 "{}{}{}{} {}",
76 prefix, connector, pkg_display, repeated_mark, version_display
77 );
78
79 if is_repeated {
80 return Ok(());
81 }
82 visited.insert(pkg_id.to_string());
83
84 if let Some(children) = graph.adj.get(pkg_id) {
85 let child_count = children.len();
86 let mut sorted_children: Vec<_> = children.iter().collect();
87 sorted_children.sort();
88
89 for (i, child_id) in sorted_children.iter().enumerate() {
90 let new_prefix = format!("{}{}", prefix, if is_last { " " } else { "│ " });
91 print_node(graph, child_id, &new_prefix, i == child_count - 1, visited)?;
92 }
93 }
94
95 Ok(())
96}