Skip to main content

auto_commit_rs/
update.rs

1use anyhow::{Context, Result};
2use colored::Colorize;
3use std::time::Duration;
4
5const GITHUB_REPO: &str = "gtkacz/auto-commit-rs";
6const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION");
7
8pub struct VersionCheck {
9    pub latest: String,
10    pub current: String,
11    pub update_available: bool,
12}
13
14/// Fetch the latest release tag from GitHub API with a short timeout
15pub fn fetch_latest_version() -> Result<String> {
16    let url = format!(
17        "https://api.github.com/repos/{}/releases/latest",
18        GITHUB_REPO
19    );
20    let agent = ureq::AgentBuilder::new()
21        .timeout(Duration::from_secs(5))
22        .build();
23    let response: serde_json::Value = agent
24        .get(&url)
25        .set("User-Agent", "cgen")
26        .set("Accept", "application/vnd.github.v3+json")
27        .call()
28        .context("Failed to reach GitHub API")?
29        .into_json()
30        .context("Failed to parse GitHub API response")?;
31
32    let tag = response["tag_name"]
33        .as_str()
34        .context("No tag_name in GitHub release response")?;
35
36    Ok(tag.to_string())
37}
38
39/// Parse a version string (strips leading 'v' if present) into (major, minor, patch)
40pub fn parse_semver(version: &str) -> Option<(u64, u64, u64)> {
41    let v = version.strip_prefix('v').unwrap_or(version);
42    let parts: Vec<&str> = v.split('.').collect();
43    if parts.len() != 3 {
44        return None;
45    }
46    Some((
47        parts[0].parse().ok()?,
48        parts[1].parse().ok()?,
49        parts[2].parse().ok()?,
50    ))
51}
52
53/// Check if a newer version is available on GitHub
54pub fn check_version() -> Result<VersionCheck> {
55    let latest = fetch_latest_version()?;
56    let current = CURRENT_VERSION.to_string();
57
58    let update_available = match (parse_semver(&latest), parse_semver(&current)) {
59        (Some(latest_v), Some(current_v)) => latest_v > current_v,
60        _ => false,
61    };
62
63    Ok(VersionCheck {
64        latest,
65        current,
66        update_available,
67    })
68}
69
70/// Run the appropriate update command for the current platform
71pub fn run_update() -> Result<()> {
72    if is_cargo_available() {
73        println!("{}", "Updating via cargo...".cyan().bold());
74        let status = std::process::Command::new("cargo")
75            .args(["install", "auto-commit-rs"])
76            .status()
77            .context("Failed to run cargo install")?;
78
79        if !status.success() {
80            anyhow::bail!("cargo install failed with exit code {}", status);
81        }
82    } else {
83        run_platform_installer()?;
84    }
85
86    println!("{}", "Update complete!".green().bold());
87    Ok(())
88}
89
90fn is_cargo_available() -> bool {
91    std::process::Command::new("cargo")
92        .arg("--version")
93        .stdout(std::process::Stdio::null())
94        .stderr(std::process::Stdio::null())
95        .status()
96        .map(|s| s.success())
97        .unwrap_or(false)
98}
99
100fn run_platform_installer() -> Result<()> {
101    if cfg!(target_os = "windows") {
102        println!("{}", "Updating via PowerShell installer...".cyan().bold());
103        let status = std::process::Command::new("powershell")
104            .args([
105                "-ExecutionPolicy",
106                "Bypass",
107                "-Command",
108                "irm https://raw.githubusercontent.com/gtkacz/auto-commit-rs/main/scripts/install.ps1 | iex",
109            ])
110            .status()
111            .context("Failed to run PowerShell installer")?;
112
113        if !status.success() {
114            anyhow::bail!("PowerShell installer failed");
115        }
116    } else {
117        println!("{}", "Updating via install script...".cyan().bold());
118        let status = std::process::Command::new("bash")
119            .args([
120                "-c",
121                "curl -fsSL https://raw.githubusercontent.com/gtkacz/auto-commit-rs/main/scripts/install.sh | bash",
122            ])
123            .status()
124            .context("Failed to run install script")?;
125
126        if !status.success() {
127            anyhow::bail!("Install script failed");
128        }
129    }
130
131    Ok(())
132}
133
134/// Print a warning that a newer version is available
135pub fn print_update_warning(latest: &str) {
136    eprintln!(
137        "\n{}  {} → {}  (run {} to update)",
138        "Update available!".yellow().bold(),
139        CURRENT_VERSION.dimmed(),
140        latest.green(),
141        "cgen update".cyan(),
142    );
143}
144
145pub fn current_version() -> &'static str {
146    CURRENT_VERSION
147}