cufflink-cli 0.8.30

CLI for the Cufflink CRUD microservice platform — deploy, init, and manage services
use crate::package_manifest::PackageManifest;
use comfy_table::{presets::NOTHING, Table, TableComponent};

const PACKAGES_DIR: &str = "cufflink-packages";

pub async fn run(name: &str, skip: &[String], env: Option<&str>) -> eyre::Result<()> {
    let package_dir = std::env::current_dir()?.join(PACKAGES_DIR).join(name);
    if !package_dir.exists() {
        eyre::bail!(
            "Package '{}' not found in {}/. Run `cufflink install` first.",
            name,
            PACKAGES_DIR
        );
    }

    let manifest = PackageManifest::load(&package_dir)?;

    let services: Vec<_> = manifest
        .services
        .iter()
        .filter(|s| !skip.contains(&s.name))
        .collect();
    let frontends: Vec<_> = manifest
        .frontends
        .iter()
        .filter(|f| !skip.contains(&f.name))
        .collect();

    if services.is_empty() && frontends.is_empty() {
        println!("Nothing to deploy (all components skipped).");
        return Ok(());
    }

    let skipped: Vec<&str> = manifest
        .services
        .iter()
        .map(|s| s.name.as_str())
        .chain(manifest.frontends.iter().map(|f| f.name.as_str()))
        .filter(|n| skip.contains(&n.to_string()))
        .collect();
    if !skipped.is_empty() {
        println!("Skipping: {}", skipped.join(", "));
    }

    if !manifest.config.required.is_empty() {
        println!("Note: Set these config keys after deployment:");
        for key in &manifest.config.required {
            println!("  cufflink config set {} <value>", key);
        }
        println!();
    }

    let original_dir = std::env::current_dir()?;
    let mut results: Vec<(String, &str, Result<(), String>)> = Vec::new();

    for service in &services {
        let service_dir = package_dir.join(&service.path);
        println!("Deploying service '{}'...", service.name);
        std::env::set_current_dir(&service_dir)?;

        match super::deploy::run(false, None, env).await {
            Ok(_) => results.push((service.name.clone(), "service", Ok(()))),
            Err(e) => {
                let msg = format!("{}", e);
                eprintln!("  Failed: {}", msg);
                results.push((service.name.clone(), "service", Err(msg)));
            }
        }
    }

    for frontend in &frontends {
        let frontend_dir = package_dir.join(&frontend.path);
        println!("Deploying frontend '{}'...", frontend.name);
        std::env::set_current_dir(&frontend_dir)?;

        match super::deploy::run(false, None, env).await {
            Ok(_) => results.push((frontend.name.clone(), "frontend", Ok(()))),
            Err(e) => {
                let msg = format!("{}", e);
                eprintln!("  Failed: {}", msg);
                results.push((frontend.name.clone(), "frontend", Err(msg)));
            }
        }
    }

    std::env::set_current_dir(&original_dir)?;

    println!();

    let mut table = Table::new();
    table.load_preset(NOTHING);
    table.set_style(TableComponent::HeaderLines, '-');
    table.set_style(TableComponent::MiddleHeaderIntersections, ' ');
    table.set_header(vec!["COMPONENT", "TYPE", "STATUS"]);

    let mut failures = 0;
    for (name, kind, result) in &results {
        let status = match result {
            Ok(_) => "deployed",
            Err(_) => {
                failures += 1;
                "FAILED"
            }
        };
        table.add_row(vec![name.as_str(), *kind, status]);
    }

    println!("{table}");

    if failures > 0 {
        eyre::bail!("{} component(s) failed to deploy", failures);
    }

    Ok(())
}