From 18e5d594d96bc2470978fb8dd257543e55839752 Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Mon, 6 Apr 2026 01:00:58 +0900 Subject: [PATCH 01/22] feat(env): add support for Nu shell version in EnvConfig --- crates/vite_shared/src/env_config.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/vite_shared/src/env_config.rs b/crates/vite_shared/src/env_config.rs index fcfa047c2f..6e63629c6b 100644 --- a/crates/vite_shared/src/env_config.rs +++ b/crates/vite_shared/src/env_config.rs @@ -122,6 +122,11 @@ pub struct EnvConfig { /// /// Env: `PSModulePath` pub ps_module_path: Option, + + /// Nu shell version (indicates running under Nu shell). + /// + /// Env: `NU_VERSION` + pub nu_version: Option, } impl EnvConfig { @@ -151,6 +156,7 @@ impl EnvConfig { .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(), } } @@ -233,6 +239,7 @@ impl EnvConfig { user_home: None, fish_version: None, ps_module_path: None, + nu_version: None, } } From fc9d45704bca9c0849832d4bb3576cfc1b831fa9 Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Mon, 6 Apr 2026 01:09:39 +0900 Subject: [PATCH 02/22] feat(shell): add support for NuShell in shell detection and command formatting --- .../vite_global_cli/src/commands/env/use.rs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/crates/vite_global_cli/src/commands/env/use.rs b/crates/vite_global_cli/src/commands/env/use.rs index 398fb57379..2a7893dda0 100644 --- a/crates/vite_global_cli/src/commands/env/use.rs +++ b/crates/vite_global_cli/src/commands/env/use.rs @@ -25,6 +25,8 @@ enum Shell { PowerShell, /// Windows cmd.exe Cmd, + /// Nushell + NuShell, } /// Detect the current shell from environment variables. @@ -32,6 +34,8 @@ fn detect_shell() -> Shell { let config = vite_shared::EnvConfig::get(); if config.fish_version.is_some() { Shell::Fish + } else if config.nu_version.is_some() { + Shell::NuShell } else if cfg!(windows) && config.ps_module_path.is_some() { Shell::PowerShell } else if cfg!(windows) { @@ -48,6 +52,7 @@ fn format_export(shell: &Shell, value: &str) -> String { Shell::Fish => format!("set -gx {VERSION_ENV_VAR} {value}"), Shell::PowerShell => format!("$env:{VERSION_ENV_VAR} = \"{value}\""), Shell::Cmd => format!("set {VERSION_ENV_VAR}={value}"), + Shell::NuShell => format!("$env.{VERSION_ENV_VAR} = \"{value}\""), } } @@ -60,6 +65,7 @@ fn format_unset(shell: &Shell) -> String { format!("Remove-Item Env:{VERSION_ENV_VAR} -ErrorAction SilentlyContinue") } Shell::Cmd => format!("set {VERSION_ENV_VAR}="), + Shell::NuShell => format!("hide-env {VERSION_ENV_VAR}"), } } @@ -194,6 +200,17 @@ mod tests { assert!(matches!(shell, Shell::Fish)); } + #[test] + fn test_detect_shell_fish_and_nushell() { + let _guard = vite_shared::EnvConfig::test_guard(vite_shared::EnvConfig { + fish_version: Some("3.7.0".into()), + nu_version: Some("0.91.0".into()), + ..vite_shared::EnvConfig::for_test() + }); + let shell = detect_shell(); + assert!(matches!(shell, Shell::Fish)); + } + #[test] fn test_detect_shell_posix_default() { // All shell detection fields None → defaults @@ -205,6 +222,16 @@ mod tests { assert!(matches!(shell, Shell::Cmd)); } + #[test] + fn test_detect_shell_nushell() { + let _guard = vite_shared::EnvConfig::test_guard(vite_shared::EnvConfig { + nu_version: Some("0.91.0".into()), + ..vite_shared::EnvConfig::for_test() + }); + let shell = detect_shell(); + assert!(matches!(shell, Shell::NuShell)); + } + #[test] fn test_format_export_posix() { let result = format_export(&Shell::Posix, "20.18.0"); @@ -252,4 +279,15 @@ mod tests { let result = format_unset(&Shell::Cmd); assert_eq!(result, "set VP_NODE_VERSION="); } + #[test] + fn test_format_export_nushell() { + let result = format_export(&Shell::NuShell, "20.18.0"); + assert_eq!(result, "$env.VP_NODE_VERSION = \"20.18.0\""); + } + + #[test] + fn test_format_unset_nushell() { + let result = format_unset(&Shell::NuShell); + assert_eq!(result, "hide-env VP_NODE_VERSION"); + } } From e17d514919d21e69cb4bebe489508200ff966f79 Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Mon, 6 Apr 2026 02:20:27 +0900 Subject: [PATCH 03/22] feat(env): add Nushell support with completion and environment setup --- .../vite_global_cli/src/commands/env/setup.rs | 80 ++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/crates/vite_global_cli/src/commands/env/setup.rs b/crates/vite_global_cli/src/commands/env/setup.rs index 45718c87cf..9cef70faa8 100644 --- a/crates/vite_global_cli/src/commands/env/setup.rs +++ b/crates/vite_global_cli/src/commands/env/setup.rs @@ -46,6 +46,23 @@ pub async fn execute(refresh: bool, env_only: bool) -> Result // Create env files with PATH guard (prevents duplicate PATH entries) create_env_files(&vite_plus_home).await?; + // Generate native Nushell completion file at ~/.vite-plus/vp.nu. + // This allows env.nu to source completions without requiring Fish to be installed. + // command_with_help() uses a deep call stack, so we spawn a thread with a larger stack. + let nu_completions_path: std::path::PathBuf = vite_plus_home.join("vp.nu").as_path().to_path_buf(); + std::thread::Builder::new() + .stack_size(8 * 1024 * 1024) + .spawn(move || -> std::io::Result<()> { + let mut file = std::fs::File::create(&nu_completions_path)?; + let mut cmd = crate::cli::command_with_help(); + clap_complete::generate(clap_complete_nushell::Nushell, &mut cmd, "vp", &mut file); + Ok(()) + }) + .map_err(Error::CommandExecution)? + .join() + .map_err(|_| Error::Other("Nushell completion generation failed".into()))? + .map_err(Error::CommandExecution)?; + if env_only { println!("{}", help::render_heading("Setup")); println!(" Updated shell environment files."); @@ -388,6 +405,7 @@ async fn cleanup_legacy_completion_dir(vite_plus_home: &vite_path::AbsolutePath) /// Creates: /// - `~/.vite-plus/env` (POSIX shell — bash/zsh) with `vp()` wrapper function /// - `~/.vite-plus/env.fish` (fish shell) with `vp` wrapper function +/// - `~/.vite-plus/env.nu` (Nushell) with `vp env use` wrapper function /// - `~/.vite-plus/env.ps1` (PowerShell) with PATH setup + `vp` function /// - `~/.vite-plus/bin/vp-use.cmd` (cmd.exe wrapper for `vp env use`) async fn create_env_files(vite_plus_home: &vite_path::AbsolutePath) -> Result<(), Error> { @@ -497,6 +515,38 @@ complete -c vpr --keep-order --exclusive --arguments "(__vpr_complete)" let env_fish_file = vite_plus_home.join("env.fish"); tokio::fs::write(&env_fish_file, env_fish_content).await?; + // Nushell env file with vp wrapper function + let env_nu_content = r#"# Vite+ environment setup (https://viteplus.dev) +$env.PATH = ($env.PATH | where { $in != "__VP_BIN__" } | prepend "__VP_BIN__") + +# Load native Nushell completions for vp (generated by clap_complete_nushell). +source "__VP_HOME__/vp.nu" + +# Intercept `vp env use` to apply environment changes to the current shell session. +# Overrides the extern defined in vp.nu for execution; vp.nu still provides completions. +def --env --wrapped "vp env use" [...args: string] { + if ("-h" in $args) or ("--help" in $args) { + ^vp env use ...$args + return + } + let out = (with-env { VP_ENV_USE_EVAL_ENABLE: "1", NU_VERSION: "1" } { + ^vp env use ...$args + }) + let exports = ($out | lines | where { $in =~ '^\$env\.' } | parse '$env.{key} = "{value}"') + if ($exports | is-not-empty) { + load-env ($exports | reduce -f {} {|it, acc| $acc | insert $it.key $it.value}) + } + let unsets = ($out | lines | where { $in =~ '^hide-env ' } | parse 'hide-env {key}' | get key) + for key in $unsets { hide-env $key } +} + +export def --env --wrapped vpr [...args: string] { ^vp run ...$args } +"# + .replace("__VP_BIN__", &bin_path_ref) + .replace("__VP_HOME__", &vite_plus_home.as_path().display().to_string()); + let env_nu_file = vite_plus_home.join("env.nu"); + tokio::fs::write(&env_nu_file, env_nu_content).await?; + // PowerShell env file let env_ps1_content = r#"# Vite+ environment setup (https://viteplus.dev) $__vp_bin = "__VP_BIN_WIN__" @@ -599,6 +649,10 @@ fn print_path_instructions(bin_dir: &vite_path::AbsolutePath) { println!(); println!(" source \"{home_path}/env.fish\""); println!(); + println!(" For Nushell, add to ~/.config/nushell/config.nu:"); + println!(); + println!(" source \"{home_path}/env.nu\""); + println!(); println!(" For PowerShell, add to your $PROFILE:"); println!(); println!(" . \"{home_path}/env.ps1\""); @@ -652,20 +706,44 @@ mod tests { let env_path = home.join("env"); let env_fish_path = home.join("env.fish"); + let env_nu_path = home.join("env.nu"); let env_ps1_path = home.join("env.ps1"); assert!(env_path.as_path().exists(), "env file should be created"); assert!(env_fish_path.as_path().exists(), "env.fish file should be created"); + assert!(env_nu_path.as_path().exists(), "env.nu file should be created"); assert!(env_ps1_path.as_path().exists(), "env.ps1 file should be created"); } #[tokio::test] - async fn test_create_env_files_replaces_placeholder_with_home_relative_path() { + async fn test_create_env_files_nu_contains_path_guard() { let temp_dir = TempDir::new().unwrap(); let home = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); let _guard = home_guard(temp_dir.path()); create_env_files(&home).await.unwrap(); + let nu_content = tokio::fs::read_to_string(home.join("env.nu")).await.unwrap(); + assert!( + !nu_content.contains("__VP_BIN__"), + "env.nu should not contain __VP_BIN__ placeholder" + ); + assert!(nu_content.contains("$HOME/bin"), "env.nu should reference $HOME/bin"); + assert!( + nu_content.contains("VP_ENV_USE_EVAL_ENABLE"), + "env.nu should set VP_ENV_USE_EVAL_ENABLE" + ); + assert!( + nu_content.contains("vp.nu"), + "env.nu should source the native Nushell completion file" + ); + } + + #[tokio::test] + async fn test_create_env_files_replaces_placeholder_with_home_relative_path() { + let temp_dir = TempDir::new().unwrap(); + let home = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + let _guard = home_guard(temp_dir.path()); + let env_content = tokio::fs::read_to_string(home.join("env")).await.unwrap(); let fish_content = tokio::fs::read_to_string(home.join("env.fish")).await.unwrap(); From f339b453404f1378f0d150ff0edb9522612582f9 Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Mon, 6 Apr 2026 02:23:05 +0900 Subject: [PATCH 04/22] feat(nushell): add Nushell completion support in main function --- crates/vite_global_cli/src/main.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/vite_global_cli/src/main.rs b/crates/vite_global_cli/src/main.rs index f339b037f4..2aff9137f3 100644 --- a/crates/vite_global_cli/src/main.rs +++ b/crates/vite_global_cli/src/main.rs @@ -25,6 +25,7 @@ use std::{ use clap::error::{ContextKind, ContextValue}; use clap_complete::env::CompleteEnv; +use clap_complete_nushell::Nushell; use owo_colors::OwoColorize; use vite_shared::output; @@ -239,6 +240,13 @@ async fn main() -> ExitCode { return ExitCode::SUCCESS; } + // Handle Nushell completion (clap_complete_nushell uses generate() directly) + if env::var_os("VP_COMPLETE").is_some_and(|shell| shell == "nushell") { + let mut cmd = command_with_help(); + clap_complete::generate(Nushell, &mut cmd, "vp", &mut std::io::stdout()); + return ExitCode::SUCCESS; + } + // Handle shell completion CompleteEnv::with_factory(command_with_help).var("VP_COMPLETE").complete(); From babc76d325ebf20ba6f7149f279ffdbc411c8692 Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Mon, 6 Apr 2026 02:23:15 +0900 Subject: [PATCH 05/22] feat(nushell): add clap_complete_nushell dependency for Nushell support --- crates/vite_global_cli/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/vite_global_cli/Cargo.toml b/crates/vite_global_cli/Cargo.toml index fa1b693d69..e8a3f6c2ce 100644 --- a/crates/vite_global_cli/Cargo.toml +++ b/crates/vite_global_cli/Cargo.toml @@ -16,6 +16,7 @@ base64-simd = { workspace = true } chrono = { workspace = true } clap = { workspace = true, features = ["derive"] } clap_complete = { workspace = true, features = ["unstable-dynamic"] } +clap_complete_nushell = { workspace = true } directories = { workspace = true } flate2 = { workspace = true } serde = { workspace = true } From 840bf1e00e713b4cce2a51a4edbe1ee4d520bb4a Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Mon, 6 Apr 2026 02:23:24 +0900 Subject: [PATCH 06/22] feat(nushell): add clap_complete_nushell dependency for Nushell completion support --- Cargo.lock | 11 +++++++++++ Cargo.toml | 1 + 2 files changed, 12 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 738eba4aa1..65f55e819d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -886,6 +886,16 @@ dependencies = [ "shlex", ] +[[package]] +name = "clap_complete_nushell" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbb9e9715d29a754b468591be588f6b926f5b0a1eb6a8b62acabeb66ff84d897" +dependencies = [ + "clap", + "clap_complete", +] + [[package]] name = "clap_derive" version = "4.6.0" @@ -7334,6 +7344,7 @@ dependencies = [ "chrono", "clap", "clap_complete", + "clap_complete_nushell", "crossterm", "directories", "flate2", diff --git a/Cargo.toml b/Cargo.toml index 2d71b26139..9aa2d7978e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ blake3 = "1.8.2" chrono = { version = "0.4", features = ["serde"] } clap = "4.5.40" clap_complete = "4.6.0" +clap_complete_nushell = "4.6.0" commondir = "1.0.0" cow-utils = "0.1.3" criterion = { version = "0.7", features = ["html_reports"] } From 4e4d49566f2a33f03f484490ac900415ec606608 Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Mon, 6 Apr 2026 02:48:27 +0900 Subject: [PATCH 07/22] chore: remove clap_complete_nushell dependency from Cargo.toml files --- Cargo.lock | 11 ----------- Cargo.toml | 1 - crates/vite_global_cli/Cargo.toml | 1 - 3 files changed, 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 65f55e819d..738eba4aa1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -886,16 +886,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "clap_complete_nushell" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb9e9715d29a754b468591be588f6b926f5b0a1eb6a8b62acabeb66ff84d897" -dependencies = [ - "clap", - "clap_complete", -] - [[package]] name = "clap_derive" version = "4.6.0" @@ -7344,7 +7334,6 @@ dependencies = [ "chrono", "clap", "clap_complete", - "clap_complete_nushell", "crossterm", "directories", "flate2", diff --git a/Cargo.toml b/Cargo.toml index 9aa2d7978e..2d71b26139 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,7 +72,6 @@ blake3 = "1.8.2" chrono = { version = "0.4", features = ["serde"] } clap = "4.5.40" clap_complete = "4.6.0" -clap_complete_nushell = "4.6.0" commondir = "1.0.0" cow-utils = "0.1.3" criterion = { version = "0.7", features = ["html_reports"] } diff --git a/crates/vite_global_cli/Cargo.toml b/crates/vite_global_cli/Cargo.toml index e8a3f6c2ce..fa1b693d69 100644 --- a/crates/vite_global_cli/Cargo.toml +++ b/crates/vite_global_cli/Cargo.toml @@ -16,7 +16,6 @@ base64-simd = { workspace = true } chrono = { workspace = true } clap = { workspace = true, features = ["derive"] } clap_complete = { workspace = true, features = ["unstable-dynamic"] } -clap_complete_nushell = { workspace = true } directories = { workspace = true } flate2 = { workspace = true } serde = { workspace = true } From d55632e54474dc393a5a9b921497a4e94e776f32 Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Mon, 6 Apr 2026 02:48:33 +0900 Subject: [PATCH 08/22] feat(nushell): update completion generation to use Fish format for Nushell support --- .../vite_global_cli/src/commands/env/setup.rs | 76 ++++++++++++------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/crates/vite_global_cli/src/commands/env/setup.rs b/crates/vite_global_cli/src/commands/env/setup.rs index 9cef70faa8..881d6157f5 100644 --- a/crates/vite_global_cli/src/commands/env/setup.rs +++ b/crates/vite_global_cli/src/commands/env/setup.rs @@ -46,21 +46,21 @@ pub async fn execute(refresh: bool, env_only: bool) -> Result // Create env files with PATH guard (prevents duplicate PATH entries) create_env_files(&vite_plus_home).await?; - // Generate native Nushell completion file at ~/.vite-plus/vp.nu. - // This allows env.nu to source completions without requiring Fish to be installed. + // Generate Fish completion file at ~/.vite-plus/vp.fish for use by the Nushell completer. + // Fish completions are generated statically so that Nushell can delegate to Fish at runtime. // command_with_help() uses a deep call stack, so we spawn a thread with a larger stack. - let nu_completions_path: std::path::PathBuf = vite_plus_home.join("vp.nu").as_path().to_path_buf(); + let fish_completions_path = vite_plus_home.join("vp.fish").as_path().to_path_buf(); std::thread::Builder::new() .stack_size(8 * 1024 * 1024) .spawn(move || -> std::io::Result<()> { - let mut file = std::fs::File::create(&nu_completions_path)?; + let mut file = std::fs::File::create(&fish_completions_path)?; let mut cmd = crate::cli::command_with_help(); - clap_complete::generate(clap_complete_nushell::Nushell, &mut cmd, "vp", &mut file); + clap_complete::generate(clap_complete::Shell::Fish, &mut cmd, "vp", &mut file); Ok(()) }) .map_err(Error::CommandExecution)? .join() - .map_err(|_| Error::Other("Nushell completion generation failed".into()))? + .map_err(|_| Error::Other("Fish completion generation failed".into()))? .map_err(Error::CommandExecution)?; if env_only { @@ -515,32 +515,48 @@ complete -c vpr --keep-order --exclusive --arguments "(__vpr_complete)" let env_fish_file = vite_plus_home.join("env.fish"); tokio::fs::write(&env_fish_file, env_fish_content).await?; - // Nushell env file with vp wrapper function + // Nushell env file with vp wrapper function. + // Completions delegate to Fish via vp.fish because clap_complete_nushell generates + // multiple rest params (e.g. for `vp install`), which Nushell does not support. let env_nu_content = r#"# Vite+ environment setup (https://viteplus.dev) $env.PATH = ($env.PATH | where { $in != "__VP_BIN__" } | prepend "__VP_BIN__") -# Load native Nushell completions for vp (generated by clap_complete_nushell). -source "__VP_HOME__/vp.nu" - -# Intercept `vp env use` to apply environment changes to the current shell session. -# Overrides the extern defined in vp.nu for execution; vp.nu still provides completions. -def --env --wrapped "vp env use" [...args: string] { - if ("-h" in $args) or ("--help" in $args) { - ^vp env use ...$args - return - } - let out = (with-env { VP_ENV_USE_EVAL_ENABLE: "1", NU_VERSION: "1" } { - ^vp env use ...$args - }) - let exports = ($out | lines | where { $in =~ '^\$env\.' } | parse '$env.{key} = "{value}"') - if ($exports | is-not-empty) { - load-env ($exports | reduce -f {} {|it, acc| $acc | insert $it.key $it.value}) - } - let unsets = ($out | lines | where { $in =~ '^hide-env ' } | parse 'hide-env {key}' | get key) - for key in $unsets { hide-env $key } +# Shell function wrapper: intercepts `vp env use` to parse its stdout, +# which sets/unsets VP_NODE_VERSION in the current shell session. +def --env --wrapped vp [...args: string@"nu-complete vp"] { + if ($args | length) >= 2 and $args.0 == "env" and $args.1 == "use" { + if ("-h" in $args) or ("--help" in $args) { + ^vp ...$args + return + } + let out = (with-env { VP_ENV_USE_EVAL_ENABLE: "1", NU_VERSION: "1" } { + ^vp ...$args + }) + let exports = ($out | lines | where { $in =~ '^\$env\.' } | parse '$env.{key} = "{value}"') + if ($exports | is-not-empty) { + load-env ($exports | reduce -f {} {|it, acc| $acc | insert $it.key $it.value}) + } + let unsets = ($out | lines | where { $in =~ '^hide-env ' } | parse 'hide-env {key}' | get key) + for key in $unsets { hide-env $key } + } else { + ^vp ...$args + } } -export def --env --wrapped vpr [...args: string] { ^vp run ...$args } +# Shell completion for nushell (delegates to fish completions) +def "nu-complete vp" [context: string] { + let fish_comp_path = "__VP_HOME__/vp.fish" + let fish_cmd = $"source '($fish_comp_path)'; complete '--do-complete=($context)'" + fish --command $fish_cmd | from tsv --flexible --noheaders --no-infer | rename value description | update value {|row| + let value = $row.value + let need_quote = ['\' ',' '[' ']' '(' ')' ' ' '\t' "'" '"' "`"] | any {$in in $value} + if ($need_quote and ($value | path exists)) { + let expanded_path = if ($value starts-with ~) {$value | path expand --no-symlink} else {$value} + $'"($expanded_path | str replace --all "\"" "\\\"")"' + } else {$value} + } +} +export def --env --wrapped vpr [...args: string@"nu-complete vp"] { ^vp run ...$args } "# .replace("__VP_BIN__", &bin_path_ref) .replace("__VP_HOME__", &vite_plus_home.as_path().display().to_string()); @@ -733,8 +749,8 @@ mod tests { "env.nu should set VP_ENV_USE_EVAL_ENABLE" ); assert!( - nu_content.contains("vp.nu"), - "env.nu should source the native Nushell completion file" + nu_content.contains("vp.fish"), + "env.nu should reference the Fish completion file for Nushell delegation" ); } @@ -744,6 +760,8 @@ mod tests { let home = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); let _guard = home_guard(temp_dir.path()); + create_env_files(&home).await.unwrap(); + let env_content = tokio::fs::read_to_string(home.join("env")).await.unwrap(); let fish_content = tokio::fs::read_to_string(home.join("env.fish")).await.unwrap(); From c060095cbf20a16d81a78039a3ac8159b2b2396b Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Mon, 6 Apr 2026 02:48:38 +0900 Subject: [PATCH 09/22] refactor(nushell): remove direct Nushell completion handling from main function --- crates/vite_global_cli/src/main.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/crates/vite_global_cli/src/main.rs b/crates/vite_global_cli/src/main.rs index 2aff9137f3..c4549455e7 100644 --- a/crates/vite_global_cli/src/main.rs +++ b/crates/vite_global_cli/src/main.rs @@ -25,7 +25,7 @@ use std::{ use clap::error::{ContextKind, ContextValue}; use clap_complete::env::CompleteEnv; -use clap_complete_nushell::Nushell; + use owo_colors::OwoColorize; use vite_shared::output; @@ -240,13 +240,6 @@ async fn main() -> ExitCode { return ExitCode::SUCCESS; } - // Handle Nushell completion (clap_complete_nushell uses generate() directly) - if env::var_os("VP_COMPLETE").is_some_and(|shell| shell == "nushell") { - let mut cmd = command_with_help(); - clap_complete::generate(Nushell, &mut cmd, "vp", &mut std::io::stdout()); - return ExitCode::SUCCESS; - } - // Handle shell completion CompleteEnv::with_factory(command_with_help).var("VP_COMPLETE").complete(); From aa170c403476ca9f12ff5bf7be132c313bb3f3c3 Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Mon, 6 Apr 2026 02:59:57 +0900 Subject: [PATCH 10/22] chore: remove unused import of CompleteEnv from main.rs --- crates/vite_global_cli/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/vite_global_cli/src/main.rs b/crates/vite_global_cli/src/main.rs index c4549455e7..f339b037f4 100644 --- a/crates/vite_global_cli/src/main.rs +++ b/crates/vite_global_cli/src/main.rs @@ -25,7 +25,6 @@ use std::{ use clap::error::{ContextKind, ContextValue}; use clap_complete::env::CompleteEnv; - use owo_colors::OwoColorize; use vite_shared::output; From f39e459efc5c12a2d7d5c0705b4ee4f8f92268d3 Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Mon, 6 Apr 2026 03:08:52 +0900 Subject: [PATCH 11/22] fix(nushell): ensure environment mutations are applied in emission order --- .../vite_global_cli/src/commands/env/setup.rs | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/crates/vite_global_cli/src/commands/env/setup.rs b/crates/vite_global_cli/src/commands/env/setup.rs index deee7b8c57..3ce2496432 100644 --- a/crates/vite_global_cli/src/commands/env/setup.rs +++ b/crates/vite_global_cli/src/commands/env/setup.rs @@ -534,12 +534,21 @@ def --env --wrapped vp [...args: string@"nu-complete vp"] { let out = (with-env { VP_ENV_USE_EVAL_ENABLE: "1", NU_VERSION: "1" } { ^vp ...$args }) - let exports = ($out | lines | where { $in =~ '^\$env\.' } | parse '$env.{key} = "{value}"') - if ($exports | is-not-empty) { - load-env ($exports | reduce -f {} {|it, acc| $acc | insert $it.key $it.value}) + # Apply mutations in emission order so that hide-env followed by $env.KEY = "..." + # correctly results in the variable being set (not unset). + for line in ($out | lines) { + if ($line =~ '^\$env\.') { + let parsed = ($line | parse '$env.{key} = "{value}"') + if ($parsed | is-not-empty) { + load-env { ($parsed.0.key): ($parsed.0.value) } + } + } else if ($line =~ '^hide-env ') { + let parsed = ($line | parse 'hide-env {key}') + if ($parsed | is-not-empty) { + hide-env ($parsed.0.key) + } + } } - let unsets = ($out | lines | where { $in =~ '^hide-env ' } | parse 'hide-env {key}' | get key) - for key in $unsets { hide-env $key } } else { ^vp ...$args } @@ -754,6 +763,10 @@ mod tests { nu_content.contains("vp.fish"), "env.nu should reference the Fish completion file for Nushell delegation" ); + assert!( + nu_content.contains("for line in ($out | lines)"), + "env.nu should apply mutations in emission order" + ); } #[tokio::test] From 2e8fc7bcf28ae2f48b108e97ff58e405bab942ce Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Mon, 6 Apr 2026 03:23:22 +0900 Subject: [PATCH 12/22] fix(nushell): ensure environment key is checked before hiding --- crates/vite_global_cli/src/commands/env/setup.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vite_global_cli/src/commands/env/setup.rs b/crates/vite_global_cli/src/commands/env/setup.rs index 3ce2496432..500a24574d 100644 --- a/crates/vite_global_cli/src/commands/env/setup.rs +++ b/crates/vite_global_cli/src/commands/env/setup.rs @@ -544,7 +544,7 @@ def --env --wrapped vp [...args: string@"nu-complete vp"] { } } else if ($line =~ '^hide-env ') { let parsed = ($line | parse 'hide-env {key}') - if ($parsed | is-not-empty) { + if ($parsed | is-not-empty) and ($parsed.0.key in $env) { hide-env ($parsed.0.key) } } From 08a8e9de56635fa33d9c931ca59fa888095ab5ed Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Mon, 6 Apr 2026 03:40:42 +0900 Subject: [PATCH 13/22] fix(nushell): update environment variable assignment syntax in setup command --- crates/vite_global_cli/src/commands/env/setup.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vite_global_cli/src/commands/env/setup.rs b/crates/vite_global_cli/src/commands/env/setup.rs index 500a24574d..c0b51ad7fb 100644 --- a/crates/vite_global_cli/src/commands/env/setup.rs +++ b/crates/vite_global_cli/src/commands/env/setup.rs @@ -540,7 +540,7 @@ def --env --wrapped vp [...args: string@"nu-complete vp"] { if ($line =~ '^\$env\.') { let parsed = ($line | parse '$env.{key} = "{value}"') if ($parsed | is-not-empty) { - load-env { ($parsed.0.key): ($parsed.0.value) } + $env.($parsed.0.key) = $parsed.0.value } } else if ($line =~ '^hide-env ') { let parsed = ($line | parse 'hide-env {key}') From 371d6efdd10a3b94cfbeecc8a8b0093ae0217b8b Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Mon, 6 Apr 2026 03:55:18 +0900 Subject: [PATCH 14/22] feat(nushell): introduce VP_SHELL_NU for explicit Nu shell detection and update environment handling --- .../vite_global_cli/src/commands/env/setup.rs | 36 ++++++++++--------- .../vite_global_cli/src/commands/env/use.rs | 21 +++++++++-- crates/vite_shared/src/env_config.rs | 10 ++++++ crates/vite_shared/src/env_vars.rs | 7 ++++ 4 files changed, 54 insertions(+), 20 deletions(-) diff --git a/crates/vite_global_cli/src/commands/env/setup.rs b/crates/vite_global_cli/src/commands/env/setup.rs index c0b51ad7fb..d10190e9f3 100644 --- a/crates/vite_global_cli/src/commands/env/setup.rs +++ b/crates/vite_global_cli/src/commands/env/setup.rs @@ -531,23 +531,21 @@ def --env --wrapped vp [...args: string@"nu-complete vp"] { ^vp ...$args return } - let out = (with-env { VP_ENV_USE_EVAL_ENABLE: "1", NU_VERSION: "1" } { + let out = (with-env { VP_ENV_USE_EVAL_ENABLE: "1", VP_SHELL_NU: "1" } { ^vp ...$args }) - # Apply mutations in emission order so that hide-env followed by $env.KEY = "..." - # correctly results in the variable being set (not unset). - for line in ($out | lines) { - if ($line =~ '^\$env\.') { - let parsed = ($line | parse '$env.{key} = "{value}"') - if ($parsed | is-not-empty) { - $env.($parsed.0.key) = $parsed.0.value - } - } else if ($line =~ '^hide-env ') { - let parsed = ($line | parse 'hide-env {key}') - if ($parsed | is-not-empty) and ($parsed.0.key in $env) { - hide-env ($parsed.0.key) - } - } + let lines = ($out | lines) + let exports = ($lines | where { $in =~ '^\$env\.' } | parse '$env.{key} = "{value}"') + let export_keys = ($exports | get key? | default []) + # Exclude keys that also appear in exports: when vp emits `hide-env X` then + # `$env.X = "v"` (e.g. `vp env use` with no args resolving from .node-version), + # the set should win. + let unsets = ($lines | where { $in =~ '^hide-env ' } | parse 'hide-env {key}' | get key? | default [] | where { $in not-in $export_keys }) + if ($exports | is-not-empty) { + load-env ($exports | reduce -f {} {|it, acc| $acc | insert $it.key $it.value}) + } + for key in $unsets { + if ($key in $env) { hide-env $key } } } else { ^vp ...$args @@ -764,8 +762,12 @@ mod tests { "env.nu should reference the Fish completion file for Nushell delegation" ); assert!( - nu_content.contains("for line in ($out | lines)"), - "env.nu should apply mutations in emission order" + 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" ); } diff --git a/crates/vite_global_cli/src/commands/env/use.rs b/crates/vite_global_cli/src/commands/env/use.rs index 2a7893dda0..b04ec6698e 100644 --- a/crates/vite_global_cli/src/commands/env/use.rs +++ b/crates/vite_global_cli/src/commands/env/use.rs @@ -34,7 +34,7 @@ fn detect_shell() -> Shell { let config = vite_shared::EnvConfig::get(); if config.fish_version.is_some() { Shell::Fish - } else if config.nu_version.is_some() { + } else if config.vp_shell_nu { Shell::NuShell } else if cfg!(windows) && config.ps_module_path.is_some() { Shell::PowerShell @@ -202,9 +202,10 @@ mod tests { #[test] fn test_detect_shell_fish_and_nushell() { + // Fish takes priority over Nu shell signal let _guard = vite_shared::EnvConfig::test_guard(vite_shared::EnvConfig { fish_version: Some("3.7.0".into()), - nu_version: Some("0.91.0".into()), + vp_shell_nu: true, ..vite_shared::EnvConfig::for_test() }); let shell = detect_shell(); @@ -225,13 +226,27 @@ mod tests { #[test] fn test_detect_shell_nushell() { let _guard = vite_shared::EnvConfig::test_guard(vite_shared::EnvConfig { - nu_version: Some("0.91.0".into()), + 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)); + } + #[test] fn test_format_export_posix() { let result = format_export(&Shell::Posix, "20.18.0"); diff --git a/crates/vite_shared/src/env_config.rs b/crates/vite_shared/src/env_config.rs index 524c55cdaa..6818058c5b 100644 --- a/crates/vite_shared/src/env_config.rs +++ b/crates/vite_shared/src/env_config.rs @@ -127,6 +127,14 @@ pub struct EnvConfig { /// /// Env: `NU_VERSION` pub nu_version: Option, + + /// 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, } impl EnvConfig { @@ -157,6 +165,7 @@ impl EnvConfig { 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(), } } @@ -240,6 +249,7 @@ impl EnvConfig { fish_version: None, ps_module_path: None, nu_version: None, + vp_shell_nu: false, } } diff --git a/crates/vite_shared/src/env_vars.rs b/crates/vite_shared/src/env_vars.rs index 6a3ecd6b49..5618e416fc 100644 --- a/crates/vite_shared/src/env_vars.rs +++ b/crates/vite_shared/src/env_vars.rs @@ -36,6 +36,13 @@ pub const VP_DEBUG_SHIM: &str = "VP_DEBUG_SHIM"; /// Enable eval mode for `vp env use`. pub const VP_ENV_USE_EVAL_ENABLE: &str = "VP_ENV_USE_EVAL_ENABLE"; +/// Explicit signal set by the Nushell wrapper to indicate Nu shell eval context. +/// +/// Unlike `NU_VERSION` (which is inherited by child processes), this is only set +/// by the `with-env` block in `env.nu`, so it cannot cause false detection when +/// bash/zsh is launched from a Nushell session. +pub const VP_SHELL_NU: &str = "VP_SHELL_NU"; + /// Filter for update task types. pub const VITE_UPDATE_TASK_TYPES: &str = "VITE_UPDATE_TASK_TYPES"; From 6fede95734f0e4eb3737a85527e8360f554816d3 Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Mon, 6 Apr 2026 03:57:41 +0900 Subject: [PATCH 15/22] test(nushell): simplify assertion for load-env in nu_content check --- crates/vite_global_cli/src/commands/env/setup.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/vite_global_cli/src/commands/env/setup.rs b/crates/vite_global_cli/src/commands/env/setup.rs index d10190e9f3..b41ebef6bd 100644 --- a/crates/vite_global_cli/src/commands/env/setup.rs +++ b/crates/vite_global_cli/src/commands/env/setup.rs @@ -765,10 +765,7 @@ mod tests { 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" - ); + assert!(nu_content.contains("load-env"), "env.nu should use load-env to apply exports"); } #[tokio::test] From 59384050e142dead522476e113cea88c9d1a4aaf Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Mon, 6 Apr 2026 04:02:45 +0900 Subject: [PATCH 16/22] test(nushell): add windows shell detection assertion in tests --- crates/vite_global_cli/src/commands/env/use.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/vite_global_cli/src/commands/env/use.rs b/crates/vite_global_cli/src/commands/env/use.rs index b04ec6698e..27ee6dc819 100644 --- a/crates/vite_global_cli/src/commands/env/use.rs +++ b/crates/vite_global_cli/src/commands/env/use.rs @@ -245,6 +245,8 @@ mod tests { let shell = detect_shell(); #[cfg(not(windows))] assert!(matches!(shell, Shell::Posix)); + #[cfg(windows)] + let _ = shell; } #[test] From 9da10f5d728d81c83466dc964907169c950d49f6 Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Fri, 10 Apr 2026 02:23:53 +0900 Subject: [PATCH 17/22] fix(setup): update Nushell path handling in print_path_instructions --- crates/vite_global_cli/src/commands/env/setup.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/vite_global_cli/src/commands/env/setup.rs b/crates/vite_global_cli/src/commands/env/setup.rs index b41ebef6bd..b87adcccbf 100644 --- a/crates/vite_global_cli/src/commands/env/setup.rs +++ b/crates/vite_global_cli/src/commands/env/setup.rs @@ -655,14 +655,16 @@ fn print_path_instructions(bin_dir: &vite_path::AbsolutePath) { .parent() .map(|p| p.as_path().display().to_string()) .unwrap_or_else(|| bin_dir.as_path().display().to_string()); - let home_path = if let Ok(home_dir) = std::env::var("HOME") { + let (home_path, nu_home_path) = if let Ok(home_dir) = std::env::var("HOME") { if let Some(suffix) = home_path.strip_prefix(&home_dir) { - format!("$HOME{suffix}") + // POSIX/Fish use $HOME; Nushell's `source` is a parse-time keyword + // that cannot expand $HOME (a runtime env var), so use ~ instead. + (format!("$HOME{suffix}"), format!("~{suffix}")) } else { - home_path + (home_path.clone(), home_path) } } else { - home_path + (home_path.clone(), home_path) }; println!("{}", help::render_heading("Next Steps")); @@ -676,7 +678,7 @@ fn print_path_instructions(bin_dir: &vite_path::AbsolutePath) { println!(); println!(" For Nushell, add to ~/.config/nushell/config.nu:"); println!(); - println!(" source \"{home_path}/env.nu\""); + println!(" source \"{nu_home_path}/env.nu\""); println!(); println!(" For PowerShell, add to your $PROFILE:"); println!(); From e4d445d3506996f42a04df57eed192ddafd345b9 Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Fri, 10 Apr 2026 02:49:35 +0900 Subject: [PATCH 18/22] feat(nushell): enhance shell completion for vp and vpr commands --- .../vite_global_cli/src/commands/env/setup.rs | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/crates/vite_global_cli/src/commands/env/setup.rs b/crates/vite_global_cli/src/commands/env/setup.rs index b87adcccbf..79bb6e2a37 100644 --- a/crates/vite_global_cli/src/commands/env/setup.rs +++ b/crates/vite_global_cli/src/commands/env/setup.rs @@ -552,10 +552,9 @@ def --env --wrapped vp [...args: string@"nu-complete vp"] { } } -# Shell completion for nushell (delegates to fish completions) +# Shell completion for nushell (delegates to fish completions dynamically) def "nu-complete vp" [context: string] { - let fish_comp_path = "__VP_HOME__/vp.fish" - let fish_cmd = $"source '($fish_comp_path)'; complete '--do-complete=($context)'" + let fish_cmd = $"VP_COMPLETE=fish command vp | source; complete '--do-complete=($context)'" fish --command $fish_cmd | from tsv --flexible --noheaders --no-infer | rename value description | update value {|row| let value = $row.value let need_quote = ['\' ',' '[' ']' '(' ')' ' ' '\t' "'" '"' "`"] | any {$in in $value} @@ -565,10 +564,22 @@ def "nu-complete vp" [context: string] { } else {$value} } } -export def --env --wrapped vpr [...args: string@"nu-complete vp"] { ^vp run ...$args } +# Completion logic for vpr (translates context to 'vp run ...') +def "nu-complete vpr" [context: string] { + let modified_context = ($context | str replace -r '^vpr' 'vp run') + let fish_cmd = $"VP_COMPLETE=fish command vp | source; complete '--do-complete=($modified_context)'" + fish --command $fish_cmd | from tsv --flexible --noheaders --no-infer | rename value description | update value {|row| + let value = $row.value + let need_quote = ['\' ',' '[' ']' '(' ')' ' ' '\t' "'" '"' "`"] | any {$in in $value} + if ($need_quote and ($value | path exists)) { + let expanded_path = if ($value starts-with ~) {$value | path expand --no-symlink} else {$value} + $'"($expanded_path | str replace --all "\"" "\\\"")"' + } else {$value} + } +} +export extern "vpr" [...args: string@"nu-complete vpr"] "# - .replace("__VP_BIN__", &bin_path_ref) - .replace("__VP_HOME__", &vite_plus_home.as_path().display().to_string()); + .replace("__VP_BIN__", &bin_path_ref); let env_nu_file = vite_plus_home.join("env.nu"); tokio::fs::write(&env_nu_file, env_nu_content).await?; From d834f63dd3d1ca07f46bad9b9dc4b78d86e799c0 Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Fri, 10 Apr 2026 02:54:33 +0900 Subject: [PATCH 19/22] feat(nushell): improve Fish completion delegation for Nushell integration --- crates/vite_global_cli/src/commands/env/setup.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/vite_global_cli/src/commands/env/setup.rs b/crates/vite_global_cli/src/commands/env/setup.rs index 79bb6e2a37..f57e68726b 100644 --- a/crates/vite_global_cli/src/commands/env/setup.rs +++ b/crates/vite_global_cli/src/commands/env/setup.rs @@ -46,8 +46,8 @@ pub async fn execute(refresh: bool, env_only: bool) -> Result // Create env files with PATH guard (prevents duplicate PATH entries) create_env_files(&vite_plus_home).await?; - // Generate Fish completion file at ~/.vite-plus/vp.fish for use by the Nushell completer. - // Fish completions are generated statically so that Nushell can delegate to Fish at runtime. + // Generate Fish completion file at ~/.vite-plus/vp.fish for Fish shell users. + // Nushell completions delegate to Fish dynamically via VP_COMPLETE=fish at runtime. // command_with_help() uses a deep call stack, so we spawn a thread with a larger stack. let fish_completions_path = vite_plus_home.join("vp.fish").as_path().to_path_buf(); std::thread::Builder::new() @@ -518,8 +518,8 @@ complete -c vpr --keep-order --exclusive --arguments "(__vpr_complete)" tokio::fs::write(&env_fish_file, env_fish_content).await?; // Nushell env file with vp wrapper function. - // Completions delegate to Fish via vp.fish because clap_complete_nushell generates - // multiple rest params (e.g. for `vp install`), which Nushell does not support. + // Completions delegate to Fish dynamically (VP_COMPLETE=fish) because clap_complete_nushell + // generates multiple rest params (e.g. for `vp install`), which Nushell does not support. let env_nu_content = r#"# Vite+ environment setup (https://viteplus.dev) $env.PATH = ($env.PATH | where { $in != "__VP_BIN__" } | prepend "__VP_BIN__") @@ -771,8 +771,8 @@ mod tests { "env.nu should set VP_ENV_USE_EVAL_ENABLE" ); assert!( - nu_content.contains("vp.fish"), - "env.nu should reference the Fish completion file for Nushell delegation" + nu_content.contains("VP_COMPLETE=fish"), + "env.nu should use dynamic Fish completion delegation" ); assert!( nu_content.contains("VP_SHELL_NU"), From 6178f08829c52963c5adf53f06eaa2db605a6f5b Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Fri, 10 Apr 2026 09:49:39 +0900 Subject: [PATCH 20/22] refactor(nushell): remove Fish completion generation from setup command --- .../vite_global_cli/src/commands/env/setup.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/crates/vite_global_cli/src/commands/env/setup.rs b/crates/vite_global_cli/src/commands/env/setup.rs index f57e68726b..ea67b85e14 100644 --- a/crates/vite_global_cli/src/commands/env/setup.rs +++ b/crates/vite_global_cli/src/commands/env/setup.rs @@ -46,23 +46,6 @@ pub async fn execute(refresh: bool, env_only: bool) -> Result // Create env files with PATH guard (prevents duplicate PATH entries) create_env_files(&vite_plus_home).await?; - // Generate Fish completion file at ~/.vite-plus/vp.fish for Fish shell users. - // Nushell completions delegate to Fish dynamically via VP_COMPLETE=fish at runtime. - // command_with_help() uses a deep call stack, so we spawn a thread with a larger stack. - let fish_completions_path = vite_plus_home.join("vp.fish").as_path().to_path_buf(); - std::thread::Builder::new() - .stack_size(8 * 1024 * 1024) - .spawn(move || -> std::io::Result<()> { - let mut file = std::fs::File::create(&fish_completions_path)?; - let mut cmd = crate::cli::command_with_help(); - clap_complete::generate(clap_complete::Shell::Fish, &mut cmd, "vp", &mut file); - Ok(()) - }) - .map_err(Error::CommandExecution)? - .join() - .map_err(|_| Error::Other("Fish completion generation failed".into()))? - .map_err(Error::CommandExecution)?; - if env_only { println!("{}", help::render_heading("Setup")); println!(" Updated shell environment files."); From b8693b932d41214107d4c4132593a1f4aa31a736 Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:00:24 +0900 Subject: [PATCH 21/22] fix(nushell): update PATH handling for Nushell to use `~` instead of `$HOME` --- crates/vite_global_cli/src/commands/env/setup.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/vite_global_cli/src/commands/env/setup.rs b/crates/vite_global_cli/src/commands/env/setup.rs index ea67b85e14..c58c69ea4c 100644 --- a/crates/vite_global_cli/src/commands/env/setup.rs +++ b/crates/vite_global_cli/src/commands/env/setup.rs @@ -408,6 +408,9 @@ async fn create_env_files(vite_plus_home: &vite_path::AbsolutePath) -> Result<() .unwrap_or_else(|| path.as_path().display().to_string()) }; let bin_path_ref = to_ref(&bin_path); + // Nushell requires `~` instead of `$HOME` in string literals — `$HOME` is not expanded + // at parse time, so PATH entries would contain a literal "$HOME/..." segment. + let bin_path_ref_nu = bin_path_ref.replace("$HOME/", "~/"); // POSIX env file (bash/zsh) // When sourced multiple times, removes existing entry and re-prepends to front @@ -562,7 +565,7 @@ def "nu-complete vpr" [context: string] { } export extern "vpr" [...args: string@"nu-complete vpr"] "# - .replace("__VP_BIN__", &bin_path_ref); + .replace("__VP_BIN__", &bin_path_ref_nu); let env_nu_file = vite_plus_home.join("env.nu"); tokio::fs::write(&env_nu_file, env_nu_content).await?; @@ -748,7 +751,7 @@ mod tests { !nu_content.contains("__VP_BIN__"), "env.nu should not contain __VP_BIN__ placeholder" ); - assert!(nu_content.contains("$HOME/bin"), "env.nu should reference $HOME/bin"); + assert!(nu_content.contains("~/bin"), "env.nu should reference ~/bin (not $HOME/bin — Nushell does not expand $HOME in string literals)"); assert!( nu_content.contains("VP_ENV_USE_EVAL_ENABLE"), "env.nu should set VP_ENV_USE_EVAL_ENABLE" From ae84b6b55e7c7f0ecc8bcde13155a0671c4cbbf6 Mon Sep 17 00:00:00 2001 From: naokihaba <59875779+naokihaba@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:11:32 +0900 Subject: [PATCH 22/22] style(tests): format assertion for Nushell path reference in env.nu --- crates/vite_global_cli/src/commands/env/setup.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/vite_global_cli/src/commands/env/setup.rs b/crates/vite_global_cli/src/commands/env/setup.rs index c58c69ea4c..1e5b7b5aed 100644 --- a/crates/vite_global_cli/src/commands/env/setup.rs +++ b/crates/vite_global_cli/src/commands/env/setup.rs @@ -751,7 +751,10 @@ mod tests { !nu_content.contains("__VP_BIN__"), "env.nu should not contain __VP_BIN__ placeholder" ); - assert!(nu_content.contains("~/bin"), "env.nu should reference ~/bin (not $HOME/bin — Nushell does not expand $HOME in string literals)"); + assert!( + nu_content.contains("~/bin"), + "env.nu should reference ~/bin (not $HOME/bin — Nushell does not expand $HOME in string literals)" + ); assert!( nu_content.contains("VP_ENV_USE_EVAL_ENABLE"), "env.nu should set VP_ENV_USE_EVAL_ENABLE"