arcature-cli 2026.2.0

Developer lifecycle CLI for Arcature applications.
Documentation
//! Shell out to the app's `arcature-metadata` binary and deserialize the
//! canonical UAG JSON.
//!
//! Mirrors the `contracts::execute` pattern: run
//! `cargo run --quiet --package <backend_package> --bin arcature-metadata`,
//! capture stdout, deserialize as the canonical [`Uag`]. This is
//! side-effect-free from the CLI's perspective — the app binary itself must
//! not boot infrastructure (PROGRAM.md §"Side-effect-free inspection"). The
//! deserialization target is the single schema type `arcature-build` owns,
//! so the CLI reads the full UAG (modules, routes, services, pages, and the
//! typed action/query/query-string field shapes) instead of a
//! hand-maintained subset that silently dropped fields (ADR-0006 §2).
//!
//! The freshness mechanism is unchanged: the CLI always regenerates the
//! manifest by running the live app's `arcature-metadata` binary, so it
//! reflects the application as currently compiled (never a stale cached
//! artifact). Only the deserialization target type changed.

use crate::process::{ProcessSpec, run_capture};
use crate::project::ProjectConfig;

use arcature_build::uag::Uag;

/// Load the canonical UAG by shelling out to the app's `arcature-metadata`
/// binary. The binary emits the full UAG JSON — modules, routes, services,
/// pages, and the typed action/query/query-string field shapes — never booted
/// infrastructure. Deserializing into [`Uag`] (the single schema type
/// `arcature-build` owns) means the CLI surfaces the full graph and can never
/// silently drop fields the way a hand-maintained mirror could.
///
/// The error is a typed [`String`] only because it flows into the existing
/// [`crate::error::CommandError::Metadata`] variant; serde's deserialization
/// error already carries line/column context, which the formatted message
/// preserves verbatim.
pub(crate) fn load(project: &ProjectConfig) -> Result<Uag, String> {
    let output = run_capture(&ProcessSpec::new("cargo", project.root()).args([
        "run",
        "--quiet",
        "--package",
        &project.backend_package,
        "--bin",
        "arcature-metadata",
    ]))
    .map_err(|error| format!("cannot inspect the application metadata: {error}"))?;
    serde_json::from_slice(&output)
        .map_err(|error| format!("arcature-metadata did not emit valid JSON: {error}"))
}