xbp 10.15.4

XBP is a zero-config build pack that can also interact with proxies, kafka, sockets, synthetic monitors.
Documentation
pub mod app;
pub mod commands;
pub mod error;
pub mod features;
pub mod handlers;
pub mod router;

pub use handlers::*;

use crate::cli::app::AppContext;
use crate::cli::error::CliResult;
use crate::commands::curl;
use crate::commands::generate_systemd::{run_generate_systemd, GenerateSystemdArgs};
use crate::commands::redeploy_v2::run_redeploy_v2;
use crate::commands::{
    install_package, list_services, open_global_config, run_config, run_init, run_login,
    run_redeploy, run_redeploy_service, run_service_command, run_setup, run_version_command,
    show_service_help,
};
use crate::commands::{run_diag, run_nginx};
use crate::config::sync_versioning_files_registry;
use crate::logging::{init_logger, log_error, log_info, log_success, log_warn};
use clap::Parser;
use commands::Cli;

pub async fn run() -> CliResult<()> {
    let cli: Cli = Cli::parse();
    let debug: bool = cli.debug;

    if let Err(e) = init_logger(debug).await {
        let _ = log_error(
            "system",
            "Failed to initialize logger",
            Some(&e.to_string()),
        )
        .await;
    }

    if let Err(e) = sync_versioning_files_registry() {
        let _ = log_warn("config", "Failed to sync versioning registry", Some(&e)).await;
    }

    let mut ctx = AppContext::new(debug);
    router::dispatch(cli, &mut ctx).await
}

pub(super) async fn handle_init(debug: bool) -> CliResult<()> {
    if let Err(e) = run_init(debug).await {
        let _ = log_error("init", "Init failed", Some(&e)).await;
        return Err(e.into());
    }
    Ok(())
}

pub(super) async fn handle_setup(debug: bool) -> CliResult<()> {
    if let Err(e) = run_setup(debug).await {
        let _ = log_error("setup", "Setup failed", Some(&e)).await;
    }
    Ok(())
}

pub(super) async fn handle_redeploy(service_name: Option<String>, debug: bool) -> CliResult<()> {
    if let Some(name) = service_name {
        if let Err(e) = run_redeploy_service(&name, debug).await {
            let _ = log_error("redeploy", "Service redeploy failed", Some(&e)).await;
        }
    } else if let Err(e) = run_redeploy().await {
        let _ = log_error("redeploy", "Redeploy failed", Some(&e)).await;
    }
    Ok(())
}

pub(super) async fn handle_redeploy_v2(cmd: commands::RedeployV2Cmd, debug: bool) -> CliResult<()> {
    let _ = log_info("redeploy_v2", "Starting remote redeploy process", None).await;
    match run_redeploy_v2(cmd.password, cmd.username, cmd.host, cmd.project_dir, debug).await {
        Ok(()) => Ok(()),
        Err(e) => {
            let _ = log_error("redeploy_v2", "Remote redeploy failed", Some(&e)).await;
            Err(e.into())
        }
    }
}

pub(super) async fn handle_config(cmd: commands::ConfigCmd, debug: bool) -> CliResult<()> {
    if cmd.project {
        let _ = run_config(debug).await;
    } else if let Err(e) = open_global_config(cmd.no_open).await {
        let _ = log_error("config", "Failed to open global config", Some(&e)).await;
    }
    Ok(())
}

pub(super) async fn handle_install(package: String, debug: bool) -> CliResult<()> {
    if package.is_empty() || package == "--help" || package == "help" {
        return install_package("", debug).await.map_err(Into::into);
    }

    let install_msg: String = format!("Installing package: {}", package);
    let _ = log_info("install", &install_msg, None).await;
    match install_package(&package, debug).await {
        Ok(()) => {
            let success_msg = format!("Successfully installed: {}", package);
            let _ = log_success("install", &success_msg, None).await;
            Ok(())
        }
        Err(e) => Err(e.into()),
    }
}

pub(super) async fn handle_curl(cmd: commands::CurlCmd, debug: bool) -> CliResult<()> {
    let url = cmd
        .url
        .unwrap_or_else(|| "https://example.com/api".to_string());
    if let Err(e) = curl::run_curl(&url, cmd.no_timeout, debug).await {
        let _ = log_error("curl", "Curl command failed", Some(&e)).await;
    }
    Ok(())
}

pub(super) async fn handle_services(debug: bool) -> CliResult<()> {
    if let Err(e) = list_services(debug).await {
        let _ = log_error("services", "Failed to list services", Some(&e)).await;
    }
    Ok(())
}

pub(super) async fn handle_service(
    command: Option<String>,
    service_name: Option<String>,
    debug: bool,
) -> CliResult<()> {
    if let Some(cmd) = command {
        if cmd == "--help" || cmd == "help" {
            if let Some(name) = service_name {
                if let Err(e) = show_service_help(&name).await {
                    let _ = log_error("service", "Failed to show service help", Some(&e)).await;
                }
            } else {
                println!("Usage: xbp service <command> <service-name>");
                println!("Commands: build, install, start, dev");
                println!("Example: xbp service build zeus");
                println!("For help on a specific service: xbp service --help <service-name>");
            }
        } else if let Some(name) = service_name {
            if let Err(e) = run_service_command(&cmd, &name, debug).await {
                let _ = log_error(
                    "service",
                    &format!("Service command '{}' failed", cmd),
                    Some(&e),
                )
                .await;
            }
        } else {
            let _ = log_error("service", "Service name required", None).await;
        }
    } else {
        println!("Usage: xbp service <command> <service-name>");
        println!("Commands: build, install, start, dev");
        println!("Example: xbp service build zeus");
    }
    Ok(())
}

pub(super) async fn handle_nginx(cmd: commands::NginxSubCommand, debug: bool) -> CliResult<()> {
    if let Err(e) = run_nginx(cmd, debug).await {
        let _ = log_error("nginx", "Nginx command failed", Some(&e.to_string())).await;
    }
    Ok(())
}

pub(super) async fn handle_diag(cmd: commands::DiagCmd, debug: bool) -> CliResult<()> {
    if let Err(e) = run_diag(cmd, debug).await {
        let _ = log_error("diag", "Diag command failed", Some(&e.to_string())).await;
    }
    Ok(())
}

pub(super) async fn handle_generate(cmd: commands::GenerateCmd, debug: bool) -> CliResult<()> {
    match cmd.command {
        commands::GenerateSubCommand::Systemd(subcmd) => {
            let args = GenerateSystemdArgs {
                output_dir: subcmd.output_dir,
                service: subcmd.service,
                api: subcmd.api,
            };
            if let Err(e) = run_generate_systemd(args, debug).await {
                let _ = log_error(
                    "generate-systemd",
                    "Failed to generate systemd units",
                    Some(&e),
                )
                .await;
            }
        }
    }
    Ok(())
}

pub(super) async fn handle_done(cmd: commands::DoneCmd, _debug: bool) -> CliResult<()> {
    if let Err(e) = crate::commands::run_done(
        cmd.root,
        cmd.since,
        cmd.output,
        cmd.no_ai,
        cmd.recursive,
        cmd.exclude,
    )
    .await
    {
        let _ = log_error("done", "Done command failed", Some(&e)).await;
        return Err(e.into());
    }
    Ok(())
}

pub(super) async fn handle_login() -> CliResult<()> {
    if let Err(e) = run_login().await {
        let _ = log_error("login", "Login failed", Some(&e)).await;
        return Err(e.into());
    }
    Ok(())
}

pub(super) async fn handle_version(cmd: commands::VersionCmd, debug: bool) -> CliResult<()> {
    if let Err(e) = run_version_command(cmd.target, cmd.git, debug).await {
        let _ = log_error("version", "Version command failed", Some(&e)).await;
        return Err(e.into());
    }
    Ok(())
}