use crate::{ui, util};
use anyhow::{anyhow, Result};
use serde_json::Value;
use tokio::task;
const CRATE_NAME: &str = "rlyx";
pub async fn run(owner_repo: &str) -> Result<()> {
ui::section("self-update");
let cur = env!("CARGO_PKG_VERSION").to_string();
let is_cur_prerelease = cur.contains('-');
let gh_ok = util::run_status("gh", &["--version"]);
let releases = if gh_ok {
fetch_releases(owner_repo).unwrap_or_default()
} else {
vec![]
};
let latest_stable =
releases.iter().find(|r| !r.prerelease && !r.draft);
let latest_pre =
releases.iter().find(|r| r.prerelease && !r.draft);
match (latest_stable, latest_pre) {
(None, _) if !gh_ok => {
ui::warn("gh not found. installing latest stable from crates.io");
install_from_crates(None).await?;
ui::ok("updated to latest stable");
Ok(())
}
(None, _) => Err(anyhow!("no releases found")),
(Some(stable), pre) => {
let stable_ver = trim_v(&stable.tag);
if is_cur_prerelease {
if let Some(pre) = pre {
let pre_ver = trim_v(&pre.tag);
ui::summary_block(&[
("Current", &cur),
("Stable", stable_ver),
("Latest pre", pre_ver),
]);
if ui::confirm(&format!(
"install latest prerelease {}?",
pre_ver
)) {
install_from_crates(Some(pre_ver)).await?;
ui::ok(&format!("updated to {}", pre_ver));
return Ok(());
}
}
ui::summary_block(&[
("Current", &cur),
("Stable", stable_ver),
]);
if ui::confirm(&format!(
"install stable {}?",
stable_ver
)) {
install_from_crates(Some(stable_ver)).await?;
ui::ok(&format!("updated to {}", stable_ver));
return Ok(());
}
ui::warn("no changes made");
Ok(())
} else if normalize(&cur) == normalize(stable_ver) {
ui::ok("already up to date");
Ok(())
} else {
ui::summary_block(&[
("Current", &cur),
("Stable", stable_ver),
]);
if ui::confirm(&format!(
"install stable {}?",
stable_ver
)) {
install_from_crates(Some(stable_ver)).await?;
ui::ok(&format!("updated to {}", stable_ver));
return Ok(());
}
ui::warn("no changes made");
Ok(())
}
}
}
}
struct Rel {
tag: String,
prerelease: bool,
draft: bool,
}
fn fetch_releases(owner_repo: &str) -> Result<Vec<Rel>> {
let path = format!("repos/{}/releases?per_page=50", owner_repo);
let out = crate::util::run_capture("gh", &["api", &path])?;
let v: Value = serde_json::from_str(&out)?;
let arr =
v.as_array().ok_or_else(|| anyhow!("bad releases json"))?;
let mut outv = Vec::with_capacity(arr.len());
for r in arr {
let tag = r
.get("tag_name")
.and_then(|x| x.as_str())
.unwrap_or("")
.to_string();
if tag.is_empty() {
continue;
}
let prerelease = r
.get("prerelease")
.and_then(|x| x.as_bool())
.unwrap_or(false);
let draft =
r.get("draft").and_then(|x| x.as_bool()).unwrap_or(false);
outv.push(Rel {
tag,
prerelease,
draft,
});
}
Ok(outv)
}
fn trim_v(tag: &str) -> &str {
tag.strip_prefix('v').unwrap_or(tag)
}
fn normalize(v: &str) -> String {
v.split('-').next().unwrap_or(v).to_string()
}
async fn install_from_crates(ver_opt: Option<&str>) -> Result<()> {
let mut args: Vec<String> = vec![
"install".into(),
"--force".into(),
"--locked".into(),
CRATE_NAME.into(),
];
if let Some(v) = ver_opt {
args.push("--version".into());
args.push(v.to_string());
}
let pb = crate::ui::make_spinner(
&crate::ui::mp(),
"[self-update]",
"installing from crates.io",
);
task::spawn_blocking(move || {
let arg_refs: Vec<&str> =
args.iter().map(|s| s.as_str()).collect();
util::run_quiet("cargo", &arg_refs, "cargo install")
})
.await??;
pb.finish_with_message(crate::ui::done_style("done"));
Ok(())
}