use std::path::{Path, PathBuf}; use crate::engine::{EngineError, EngineResult}; /// Link the user-facing command to a packaged `bds-cli` binary. pub fn install_launcher(executable: &Path, home_dir: &Path) -> EngineResult { if !executable.is_file() { return Err(EngineError::Validation(format!( "installing the CLI requires the packaged bds-cli executable (not found at {})", executable.display() ))); } let bin_dir = home_dir.join(".local/bin"); std::fs::create_dir_all(&bin_dir)?; let target = bin_dir.join(if cfg!(windows) { "bds-cli.exe" } else { "bds-cli" }); let source = executable.canonicalize()?; if let Err(error) = std::fs::remove_file(&target) && error.kind() != std::io::ErrorKind::NotFound { return Err(error.into()); } #[cfg(unix)] std::os::unix::fs::symlink(source, &target)?; #[cfg(windows)] std::os::windows::fs::symlink_file(source, &target)?; #[cfg(not(any(unix, windows)))] return Err(EngineError::Validation( "installing the CLI is not supported on this platform".to_string(), )); Ok(target) } /// Resolve the CLI shipped beside the desktop executable and install it. pub fn install_packaged_launcher(home_dir: &Path) -> EngineResult { let app = std::env::current_exe()?; let cli = app.with_file_name(if cfg!(windows) { "bds-cli.exe" } else { "bds-cli" }); install_launcher(&cli, home_dir) } #[cfg(test)] mod tests { use super::*; #[cfg(unix)] #[test] fn install_replaces_existing_launcher_with_symlink() { let root = tempfile::tempdir().unwrap(); let executable = root.path().join("packaged-bds-cli"); std::fs::write(&executable, b"binary").unwrap(); let home = root.path().join("home"); let target = home.join(".local/bin/bds-cli"); std::fs::create_dir_all(target.parent().unwrap()).unwrap(); std::fs::write(&target, b"old launcher").unwrap(); let target = install_launcher(&executable, &home).unwrap(); assert!(target.is_symlink()); assert_eq!( std::fs::read_link(&target).unwrap(), executable.canonicalize().unwrap() ); assert_eq!(install_launcher(&executable, &home).unwrap(), target); let replacement = root.path().join("replacement-bds-cli"); std::fs::write(&replacement, b"new binary").unwrap(); assert_eq!(install_launcher(&replacement, &home).unwrap(), target); assert_eq!( std::fs::read_link(target).unwrap(), replacement.canonicalize().unwrap() ); } #[cfg(unix)] #[test] fn install_replaces_broken_symlink() { let root = tempfile::tempdir().unwrap(); let executable = root.path().join("packaged-bds-cli"); std::fs::write(&executable, b"binary").unwrap(); let home = root.path().join("home"); let target = home.join(".local/bin/bds-cli"); std::fs::create_dir_all(target.parent().unwrap()).unwrap(); std::os::unix::fs::symlink(root.path().join("missing-bds-cli"), &target).unwrap(); assert_eq!(install_launcher(&executable, &home).unwrap(), target); assert!(target.is_symlink()); assert_eq!( std::fs::read_link(target).unwrap(), executable.canonicalize().unwrap() ); } }