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
19 changes: 6 additions & 13 deletions crates/vite_global_cli/src/commands/env/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ unset __vp_bin
vp() {
if [ "$1" = "env" ] && [ "$2" = "use" ]; then
case " $* " in *" -h "*|*" --help "*) command vp "$@"; return; esac
__vp_out="$(VP_ENV_USE_EVAL_ENABLE=1 command vp "$@")" || return $?
__vp_out="$(VP_ENV_USE_EVAL_ENABLE=1 VP_SHELL=sh command vp "$@")" || return $?
eval "$__vp_out"
else
command vp "$@"
Expand Down Expand Up @@ -530,7 +530,8 @@ function vp
command vp $argv; return
end
set -lx VP_ENV_USE_EVAL_ENABLE 1
set -l __vp_out (env FISH_VERSION=$FISH_VERSION __VP_BIN__/vp $argv); or return $status
set -lx VP_SHELL fish
set -l __vp_out (command vp $argv); or return $status
eval (string join ';' $__vp_out)
else
command vp $argv
Expand Down Expand Up @@ -562,7 +563,7 @@ def --env --wrapped vp [...args: string@"nu-complete vp"] {
^vp ...$args
return
}
let out = (with-env { VP_ENV_USE_EVAL_ENABLE: "1", VP_SHELL_NU: "1" } {
let out = (with-env { VP_ENV_USE_EVAL_ENABLE: "1", VP_SHELL: "nu" } {
^vp ...$args
})
let lines = ($out | lines)
Expand Down Expand Up @@ -625,7 +626,7 @@ function vp {
& (Join-Path $__vp_bin "vp") @args; return
}
$env:VP_ENV_USE_EVAL_ENABLE = "1"
$env:VP_SHELL_PWSH = "1"
$env:VP_SHELL = "pwsh"
$output = & (Join-Path $__vp_bin "vp") @args 2>&1 | ForEach-Object {
if ($_ -is [System.Management.Automation.ErrorRecord]) {
Write-Host $_.Exception.Message
Expand All @@ -634,7 +635,7 @@ function vp {
}
}
Remove-Item Env:VP_ENV_USE_EVAL_ENABLE -ErrorAction SilentlyContinue
Remove-Item Env:VP_SHELL_PWSH -ErrorAction SilentlyContinue
Remove-Item Env:VP_SHELL -ErrorAction SilentlyContinue
if ($LASTEXITCODE -eq 0 -and $output) {
Invoke-Expression ($output -join "`n")
}
Expand Down Expand Up @@ -848,10 +849,6 @@ mod tests {
nu_content.contains("VP_COMPLETE=fish"),
"env.nu should use dynamic Fish completion delegation"
);
assert!(
nu_content.contains("VP_SHELL_NU"),
"env.nu should use VP_SHELL_NU explicit marker instead of inherited NU_VERSION"
);
assert!(nu_content.contains("load-env"), "env.nu should use load-env to apply exports");
}

Expand Down Expand Up @@ -1037,10 +1034,6 @@ mod tests {
fish_content.contains("\"$argv[2]\" = \"use\""),
"env.fish should check for 'use' subcommand"
);
assert!(
fish_content.contains("/vp $argv"),
"env.fish should use absolute path to vp for passthrough"
);
}

#[tokio::test]
Expand Down
53 changes: 12 additions & 41 deletions crates/vite_global_cli/src/commands/env/use.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,72 +181,43 @@ mod tests {
use super::*;

#[test]
fn test_detect_shell_pwsh() {
fn test_detect_shell_vp_shell_powershell() {
let _guard = vite_shared::EnvConfig::test_guard(vite_shared::EnvConfig {
vp_shell_pwsh: true,
vp_shell: Some("powershell".into()),
..vite_shared::EnvConfig::for_test()
});
let shell = detect_shell();
assert!(matches!(shell, Shell::PowerShell));
assert_eq!(shell, Shell::PowerShell);
}

#[test]
fn test_detect_shell_fish() {
fn test_detect_shell_vp_shell_fish() {
let _guard = vite_shared::EnvConfig::test_guard(vite_shared::EnvConfig {
fish_version: Some("3.7.0".into()),
vp_shell: Some("fish".into()),
..vite_shared::EnvConfig::for_test()
});
let shell = detect_shell();
assert!(matches!(shell, Shell::Fish));
assert_eq!(shell, Shell::Fish);
}

#[test]
fn test_detect_shell_fish_and_nushell() {
// Fish takes priority over Nu shell signal
fn test_detect_shell_vp_shell_nu() {
let _guard = vite_shared::EnvConfig::test_guard(vite_shared::EnvConfig {
fish_version: Some("3.7.0".into()),
vp_shell_nu: true,
vp_shell: Some("nu".into()),
..vite_shared::EnvConfig::for_test()
});
let shell = detect_shell();
assert!(matches!(shell, Shell::Fish));
assert_eq!(shell, Shell::NuShell);
}

#[test]
fn test_detect_shell_posix_default() {
// All shell detection fields None → defaults
let _guard = vite_shared::EnvConfig::test_guard(vite_shared::EnvConfig::for_test());
let shell = detect_shell();
#[cfg(not(windows))]
assert!(matches!(shell, Shell::Posix));
assert_eq!(shell, Shell::Posix);
#[cfg(windows)]
assert!(matches!(shell, Shell::Cmd));
}

#[test]
fn test_detect_shell_nushell() {
let _guard = vite_shared::EnvConfig::test_guard(vite_shared::EnvConfig {
vp_shell_nu: true,
..vite_shared::EnvConfig::for_test()
});
let shell = detect_shell();
assert!(matches!(shell, Shell::NuShell));
}

#[test]
fn test_detect_shell_inherited_nu_version_is_posix() {
// NU_VERSION alone (inherited from parent Nushell) must NOT trigger Nu detection.
// Only the explicit VP_SHELL_NU marker set by env.nu wrapper counts.
let _guard = vite_shared::EnvConfig::test_guard(vite_shared::EnvConfig {
nu_version: Some("0.111.0".into()),
vp_shell_nu: false,
..vite_shared::EnvConfig::for_test()
});
let shell = detect_shell();
#[cfg(not(windows))]
assert!(matches!(shell, Shell::Posix));
#[cfg(windows)]
let _ = shell;
assert_eq!(shell, Shell::Cmd);
}

#[test]
Expand Down Expand Up @@ -346,7 +317,7 @@ mod tests {
let cwd = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap();
let _guard = vite_shared::EnvConfig::test_guard(vite_shared::EnvConfig {
env_use_eval_enable: true,
vp_shell_pwsh: true,
vp_shell: Some("powershell".into()),
..vite_shared::EnvConfig::for_test_with_home(temp_dir.path())
});

Expand Down
123 changes: 93 additions & 30 deletions crates/vite_global_cli/src/commands/shell.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Shared shell detection and profile helpers.

use std::str::FromStr;

use directories::BaseDirs;
use vite_path::{AbsolutePath, AbsolutePathBuf};
use vite_str::Str;
Expand All @@ -19,21 +21,37 @@ pub enum Shell {
Cmd,
}

/// Detect the current shell from environment variables.
impl FromStr for Shell {
type Err = ();

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"sh" | "bash" | "zsh" => Ok(Shell::Posix),
"fish" => Ok(Shell::Fish),
"nu" | "nushell" => Ok(Shell::NuShell),
"pwsh" | "powershell" => Ok(Shell::PowerShell),
"cmd" => Ok(Shell::Cmd),
Comment thread
nekomoyi marked this conversation as resolved.
_ => Err(()),
}
}
}

/// Detect the current shell:
/// 1. `VP_SHELL` environment variable
/// 2. Platform default
#[must_use]
pub fn detect_shell() -> Shell {
let config = vite_shared::EnvConfig::get();
if config.fish_version.is_some() {
Shell::Fish
} else if config.vp_shell_nu {
Shell::NuShell
} else if config.vp_shell_pwsh {
Shell::PowerShell
} else if cfg!(windows) {
Shell::Cmd
} else {
Shell::Posix

// 1. Check VP_SHELL environment variable
if let Some(vp_shell) = &config.vp_shell {
if let Ok(shell) = Shell::from_str(vp_shell) {
return shell;
}
}

// 2. Platform default
if cfg!(windows) { Shell::Cmd } else { Shell::Posix }
}

/// All shell profile files that interactive terminal sessions may source.
Expand Down Expand Up @@ -235,53 +253,98 @@ mod tests {
use super::*;

#[test]
fn test_detect_shell_pwsh() {
fn test_shell_from_str() {
// POSIX shells
assert_eq!(Shell::from_str("sh"), Ok(Shell::Posix));
assert_eq!(Shell::from_str("bash"), Ok(Shell::Posix));
assert_eq!(Shell::from_str("zsh"), Ok(Shell::Posix));

// Other shells
assert_eq!(Shell::from_str("fish"), Ok(Shell::Fish));
assert_eq!(Shell::from_str("nu"), Ok(Shell::NuShell));
assert_eq!(Shell::from_str("nushell"), Ok(Shell::NuShell));
assert_eq!(Shell::from_str("powershell"), Ok(Shell::PowerShell));
assert_eq!(Shell::from_str("pwsh"), Ok(Shell::PowerShell));
assert_eq!(Shell::from_str("cmd"), Ok(Shell::Cmd));

// Case insensitive
assert_eq!(Shell::from_str("SH"), Ok(Shell::Posix));
assert_eq!(Shell::from_str("BASH"), Ok(Shell::Posix));
assert_eq!(Shell::from_str("Fish"), Ok(Shell::Fish));
assert_eq!(Shell::from_str("POWERSHELL"), Ok(Shell::PowerShell));
assert_eq!(Shell::from_str("Nu"), Ok(Shell::NuShell));

// Invalid
assert!(Shell::from_str("posix").is_err());
assert!(Shell::from_str("invalid").is_err());
assert!(Shell::from_str("").is_err());
}

#[test]
fn test_detect_shell_vp_shell_explicit() {
let _guard = vite_shared::EnvConfig::test_guard(vite_shared::EnvConfig {
vp_shell_pwsh: true,
vp_shell: Some("nu".into()),
..vite_shared::EnvConfig::for_test()
});
let shell = detect_shell();
assert!(matches!(shell, Shell::PowerShell));
assert_eq!(shell, Shell::NuShell);
}

#[test]
fn test_detect_shell_fish() {
fn test_detect_shell_vp_shell_case_insensitive() {
let _guard = vite_shared::EnvConfig::test_guard(vite_shared::EnvConfig {
fish_version: Some("3.7.0".into()),
vp_shell: Some("POWERSHELL".into()),
..vite_shared::EnvConfig::for_test()
});
let shell = detect_shell();
assert!(matches!(shell, Shell::Fish));
assert_eq!(shell, Shell::PowerShell);
}

#[test]
fn test_detect_shell_nushell() {
fn test_detect_shell_vp_shell_pwsh_alias() {
let _guard = vite_shared::EnvConfig::test_guard(vite_shared::EnvConfig {
vp_shell_nu: true,
vp_shell: Some("pwsh".into()),
..vite_shared::EnvConfig::for_test()
});
let shell = detect_shell();
assert!(matches!(shell, Shell::NuShell));
assert_eq!(shell, Shell::PowerShell);
}

#[test]
fn test_detect_shell_nushell_wins_over_powershell() {
fn test_detect_shell_vp_shell_fish() {
let _guard = vite_shared::EnvConfig::test_guard(vite_shared::EnvConfig {
vp_shell_nu: true,
ps_module_path: Some("/some/path".into()),
vp_shell: Some("fish".into()),
..vite_shared::EnvConfig::for_test()
});
let shell = detect_shell();
assert!(matches!(shell, Shell::NuShell));
assert_eq!(shell, Shell::Fish);
}

#[test]
fn test_detect_shell_posix_default() {
let _guard = vite_shared::EnvConfig::test_guard(vite_shared::EnvConfig::for_test());
fn test_detect_shell_defaults_without_vp_shell() {
let _guard = vite_shared::EnvConfig::test_guard(vite_shared::EnvConfig {
vp_shell: None,
..vite_shared::EnvConfig::for_test()
});
let shell = detect_shell();
if cfg!(windows) {
assert_eq!(shell, Shell::Cmd);
} else {
assert_eq!(shell, Shell::Posix);
}
}

#[test]
fn test_detect_shell_invalid_vp_shell_falls_back_to_default() {
let _guard = vite_shared::EnvConfig::test_guard(vite_shared::EnvConfig {
vp_shell: Some("invalid".into()),
..vite_shared::EnvConfig::for_test()
});
let shell = detect_shell();
#[cfg(not(windows))]
assert!(matches!(shell, Shell::Posix));
#[cfg(windows)]
assert!(matches!(shell, Shell::Cmd));
if cfg!(windows) {
assert_eq!(shell, Shell::Cmd);
} else {
assert_eq!(shell, Shell::Posix);
}
}
}
41 changes: 5 additions & 36 deletions crates/vite_shared/src/env_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,33 +113,10 @@ pub struct EnvConfig {
/// Env: `HOME` (Unix) / `USERPROFILE` (Windows)
pub user_home: Option<PathBuf>,

/// Fish shell version (indicates running under fish).
/// Explicitly specify the current shell.
///
/// Env: `FISH_VERSION`
pub fish_version: Option<String>,

/// `PowerShell` module path (indicates running under `PowerShell` on Windows).
///
/// Env: `PSModulePath`
pub ps_module_path: Option<String>,

/// Nu shell version (indicates running under Nu shell).
///
/// Env: `NU_VERSION`
pub nu_version: Option<String>,

/// Explicit Nu shell eval signal set by the `env.nu` wrapper.
///
/// Unlike `NU_VERSION`, this is not inherited by child processes — it is only
/// present when the Nushell wrapper explicitly passes it via `with-env`.
///
/// Env: `VP_SHELL_NU`
pub vp_shell_nu: bool,

/// Explicit `PowerShell` eval signal set by the `env.ps1` wrapper.
///
/// Env: `VP_SHELL_PWSH`
pub vp_shell_pwsh: bool,
/// Env: `VP_SHELL`
pub vp_shell: Option<String>,
}

impl EnvConfig {
Expand Down Expand Up @@ -167,11 +144,7 @@ impl EnvConfig {
.or_else(|_| std::env::var("USERPROFILE"))
.ok()
.map(PathBuf::from),
fish_version: std::env::var("FISH_VERSION").ok(),
ps_module_path: std::env::var("PSModulePath").ok(),
nu_version: std::env::var("NU_VERSION").ok(),
vp_shell_nu: std::env::var(env_vars::VP_SHELL_NU).is_ok(),
vp_shell_pwsh: std::env::var(env_vars::VP_SHELL_PWSH).is_ok(),
vp_shell: std::env::var(env_vars::VP_SHELL).ok(),
}
}

Expand Down Expand Up @@ -254,11 +227,7 @@ impl EnvConfig {
update_task_types: None,
node_version: None,
user_home: None,
fish_version: None,
ps_module_path: None,
nu_version: None,
vp_shell_nu: false,
vp_shell_pwsh: false,
vp_shell: None,
}
}

Expand Down
Loading
Loading