zoi-cli 1.24.6

Advanced Package Manager & Environment Orchestrator
Documentation
use crate::pkg::install::resolver;
use anyhow::{Result, anyhow};
use colored::*;
use std::collections::HashSet;

pub fn run(package_names: &[String]) -> Result<()> {
    if package_names.is_empty() {
        println!("{}", "Please specify at least one package name.".yellow());
        return Ok(());
    }

    println!("{} Resolving dependency tree...", "::".bold().blue());

    let (graph, non_zoi_deps) = resolver::resolve_dependency_graph(
        package_names,
        None,
        false,
        true,
        true,
        None,
        true,
        None,
    )?;

    if !non_zoi_deps.is_empty() {
        println!(
            "\n{} External dependencies (non-Zoi):",
            "::".bold().yellow()
        );
        for dep in non_zoi_deps {
            println!("  - {}", dep.dimmed());
        }
    }

    println!("\n{} Dependency tree:", "::".bold().blue());

    let mut visited = HashSet::new();
    for source in package_names {
        if let Some(children) = graph.adj.get("$root") {
            for pkg_id in children {
                let Some(node) = graph.nodes.get(pkg_id) else {
                    continue;
                };
                if source.contains(&node.pkg.name) {
                    print_node(&graph, pkg_id, "", true, &mut visited)?;
                }
            }
        }
    }

    Ok(())
}

fn print_node(
    graph: &resolver::DependencyGraph,
    pkg_id: &str,
    prefix: &str,
    is_last: bool,
    visited: &mut HashSet<String>,
) -> Result<()> {
    let node = graph
        .nodes
        .get(pkg_id)
        .ok_or_else(|| anyhow!("Package not found in graph: {}", pkg_id))?;
    let is_repeated = visited.contains(pkg_id);

    let connector = if is_last { "└── " } else { "├── " };

    let pkg_display = if let Some(sub) = &node.sub_package {
        format!("{}:{}", node.pkg.name.cyan().bold(), sub.yellow())
    } else {
        node.pkg.name.cyan().bold().to_string()
    };

    let version_display = format!("v{}", node.version);
    let repeated_mark = if is_repeated {
        " (*)".dimmed()
    } else {
        "".normal()
    };

    println!(
        "{}{}{}{} {}",
        prefix, connector, pkg_display, repeated_mark, version_display
    );

    if is_repeated {
        return Ok(());
    }
    visited.insert(pkg_id.to_string());

    if let Some(children) = graph.adj.get(pkg_id) {
        let child_count = children.len();
        let mut sorted_children: Vec<_> = children.iter().collect();
        sorted_children.sort();

        for (i, child_id) in sorted_children.iter().enumerate() {
            let new_prefix = format!("{}{}", prefix, if is_last { "    " } else { "" });
            print_node(graph, child_id, &new_prefix, i == child_count - 1, visited)?;
        }
    }

    Ok(())
}