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(())
}
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)"
)))
}