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
11 changes: 11 additions & 0 deletions docs/tips-and-tricks.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,17 @@ minimum_release_age = "1d" # trivy updates are time-sensitive, use a shorter wi

Precedence: `--minimum-release-age` CLI flag > per-tool `minimum_release_age` > global `minimum_release_age` setting.

Use `minimum_release_age_excludes` to exclude tools or backends from the global setting:

```toml
[settings]
minimum_release_age = "7d"
minimum_release_age_excludes = ["trivy", "npm:*"]
```

Exclusions can match backend wildcards like `npm:*`, tool shorthands like `trivy`, or full backend IDs like `npm:prettier`.
Per-tool `minimum_release_age` options and the CLI flag still apply even when a tool matches the exclusion list.

See [`minimum_release_age`](/configuration/settings.html#minimum_release_age) for more details.

## [`mise up --bump`](/cli/upgrade.html)
Expand Down
15 changes: 15 additions & 0 deletions e2e/cli/test_install_before
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,21 @@ EOF
mise install
assert_contains "mise ls --installed jq" "1.6"

# Test: minimum_release_age_excludes skips the global setting for matching tools
mise uninstall jq --all 2>/dev/null || true
rm -f mise.toml
cat <<EOF >mise.toml
[settings]
minimum_release_age = "2019-01-01"
minimum_release_age_excludes = ["jq"]

[tools]
jq = "latest"
EOF
mise install
assert_not_empty "mise ls --installed jq"
assert_not_contains "mise ls --installed jq" "1.6"

# Test: CLI --minimum-release-age flag overrides per-tool minimum_release_age
# per-tool=2024-01-01 would give jq 1.7.1, CLI --minimum-release-age=2019-01-01 gives jq 1.6
Comment thread
greptile-apps[bot] marked this conversation as resolved.
mise uninstall jq --all 2>/dev/null || true
Expand Down
8 changes: 8 additions & 0 deletions schema/mise.json
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,14 @@
"description": "Minimum release age / supply chain protection — only install versions older than this threshold",
"type": "string"
},
"minimum_release_age_excludes": {
"default": [],
"description": "Tools and backends to exclude from the global minimum_release_age setting",
"type": "array",
"items": {
"type": "string"
}
},
"netrc": {
"default": true,
"description": "Use a netrc file for HTTP Basic authentication.",
Expand Down
28 changes: 28 additions & 0 deletions settings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1402,6 +1402,34 @@ env = "MISE_MINIMUM_RELEASE_AGE"
optional = true
type = "String"

[minimum_release_age_excludes]
default = []
description = "Tools and backends to exclude from the global minimum_release_age setting"
docs = """
Exclude tools or backends from the global [`minimum_release_age`](#minimum_release_age) setting.
This is useful for tools where newly published releases are time-sensitive.

Exclusions match any of:

- Backend wildcard: `npm:*`, `pipx:*`, `github:*`
- Registry tool shorthand: `trivy`, `node`
- Full backend ID: `npm:prettier`, `aqua:aquasecurity/trivy`

Example:

```toml
[settings]
minimum_release_age = "7d"
minimum_release_age_excludes = ["trivy", "npm:*"]
```

This only excludes tools from the global setting. A per-tool `minimum_release_age` option
or the `--minimum-release-age` CLI flag still applies to matching tools.
"""
env = "MISE_MINIMUM_RELEASE_AGE_EXCLUDES"
parse_env = "list_by_comma"
type = "ListString"

[netrc]
default = true
description = "Use a netrc file for HTTP Basic authentication."
Expand Down
4 changes: 2 additions & 2 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::duration::parse_into_timestamp;
use crate::file::{
canonicalize_cached, display_path, remove_all_with_progress, remove_all_with_warning,
};
use crate::install_before::resolve_before_date;
use crate::install_before::resolve_before_date_for_tool;
use crate::install_context::InstallContext;
use crate::lockfile::{PlatformInfo, ProvenanceType};
use crate::path_env::PathEnv;
Expand Down Expand Up @@ -2444,7 +2444,7 @@ async fn effective_latest_before_date<B: Backend + ?Sized>(
}

let opts = config.get_tool_opts_with_overrides(backend.ba()).await?;
resolve_before_date(None, opts.minimum_release_age())
resolve_before_date_for_tool(backend.ba(), None, opts.minimum_release_age())
}

fn latest_stable_candidate_allowed_by_before_date(
Expand Down
124 changes: 122 additions & 2 deletions src/install_before.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use eyre::Result;
use jiff::Timestamp;

use crate::backend::Backend;
use crate::cli::args::BackendArg;
use crate::config::{Config, Settings};
use crate::duration::parse_into_timestamp;

Expand All @@ -23,19 +24,59 @@ use crate::duration::parse_into_timestamp;
pub fn resolve_before_date(
before_date: Option<Timestamp>,
minimum_release_age: Option<&str>,
) -> Result<Option<Timestamp>> {
resolve_before_date_with_excludes(before_date, minimum_release_age, false)
}

pub fn resolve_before_date_for_tool(
backend_arg: &BackendArg,
before_date: Option<Timestamp>,
minimum_release_age: Option<&str>,
) -> Result<Option<Timestamp>> {
resolve_before_date_with_excludes(
before_date,
minimum_release_age,
is_minimum_release_age_excluded(backend_arg),
)
}

fn resolve_before_date_with_excludes(
before_date: Option<Timestamp>,
minimum_release_age: Option<&str>,
excluded: bool,
) -> Result<Option<Timestamp>> {
if let Some(before_date) = before_date {
return Ok(Some(before_date));
}
if let Some(before) = minimum_release_age {
return Ok(Some(parse_into_timestamp(before)?));
}
if excluded {
return Ok(None);
}
if let Some(before) = &Settings::get().minimum_release_age {
return Ok(Some(parse_into_timestamp(before)?));
}
Ok(None)
}

fn is_minimum_release_age_excluded(backend_arg: &BackendArg) -> bool {
let excludes = &Settings::get().minimum_release_age_excludes;
if excludes.is_empty() {
return false;
}

let full = backend_arg.full_without_opts();
let backend_type = backend_arg.backend_type().to_string();
let backend_wildcard = format!("{backend_type}:*");

excludes.iter().any(|exclude| {
let exclude = exclude.trim();
!exclude.is_empty()
&& (exclude == backend_arg.short || exclude == full || exclude == backend_wildcard)
})
Comment thread
greptile-apps[bot] marked this conversation as resolved.
}

pub(crate) async fn resolve_before_date_for_backend<B: Backend + ?Sized>(
config: &Arc<Config>,
backend: &B,
Expand All @@ -46,12 +87,13 @@ pub(crate) async fn resolve_before_date_for_backend<B: Backend + ?Sized>(
}

let opts = config.get_tool_opts_with_overrides(backend.ba()).await?;
resolve_before_date(None, opts.minimum_release_age())
resolve_before_date_for_tool(backend.ba(), None, opts.minimum_release_age())
}

#[cfg(test)]
mod tests {
use super::resolve_before_date;
use super::{resolve_before_date, resolve_before_date_for_tool};
use crate::cli::args::BackendArg;
use crate::config::settings::{Settings, SettingsPartial};
use confique::Layer;
use jiff::Timestamp;
Expand All @@ -64,6 +106,15 @@ mod tests {
resolve_before_date(before_date, minimum_release_age).unwrap()
}

fn resolved_tool_timestamp(
tool: &str,
before_date: Option<Timestamp>,
minimum_release_age: Option<&str>,
) -> Option<Timestamp> {
let backend_arg: BackendArg = tool.into();
resolve_before_date_for_tool(&backend_arg, before_date, minimum_release_age).unwrap()
}

#[test]
fn test_effective_before_date_prefers_override() {
Settings::reset(None);
Expand Down Expand Up @@ -97,6 +148,75 @@ mod tests {
Settings::reset(None);
}

#[test]
fn test_effective_before_date_excludes_global_by_tool() {
let mut partial = SettingsPartial::empty();
partial.minimum_release_age = Some("2024-01-03".to_string());
partial.minimum_release_age_excludes = Some(vec!["node".to_string()]);
Settings::reset(Some(partial));
assert_eq!(resolved_tool_timestamp("node", None, None), None);
Settings::reset(None);
}

#[test]
fn test_effective_before_date_does_not_exclude_backend_by_bare_name() {
let mut partial = SettingsPartial::empty();
partial.minimum_release_age = Some("2024-01-03".to_string());
partial.minimum_release_age_excludes = Some(vec!["npm".to_string()]);
Settings::reset(Some(partial));
assert_eq!(
resolved_tool_timestamp("npm:prettier", None, None),
Some(crate::duration::parse_into_timestamp("2024-01-03").unwrap())
);
Settings::reset(None);
}

#[test]
fn test_effective_before_date_excludes_global_by_backend_wildcard() {
let mut partial = SettingsPartial::empty();
partial.minimum_release_age = Some("2024-01-03".to_string());
partial.minimum_release_age_excludes = Some(vec!["npm:*".to_string()]);
Settings::reset(Some(partial));
assert_eq!(resolved_tool_timestamp("npm:prettier", None, None), None);
Settings::reset(None);
}

#[test]
fn test_effective_before_date_excludes_global_by_full_backend_id() {
let mut partial = SettingsPartial::empty();
partial.minimum_release_age = Some("2024-01-03".to_string());
partial.minimum_release_age_excludes = Some(vec!["npm:prettier".to_string()]);
Settings::reset(Some(partial));
assert_eq!(resolved_tool_timestamp("npm:prettier", None, None), None);
Settings::reset(None);
}

#[test]
fn test_effective_before_date_does_not_exclude_by_bare_backend_tool_name() {
let mut partial = SettingsPartial::empty();
partial.minimum_release_age = Some("2024-01-03".to_string());
partial.minimum_release_age_excludes = Some(vec!["prettier".to_string()]);
Settings::reset(Some(partial));
assert_eq!(
resolved_tool_timestamp("npm:prettier", None, None),
Some(crate::duration::parse_into_timestamp("2024-01-03").unwrap())
);
Settings::reset(None);
}

#[test]
fn test_effective_before_date_exclude_does_not_override_tool_option() {
let mut partial = SettingsPartial::empty();
partial.minimum_release_age = Some("2024-01-03".to_string());
partial.minimum_release_age_excludes = Some(vec!["npm".to_string()]);
Settings::reset(Some(partial));
assert_eq!(
resolved_tool_timestamp("npm:prettier", None, Some("2024-01-02")),
Some(crate::duration::parse_into_timestamp("2024-01-02").unwrap())
);
Settings::reset(None);
}

#[test]
fn test_effective_before_date_none_when_unset() {
Settings::reset(None);
Expand Down
8 changes: 6 additions & 2 deletions src/toolset/tool_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::cli::args::BackendArg;
use crate::config::config_file::config_root;
use crate::dirs;
use crate::env;
use crate::install_before::resolve_before_date;
use crate::install_before::resolve_before_date_for_tool;
use crate::lockfile::LockfileTool;
use crate::path::PathExt;
use crate::runtime_symlinks::is_runtime_symlink;
Expand Down Expand Up @@ -376,7 +376,11 @@ impl ToolRequest {
pub fn resolve_options(&self, opts: &ResolveOptions) -> Result<ResolveOptions> {
let minimum_release_age = self.options().minimum_release_age().map(str::to_string);
let mut opts = opts.clone();
opts.before_date = resolve_before_date(opts.before_date, minimum_release_age.as_deref())?;
opts.before_date = resolve_before_date_for_tool(
self.ba(),
opts.before_date,
minimum_release_age.as_deref(),
)?;
Ok(opts)
}

Expand Down
8 changes: 6 additions & 2 deletions src/toolset/tool_version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::env;
#[cfg(windows)]
use crate::file;
use crate::hash::hash_to_str;
use crate::install_before::resolve_before_date;
use crate::install_before::resolve_before_date_for_tool;
use crate::lockfile::{CondaPackageInfo, LockfileTool, PlatformInfo};
use crate::runtime_symlinks::is_runtime_symlink;
use crate::toolset::{ToolRequest, ToolSource, ToolVersionOptions, tool_request};
Expand Down Expand Up @@ -78,7 +78,11 @@ impl ToolVersion {
) -> Result<Self> {
let minimum_release_age = request.options().minimum_release_age().map(str::to_string);
let mut opts = opts.clone();
opts.before_date = resolve_before_date(opts.before_date, minimum_release_age.as_deref())?;
opts.before_date = resolve_before_date_for_tool(
request.ba(),
opts.before_date,
minimum_release_age.as_deref(),
)?;

trace!("resolving {} {}", &request, opts);
if opts.use_locked_version
Expand Down
Loading