From 0670a3f29056de9e5e837f5f5976d9c4589cfbe7 Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Wed, 11 Dec 2024 07:09:18 -0600 Subject: [PATCH 1/2] fix: improve hook-env * check config filetime modification in millis so scripts work better * only warn if files are untrusted --- e2e/cli/test_activate_aggressive | 14 ++++++++++++++ src/cli/activate.rs | 20 ++++++++++++++++++-- src/cli/hook_env.rs | 21 ++++++++------------- src/config/config_file/mod.rs | 2 +- src/config/mod.rs | 22 +++++++++++++++------- src/env.rs | 8 +++++++- src/hook_env.rs | 8 ++++---- src/shell/bash.rs | 28 +++++++++++++++++++++------- src/shell/elvish.rs | 19 +++++++++++++------ src/shell/fish.rs | 20 ++++++++++++++++---- src/shell/mod.rs | 10 ++++++++-- src/shell/nushell.rs | 15 +++++++++++---- src/shell/xonsh.rs | 15 +++++++++++---- src/shell/zsh.rs | 20 ++++++++++++++++---- 14 files changed, 163 insertions(+), 59 deletions(-) create mode 100644 e2e/cli/test_activate_aggressive diff --git a/e2e/cli/test_activate_aggressive b/e2e/cli/test_activate_aggressive new file mode 100644 index 0000000000..ce257410d1 --- /dev/null +++ b/e2e/cli/test_activate_aggressive @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +ORIG_PATH="$PATH" +eval "$(mise activate bash)" +export PATH="/added:$PATH" + +assert "mise hook-env -s bash --trace" "" # checking early exit functions +mkdir -p "$MISE_DATA_DIR/installs/dummy/1.0.0/bin" +echo "#!/usr/bin/env bash" >"$MISE_DATA_DIR/installs/dummy/1.0.0/bin/dummy" +chmod +x "$MISE_DATA_DIR/installs/dummy/1.0.0/bin/dummy" +echo "tools.dummy = '1'" >mise.toml +assert_contains "mise hook-env -s bash --trace" "export PATH='/added:$MISE_DATA_DIR/installs/dummy/1.0.0/bin:" +export MISE_ACTIVATE_AGGRESSIVE=1 +assert_contains "mise hook-env -s bash --trace" "export PATH='$MISE_DATA_DIR/installs/dummy/1.0.0/bin:/added:" diff --git a/src/cli/activate.rs b/src/cli/activate.rs index 839150ce4f..a52bda345c 100644 --- a/src/cli/activate.rs +++ b/src/cli/activate.rs @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf}; use crate::env::PATH_KEY; use crate::file::touch_dir; use crate::path_env::PathEnv; -use crate::shell::{get_shell, Shell, ShellType}; +use crate::shell::{get_shell, ActivateOptions, Shell, ShellType}; use crate::{dirs, env}; use eyre::Result; use itertools::Itertools; @@ -49,6 +49,15 @@ pub struct Activate { /// Suppress non-error messages #[clap(long, short)] quiet: bool, + + /// Do not automatically call hook-env + /// + /// This can be helpful for debugging mise. If you run `eval "$(mise activate --no-hook-env)"`, then + /// you can call `mise hook-env` manually which will output the env vars to stdout without actually + /// modifying the environment. That way you can do things like `mise hook-env --trace` to get more + /// information or just see the values that hook-env is outputting. + #[clap(long)] + no_hook_env: bool, } impl Activate { @@ -91,7 +100,14 @@ impl Activate { flags.push(" --status"); } miseprint!("{}", self.prepend_path(shell, exe_dir))?; - miseprint!("{}", shell.activate(mise_bin, flags.join("")))?; + miseprint!( + "{}", + shell.activate(ActivateOptions { + exe: mise_bin.to_path_buf(), + flags: flags.join(""), + no_hook_env: self.no_hook_env, + }) + )?; Ok(()) } diff --git a/src/cli/hook_env.rs b/src/cli/hook_env.rs index b80754210e..41b3937736 100644 --- a/src/cli/hook_env.rs +++ b/src/cli/hook_env.rs @@ -1,7 +1,7 @@ +use crate::env::{join_paths, split_paths, PATH_ENV_SEP}; use console::truncate_str; use eyre::Result; use itertools::Itertools; -use std::env::{join_paths, split_paths}; use std::ops::Deref; use std::path::PathBuf; @@ -138,18 +138,13 @@ impl HookEnv { ) -> Result> { let full = join_paths(&*env::PATH)?.to_string_lossy().to_string(); let (pre, post) = match &*env::__MISE_ORIG_PATH { - Some(orig_path) => { - match full.split_once(&format!( - "{}{orig_path}", - if cfg!(windows) { ';' } else { ':' } - )) { - Some((pre, post)) if !settings.activate_aggressive => ( - split_paths(pre).collect_vec(), - split_paths(&format!("{orig_path}{post}")).collect_vec(), - ), - _ => (vec![], split_paths(&full).collect_vec()), - } - } + Some(orig_path) => match full.split_once(&format!("{PATH_ENV_SEP}{orig_path}")) { + Some((pre, post)) if !settings.activate_aggressive => ( + split_paths(pre).collect_vec(), + split_paths(&format!("{orig_path}{post}")).collect_vec(), + ), + _ => (vec![], split_paths(&full).collect_vec()), + }, None => (vec![], split_paths(&full).collect_vec()), }; diff --git a/src/config/config_file/mod.rs b/src/config/config_file/mod.rs index a18a627624..d5d3ab6e28 100644 --- a/src/config/config_file/mod.rs +++ b/src/config/config_file/mod.rs @@ -215,7 +215,7 @@ pub fn parse_or_init(path: &Path) -> eyre::Result> { Ok(cf) } -pub fn parse(path: &Path) -> eyre::Result> { +pub fn parse(path: &Path) -> Result> { if let Ok(settings) = Settings::try_get() { if settings.paranoid { trust_check(path)?; diff --git a/src/config/mod.rs b/src/config/mod.rs index 2293f8bc2f..7fefe4ca60 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -900,19 +900,27 @@ fn load_all_config_files( .collect_vec() .into_par_iter() .map(|f| { - let cf = parse_config_file(f, idiomatic_filenames).wrap_err_with(|| { - format!( - "error parsing config file: {}", - style::ebold(display_path(f)) - ) - })?; + let cf = match parse_config_file(f, idiomatic_filenames) { + Ok(cfg) => cfg, + Err(err) => { + if err.to_string().contains("are not trusted.") { + warn!("{err}"); + return Ok(None); + } + return Err(err.wrap_err(format!( + "error parsing config file: {}", + style::ebold(display_path(f)) + ))); + } + }; if let Err(err) = Tracker::track(f) { warn!("tracking config: {err:#}"); } - Ok((f.clone(), cf)) + Ok(Some((f.clone(), cf))) }) .collect::>>()? .into_iter() + .flatten() .collect()) } diff --git a/src/env.rs b/src/env.rs index 13ffcbf6af..b7a934cedb 100644 --- a/src/env.rs +++ b/src/env.rs @@ -164,7 +164,8 @@ pub static __MISE_DIR: Lazy> = Lazy::new(|| { pub static __MISE_ORIG_PATH: Lazy> = Lazy::new(|| var("__MISE_ORIG_PATH").ok()); pub static __MISE_WATCH: Lazy> = Lazy::new(|| match var("__MISE_WATCH") { Ok(raw) => deserialize_watches(raw) - .map_err(|e| warn!("Failed to deserialize __MISE_WATCH {e}")) + // TODO: enable this later when the bigint change goes out + .map_err(|e| debug!("Failed to deserialize __MISE_WATCH {e}")) .ok(), _ => None, }); @@ -294,6 +295,11 @@ pub static NVM_DIR: Lazy = pub static NODENV_ROOT: Lazy = Lazy::new(|| var_path("NODENV_ROOT").unwrap_or_else(|| HOME.join(".nodenv"))); +#[cfg(unix)] +pub const PATH_ENV_SEP: char = ':'; +#[cfg(windows)] +pub const PATH_ENV_SEP: char = ';'; + fn get_env_diff() -> EnvDiff { let env = vars().collect::>(); match env.get("__MISE_DIFF") { diff --git a/src/hook_env.rs b/src/hook_env.rs index ae42c17f01..64039c0abe 100644 --- a/src/hook_env.rs +++ b/src/hook_env.rs @@ -91,7 +91,7 @@ fn have_files_been_modified(watches: &HookEnvWatches, watch_files: BTreeSet watches.latest_update { trace!("file modified: {:?}", fp); modified = true; @@ -100,7 +100,7 @@ fn have_files_been_modified(watches: &HookEnvWatches, watch_files: BTreeSet bool { #[derive(Debug, Default, Serialize, Deserialize)] pub struct HookEnvWatches { - latest_update: u64, + latest_update: u128, env_var_hash: String, } @@ -148,7 +148,7 @@ pub fn build_watches( latest_update: max_modtime .duration_since(SystemTime::UNIX_EPOCH) .unwrap() - .as_secs(), + .as_millis(), }) } diff --git a/src/shell/bash.rs b/src/shell/bash.rs index 96aa74658d..3fb93b68a2 100644 --- a/src/shell/bash.rs +++ b/src/shell/bash.rs @@ -1,16 +1,17 @@ use std::fmt::Display; -use std::path::Path; use indoc::formatdoc; use crate::config::Settings; -use crate::shell::Shell; +use crate::shell::{ActivateOptions, Shell}; #[derive(Default)] pub struct Bash {} impl Shell for Bash { - fn activate(&self, exe: &Path, flags: String) -> String { + fn activate(&self, opts: ActivateOptions) -> String { + let exe = opts.exe; + let flags = opts.flags; let settings = Settings::get(); let exe = exe.to_string_lossy(); let mut out = formatdoc! {r#" @@ -43,14 +44,21 @@ impl Shell for Bash { eval "$(mise hook-env{flags} -s bash)"; return $previous_exit_status; }}; + "#}; + if !opts.no_hook_env { + out.push_str(&formatdoc! {r#" if [[ ";${{PROMPT_COMMAND:-}};" != *";_mise_hook;"* ]]; then PROMPT_COMMAND="_mise_hook${{PROMPT_COMMAND:+;$PROMPT_COMMAND}}" fi - {} - {} + {chpwd_functions} + {chpwd_load} chpwd_functions+=(_mise_hook) _mise_hook - "#, include_str!("../assets/bash_zsh_support/chpwd/function.sh"), include_str!("../assets/bash_zsh_support/chpwd/load.sh")}; + "#, + chpwd_functions = include_str!("../assets/bash_zsh_support/chpwd/function.sh"), + chpwd_load = include_str!("../assets/bash_zsh_support/chpwd/load.sh") + }); + } if settings.not_found_auto_install { out.push_str(&formatdoc! {r#" if [ -z "${{_mise_cmd_not_found:-}}" ]; then @@ -114,6 +122,7 @@ impl Display for Bash { #[cfg(test)] mod tests { use insta::assert_snapshot; + use std::path::Path; use test_log::test; use crate::test::replace_path; @@ -124,7 +133,12 @@ mod tests { fn test_activate() { let bash = Bash::default(); let exe = Path::new("/some/dir/mise"); - assert_snapshot!(bash.activate(exe, " --status".into())); + let opts = ActivateOptions { + exe: exe.to_path_buf(), + flags: " --status".into(), + no_hook_env: false, + }; + assert_snapshot!(bash.activate(opts)); } #[test] diff --git a/src/shell/elvish.rs b/src/shell/elvish.rs index 88af436d53..9d0f400c45 100644 --- a/src/shell/elvish.rs +++ b/src/shell/elvish.rs @@ -1,14 +1,15 @@ use std::fmt::Display; -use std::path::Path; -use crate::shell::Shell; +use crate::shell::{ActivateOptions, Shell}; use indoc::formatdoc; #[derive(Default)] pub struct Elvish {} impl Shell for Elvish { - fn activate(&self, exe: &Path, flags: String) -> String { + fn activate(&self, opts: ActivateOptions) -> String { + let exe = opts.exe; + let flags = opts.flags; let exe = exe.to_string_lossy(); formatdoc! {r#" @@ -25,7 +26,7 @@ impl Shell for Elvish { fn activate {{ set-env MISE_SHELL elvish - set hook-enabled = $true + set hook-enabled = ${hook_enabled} hook-env }} @@ -55,7 +56,7 @@ impl Shell for Elvish { }} {exe} $@a }} - "#} + "#, hook_enabled = !opts.no_hook_env} } fn deactivate(&self) -> String { @@ -93,6 +94,7 @@ impl Display for Elvish { #[cfg(test)] mod tests { use insta::assert_snapshot; + use std::path::Path; use test_log::test; use crate::test::replace_path; @@ -103,7 +105,12 @@ mod tests { fn test_hook_init() { let elvish = Elvish::default(); let exe = Path::new("/some/dir/mise"); - assert_snapshot!(elvish.activate(exe, " --status".into())); + let opts = ActivateOptions { + exe: exe.to_path_buf(), + flags: " --status".into(), + no_hook_env: false, + }; + assert_snapshot!(elvish.activate(opts)); } #[test] diff --git a/src/shell/fish.rs b/src/shell/fish.rs index e9bdb78699..0a36dc9062 100644 --- a/src/shell/fish.rs +++ b/src/shell/fish.rs @@ -1,8 +1,7 @@ use std::fmt::{Display, Formatter}; -use std::path::Path; use crate::config::Settings; -use crate::shell::Shell; +use crate::shell::{ActivateOptions, Shell}; use indoc::formatdoc; use shell_escape::unix::escape; @@ -10,7 +9,9 @@ use shell_escape::unix::escape; pub struct Fish {} impl Shell for Fish { - fn activate(&self, exe: &Path, flags: String) -> String { + fn activate(&self, opts: ActivateOptions) -> String { + let exe = opts.exe; + let flags = opts.flags; let exe = exe.to_string_lossy(); let description = "'Update mise environment when changing directories'"; let mut out = String::new(); @@ -49,7 +50,11 @@ impl Shell for Fish { command {exe} "$command" $argv end end + "#}); + if !opts.no_hook_env { + out.push_str(&formatdoc! {r#" + function __mise_env_eval --on-event fish_prompt --description {description}; {exe} hook-env{flags} -s fish | source; @@ -76,6 +81,7 @@ impl Shell for Fish { __mise_env_eval "#}); + } if Settings::get().not_found_auto_install { out.push_str(&formatdoc! {r#" if functions -q fish_command_not_found; and not functions -q __mise_fish_command_not_found @@ -136,6 +142,7 @@ impl Display for Fish { #[cfg(test)] mod tests { use insta::assert_snapshot; + use std::path::Path; use test_log::test; use crate::test::replace_path; @@ -146,7 +153,12 @@ mod tests { fn test_activate() { let fish = Fish::default(); let exe = Path::new("/some/dir/mise"); - assert_snapshot!(fish.activate(exe, " --status".into())); + let opts = ActivateOptions { + exe: exe.to_path_buf(), + flags: " --status".into(), + no_hook_env: false, + }; + assert_snapshot!(fish.activate(opts)); } #[test] diff --git a/src/shell/mod.rs b/src/shell/mod.rs index b3564cca60..9d53da2fe0 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -1,6 +1,6 @@ use crate::env; use std::fmt::{Display, Formatter}; -use std::path::Path; +use std::path::PathBuf; use std::str::FromStr; mod bash; @@ -73,13 +73,19 @@ impl FromStr for ShellType { } pub trait Shell: Display { - fn activate(&self, exe: &Path, flags: String) -> String; + fn activate(&self, opts: ActivateOptions) -> String; fn deactivate(&self) -> String; fn set_env(&self, k: &str, v: &str) -> String; fn prepend_env(&self, k: &str, v: &str) -> String; fn unset_env(&self, k: &str) -> String; } +pub struct ActivateOptions { + pub exe: PathBuf, + pub flags: String, + pub no_hook_env: bool, +} + pub fn get_shell(shell: Option) -> Option> { shell.or_else(ShellType::load).map(|st| st.as_shell()) } diff --git a/src/shell/nushell.rs b/src/shell/nushell.rs index 06ba4e7aa1..e90006fda5 100644 --- a/src/shell/nushell.rs +++ b/src/shell/nushell.rs @@ -1,9 +1,8 @@ use std::fmt::Display; -use std::path::Path; use indoc::formatdoc; -use crate::shell::Shell; +use crate::shell::{ActivateOptions, Shell}; #[derive(Default)] pub struct Nushell {} @@ -33,7 +32,9 @@ impl Nushell { } impl Shell for Nushell { - fn activate(&self, exe: &Path, flags: String) -> String { + fn activate(&self, opts: ActivateOptions) -> String { + let exe = opts.exe; + let flags = opts.flags; let exe = exe.to_string_lossy().replace('\\', r#"\\"#); formatdoc! {r#" @@ -127,6 +128,7 @@ impl Display for Nushell { #[cfg(test)] mod tests { use insta::assert_snapshot; + use std::path::Path; use test_log::test; use crate::test::replace_path; @@ -137,7 +139,12 @@ mod tests { fn test_hook_init() { let nushell = Nushell::default(); let exe = Path::new("/some/dir/mise"); - assert_snapshot!(nushell.activate(exe, " --status".into())); + let opts = ActivateOptions { + exe: exe.to_path_buf(), + flags: " --status".into(), + no_hook_env: false, + }; + assert_snapshot!(nushell.activate(opts)); } #[test] diff --git a/src/shell/xonsh.rs b/src/shell/xonsh.rs index 790d2f6d2d..2f65f6d584 100644 --- a/src/shell/xonsh.rs +++ b/src/shell/xonsh.rs @@ -1,10 +1,9 @@ use std::borrow::Cow; use std::fmt::Display; -use std::path::Path; use indoc::formatdoc; -use crate::shell::Shell; +use crate::shell::{ActivateOptions, Shell}; #[derive(Default)] pub struct Xonsh {} @@ -38,7 +37,9 @@ fn xonsh_escape_char(ch: char) -> Option<&'static str> { } impl Shell for Xonsh { - fn activate(&self, exe: &Path, flags: String) -> String { + fn activate(&self, opts: ActivateOptions) -> String { + let exe = opts.exe; + let flags = opts.flags; let exe = exe.display(); // todo: xonsh doesn't update the environment that mise relies on with $PATH.add even with $UPDATE_OS_ENVIRON (github.com/xonsh/xonsh/issues/3207) @@ -149,6 +150,7 @@ impl Display for Xonsh { mod tests { use insta::assert_snapshot; use pretty_assertions::assert_eq; + use std::path::Path; use crate::test::replace_path; @@ -158,7 +160,12 @@ mod tests { fn test_hook_init() { let xonsh = Xonsh::default(); let exe = Path::new("/some/dir/mise"); - insta::assert_snapshot!(xonsh.activate(exe, " --status".into())); + let opts = ActivateOptions { + exe: exe.to_path_buf(), + flags: " --status".into(), + no_hook_env: false, + }; + insta::assert_snapshot!(xonsh.activate(opts)); } #[test] diff --git a/src/shell/zsh.rs b/src/shell/zsh.rs index df4d62070d..788a145d6b 100644 --- a/src/shell/zsh.rs +++ b/src/shell/zsh.rs @@ -1,17 +1,18 @@ use std::fmt::Display; -use std::path::Path; use indoc::formatdoc; use crate::config::Settings; use crate::shell::bash::Bash; -use crate::shell::Shell; +use crate::shell::{ActivateOptions, Shell}; #[derive(Default)] pub struct Zsh {} impl Shell for Zsh { - fn activate(&self, exe: &Path, flags: String) -> String { + fn activate(&self, opts: ActivateOptions) -> String { + let exe = opts.exe; + let flags = opts.flags; let exe = exe.to_string_lossy(); let mut out = String::new(); @@ -41,7 +42,11 @@ impl Shell for Zsh { esac command {exe} "$command" "$@" }} + "#}); + if !opts.no_hook_env { + out.push_str(&formatdoc! {r#" + _mise_hook() {{ eval "$({exe} hook-env{flags} -s zsh)"; }} @@ -56,6 +61,7 @@ impl Shell for Zsh { _mise_hook "#}); + } if Settings::get().not_found_auto_install { out.push_str(&formatdoc! {r#" if [ -z "${{_mise_cmd_not_found:-}}" ]; then @@ -114,6 +120,7 @@ impl Display for Zsh { #[cfg(test)] mod tests { use insta::assert_snapshot; + use std::path::Path; use test_log::test; use crate::test::replace_path; @@ -124,7 +131,12 @@ mod tests { fn test_activate() { let zsh = Zsh::default(); let exe = Path::new("/some/dir/mise"); - assert_snapshot!(zsh.activate(exe, " --status".into())); + let opts = ActivateOptions { + exe: exe.to_path_buf(), + flags: " --status".into(), + no_hook_env: false, + }; + assert_snapshot!(zsh.activate(opts)); } #[test] From 9d5fb66a2a4e64288ecc794f8afad23a6eb931a6 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 14:11:44 +0000 Subject: [PATCH 2/2] [autofix.ci] apply automated fixes --- docs/cli/activate.md | 8 +++++++- docs/cli/index.md | 2 +- mise.usage.kdl | 3 +++ xtasks/fig/src/mise.ts | 7 +++++++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/cli/activate.md b/docs/cli/activate.md index e274248b2c..4798e9de5f 100644 --- a/docs/cli/activate.md +++ b/docs/cli/activate.md @@ -1,6 +1,6 @@ # `mise activate` -- **Usage**: `mise activate [--shims] [-q --quiet] [SHELL_TYPE]` +- **Usage**: `mise activate [FLAGS] [SHELL_TYPE]` - **Source code**: [`src/cli/activate.rs`](https://github.com/jdx/mise/blob/main/src/cli/activate.rs) Initializes mise in the current shell session @@ -48,6 +48,12 @@ Effectively the same as: Suppress non-error messages +### `--no-hook-env` + +Do not automatically call hook-env + +This can be helpful for debugging mise. If you run `eval "$(mise activate --no-hook-env)"`, then you can call `mise hook-env` manually which will output the env vars to stdout without actually modifying the environment. That way you can do things like `mise hook-env --trace` to get more information or just see the values that hook-env is outputting. + Examples: eval "$(mise activate bash)" diff --git a/docs/cli/index.md b/docs/cli/index.md index 03a52e4d49..5e4db1f7d0 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -44,7 +44,7 @@ Answer yes to all confirmation prompts ## Subcommands -- [`mise activate [--shims] [-q --quiet] [SHELL_TYPE]`](/cli/activate.md) +- [`mise activate [FLAGS] [SHELL_TYPE]`](/cli/activate.md) - [`mise alias [-p --plugin ] [--no-header] `](/cli/alias.md) - [`mise alias get `](/cli/alias/get.md) - [`mise alias ls [--no-header] [TOOL]`](/cli/alias/ls.md) diff --git a/mise.usage.kdl b/mise.usage.kdl index a9d77545f0..5c03ea0673 100644 --- a/mise.usage.kdl +++ b/mise.usage.kdl @@ -88,6 +88,9 @@ Customize status output with `status` settings."# long_help "Use shims instead of modifying PATH\nEffectively the same as:\n\n PATH=\"$HOME/.local/share/mise/shims:$PATH\"" } flag "-q --quiet" help="Suppress non-error messages" + flag "--no-hook-env" help="Do not automatically call hook-env" { + long_help "Do not automatically call hook-env\n\nThis can be helpful for debugging mise. If you run `eval \"$(mise activate --no-hook-env)\"`, then you can call `mise hook-env` manually which will output the env vars to stdout without actually modifying the environment. That way you can do things like `mise hook-env --trace` to get more information or just see the values that hook-env is outputting." + } arg "[SHELL_TYPE]" help="Shell type to generate the script for" { choices "bash" "elvish" "fish" "nu" "xonsh" "zsh" } diff --git a/xtasks/fig/src/mise.ts b/xtasks/fig/src/mise.ts index 1ab89b6c4e..598cdfaaf3 100644 --- a/xtasks/fig/src/mise.ts +++ b/xtasks/fig/src/mise.ts @@ -399,6 +399,13 @@ const completionSpec: Fig.Spec = { ], "description": "Suppress non-error messages", "isRepeatable": false + }, + { + "name": [ + "--no-hook-env" + ], + "description": "Do not automatically call hook-env", + "isRepeatable": false } ], "args": [