tovuk 0.1.70

Deploy Rust workers, static frontends, and worker-static apps to Tovuk.
use super::super::{cargo_lints::cargo_lints, report::DoctorCheck};
use crate::cli::{constants::RUST_STRICT_CLIPPY_DENY_LINTS, project::walk_project_files};
use std::{
    fs,
    path::Path,
    process::{Command, Stdio},
};

pub(super) fn rust_backend_checks(project_dir: &Path, config_valid: bool) -> Vec<DoctorCheck> {
    let mut checks = vec![cargo_lints(project_dir), unsafe_check(project_dir)];
    if config_valid {
        checks.extend(rust_command_checks(project_dir));
    }
    checks
}

fn rust_command_checks(project_dir: &Path) -> Vec<DoctorCheck> {
    vec![
        cargo_command_check(
            project_dir,
            "cargo fmt",
            &["fmt", "--all", "--check"],
            "Install rustfmt with Rust, then run `cargo fmt --all --check` before deploying.",
            "Run `cargo fmt --all`, then redeploy.",
        ),
        cargo_command_check(
            project_dir,
            "cargo check",
            &[
                "check",
                "--locked",
                "--release",
                "--all-targets",
                "--all-features",
                "--quiet",
            ],
            "Install Rust and Cargo, then run `cargo check --locked --release --all-targets --all-features` locally before deploying.",
            "Run `cargo check --locked --release --all-targets --all-features`, fix every compiler error and warning, then redeploy.",
        ),
        cargo_command_check(
            project_dir,
            "cargo test",
            &[
                "test",
                "--locked",
                "--release",
                "--all-targets",
                "--all-features",
                "--quiet",
            ],
            "Install Rust and Cargo, then run `cargo test --locked --release --all-targets --all-features` locally before deploying.",
            "Run `cargo test --locked --release --all-targets --all-features`, fix every failed test, then redeploy.",
        ),
        strict_clippy_check(project_dir),
    ]
}

fn strict_clippy_check(project_dir: &Path) -> DoctorCheck {
    let mut clippy_args = vec![
        "clippy",
        "--locked",
        "--release",
        "--all-targets",
        "--all-features",
        "--quiet",
        "--",
        "-D",
        "warnings",
    ];
    for lint in RUST_STRICT_CLIPPY_DENY_LINTS {
        clippy_args.push("-D");
        clippy_args.push(lint);
    }
    cargo_command_check(
        project_dir,
        "cargo clippy",
        &clippy_args,
        "Install Rust clippy, then run Tovuk strict Clippy checks before deploying.",
        "Run the strict Tovuk Clippy command from tovuk.toml, fix every warning, panic/unwrap issue, and resource lint, then redeploy.",
    )
}

fn cargo_command_check(
    project_dir: &Path,
    name: &str,
    args: &[&str],
    missing: &str,
    failed: &str,
) -> DoctorCheck {
    let result = Command::new("cargo")
        .args(args)
        .current_dir(project_dir)
        .env("CARGO_TERM_COLOR", "never")
        .stdin(Stdio::null())
        .output();
    let output = match result {
        Ok(output) => output,
        Err(error) => {
            return DoctorCheck {
                name: name.to_owned(),
                ok: false,
                message: error.to_string(),
                agent_instruction: Some(missing.to_owned()),
            };
        }
    };
    let message = if output.status.success() {
        "passed".to_owned()
    } else {
        first_output_line(&output.stderr, &output.stdout, name)
    };
    DoctorCheck {
        name: name.to_owned(),
        ok: output.status.success(),
        message,
        agent_instruction: if output.status.success() {
            None
        } else {
            Some(failed.to_owned())
        },
    }
}

pub(crate) fn first_output_line(stderr: &[u8], stdout: &[u8], fallback: &str) -> String {
    let text = if stderr.is_empty() { stdout } else { stderr };
    let value = String::from_utf8_lossy(text).trim().to_owned();
    let value = if value.is_empty() {
        format!("{fallback} failed")
    } else {
        value
    };
    value.chars().take(240).collect()
}

pub(super) fn unsafe_check(project_dir: &Path) -> DoctorCheck {
    let hits = scan_unsafe(project_dir);
    DoctorCheck {
        name: "unsafe".to_owned(),
        ok: hits.is_empty(),
        message: if hits.is_empty() {
            "no direct unsafe found".to_owned()
        } else {
            hits.iter().take(5).cloned().collect::<Vec<_>>().join(", ")
        },
        agent_instruction: if hits.is_empty() {
            None
        } else {
            Some(
                "Remove direct unsafe usage from workspace Rust source before deploying."
                    .to_owned(),
            )
        },
    }
}

fn scan_unsafe(project_dir: &Path) -> Vec<String> {
    let mut hits = Vec::new();
    walk_project_files(project_dir, |file, relative| {
        if is_rust_source(relative)
            && fs::read_to_string(file).is_ok_and(|source| source.contains("unsafe"))
        {
            hits.push(relative.to_owned());
        }
    });
    hits
}

fn is_rust_source(relative: &str) -> bool {
    Path::new(relative)
        .extension()
        .is_some_and(|extension| extension.eq_ignore_ascii_case("rs"))
}