arcature-cli 2026.1.1

Developer lifecycle CLI for Arcature applications.
Documentation
//! `arc release validate` — side-effect-free metadata validation.
//!
//! Loads cargo metadata, discovers the Arcature release crates, and
//! validates their `[package.metadata.arcature]` blocks. Writes nothing to
//! disk and makes no network calls; it is safe to run anywhere and is the
//! CI gate for release metadata (ADR-0005 invariant 7).

use crate::cli::OutputFormat;
use crate::error::CommandError;
use crate::release::{self, ReleaseError};

pub(crate) fn run_validate(format: OutputFormat) -> Result<(), CommandError> {
    let document = release::load_metadata()?;
    let crates = release::discover(&document);
    match release::validate(&crates) {
        Ok(report) => {
            print_ok(&report, format)?;
            Ok(())
        }
        Err(diagnostics) => {
            print_fail(&diagnostics, format)?;
            Err(CommandError::Release(ReleaseError::Validation(diagnostics)))
        }
    }
}

fn print_ok(report: &release::ValidationReport, format: OutputFormat) -> Result<(), CommandError> {
    match format {
        OutputFormat::Human => {
            if report.arcature_workspace {
                println!(
                    "release metadata OK: {} managed crates across {} release unit(s)",
                    report.managed_crates, report.release_units
                );
            } else {
                println!(
                    "no [package.metadata.arcature] found in this workspace; nothing to validate"
                );
            }
            Ok(())
        }
        OutputFormat::Json => {
            let payload = serde_json::json!({
                "ok": true,
                "arcature_workspace": report.arcature_workspace,
                "managed_crates": report.managed_crates,
                "release_units": report.release_units,
            });
            println!("{}", serde_json::to_string_pretty(&payload)?);
            Ok(())
        }
    }
}

fn print_fail(
    diagnostics: &[release::Diagnostic],
    format: OutputFormat,
) -> Result<(), CommandError> {
    match format {
        OutputFormat::Human => {
            eprintln!(
                "release metadata validation failed ({} crates):",
                diagnostics.len()
            );
            for diagnostic in diagnostics {
                eprintln!("  {diagnostic}");
            }
            Ok(())
        }
        OutputFormat::Json => {
            let payload = serde_json::json!({
                "ok": false,
                "diagnostics": diagnostics.iter().map(|d| serde_json::json!({
                    "crate": d.crate_name,
                    "message": d.message,
                })).collect::<Vec<_>>(),
            });
            println!("{}", serde_json::to_string_pretty(&payload)?);
            Ok(())
        }
    }
}