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
10 changes: 10 additions & 0 deletions e2e/cli/test_hook_env
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,13 @@ assert_contains "dummy" "1.0.0"
export MISE_DUMMY_VERSION=1.1.0
pushd .. && popd
assert_contains "dummy" "1.1.0"

ln -s "$ROOT/test/data/plugins/dummy" "$MISE_DATA_DIR/plugins/hyphen-tool"
mise install hyphen-tool@1.0.0
mise shell hyphen-tool@1.0.0
assert "echo $MISE_HYPHEN_TOOL_VERSION" "1.0.0"
pushd .. && popd
assert_contains "mise current hyphen-tool" "1.0.0"

mise shell --unset hyphen-tool
assert "echo ${MISE_HYPHEN_TOOL_VERSION+set}" ""
2 changes: 1 addition & 1 deletion src/backend/asdf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ impl AsdfBackend {
.with_env("MISE_DOWNLOAD_PATH", download)
.with_env("MISE_INSTALL_PATH", install)
.with_env("MISE_INSTALL_TYPE", install_type)
.with_env("MISE_INSTALL_VERSION", install_version);
.with_env(env::MISE_INSTALL_VERSION_ENV_VAR, install_version);
Ok(sm)
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2382,7 +2382,7 @@ pub trait Backend: Debug + Send + Sync {
.env(&*env::PATH_KEY, path_env.join())
.env("MISE_TOOL_INSTALL_PATH", tv.install_path())
.env("MISE_TOOL_NAME", tv.ba().short.clone())
.env("MISE_TOOL_VERSION", tv.version.clone())
.env(env::MISE_TOOL_VERSION_ENV_VAR, tv.version.clone())
.with_pr(ctx.pr.as_ref())
.cmd_body_args(shell_args, &rendered_script)
.envs(env_vars);
Expand Down
10 changes: 3 additions & 7 deletions src/cli/shell.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use color_eyre::eyre::{Result, bail, eyre};
use console::style;
use heck::ToShoutySnakeCase;
use indoc::formatdoc;

use crate::cli::args::ToolArg;
use crate::config::Config;
use crate::env;
use crate::shell::get_shell;
use crate::toolset::{InstallOptions, ToolSource, ToolsetBuilder};
use crate::toolset::{InstallOptions, ToolSource, ToolsetBuilder, tool_env_var_name};

/// Sets a tool version for the current session.
///
Expand Down Expand Up @@ -48,10 +47,7 @@ impl Shell {

if self.unset {
for ta in &self.tool {
let op = shell.unset_env(&format!(
"MISE_{}_VERSION",
ta.ba.short.to_shouty_snake_case()
));
let op = shell.unset_env(&tool_env_var_name(&ta.ba.short));
print!("{op}");
}
return Ok(());
Expand Down Expand Up @@ -82,7 +78,7 @@ impl Shell {
for (p, tv) in ts.list_current_installed_versions(&config) {
let source = &ts.versions.get(p.ba().as_ref()).unwrap().source;
if matches!(source, ToolSource::Argument) {
let k = format!("MISE_{}_VERSION", p.id().to_shouty_snake_case());
let k = tool_env_var_name(p.id());
let op = shell.set_env(&k, &tv.version);
print!("{op}");
}
Expand Down
4 changes: 4 additions & 0 deletions src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ use std::{path::PathBuf, sync::atomic::AtomicBool};

pub static ARGS: RwLock<Vec<String>> = RwLock::new(vec![]);
pub static TOOL_ARGS: RwLock<Vec<ToolArg>> = RwLock::new(vec![]);
pub const MISE_INSTALL_VERSION_ENV_VAR: &str = "MISE_INSTALL_VERSION";
pub const MISE_TOOL_VERSION_ENV_VAR: &str = "MISE_TOOL_VERSION";
pub const NON_TOOL_VERSION_ENV_VARS: &[&str] =
&[MISE_INSTALL_VERSION_ENV_VAR, MISE_TOOL_VERSION_ENV_VAR];
#[cfg(unix)]
pub static SHELL: Lazy<String> = Lazy::new(|| var("SHELL").unwrap_or_else(|_| "sh".into()));
#[cfg(windows)]
Expand Down
12 changes: 2 additions & 10 deletions src/toolset/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::cli::args::{BackendArg, ToolArg};
use crate::config::{Config, ConfigMap};
use crate::env_diff::EnvMap;
use crate::errors::Error;
use crate::toolset::{ResolveOptions, ToolRequest, ToolSource, Toolset};
use crate::toolset::{ResolveOptions, ToolRequest, ToolSource, Toolset, tool_from_env_var_name};
use crate::{config, env};

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -111,15 +111,7 @@ impl ToolsetBuilder {
return Ok(());
}
for (k, v) in env {
if k.starts_with("MISE_") && k.ends_with("_VERSION") && k != "MISE_VERSION" {
let tool_name = k
.trim_start_matches("MISE_")
.trim_end_matches("_VERSION")
.to_lowercase();
if tool_name == "install" || tool_name == "tool" {
// ignore MISE_INSTALL_VERSION and MISE_TOOL_VERSION (set during hooks)
continue;
}
if let Some(tool_name) = tool_from_env_var_name(&k) {
let ba: Arc<BackendArg> = Arc::new(tool_name.as_str().into());
let source = ToolSource::Environment(k, v.clone());
let mut env_ts = Toolset::new(source.clone());
Expand Down
4 changes: 3 additions & 1 deletion src/toolset/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ use tokio::sync::OnceCell;

pub use install_options::InstallOptions;
pub use tool_request::ToolRequest;
pub use tool_request_set::{ToolRequestSet, ToolRequestSetBuilder, tool_env_vars};
pub use tool_request_set::{
ToolRequestSet, ToolRequestSetBuilder, tool_env_var_name, tool_env_vars, tool_from_env_var_name,
};
pub use tool_source::ToolSource;
pub use tool_version::{ResolveOptions, ToolVersion};
pub use tool_version_list::ToolVersionList;
Expand Down
59 changes: 42 additions & 17 deletions src/toolset/tool_request_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::config::{Config, ConfigMap, Settings};
use crate::env;
use crate::registry::{REGISTRY, tool_enabled};
use crate::toolset::{ToolRequest, ToolSource, Toolset};
use heck::{ToKebabCase, ToShoutySnakeCase};
use indexmap::IndexMap;
use itertools::Itertools;

Expand Down Expand Up @@ -300,23 +301,33 @@ fn merge(mut a: ToolRequestSet, mut b: ToolRequestSet) -> ToolRequestSet {
b
}

/// Returns the environment variable used to select a tool version for a shell session.
pub fn tool_env_var_name(tool: &str) -> String {
format!("MISE_{}_VERSION", tool.to_shouty_snake_case())
}

/// Returns the tool selected by a shell-session environment variable.
///
/// Shell environment variable names cannot preserve the distinction between `-` and `_`.
/// Since mise tool names conventionally use kebab-case, both spellings decode to `-`.
pub fn tool_from_env_var_name(name: &str) -> Option<String> {
if env::NON_TOOL_VERSION_ENV_VARS.contains(&name) {
return None;
}
let raw = name.strip_prefix("MISE_")?.strip_suffix("_VERSION")?;
if raw.is_empty() {
return None;
}
Some(crate::backend::unalias_backend(&raw.to_kebab_case()).to_string())
}

/// Yields `(short, key, value)` for each `MISE_<TOOL>_VERSION` env var that
/// maps to a tool. `short` is the unaliased backend short name (so
/// `MISE_NODEJS_VERSION` yields `"node"`). Skips `MISE_VERSION` and the
/// `MISE_INSTALL_VERSION` / `MISE_TOOL_VERSION` vars set during hooks.
pub fn tool_env_vars() -> impl Iterator<Item = (String, String, String)> {
env::vars_safe().filter_map(|(k, v)| {
if !k.starts_with("MISE_") || !k.ends_with("_VERSION") || k == "MISE_VERSION" {
return None;
}
let raw = k
.trim_start_matches("MISE_")
.trim_end_matches("_VERSION")
.to_lowercase();
if raw == "install" || raw == "tool" {
return None;
}
let short = crate::backend::unalias_backend(&raw).to_string();
let short = tool_from_env_var_name(&k)?;
Some((short, k, v))
})
}
Expand All @@ -326,6 +337,20 @@ mod tests {
use super::*;
use crate::toolset::{CoreToolOptions, ToolVersionOptions};

#[test]
fn test_tool_env_var_name_round_trip() {
assert_eq!(tool_env_var_name("ls-lint"), "MISE_LS_LINT_VERSION");
assert_eq!(
tool_from_env_var_name("MISE_LS_LINT_VERSION").as_deref(),
Some("ls-lint")
);
assert_eq!(tool_env_var_name("ls-lint"), tool_env_var_name("ls_lint"));
assert_eq!(
tool_from_env_var_name("MISE_NODEJS_VERSION").as_deref(),
Some("node")
);
}

#[test]
fn test_load_runtime_env_with_valid_utf8() {
// This test verifies that valid UTF-8 MISE_*_VERSION variables work correctly
Expand Down Expand Up @@ -384,19 +409,19 @@ mod tests {
fn test_tool_env_vars_skips_non_tool_vars() {
unsafe {
std::env::set_var("MISE_VERSION", "2026.4.28");
std::env::set_var("MISE_INSTALL_VERSION", "1.0.0");
std::env::set_var("MISE_TOOL_VERSION", "1.0.0");
std::env::set_var(env::MISE_INSTALL_VERSION_ENV_VAR, "1.0.0");
std::env::set_var(env::MISE_TOOL_VERSION_ENV_VAR, "1.0.0");
}

let keys: HashSet<String> = tool_env_vars().map(|(_, k, _)| k).collect();
assert!(!keys.contains("MISE_VERSION"));
assert!(!keys.contains("MISE_INSTALL_VERSION"));
assert!(!keys.contains("MISE_TOOL_VERSION"));
assert!(!keys.contains(env::MISE_INSTALL_VERSION_ENV_VAR));
assert!(!keys.contains(env::MISE_TOOL_VERSION_ENV_VAR));

unsafe {
std::env::remove_var("MISE_VERSION");
std::env::remove_var("MISE_INSTALL_VERSION");
std::env::remove_var("MISE_TOOL_VERSION");
std::env::remove_var(env::MISE_INSTALL_VERSION_ENV_VAR);
std::env::remove_var(env::MISE_TOOL_VERSION_ENV_VAR);
}
}

Expand Down