ci / rust (push) Successful in 2m40s
Rewrites CLAUDE.md as the current-architecture reference, drops the registry from INSTALL/PRODUCT/DESIGN and the web README, deletes the registry Dockerfile, and marks the dated design specs superseded where they describe the registry, JWT auth, or cross-host attach. The installer loses its configure step — every config field now has a default, so a daemon starts with no config file at all.
212 lines
6.3 KiB
Rust
212 lines
6.3 KiB
Rust
//! Builders for the external commands the wizard shells out to.
|
|
//!
|
|
//! Kept pure so unit tests can assert the exact argv we'd hand to
|
|
//! `std::process::Command` without ever exec'ing `cargo` or `systemctl`.
|
|
|
|
use std::path::PathBuf;
|
|
|
|
/// Where to fetch the maestro crates from when running `cargo install`.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum CargoSource {
|
|
/// `cargo install --git <url> [--branch <branch>] <crate>`.
|
|
Git { url: String, branch: Option<String> },
|
|
/// `cargo install --path <workspace_root> -p <crate>`.
|
|
Path(PathBuf),
|
|
}
|
|
|
|
/// `systemctl --user` verbs the wizard runs.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum SystemctlAction {
|
|
DaemonReload,
|
|
Enable,
|
|
Start,
|
|
Restart,
|
|
}
|
|
|
|
impl SystemctlAction {
|
|
pub fn verb(self) -> &'static str {
|
|
match self {
|
|
Self::DaemonReload => "daemon-reload",
|
|
Self::Enable => "enable",
|
|
Self::Start => "start",
|
|
Self::Restart => "restart",
|
|
}
|
|
}
|
|
|
|
fn takes_unit_arg(self) -> bool {
|
|
!matches!(self, Self::DaemonReload)
|
|
}
|
|
}
|
|
|
|
/// Build the args for `cargo install <args...>` (excluding the `cargo` and
|
|
/// `install` words themselves). `locked` adds `--locked` so the install uses
|
|
/// the committed `Cargo.lock`.
|
|
/// Build the `cargo install` argument vector.
|
|
///
|
|
/// `features`, when `Some`, restricts the build to an explicit feature set via
|
|
/// `--no-default-features --features <list>`.
|
|
pub fn cargo_install_args(
|
|
source: &CargoSource,
|
|
crate_name: &str,
|
|
locked: bool,
|
|
features: Option<&str>,
|
|
) -> Vec<String> {
|
|
let mut args: Vec<String> = Vec::new();
|
|
match source {
|
|
CargoSource::Git { url, branch } => {
|
|
args.push("--git".into());
|
|
args.push(url.clone());
|
|
if let Some(b) = branch {
|
|
args.push("--branch".into());
|
|
args.push(b.clone());
|
|
}
|
|
args.push(crate_name.to_string());
|
|
}
|
|
CargoSource::Path(root) => {
|
|
args.push("--path".into());
|
|
args.push(root.display().to_string());
|
|
args.push("-p".into());
|
|
args.push(crate_name.to_string());
|
|
}
|
|
}
|
|
if let Some(list) = features {
|
|
args.push("--no-default-features".into());
|
|
args.push("--features".into());
|
|
args.push(list.to_string());
|
|
}
|
|
if locked {
|
|
args.push("--locked".into());
|
|
}
|
|
args
|
|
}
|
|
|
|
/// Build the args for `systemctl --user <verb> [unit]`.
|
|
pub fn systemctl_user_args(action: SystemctlAction, unit: &str) -> Vec<String> {
|
|
let mut args = vec!["--user".to_string(), action.verb().to_string()];
|
|
if action.takes_unit_arg() {
|
|
args.push(unit.to_string());
|
|
}
|
|
args
|
|
}
|
|
|
|
/// `cargo` binary path. We prefer `$CARGO` (set by `cargo install` itself when
|
|
/// it invokes child processes, and by `rustup`), then `~/.cargo/bin/cargo`, and
|
|
/// finally a bare `cargo` so PATH lookup applies.
|
|
pub fn cargo_bin() -> PathBuf {
|
|
if let Ok(p) = std::env::var("CARGO") {
|
|
return PathBuf::from(p);
|
|
}
|
|
if let Some(home) = dirs::home_dir() {
|
|
let in_cargo_home = home.join(".cargo").join("bin").join("cargo");
|
|
if in_cargo_home.exists() {
|
|
return in_cargo_home;
|
|
}
|
|
}
|
|
PathBuf::from("cargo")
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn git_args_no_branch() {
|
|
let src = CargoSource::Git {
|
|
url: "https://git.naps.pt/naps62/maestro".into(),
|
|
branch: None,
|
|
};
|
|
let args = cargo_install_args(&src, "maestro", false, None);
|
|
assert_eq!(
|
|
args,
|
|
vec!["--git", "https://git.naps.pt/naps62/maestro", "maestro",]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn git_args_with_features_omit_defaults() {
|
|
let src = CargoSource::Git {
|
|
url: "https://git.naps.pt/naps62/maestro".into(),
|
|
branch: None,
|
|
};
|
|
let args = cargo_install_args(&src, "maestro", true, Some("cli,daemon,tui"));
|
|
assert_eq!(
|
|
args,
|
|
vec![
|
|
"--git",
|
|
"https://git.naps.pt/naps62/maestro",
|
|
"maestro",
|
|
"--no-default-features",
|
|
"--features",
|
|
"cli,daemon,tui",
|
|
"--locked",
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn git_args_with_branch_and_locked() {
|
|
let src = CargoSource::Git {
|
|
url: "https://git.example/x".into(),
|
|
branch: Some("main".into()),
|
|
};
|
|
let args = cargo_install_args(&src, "maestro", true, None);
|
|
assert_eq!(
|
|
args,
|
|
vec![
|
|
"--git",
|
|
"https://git.example/x",
|
|
"--branch",
|
|
"main",
|
|
"maestro",
|
|
"--locked",
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn path_args_use_p_flag() {
|
|
let src = CargoSource::Path(PathBuf::from("/repos/maestro"));
|
|
let args = cargo_install_args(&src, "maestro", true, None);
|
|
assert_eq!(
|
|
args,
|
|
vec!["--path", "/repos/maestro", "-p", "maestro", "--locked",]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn systemctl_daemon_reload_omits_unit() {
|
|
let args = systemctl_user_args(SystemctlAction::DaemonReload, "maestro.service");
|
|
assert_eq!(args, vec!["--user", "daemon-reload"]);
|
|
}
|
|
|
|
#[test]
|
|
fn systemctl_enable_includes_unit() {
|
|
let args = systemctl_user_args(SystemctlAction::Enable, "maestro.service");
|
|
assert_eq!(args, vec!["--user", "enable", "maestro.service"]);
|
|
}
|
|
|
|
#[test]
|
|
fn systemctl_start_includes_unit() {
|
|
let args = systemctl_user_args(SystemctlAction::Start, "maestro.service");
|
|
assert_eq!(args, vec!["--user", "start", "maestro.service"]);
|
|
}
|
|
|
|
#[test]
|
|
fn systemctl_restart_includes_unit() {
|
|
let args = systemctl_user_args(SystemctlAction::Restart, "maestro.service");
|
|
assert_eq!(args, vec!["--user", "restart", "maestro.service"]);
|
|
}
|
|
|
|
#[test]
|
|
fn cargo_bin_honors_env_var() {
|
|
// Save & restore $CARGO to keep the test hermetic.
|
|
let prev = std::env::var("CARGO").ok();
|
|
std::env::set_var("CARGO", "/opt/rust/bin/cargo");
|
|
assert_eq!(cargo_bin(), PathBuf::from("/opt/rust/bin/cargo"));
|
|
match prev {
|
|
Some(v) => std::env::set_var("CARGO", v),
|
|
None => std::env::remove_var("CARGO"),
|
|
}
|
|
}
|
|
}
|