Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/vite_shared/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ supports-color = "3"
tracing-subscriber = { workspace = true }
vite_path = { workspace = true }
vite_str = { workspace = true }
which = { workspace = true }

[target.'cfg(not(target_os = "windows"))'.dependencies]
rustls = { workspace = true }
Expand Down
74 changes: 73 additions & 1 deletion crates/vite_shared/src/home.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use directories::BaseDirs;
use vite_path::{AbsolutePathBuf, current_dir};
use which::which;

use crate::EnvConfig;

Expand All @@ -8,7 +9,9 @@ const VITE_PLUS_HOME_DIR: &str = ".vite-plus";

/// Get the vite-plus home directory.
///
/// Uses `EnvConfig::get().vite_plus_home` if set, otherwise defaults to `~/.vite-plus`.
/// Uses `EnvConfig::get().vite_plus_home` if set,
/// or the `node` executable's grandparent directory if it ends with `.vite-plus`,
/// otherwise defaults to `~/.vite-plus`.
/// Falls back to `$CWD/.vite-plus` if the home directory cannot be determined.
pub fn get_vite_plus_home() -> std::io::Result<AbsolutePathBuf> {
let config = EnvConfig::get();
Expand All @@ -18,6 +21,16 @@ pub fn get_vite_plus_home() -> std::io::Result<AbsolutePathBuf> {
}
}

// Get from `node` executable file's grandparent directory (~/.vite-plus/bin/node)
// For the case where `$HOME` is overridden
if let Ok(path) = which("node")
&& let Some(parent) = path.parent()
&& let Some(grandparent) = parent.parent()
&& grandparent.ends_with(VITE_PLUS_HOME_DIR)
{
return Ok(AbsolutePathBuf::new(grandparent.to_path_buf()).unwrap());
}

// Default to ~/.vite-plus
match BaseDirs::new() {
Some(dirs) => {
Expand Down Expand Up @@ -49,4 +62,63 @@ mod tests {
assert_eq!(home.as_path(), temp_dir.as_path());
});
}

#[test]
fn test_get_vite_plus_without_home() {
use std::path::PathBuf;

// Create a temp directory structure: /tmp/xxx/.vite-plus/bin/node
let temp_dir = PathBuf::from(
std::env::temp_dir().join(format!("vp-test-node-path-{}", std::process::id())),
);
let vite_plus_home = temp_dir.join(".vite-plus");
let bin_dir = vite_plus_home.join("bin");
std::fs::create_dir_all(&bin_dir).unwrap();

// Create a fake node executable with platform-specific extension
#[cfg(windows)]
let node_path = bin_dir.join("node.exe");
#[cfg(not(windows))]
let node_path = bin_dir.join("node");

// Write minimal content - on Windows, the file just needs to exist with .exe extension
// On Unix, we need a shebang and executable permissions
#[cfg(windows)]
std::fs::write(&node_path, b"MZ").unwrap(); // Minimal PE header for Windows
#[cfg(not(windows))]
{
std::fs::write(&node_path, "#!/bin/sh\necho 'fake node'").unwrap();
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(&node_path).unwrap().permissions();
perms.set_mode(0o755);
std::fs::set_permissions(&node_path, perms).unwrap();
}

// Set PATH to include the fake node directory FIRST (prepended)
let original_path = std::env::var("PATH").unwrap_or_default();
#[cfg(windows)]
let path_separator = ';';
#[cfg(not(windows))]
let path_separator = ':';
let new_path = format!("{}{}{}", bin_dir.display(), path_separator, original_path);
// SAFETY: restore PATH after test
unsafe {
std::env::set_var("PATH", &new_path);
}

// Clear any existing VITE_PLUS_HOME env var by using a test config without it
EnvConfig::test_scope(EnvConfig::for_test(), || {
// Test: get_vite_plus_home should return /tmp/xxx/.vite-plus
let home = get_vite_plus_home().unwrap();
assert_eq!(home.as_path(), vite_plus_home.as_path());
});

// SAFETY: restore PATH after test
unsafe {
std::env::set_var("PATH", original_path);
}

// Cleanup
let _ = std::fs::remove_dir_all(&temp_dir);
}
}
Loading