hostcraft_cli/update/
mod.rs1mod utils;
2
3use crate::display::{print_success, print_up_to_date, print_updating};
4use std::error::Error;
5use std::sync::mpsc;
6use std::thread;
7use utils::{
8 binary_download_url, current_target, download_bytes, fetch_latest_cli_release,
9 is_newer_version, record_last_checked, replace_binary, should_check_for_update,
10 version_from_tag,
11};
12
13pub fn check_for_update() -> Box<dyn FnOnce() -> Option<String>> {
20 if !should_check_for_update() {
21 return Box::new(|| None);
22 }
23
24 let (tx, rx) = mpsc::channel();
25
26 thread::spawn(move || {
27 let result = fetch_latest_version().ok().flatten();
28 record_last_checked();
29 let _ = tx.send(result);
30 });
31
32 Box::new(move || rx.recv().ok().flatten())
33}
34
35pub fn fetch_latest_version() -> Result<Option<String>, Box<dyn Error>> {
39 let release = fetch_latest_cli_release()
40 .ok_or("Could not fetch release info from GitHub. Check your connection.")?;
41
42 let version = version_from_tag(&release.tag_name)
43 .ok_or_else(|| format!("Unexpected tag format: '{}'", release.tag_name))?;
44
45 let is_newer = is_newer_version(version)
46 .ok_or_else(|| format!("Could not parse version string: '{}'", version))?;
47
48 Ok(if is_newer {
49 Some(version.to_owned())
50 } else {
51 None
52 })
53}
54
55pub fn handle_update() -> Result<(), Box<dyn Error>> {
57 let latest =
58 fetch_latest_version().map_err(|e| format!("Failed to check for updates: {}", e))?;
59
60 match latest {
61 None => print_up_to_date(),
62 Some(version) => {
63 print_updating(&version);
64 install_latest(&version).map_err(|e| format!("Update failed: {}", e))?;
65 print_success(&format!("Updated to v{}", version));
66 }
67 }
68 Ok(())
69}
70
71fn install_latest(version: &str) -> Result<(), Box<dyn Error>> {
77 match current_target() {
78 Some(target) => {
79 let url = binary_download_url(version, target);
80 let bytes = download_bytes(&url)?;
81 replace_binary(&bytes)
82 }
83 None => install_via_cargo(),
84 }
85}
86
87fn install_via_cargo() -> Result<(), Box<dyn Error>> {
88 let status = std::process::Command::new("cargo")
89 .args(["install", "hostcraft-cli"])
90 .status()
91 .map_err(|e| format!("Failed to run cargo: {}", e))?;
92
93 if status.success() {
94 Ok(())
95 } else {
96 Err(format!("cargo install exited with status: {}", status).into())
97 }
98}