From 8c57c060c78838c71b9791933be53029ff2fbbb5 Mon Sep 17 00:00:00 2001 From: Isaac Halvorson Date: Tue, 28 Apr 2026 13:54:31 -0500 Subject: [PATCH 1/4] Make config_root available to environment plugins --- crates/vfox/src/hooks/mise_env.rs | 2 + crates/vfox/src/hooks/mise_path.rs | 2 + crates/vfox/src/vfox.rs | 4 ++ e2e/env/test_env_module_config_root | 75 +++++++++++++++++++++++++++++ mise.lock | 48 ++++++++++++++++-- src/config/env_directive/module.rs | 11 ++--- src/plugins/vfox_plugin.rs | 8 +-- 7 files changed, 138 insertions(+), 12 deletions(-) create mode 100644 e2e/env/test_env_module_config_root diff --git a/crates/vfox/src/hooks/mise_env.rs b/crates/vfox/src/hooks/mise_env.rs index 4fb8b645ae..d9a8fa04ce 100644 --- a/crates/vfox/src/hooks/mise_env.rs +++ b/crates/vfox/src/hooks/mise_env.rs @@ -10,6 +10,7 @@ use crate::hooks::env_keys::EnvKey; pub struct MiseEnvContext { pub args: Vec, pub options: T, + pub config_root: Option, } /// Result from a mise_env hook call @@ -50,6 +51,7 @@ impl IntoLua for MiseEnvContext { fn into_lua(self, lua: &Lua) -> mlua::Result { let table = lua.create_table()?; table.set("options", lua.to_value(&self.options)?)?; + table.set("config_root", self.config_root)?; Ok(Value::Table(table)) } } diff --git a/crates/vfox/src/hooks/mise_path.rs b/crates/vfox/src/hooks/mise_path.rs index 4290501218..b776d8ed62 100644 --- a/crates/vfox/src/hooks/mise_path.rs +++ b/crates/vfox/src/hooks/mise_path.rs @@ -7,6 +7,7 @@ use crate::error::Result; pub struct MisePathContext { pub args: Vec, pub options: T, + pub config_root: Option, } impl Plugin { @@ -30,6 +31,7 @@ impl IntoLua for MisePathContext { fn into_lua(self, lua: &Lua) -> mlua::Result { let table = lua.create_table()?; table.set("options", lua.to_value(&self.options)?)?; + table.set("config_root", self.config_root)?; Ok(Value::Table(table)) } } diff --git a/crates/vfox/src/vfox.rs b/crates/vfox/src/vfox.rs index d806b6db4d..240bfcf77b 100644 --- a/crates/vfox/src/vfox.rs +++ b/crates/vfox/src/vfox.rs @@ -302,6 +302,7 @@ impl Vfox { sdk: &str, opts: T, env: &indexmap::IndexMap, + config_root: Option<&str>, ) -> Result { let plugin = self.get_sdk(sdk)?; if !plugin.get_metadata()?.hooks.contains("mise_env") { @@ -319,6 +320,7 @@ impl Vfox { let ctx = MiseEnvContext { args: vec![], options: opts, + config_root: config_root.map(|s| s.to_string()), }; plugin.mise_env(ctx).await } @@ -381,6 +383,7 @@ impl Vfox { sdk: &str, opts: T, env: &indexmap::IndexMap, + config_root: Option<&str>, ) -> Result> { let plugin = self.get_sdk(sdk)?; if !plugin.get_metadata()?.hooks.contains("mise_path") { @@ -391,6 +394,7 @@ impl Vfox { let ctx = MisePathContext { args: vec![], options: opts, + config_root: config_root.map(|s| s.to_string()), }; plugin.mise_path(ctx).await } diff --git a/e2e/env/test_env_module_config_root b/e2e/env/test_env_module_config_root new file mode 100644 index 0000000000..cf65aebe94 --- /dev/null +++ b/e2e/env/test_env_module_config_root @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +# Test that env module plugins receive ctx.config_root pointing to the +# directory of the mise.toml that defines the module directive. +# config_root resolves to the project root (not the config file's directory), +# matching the behavior of built-in directives like _.file. + +# Create a vfox env plugin that returns config_root as an env var +PLUGIN_DIR="$MISE_DATA_DIR/plugins/test-config-root" +mkdir -p "$PLUGIN_DIR/hooks" + +cat >"$PLUGIN_DIR/metadata.lua" <<'EOFMETA' +PLUGIN = {} +PLUGIN.name = "test-config-root" +PLUGIN.version = "1.0.0" +PLUGIN.homepage = "https://example.com" +PLUGIN.license = "MIT" +PLUGIN.description = "Test plugin for config_root access" +PLUGIN.minRuntimeVersion = "0.3.0" +EOFMETA + +cat >"$PLUGIN_DIR/hooks/mise_env.lua" <<'EOFHOOK' +function PLUGIN:MiseEnv(ctx) + -- ctx.config_root should be the project root for the config file + local config_root = ctx.config_root or "NIL" + local version_file = ctx.options.version_file or "" + + -- Resolve relative path against config_root + local resolved = "" + if config_root ~= "NIL" and version_file ~= "" then + resolved = config_root .. "/" .. version_file + end + + return { + env = { + {key = "TEST_CONFIG_ROOT", value = config_root}, + {key = "TEST_RESOLVED_PATH", value = resolved} + }, + cacheable = false, + watch_files = {} + } +end +EOFHOOK + +# Test 1: config_root is set when using global config +# Global config at $MISE_CONFIG_DIR/config.toml -> config_root is $HOME +cat >"$MISE_CONFIG_DIR/config.toml" <<'EOF' +[env] +_.test-config-root = { version_file = ".xcode-version" } +EOF + +eval "$(mise env -s bash)" +assert "echo $TEST_CONFIG_ROOT" "$HOME" +assert "echo $TEST_RESOLVED_PATH" "$HOME/.xcode-version" + +# Test 2: project-level mise.toml uses its own directory as config_root +PROJECT_DIR="$HOME/myproject" +mkdir -p "$PROJECT_DIR" +cat >"$PROJECT_DIR/mise.toml" <<'EOF' +[env] +_.test-config-root = { version_file = ".config/.xcode-version" } +EOF + +cd "$PROJECT_DIR" +eval "$(mise env -s bash)" +assert "echo $TEST_CONFIG_ROOT" "$PROJECT_DIR" +assert "echo $TEST_RESOLVED_PATH" "$PROJECT_DIR/.config/.xcode-version" + +# Test 3: config_root is stable regardless of cwd +SUBDIR="$PROJECT_DIR/deep/sub" +mkdir -p "$SUBDIR" +cd "$SUBDIR" +eval "$(mise env -s bash)" +# Should still point to the project dir where mise.toml lives +assert "echo $TEST_CONFIG_ROOT" "$PROJECT_DIR" diff --git a/mise.lock b/mise.lock index ea1c7b8185..4c110cc6cb 100644 --- a/mise.lock +++ b/mise.lock @@ -146,6 +146,7 @@ url_api = "https://api.github.com/repos/endevco/aube/releases/assets/404654425" checksum = "sha256:4d77b4f54c78297e6aef6da4ccb8a2327c31297f1a9f37b84fb746928129c4b2" url = "https://github.com/endevco/aube/releases/download/v1.1.0/aube-v1.1.0-aarch64-apple-darwin.tar.gz" url_api = "https://api.github.com/repos/endevco/aube/releases/assets/404655454" +provenance = "github-attestations" [tools.aube."platforms.windows-x64"] checksum = "sha256:41f7d31e35bf7e21d32221ad74fb535bca3fc2fde9da80aee6061cc061f5e0f7" @@ -315,6 +316,7 @@ url_api = "https://api.github.com/repos/jdx/communique/releases/assets/405964691 checksum = "sha256:459993e31a6c4ccbd09882f5679a2bc1ea5d9068701ecefc411a00fb69ce82e6" url = "https://github.com/jdx/communique/releases/download/v1.1.2/communique-aarch64-apple-darwin.tar.gz" url_api = "https://api.github.com/repos/jdx/communique/releases/assets/405964098" +provenance = "github-attestations" [tools.communique."platforms.windows-x64"] checksum = "sha256:3cc0e880ac2168aed3163223627bbd1eee62e07a9901cb85cb507c6c8927bc93" @@ -581,12 +583,52 @@ checksum = "sha256:795670a80d35b23e7ecf932ede742609f2267314f2df64054b020698fcc8a url = "https://github.com/LuaLS/lua-language-server/releases/download/3.17.1/lua-language-server-3.17.1-win32-x64.zip" [[tools.node]] -version = "25.9.0" +version = "24.15.0" backend = "core:node" +[tools.node."platforms.linux-arm64"] +checksum = "sha256:73afc234d558c24919875f51c2d1ea002a2ada4ea6f83601a383869fefa64eed" +url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-linux-arm64.tar.gz" + +[tools.node."platforms.linux-arm64-musl"] +checksum = "sha256:31e98aa960a067da91edffd5d93bc46657b5d2a8029612c359f5f2ac0060152a" +url = "https://unofficial-builds.nodejs.org/download/release/v24.15.0/node-v24.15.0-linux-arm64-musl.tar.gz" + [tools.node."platforms.linux-x64"] -checksum = "sha256:134e55b2408448a219760fe04dc44d6851f9de8a79549021ffd870e9082d9e7b" -url = "https://nodejs.org/dist/v25.9.0/node-v25.9.0-linux-x64.tar.gz" +checksum = "sha256:44836872d9aec49f1e6b52a9a922872db9a2b02d235a616a5681b6a85fec8d89" +url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-linux-x64.tar.gz" + +[tools.node."platforms.linux-x64-baseline"] +checksum = "sha256:44836872d9aec49f1e6b52a9a922872db9a2b02d235a616a5681b6a85fec8d89" +url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-linux-x64.tar.gz" + +[tools.node."platforms.linux-x64-musl"] +checksum = "sha256:f55af5bd489c5347b113ca6594cae00a54b30ba57ac5875324311bfc6f4762e3" +url = "https://unofficial-builds.nodejs.org/download/release/v24.15.0/node-v24.15.0-linux-x64-musl.tar.gz" + +[tools.node."platforms.linux-x64-musl-baseline"] +checksum = "sha256:f55af5bd489c5347b113ca6594cae00a54b30ba57ac5875324311bfc6f4762e3" +url = "https://unofficial-builds.nodejs.org/download/release/v24.15.0/node-v24.15.0-linux-x64-musl.tar.gz" + +[tools.node."platforms.macos-arm64"] +checksum = "sha256:372331b969779ab5d15b949884fc6eaf88d5afe87bde8ba881d6400b9100ffc4" +url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-darwin-arm64.tar.gz" + +[tools.node."platforms.macos-x64"] +checksum = "sha256:ffd5ee293467927f3ee731a553eb88fd1f48cf74eebc2d74a6babe4af228673b" +url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-darwin-x64.tar.gz" + +[tools.node."platforms.macos-x64-baseline"] +checksum = "sha256:ffd5ee293467927f3ee731a553eb88fd1f48cf74eebc2d74a6babe4af228673b" +url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-darwin-x64.tar.gz" + +[tools.node."platforms.windows-x64"] +checksum = "sha256:cc5149eabd53779ce1e7bdc5401643622d0c7e6800ade18928a767e940bb0e62" +url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-win-x64.zip" + +[tools.node."platforms.windows-x64-baseline"] +checksum = "sha256:cc5149eabd53779ce1e7bdc5401643622d0c7e6800ade18928a767e940bb0e62" +url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-win-x64.zip" [[tools."npm:ajv-cli"]] version = "5.0.0" diff --git a/src/config/env_directive/module.rs b/src/config/env_directive/module.rs index 1920de2eba..842132da22 100644 --- a/src/config/env_directive/module.rs +++ b/src/config/env_directive/module.rs @@ -21,12 +21,13 @@ impl EnvResults { redact: Option, env: IndexMap, ) -> Result<()> { + let config_root = crate::config::config_file::config_root::config_root(&source); let path = dirs::PLUGINS.join(name.to_kebab_case()); let plugin = VfoxPlugin::new(name, path.clone()); plugin .ensure_installed(config, &MultiProgressReport::get(), false, false) .await?; - if let Some(response) = plugin.mise_env(value, &env).await? { + if let Some(response) = plugin.mise_env(value, &env, Some(&config_root)).await? { // Track cacheability if !response.cacheable { r.has_uncacheable = true; @@ -37,14 +38,12 @@ impl EnvResults { r.watch_files.push(path); // Add watch files for cache invalidation - // Absolutize relative paths to ensure consistent cache validation - // regardless of which directory mise is run from - let cwd = std::env::current_dir().unwrap_or_default(); + // Absolutize relative paths relative to config_root for consistent cache validation for watch_file in response.watch_files { if watch_file.is_absolute() { r.watch_files.push(watch_file); } else { - r.watch_files.push(cwd.join(watch_file)); + r.watch_files.push(config_root.join(watch_file)); } } @@ -58,7 +57,7 @@ impl EnvResults { r.env.insert(k, (v, source.clone())); } } - if let Some(path) = plugin.mise_path(value, &env).await? { + if let Some(path) = plugin.mise_path(value, &env, Some(&config_root)).await? { for p in path { r.env_paths.push(p.into()); } diff --git a/src/plugins/vfox_plugin.rs b/src/plugins/vfox_plugin.rs index 91da5e77e1..04ef1cd35a 100644 --- a/src/plugins/vfox_plugin.rs +++ b/src/plugins/vfox_plugin.rs @@ -15,7 +15,7 @@ use console::style; use contracts::requires; use eyre::{Context, bail, eyre}; use indexmap::{IndexMap, indexmap}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex, MutexGuard, mpsc}; use url::Url; use vfox::Vfox; @@ -82,9 +82,10 @@ impl VfoxPlugin { &self, opts: &toml::Value, env: &IndexMap, + config_root: Option<&Path>, ) -> Result> { let (vfox, _) = self.vfox(); - let result = vfox.mise_env(&self.name, opts, env).await?; + let result = vfox.mise_env(&self.name, opts, env, config_root.map(|p| p.to_str().unwrap_or_default())).await?; let mut result_env = indexmap!(); for ek in result.env { result_env.insert(ek.key, ek.value); @@ -101,10 +102,11 @@ impl VfoxPlugin { &self, opts: &toml::Value, env: &IndexMap, + config_root: Option<&Path>, ) -> Result>> { let (vfox, _) = self.vfox(); let mut out = vec![]; - let results = vfox.mise_path(&self.name, opts, env).await?; + let results = vfox.mise_path(&self.name, opts, env, config_root.map(|p| p.to_str().unwrap_or_default())).await?; for entry in results { out.push(entry); } From 2ef4cfd8c030a1136da8afa86d065203a1bce7d2 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:04:45 +0000 Subject: [PATCH 2/4] [autofix.ci] apply automated fixes --- mise.lock | 46 +++----------------------------------- src/plugins/vfox_plugin.rs | 18 +++++++++++++-- 2 files changed, 19 insertions(+), 45 deletions(-) diff --git a/mise.lock b/mise.lock index 4c110cc6cb..54592d6179 100644 --- a/mise.lock +++ b/mise.lock @@ -583,52 +583,12 @@ checksum = "sha256:795670a80d35b23e7ecf932ede742609f2267314f2df64054b020698fcc8a url = "https://github.com/LuaLS/lua-language-server/releases/download/3.17.1/lua-language-server-3.17.1-win32-x64.zip" [[tools.node]] -version = "24.15.0" +version = "25.9.0" backend = "core:node" -[tools.node."platforms.linux-arm64"] -checksum = "sha256:73afc234d558c24919875f51c2d1ea002a2ada4ea6f83601a383869fefa64eed" -url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-linux-arm64.tar.gz" - -[tools.node."platforms.linux-arm64-musl"] -checksum = "sha256:31e98aa960a067da91edffd5d93bc46657b5d2a8029612c359f5f2ac0060152a" -url = "https://unofficial-builds.nodejs.org/download/release/v24.15.0/node-v24.15.0-linux-arm64-musl.tar.gz" - [tools.node."platforms.linux-x64"] -checksum = "sha256:44836872d9aec49f1e6b52a9a922872db9a2b02d235a616a5681b6a85fec8d89" -url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-linux-x64.tar.gz" - -[tools.node."platforms.linux-x64-baseline"] -checksum = "sha256:44836872d9aec49f1e6b52a9a922872db9a2b02d235a616a5681b6a85fec8d89" -url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-linux-x64.tar.gz" - -[tools.node."platforms.linux-x64-musl"] -checksum = "sha256:f55af5bd489c5347b113ca6594cae00a54b30ba57ac5875324311bfc6f4762e3" -url = "https://unofficial-builds.nodejs.org/download/release/v24.15.0/node-v24.15.0-linux-x64-musl.tar.gz" - -[tools.node."platforms.linux-x64-musl-baseline"] -checksum = "sha256:f55af5bd489c5347b113ca6594cae00a54b30ba57ac5875324311bfc6f4762e3" -url = "https://unofficial-builds.nodejs.org/download/release/v24.15.0/node-v24.15.0-linux-x64-musl.tar.gz" - -[tools.node."platforms.macos-arm64"] -checksum = "sha256:372331b969779ab5d15b949884fc6eaf88d5afe87bde8ba881d6400b9100ffc4" -url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-darwin-arm64.tar.gz" - -[tools.node."platforms.macos-x64"] -checksum = "sha256:ffd5ee293467927f3ee731a553eb88fd1f48cf74eebc2d74a6babe4af228673b" -url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-darwin-x64.tar.gz" - -[tools.node."platforms.macos-x64-baseline"] -checksum = "sha256:ffd5ee293467927f3ee731a553eb88fd1f48cf74eebc2d74a6babe4af228673b" -url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-darwin-x64.tar.gz" - -[tools.node."platforms.windows-x64"] -checksum = "sha256:cc5149eabd53779ce1e7bdc5401643622d0c7e6800ade18928a767e940bb0e62" -url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-win-x64.zip" - -[tools.node."platforms.windows-x64-baseline"] -checksum = "sha256:cc5149eabd53779ce1e7bdc5401643622d0c7e6800ade18928a767e940bb0e62" -url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-win-x64.zip" +checksum = "sha256:134e55b2408448a219760fe04dc44d6851f9de8a79549021ffd870e9082d9e7b" +url = "https://nodejs.org/dist/v25.9.0/node-v25.9.0-linux-x64.tar.gz" [[tools."npm:ajv-cli"]] version = "5.0.0" diff --git a/src/plugins/vfox_plugin.rs b/src/plugins/vfox_plugin.rs index 04ef1cd35a..3f0a7678c4 100644 --- a/src/plugins/vfox_plugin.rs +++ b/src/plugins/vfox_plugin.rs @@ -85,7 +85,14 @@ impl VfoxPlugin { config_root: Option<&Path>, ) -> Result> { let (vfox, _) = self.vfox(); - let result = vfox.mise_env(&self.name, opts, env, config_root.map(|p| p.to_str().unwrap_or_default())).await?; + let result = vfox + .mise_env( + &self.name, + opts, + env, + config_root.map(|p| p.to_str().unwrap_or_default()), + ) + .await?; let mut result_env = indexmap!(); for ek in result.env { result_env.insert(ek.key, ek.value); @@ -106,7 +113,14 @@ impl VfoxPlugin { ) -> Result>> { let (vfox, _) = self.vfox(); let mut out = vec![]; - let results = vfox.mise_path(&self.name, opts, env, config_root.map(|p| p.to_str().unwrap_or_default())).await?; + let results = vfox + .mise_path( + &self.name, + opts, + env, + config_root.map(|p| p.to_str().unwrap_or_default()), + ) + .await?; for entry in results { out.push(entry); } From 57d5d5b273ac583de6077e4d924914a1b2f143c5 Mon Sep 17 00:00:00 2001 From: Isaac Halvorson Date: Wed, 29 Apr 2026 08:12:34 -0500 Subject: [PATCH 3/4] Suggestions from code review --- src/plugins/vfox_plugin.rs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/plugins/vfox_plugin.rs b/src/plugins/vfox_plugin.rs index 3f0a7678c4..a304348b5f 100644 --- a/src/plugins/vfox_plugin.rs +++ b/src/plugins/vfox_plugin.rs @@ -85,14 +85,7 @@ impl VfoxPlugin { config_root: Option<&Path>, ) -> Result> { let (vfox, _) = self.vfox(); - let result = vfox - .mise_env( - &self.name, - opts, - env, - config_root.map(|p| p.to_str().unwrap_or_default()), - ) - .await?; + let result = vfox.mise_env(&self.name, opts, env, config_root.and_then(|p| p.to_str())).await?; let mut result_env = indexmap!(); for ek in result.env { result_env.insert(ek.key, ek.value); @@ -113,14 +106,7 @@ impl VfoxPlugin { ) -> Result>> { let (vfox, _) = self.vfox(); let mut out = vec![]; - let results = vfox - .mise_path( - &self.name, - opts, - env, - config_root.map(|p| p.to_str().unwrap_or_default()), - ) - .await?; + let results = vfox.mise_path(&self.name, opts, env, config_root.and_then(|p| p.to_str())).await?; for entry in results { out.push(entry); } From f454c9b7aff4143f5208e40f2b23fa34977fcef2 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:22:31 +0000 Subject: [PATCH 4/4] [autofix.ci] apply automated fixes --- src/plugins/vfox_plugin.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/plugins/vfox_plugin.rs b/src/plugins/vfox_plugin.rs index a304348b5f..556e526829 100644 --- a/src/plugins/vfox_plugin.rs +++ b/src/plugins/vfox_plugin.rs @@ -85,7 +85,9 @@ impl VfoxPlugin { config_root: Option<&Path>, ) -> Result> { let (vfox, _) = self.vfox(); - let result = vfox.mise_env(&self.name, opts, env, config_root.and_then(|p| p.to_str())).await?; + let result = vfox + .mise_env(&self.name, opts, env, config_root.and_then(|p| p.to_str())) + .await?; let mut result_env = indexmap!(); for ek in result.env { result_env.insert(ek.key, ek.value); @@ -106,7 +108,9 @@ impl VfoxPlugin { ) -> Result>> { let (vfox, _) = self.vfox(); let mut out = vec![]; - let results = vfox.mise_path(&self.name, opts, env, config_root.and_then(|p| p.to_str())).await?; + let results = vfox + .mise_path(&self.name, opts, env, config_root.and_then(|p| p.to_str())) + .await?; for entry in results { out.push(entry); }