Skip to content

Commit 40bb28b

Browse files
committed
wip
1 parent 1e63a6c commit 40bb28b

3 files changed

Lines changed: 149 additions & 56 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_shared/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ tracing = { workspace = true }
1919
tracing-subscriber = { workspace = true }
2020
vite_path = { workspace = true }
2121
vite_str = { workspace = true }
22-
which = { workspace = true }
2322

2423
[target.'cfg(target_os = "windows")'.dependencies]
2524
reqwest = { workspace = true, features = ["native-tls-vendored", "stream", "json", "system-proxy"] }

crates/vite_shared/src/home.rs

Lines changed: 149 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
use std::{env, path::PathBuf};
2+
13
use directories::BaseDirs;
24
use vite_path::{AbsolutePathBuf, current_dir};
3-
use which::which;
45

56
use crate::EnvConfig;
67

@@ -10,7 +11,7 @@ const VITE_PLUS_HOME_DIR: &str = ".vite-plus";
1011
/// Get the vite-plus home directory.
1112
///
1213
/// Uses `EnvConfig::get().vite_plus_home` if set,
13-
/// or the `vp` executable's grandparent directory,
14+
/// or the `VP_HOME/bin` directory on `PATH`,
1415
/// otherwise defaults to `~/.vite-plus`.
1516
/// Falls back to `$CWD/.vite-plus` if the home directory cannot be determined.
1617
pub fn get_vp_home() -> std::io::Result<AbsolutePathBuf> {
@@ -21,14 +22,9 @@ pub fn get_vp_home() -> std::io::Result<AbsolutePathBuf> {
2122
return Ok(path);
2223
}
2324

24-
// Get from `vp` executable file's grandparent directory (~/.vite-plus/bin/vp)
25-
// For the case where `VP_HOME` is missing
26-
if let Ok(path) = which("vp")
27-
&& let Some(parent) = path.parent()
28-
&& parent.file_name().is_some_and(|name| name == "bin")
29-
&& let Some(grandparent) = parent.parent()
30-
{
31-
return Ok(AbsolutePathBuf::new(grandparent.to_path_buf()).unwrap());
25+
// Project-local .bin wrappers can shadow Vite+ shims; only trust a full install layout.
26+
if let Some(home) = infer_vp_home_from_path()? {
27+
return Ok(home);
3228
}
3329

3430
// Default to ~/.vite-plus
@@ -44,10 +40,120 @@ pub fn get_vp_home() -> std::io::Result<AbsolutePathBuf> {
4440
}
4541
}
4642

43+
fn infer_vp_home_from_path() -> std::io::Result<Option<AbsolutePathBuf>> {
44+
let Some(path_env) = env::var_os("PATH") else {
45+
return Ok(None);
46+
};
47+
48+
for path_entry in env::split_paths(&path_env) {
49+
if path_entry.as_os_str().is_empty() {
50+
continue;
51+
}
52+
53+
let bin_dir = absolute_path_entry(path_entry)?;
54+
if bin_dir.as_path().file_name().is_none_or(|name| name != "bin") {
55+
continue;
56+
}
57+
let Some(home) = bin_dir.parent() else {
58+
continue;
59+
};
60+
if is_vp_home_layout(&bin_dir, home) {
61+
return Ok(Some(home.to_absolute_path_buf()));
62+
}
63+
}
64+
65+
Ok(None)
66+
}
67+
68+
fn absolute_path_entry(path: PathBuf) -> std::io::Result<AbsolutePathBuf> {
69+
if let Some(path) = AbsolutePathBuf::new(path.clone()) {
70+
return Ok(path);
71+
}
72+
73+
Ok(current_dir()?.join(path))
74+
}
75+
76+
fn is_vp_home_layout(bin_dir: &vite_path::AbsolutePath, home: &vite_path::AbsolutePath) -> bool {
77+
let vp_bin = if cfg!(windows) { bin_dir.join("vp.exe") } else { bin_dir.join("vp") };
78+
let current_vp = if cfg!(windows) {
79+
home.join("current").join("bin").join("vp.exe")
80+
} else {
81+
home.join("current").join("bin").join("vp")
82+
};
83+
84+
vp_bin.as_path().is_file() && current_vp.as_path().is_file()
85+
}
86+
4787
#[cfg(test)]
4888
mod tests {
89+
use std::ffi::{OsStr, OsString};
90+
4991
use super::*;
5092

93+
struct EnvVarGuard {
94+
name: &'static str,
95+
original: Option<OsString>,
96+
}
97+
98+
impl EnvVarGuard {
99+
fn set(name: &'static str, value: impl AsRef<OsStr>) -> Self {
100+
let guard = Self { name, original: std::env::var_os(name) };
101+
// SAFETY: these serial tests own process environment mutations and restore them on drop.
102+
unsafe { std::env::set_var(name, value) };
103+
guard
104+
}
105+
106+
fn remove(name: &'static str) -> Self {
107+
let guard = Self { name, original: std::env::var_os(name) };
108+
// SAFETY: these serial tests own process environment mutations and restore them on drop.
109+
unsafe { std::env::remove_var(name) };
110+
guard
111+
}
112+
}
113+
114+
impl Drop for EnvVarGuard {
115+
fn drop(&mut self) {
116+
// SAFETY: restore the environment snapshot captured by this serial test.
117+
unsafe {
118+
match &self.original {
119+
Some(value) => std::env::set_var(self.name, value),
120+
None => std::env::remove_var(self.name),
121+
}
122+
}
123+
}
124+
}
125+
126+
struct CurrentDirGuard {
127+
original: AbsolutePathBuf,
128+
}
129+
130+
impl CurrentDirGuard {
131+
fn set(path: impl AsRef<std::path::Path>) -> Self {
132+
let guard = Self { original: current_dir().unwrap() };
133+
std::env::set_current_dir(path).unwrap();
134+
guard
135+
}
136+
}
137+
138+
impl Drop for CurrentDirGuard {
139+
fn drop(&mut self) {
140+
std::env::set_current_dir(&self.original).unwrap();
141+
}
142+
}
143+
144+
fn write_executable(path: &std::path::Path) {
145+
#[cfg(windows)]
146+
std::fs::write(path, b"MZ").unwrap();
147+
#[cfg(not(windows))]
148+
{
149+
std::fs::write(path, "#!/bin/sh\necho 'fake vp'").unwrap();
150+
use std::os::unix::fs::PermissionsExt;
151+
let mut perms = std::fs::metadata(path).unwrap().permissions();
152+
perms.set_mode(0o755);
153+
std::fs::set_permissions(path, perms).unwrap();
154+
}
155+
}
156+
51157
#[test]
52158
fn test_get_vp_home() {
53159
let home = get_vp_home().unwrap();
@@ -66,74 +172,63 @@ mod tests {
66172
#[test]
67173
#[serial_test::serial]
68174
fn test_get_vp_home_without_vp_home_infers_from_vp_on_path() {
69-
use std::{
70-
ffi::{OsStr, OsString},
71-
path::PathBuf,
72-
};
73-
74175
let temp_dir = PathBuf::from(
75176
std::env::temp_dir().join(format!("vp-test-vp-path-{}", std::process::id())),
76177
);
77178
let vite_plus_home = temp_dir.join(".vite-plus");
78179
let bin_dir = vite_plus_home.join("bin");
180+
let current_bin_dir = vite_plus_home.join("current").join("bin");
79181
std::fs::create_dir_all(&bin_dir).unwrap();
182+
std::fs::create_dir_all(&current_bin_dir).unwrap();
80183

81184
#[cfg(windows)]
82185
let vp_path = bin_dir.join("vp.exe");
83186
#[cfg(not(windows))]
84187
let vp_path = bin_dir.join("vp");
188+
write_executable(&vp_path);
85189

86190
#[cfg(windows)]
87-
std::fs::write(&vp_path, b"MZ").unwrap();
191+
let current_vp_path = current_bin_dir.join("vp.exe");
88192
#[cfg(not(windows))]
89-
{
90-
std::fs::write(&vp_path, "#!/bin/sh\necho 'fake vp'").unwrap();
91-
use std::os::unix::fs::PermissionsExt;
92-
let mut perms = std::fs::metadata(&vp_path).unwrap().permissions();
93-
perms.set_mode(0o755);
94-
std::fs::set_permissions(&vp_path, perms).unwrap();
95-
}
193+
let current_vp_path = current_bin_dir.join("vp");
194+
write_executable(&current_vp_path);
96195

97-
struct EnvVarGuard {
98-
name: &'static str,
99-
original: Option<OsString>,
100-
}
196+
let path = std::env::join_paths([bin_dir.as_os_str()]).unwrap();
197+
let _path_guard = EnvVarGuard::set("PATH", path);
198+
let _vp_home_guard = EnvVarGuard::remove(crate::env_vars::VP_HOME);
101199

102-
impl EnvVarGuard {
103-
fn set(name: &'static str, value: impl AsRef<OsStr>) -> Self {
104-
let guard = Self { name, original: std::env::var_os(name) };
105-
// SAFETY: this serial test owns process environment mutations and restores them on drop.
106-
unsafe { std::env::set_var(name, value) };
107-
guard
108-
}
200+
EnvConfig::test_scope(EnvConfig::for_test(), || {
201+
let home = get_vp_home().unwrap();
202+
assert_eq!(home.as_path(), vite_plus_home.as_path());
203+
});
109204

110-
fn remove(name: &'static str) -> Self {
111-
let guard = Self { name, original: std::env::var_os(name) };
112-
// SAFETY: this serial test owns process environment mutations and restores them on drop.
113-
unsafe { std::env::remove_var(name) };
114-
guard
115-
}
116-
}
205+
let _ = std::fs::remove_dir_all(&temp_dir);
206+
}
117207

118-
impl Drop for EnvVarGuard {
119-
fn drop(&mut self) {
120-
// SAFETY: restore the environment snapshot captured by this serial test.
121-
unsafe {
122-
match &self.original {
123-
Some(value) => std::env::set_var(self.name, value),
124-
None => std::env::remove_var(self.name),
125-
}
126-
}
127-
}
128-
}
208+
#[test]
209+
#[serial_test::serial]
210+
fn test_get_vp_home_without_vp_home_ignores_relative_bin_without_current_vp() {
211+
let temp_dir = PathBuf::from(
212+
std::env::temp_dir().join(format!("vp-test-relative-bin-{}", std::process::id())),
213+
);
214+
let project_dir = temp_dir.join("project");
215+
let bin_dir = project_dir.join("tools").join("bin");
216+
std::fs::create_dir_all(&bin_dir).unwrap();
129217

130-
let path = std::env::join_paths([bin_dir.as_os_str()]).unwrap();
218+
#[cfg(windows)]
219+
let vp_path = bin_dir.join("vp.exe");
220+
#[cfg(not(windows))]
221+
let vp_path = bin_dir.join("vp");
222+
write_executable(&vp_path);
223+
224+
let _cwd_guard = CurrentDirGuard::set(&project_dir);
225+
let path = std::env::join_paths([std::path::Path::new("tools/bin")]).unwrap();
131226
let _path_guard = EnvVarGuard::set("PATH", path);
132227
let _vp_home_guard = EnvVarGuard::remove(crate::env_vars::VP_HOME);
133228

134229
EnvConfig::test_scope(EnvConfig::for_test(), || {
135230
let home = get_vp_home().unwrap();
136-
assert_eq!(home.as_path(), vite_plus_home.as_path());
231+
assert_ne!(home.as_path(), project_dir.join("tools").as_path());
137232
});
138233

139234
let _ = std::fs::remove_dir_all(&temp_dir);

0 commit comments

Comments
 (0)