arcature-cli 2026.2.0

Developer lifecycle CLI for Arcature applications.
Documentation
use crate::contracts;
use crate::error::CommandError;
use crate::process::{ProcessSpec, run};
use crate::project;
use crate::tool::Tool;

pub(crate) fn execute() -> Result<(), CommandError> {
    let project = project::discover()?;
    let artifact = contracts::check(&project).map_err(CommandError::Contract)?;
    exposure_lint(&artifact)?;
    super::install::frontend(&project)?;
    println!("frontend  building production assets");
    run(&ProcessSpec::new(Tool::Pnpm.executable(), project.frontend_root()).arg("build"))?;
    println!("backend   building release executable");
    run(&ProcessSpec::new("cargo", project.root()).args([
        "build",
        "--release",
        "--package",
        &project.backend_package,
    ]))?;
    println!(
        "build complete: target/release/{} + public/build",
        project.backend_binary
    );
    Ok(())
}

/// Run the Client Exposure Firewall dangerous-field lint during `arc build`
/// so a secret-bearing field name fails the build, not just `arc check`.
fn exposure_lint(artifact: &contracts::Artifact) -> Result<(), CommandError> {
    let hits = contracts::dangerous_fields(&artifact.pages);
    if hits.is_empty() {
        return Ok(());
    }
    let listed = hits
        .iter()
        .map(|hit| format!("`{}`.`{}`", hit.page, hit.field))
        .collect::<Vec<_>>()
        .join(", ");
    Err(CommandError::Contract(format!(
        "secret-bearing field names in browser contracts: {listed} (field-name linting is \
         defense-in-depth; the boundary is the explicit ClientData opt-in)"
    )))
}