rustauth-cli 0.2.0

Command-line tools for RustAuth.
Documentation
use serde::Serialize;

use crate::app::{AppContext, AppError, InfoArgs};
use crate::diagnostics::{doctor, DiagnosticReport};
use crate::output::copy_to_clipboard;
use crate::workspace::{inspect, DetectedItem};

#[derive(Debug, Serialize)]
pub struct InfoReport {
    pub rustauth: RustAuthInfo,
    pub toolchain: ToolchainInfo,
    pub workspace: WorkspaceInfoReport,
    pub config_loaded: bool,
    pub config: DiagnosticReport,
}

#[derive(Debug, Serialize)]
pub struct RustAuthInfo {
    pub cli_version: String,
}

#[derive(Debug, Serialize)]
pub struct ToolchainInfo {
    pub rust: String,
    pub cargo: String,
}

#[derive(Debug, Serialize)]
pub struct WorkspaceInfoReport {
    pub root: Option<String>,
    pub target_package: Option<String>,
    pub detected_frameworks: Vec<DetectedItem>,
    pub detected_databases: Vec<DetectedItem>,
}

pub async fn run(context: &AppContext, args: InfoArgs) -> Result<(), AppError> {
    let (config, config_loaded) = context.load_config_or_default()?;
    let workspace = inspect(context.cwd()).ok();
    let report = doctor(context.cwd(), &config, false, config_loaded).await;
    let info = InfoReport {
        rustauth: RustAuthInfo {
            cli_version: report.rustauth_version.clone(),
        },
        toolchain: ToolchainInfo {
            rust: report.rust.clone(),
            cargo: report.cargo.clone(),
        },
        workspace: WorkspaceInfoReport {
            root: report.workspace_root.clone(),
            target_package: report.target_package.clone(),
            detected_frameworks: workspace
                .as_ref()
                .map(|info| info.detected_frameworks.clone())
                .unwrap_or_default(),
            detected_databases: workspace
                .as_ref()
                .map(|info| info.detected_databases.clone())
                .unwrap_or_default(),
        },
        config_loaded,
        config: report,
    };

    if args.json {
        let rendered = serde_json::to_string_pretty(&info)?;
        println!("{rendered}");
        if args.copy {
            copy_to_clipboard(&rendered)?;
            println!("Copied JSON to clipboard.");
        }
        return Ok(());
    }

    print_human(&info);
    if args.copy {
        let rendered = serde_json::to_string_pretty(&info)?;
        copy_to_clipboard(&rendered)?;
        println!("Copied JSON to clipboard.");
    }
    Ok(())
}

fn print_human(info: &InfoReport) {
    println!("RustAuth info");
    println!("CLI: {}", info.rustauth.cli_version);
    println!("Rust: {}", info.toolchain.rust);
    println!("Cargo: {}", info.toolchain.cargo);
    if let Some(root) = &info.workspace.root {
        println!("Workspace: {root}");
    }
    if let Some(package) = &info.workspace.target_package {
        println!("Package: {package}");
    }
    if !info.workspace.detected_frameworks.is_empty() {
        println!("Detected frameworks:");
        for item in &info.workspace.detected_frameworks {
            println!("  - {} ({:?})", item.name, item.confidence);
        }
    }
    if !info.workspace.detected_databases.is_empty() {
        println!("Detected databases:");
        for item in &info.workspace.detected_databases {
            println!("  - {} ({:?})", item.name, item.confidence);
        }
    }
    println!(
        "Config: {}",
        if info.config_loaded {
            "loaded from rustauth.toml"
        } else {
            "defaults (no rustauth.toml)"
        }
    );
    for finding in &info.config.findings {
        let label = match finding.severity {
            crate::diagnostics::Severity::Info => "INFO",
            crate::diagnostics::Severity::Warn => "WARN",
            crate::diagnostics::Severity::Error => "ERROR",
        };
        println!("[{label}] {}: {}", finding.code, finding.message);
    }
    println!("Tip: use --json for machine-readable output.");
}