mod cli; mod config; mod local; use std::process::ExitCode; use anyhow::{Context, Result}; use clap::Parser; use colored::Colorize; fn main() -> ExitCode { let cli = cli::Cli::parse(); match run(&cli) { Ok(()) => ExitCode::SUCCESS, Err(err) => { eprintln!("{} {err:#}", "error:".red().bold()); ExitCode::FAILURE } } } fn run(cli: &cli::Cli) -> Result<()> { let path = config::resolve(cli.config.as_deref())?; let cfg = config::load(&path)?; if cli.list { println!("config: {}", path.display()); println!("configured projects:"); for name in cfg.projects.keys() { println!(" - {name}"); } return Ok(()); } let name = cli .project .as_deref() .expect("clap guarantees or --list"); let project = cfg .projects .get(name) .with_context(|| format!("project '{name}' not found in {}", path.display()))?; let target = match &project.user { Some(user) => format!("{user}@{}:{}", project.host, project.port), None => format!("{}:{}", project.host, project.port), }; println!( "deploying '{name}' to {target}: {} local cmd(s), {} sync rule(s), {} remote cmd(s)", project.local.as_ref().map_or(0, |c| c.commands.len()), project.sync.len(), project.remote.as_ref().map_or(0, |c| c.commands.len()), ); let local_cmds = project .local .as_ref() .map_or([].as_slice(), |c| c.commands.as_slice()); let remote_cmds = project .remote .as_ref() .map_or([].as_slice(), |c| c.commands.as_slice()); if cli.dry_run { println!("dry-run plan:"); for cmd in local_cmds { println!(" local : {cmd}"); } for rule in &project.sync { println!( " sync : {} -> {}", rule.source.display(), rule.target.display() ); } for cmd in remote_cmds { println!(" remote: {cmd}"); } } else { local::run_commands(local_cmds, &project.workdir)?; println!("[Local] done: {} cmd(s) succeeded", local_cmds.len()); if !project.sync.is_empty() { println!("stage 4 not implemented yet: SFTP file transfer"); } if !remote_cmds.is_empty() { println!("stage 3 not implemented yet: SSH remote execution"); } } Ok(()) }