Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions crates/update/src/cli/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ impl ArtifactType {
}
}

pub(super) async fn fetch_latest_version(client: &reqwest::Client) -> anyhow::Result<semver::Version> {
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")
}
Copy link
Collaborator

@bfops bfops Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: instead of adding this function, I think we could instead factor this out of download_and_install instead:

pub(super) async fn fetch_release(
    client: &reqwest::Client,
    version: Option<semver::Version>,
) -> anyhow::Result<Release> {
    let releases_url = releases_url();
    let url = match &version {
        Some(version) => format!("{releases_url}/tags/v{version}"),
        None => [&*releases_url, "/latest"].concat(),
    };
    let release = client
        .get(url)
        .send()
        .await?
        .error_for_status()
        .map_err(|e| {
            if e.status() == Some(reqwest::StatusCode::NOT_FOUND) {
                if let Some(version) = &version {
                    return anyhow::anyhow!(e).context(format!("No release found for version {version}"));
                }
            }
            anyhow::anyhow!(e).context("Could not fetch release info")
        })?
        .json()
        .await?;
    Ok(release)
}


pub(super) async fn available_releases(client: &reqwest::Client) -> anyhow::Result<Vec<String>> {
let url = releases_url();
let releases: Vec<Release> = client.get(url).send().await?.json().await?;
Expand Down
63 changes: 59 additions & 4 deletions crates/update/src/cli/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe a dumb question, but why would a version fail to parse?

let mut parsed: Vec<(semver::Version, String)> = Vec::new();
let mut unparsed: Vec<String> = 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<String> = parsed.into_iter().map(|(_, s)| s).chain(unparsed).collect();
Comment on lines 47 to 59
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says unparseable versions are placed at the end in alphabetical order, but unparsed is never sorted—it's appended in the original iteration order. Either sort unparsed before chaining (e.g., unparsed.sort()), or adjust the comment to match the actual behavior.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the comment


// Check for a newer version available on GitHub.
let latest = if !self.all {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why only in the !self.all case? I would expect this hint to happen regardless of whether we run with --all?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah because it will be self-evident in that case?

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()),
Comment on lines +73 to +77
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This compares the GitHub latest version against the newest installed version, but the PR description says the hint should appear when the current version is older than the newest available. If a user has a newer version installed but not selected as current, show_latest will be None and the upgrade hint won’t show even though current is older than latest. Consider parsing current (when present) to semver::Version and comparing latest > current_version (falling back to installed/newest only when current is missing).

Suggested change
// 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()),
// Determine whether the latest version is newer than the current version
// (if set), falling back to the newest installed version otherwise.
let newest_installed = sorted_versions
.first()
.and_then(|v| semver::Version::parse(v).ok());
let current_version = current
.as_ref()
.and_then(|v| semver::Version::parse(v).ok());
let show_latest = match (&latest, &current_version, &newest_installed) {
// Prefer comparing against the explicitly selected current version.
(Some(lat), Some(cur), _) if lat > cur => Some(lat.to_string()),
// If there is no valid current version, fall back to newest installed.
(Some(lat), None, Some(installed)) if lat > installed => Some(lat.to_string()),

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description is inaccurate in this case. The hint should only show if there is a newer version than any installed version. This is because all installed versions are listed in spacetime version list and therefore discoverable. The main point of showing that there is a new version is that it is otherwise unknown to the user that a new version is available unless they run spacetime upgrade.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if the current version doesn't parse, we should probably the hint to upgrade them to the latest version.

_ => 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`)");
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this does seem weird if the current version isn't the latest

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about instead of trying to show the latest version "inline", we just print a message at the top that says something like "version xyz is available, run spacetime version upgrade to update!" and then print the list as normal?


if is_current {
println!("{} {}", ver, "(current)");
} else {
println!("{ver}");
}
}

Ok(())
}
}
Loading