arcature-cli 2026.1.1

Developer lifecycle CLI for Arcature applications.
Documentation
//! `arc release graph` — show the publishable dependency graph (RV2.4).
//!
//! Loads cargo metadata, discovers the publishable crates, builds the
//! crate and unit graphs, and prints the topological publish order.
//! Side-effect-free: no writes, no network.

use crate::cli::OutputFormat;
use crate::error::CommandError;
use crate::release;

pub(crate) fn run_graph(format: OutputFormat) -> Result<(), CommandError> {
    let document = release::load_metadata()?;
    let crates = release::discover(&document);
    release::validate(&crates).map_err(|diagnostics| {
        CommandError::Release(release::ReleaseError::Validation(diagnostics))
    })?;

    let crate_graph = release::build_crate_graph(&document, &crates);
    let unit_graph = release::build_unit_graph(&crate_graph);

    let crate_order = crate_graph.topological_order().map_err(|topo_err| {
        CommandError::Release(release::ReleaseError::Validation(vec![
            release::Diagnostic {
                crate_name: "graph".to_string(),
                message: format!("{topo_err}"),
            },
        ]))
    })?;
    let unit_order = unit_graph.topological_order().map_err(|topo_err| {
        CommandError::Release(release::ReleaseError::Validation(vec![
            release::Diagnostic {
                crate_name: "graph".to_string(),
                message: format!("{topo_err}"),
            },
        ]))
    })?;

    match format {
        OutputFormat::Human => {
            println!("Publishable crates: {}", crate_graph.nodes.len());
            println!("Release units: {}", unit_graph.nodes.len());
            println!();
            println!("Crate topological order:");
            for (i, name) in crate_order.iter().enumerate() {
                println!("  {}. {name}", i + 1);
            }
            println!();
            println!("Unit topological order:");
            for (i, name) in unit_order.iter().enumerate() {
                let node = &unit_graph.nodes[name];
                println!("  {}. {name} (members: {})", i + 1, node.members.len());
            }
            Ok(())
        }
        OutputFormat::Json => {
            let payload = serde_json::json!({
                "crate_count": crate_graph.nodes.len(),
                "unit_count": unit_graph.nodes.len(),
                "crate_topological_order": crate_order,
                "unit_topological_order": unit_order,
                "units": unit_graph.nodes.iter().map(|(name, node)| {
                    serde_json::json!({
                        "name": name,
                        "members": node.members,
                        "dependencies": node.dependencies.keys().collect::<Vec<_>>(),
                    })
                }).collect::<Vec<_>>(),
            });
            println!("{}", serde_json::to_string_pretty(&payload)?);
            Ok(())
        }
    }
}