arcature-cli 2026.2.0

Developer lifecycle CLI for Arcature applications.
Documentation
//! `arc new` — generate a certified Arcature application (RV2.12).
//!
//! Generated projects are reproducible against a certified Platform set,
//! not "latest of each crate" (ADR-0005 invariant 14). The `arcature` and
//! `arcature-build` dependency versions in the generated `Cargo.toml` and the
//! `@arcature/client` version in the generated `frontend/package.json` are
//! derived from the committed Platform manifest (`platform/<version>.toml`),
//! not from hardcoded template literals. `arcature-build` is its own release
//! unit (ADR-0006 §6), so it carries its own certified version, distinct from
//! `arcature`'s. The client version is the *real shipping npm dependency* —
//! never a monorepo `file:` path (ADR-0006 §6).

use std::path::Path;

use arcature_templates::{CertifiedVersions, Database, Frontend as TemplateFrontend, Preset};

use crate::cli::NewOptions;
use crate::error::CommandError;
use crate::project::Frontend;
use crate::release::{self, ReleaseError};

/// The committed Platform manifest to use for `arc new`.
const PLATFORM_MANIFEST: &str = "platform/2026.2.toml";

pub(crate) fn execute(options: NewOptions) -> Result<(), CommandError> {
    let frontend = match Frontend::from(options.frontend) {
        Frontend::React => TemplateFrontend::React,
        Frontend::Vue => TemplateFrontend::Vue,
    };
    let database = if options.no_db {
        Database::Disabled
    } else {
        Database::Enabled
    };
    let preset = Preset::new(frontend, database);

    // Read the certified arcature + arcature-build + @arcature/client versions
    // from the Platform manifest (ADR-0005 invariant 14). The client version is
    // the real shipping npm dependency written into package.json — never a
    // monorepo `file:` path.
    let versions = read_certified_versions()?;

    arcature_templates::generate(&options.destination, preset, &versions)?;
    println!("created {}", options.destination.display());
    println!(
        "  certified platform: arcature v{}, arcature-build v{}",
        versions.arcature, versions.build
    );
    println!("next: cd {} && arc dev", options.destination.display());
    Ok(())
}

/// Read the certified `arcature`, `arcature-build`, and `@arcature/client`
/// versions from the committed Platform manifest. These are the versions
/// written into the generated project's `Cargo.toml` and
/// `frontend/package.json`. `arcature-build` is a separate release unit from
/// `arcature` (ADR-0006 §6) and may sit in a different YBF generation in the
/// same Platform set.
///
/// If the Platform manifest is not found on disk (e.g. when `arc new` runs
/// from outside the workspace), fall back to the certified versions embedded
/// in the stack contract (`contract.toml`), which is compiled into the CLI
/// binary via `include_str!` and is always available.
fn read_certified_versions() -> Result<CertifiedVersions, CommandError> {
    // Try the committed Platform manifest first.
    let path = Path::new(PLATFORM_MANIFEST);
    if path.exists() {
        let text = std::fs::read_to_string(path).map_err(|e| {
            CommandError::Release(ReleaseError::Validation(vec![release::Diagnostic {
                crate_name: "platform".to_string(),
                message: format!("cannot read Platform manifest {PLATFORM_MANIFEST}: {e}"),
            }]))
        })?;

        let arcature_version = text.lines().find_map(|line| {
            let trimmed = line.trim();
            trimmed
                .strip_prefix("arcature = ")
                .map(|rest| rest.trim().trim_matches('"').to_string())
        });
        let build_version = text.lines().find_map(|line| {
            let trimmed = line.trim();
            trimmed
                .strip_prefix("arcature-build = ")
                .map(|rest| rest.trim().trim_matches('"').to_string())
        });
        let client_version = text.lines().find_map(|line| {
            let trimmed = line.trim();
            trimmed
                .strip_prefix("arcature_client_version = ")
                .map(|rest| rest.trim().trim_matches('"').to_string())
        });

        if let (Some(arcature_version), Some(build_version), Some(client_version)) =
            (arcature_version, build_version, client_version)
        {
            return Ok(CertifiedVersions::new(
                arcature_version,
                build_version,
                client_version,
            ));
        }
    }

    // Fall back to the embedded stack contract versions. The contract is
    // compiled into the binary via include_str! and is always available
    // (ADR-0005 invariant 14 — generated projects target a certified set).
    let contract = crate::stack::load();
    Ok(CertifiedVersions::new(
        contract.snapshot.arcature_version,
        contract.snapshot.arcature_build_version,
        contract.snapshot.arcature_client_version,
    ))
}