xbp 0.6.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::commands::{list_services, run_service_command, show_service_help, is_xbp_project, run_redeploy_service};
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,
    #[arg(short = 'p', long = "port", help = "Filter by port number")]
    port: Option<u16>,

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

#[derive(Subcommand, Debug)]
enum Commands {
    Ports(PortsCmd),
    Setup,
    Redeploy {
        #[arg(help = "Service name to redeploy (optional, uses legacy redeploy.sh if not provided)")]
        service_name: Option<String>,
    },
    RedeployV2(RedeployV2Cmd),
    Config,
    Install {
        package: String,
    },
    Logs(LogsCmd),
    List,
    Curl {
        #[arg(help = "URL to fetch, e.g. https://example.com/api")]
        url: Option<String>,
    },
    Services,
    Service {
        #[arg(help = "Command to run: build, install, start, dev, or --help")]
        command: Option<String>,
        #[arg(help = "Service name")]
        service_name: 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();
            // Use global port flag if set, otherwise use command-specific port
            let port = cli.port.or(cmd.port);
            if let Some(p) = 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 { service_name }) => {
            if let Some(name) = service_name {
                // New service-based redeploy
                if let Err(e) = run_redeploy_service(&name, debug).await {
                    let _ = log_error("redeploy", "Service redeploy failed", Some(&e)).await;
                }
            } else {
                // Legacy redeploy.sh
                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;
            }
        }
        Some(Commands::Services) => {
            if let Err(e) = list_services(debug).await {
                let _ = log_error("services", "Failed to list services", Some(&e)).await;
            }
        }
        Some(Commands::Service { command, service_name }) => {
            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");
            }
        }
        None => {
            // If -p flag is provided without a command, treat it as ports command
            if let Some(port) = cli.port {
                let sim_args = vec!["-p".to_string(), port.to_string()];
                if let Err(e) = run_ports(&sim_args, debug).await {
                    let _ = log_error("ports", "Error running ports", Some(&e)).await;
                }
                return;
            }
            
            // Check if we're in an xbp project
            if is_xbp_project().await {
                println!("XBP Project Detected\n");
                if let Err(e) = list_services(debug).await {
                    let _ = log_error("services", "Failed to list services", Some(&e)).await;
                }
                println!("\nAvailable Commands:");
                println!("  xbp services                    - List all services");
                println!("  xbp service <cmd> <name>       - Run command for a service");
                println!("  xbp redeploy <name>             - Redeploy a service");
                println!("  xbp config                      - Show configuration");
                println!("\nFor more help: xbp --help");
            } else {
                let _ = log_error(
                    "system",
                    "No subcommand provided. Usage: xbp [SUBCOMMAND] | xbp -l | xbp -p <port>",
                    None,
                )
                .await;
            }
        }
    }
}