diff --git a/src/commands/env.rs b/src/commands/env.rs index 60d8dbe6d..d50eb4b09 100644 --- a/src/commands/env.rs +++ b/src/commands/env.rs @@ -26,6 +26,9 @@ pub struct Env { /// Print the script to change Node versions every directory change #[clap(long)] use_on_cd: bool, + /// Install the target version if missing (only relevant with --use-on-cd) + #[clap(long, requires = "use_on_cd")] + install_if_missing: bool, } fn generate_symlink_path() -> String { @@ -144,7 +147,7 @@ impl Command for Env { config.clone().with_multishell_path(multishell_path.clone()); let use_cmd = Use { version: None, - install_if_missing: false, + install_if_missing: self.install_if_missing, silent_if_unchanged: true, info_to_stderr: true, }; @@ -160,7 +163,7 @@ impl Command for Env { colored::control::unset_override(); } - println!("{}", shell.use_on_cd(config)?); + println!("{}", shell.use_on_cd(config, self.install_if_missing)?); } if let Some(v) = shell.rehash() { println!("{v}"); diff --git a/src/shell/bash.rs b/src/shell/bash.rs index 667c96b8b..f5baed30c 100644 --- a/src/shell/bash.rs +++ b/src/shell/bash.rs @@ -25,22 +25,32 @@ impl Shell for Bash { format!("export {name}={value:?}") } - fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result { + fn use_on_cd( + &self, + config: &crate::config::FnmConfig, + install_if_missing: bool, + ) -> anyhow::Result { let version_file_exists_condition = if config.resolve_engines() { "-f .node-version || -f .nvmrc || -f package.json" } else { "-f .node-version || -f .nvmrc" }; + let fnm_use_cmd = if install_if_missing { + "fnm use --silent-if-unchanged --install-if-missing" + } else { + "fnm use --silent-if-unchanged" + }; let autoload_hook = match config.version_file_strategy() { VersionFileStrategy::Local => formatdoc!( r" if [[ {version_file_exists_condition} ]]; then - fnm use --silent-if-unchanged + {fnm_use_cmd} fi ", version_file_exists_condition = version_file_exists_condition, + fnm_use_cmd = fnm_use_cmd, ), - VersionFileStrategy::Recursive => String::from(r"fnm use --silent-if-unchanged"), + VersionFileStrategy::Recursive => String::from(fnm_use_cmd), }; Ok(formatdoc!( r#" @@ -59,3 +69,25 @@ impl Shell for Bash { )) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn use_on_cd_without_install_if_missing() { + let output = Bash + .use_on_cd(&crate::config::FnmConfig::default(), false) + .unwrap(); + assert!(output.contains("fnm use --silent-if-unchanged")); + assert!(!output.contains("--install-if-missing")); + } + + #[test] + fn use_on_cd_with_install_if_missing() { + let output = Bash + .use_on_cd(&crate::config::FnmConfig::default(), true) + .unwrap(); + assert!(output.contains("fnm use --silent-if-unchanged --install-if-missing")); + } +} diff --git a/src/shell/fish.rs b/src/shell/fish.rs index 90078aab2..2f241f0b4 100644 --- a/src/shell/fish.rs +++ b/src/shell/fish.rs @@ -25,22 +25,32 @@ impl Shell for Fish { format!("set -gx {name} {value:?};") } - fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result { + fn use_on_cd( + &self, + config: &crate::config::FnmConfig, + install_if_missing: bool, + ) -> anyhow::Result { let version_file_exists_condition = if config.resolve_engines() { "test -f .node-version -o -f .nvmrc -o -f package.json" } else { "test -f .node-version -o -f .nvmrc" }; + let fnm_use_cmd = if install_if_missing { + "fnm use --silent-if-unchanged --install-if-missing" + } else { + "fnm use --silent-if-unchanged" + }; let autoload_hook = match config.version_file_strategy() { VersionFileStrategy::Local => formatdoc!( r" if {version_file_exists_condition} - fnm use --silent-if-unchanged + {fnm_use_cmd} end ", version_file_exists_condition = version_file_exists_condition, + fnm_use_cmd = fnm_use_cmd, ), - VersionFileStrategy::Recursive => String::from(r"fnm use --silent-if-unchanged"), + VersionFileStrategy::Recursive => String::from(fnm_use_cmd), }; Ok(formatdoc!( r" @@ -53,3 +63,25 @@ impl Shell for Fish { )) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn use_on_cd_without_install_if_missing() { + let output = Fish + .use_on_cd(&crate::config::FnmConfig::default(), false) + .unwrap(); + assert!(output.contains("fnm use --silent-if-unchanged")); + assert!(!output.contains("--install-if-missing")); + } + + #[test] + fn use_on_cd_with_install_if_missing() { + let output = Fish + .use_on_cd(&crate::config::FnmConfig::default(), true) + .unwrap(); + assert!(output.contains("fnm use --silent-if-unchanged --install-if-missing")); + } +} diff --git a/src/shell/powershell.rs b/src/shell/powershell.rs index 5468daa82..cd4e421d8 100644 --- a/src/shell/powershell.rs +++ b/src/shell/powershell.rs @@ -25,20 +25,30 @@ impl Shell for PowerShell { format!(r#"$env:{name} = "{value}""#) } - fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result { + fn use_on_cd( + &self, + config: &crate::config::FnmConfig, + install_if_missing: bool, + ) -> anyhow::Result { let version_file_exists_condition = if config.resolve_engines() { "(Test-Path .nvmrc) -Or (Test-Path .node-version) -Or (Test-Path package.json)" } else { "(Test-Path .nvmrc) -Or (Test-Path .node-version)" }; + let fnm_use_cmd = if install_if_missing { + "fnm use --silent-if-unchanged --install-if-missing" + } else { + "fnm use --silent-if-unchanged" + }; let autoload_hook = match config.version_file_strategy() { VersionFileStrategy::Local => formatdoc!( r" - If ({version_file_exists_condition}) {{ & fnm use --silent-if-unchanged }} + If ({version_file_exists_condition}) {{ & {fnm_use_cmd} }} ", version_file_exists_condition = version_file_exists_condition, + fnm_use_cmd = fnm_use_cmd, ), - VersionFileStrategy::Recursive => String::from(r"fnm use --silent-if-unchanged"), + VersionFileStrategy::Recursive => String::from(fnm_use_cmd), }; Ok(formatdoc!( r" @@ -54,3 +64,25 @@ impl Shell for PowerShell { clap_complete::Shell::PowerShell } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn use_on_cd_without_install_if_missing() { + let output = PowerShell + .use_on_cd(&crate::config::FnmConfig::default(), false) + .unwrap(); + assert!(output.contains("fnm use --silent-if-unchanged")); + assert!(!output.contains("--install-if-missing")); + } + + #[test] + fn use_on_cd_with_install_if_missing() { + let output = PowerShell + .use_on_cd(&crate::config::FnmConfig::default(), true) + .unwrap(); + assert!(output.contains("fnm use --silent-if-unchanged --install-if-missing")); + } +} diff --git a/src/shell/shell.rs b/src/shell/shell.rs index 481b5e04d..aa304bbef 100644 --- a/src/shell/shell.rs +++ b/src/shell/shell.rs @@ -6,7 +6,11 @@ use clap::ValueEnum; pub trait Shell: Debug { fn path(&self, path: &Path) -> anyhow::Result; fn set_env_var(&self, name: &str, value: &str) -> String; - fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result; + fn use_on_cd( + &self, + config: &crate::config::FnmConfig, + install_if_missing: bool, + ) -> anyhow::Result; fn rehash(&self) -> Option<&'static str> { None } diff --git a/src/shell/windows_cmd/mod.rs b/src/shell/windows_cmd/mod.rs index cb968e437..1d9d193db 100644 --- a/src/shell/windows_cmd/mod.rs +++ b/src/shell/windows_cmd/mod.rs @@ -27,9 +27,13 @@ impl Shell for WindowsCmd { format!("SET {name}={value}") } - fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result { + fn use_on_cd( + &self, + config: &crate::config::FnmConfig, + install_if_missing: bool, + ) -> anyhow::Result { let path = config.base_dir_with_default().join("cd.cmd"); - create_cd_file_at(&path).map_err(|source| { + create_cd_file_at(&path, install_if_missing).map_err(|source| { anyhow::anyhow!( "Can't create cd.cmd file for use-on-cd at {}: {}", path.display(), @@ -43,11 +47,19 @@ impl Shell for WindowsCmd { } } -fn create_cd_file_at(path: &std::path::Path) -> std::io::Result<()> { +fn create_cd_file_at(path: &std::path::Path, install_if_missing: bool) -> std::io::Result<()> { use std::io::Write; - let cmd_contents = include_bytes!("./cd.cmd"); + let extra_flags = if install_if_missing { + " --install-if-missing" + } else { + "" + }; + let cmd_contents = format!( + "@echo off\r\ncd /d %*\r\nif \"%FNM_VERSION_FILE_STRATEGY%\" == \"recursive\" (\r\n fnm use --silent-if-unchanged{extra_flags}\r\n) else (\r\n if exist .nvmrc (\r\n fnm use --silent-if-unchanged{extra_flags}\r\n ) else (\r\n if exist .node-version (\r\n fnm use --silent-if-unchanged{extra_flags}\r\n )\r\n )\r\n)\r\n@echo on\r\n", + extra_flags = extra_flags, + ); let mut file = std::fs::File::create(path)?; - file.write_all(cmd_contents)?; + file.write_all(cmd_contents.as_bytes())?; Ok(()) } @@ -60,7 +72,7 @@ mod tests { let base_dir = std::env::temp_dir().join("fnm cmd test with spaces"); std::fs::create_dir_all(&base_dir).unwrap(); let config = crate::config::FnmConfig::default().with_base_dir(Some(base_dir.clone())); - let output = WindowsCmd.use_on_cd(&config).unwrap(); + let output = WindowsCmd.use_on_cd(&config, false).unwrap(); assert!(output.starts_with("doskey cd=\"")); assert!(output.ends_with("\" $*")); @@ -70,4 +82,33 @@ mod tests { std::fs::remove_file(base_dir.join("cd.cmd")).unwrap(); std::fs::remove_dir_all(base_dir).unwrap(); } + + #[test] + fn use_on_cd_without_install_if_missing() { + let base_dir = std::env::temp_dir().join("fnm cmd test no install"); + std::fs::create_dir_all(&base_dir).unwrap(); + let config = crate::config::FnmConfig::default().with_base_dir(Some(base_dir.clone())); + WindowsCmd.use_on_cd(&config, false).unwrap(); + + let cd_cmd = std::fs::read_to_string(base_dir.join("cd.cmd")).unwrap(); + assert!(cd_cmd.contains("fnm use --silent-if-unchanged")); + assert!(!cd_cmd.contains("--install-if-missing")); + + std::fs::remove_file(base_dir.join("cd.cmd")).unwrap(); + std::fs::remove_dir_all(base_dir).unwrap(); + } + + #[test] + fn use_on_cd_with_install_if_missing() { + let base_dir = std::env::temp_dir().join("fnm cmd test install"); + std::fs::create_dir_all(&base_dir).unwrap(); + let config = crate::config::FnmConfig::default().with_base_dir(Some(base_dir.clone())); + WindowsCmd.use_on_cd(&config, true).unwrap(); + + let cd_cmd = std::fs::read_to_string(base_dir.join("cd.cmd")).unwrap(); + assert!(cd_cmd.contains("fnm use --silent-if-unchanged --install-if-missing")); + + std::fs::remove_file(base_dir.join("cd.cmd")).unwrap(); + std::fs::remove_dir_all(base_dir).unwrap(); + } } diff --git a/src/shell/zsh.rs b/src/shell/zsh.rs index 401ca2eb8..657d0162d 100644 --- a/src/shell/zsh.rs +++ b/src/shell/zsh.rs @@ -29,22 +29,32 @@ impl Shell for Zsh { Some("rehash") } - fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result { + fn use_on_cd( + &self, + config: &crate::config::FnmConfig, + install_if_missing: bool, + ) -> anyhow::Result { let version_file_exists_condition = if config.resolve_engines() { "-f .node-version || -f .nvmrc || -f package.json" } else { "-f .node-version || -f .nvmrc" }; + let fnm_use_cmd = if install_if_missing { + "fnm use --silent-if-unchanged --install-if-missing" + } else { + "fnm use --silent-if-unchanged" + }; let autoload_hook = match config.version_file_strategy() { VersionFileStrategy::Local => formatdoc!( r" if [[ {version_file_exists_condition} ]]; then - fnm use --silent-if-unchanged + {fnm_use_cmd} fi ", version_file_exists_condition = version_file_exists_condition, + fnm_use_cmd = fnm_use_cmd, ), - VersionFileStrategy::Recursive => String::from(r"fnm use --silent-if-unchanged"), + VersionFileStrategy::Recursive => String::from(fnm_use_cmd), }; Ok(formatdoc!( r" @@ -67,7 +77,26 @@ mod tests { #[test] fn use_on_cd_removes_existing_hook_before_adding() { - let output = Zsh.use_on_cd(&crate::config::FnmConfig::default()).unwrap(); + let output = Zsh + .use_on_cd(&crate::config::FnmConfig::default(), false) + .unwrap(); assert!(output.contains("add-zsh-hook -D chpwd _fnm_autoload_hook")); } + + #[test] + fn use_on_cd_without_install_if_missing() { + let output = Zsh + .use_on_cd(&crate::config::FnmConfig::default(), false) + .unwrap(); + assert!(output.contains("fnm use --silent-if-unchanged")); + assert!(!output.contains("--install-if-missing")); + } + + #[test] + fn use_on_cd_with_install_if_missing() { + let output = Zsh + .use_on_cd(&crate::config::FnmConfig::default(), true) + .unwrap(); + assert!(output.contains("fnm use --silent-if-unchanged --install-if-missing")); + } }