Skip to main content

ferrous_forge/commands/
update.rs

1//! Update command implementation
2
3use crate::{Error, Result};
4use console::style;
5
6/// Execute the update command
7///
8/// # Errors
9///
10/// Returns an error if the binary or rules update process fails.
11pub async fn execute(channel: String, rules_only: bool, dry_run: bool) -> Result<()> {
12    if dry_run {
13        println!(
14            "{}",
15            style("🔍 Dry run mode - showing what would be updated")
16                .bold()
17                .yellow()
18        );
19    } else {
20        println!("{}", style("🔄 Updating Ferrous Forge...").bold().cyan());
21    }
22
23    if rules_only {
24        update_rules(&channel, dry_run).await?;
25    } else {
26        update_binary(&channel, dry_run).await?;
27        update_rules(&channel, dry_run).await?;
28    }
29
30    if !dry_run {
31        println!("{}", style("✅ Update complete!").bold().green());
32    }
33
34    Ok(())
35}
36
37/// Update the Ferrous Forge binary to the latest version from GitHub releases.
38///
39/// Uses the `self_update` crate to download the correct archive for the
40/// current platform, extract it, and replace the running binary.
41///
42/// # Arguments
43/// * `channel` - Update channel (stable, beta, nightly)
44/// * `dry_run` - If true, only shows what would be updated without making changes
45async fn update_binary(channel: &str, dry_run: bool) -> Result<()> {
46    println!("đŸ“Ļ Checking for binary updates on {} channel...", channel);
47
48    if dry_run {
49        println!("  Would check GitHub releases for newer version");
50        println!(
51            "  Would download and install new binary for {}",
52            get_target_name()?
53        );
54        return Ok(());
55    }
56
57    // self_update performs blocking I/O; run it on a dedicated blocking thread
58    // to avoid panicking the async runtime.
59    let status = tokio::task::spawn_blocking(move || {
60        self_update::backends::github::Update::configure()
61            .repo_owner("kryptobaseddev")
62            .repo_name("ferrous-forge")
63            .bin_name("ferrous-forge")
64            .target(&get_target_name()?)
65            .bin_path_in_archive(&get_bin_path_in_archive())
66            .bin_install_path(
67                std::env::current_exe()
68                    .map_err(|e| Error::io(format!("Cannot locate current executable: {e}")))?,
69            )
70            .current_version(env!("CARGO_PKG_VERSION"))
71            .show_download_progress(true)
72            .show_output(true)
73            .build()
74            .map_err(|e| Error::process(format!("Failed to configure updater: {e}")))?
75            .update()
76            .map_err(|e| Error::process(format!("Update failed: {e}")))
77    })
78    .await
79    .map_err(|e| Error::process(format!("Update task panicked: {e}")))??;
80
81    match status {
82        self_update::Status::Updated(v) => {
83            println!(
84                "{}",
85                style(format!("🆕 Updated to version {v}!")).green().bold()
86            );
87        }
88        self_update::Status::UpToDate(v) => {
89            println!("{}", style(format!("✅ Already up to date (v{v})")).green());
90        }
91    }
92
93    Ok(())
94}
95
96/// Determine the asset target name for the current platform.
97///
98/// GitHub release assets are named like `ferrous-forge-{target}.{ext}`.
99/// This function returns the `{target}` portion.
100fn get_target_name() -> Result<String> {
101    let os = std::env::consts::OS;
102    let arch = std::env::consts::ARCH;
103
104    let suffix = match (os, arch) {
105        ("linux", "x86_64") => {
106            if cfg!(target_env = "musl") {
107                "linux-x86_64-musl.tar.gz"
108            } else {
109                "linux-x86_64.tar.gz"
110            }
111        }
112        ("macos", "x86_64") => "macos-x86_64.tar.gz",
113        ("macos", "aarch64") => "macos-aarch64.tar.gz",
114        ("windows", "x86_64") => "windows-x86_64.zip",
115        _ => {
116            return Err(Error::config(format!(
117                "Self-update is not supported on {os}/{arch}"
118            )));
119        }
120    };
121
122    Ok(suffix.to_string())
123}
124
125/// Return the binary path inside the release archive.
126fn get_bin_path_in_archive() -> String {
127    if cfg!(windows) {
128        "ferrous-forge.exe".to_string()
129    } else {
130        "ferrous-forge".to_string()
131    }
132}
133
134/// Update validation rules from the remote repository
135///
136/// Fetches the latest clippy rules and other validation configurations
137/// from the Ferrous Forge repository for the specified channel.
138///
139/// # Arguments
140/// * `channel` - Update channel for rules (stable, beta, nightly)
141/// * `dry_run` - If true, only shows what would be updated without making changes
142async fn update_rules(channel: &str, dry_run: bool) -> Result<()> {
143    println!("📋 Checking for rules updates on {} channel...", channel);
144
145    if dry_run {
146        println!("  Would fetch latest clippy rules from repository");
147        println!("  Would update ~/.clippy.toml with new rules");
148        return Ok(());
149    }
150
151    // Rules are embedded in the binary; updating the binary also updates rules.
152    println!("  â„šī¸  Rules are bundled with the binary and updated automatically.");
153
154    Ok(())
155}