Skip to content
Open
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
7 changes: 5 additions & 2 deletions src/commands/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
};
Expand All @@ -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}");
Expand Down
38 changes: 35 additions & 3 deletions src/shell/bash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,32 @@ impl Shell for Bash {
format!("export {name}={value:?}")
}

fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result<String> {
fn use_on_cd(
&self,
config: &crate::config::FnmConfig,
install_if_missing: bool,
) -> anyhow::Result<String> {
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#"
Expand All @@ -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"));
}
}
38 changes: 35 additions & 3 deletions src/shell/fish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,32 @@ impl Shell for Fish {
format!("set -gx {name} {value:?};")
}

fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result<String> {
fn use_on_cd(
&self,
config: &crate::config::FnmConfig,
install_if_missing: bool,
) -> anyhow::Result<String> {
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"
Expand All @@ -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"));
}
}
38 changes: 35 additions & 3 deletions src/shell/powershell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,30 @@ impl Shell for PowerShell {
format!(r#"$env:{name} = "{value}""#)
}

fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result<String> {
fn use_on_cd(
&self,
config: &crate::config::FnmConfig,
install_if_missing: bool,
) -> anyhow::Result<String> {
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"
Expand All @@ -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"));
}
}
6 changes: 5 additions & 1 deletion src/shell/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ use clap::ValueEnum;
pub trait Shell: Debug {
fn path(&self, path: &Path) -> anyhow::Result<String>;
fn set_env_var(&self, name: &str, value: &str) -> String;
fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result<String>;
fn use_on_cd(
&self,
config: &crate::config::FnmConfig,
install_if_missing: bool,
) -> anyhow::Result<String>;
fn rehash(&self) -> Option<&'static str> {
None
}
Expand Down
53 changes: 47 additions & 6 deletions src/shell/windows_cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,13 @@ impl Shell for WindowsCmd {
format!("SET {name}={value}")
}

fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result<String> {
fn use_on_cd(
&self,
config: &crate::config::FnmConfig,
install_if_missing: bool,
) -> anyhow::Result<String> {
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(),
Expand All @@ -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(())
}

Expand All @@ -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("\" $*"));
Expand All @@ -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();
}
}
37 changes: 33 additions & 4 deletions src/shell/zsh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,32 @@ impl Shell for Zsh {
Some("rehash")
}

fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result<String> {
fn use_on_cd(
&self,
config: &crate::config::FnmConfig,
install_if_missing: bool,
) -> anyhow::Result<String> {
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"
Expand All @@ -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"));
}
}