use std::sync::mpsc::Receiver;
use crate::term::WARN;
const DISABLE_ENV: &str = "DAY_NO_UPDATE_CHECK";
#[derive(serde::Deserialize)]
struct CratesResp {
#[serde(rename = "crate")]
krate: CrateInfo,
}
#[derive(serde::Deserialize)]
struct CrateInfo {
max_stable_version: Option<String>,
}
pub fn spawn(enabled: bool) -> Option<Receiver<String>> {
let opted_out = std::env::var_os(DISABLE_ENV).is_some_and(|v| !v.is_empty());
if cfg!(debug_assertions) || !enabled || opted_out {
return None;
}
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
if let Some(latest) = fetch_latest() {
let _ = tx.send(latest);
}
});
Some(rx)
}
pub fn finish(rx: Option<Receiver<String>>) {
let Some(rx) = rx else { return };
let Ok(latest) = rx.try_recv() else { return };
let current = env!("CARGO_PKG_VERSION");
if is_newer(&latest, current) {
eprintln!(
"{WARN}A new release of day-cli is available: {current} → {latest}. \
Update with `cargo install day-cli`.{WARN:#}"
);
}
}
fn fetch_latest() -> Option<String> {
let ua = format!(
"day-cli/{} ({}; {}; +{})",
env!("CARGO_PKG_VERSION"),
std::env::consts::OS,
std::env::consts::ARCH,
env!("CARGO_PKG_REPOSITORY"),
);
let body = ureq::get("https://crates.io/api/v1/crates/day-cli")
.header("User-Agent", &ua)
.config()
.timeout_global(Some(std::time::Duration::from_secs(5)))
.build()
.call()
.ok()?
.into_body()
.read_to_string()
.ok()?;
serde_json::from_str::<CratesResp>(&body)
.ok()?
.krate
.max_stable_version
}
fn is_newer(latest: &str, current: &str) -> bool {
match (parse(latest), parse(current)) {
(Some(l), Some(c)) => l > c,
_ => false,
}
}
fn parse(v: &str) -> Option<(u64, u64, u64)> {
let core = v.split(['-', '+']).next().unwrap_or(v);
let mut parts = core.split('.');
let major = parts.next()?.trim().parse().ok()?;
let minor = parts.next()?.trim().parse().ok()?;
let patch = parts.next()?.trim().parse().ok()?;
Some((major, minor, patch))
}
#[cfg(test)]
mod tests {
use super::{is_newer, parse};
#[test]
fn parses_semver_core() {
assert_eq!(parse("0.0.5"), Some((0, 0, 5)));
assert_eq!(parse("1.2.3-alpha.1"), Some((1, 2, 3)));
assert_eq!(parse("2.0.0+build.7"), Some((2, 0, 0)));
assert_eq!(parse("nonsense"), None);
assert_eq!(parse("1.2"), None);
}
#[test]
fn newer_only_when_strictly_greater() {
assert!(is_newer("0.1.0", "0.0.9"));
assert!(is_newer("1.0.0", "0.9.9"));
assert!(is_newer("0.0.6", "0.0.5"));
assert!(!is_newer("0.0.5", "0.0.5")); assert!(!is_newer("0.0.4", "0.0.5")); assert!(!is_newer("garbage", "0.0.5")); }
}