Skip to content

Commit 638c447

Browse files
liangmiQwQfengmk2
andauthored
fix(global): respect custom VP_HOME (#2029)
At present, Vite+ allows users to specific `VP_HOME` as an installation directory. However, for any future `vp` runs without manual `VP_HOME` environment, we still use default `~/.vite-plus` or `vp`'s grandparent directory if its name is `.vite-plus` (It's related to #1185 by me). It will cause mess, like multiple Vite+ instances, vp binary not finding, packages unusable and a lot of unexpected behavior. This PR improves all related logic, include: - Set `VP_HOME` in env files - Improve prompt and implode check related to `VP_HOME` - Respect `VP_HOME` in `pnpm boostrap-cli` at devtime. The new `install.sh` prompt looks like that: <img width="977" height="270" alt="image" src="https://github.com/user-attachments/assets/c96435d1-4b49-4e51-bb75-3da06205f23e" /> 🤖 Generated by Codex --------- Co-authored-by: MK (fengmk2) <fengmk2@gmail.com>
1 parent d3f6ac2 commit 638c447

12 files changed

Lines changed: 788 additions & 140 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/vite_global_cli/src/commands/env/setup.rs

Lines changed: 115 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,7 @@ pub(crate) async fn cleanup_legacy_windows_shim(bin_dir: &vite_path::AbsolutePat
526526
// Includes shell completion support
527527
const ENV_TEMPLATE_POSIX: &str = r#"#!/bin/sh
528528
# Vite+ environment setup (https://viteplus.dev)
529+
export VP_HOME="__VP_HOME__"
529530
__vp_bin="__VP_BIN__"
530531
case ":${PATH}:" in
531532
*":${__vp_bin}:"*)
@@ -573,6 +574,7 @@ fi
573574
"#;
574575

575576
const ENV_TEMPLATE_FISH: &str = r#"# Vite+ environment setup (https://viteplus.dev)
577+
set -gx VP_HOME "__VP_HOME__"
576578
set -l __vp_idx (contains -i -- __VP_BIN__ $PATH)
577579
and set -e PATH[$__vp_idx]
578580
set -gx PATH __VP_BIN__ $PATH
@@ -608,6 +610,7 @@ complete -c vpr --keep-order --exclusive --arguments "(__vpr_complete)"
608610
// Completions delegate to Fish dynamically (VP_COMPLETE=fish) because clap_complete_nushell
609611
// generates multiple rest params (e.g. for `vp install`), which Nushell does not support.
610612
const ENV_TEMPLATE_NU: &str = r#"# Vite+ environment setup (https://viteplus.dev)
613+
$env.VP_HOME = ("__VP_HOME__" | path expand --no-symlink)
611614
$env.PATH = ($env.PATH | where { $in != "__VP_BIN__" } | prepend "__VP_BIN__")
612615
613616
# Shell function wrapper: intercepts `vp env use` to parse its stdout,
@@ -668,6 +671,7 @@ export extern "vpr" [...args: string@"nu-complete vpr"]
668671
"#;
669672

670673
const ENV_TEMPLATE_PS1: &str = r#"# Vite+ environment setup (https://viteplus.dev)
674+
$env:VP_HOME = "__VP_HOME_WIN__"
671675
$__vp_bin = "__VP_BIN_WIN__"
672676
if ($env:Path -split ';' -notcontains $__vp_bin) {
673677
$env:Path = "$__vp_bin;$env:Path"
@@ -728,37 +732,63 @@ Register-ArgumentCompleter -Native -CommandName vpr -ScriptBlock $__vpr_comp
728732

729733
// cmd.exe wrapper for `vp env use` (cmd.exe cannot define shell functions).
730734
// Users run `vp-use 24` in cmd.exe instead of `vp env use 24`.
731-
const VP_USE_CMD_CONTENT: &str = "@echo off\r\nset VP_ENV_USE_EVAL_ENABLE=1\r\nfor /f \"delims=\" %%i in ('%~dp0..\\current\\bin\\vp.exe env use %*') do %%i\r\nset VP_ENV_USE_EVAL_ENABLE=\r\n";
732-
733-
/// Render the env-file content for `shell` against `vite_plus_home`.
734-
fn render_env_content(shell: EnvShell, vite_plus_home: &vite_path::AbsolutePath) -> String {
735-
let bin_path = vite_plus_home.join("bin");
735+
const VP_USE_CMD_CONTENT: &str = "@echo off\r\nset VP_ENV_USE_EVAL_ENABLE=1\r\nset VP_HOME=%~dp0..\r\nfor /f \"delims=\" %%i in ('%~dp0..\\current\\bin\\vp.exe env use %*') do %%i\r\nset VP_ENV_USE_EVAL_ENABLE=\r\n";
736736

737+
fn render_home_relative_path(path: &std::path::Path, home_dir: Option<&std::path::Path>) -> String {
737738
// Use $HOME-relative path if install dir is under HOME (like rustup's ~/.cargo/env).
738739
// This makes the env file portable across sessions where HOME may differ.
739-
let home_dir = vite_shared::EnvConfig::get().user_home;
740-
let bin_path_ref = home_dir
741-
.as_ref()
742-
.and_then(|h| bin_path.as_path().strip_prefix(h).ok())
740+
home_dir
741+
.and_then(|h| path.strip_prefix(h).ok())
743742
.map(|s| {
744-
// Normalize to forward slashes for $HOME/... paths (POSIX-style)
745-
format!("$HOME/{}", s.display().to_string().replace('\\', "/"))
743+
if s.as_os_str().is_empty() {
744+
"$HOME".to_string()
745+
} else {
746+
// Normalize to forward slashes for $HOME/... paths (POSIX-style)
747+
format!("$HOME/{}", s.display().to_string().replace('\\', "/"))
748+
}
746749
})
747-
.unwrap_or_else(|| bin_path.as_path().display().to_string().replace('\\', "/"));
750+
.unwrap_or_else(|| path.display().to_string().replace('\\', "/"))
751+
}
752+
753+
fn render_nu_path_ref(path_ref: &str) -> String {
754+
match path_ref.strip_prefix("$HOME") {
755+
Some("") => "~".to_string(),
756+
Some(suffix) if suffix.starts_with('/') => format!("~{suffix}"),
757+
_ => path_ref.to_string(),
758+
}
759+
}
760+
761+
/// Render the env-file content for `shell` against `vite_plus_home`.
762+
fn render_env_content(shell: EnvShell, vite_plus_home: &vite_path::AbsolutePath) -> String {
763+
let bin_path = vite_plus_home.join("bin");
764+
let home_dir = vite_shared::EnvConfig::get().user_home;
765+
let home_dir = home_dir.as_deref();
766+
let home_path_ref = render_home_relative_path(vite_plus_home.as_path(), home_dir);
767+
let bin_path_ref = render_home_relative_path(bin_path.as_path(), home_dir);
748768

749769
match shell {
750-
EnvShell::Posix => ENV_TEMPLATE_POSIX.replace("__VP_BIN__", &bin_path_ref),
751-
EnvShell::Fish => ENV_TEMPLATE_FISH.replace("__VP_BIN__", &bin_path_ref),
770+
EnvShell::Posix => ENV_TEMPLATE_POSIX
771+
.replace("__VP_HOME__", &home_path_ref)
772+
.replace("__VP_BIN__", &bin_path_ref),
773+
EnvShell::Fish => ENV_TEMPLATE_FISH
774+
.replace("__VP_HOME__", &home_path_ref)
775+
.replace("__VP_BIN__", &bin_path_ref),
752776
EnvShell::Nu => {
753777
// Nushell requires `~` instead of `$HOME` in string literals — `$HOME` is not
754778
// expanded at parse time, so PATH entries would contain a literal "$HOME/...".
755-
let bin_path_ref_nu = bin_path_ref.replace("$HOME/", "~/");
756-
ENV_TEMPLATE_NU.replace("__VP_BIN__", &bin_path_ref_nu)
779+
let home_path_ref_nu = render_nu_path_ref(&home_path_ref);
780+
let bin_path_ref_nu = render_nu_path_ref(&bin_path_ref);
781+
ENV_TEMPLATE_NU
782+
.replace("__VP_HOME__", &home_path_ref_nu)
783+
.replace("__VP_BIN__", &bin_path_ref_nu)
757784
}
758785
EnvShell::Powershell => {
759786
// PowerShell uses the actual absolute path (not $HOME-relative)
787+
let home_path_win = vite_plus_home.as_path().display().to_string();
760788
let bin_path_win = bin_path.as_path().display().to_string();
761-
ENV_TEMPLATE_PS1.replace("__VP_BIN_WIN__", &bin_path_win)
789+
ENV_TEMPLATE_PS1
790+
.replace("__VP_HOME_WIN__", &home_path_win)
791+
.replace("__VP_BIN_WIN__", &bin_path_win)
762792
}
763793
}
764794
}
@@ -919,13 +949,16 @@ mod tests {
919949
#[tokio::test]
920950
async fn test_create_env_files_replaces_placeholder_with_home_relative_path() {
921951
let temp_dir = TempDir::new().unwrap();
922-
let home = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap();
952+
let home = AbsolutePathBuf::new(temp_dir.path().join("vp_home")).unwrap();
923953
let _guard = home_guard(temp_dir.path());
954+
tokio::fs::create_dir_all(&home).await.unwrap();
924955

925956
create_env_files(&home).await.unwrap();
926957

927958
let env_content = tokio::fs::read_to_string(home.join("env")).await.unwrap();
928959
let fish_content = tokio::fs::read_to_string(home.join("env.fish")).await.unwrap();
960+
let nu_content = tokio::fs::read_to_string(home.join("env.nu")).await.unwrap();
961+
let ps1_content = tokio::fs::read_to_string(home.join("env.ps1")).await.unwrap();
929962

930963
// Placeholder should be fully replaced
931964
assert!(
@@ -936,15 +969,45 @@ mod tests {
936969
!fish_content.contains("__VP_BIN__"),
937970
"env.fish file should not contain __VP_BIN__ placeholder"
938971
);
972+
assert!(
973+
!env_content.contains("__VP_HOME__") && !fish_content.contains("__VP_HOME__"),
974+
"env files should not contain __VP_HOME__ placeholder"
975+
);
976+
assert!(
977+
!nu_content.contains("__VP_HOME__") && !ps1_content.contains("__VP_HOME_WIN__"),
978+
"env files should not contain VP_HOME placeholders"
979+
);
939980

940981
// Should use $HOME-relative path since install dir is under HOME
941982
assert!(
942-
env_content.contains("$HOME/bin"),
943-
"env file should reference $HOME/bin, got: {env_content}"
983+
env_content.contains("$HOME/vp_home/bin"),
984+
"env file should reference $HOME/vp_home/bin, got: {env_content}"
985+
);
986+
assert!(
987+
fish_content.contains("$HOME/vp_home/bin"),
988+
"env.fish file should reference $HOME/vp_home/bin, got: {fish_content}"
989+
);
990+
assert!(
991+
env_content.contains("export VP_HOME=\"$HOME/vp_home\""),
992+
"env file should export VP_HOME, got: {env_content}"
944993
);
945994
assert!(
946-
fish_content.contains("$HOME/bin"),
947-
"env.fish file should reference $HOME/bin, got: {fish_content}"
995+
fish_content.contains("set -gx VP_HOME \"$HOME/vp_home\""),
996+
"env.fish file should export VP_HOME, got: {fish_content}"
997+
);
998+
assert!(
999+
nu_content.contains("$env.VP_HOME = (\"~/vp_home\" | path expand --no-symlink)"),
1000+
"env.nu file should set home-relative VP_HOME, got: {nu_content}"
1001+
);
1002+
assert!(
1003+
nu_content.contains("~/vp_home/bin"),
1004+
"env.nu file should reference ~/vp_home/bin, got: {nu_content}"
1005+
);
1006+
1007+
let expected_home = home.as_path().display().to_string();
1008+
assert!(
1009+
ps1_content.contains(&format!("$env:VP_HOME = \"{expected_home}\"")),
1010+
"env.ps1 file should set VP_HOME, got: {ps1_content}"
9481011
);
9491012
}
9501013

@@ -963,6 +1026,7 @@ mod tests {
9631026
// Should use absolute path since install dir is not under HOME
9641027
let expected_bin = home.join("bin");
9651028
let expected_str = expected_bin.as_path().display().to_string().replace('\\', "/");
1029+
let expected_home = home.as_path().display().to_string().replace('\\', "/");
9661030
assert!(
9671031
env_content.contains(&expected_str),
9681032
"env file should use absolute path {expected_str}, got: {env_content}"
@@ -971,6 +1035,14 @@ mod tests {
9711035
fish_content.contains(&expected_str),
9721036
"env.fish file should use absolute path {expected_str}, got: {fish_content}"
9731037
);
1038+
assert!(
1039+
env_content.contains(&format!("export VP_HOME=\"{expected_home}\"")),
1040+
"env file should export absolute VP_HOME {expected_home}, got: {env_content}"
1041+
);
1042+
assert!(
1043+
fish_content.contains(&format!("set -gx VP_HOME \"{expected_home}\"")),
1044+
"env.fish file should export absolute VP_HOME {expected_home}, got: {fish_content}"
1045+
);
9741046

9751047
// Should NOT use $HOME-relative path
9761048
assert!(!env_content.contains("$HOME/bin"), "env file should not reference $HOME/bin");
@@ -1120,6 +1192,27 @@ mod tests {
11201192
);
11211193
}
11221194

1195+
#[tokio::test]
1196+
async fn test_create_env_files_cmd_wrapper_sets_vp_home_before_env_use() {
1197+
let temp_dir = TempDir::new().unwrap();
1198+
let home = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap();
1199+
let _guard = home_guard(temp_dir.path());
1200+
let bin_dir = home.join("bin");
1201+
tokio::fs::create_dir_all(&bin_dir).await.unwrap();
1202+
1203+
create_env_files(&home).await.unwrap();
1204+
1205+
let cmd_content = tokio::fs::read_to_string(bin_dir.join("vp-use.cmd")).await.unwrap();
1206+
assert!(
1207+
cmd_content.contains("set VP_HOME=%~dp0..\r\nfor /f"),
1208+
"vp-use.cmd should set VP_HOME before invoking vp env use, got: {cmd_content}"
1209+
);
1210+
assert!(
1211+
cmd_content.contains("%~dp0..\\current\\bin\\vp.exe env use %*"),
1212+
"vp-use.cmd should invoke the install-local vp.exe"
1213+
);
1214+
}
1215+
11231216
#[tokio::test]
11241217
async fn test_execute_env_only_creates_home_dir_and_env_files() {
11251218
let temp_dir = TempDir::new().unwrap();

0 commit comments

Comments
 (0)