use crate::ansi_colors::Colorize;
use crate::config::BatutaConfig;
use crate::report;
use crate::types::{PhaseStatus, WorkflowPhase, WorkflowState};
use std::path::{Path, PathBuf};
use tracing::warn;
use super::ReportFormat;
fn run_cargo_build(
project_dir: &Path,
release: bool,
target: Option<&str>,
wasm: bool,
extra_flags: &[String],
) -> anyhow::Result<()> {
let mut cmd = std::process::Command::new("cargo");
cmd.arg("build").current_dir(project_dir);
if wasm {
cmd.arg("--target").arg("wasm32-unknown-unknown");
} else if let Some(t) = target {
cmd.arg("--target").arg(t);
}
if release {
cmd.arg("--release");
}
for flag in extra_flags {
cmd.arg(flag);
}
let mut display = String::from("cargo build");
if release {
display.push_str(" --release");
}
if wasm {
display.push_str(" --target wasm32-unknown-unknown");
} else if let Some(t) = target {
display.push_str(&format!(" --target {}", t));
}
for flag in extra_flags {
display.push(' ');
display.push_str(flag);
}
println!("{} {}", "Running:".bright_yellow(), display.cyan());
println!();
let status = cmd
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit())
.status()
.map_err(|e| anyhow::anyhow!("Failed to execute cargo (is it in PATH?): {}", e))?;
if status.success() {
println!();
println!("{}", "✅ Build completed successfully!".bright_green().bold());
Ok(())
} else {
let code = status.code().map_or("signal".to_string(), |c| c.to_string());
println!();
println!("{} Build failed with exit code: {}", "✗".red(), code);
anyhow::bail!("cargo build failed (exit {})", code)
}
}
pub fn cmd_build(release: bool, target: Option<String>, wasm: bool) -> anyhow::Result<()> {
println!("{}", "🔨 Building Rust project...".bright_cyan().bold());
println!();
let state_file = crate::cli::get_state_file_path();
let mut state = WorkflowState::load(&state_file).unwrap_or_else(|e| {
warn!("Failed to load workflow state, starting fresh: {e}");
WorkflowState::new()
});
if !state.is_phase_completed(WorkflowPhase::Validation) {
eprintln!("{}", "⚠️ Validation phase not completed!".yellow().bold());
eprintln!();
eprintln!("Run {} first to validate your project.", "batuta validate".cyan());
eprintln!();
crate::cli::workflow::display_workflow_progress(&state);
anyhow::bail!("Prerequisite phase not completed: validation");
}
state.start_phase(WorkflowPhase::Deployment);
state.save(&state_file)?;
println!("{}", "Build Settings:".bright_yellow().bold());
println!(
" {} Build mode: {}",
"•".bright_blue(),
if release { "release".green() } else { "debug".dimmed() }
);
if let Some(t) = &target {
println!(" {} Target: {}", "•".bright_blue(), t.cyan());
}
println!(
" {} WebAssembly: {}",
"•".bright_blue(),
if wasm { "enabled".green() } else { "disabled".dimmed() }
);
println!();
let config_path = PathBuf::from("batuta.toml");
let config = if config_path.exists() {
BatutaConfig::load(&config_path)?
} else {
BatutaConfig::default()
};
let output_dir = &config.transpilation.output_dir;
if !output_dir.join("Cargo.toml").exists() {
println!("{} No Cargo.toml found in {}", "✗".red(), output_dir.display());
println!();
println!("Run {} first to generate the Rust project.", "batuta transpile".cyan());
state.fail_phase(
WorkflowPhase::Deployment,
format!("No Cargo.toml in {}", output_dir.display()),
);
state.save(&state_file)?;
anyhow::bail!("No Cargo.toml in transpiled output directory: {}", output_dir.display());
}
println!(" {} Project: {}", "•".bright_blue(), output_dir.display());
println!();
match run_cargo_build(output_dir, release, target.as_deref(), wasm, &config.build.cargo_flags) {
Ok(()) => {
state.complete_phase(WorkflowPhase::Deployment);
state.save(&state_file)?;
}
Err(e) => {
state.fail_phase(WorkflowPhase::Deployment, e.to_string());
state.save(&state_file)?;
return Err(e);
}
}
crate::cli::workflow::display_workflow_progress(&state);
println!("{}", "🎉 Migration Complete!".bright_green().bold());
println!();
println!("{}", "💡 Next Steps:".bright_yellow().bold());
println!(
" {} Run {} to generate migration report",
"1.".bright_blue(),
"batuta report".cyan()
);
println!(" {} Check your output directory for the final binary", "2.".bright_blue());
println!(" {} Run {} to start fresh", "3.".bright_blue(), "batuta reset".cyan());
println!();
Ok(())
}
pub fn cmd_report(output: PathBuf, format: ReportFormat) -> anyhow::Result<()> {
println!("{}", "📊 Generating migration report...".bright_cyan().bold());
println!();
let state_file = crate::cli::get_state_file_path();
let state = WorkflowState::load(&state_file).unwrap_or_else(|e| {
warn!("Failed to load workflow state, starting fresh: {e}");
WorkflowState::new()
});
let has_started = state.phases.values().any(|info| info.status != PhaseStatus::NotStarted);
if !has_started {
eprintln!("{}", "⚠️ No workflow data found!".yellow().bold());
eprintln!();
eprintln!("Run {} first to generate analysis data.", "batuta analyze".cyan());
eprintln!();
anyhow::bail!("No workflow data found — run `batuta analyze` first");
}
let config_path = PathBuf::from("batuta.toml");
let analysis = if config_path.exists() {
let config = BatutaConfig::load(&config_path)?;
crate::analyzer::analyze_project(&config.source.path, true, true, true)?
} else {
crate::analyzer::analyze_project(&PathBuf::from("."), true, true, true)?
};
let project_name =
analysis.root_path.file_name().and_then(|n| n.to_str()).unwrap_or("unknown").to_string();
let migration_report = report::MigrationReport::new(project_name, analysis, state);
let report_format = match format {
ReportFormat::Html => report::ReportFormat::Html,
ReportFormat::Markdown => report::ReportFormat::Markdown,
ReportFormat::Json => report::ReportFormat::Json,
ReportFormat::Text => report::ReportFormat::Text,
};
migration_report.save(&output, report_format)?;
println!("{}", "✅ Report generated successfully!".bright_green().bold());
println!();
println!("{}: {:?}", "Output file".bold(), output);
println!("{}: {:?}", "Format".bold(), format);
println!();
if matches!(format, ReportFormat::Text | ReportFormat::Markdown) {
println!("{}", "Preview (first 20 lines):".dimmed());
println!("{}", "─".repeat(80).dimmed());
let content = std::fs::read_to_string(&output)?;
for line in content.lines().take(20) {
println!("{}", line.dimmed());
}
if content.lines().count() > 20 {
println!("{}", "...".dimmed());
}
println!("{}", "─".repeat(80).dimmed());
println!();
}
println!("{}", "💡 Next Steps:".bright_green().bold());
println!(" {} Open the report to view detailed analysis", "1.".bright_blue());
if matches!(format, ReportFormat::Html) {
println!(
" {} Open in browser: file://{}",
"2.".bright_blue(),
output.canonicalize()?.display()
);
}
println!();
Ok(())
}