xbp 0.5.4

XBP is a build pack and deployment management tool to deploy, rust, nextjs etc and manage the NGINX configs below it
Documentation
//! XBP CLI entrypoint
//!
//! Uses `clap` to define a structured, typed CLI and delegates all behavior to
//! the decoupled command modules in `crate::commands`. The entrypoint is kept
//! thin: parse → init logging → dispatch.
use clap::{Args, Parser, Subcommand};

use xbp::commands::curl;
use xbp::commands::redeploy_v2::run_redeploy_v2;
use xbp::commands::{install_package, run_config, run_ports, run_redeploy, run_setup};
use xbp::commands::{pm2_list, pm2_logs};
use xbp::logging::{init_logger, log_error, log_info, log_success};

#[derive(Parser, Debug)]
#[command(
    name = "xbp",
    version,
    about = "XBP CLI",
    disable_help_subcommand = false
)]

struct Cli {
    #[arg(long, global = true)]
    debug: bool,
    #[arg(short = 'l', help = "List pm2 processes")]
    list: bool,

    #[command(subcommand)]
    command: Option<Commands>,
}

#[derive(Subcommand, Debug)]
enum Commands {
    Ports(PortsCmd),
    Setup,
    Redeploy,
    RedeployV2(RedeployV2Cmd),
    Config,
    Install {
        package: String,
    },
    Logs(LogsCmd),
    List,
    Curl {
        #[arg(help = "URL to fetch, e.g. https://example.com/api")]
        url: Option<String>,
    },
}

#[derive(Args, Debug)]
struct PortsCmd {
    #[arg(short = 'p', long = "port")]
    port: Option<u16>,
    #[arg(long = "kill")]
    kill: bool,
    #[arg(short = 'n')]
    nginx: bool,
}

#[derive(Args, Debug)]
struct RedeployV2Cmd {
    #[arg(short = 'p', long = "password")]
    password: Option<String>,
    #[arg(short = 'u', long = "username")]
    username: Option<String>,
    #[arg(short = 'h', long = "host")]
    host: Option<String>,
    #[arg(short = 'd', long = "project-dir")]
    project_dir: Option<String>,
}

#[derive(Args, Debug)]
struct LogsCmd {
    #[arg()]
    project: Option<String>,
}

#[tokio::main]
async fn main() {
    let cli = Cli::parse();
    let debug = cli.debug;

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

    if cli.list && cli.command.is_none() {
        if let Err(e) = pm2_list(debug).await {
            let _ = log_error("pm2", "pm2 list failed", Some(&e)).await;
        }
        return;
    }

    match cli.command {
        Some(Commands::Ports(cmd)) => {
            let mut sim_args: Vec<String> = Vec::new();
            if let Some(p) = cmd.port {
                sim_args.push("-p".to_string());
                sim_args.push(p.to_string());
            }
            if cmd.kill {
                sim_args.push("--kill".to_string());
                sim_args.push("-k".to_string());
            }
            if cmd.nginx {
                sim_args.push("-n".to_string());
                sim_args.push("--nginx".to_string());
            }
            if let Err(e) = run_ports(&sim_args, debug).await {
                let _ = log_error("ports", "Error running ports", Some(&e)).await;
            }
        }
        Some(Commands::Setup) => {
            if let Err(e) = run_setup(debug).await {
                let _ = log_error("setup", "Setup failed", Some(&e)).await;
            }
        }
        Some(Commands::Redeploy) => {
            if let Err(e) = run_redeploy().await {
                let _ = log_error("redeploy", "Redeploy failed", Some(&e)).await;
            }
        }
        Some(Commands::RedeployV2(cmd)) => {
            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(()) => {}
                Err(e) => {
                    let _ = log_error("redeploy_v2", "Remote redeploy failed", Some(&e)).await;
                }
            }
        }
        Some(Commands::Config) => {
            let _ = run_config(debug).await;
        }
        Some(Commands::Install { package }) => {
            let install_msg = 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;
                }
                Err(e) => {
                    let error_msg = format!("Failed to install: {}", package);
                    let _ = log_error("install", &error_msg, Some(&e)).await;
                }
            }
        }
        Some(Commands::Logs(cmd)) => {
            if let Err(e) = pm2_logs(cmd.project, debug).await {
                let _ = log_error("pm2", "pm2 logs failed", Some(&e)).await;
            }
        }
        Some(Commands::List) => {
            if let Err(e) = pm2_list(debug).await {
                let _ = log_error("pm2", "pm2 list failed", Some(&e)).await;
            }
        }
        Some(Commands::Curl { url }) => {
            let url = url.unwrap_or_else(|| "https://example.com/api".to_string());
            if let Err(e) = curl::run_curl(&url, debug).await {
                let _ = log_error("curl", "Curl command failed", Some(&e)).await;
            }
        }
        None => {
            let _ = log_error(
                "system",
                "No subcommand provided. Usage: xbp [SUBCOMMAND] | xbp -l",
                None,
            )
            .await;
        }
    }
}