workhelix_cli_common/
update.rs

1//! Self-update module.
2//!
3//! This module provides self-update functionality for CLI tools by delegating
4//! to the install script (install.sh), which handles:
5//! - Checking for latest releases on GitHub
6//! - Downloading release binaries
7//! - Verifying checksums (mandatory)
8//! - Version comparison and upgrade logic
9//! - Replacing the current binary
10
11use crate::types::RepoInfo;
12use std::path::Path;
13use std::process::Command;
14
15/// Run update command to install latest or specified version.
16///
17/// This delegates to the install.sh script, which handles version checking,
18/// download, checksum verification, and installation.
19///
20/// Returns exit code: 0 if successful, 1 on error, 2 if already up-to-date.
21///
22/// # Arguments
23/// * `repo_info` - Repository information for GitHub integration
24/// * `_current_version` - Current version of the tool (unused, install.sh detects this)
25/// * `version` - Optional specific version to install (currently unsupported, always installs latest)
26/// * `force` - Force reinstall even if already up-to-date
27/// * `install_dir` - Optional custom installation directory
28///
29/// # Panics
30/// May panic if stdout flush fails during user interaction.
31#[must_use]
32pub fn run_update(
33    repo_info: &RepoInfo,
34    _current_version: &str,
35    version: Option<&str>,
36    force: bool,
37    install_dir: Option<&Path>,
38) -> i32 {
39    if version.is_some() {
40        eprintln!("⚠️  Specific version installation not yet supported");
41        eprintln!("   The install script will install the latest version");
42        println!();
43    }
44
45    println!("🔄 Running installation script...");
46    println!();
47
48    // Build install.sh URL
49    let install_script_url = format!(
50        "https://raw.githubusercontent.com/{}/{}/main/install.sh",
51        repo_info.owner, repo_info.name
52    );
53
54    // Build command to download and execute install script
55    let mut cmd = Command::new("sh");
56    cmd.arg("-c");
57
58    // Build the command string with environment variables
59    let mut env_vars = Vec::new();
60    env_vars.push(format!("REPO_OWNER={}", repo_info.owner));
61    env_vars.push(format!("REPO_NAME={}", repo_info.name));
62
63    if force {
64        env_vars.push("FORCE_INSTALL=1".to_string());
65    }
66
67    if let Some(dir) = install_dir {
68        env_vars.push(format!("INSTALL_DIR={}", dir.display()));
69    }
70
71    let env_string = env_vars.join(" ");
72    let command_string = format!("{env_string} curl -fsSL {install_script_url} | sh");
73
74    cmd.arg(&command_string);
75
76    // Execute the command
77    match cmd.status() {
78        Ok(status) => {
79            if status.success() {
80                0
81            } else {
82                status.code().unwrap_or(1)
83            }
84        }
85        Err(e) => {
86            eprintln!("❌ Failed to run install script: {e}");
87            eprintln!("   Make sure curl is installed and you have internet access");
88            1
89        }
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn test_repo_info_latest_release_url() {
99        let repo = RepoInfo::new("workhelix", "prompter", "prompter-v");
100        let url = repo.latest_release_url();
101        assert_eq!(
102            url,
103            "https://api.github.com/repos/workhelix/prompter/releases/latest"
104        );
105    }
106
107    #[test]
108    fn test_install_script_url_construction() {
109        let repo = RepoInfo::new("tftio", "peter-hook", "v");
110        let expected = "https://raw.githubusercontent.com/tftio/peter-hook/main/install.sh";
111        let actual = format!(
112            "https://raw.githubusercontent.com/{}/{}/main/install.sh",
113            repo.owner, repo.name
114        );
115        assert_eq!(actual, expected);
116    }
117}