oph 0.1.0

OpenHealth CLI - Universal development tool for the OpenHealth team
Documentation
use crate::config::{project_root, Environment, DOCKER_DEV_DIR};
use crate::docker;
use crate::ui;
use anyhow::Result;
use clap::Subcommand;
use std::process::Command;

#[derive(Subcommand)]
pub enum DevCommand {
    /// Initial setup for development environment
    Setup,
    /// Start all development services
    Up,
    /// Stop all development services
    Down,
    /// Restart all development services
    Restart,
    /// Show status of development services
    Status,
    /// Show logs from development services (follow mode)
    Logs {
        /// Service name (optional, shows all if not specified)
        service: Option<String>,
    },
    /// Open shell in a service container
    Shell {
        /// Service name (e.g., database, cache)
        service: String,
    },
    /// Rebuild and restart development services
    Rebuild,
    /// Stop services and remove volumes (destroys data)
    Clean,
    /// Regenerate docker-compose.yml from .env
    Generate,
}

pub fn run(cmd: DevCommand) -> Result<()> {
    docker::check_docker()?;
    let env = Environment::Dev;

    match cmd {
        DevCommand::Setup => setup()?,
        DevCommand::Up => up(env)?,
        DevCommand::Down => down(env)?,
        DevCommand::Restart => restart(env)?,
        DevCommand::Status => status(env)?,
        DevCommand::Logs { service } => logs(env, service.as_deref())?,
        DevCommand::Shell { service } => shell(env, &service)?,
        DevCommand::Rebuild => rebuild(env)?,
        DevCommand::Clean => clean(env)?,
        DevCommand::Generate => generate()?,
    }

    Ok(())
}

fn setup() -> Result<()> {
    ui::info("Setting up development environment...");

    let root = project_root()?;
    let dev_dir = root.join(DOCKER_DEV_DIR);
    let env_example = dev_dir.join(".env.example");
    let env_file = dev_dir.join(".env");

    // Copy .env.example to .env if it doesn't exist
    if env_example.exists() && !env_file.exists() {
        std::fs::copy(&env_example, &env_file)?;
        ui::success("Created .env file from .env.example");
    } else if env_file.exists() {
        ui::warning(".env file already exists, skipping");
    }

    // Generate SSL certificates
    ui::info("Generating SSL certificates...");
    let script = dev_dir.join("scripts/generate-ssl.sh");
    if script.exists() {
        let status = Command::new("sh")
            .arg(&script)
            .arg("localhost")
            .current_dir(&dev_dir)
            .status()?;

        if status.success() {
            ui::success("SSL certificates generated!");
        } else {
            ui::warning("SSL certificate generation may have failed");
        }
    } else {
        ui::warning("SSL generation script not found, skipping");
    }

    ui::success("Development environment ready!");
    Ok(())
}

fn up(env: Environment) -> Result<()> {
    ui::info("Starting development services...");
    let status = docker::compose(env, &["up", "-d"])?;

    if status.success() {
        ui::success("Services started!");
        status_internal(env)?;
    }

    Ok(())
}

fn down(env: Environment) -> Result<()> {
    ui::info("Stopping development services...");
    let status = docker::compose(env, &["down"])?;

    if status.success() {
        ui::success("Services stopped!");
    }

    Ok(())
}

fn restart(env: Environment) -> Result<()> {
    ui::info("Restarting development services...");
    let status = docker::compose(env, &["restart"])?;

    if status.success() {
        ui::success("Services restarted!");
    }

    Ok(())
}

fn status(env: Environment) -> Result<()> {
    ui::info("Development Services Status:");
    status_internal(env)
}

fn status_internal(env: Environment) -> Result<()> {
    docker::compose(env, &["ps"])?;
    Ok(())
}

fn logs(env: Environment, service: Option<&str>) -> Result<()> {
    let args = match service {
        Some(s) => vec!["logs", "-f", s],
        None => vec!["logs", "-f"],
    };

    docker::compose(env, &args)?;
    Ok(())
}

fn shell(env: Environment, service: &str) -> Result<()> {
    docker::exec(env, service, &["sh"])?;
    Ok(())
}

fn rebuild(env: Environment) -> Result<()> {
    ui::info("Rebuilding development services...");
    let status = docker::compose(env, &["up", "-d", "--build", "--force-recreate"])?;

    if status.success() {
        ui::success("Services rebuilt!");
    }

    Ok(())
}

fn clean(env: Environment) -> Result<()> {
    if !ui::confirm_dangerous("This will destroy all development data!") {
        ui::info("Aborted.");
        return Ok(());
    }

    let status = docker::compose(env, &["down", "-v"])?;

    if status.success() {
        ui::success("Cleanup complete!");
    }

    Ok(())
}

fn generate() -> Result<()> {
    ui::info("Regenerating docker-compose.yml...");

    let root = project_root()?;
    let dev_dir = root.join(DOCKER_DEV_DIR);
    let script = dev_dir.join("scripts/generate-compose.sh");

    if script.exists() {
        let status = Command::new("sh")
            .arg(&script)
            .current_dir(&dev_dir)
            .status()?;

        if status.success() {
            ui::success("Done!");
        }
    } else {
        ui::error("generate-compose.sh script not found");
    }

    Ok(())
}