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 {
Setup,
Up,
Down,
Restart,
Status,
Logs {
service: Option<String>,
},
Shell {
service: String,
},
Rebuild,
Clean,
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");
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");
}
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(())
}