feat(backend): add version timestamps for spm, conda, and gem backends#7383
feat(backend): add version timestamps for spm, conda, and gem backends#7383
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds version timestamp support for three backends (spm, conda, and gem) to enable time-based version filtering. Each backend is updated to implement _list_remote_versions_with_info instead of _list_remote_versions, returning VersionInfo objects that include version strings and their creation timestamps.
- Leverages existing timestamp fields from GitHub/GitLab release APIs for spm
- Parses
upload_timefrom anaconda.org API for conda packages - Switches from
gem infoCLI to RubyGems HTTP API for gem versions - Removes obsolete gem version parsing logic and associated tests
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/backend/spm.rs | Updates to map GitHub created_at and GitLab released_at fields into VersionInfo objects |
| src/backend/gem.rs | Replaces CLI-based version fetching with RubyGems API calls, adds gem source detection, removes parsing helper and tests |
| src/backend/conda.rs | Extracts upload_time from package files and tracks latest timestamp per version |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| /// Cached gem source URL (global, fetched once) | ||
| static GEM_SOURCE: LazyLock<String> = LazyLock::new(|| { | ||
| let output = cmd!("gem", "sources").read().unwrap_or_default(); |
There was a problem hiding this comment.
The unwrap_or_default() silently swallows errors from gem sources. If the gem command fails or is not available, this will fall through to the default rubygems.org without informing the user. Consider logging a warning when the command fails so users know their custom gem source configuration wasn't respected.
| let output = cmd!("gem", "sources").read().unwrap_or_default(); | |
| let output = match cmd!("gem", "sources").read() { | |
| Ok(output) => output, | |
| Err(err) => { | |
| eprintln!( | |
| "warning: failed to run `gem sources` ({}). Falling back to default gem source https://rubygems.org/.", | |
| err | |
| ); | |
| String::new() | |
| } | |
| }; |
| .and_modify(|existing| { | ||
| // Keep the latest upload time for each version | ||
| if let Some(new_time) = &f.upload_time { | ||
| if existing.is_none() || existing.as_ref().is_some_and(|e| new_time > e) { |
There was a problem hiding this comment.
The logic for determining the latest upload time can be simplified. The condition existing.as_ref().is_some_and(|e| new_time > e) will never be true when existing.is_none() is true, making the first check redundant. Consider simplifying to: if existing.as_ref().map_or(true, |e| new_time > e).
| if existing.is_none() || existing.as_ref().is_some_and(|e| new_time > e) { | |
| if existing.as_ref().map_or(true, |e| new_time > e) { |
74c271a to
4256650
Compare
Implement `_list_remote_versions_with_info` for three backends to provide
version release dates, enabling time-based version filtering:
- **spm**: Use existing `created_at` from GitHub releases and `released_at`
from GitLab releases
- **conda**: Parse `upload_time` field from anaconda.org API response
- **gem**: Use RubyGems API (`/api/v1/versions/{gem}.json`) with `created_at`,
respecting custom gem sources from `gem sources`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
9396b50 to
b5bcda2
Compare
Cache the gem source URL after first successful detection using OnceCell. The first call to get_gem_source() uses the mise-managed Ruby environment to run `gem sources`, then caches the result globally so subsequent calls return instantly. Uses get_or_try_init to only memoize on success - if `gem sources` fails, a warning is shown and rubygems.org is used as fallback without caching (allowing retry on next call). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
b5bcda2 to
0798572
Compare
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.12.11 x -- echo |
19.8 ± 0.4 | 19.1 | 23.6 | 1.00 |
mise x -- echo |
20.2 ± 0.5 | 19.2 | 21.5 | 1.02 ± 0.03 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.12.11 env |
19.6 ± 0.9 | 18.9 | 33.8 | 1.00 |
mise env |
20.2 ± 0.5 | 18.9 | 24.1 | 1.03 ± 0.05 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.12.11 hook-env |
19.4 ± 0.4 | 18.5 | 21.6 | 1.00 ± 0.04 |
mise hook-env |
19.4 ± 0.7 | 18.6 | 29.2 | 1.00 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.12.11 ls |
17.0 ± 0.7 | 16.3 | 27.6 | 1.00 |
mise ls |
17.4 ± 0.4 | 16.6 | 19.6 | 1.02 ± 0.05 |
xtasks/test/perf
| Command | mise-2025.12.11 | mise | Variance |
|---|---|---|---|
| install (cached) | 108ms | 109ms | +0% |
| ls (cached) | 65ms | 65ms | +0% |
| bin-paths (cached) | 71ms | 71ms | +0% |
| task-ls (cached) | 278ms | 278ms | +0% |
The Java backend has separate caches for GA and EA release types via fetch_java_metadata, but the Backend trait's list_remote_versions_with_info used a shared cache that didn't account for release_type. This caused fuzzy version matching to fail when installing EA versions after GA versions were cached. Override list_remote_versions_with_info in the Java backend to bypass the shared cache and use the release-type-aware caching directly. Also adds proxy environment variable passthrough in e2e test runner for environments that require proxy access. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
The conda backend has a 'channel' option that affects which versions are available. Override list_remote_versions_with_info to bypass the shared remote_versions cache, similar to the fix for Java's release_type option. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
Summary
created_atfrom GitHub releases andreleased_atfrom GitLab releasesupload_timefield from anaconda.org API response/api/v1/versions/{gem}.json) withcreated_at, respecting custom gem sources fromgem sourcesThis enables time-based version filtering (e.g.,
mise use tool@latest --before 2024-01-01) for these backends.Test plan
mise ls-remotereturns versions for spm, conda, and gem tools🤖 Generated with Claude Code
Note
Refactors backends to return
VersionInfo(with optional timestamps) and populatescreated_atfor many providers (conda, gem, spm, github/gitlab, npm, etc.), updates caching, and passes proxy envs in e2e._list_remote_versions_with_infowith unified_list_remote_versionsreturningVec<VersionInfo>;list_remote_versions_with_infonow caches and calls unified method.aqua,asdf,cargo,conda,dotnet,github,go,http,npm,pipx,spm,ubi,vfox).conda: group by version and useupload_timeforcreated_at; bypass shared cache due to channel.gem: switch to RubyGems API (/api/v1/versions/*.json) withcreated_at; detect custom source viagem sources.spm: use GitHub/GitLab releases withcreated_at.github/gitlab,npm,pipx,cargo,bun,deno,elixir,erlang,go(core),java(with GA/EA cache bypass),node,python,ruby(incl. Windows),rust,swift,zig: returnVersionInfoand attach release dates where available.e2e/run_test: pass through proxy env vars inside isolated env (http_proxy,https_proxy, etc.).Written by Cursor Bugbot for commit b241375. This will update automatically on new commits. Configure here.