feat: add deploy from current work directory command
This commit is contained in:
@@ -24,6 +24,7 @@ xboctploy --list-servers # показать серверы из конфига
|
||||
xboctploy --pick # интерактивно выбрать проект (нечёткий поиск)
|
||||
xboctploy --dry-run demo # напечатать план деплоя без выполнения
|
||||
xboctploy demo # задеплоить проект demo
|
||||
xboctploy --this # задеплоить проект текущей папки (глобальный конфиг)
|
||||
```
|
||||
|
||||
Вывод:
|
||||
@@ -44,6 +45,10 @@ Deploy successful! (total 2.3s)
|
||||
2. `./deploy.toml` в текущей папке — удобно держать рядом с проектом и в тестах;
|
||||
3. глобальный `~/.config/xboctploy/deploy.toml` — запуск «из любой папки».
|
||||
|
||||
`--this` деплоит проект, чей `workdir` совпадает с текущей папкой; конфиг
|
||||
ищется по обычной цепочке, так что временный локальный `./deploy.toml`
|
||||
переопределяет глобальный — создали файл, задеплоились, удалили.
|
||||
|
||||
Схема:
|
||||
|
||||
```toml
|
||||
|
||||
+4
-1
@@ -6,7 +6,7 @@ use clap::{ArgGroup, Parser};
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name = "xboctploy", version, about)]
|
||||
#[command(arg_required_else_help = true)]
|
||||
#[command(group = ArgGroup::new("target").required(true).args(["project", "list", "list_servers", "pick"]))]
|
||||
#[command(group = ArgGroup::new("target").required(true).args(["project", "list", "list_servers", "pick", "this"]))]
|
||||
pub struct Cli {
|
||||
/// Project name from deploy.toml
|
||||
pub project: Option<String>,
|
||||
@@ -25,4 +25,7 @@ pub struct Cli {
|
||||
/// Interactively pick a project to deploy (fuzzy search)
|
||||
#[arg(long)]
|
||||
pub pick: bool,
|
||||
/// Deploy the project whose workdir matches the current folder (global config)
|
||||
#[arg(long)]
|
||||
pub this: bool,
|
||||
}
|
||||
|
||||
+67
-5
@@ -168,16 +168,42 @@ pub fn expand_tilde(path: &Path) -> Result<PathBuf> {
|
||||
Ok(path.to_path_buf())
|
||||
}
|
||||
|
||||
pub fn display(path: &Path) -> String {
|
||||
let Some(home) = home::home_dir() else {
|
||||
return path.display().to_string();
|
||||
};
|
||||
let Ok(rest) = path.strip_prefix(&home) else {
|
||||
return path.display().to_string();
|
||||
};
|
||||
format!("~/{}", rest.to_string_lossy().replace('\\', "/"))
|
||||
}
|
||||
|
||||
pub fn global_path(home: Option<&Path>) -> Option<PathBuf> {
|
||||
let path = home?
|
||||
.join(".config")
|
||||
.join(GLOBAL_DIR)
|
||||
.join(CONFIG_FILE_NAME);
|
||||
path.is_file().then_some(path)
|
||||
}
|
||||
|
||||
fn pick_config(cwd: &Path, home: Option<&Path>) -> Option<PathBuf> {
|
||||
let local = cwd.join(CONFIG_FILE_NAME);
|
||||
if local.is_file() {
|
||||
return Some(local);
|
||||
}
|
||||
let global = home?
|
||||
.join(".config")
|
||||
.join(GLOBAL_DIR)
|
||||
.join(CONFIG_FILE_NAME);
|
||||
global.is_file().then_some(global)
|
||||
global_path(home)
|
||||
}
|
||||
|
||||
pub fn find_by_cwd<'a>(cfg: &'a Config, cwd: &Path) -> Option<&'a str> {
|
||||
let current = fs::canonicalize(cwd).unwrap_or_else(|_| cwd.to_path_buf());
|
||||
cfg.projects.iter().find_map(|(name, project)| {
|
||||
let wd = canonicalized(&project.workdir);
|
||||
(wd == current).then_some(name.as_str())
|
||||
})
|
||||
}
|
||||
|
||||
fn canonicalized(path: &Path) -> PathBuf {
|
||||
fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
|
||||
}
|
||||
|
||||
pub fn resolve(explicit: Option<&Path>) -> Result<PathBuf> {
|
||||
@@ -383,6 +409,18 @@ commands = []
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_abbreviates_home_with_tilde() {
|
||||
let home = home::home_dir().unwrap();
|
||||
let inside = home.join(".config").join(GLOBAL_DIR).join(CONFIG_FILE_NAME);
|
||||
assert_eq!(
|
||||
display(&inside),
|
||||
format!("~/.config/{GLOBAL_DIR}/{CONFIG_FILE_NAME}")
|
||||
);
|
||||
let outside = home.ancestors().nth(1).unwrap();
|
||||
assert_eq!(display(outside), outside.display().to_string());
|
||||
}
|
||||
|
||||
fn tmpdir(tag: &str) -> PathBuf {
|
||||
let dir =
|
||||
std::env::temp_dir().join(format!("xboctploy-tests-{}-{tag}", std::process::id()));
|
||||
@@ -424,4 +462,28 @@ commands = []
|
||||
assert_eq!(pick_config(&cwd, Some(&root.join("nope"))), None);
|
||||
let _ = fs::remove_dir_all(root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finds_project_whose_workdir_matches_cwd() {
|
||||
let root = tmpdir("find-cwd");
|
||||
let proj = root.join("proj");
|
||||
fs::create_dir_all(&proj).unwrap();
|
||||
let s = format!(
|
||||
r#"
|
||||
[projects.web]
|
||||
workdir = '{}'
|
||||
host = "vps"
|
||||
|
||||
[projects.api]
|
||||
workdir = '~/code/api'
|
||||
host = "vps"
|
||||
"#,
|
||||
proj.display()
|
||||
);
|
||||
let cfg = toml::from_str::<Config>(&s).unwrap().validate().unwrap();
|
||||
|
||||
assert_eq!(find_by_cwd(&cfg, &proj), Some("web"));
|
||||
assert_eq!(find_by_cwd(&cfg, &root.join("other")), None);
|
||||
let _ = fs::remove_dir_all(root);
|
||||
}
|
||||
}
|
||||
|
||||
+22
-2
@@ -28,11 +28,15 @@ fn main() -> ExitCode {
|
||||
}
|
||||
|
||||
fn run(cli: &cli::Cli) -> Result<()> {
|
||||
if cli.this {
|
||||
return deploy_this(cli.dry_run);
|
||||
}
|
||||
|
||||
let path = config::resolve(cli.config.as_deref())?;
|
||||
let cfg = config::load(&path)?;
|
||||
|
||||
if cli.list_servers {
|
||||
println!("config: {}", path.display());
|
||||
println!("config: {}", config::display(&path));
|
||||
println!("configured servers:");
|
||||
for (name, server) in &cfg.servers {
|
||||
let mut line = format!(" - {name} {}", server.host);
|
||||
@@ -48,7 +52,7 @@ fn run(cli: &cli::Cli) -> Result<()> {
|
||||
}
|
||||
|
||||
if cli.list {
|
||||
println!("config: {}", path.display());
|
||||
println!("config: {}", config::display(&path));
|
||||
println!("configured projects:");
|
||||
for name in cfg.projects.keys() {
|
||||
println!(" - {name}");
|
||||
@@ -74,6 +78,22 @@ fn run(cli: &cli::Cli) -> Result<()> {
|
||||
deploy::deploy(&name, project, cli.dry_run)
|
||||
}
|
||||
|
||||
fn deploy_this(dry_run: bool) -> Result<()> {
|
||||
let path = config::resolve(None)?;
|
||||
let cfg = config::load(&path)?;
|
||||
|
||||
let cwd = std::env::current_dir().context("cannot determine current directory")?;
|
||||
let Some(name) = config::find_by_cwd(&cfg, &cwd) else {
|
||||
bail!(
|
||||
"no project in {} has workdir matching {}",
|
||||
config::display(&path),
|
||||
cwd.display()
|
||||
);
|
||||
};
|
||||
println!("config: {}", config::display(&path));
|
||||
deploy::deploy(name, &cfg.projects[name], dry_run)
|
||||
}
|
||||
|
||||
fn pick_project(cfg: &config::Config) -> Result<Option<String>> {
|
||||
use dialoguer::FuzzySelect;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user