diff --git a/docs/cli/index.md b/docs/cli/index.md index 3531e108cc..612d99c445 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -63,6 +63,18 @@ Do not load any config files Can also use `MISE_NO_CONFIG=1` +### `--no-env` + +Do not load environment variables from config files + +Can also use `MISE_NO_ENV=1` + +### `--no-hooks` + +Do not execute hooks from config files + +Can also use `MISE_NO_HOOKS=1` + ### `--output ` ## Subcommands diff --git a/e2e/cli/test_no_env b/e2e/cli/test_no_env new file mode 100755 index 0000000000..22df3cac56 --- /dev/null +++ b/e2e/cli/test_no_env @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# Test that --no-env flag prevents loading environment variables from config files + +# Create a config file with environment variables +cat <mise.toml +[env] +TEST_FOO_VAR = "FOO" +EOF + +# Test if it works with env +assert_contains "mise env" "TEST_FOO_VAR" +assert_not_contains "mise --no-env env" "TEST_FOO_VAR" + +# Test with exec as well +assert_contains "mise exec -- env | grep TEST_FOO_VAR" "TEST_FOO_VAR=FOO" +assert_not_contains "mise --no-env exec -- env" "TEST_FOO_VAR" + +# Test that MISE_NO_ENV=1 environment variable works the same way +assert_not_contains "MISE_NO_ENV=1 mise env" "TEST_FOO_VAR" +assert_not_contains "MISE_NO_ENV=1 mise exec -- env" "TEST_FOO_VAR" + +cat <mise.toml +[settings] +no_env = true + +[env] +TEST_FOO_VAR = "FOO" +EOF + +assert_not_contains "mise env" "TEST_FOO_VAR" + +# Cleanup +rm -f mise.toml diff --git a/e2e/cli/test_no_hooks b/e2e/cli/test_no_hooks new file mode 100755 index 0000000000..8d7965022b --- /dev/null +++ b/e2e/cli/test_no_hooks @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +cat <mise.toml +[settings] +experimental = true +[tools] +dummy = 'latest' +[hooks] +preinstall = 'echo PREINSTALL' +EOF + +assert_contains "mise i dummy 2>&1" "PREINSTALL" +assert_not_contains "mise --no-hooks i dummy 2>&1" "PREINSTALL" + +assert_not_contains "MISE_NO_HOOKS=1 mise --no-hooks i dummy 2>&1" "PREINSTALL" + +cat <mise.toml +[settings] +experimental = true +no_hooks = true +[tools] +dummy = 'latest' +[hooks] +preinstall = 'echo PREINSTALL' +EOF + +assert_not_contains "mise i dummy 2>&1" "PREINSTALL" + +rm -f mise.toml diff --git a/man/man1/mise.1 b/man/man1/mise.1 index a2673cebd9..361fb99872 100644 --- a/man/man1/mise.1 +++ b/man/man1/mise.1 @@ -60,6 +60,16 @@ Do not load any config files Can also use `MISE_NO_CONFIG=1` .TP +\fB\-\-no\-env\fR +Do not load environment variables from config files + +Can also use `MISE_NO_ENV=1` +.TP +\fB\-\-no\-hooks\fR +Do not execute hooks from config files + +Can also use `MISE_NO_HOOKS=1` +.TP \fB\-\-no\-timings\fR Hides elapsed time after each task completes diff --git a/mise.usage.kdl b/mise.usage.kdl index 1ef0d625cb..745fa24689 100644 --- a/mise.usage.kdl +++ b/mise.usage.kdl @@ -41,6 +41,12 @@ flag --log-level hide=#true global=#true { flag --no-config help="Do not load any config files" { long_help "Do not load any config files\n\nCan also use `MISE_NO_CONFIG=1`" } +flag --no-env help="Do not load environment variables from config files" { + long_help "Do not load environment variables from config files\n\nCan also use `MISE_NO_ENV=1`" +} +flag --no-hooks help="Do not execute hooks from config files" { + long_help "Do not execute hooks from config files\n\nCan also use `MISE_NO_HOOKS=1`" +} flag --no-timings help="Hides elapsed time after each task completes" hide=#true { long_help "Hides elapsed time after each task completes\n\nDefault to always hide with `MISE_TASK_TIMINGS=0`" } diff --git a/schema/mise.json b/schema/mise.json index 974ce25a87..83e7405ea9 100644 --- a/schema/mise.json +++ b/schema/mise.json @@ -814,6 +814,14 @@ "description": "Path to the netrc file to use for HTTP Basic authentication.", "type": "string" }, + "no_env": { + "description": "Do not load environment variables from config files.", + "type": "boolean" + }, + "no_hooks": { + "description": "Do not execute hooks from config files.", + "type": "boolean" + }, "node": { "type": "object", "additionalProperties": false, diff --git a/settings.toml b/settings.toml index 6f5f9b3200..e08b12ed4c 100644 --- a/settings.toml +++ b/settings.toml @@ -861,6 +861,18 @@ env = "MISE_NETRC_FILE" optional = true type = "Path" +[no_env] +description = "Do not load environment variables from config files." +env = "MISE_NO_ENV" +optional = true +type = "Bool" + +[no_hooks] +description = "Do not execute hooks from config files." +env = "MISE_NO_HOOKS" +optional = true +type = "Bool" + [node.compile] description = "Compile node from source." env = "MISE_NODE_COMPILE" @@ -1752,7 +1764,7 @@ type = "Bool" default = false description = "Opt out of parsing task run scripts to infer the usage spec (arguments and flags). When enabled, mise will derive the usage spec only from the `usage` field, ignoring any `arg()`, `option()`, or `flag()` templates used in run scripts. This can restore previous behavior and avoid the extra template pass over run scripts when collecting specs." docs = """ -When enabled, `arg()`, `option()`, and `flag()` Tera functions in run scripts will not contribute +When enabled, `arg()`, `option()`, and `flag()` Tera functions in run scripts will not contribute to the task's usage spec—only the explicit `usage` field is used. This is useful for: diff --git a/src/cli/mod.rs b/src/cli/mod.rs index a5d722b482..bd6abd1397 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -158,6 +158,16 @@ pub struct Cli { /// Can also use `MISE_NO_CONFIG=1` #[clap(long)] pub no_config: bool, + /// Do not load environment variables from config files + /// + /// Can also use `MISE_NO_ENV=1` + #[clap(long)] + pub no_env: bool, + /// Do not execute hooks from config files + /// + /// Can also use `MISE_NO_HOOKS=1` + #[clap(long)] + pub no_hooks: bool, /// Hides elapsed time after each task completes /// /// Default to always hide with `MISE_TASK_TIMINGS=0` diff --git a/src/config/mod.rs b/src/config/mod.rs index 1bb6257a4e..78d0f04a67 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -556,6 +556,9 @@ impl Config { } async fn load_env(self: &Arc) -> Result { + if Settings::no_env() || Settings::get().no_env.unwrap_or(false) { + return Ok(EnvResults::default()); + } time!("load_env start"); let entries = self .config_files diff --git a/src/config/settings.rs b/src/config/settings.rs index 887dbe5354..29d29bb1a9 100644 --- a/src/config/settings.rs +++ b/src/config/settings.rs @@ -502,6 +502,28 @@ impl Settings { .take_while(|a| *a != "--") .any(|a| a == "--no-config") } + + pub fn no_env() -> bool { + *env::MISE_NO_ENV + || !*crate::env::IS_RUNNING_AS_SHIM + && env::ARGS + .read() + .unwrap() + .iter() + .take_while(|a| *a != "--") + .any(|a| a == "--no-env") + } + + pub fn no_hooks() -> bool { + *env::MISE_NO_HOOKS + || !*crate::env::IS_RUNNING_AS_SHIM + && env::ARGS + .read() + .unwrap() + .iter() + .take_while(|a| *a != "--") + .any(|a| a == "--no-hooks") + } } impl Display for Settings { diff --git a/src/env.rs b/src/env.rs index 62298c140c..43ef0694a4 100644 --- a/src/env.rs +++ b/src/env.rs @@ -92,6 +92,8 @@ pub static MISE_FRIENDLY_ERROR: Lazy = Lazy::new(|| { pub static MISE_TOOL_STUB: Lazy = Lazy::new(|| ARGS.read().unwrap().get(1).map(|s| s.as_str()) == Some("tool-stub")); pub static MISE_NO_CONFIG: Lazy = Lazy::new(|| var_is_true("MISE_NO_CONFIG")); +pub static MISE_NO_ENV: Lazy = Lazy::new(|| var_is_true("MISE_NO_ENV")); +pub static MISE_NO_HOOKS: Lazy = Lazy::new(|| var_is_true("MISE_NO_HOOKS")); pub static MISE_PROGRESS_TRACE: Lazy = Lazy::new(|| var_is_true("MISE_PROGRESS_TRACE")); pub static MISE_CACHE_DIR: Lazy = Lazy::new(|| var_path("MISE_CACHE_DIR").unwrap_or_else(|| XDG_CACHE_HOME.join("mise"))); diff --git a/src/hooks.rs b/src/hooks.rs index 22b25caf0f..c501330c7e 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -65,6 +65,9 @@ pub fn schedule_hook(hook: Hooks) { } pub async fn run_all_hooks(config: &Arc, ts: &Toolset, shell: &dyn Shell) { + if Settings::no_hooks() || Settings::get().no_hooks.unwrap_or(false) { + return; + } let hooks = { let mut mu = SCHEDULED_HOOKS.lock().unwrap(); mu.drain(..).collect::>() @@ -113,6 +116,9 @@ pub async fn run_one_hook_with_context( shell: Option<&dyn Shell>, installed_tools: Option<&[InstalledToolInfo]>, ) { + if Settings::no_hooks() || Settings::get().no_hooks.unwrap_or(false) { + return; + } for (root, h) in all_hooks(config).await { if hook != h.hook || (h.shell.is_some() && h.shell != shell.map(|s| s.to_string())) { continue; diff --git a/src/toolset/toolset_env.rs b/src/toolset/toolset_env.rs index c1907fb441..936d04e4ea 100644 --- a/src/toolset/toolset_env.rs +++ b/src/toolset/toolset_env.rs @@ -3,8 +3,8 @@ use std::sync::Arc; use eyre::Result; -use crate::config::Config; use crate::config::env_directive::{EnvResolveOptions, EnvResults, ToolsFilter}; +use crate::config::{Config, Settings}; use crate::env::{PATH_KEY, WARN_ON_MISSING_REQUIRED_ENV}; use crate::env_diff::EnvMap; use crate::path_env::PathEnv; @@ -134,6 +134,9 @@ impl Toolset { ctx: tera::Context, env: &EnvMap, ) -> Result { + if Settings::no_env() || Settings::get().no_env.unwrap_or(false) { + return Ok(EnvResults::default()); + } let entries = config .config_files .iter() diff --git a/xtasks/fig/src/mise.ts b/xtasks/fig/src/mise.ts index 8a3d66e6f4..2dcdd486be 100644 --- a/xtasks/fig/src/mise.ts +++ b/xtasks/fig/src/mise.ts @@ -3844,6 +3844,16 @@ const completionSpec: Fig.Spec = { description: "Do not load any config files", isRepeatable: false, }, + { + name: "--no-env", + description: "Do not load environment variables from config files", + isRepeatable: false, + }, + { + name: "--no-hooks", + description: "Do not execute hooks from config files", + isRepeatable: false, + }, { name: "--output", isRepeatable: false,