diff --git a/crates/update/src/cli/install.rs b/crates/update/src/cli/install.rs index bc04ab2b3ef..dafa4a23b60 100644 --- a/crates/update/src/cli/install.rs +++ b/crates/update/src/cli/install.rs @@ -152,6 +152,12 @@ impl ArtifactType { } } +pub(super) async fn fetch_latest_version(client: &reqwest::Client) -> anyhow::Result { + let url = format!("{}/latest", releases_url()); + let release: Release = client.get(url).send().await?.error_for_status()?.json().await?; + release.version().context("Could not parse latest version number") +} + pub(super) async fn available_releases(client: &reqwest::Client) -> anyhow::Result> { let url = releases_url(); let releases: Vec = client.get(url).send().await?.json().await?; diff --git a/crates/update/src/cli/list.rs b/crates/update/src/cli/list.rs index 25b10458e2a..9c569d30b5d 100644 --- a/crates/update/src/cli/list.rs +++ b/crates/update/src/cli/list.rs @@ -36,19 +36,74 @@ impl List { Some(file_name.to_string()) } }; + let versions = if self.all { let client = super::reqwest_client()?; super::tokio_block_on(super::install::available_releases(&client))?? } else { paths.cli_bin_dir.installed_versions()? }; + + // Sort versions using semver ordering. Versions that fail to parse are + // placed at the end in their original listed. + let mut parsed: Vec<(semver::Version, String)> = Vec::new(); + let mut unparsed: Vec = Vec::new(); for ver in versions { - print!("{ver}"); - if Some(&ver) == current.as_ref() { - print!(" (current)"); + match semver::Version::parse(&ver) { + Ok(sv) => parsed.push((sv, ver)), + Err(_) => unparsed.push(ver), } - println!(); } + parsed.sort_by(|(a, _), (b, _)| b.cmp(a)); + + let sorted_versions: Vec = parsed.into_iter().map(|(_, s)| s).chain(unparsed).collect(); + + // Check for a newer version available on GitHub. + let latest = if !self.all { + let client = super::reqwest_client().ok(); + client.and_then(|c| { + super::tokio_block_on(super::install::fetch_latest_version(&c)) + .ok() + .and_then(|r| r.ok()) + }) + } else { + None + }; + + // Determine whether the latest version is newer than any installed version. + let newest_installed = sorted_versions.first().and_then(|v| semver::Version::parse(v).ok()); + + let show_latest = match (&latest, &newest_installed) { + (Some(lat), Some(cur)) if lat > cur => Some(lat.to_string()), + _ => None, + }; + + // If there's no current version set but a latest is available, + // still show it first. + if current.is_none() { + if let Some(ref new_ver) = show_latest { + println!("{} {}", new_ver, "(available - run `spacetime version upgrade`)"); + } + } + + for ver in &sorted_versions { + let is_current = Some(ver) == current.as_ref(); + + // If this is the current version and there's a newer version + // available, print the newer version first. + if is_current { + if let Some(ref new_ver) = show_latest { + println!("{} {}", new_ver, "(available - run `spacetime version upgrade`)"); + } + } + + if is_current { + println!("{} {}", ver, "(current)"); + } else { + println!("{ver}"); + } + } + Ok(()) } }