kegani-cli 0.1.4

CLI tool for Kegani framework
Documentation
//! `keg build` command โ€” Build release binary with resource embedding

use anyhow::{Context, Result};
use console::{style, Emoji};
use std::path::PathBuf;
use std::process::Command;

/// Build release binary
pub fn build(target: Option<&str>, strip: bool, verbose: bool, size: bool) -> Result<()> {
    // Capture project directory before cargo changes it
    let project_dir = std::env::current_dir().context("Failed to get current directory")?;

    println!();
    println!("{} {}", Emoji("๐Ÿ—๏ธ", ""), style("Building release binary").bold());
    println!();

    let mut args = vec!["build", "--release"];

    if let Some(t) = target {
        args.push("--target");
        args.push(t);
        println!("  {} {}", style("Target:").dim(), style(t).cyan());
    }

    if strip {
        args.push("--bin");
        args.push("--strip");
        println!("  {} {}", style("Strip:").dim(), style("enabled").cyan());
    }

    println!("  {} {}", style("Mode:").dim(), style("release (opt-level=3, lto=true)").cyan());
    if verbose {
        args.push("--verbose");
    }
    println!();

    let start = std::time::Instant::now();

    let status = Command::new("cargo")
        .args(&args)
        .current_dir(&project_dir)
        .status()
        .context("Failed to run cargo build")?;

    let elapsed = start.elapsed();

    if !status.success() {
        anyhow::bail!("Build failed with exit code: {:?}", status.code());
    }

    // Find output binary
    let package_name = project_dir
        .file_name()
        .map(|n| n.to_string_lossy().to_string())
        .unwrap_or_else(|| "app".to_string());

    let binary_path = if target.is_some() {
        PathBuf::from("target")
            .join(target.unwrap())
            .join("release")
            .join(&package_name)
    } else {
        PathBuf::from("target")
            .join("release")
            .join(&package_name)
    };

    let full_path = project_dir.join(&binary_path);
    println!();
    println!("{} {}", Emoji("โœ…", ""), style("Build successful!").green());
    println!("  {} ({} ms)", style("Time:").dim(), elapsed.as_millis());

    if full_path.exists() {
        let size_bytes = std::fs::metadata(&full_path)
            .map(|m| m.len())
            .unwrap_or(0);
        let size_kb = size_bytes / 1024;
        let size_mb = size_bytes / (1024 * 1024);

        println!("  {} {} ({} KB / {} MB)", style("Binary:").dim(), full_path.display(), size_kb, size_mb);

        if size {
            println!();
            println!("{} Binary size breakdown:", style("๐Ÿ“Š").cyan());
            println!("  {} {:.2} KB (binary)", style("โ†’").dim(), size_kb as f64);
        }
    } else {
        println!("  {} (in {})", style("Binary:").dim(), binary_path.display());
    }
    println!();

    Ok(())
}

/// Run cargo check with pretty output
pub fn check(verbose: bool) -> Result<()> {
    let project_dir = std::env::current_dir().context("Failed to get current directory")?;

    println!();
    println!("{} {}", Emoji("๐Ÿ”", ""), style("Running cargo check").bold());
    println!();

    let mut args = vec!["check"];
    if verbose {
        args.push("--verbose");
    }

    let start = std::time::Instant::now();
    let output = Command::new("cargo")
        .args(&args)
        .current_dir(&project_dir)
        .output()
        .context("Failed to run cargo check")?;
    let elapsed = start.elapsed();

    if output.status.success() {
        println!();
        println!("{} {}", Emoji("โœ…", ""), style("Check passed!").green());
        println!("  {} ({} ms)", style("Time:").dim(), elapsed.as_millis());
    } else {
        // Print errors
        println!();
        println!("{} {}", style("โœ—").red(), style("Check failed:").red().bold());
        println!("{}", String::from_utf8_lossy(&output.stderr));
        anyhow::bail!("cargo check failed");
    }

    Ok(())
}

/// Run tests
pub fn test(filter: &str, release: bool, nocapture: bool) -> Result<()> {
    let project_dir = std::env::current_dir().context("Failed to get current directory")?;

    println!();
    println!("{} {}", Emoji("๐Ÿงช", ""), style("Running tests").bold());
    println!();

    let mut args = vec!["test"];
    if !filter.is_empty() {
        args.push(filter);
    }
    if release {
        args.push("--release");
        println!("  {} {}", style("Mode:").dim(), style("release").cyan());
    }
    if nocapture {
        args.push("--nocapture");
    }

    println!("  {} {}", style("Command:").dim(), format!("cargo {}", args.join(" ")));
    println!();

    let start = std::time::Instant::now();
    let output = Command::new("cargo")
        .args(&args)
        .current_dir(&project_dir)
        .output()
        .context("Failed to run cargo test")?;
    let elapsed = start.elapsed();

    println!("{}", String::from_utf8_lossy(&output.stdout));

    if output.status.success() {
        println!();
        println!("{} {}", Emoji("โœ…", ""), style("All tests passed!").green());
        println!("  {} ({} ms)", style("Time:").dim(), elapsed.as_millis());
    } else {
        println!();
        println!("{} {}", style("โœ—").red(), style("Some tests failed:").red().bold());
        anyhow::bail!("cargo test failed");
    }

    Ok(())
}