From 77f93c84e7912848447d3f9897d11e34773f8c5c Mon Sep 17 00:00:00 2001 From: mst-mkt Date: Sun, 15 Mar 2026 01:25:55 +0900 Subject: [PATCH 01/11] fix(cli): respect ZDOTDIR for zsh config paths in install.sh --- packages/cli/install.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/cli/install.sh b/packages/cli/install.sh index 8155a0379e..b742d911dc 100644 --- a/packages/cli/install.sh +++ b/packages/cli/install.sh @@ -350,10 +350,11 @@ configure_shell_path() { # Add to both .zshenv (for all shells including IDE) and .zshrc (to ensure PATH is at front) # Create .zshenv if missing — it's the canonical place for PATH in zsh # and is sourced by all session types (interactive, non-interactive, IDE) - [ -f "$HOME/.zshenv" ] || touch "$HOME/.zshenv" + local zsh_dir="${ZDOTDIR:-$HOME}" + [ -f "$zsh_dir/.zshenv" ] || touch "$zsh_dir/.zshenv" local zshenv_result=0 zshrc_result=0 - add_bin_to_path "$HOME/.zshenv" || zshenv_result=$? - add_bin_to_path "$HOME/.zshrc" || zshrc_result=$? + add_bin_to_path "$zsh_dir/.zshenv" || zshenv_result=$? + add_bin_to_path "$zsh_dir/.zshrc" || zshrc_result=$? # Prioritize .zshrc for user notification (easier to source) if [ $zshrc_result -eq 0 ]; then result=0 From d2f01f0cffaad1fc595486edd40ec5cffb054400 Mon Sep 17 00:00:00 2001 From: mst-mkt Date: Sun, 15 Mar 2026 01:32:32 +0900 Subject: [PATCH 02/11] fix(cli): respect XDG_CONFIG_HOME for fish config path in install.sh --- packages/cli/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/install.sh b/packages/cli/install.sh index b742d911dc..0b1f50431d 100644 --- a/packages/cli/install.sh +++ b/packages/cli/install.sh @@ -390,7 +390,7 @@ configure_shell_path() { fi ;; */fish) - local fish_dir="$HOME/.config/fish/conf.d" + local fish_dir="${XDG_CONFIG_HOME:-$HOME/.config}/fish/conf.d" local fish_config="$fish_dir/vite-plus.fish" if [ -f "$fish_config" ]; then result=2 From 18ebfb6e591ab2342ae1469c1fda1d8d3d820565 Mon Sep 17 00:00:00 2001 From: mst-mkt Date: Sun, 15 Mar 2026 01:42:02 +0900 Subject: [PATCH 03/11] refactor(cli): use absolute paths for SHELL_CONFIG_UPDATED and fix display --- packages/cli/install.sh | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/cli/install.sh b/packages/cli/install.sh index 0b1f50431d..d3175665b9 100644 --- a/packages/cli/install.sh +++ b/packages/cli/install.sh @@ -358,10 +358,10 @@ configure_shell_path() { # Prioritize .zshrc for user notification (easier to source) if [ $zshrc_result -eq 0 ]; then result=0 - SHELL_CONFIG_UPDATED=".zshrc" + SHELL_CONFIG_UPDATED="$zsh_dir/.zshrc" elif [ $zshenv_result -eq 0 ]; then result=0 - SHELL_CONFIG_UPDATED=".zshenv" + SHELL_CONFIG_UPDATED="$zsh_dir/.zshenv" elif [ $zshenv_result -eq 2 ] || [ $zshrc_result -eq 2 ]; then result=2 # already configured in at least one file fi @@ -378,13 +378,13 @@ configure_shell_path() { # Prioritize .bashrc for user notification (most commonly edited) if [ $bashrc_result -eq 0 ]; then result=0 - SHELL_CONFIG_UPDATED=".bashrc" + SHELL_CONFIG_UPDATED="$HOME/.bashrc" elif [ $bash_profile_result -eq 0 ]; then result=0 - SHELL_CONFIG_UPDATED=".bash_profile" + SHELL_CONFIG_UPDATED="$HOME/.bash_profile" elif [ $profile_result -eq 0 ]; then result=0 - SHELL_CONFIG_UPDATED=".profile" + SHELL_CONFIG_UPDATED="$HOME/.profile" elif [ $bash_profile_result -eq 2 ] || [ $bashrc_result -eq 2 ] || [ $profile_result -eq 2 ]; then result=2 # already configured in at least one file fi @@ -399,7 +399,7 @@ configure_shell_path() { echo "# Vite+ bin (https://viteplus.dev)" >> "$fish_config" echo "source \"$INSTALL_DIR_REF/env.fish\"" >> "$fish_config" result=0 - SHELL_CONFIG_UPDATED=".config/fish/conf.d/vite-plus.fish" + SHELL_CONFIG_UPDATED="$fish_config" fi ;; esac @@ -651,8 +651,9 @@ NPMRC_EOF # Show restart note if PATH was added to shell config if [ "$PATH_CONFIGURED" = "true" ] && [ -n "$SHELL_CONFIG_UPDATED" ]; then + local display_config="~${SHELL_CONFIG_UPDATED#"$HOME"}" echo "" - echo " Note: Run \`source ~/$SHELL_CONFIG_UPDATED\` or restart your terminal." + echo " Note: Run \`source $display_config\` or restart your terminal." fi # Show warning if PATH could not be automatically configured From dc19288af2d7e8ac8adda027d9632bdf9b55dedc Mon Sep 17 00:00:00 2001 From: mst-mkt Date: Sun, 15 Mar 2026 06:11:25 +0900 Subject: [PATCH 04/11] fix(cli): respect ZDOTDIR and XDG_CONFIG_HOME in doctor and implode --- .../src/commands/env/doctor.rs | 119 +++++++++++ .../vite_global_cli/src/commands/implode.rs | 185 ++++++++++++++++-- 2 files changed, 288 insertions(+), 16 deletions(-) diff --git a/crates/vite_global_cli/src/commands/env/doctor.rs b/crates/vite_global_cli/src/commands/env/doctor.rs index 610a277922..fdc76a497f 100644 --- a/crates/vite_global_cli/src/commands/env/doctor.rs +++ b/crates/vite_global_cli/src/commands/env/doctor.rs @@ -476,6 +476,33 @@ fn check_profile_files(vite_plus_home: &str) -> Option { } } + // If ZDOTDIR is set and differs from $HOME, also check $ZDOTDIR/.zshenv and .zshrc + if let Ok(zdotdir) = std::env::var("ZDOTDIR") { + if !zdotdir.is_empty() && zdotdir != home_dir { + for file in [".zshenv", ".zshrc"] { + let path = format!("{zdotdir}/{file}"); + if let Ok(content) = std::fs::read_to_string(&path) { + if search_strings.iter().any(|s| content.contains(s)) { + return Some(abbreviate_home(&path)); + } + } + } + } + } + + // If XDG_CONFIG_HOME is set and differs from default, also check fish conf.d + if let Ok(xdg_config) = std::env::var("XDG_CONFIG_HOME") { + let default_config = format!("{home_dir}/.config"); + if !xdg_config.is_empty() && xdg_config != default_config { + let path = format!("{xdg_config}/fish/conf.d/vite-plus.fish"); + if let Ok(content) = std::fs::read_to_string(&path) { + if search_strings.iter().any(|s| content.contains(s)) { + return Some(abbreviate_home(&path)); + } + } + } + } + None } @@ -766,4 +793,96 @@ mod tests { assert_eq!(abbreviate_home("/usr/local/bin"), "/usr/local/bin"); } } + + /// Guard for env vars used by profile file tests. + struct ProfileEnvGuard { + original_home: Option, + original_zdotdir: Option, + original_xdg_config: Option, + } + + impl ProfileEnvGuard { + fn new( + home: &std::path::Path, + zdotdir: Option<&std::path::Path>, + xdg_config: Option<&std::path::Path>, + ) -> Self { + let guard = Self { + original_home: std::env::var_os("HOME"), + original_zdotdir: std::env::var_os("ZDOTDIR"), + original_xdg_config: std::env::var_os("XDG_CONFIG_HOME"), + }; + unsafe { + std::env::set_var("HOME", home); + match zdotdir { + Some(v) => std::env::set_var("ZDOTDIR", v), + None => std::env::remove_var("ZDOTDIR"), + } + match xdg_config { + Some(v) => std::env::set_var("XDG_CONFIG_HOME", v), + None => std::env::remove_var("XDG_CONFIG_HOME"), + } + } + guard + } + } + + impl Drop for ProfileEnvGuard { + fn drop(&mut self) { + unsafe { + match &self.original_home { + Some(v) => std::env::set_var("HOME", v), + None => std::env::remove_var("HOME"), + } + match &self.original_zdotdir { + Some(v) => std::env::set_var("ZDOTDIR", v), + None => std::env::remove_var("ZDOTDIR"), + } + match &self.original_xdg_config { + Some(v) => std::env::set_var("XDG_CONFIG_HOME", v), + None => std::env::remove_var("XDG_CONFIG_HOME"), + } + } + } + } + + #[test] + #[serial] + #[cfg(not(windows))] + fn test_check_profile_files_finds_zdotdir() { + let temp = TempDir::new().unwrap(); + let fake_home = temp.path().join("home"); + let zdotdir = temp.path().join("zdotdir"); + std::fs::create_dir_all(&fake_home).unwrap(); + std::fs::create_dir_all(&zdotdir).unwrap(); + + std::fs::write(zdotdir.join(".zshenv"), ". \"$HOME/.vite-plus/env\"\n").unwrap(); + + let _guard = ProfileEnvGuard::new(&fake_home, Some(&zdotdir), None); + + let result = check_profile_files("$HOME/.vite-plus"); + assert!(result.is_some(), "Should find .zshenv in ZDOTDIR"); + assert!(result.unwrap().ends_with(".zshenv")); + } + + #[test] + #[serial] + #[cfg(not(windows))] + fn test_check_profile_files_finds_xdg_fish() { + let temp = TempDir::new().unwrap(); + let fake_home = temp.path().join("home"); + let xdg_config = temp.path().join("xdg_config"); + let fish_dir = xdg_config.join("fish/conf.d"); + std::fs::create_dir_all(&fake_home).unwrap(); + std::fs::create_dir_all(&fish_dir).unwrap(); + + std::fs::write(fish_dir.join("vite-plus.fish"), "source \"$HOME/.vite-plus/env\"\n") + .unwrap(); + + let _guard = ProfileEnvGuard::new(&fake_home, None, Some(&xdg_config)); + + let result = check_profile_files("$HOME/.vite-plus"); + assert!(result.is_some(), "Should find vite-plus.fish in XDG_CONFIG_HOME"); + assert!(result.unwrap().contains("vite-plus.fish")); + } } diff --git a/crates/vite_global_cli/src/commands/implode.rs b/crates/vite_global_cli/src/commands/implode.rs index fc5a91ff13..6eca5b99d8 100644 --- a/crates/vite_global_cli/src/commands/implode.rs +++ b/crates/vite_global_cli/src/commands/implode.rs @@ -7,7 +7,7 @@ use std::{ use directories::BaseDirs; use owo_colors::OwoColorize; -use vite_path::AbsolutePathBuf; +use vite_path::{AbsolutePath, AbsolutePathBuf}; use vite_shared::output; use vite_str::Str; @@ -23,6 +23,14 @@ const SHELL_PROFILES: &[(&str, bool)] = &[ (".config/fish/conf.d/vite-plus.fish", true), ]; +/// Abbreviate a path for display: replace `$HOME` prefix with `~`. +fn abbreviate_home_path(path: &AbsolutePath, user_home: &AbsolutePath) -> Str { + match path.strip_prefix(user_home) { + Ok(Some(suffix)) => vite_str::format!("~/{suffix}"), + _ => Str::from(path.to_string()), + } +} + /// Comment marker written by the install script above the sourcing line. const VITE_PLUS_COMMENT: &str = "# Vite+ bin"; @@ -97,26 +105,52 @@ enum AffectedProfileKind { /// Content is cached so we don't need to re-read during cleaning. fn collect_affected_profiles(user_home: &AbsolutePathBuf) -> Vec { let mut affected = Vec::new(); - for &(name, is_snippet) in SHELL_PROFILES { - let path = user_home.join(name); + + // Build full list of (display_name, path, is_snippet) from the base set + let mut profiles: Vec<(Str, AbsolutePathBuf, bool)> = SHELL_PROFILES + .iter() + .map(|&(name, is_snippet)| { + (vite_str::format!("~/{name}"), user_home.join(name), is_snippet) + }) + .collect(); + + // If ZDOTDIR is set and differs from $HOME, also check there. + if let Ok(zdotdir) = std::env::var("ZDOTDIR") + && let Some(zdotdir_path) = AbsolutePathBuf::new(zdotdir.into()) + && zdotdir_path != *user_home + { + for name in [".zshenv", ".zshrc"] { + let path = zdotdir_path.join(name); + let display = abbreviate_home_path(&path, user_home); + profiles.push((display, path, false)); + } + } + + // If XDG_CONFIG_HOME is set and differs from $HOME/.config, also check there. + if let Ok(xdg_config) = std::env::var("XDG_CONFIG_HOME") + && let Some(xdg_path) = AbsolutePathBuf::new(xdg_config.into()) + && xdg_path != user_home.join(".config") + { + let path = xdg_path.join("fish/conf.d/vite-plus.fish"); + let display = abbreviate_home_path(&path, user_home); + profiles.push((display, path, true)); + } + + for (name, path, is_snippet) in profiles { // For snippets, check if the file exists only if is_snippet { - if let Some(true) = std::fs::exists(&path).ok() { - affected.push(AffectedProfile { - name: Str::from(name), - path, - kind: AffectedProfileKind::Snippet, - }) + if let Ok(true) = std::fs::exists(&path) { + affected.push(AffectedProfile { name, path, kind: AffectedProfileKind::Snippet }) } continue; } // Read directly — if the file doesn't exist, read_to_string returns Err - // which is_ok_and handles gracefully (no redundant exists() check). + // which .ok().filter() handles gracefully (no redundant exists() check). if let Some(content) = std::fs::read_to_string(&path).ok().filter(|c| has_vite_plus_lines(c)) { affected.push(AffectedProfile { - name: Str::from(name), + name, path, kind: AffectedProfileKind::Main { content: Str::from(content) }, }); @@ -144,7 +178,7 @@ fn confirm_implode( if !affected_profiles.is_empty() { output::raw(" Shell profiles to clean:"); for profile in affected_profiles { - output::raw(&vite_str::format!(" - ~/{}", profile.name)); + output::raw(&vite_str::format!(" - {}", profile.name)); } } output::raw(""); @@ -171,16 +205,16 @@ fn clean_affected_profiles(affected_profiles: &[AffectedProfile]) { AffectedProfileKind::Main { content } => { let cleaned = remove_vite_plus_lines(content); match std::fs::write(&profile.path, cleaned.as_bytes()) { - Ok(()) => output::success(&vite_str::format!("Cleaned ~/{}", profile.name)), + Ok(()) => output::success(&vite_str::format!("Cleaned {}", profile.name)), Err(e) => { - output::warn(&vite_str::format!("Failed to clean ~/{}: {e}", profile.name)); + output::warn(&vite_str::format!("Failed to clean {}: {e}", profile.name)); } } } AffectedProfileKind::Snippet => match std::fs::remove_file(&profile.path) { - Ok(()) => output::success(&vite_str::format!("Removed ~/{}", profile.name)), + Ok(()) => output::success(&vite_str::format!("Removed {}", profile.name)), Err(e) => { - output::warn(&vite_str::format!("Failed to remove ~/{}: {e}", profile.name)); + output::warn(&vite_str::format!("Failed to remove {}: {e}", profile.name)); } }, } @@ -336,6 +370,8 @@ fn remove_windows_path_entry(bin_path: &vite_path::AbsolutePath) -> std::io::Res #[cfg(test)] mod tests { + use serial_test::serial; + use super::*; #[test] @@ -420,6 +456,123 @@ mod tests { assert!(script.contains("timeout /T 1 /NOBREAK")); } + #[test] + fn test_abbreviate_home_path() { + let home = AbsolutePathBuf::new("/home/user".into()).unwrap(); + // Under home → ~/... + let under = AbsolutePathBuf::new("/home/user/.zshrc".into()).unwrap(); + assert_eq!(&*abbreviate_home_path(&under, &home), "~/.zshrc"); + // Outside home → absolute path as-is + let outside = AbsolutePathBuf::new("/opt/zdotdir/.zshenv".into()).unwrap(); + assert_eq!(&*abbreviate_home_path(&outside, &home), "/opt/zdotdir/.zshenv"); + } + + #[test] + #[serial] + fn test_collect_affected_profiles() { + let temp_dir = tempfile::tempdir().unwrap(); + let home = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + + // Clear ZDOTDIR/XDG_CONFIG_HOME so the test environment doesn't affect results + let _guard = ProfileEnvGuard::new(None, None); + + // Main profile with vite-plus line + std::fs::write(home.join(".zshrc"), ". \"$HOME/.vite-plus/env\"\n").unwrap(); + // Unrelated profile (should be ignored) + std::fs::write(home.join(".bashrc"), "export PATH=/usr/bin\n").unwrap(); + // Snippet file (just needs to exist) + let fish_dir = home.join(".config/fish/conf.d"); + std::fs::create_dir_all(&fish_dir).unwrap(); + std::fs::write(fish_dir.join("vite-plus.fish"), "source ~/.vite-plus/env.fish\n").unwrap(); + + let profiles = collect_affected_profiles(&home); + assert_eq!(profiles.len(), 2); + assert!(matches!(&profiles[0].kind, AffectedProfileKind::Main { .. })); + assert!(matches!(&profiles[1].kind, AffectedProfileKind::Snippet)); + } + + /// Guard that saves and restores ZDOTDIR and XDG_CONFIG_HOME env vars. + struct ProfileEnvGuard { + original_zdotdir: Option, + original_xdg_config: Option, + } + + impl ProfileEnvGuard { + fn new(zdotdir: Option<&std::path::Path>, xdg_config: Option<&std::path::Path>) -> Self { + let guard = Self { + original_zdotdir: std::env::var_os("ZDOTDIR"), + original_xdg_config: std::env::var_os("XDG_CONFIG_HOME"), + }; + unsafe { + match zdotdir { + Some(v) => std::env::set_var("ZDOTDIR", v), + None => std::env::remove_var("ZDOTDIR"), + } + match xdg_config { + Some(v) => std::env::set_var("XDG_CONFIG_HOME", v), + None => std::env::remove_var("XDG_CONFIG_HOME"), + } + } + guard + } + } + + impl Drop for ProfileEnvGuard { + fn drop(&mut self) { + unsafe { + match &self.original_zdotdir { + Some(v) => std::env::set_var("ZDOTDIR", v), + None => std::env::remove_var("ZDOTDIR"), + } + match &self.original_xdg_config { + Some(v) => std::env::set_var("XDG_CONFIG_HOME", v), + None => std::env::remove_var("XDG_CONFIG_HOME"), + } + } + } + } + + #[test] + #[serial] + fn test_collect_affected_profiles_zdotdir() { + let temp_dir = tempfile::tempdir().unwrap(); + let home = AbsolutePathBuf::new(temp_dir.path().join("home")).unwrap(); + let zdotdir = temp_dir.path().join("zdotdir"); + std::fs::create_dir_all(&home).unwrap(); + std::fs::create_dir_all(&zdotdir).unwrap(); + + std::fs::write(zdotdir.join(".zshenv"), ". \"$HOME/.vite-plus/env\"\n").unwrap(); + + let _guard = ProfileEnvGuard::new(Some(&zdotdir), None); + + let profiles = collect_affected_profiles(&home); + let zdotdir_profiles: Vec<_> = + profiles.iter().filter(|p| p.path.as_path().starts_with(&zdotdir)).collect(); + assert_eq!(zdotdir_profiles.len(), 1); + assert!(matches!(&zdotdir_profiles[0].kind, AffectedProfileKind::Main { .. })); + } + + #[test] + #[serial] + fn test_collect_affected_profiles_xdg_config() { + let temp_dir = tempfile::tempdir().unwrap(); + let home = AbsolutePathBuf::new(temp_dir.path().join("home")).unwrap(); + let xdg_config = temp_dir.path().join("xdg_config"); + let fish_dir = xdg_config.join("fish/conf.d"); + std::fs::create_dir_all(&home).unwrap(); + std::fs::create_dir_all(&fish_dir).unwrap(); + + std::fs::write(fish_dir.join("vite-plus.fish"), "").unwrap(); + + let _guard = ProfileEnvGuard::new(None, Some(&xdg_config)); + + let profiles = collect_affected_profiles(&home); + let xdg_profiles: Vec<_> = + profiles.iter().filter(|p| p.path.as_path().starts_with(&xdg_config)).collect(); + assert_eq!(xdg_profiles.len(), 1); + assert!(matches!(&xdg_profiles[0].kind, AffectedProfileKind::Snippet)); + } + #[test] fn test_execute_not_installed() { let temp_dir = tempfile::tempdir().unwrap(); From 0c21137ba4ccd7c2920f5f18da3e875d814501e9 Mon Sep 17 00:00:00 2001 From: mst-mkt Date: Sun, 15 Mar 2026 07:10:54 +0900 Subject: [PATCH 05/11] fix(cli): handle display path when shell config is outside `$HOME` --- packages/cli/install.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/cli/install.sh b/packages/cli/install.sh index d3175665b9..8a3d4cf5f4 100644 --- a/packages/cli/install.sh +++ b/packages/cli/install.sh @@ -651,7 +651,12 @@ NPMRC_EOF # Show restart note if PATH was added to shell config if [ "$PATH_CONFIGURED" = "true" ] && [ -n "$SHELL_CONFIG_UPDATED" ]; then - local display_config="~${SHELL_CONFIG_UPDATED#"$HOME"}" + local display_config + if [ "${SHELL_CONFIG_UPDATED#"$HOME"}" != "$SHELL_CONFIG_UPDATED" ]; then + display_config="~${SHELL_CONFIG_UPDATED#"$HOME"}" + else + display_config="$SHELL_CONFIG_UPDATED" + fi echo "" echo " Note: Run \`source $display_config\` or restart your terminal." fi From 80123f43a7976835cc02df9bbe0964afc09ab73d Mon Sep 17 00:00:00 2001 From: mst-mkt Date: Sun, 15 Mar 2026 16:13:16 +0900 Subject: [PATCH 06/11] fix(cli): gate `ProfileEnvGuard` behind `cfg(not(windows))` to fix Windows CI --- crates/vite_global_cli/src/commands/env/doctor.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/vite_global_cli/src/commands/env/doctor.rs b/crates/vite_global_cli/src/commands/env/doctor.rs index fdc76a497f..e76697b41d 100644 --- a/crates/vite_global_cli/src/commands/env/doctor.rs +++ b/crates/vite_global_cli/src/commands/env/doctor.rs @@ -795,12 +795,14 @@ mod tests { } /// Guard for env vars used by profile file tests. + #[cfg(not(windows))] struct ProfileEnvGuard { original_home: Option, original_zdotdir: Option, original_xdg_config: Option, } + #[cfg(not(windows))] impl ProfileEnvGuard { fn new( home: &std::path::Path, @@ -827,6 +829,7 @@ mod tests { } } + #[cfg(not(windows))] impl Drop for ProfileEnvGuard { fn drop(&mut self) { unsafe { From ca35073cb0433096bb1eada15b73782bbade83b0 Mon Sep 17 00:00:00 2001 From: mst-mkt Date: Mon, 16 Mar 2026 03:51:02 +0900 Subject: [PATCH 07/11] fix(cli): gate `ProfileEnvGuard` and implode tests behind `cfg(not(windows))` --- crates/vite_global_cli/src/commands/implode.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/vite_global_cli/src/commands/implode.rs b/crates/vite_global_cli/src/commands/implode.rs index 6eca5b99d8..e21d1d6bf6 100644 --- a/crates/vite_global_cli/src/commands/implode.rs +++ b/crates/vite_global_cli/src/commands/implode.rs @@ -457,6 +457,7 @@ mod tests { } #[test] + #[cfg(not(windows))] fn test_abbreviate_home_path() { let home = AbsolutePathBuf::new("/home/user".into()).unwrap(); // Under home → ~/... @@ -469,6 +470,7 @@ mod tests { #[test] #[serial] + #[cfg(not(windows))] fn test_collect_affected_profiles() { let temp_dir = tempfile::tempdir().unwrap(); let home = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); @@ -492,11 +494,13 @@ mod tests { } /// Guard that saves and restores ZDOTDIR and XDG_CONFIG_HOME env vars. + #[cfg(not(windows))] struct ProfileEnvGuard { original_zdotdir: Option, original_xdg_config: Option, } + #[cfg(not(windows))] impl ProfileEnvGuard { fn new(zdotdir: Option<&std::path::Path>, xdg_config: Option<&std::path::Path>) -> Self { let guard = Self { @@ -517,6 +521,7 @@ mod tests { } } + #[cfg(not(windows))] impl Drop for ProfileEnvGuard { fn drop(&mut self) { unsafe { @@ -534,6 +539,7 @@ mod tests { #[test] #[serial] + #[cfg(not(windows))] fn test_collect_affected_profiles_zdotdir() { let temp_dir = tempfile::tempdir().unwrap(); let home = AbsolutePathBuf::new(temp_dir.path().join("home")).unwrap(); @@ -554,6 +560,7 @@ mod tests { #[test] #[serial] + #[cfg(not(windows))] fn test_collect_affected_profiles_xdg_config() { let temp_dir = tempfile::tempdir().unwrap(); let home = AbsolutePathBuf::new(temp_dir.path().join("home")).unwrap(); From adc35021a59d8fa2ddc3544ceb344db23d71c9fa Mon Sep 17 00:00:00 2001 From: mst-mkt Date: Mon, 16 Mar 2026 04:18:01 +0900 Subject: [PATCH 08/11] fix(cli): ensure ZDOTDIR exists before touching .zshenv in install.sh Co-Authored-By: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/cli/install.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/install.sh b/packages/cli/install.sh index 8a3d4cf5f4..5dd6f842d0 100644 --- a/packages/cli/install.sh +++ b/packages/cli/install.sh @@ -351,6 +351,7 @@ configure_shell_path() { # Create .zshenv if missing — it's the canonical place for PATH in zsh # and is sourced by all session types (interactive, non-interactive, IDE) local zsh_dir="${ZDOTDIR:-$HOME}" + mkdir -p "$zsh_dir" [ -f "$zsh_dir/.zshenv" ] || touch "$zsh_dir/.zshenv" local zshenv_result=0 zshrc_result=0 add_bin_to_path "$zsh_dir/.zshenv" || zshenv_result=$? From d382e7f7cad36734a3f1c7d8a6c11f871cc8ebb8 Mon Sep 17 00:00:00 2001 From: mst-mkt Date: Mon, 16 Mar 2026 04:52:34 +0900 Subject: [PATCH 09/11] fix(cli): quote display path in shell restart hint for paths with spaces Co-Authored-By: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/cli/install.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/cli/install.sh b/packages/cli/install.sh index 5dd6f842d0..8d4fd05fac 100644 --- a/packages/cli/install.sh +++ b/packages/cli/install.sh @@ -659,7 +659,11 @@ NPMRC_EOF display_config="$SHELL_CONFIG_UPDATED" fi echo "" - echo " Note: Run \`source $display_config\` or restart your terminal." + if [ "${display_config#"~"}" != "$display_config" ]; then + echo " Note: Run \`source $display_config\` or restart your terminal." + else + echo " Note: Run \`source \"$display_config\"\` or restart your terminal." + fi fi # Show warning if PATH could not be automatically configured From 6e79a605e1200c04e931fae9a34514d888153a24 Mon Sep 17 00:00:00 2001 From: mst-mkt Date: Mon, 16 Mar 2026 04:52:44 +0900 Subject: [PATCH 10/11] test(cli): use env.fish in XDG fish test to match install.sh output Co-Authored-By: Copilot <175728472+Copilot@users.noreply.github.com> --- crates/vite_global_cli/src/commands/env/doctor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vite_global_cli/src/commands/env/doctor.rs b/crates/vite_global_cli/src/commands/env/doctor.rs index e76697b41d..2921f5a766 100644 --- a/crates/vite_global_cli/src/commands/env/doctor.rs +++ b/crates/vite_global_cli/src/commands/env/doctor.rs @@ -879,7 +879,7 @@ mod tests { std::fs::create_dir_all(&fake_home).unwrap(); std::fs::create_dir_all(&fish_dir).unwrap(); - std::fs::write(fish_dir.join("vite-plus.fish"), "source \"$HOME/.vite-plus/env\"\n") + std::fs::write(fish_dir.join("vite-plus.fish"), "source \"$HOME/.vite-plus/env.fish\"\n") .unwrap(); let _guard = ProfileEnvGuard::new(&fake_home, None, Some(&xdg_config)); From 7287290fbe37ca68b1a9b06b533ffa98c73938bb Mon Sep 17 00:00:00 2001 From: mst-mkt Date: Mon, 16 Mar 2026 13:01:03 +0900 Subject: [PATCH 11/11] test(cli): add cfg(not(windows)) to serial_test import --- crates/vite_global_cli/src/commands/implode.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/vite_global_cli/src/commands/implode.rs b/crates/vite_global_cli/src/commands/implode.rs index e21d1d6bf6..78c15e1b2d 100644 --- a/crates/vite_global_cli/src/commands/implode.rs +++ b/crates/vite_global_cli/src/commands/implode.rs @@ -370,6 +370,7 @@ fn remove_windows_path_entry(bin_path: &vite_path::AbsolutePath) -> std::io::Res #[cfg(test)] mod tests { + #[cfg(not(windows))] use serial_test::serial; use super::*;