Files
xboct-deploy/src/main.rs
T

200 lines
5.6 KiB
Rust

mod cli;
mod config;
mod deploy;
mod git;
mod local;
mod ssh;
mod sync;
use std::{fs, path::Path, process::ExitCode};
use anyhow::{Context, Result, bail};
use clap::Parser;
use colored::Colorize;
use crate::{
cli::{Cli, Commands, DeployArgs},
config::{Config, Project},
};
fn main() -> ExitCode {
#[cfg(windows)]
if enable_ansi_support::enable_ansi_support().is_err() {
colored::control::set_override(false);
}
let cli = Cli::parse();
match run(&cli) {
Ok(()) => ExitCode::SUCCESS,
Err(err) => {
eprintln!("{} {err:#}", "error:".red().bold());
ExitCode::FAILURE
}
}
}
fn handle_deploy(cli: &Cli, args: &DeployArgs, cfg: &config::Config) -> Result<()> {
let name = match &args.project_name {
Some(name) => name,
None => get_project_name_by_cwd(cfg)?,
};
let project = &cfg.projects[name];
deploy(cli, name, project)
}
fn handle_list(path: &Path, cfg: &config::Config) -> Result<()> {
println!("Config: {}", config::display(path));
println!("Configured projects:");
for name in cfg.projects.keys() {
println!(" - {name}");
}
Ok(())
}
fn handle_edit_config() -> Result<()> {
let path = config::global_config_path(home::home_dir().as_deref())
.context("cannot determine home directory")?;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)
.with_context(|| format!("cannot create {}", parent.display()))?;
}
if !path.exists() {
fs::write(&path, "").with_context(|| format!("cannot create {}", path.display()))?;
}
let editor = std::env::var("EDITOR")
.or_else(|_| std::env::var("VISUAL"))
.unwrap_or_else(|_| {
#[cfg(target_os = "windows")]
{
"notepad".to_string()
}
#[cfg(not(target_os = "windows"))]
{
"vi".to_string()
}
});
let cmd = format!("{editor} {}", path.display());
let mut process = {
#[cfg(target_os = "windows")]
{
let mut c = std::process::Command::new("cmd");
c.arg("/C").arg(&cmd);
c
}
#[cfg(not(target_os = "windows"))]
{
let mut c = std::process::Command::new("sh");
c.arg("-c").arg(&cmd);
c
}
};
let status = process
.status()
.with_context(|| format!("failed to launch editor: {editor}"))?;
if !status.success() {
std::process::exit(status.code().unwrap_or(1));
}
Ok(())
}
fn handle_list_servers(path: &Path, cfg: &config::Config) -> Result<()> {
println!("config: {}", config::display(path));
println!("configured servers:");
for (name, server) in &cfg.servers {
let mut line = format!(" - {name} {}", server.host);
if let Some(port) = server.port {
line.push_str(&format!(":{port}"));
}
if let Some(user) = &server.user {
line.push_str(&format!(" as {user}"));
}
println!("{line}");
}
Ok(())
}
fn handle_pick(cli: &Cli, path: &Path, cfg: &config::Config) -> Result<()> {
let name = match pick_project(cfg)? {
Some(name) => name,
None => return Ok(()),
};
let project = cfg
.projects
.get(&name)
.with_context(|| format!("project '{name}' not found in {}", path.display()))?;
deploy(cli, &name, project)
}
fn handle_default(path: &Path, cfg: &config::Config) -> Result<()> {
match get_project_name_by_cwd(cfg) {
Ok(project_name) => {
println!("Current project: '{}'", project_name);
println!("`To deploy run 'xd deploy'`");
Ok(())
}
Err(_) => {
println!("No project found in current directory");
println!("Run 'xd deploy [project]' to deploy a project");
handle_list(path, cfg)
}
}
}
fn deploy(cli: &Cli, name: &str, project: &Project) -> Result<()> {
deploy::deploy(name, project, cli.dry_run)
}
fn run(cli: &Cli) -> Result<()> {
let path = config::resolve(cli.config.as_deref())?;
let cfg = config::load(&path)?;
match &cli.command {
Some(Commands::Deploy(args)) => handle_deploy(cli, args, &cfg),
Some(Commands::Pick) => handle_pick(cli, &path, &cfg),
Some(Commands::List) => handle_list(&path, &cfg),
Some(Commands::ListServers) => handle_list_servers(&path, &cfg),
Some(Commands::EditConfig) => handle_edit_config(),
None => handle_default(&path, &cfg),
}
}
fn pick_project(cfg: &config::Config) -> Result<Option<String>> {
use dialoguer::FuzzySelect;
use std::io::IsTerminal;
if !std::io::stdout().is_terminal() {
bail!("interactive picker needs a real terminal; use --list instead");
}
let names: Vec<&str> = cfg.projects.keys().map(String::as_str).collect();
match FuzzySelect::new()
.with_prompt("Pick a project")
.items(&names)
.default(0)
.interact()
{
Ok(idx) => Ok(Some(names[idx].to_string())),
Err(dialoguer::Error::IO(err)) if err.kind() == std::io::ErrorKind::Interrupted => {
println!("cancelled.");
Ok(None)
}
Err(err) => Err(err).context("project selection failed"),
}
}
fn get_project_name_by_cwd(cfg: &Config) -> Result<&str> {
let cwd = std::env::current_dir().context("cannot determine current directory")?;
match config::find_by_cwd(cfg, &cwd) {
Some(name) => Ok(name),
None => bail!("no project found in {}", cwd.display()),
}
}