rok-cli 0.3.3

Developer CLI for rok-based Axum applications
use console::style;
use std::path::Path;

type FileCheck = (&'static str, &'static str, fn(&Path) -> bool);

pub fn run() {
    let mut ok = 0u32;
    let mut warn = 0u32;
    let mut err = 0u32;

    let root = Path::new(".");

    println!(
        "{}",
        style("rok check — project health").bold().underlined()
    );
    println!();

    // ── file / directory checks ────────────────────────────────────────────
    let checks: &[FileCheck] = &[
        ("Cargo.toml", "workspace manifest", |p| {
            p.join("Cargo.toml").exists()
        }),
        (".env", "environment config", |p| p.join(".env").exists()),
        (".env.example", "env template", |p| {
            p.join(".env.example").exists()
        }),
        ("src/main.rs", "application entry", |p| {
            p.join("src/main.rs").exists()
        }),
        ("src/lib.rs", "library entry", |p| {
            p.join("src/lib.rs").exists()
        }),
        ("src/state.rs", "app state", |p| {
            p.join("src/state.rs").exists()
        }),
        ("src/routes/", "routes directory", |p| {
            p.join("src/routes").is_dir()
        }),
        ("src/app/controllers/", "controllers directory", |p| {
            p.join("src/app/controllers").is_dir()
        }),
        ("src/app/models/", "models directory", |p| {
            p.join("src/app/models").is_dir()
        }),
        ("src/app/validators/", "validators directory", |p| {
            p.join("src/app/validators").is_dir()
        }),
        (
            "database/migrations/",
            "migrations directory (rok layout)",
            |p| p.join("database/migrations").is_dir(),
        ),
        ("migrations/", "migrations directory (flat layout)", |p| {
            p.join("migrations").is_dir()
        }),
        (".github/workflows/ci.yml", "CI workflow", |p| {
            p.join(".github/workflows/ci.yml").exists()
        }),
        ("Dockerfile", "Docker build", |p| {
            p.join("Dockerfile").exists()
        }),
        ("docker-compose.yml", "Docker Compose", |p| {
            p.join("docker-compose.yml").exists()
        }),
        (".dockerignore", "Docker ignore", |p| {
            p.join(".dockerignore").exists()
        }),
        ("roadmap.md", "roadmap", |p| p.join("roadmap.md").exists()),
        ("CHANGELOG.md", "changelog", |p| {
            p.join("CHANGELOG.md").exists()
        }),
        ("README.md", "readme", |p| p.join("README.md").exists()),
    ];

    for (name, label, check) in checks {
        if check(root) {
            println!(
                "  {}  {} ({})",
                style("").green(),
                name,
                style(label).dim()
            );
            ok += 1;
        } else {
            println!(
                "  {}  {}{} ({})",
                style("").red(),
                name,
                style("missing").red(),
                style(label).dim()
            );
            err += 1;
        }
    }

    // ── Cargo.toml workspace check ─────────────────────────────────────────
    let cargo_toml = root.join("Cargo.toml");
    if cargo_toml.exists() {
        if let Ok(content) = std::fs::read_to_string(&cargo_toml) {
            if content.contains("[workspace]") {
                println!("  {}  workspace root detected", style("").green());
                ok += 1;
            } else {
                println!(
                    "  {}  not a workspace root ([workspace] missing)",
                    style("").yellow()
                );
                warn += 1;
            }
            if content.contains("[package]") {
                println!("  {}  binary crate defined", style("").green());
                ok += 1;
            }
        }
    }

    // ── rok-cli installed check ────────────────────────────────────────────
    let cli_check = std::process::Command::new("rok").arg("--version").output();
    match cli_check {
        Ok(output) if output.status.success() => {
            let ver = String::from_utf8_lossy(&output.stdout).trim().to_string();
            println!(
                "  {}  rok CLI installed ({})",
                style("").green(),
                style(ver).dim()
            );
            ok += 1;
        }
        _ => {
            println!(
                "  {}  rok CLI not found — run: cargo install rok-cli",
                style("").yellow()
            );
            warn += 1;
        }
    }

    // ── cargo-watch check ───────────────────────────────────────────────────
    let watch_check = std::process::Command::new("cargo")
        .args(["watch", "--version"])
        .output();
    match watch_check {
        Ok(output) if output.status.success() => {
            println!(
                "  {}  cargo-watch installed (hot reload available)",
                style("").green()
            );
            ok += 1;
        }
        _ => {
            println!(
                "  {}  cargo-watch not found — install: cargo install cargo-watch",
                style("").yellow()
            );
            warn += 1;
        }
    }

    // ── Summary ────────────────────────────────────────────────────────────
    println!();
    println!(
        "  {}  {} {}, {} {}, {} {}",
        if err == 0 {
            style("Summary:").green().bold()
        } else {
            style("Summary:").red().bold()
        },
        ok,
        style("ok").green(),
        warn,
        style("warnings").yellow(),
        err,
        style("errors").red(),
    );
}