Skip to content
Merged
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: 3 additions & 3 deletions docs/cli/ls-remote.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ Disable checking the mise-versions host
### `--prerelease`

Include pre-release versions in the output for backends that report
an upstream prerelease flag (currently github + aqua). Equivalent to
setting `MISE_PRERELEASES=1` or the `prereleases` setting for the
duration of this command.
upstream prerelease metadata or opt in to regex-based prerelease
detection. Equivalent to setting `MISE_PRERELEASES=1` or the
`prereleases` setting for the duration of this command.

### `--strict-metadata`

Expand Down
6 changes: 3 additions & 3 deletions man/man1/mise.1
Original file line number Diff line number Diff line change
Expand Up @@ -1517,9 +1517,9 @@ Disable checking the mise\-versions host
.TP
\fB\-\-prerelease\fR
Include pre\-release versions in the output for backends that report
an upstream prerelease flag (currently github + aqua). Equivalent to
setting `MISE_PRERELEASES=1` or the `prereleases` setting for the
duration of this command.
upstream prerelease metadata or opt in to regex\-based prerelease
detection. Equivalent to setting `MISE_PRERELEASES=1` or the
`prereleases` setting for the duration of this command.
.TP
\fB\-\-strict\-metadata\fR
Fail if release metadata fetches fail
Expand Down
2 changes: 1 addition & 1 deletion mise.usage.kdl
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ cmd ls-remote help="List runtime versions available for install." {
flag --all help="Show all installed plugins and versions"
flag "-J --json" help="Output in JSON format (includes version metadata like created_at timestamps when available)"
flag --no-versions-host help="Disable checking the mise-versions host"
flag --prerelease help="Include pre-release versions in the output for backends that report\nan upstream prerelease flag (currently github + aqua). Equivalent to\nsetting `MISE_PRERELEASES=1` or the `prereleases` setting for the\nduration of this command."
flag --prerelease help="Include pre-release versions in the output for backends that report\nupstream prerelease metadata or opt in to regex-based prerelease\ndetection. Equivalent to setting `MISE_PRERELEASES=1` or the\n`prereleases` setting for the duration of this command."
flag --strict-metadata help="Fail if release metadata fetches fail" {
long_help "Fail if release metadata fetches fail\n\nRequires --json and --no-versions-host.\n\nThis prevents metadata consumers from accepting empty fallback results\nwhen a backend's metadata-producing upstream request fails."
}
Expand Down
4 changes: 4 additions & 0 deletions src/backend/asdf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,10 @@ impl Backend for AsdfBackend {
Some(PluginType::Asdf)
}

fn mark_prereleases_from_version_pattern(&self) -> bool {
true
}

/// ASDF plugins handle their own downloads through plugin scripts.
/// Lockfile URLs are not applicable since installation is delegated to plugin scripts.
fn supports_lockfile_url(&self) -> bool {
Expand Down
4 changes: 4 additions & 0 deletions src/backend/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ impl Backend for CargoBackend {
false
}

fn mark_prereleases_from_version_pattern(&self) -> bool {
true
}

async fn _list_remote_versions(&self, _config: &Arc<Config>) -> eyre::Result<Vec<VersionInfo>> {
if self.git_url().is_some() {
// TODO: maybe fetch tags/branches from git?
Expand Down
17 changes: 15 additions & 2 deletions src/backend/conda.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::backend::VersionInfo;
use crate::backend::backend_type::BackendType;
use crate::backend::platform_target::PlatformTarget;
use crate::backend::{
VersionInfo, filter_cached_prereleases, include_prereleases, mark_prerelease,
};
use crate::cli::args::BackendArg;
use crate::config::Config;
use crate::config::Settings;
Expand Down Expand Up @@ -662,7 +664,18 @@ impl Backend for CondaBackend {
&self,
config: &Arc<Config>,
) -> Result<Vec<VersionInfo>> {
self._list_remote_versions(config).await
let opts = config
.get_tool_opts(&self.ba)
.await?
.unwrap_or_else(|| self.ba.opts());
let want_prereleases = include_prereleases(&opts);
let versions = self
._list_remote_versions(config)
.await?
.into_iter()
.map(mark_prerelease)
.collect();
Ok(filter_cached_prereleases(versions, want_prereleases))
}

async fn install_version_(
Expand Down
4 changes: 4 additions & 0 deletions src/backend/dotnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ impl Backend for DotnetBackend {
Ok(vec!["dotnet"])
}

fn mark_prereleases_from_version_pattern(&self) -> bool {
true
}

async fn _list_remote_versions(&self, _config: &Arc<Config>) -> eyre::Result<Vec<VersionInfo>> {
let feed_url = self.get_search_url().await?;

Expand Down
4 changes: 4 additions & 0 deletions src/backend/gem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ impl Backend for GemBackend {
Ok(vec!["ruby"])
}

fn mark_prereleases_from_version_pattern(&self) -> bool {
true
}

async fn _list_remote_versions(&self, config: &Arc<Config>) -> eyre::Result<Vec<VersionInfo>> {
// Get the gem source URL using the mise-managed Ruby environment
let source_url = self.get_gem_source(config).await;
Expand Down
4 changes: 4 additions & 0 deletions src/backend/go.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ impl Backend for GoBackend {
false
}

fn mark_prereleases_from_version_pattern(&self) -> bool {
true
}

async fn _list_remote_versions(&self, config: &Arc<Config>) -> eyre::Result<Vec<VersionInfo>> {
// Check if go is available
self.warn_if_dependency_missing(
Expand Down
4 changes: 4 additions & 0 deletions src/backend/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,10 @@ impl Backend for HttpBackend {
&self.ba
}

fn mark_prereleases_from_version_pattern(&self) -> bool {
true
}

async fn install_operation_count(&self, tv: &ToolVersion, _ctx: &InstallContext) -> usize {
let opts = tv.request.options();
super::http_install_operation_count(
Expand Down
62 changes: 49 additions & 13 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,10 @@ pub struct VersionInfo {
/// Checksum of the release asset, used to detect changes in rolling releases
#[serde(skip_serializing_if = "Option::is_none", default)]
pub checksum: Option<String>,
/// Whether the upstream flagged this as a pre-release. Backends that have a
/// reliable signal (e.g. GitHub releases' `prerelease: true`) populate this
/// so the shared remote-versions cache can store the superset and apply the
/// `prerelease` tool option as a read-time filter.
/// Whether this is a pre-release. Backends with a reliable upstream signal
/// (e.g. GitHub releases' `prerelease: true`) populate this directly.
/// Metadata-free listing backends can opt in to stamping this from mise's
/// legacy pre-release pattern before caching.
#[serde(default, skip_serializing_if = "is_false")]
pub prerelease: bool,
}
Expand Down Expand Up @@ -479,10 +479,8 @@ mod tests {

#[test]
fn test_filter_cached_prereleases_leaves_unflagged_backends_alone() {
// Backends that don't populate `VersionInfo.prerelease` (e.g. node,
// ruby, aqua's `github_tag` source) keep the legacy regex-based filter
// in `fuzzy_match_versions` for pre-release-shaped strings. The
// cache-layer filter must not strip those entries on its own.
// The cache-layer filter only trusts the metadata bit. Regex-shaped
// versions are stamped before they enter the cache.
let cached = vec![
VersionInfo {
version: "1.0.0".into(),
Expand All @@ -498,6 +496,28 @@ mod tests {
assert_eq!(versions, vec!["1.0.0", "1.1.0-rc1"]);
}

#[test]
fn test_mark_prerelease_flags_regex_matches() {
let stable = mark_prerelease(VersionInfo {
version: "1.0.0".into(),
..Default::default()
});
assert!(!stable.prerelease);

let rc = mark_prerelease(VersionInfo {
version: "1.1.0-rc1".into(),
..Default::default()
});
assert!(rc.prerelease);

let already_flagged = mark_prerelease(VersionInfo {
version: "2.0.0".into(),
prerelease: true,
..Default::default()
});
assert!(already_flagged.prerelease);
}

#[test]
fn test_include_prereleases_accepts_bool_and_string_values() {
use crate::toolset::ToolVersionOptions;
Expand Down Expand Up @@ -623,6 +643,12 @@ pub trait Backend: Debug + Send + Sync {
fn get_dependencies(&self) -> Result<Vec<&str>> {
Ok(vec![])
}

/// Whether this backend's version source lacks an upstream prerelease flag
/// and should mark regex-shaped versions as prereleases before caching.
fn mark_prereleases_from_version_pattern(&self) -> bool {
false
}
/// dependencies which wait for install but do not warn, like cargo-binstall
fn get_optional_dependencies(&self) -> Result<Vec<&str>> {
Ok(vec![])
Expand Down Expand Up @@ -816,6 +842,10 @@ pub trait Backend: Debug + Send + Sync {
._list_remote_versions(config)
.await?
.into_iter()
.map(|v| match self.mark_prereleases_from_version_pattern() {
true => mark_prerelease(v),
false => v,
})
.filter(|v| match v.version.parse::<ToolVersionType>() {
Ok(ToolVersionType::Version(_)) => true,
_ => {
Expand Down Expand Up @@ -2328,11 +2358,10 @@ fn find_match_in_list(list: &[String], query: &str) -> Option<String> {
}

/// Apply the read-time `prerelease` filter to the cached remote-versions
/// superset. Backends that honor `prerelease` (github, aqua) cache the full
/// list and stamp `VersionInfo.prerelease` per entry; this helper drops
/// pre-release entries when the current tool opts don't opt in. Backends that
/// don't populate the flag are unaffected because their entries default to
/// `prerelease = false`.
/// superset. Backends cache the full list and stamp `VersionInfo.prerelease`
/// either from upstream metadata or, for metadata-free listing backends, mise's
/// legacy pre-release pattern. This helper drops pre-release entries when the
/// current tool opts don't opt in.
pub(crate) fn filter_cached_prereleases(
versions: Vec<VersionInfo>,
want_prereleases: bool,
Expand All @@ -2344,6 +2373,13 @@ pub(crate) fn filter_cached_prereleases(
}
}

pub(crate) fn mark_prerelease(mut version: VersionInfo) -> VersionInfo {
if !version.prerelease && VERSION_REGEX.is_match(&version.version) {
version.prerelease = true;
}
version
}
Comment on lines +2376 to +2381
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This helper can be slightly optimized to avoid unnecessary regex matching for versions that have already been flagged as pre-releases by the upstream backend (e.g., via GitHub API metadata).

Suggested change
pub(crate) fn mark_prerelease(mut version: VersionInfo) -> VersionInfo {
if VERSION_REGEX.is_match(&version.version) {
version.prerelease = true;
}
version
}
pub(crate) fn mark_prerelease(mut version: VersionInfo) -> VersionInfo {
if !version.prerelease && VERSION_REGEX.is_match(&version.version) {
version.prerelease = true;
}
version
}


/// Whether pre-release versions should be included for the current tool.
///
/// Returns true if either the global `prereleases` setting (`MISE_PRERELEASES=1`,
Expand Down
4 changes: 4 additions & 0 deletions src/backend/npm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ impl Backend for NPMBackend {
&self.ba
}

fn mark_prereleases_from_version_pattern(&self) -> bool {
true
}

fn get_dependencies(&self) -> eyre::Result<Vec<&str>> {
// npm CLI is always needed for version queries (npm view), plus the configured
// package manager for installation. We avoid listing all package managers to
Expand Down
4 changes: 4 additions & 0 deletions src/backend/pipx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ impl Backend for PIPXBackend {
Ok(vec!["uv"])
}

fn mark_prereleases_from_version_pattern(&self) -> bool {
true
}

/// Pipx installs packages from PyPI or Git using version specs (e.g., black==24.3.0).
/// It doesn't support installing from direct URLs, so lockfile URLs are not applicable.
fn supports_lockfile_url(&self) -> bool {
Expand Down
4 changes: 4 additions & 0 deletions src/backend/s3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,10 @@ impl Backend for S3Backend {
&self.ba
}

fn mark_prereleases_from_version_pattern(&self) -> bool {
true
}

async fn install_operation_count(&self, tv: &ToolVersion, _ctx: &InstallContext) -> usize {
let opts = tv.request.options();
super::http_install_operation_count(
Expand Down
4 changes: 4 additions & 0 deletions src/backend/spm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ impl Backend for SPMBackend {
Ok(vec!["swift"])
}

fn mark_prereleases_from_version_pattern(&self) -> bool {
true
}

async fn _list_remote_versions(&self, _config: &Arc<Config>) -> eyre::Result<Vec<VersionInfo>> {
let provider = GitProvider::from_ba(&self.ba);
let repo = SwiftPackageRepo::new(&self.tool_name(), &provider)?;
Expand Down
4 changes: 4 additions & 0 deletions src/backend/ubi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ impl Backend for UbiBackend {
&self.ba
}

fn mark_prereleases_from_version_pattern(&self) -> bool {
true
}

async fn _list_remote_versions(&self, _config: &Arc<Config>) -> eyre::Result<Vec<VersionInfo>> {
deprecated_at!(
"2026.4.0",
Expand Down
4 changes: 4 additions & 0 deletions src/backend/vfox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ impl Backend for VfoxBackend {
Ok(deps.iter().map(|s| s.as_str()).collect())
}

fn mark_prereleases_from_version_pattern(&self) -> bool {
true
}

fn supports_lockfile_url(&self) -> bool {
// TODO: expose a plugin hook (e.g. BackendLockInfo) so custom Lua backends
// can surface a download URL + checksum, and flip this back on for them.
Expand Down
11 changes: 5 additions & 6 deletions src/cli/ls_remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ struct VersionOutputAll {
version: String,
#[serde(skip_serializing_if = "Option::is_none")]
created_at: Option<String>,
/// Upstream pre-release flag, sourced from the versions host or the
/// backend (currently github + aqua report this). Always emitted so
/// JSON consumers can rely on its presence.
/// Pre-release flag, sourced from upstream metadata or backend opt-in
/// detection. Always emitted so JSON consumers can rely on its presence.
prerelease: bool,
}

Expand Down Expand Up @@ -52,9 +51,9 @@ pub struct LsRemote {
pub no_versions_host: bool,

/// Include pre-release versions in the output for backends that report
/// an upstream prerelease flag (currently github + aqua). Equivalent to
/// setting `MISE_PRERELEASES=1` or the `prereleases` setting for the
/// duration of this command.
/// upstream prerelease metadata or opt in to regex-based prerelease
/// detection. Equivalent to setting `MISE_PRERELEASES=1` or the
/// `prereleases` setting for the duration of this command.
#[clap(long, verbatim_doc_comment)]
pub prerelease: bool,

Expand Down
1 change: 1 addition & 0 deletions src/plugins/core/java.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ impl Backend for JavaPlugin {
.map(|(v, m)| VersionInfo {
version: v.clone(),
created_at: m.created_at.clone(),
prerelease: VERSION_REGEX.is_match(v),
..Default::default()
})
.unique_by(|v| v.version.clone())
Expand Down
11 changes: 5 additions & 6 deletions src/versions_host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,11 @@ struct VersionEntry {
created_at: toml::value::Datetime,
#[serde(default)]
release_url: Option<String>,
/// Upstream pre-release flag, when the producing source can distinguish
/// it (currently github + aqua releases). Defaults to false so old
/// host data — and entries from sources that don't track prereleases —
/// stay correct without any schema upgrade. Old mise clients that don't
/// know about this field ignore it (toml-rs accepts unknown fields by
/// default), so populating it in mise-versions is forward-compatible.
/// Pre-release flag, when the producing source can distinguish it. Defaults
/// to false so old host data — and entries from sources that don't track
/// prereleases — stay correct without any schema upgrade. Old mise clients
/// that don't know about this field ignore it (toml-rs accepts unknown
/// fields by default), so populating it in mise-versions is forward-compatible.
#[serde(default)]
prerelease: bool,
}
Expand Down
2 changes: 1 addition & 1 deletion xtasks/fig/src/mise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1757,7 +1757,7 @@ const completionSpec: Fig.Spec = {
{
name: "--prerelease",
description:
"Include pre-release versions in the output for backends that report\nan upstream prerelease flag (currently github + aqua). Equivalent to\nsetting `MISE_PRERELEASES=1` or the `prereleases` setting for the\nduration of this command.",
"Include pre-release versions in the output for backends that report\nupstream prerelease metadata or opt in to regex-based prerelease\ndetection. Equivalent to setting `MISE_PRERELEASES=1` or the\n`prereleases` setting for the duration of this command.",
isRepeatable: false,
},
{
Expand Down
Loading