use anyhow::Result;
use console::style;
use std::env;
use crate::cmd::workflow::BuildWorkflow;
use crate::fmt::{format_bytes, CHECKMARK, ROCKET};
pub fn cmd_build(
dry_run: bool,
check: bool,
json_output: bool,
target_dir: Option<&str>,
) -> Result<()> {
println!("{} {} Build Pipeline", ROCKET, style("wasm-slim").bold());
println!();
let project_root = env::current_dir()?;
let workflow = BuildWorkflow::new(&project_root);
let result = workflow.execute(dry_run, check, target_dir)?;
if result.dry_run {
present_dry_run_info(&result.dry_run_files);
} else {
present_cargo_changes(&result.cargo_changes);
}
present_build_results(&result.metrics);
present_budget_check(result.budget_check_passed, result.budget_threshold);
if json_output {
present_json_report(&result.metrics)?;
}
Ok(())
}
fn present_dry_run_info(files: &[String]) {
if !files.is_empty() {
println!();
println!("[DRY RUN] Would optimize {} file(s):", files.len());
for file in files {
println!(" {} Would optimize: {}", style("→").dim(), file);
}
println!();
}
}
fn present_cargo_changes(changes: &[String]) {
if !changes.is_empty() {
println!();
println!("{} Applied {} optimizations:", CHECKMARK, changes.len());
for change in changes {
println!(" {} {}", style("•").dim(), change);
}
println!();
}
}
fn present_build_results(metrics: &crate::pipeline::SizeMetrics) {
println!();
println!("{} Build completed successfully!", CHECKMARK);
println!(
" Final size: {}",
style(format_bytes(metrics.after_bytes)).green().bold()
);
let reduction = metrics.before_bytes.saturating_sub(metrics.after_bytes);
println!(
" Reduction: {} ({:.1}%)",
style(format_bytes(reduction)).green(),
metrics.reduction_percent()
);
println!();
}
fn present_budget_check(passed: Option<bool>, threshold: Option<u64>) {
if let (Some(passed), Some(threshold)) = (passed, threshold) {
if passed {
println!(
" {} Size within threshold ({})",
CHECKMARK,
format_bytes(threshold)
);
}
}
}
fn present_json_report(metrics: &crate::pipeline::SizeMetrics) -> Result<()> {
let report = serde_json::json!({
"final_size": metrics.after_bytes,
"original_size": metrics.before_bytes,
"reduction_bytes": metrics.before_bytes.saturating_sub(metrics.after_bytes),
"reduction_percent": metrics.reduction_percent(),
"timestamp": std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
});
println!();
println!("{}", serde_json::to_string_pretty(&report)?);
println!();
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pipeline::SizeMetrics;
#[test]
fn test_present_dry_run_info_with_empty_list() {
present_dry_run_info(&[]);
}
#[test]
fn test_present_dry_run_info_with_files() {
let files = vec!["Cargo.toml".to_string(), "src/lib/Cargo.toml".to_string()];
present_dry_run_info(&files);
}
#[test]
fn test_present_cargo_changes_with_empty_list() {
present_cargo_changes(&[]);
}
#[test]
fn test_present_cargo_changes_with_changes() {
let changes = vec![
"Added opt-level = 'z'".to_string(),
"Added lto = true".to_string(),
];
present_cargo_changes(&changes);
}
#[test]
fn test_present_build_results_displays_metrics() {
let metrics = SizeMetrics {
before_bytes: 2000,
after_bytes: 1000,
};
present_build_results(&metrics);
}
#[test]
fn test_present_build_results_with_zero_reduction() {
let metrics = SizeMetrics {
before_bytes: 1000,
after_bytes: 1000,
};
present_build_results(&metrics);
}
#[test]
fn test_present_budget_check_with_passed() {
present_budget_check(Some(true), Some(2000));
}
#[test]
fn test_present_budget_check_with_none() {
present_budget_check(None, None);
}
#[test]
fn test_present_json_report_generates_valid_json() {
let metrics = SizeMetrics {
before_bytes: 2000,
after_bytes: 1500,
};
let result = present_json_report(&metrics);
assert!(result.is_ok());
}
#[test]
fn test_format_bytes_via_shared_module() {
use crate::fmt::format_bytes;
assert_eq!(format_bytes(512), "512 B");
assert_eq!(format_bytes(1024), "1.00 KB");
assert_eq!(format_bytes(1536), "1.50 KB");
assert_eq!(format_bytes(1_048_576), "1.00 MB");
assert_eq!(format_bytes(2_621_440), "2.50 MB");
}
}