arcature-cli 2026.1.1

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`
//! dependency version 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. The client version is the *real
//! shipping npm dependency* — never a monorepo `file:` path (ADR-0006 §6).

use std::path::Path;

use arcature_templates::{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.1.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/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 (arcature_version, client_version) = read_certified_versions()?;

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

/// Read the certified `arcature` crate version and `@arcature/client` npm
/// version from the committed Platform manifest. These are the versions
/// written into the generated project's `Cargo.toml` and
/// `frontend/package.json`.
///
/// 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<(String, String), 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 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(client_version)) = (arcature_version, client_version) {
            return Ok((arcature_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((
        contract.snapshot.arcature_version,
        contract.snapshot.arcature_client_version,
    ))
}