arcature-cli 2026.2.0

Developer lifecycle CLI for Arcature applications.
Documentation
//! Graph data types (RV2.4).
//!
//! The crate graph is the real internal dependency DAG from cargo metadata,
//! restricted to publishable Arcature crates and their Arcature sibling
//! edges. The unit graph collapses crates to their release units — so
//! `arcature` and `arcature-dx` become one `core` node, and the topological
//! order over units determines publish order at the unit level.
//!
//! Both graphs use `BTreeMap`/`BTreeSet` for deterministic iteration. The
//! same input always produces byte-identical output (ADR-0005 invariant 15).

use std::collections::{BTreeMap, BTreeSet};

use super::topo::topological_order;

/// Whether a dependency edge is a production (normal/build) or dev-only
/// dependency. Both affect publish order (cargo resolves dev-deps from the
/// registry), but only production edges couple release units.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum DepKind {
    /// Normal or build dependency.
    Production,
    /// Dev-only dependency (affects publish order, not unit coupling).
    Dev,
}

/// One node in the crate graph: a publishable crate and its Arcature
/// sibling dependency edges.
#[derive(Debug, Clone)]
pub(crate) struct CrateNode {
    #[allow(dead_code)]
    pub(crate) name: String,
    pub(crate) release_unit: String,
    /// Sibling crate -> dependency kind. Sorted by crate name (BTreeMap).
    pub(crate) dependencies: BTreeMap<String, DepKind>,
}

/// The publishable crate graph: crate name -> node. This is the real
/// internal dependency DAG from cargo metadata, restricted to
/// publishable Arcature crates and their Arcature sibling edges.
#[derive(Debug, Clone)]
pub(crate) struct CrateGraph {
    pub(crate) nodes: BTreeMap<String, CrateNode>,
}

impl CrateGraph {
    /// Return the topological publish order of the crates, or an error if
    /// the graph contains a cycle. Leaves (no dependencies) come first.
    pub(crate) fn topological_order(&self) -> Result<Vec<String>, super::topo::TopoError> {
        let edges: BTreeMap<String, BTreeSet<String>> = self
            .nodes
            .iter()
            .map(|(name, node)| (name.clone(), node.dependencies.keys().cloned().collect()))
            .collect();
        topological_order(&edges)
    }
}

/// One node in the unit graph: a release unit and its dependency edges to
/// other units (production only — dev edges don't couple units).
#[derive(Debug, Clone)]
pub(crate) struct UnitNode {
    #[allow(dead_code)]
    pub(crate) name: String,
    /// Member crate names in this unit.
    pub(crate) members: BTreeSet<String>,
    /// Dependency unit name -> set of crate-level edges that produced it.
    /// Production edges only.
    pub(crate) dependencies: BTreeMap<String, BTreeSet<String>>,
}

/// The release-unit graph: unit name -> node. Edges are derived from the
/// crate graph by collapsing crates to their release units. Dev-only
/// edges are excluded (they affect publish order but not unit coupling).
#[derive(Debug, Clone)]
pub(crate) struct UnitGraph {
    pub(crate) nodes: BTreeMap<String, UnitNode>,
}

impl UnitGraph {
    /// Return the topological publish order of the units, or an error if
    /// the unit graph contains a cycle.
    pub(crate) fn topological_order(&self) -> Result<Vec<String>, super::topo::TopoError> {
        let edges: BTreeMap<String, BTreeSet<String>> = self
            .nodes
            .iter()
            .map(|(name, node)| (name.clone(), node.dependencies.keys().cloned().collect()))
            .collect();
        topological_order(&edges)
    }
}