xbp 0.5.1

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 {
        eprintln!("Failed to initialize logger: {}", e);
    }

    if cli.list && cli.command.is_none() {
        if let Err(e) = pm2_list(debug).await {
            eprintln!("\x1b[91m❌ pm2 list failed: {}\x1b[0m", e);
        }
        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());
            }
            if cmd.nginx {
                sim_args.push("-n".to_string());
            }
            if let Err(e) = run_ports(&sim_args, debug).await {
                eprintln!("\x1b[91mError running ports: {}\x1b[0m", e);
            }
        }
        Some(Commands::Setup) => {
            if let Err(e) = run_setup(debug).await {
                eprintln!("\x1b[91mSetup failed: {}\x1b[0m", e);
            }
        }
        Some(Commands::Redeploy) => {
            if let Err(e) = run_redeploy(debug).await {
                eprintln!("\x1b[91mRedeploy failed: {}\x1b[0m", e);
                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) => {
                    eprintln!("\x1b[91mRemote redeploy failed: {}\x1b[0m", 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(()) => {
                    println!("Installation successful");
                    let success_msg = format!("Successfully installed: {}", package);
                    let _ = log_success("install", &success_msg, None).await;
                }
                Err(e) => {
                    eprintln!("Installation failed: {}", 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 {
                eprintln!("\x1b[91m❌ pm2 logs failed: {}\x1b[0m", e);
            }
        }
        Some(Commands::List) => {
            if let Err(e) = pm2_list(debug).await {
                eprintln!("\x1b[91m❌ pm2 list failed: {}\x1b[0m", e);
            }
        }
        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 {
                eprintln!("\x1b[91m❌ Curl command failed: {}\x1b[0m", e);
            }
        }
        None => {
            eprintln!("Usage: xbp [SUBCOMMAND] | xbp -l");
        }
    }
}