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
17 changes: 13 additions & 4 deletions crates/vite_global_cli/src/commands/global/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ pub(crate) fn is_vp_shim_target(
/// created for packages either, with one exception: `vp install -g corepack`
/// may take BinConfig ownership of the corepack shim (see
/// `create_package_shim`).
pub(crate) fn is_protected_shim(bin_name: &str) -> bool {
pub(crate) fn is_protected_shim(bin_name: &str, ignore_case: bool) -> bool {
let bin_name =
if cfg!(target_os = "linux") || !ignore_case { bin_name } else { &bin_name.to_lowercase() };
CORE_SHIMS.contains(&bin_name) || crate::commands::env::setup::SHIM_TOOLS.contains(&bin_name)
}

Expand All @@ -82,7 +84,7 @@ pub(crate) fn is_protected_shim(bin_name: &str) -> bool {
/// resolution order. The exemption is scoped to the package name; any other
/// package declaring a `corepack` bin must not take BinConfig ownership.
pub(crate) fn package_may_own_bin(package_name: &str, bin_name: &str) -> bool {
!is_protected_shim(bin_name) || (bin_name == "corepack" && package_name == "corepack")
!is_protected_shim(bin_name, true) || (bin_name == "corepack" && package_name == "corepack")
}

/// Options for [`install`].
Expand Down Expand Up @@ -902,7 +904,7 @@ pub async fn uninstall(package_name: &str, dry_run: bool) -> Result<(), Error> {
output::raw(&format!("Would uninstall {}:", package_name));
for bin_name in &bins {
// Protected shims survive the real uninstall; keep dry-run honest.
if is_protected_shim(bin_name) {
if is_protected_shim(bin_name, false) {
output::raw(&format!(
" - shim: {} (kept: default shim)",
bin_dir.join(bin_name).as_path().display()
Expand Down Expand Up @@ -1093,7 +1095,7 @@ async fn remove_package_shim(
// Don't remove protected shims (e.g., `vp remove -g corepack` must keep
// the default corepack shim so it falls back to the Node-bundled or
// auto-installed corepack).
if is_protected_shim(bin_name) {
if is_protected_shim(bin_name, false) {
return Ok(());
}

Expand Down Expand Up @@ -1228,6 +1230,13 @@ mod tests {

// Regular bins are unrestricted
assert!(package_may_own_bin("typescript", "tsc"));

#[cfg(any(windows, target_os = "macos"))]
assert!(!package_may_own_bin("some-package", "NPM"));
#[cfg(any(windows, target_os = "macos"))]
assert!(!package_may_own_bin("some-package", "Node"));
#[cfg(any(windows, target_os = "macos"))]
assert!(!package_may_own_bin("some-package", "VP"));
}

#[tokio::test]
Expand Down
4 changes: 2 additions & 2 deletions crates/vite_global_cli/src/shim/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ fn check_npm_global_install_result(
// Skip protected shims (core shims and default env shims). Tell
// the user for the non-core names (e.g. `npm i -g corepack`):
// npm installed the package, but the binary stays unlinked.
if is_protected_shim(&bin_name) {
if is_protected_shim(&bin_name, false) {
if !crate::commands::global::CORE_SHIMS.contains(&bin_name.as_str()) {
let hint = if bin_name == "corepack" {
" Use `vp install -g corepack` to manage its version."
Expand Down Expand Up @@ -530,7 +530,7 @@ fn remove_npm_global_uninstall_links(bin_entries: &[(String, String)], npm_prefi
// Skip protected shims: a stale Npm BinConfig (e.g. a pre-default-shim
// `npm install -g corepack`) must not let `npm uninstall -g` delete a
// default shim that `vp env setup` now owns.
if is_protected_shim(bin_name) {
if is_protected_shim(bin_name, false) {
continue;
}

Expand Down
37 changes: 32 additions & 5 deletions crates/vite_global_cli/src/shim/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ pub(crate) mod corepack;
pub(crate) mod dispatch;
pub(crate) mod exec;

use std::fs;

pub(crate) use cache::invalidate_cache;
pub use dispatch::dispatch;
pub(crate) use dispatch::find_system_tool;
use vite_shared::env_vars;

use crate::commands::env::config::get_bin_dir;

/// Core shim tools (node, npm, npx).
///
/// `corepack` is also a default shim (see `commands::env::setup::SHIM_TOOLS`)
Expand All @@ -28,6 +32,7 @@ use vite_shared::env_vars;
pub const CORE_SHIM_TOOLS: &[&str] = &["node", "npm", "npx"];

/// Extract the tool name from argv[0].
/// We hope all bins should be put under $VP_HOME/bin
///
/// Handles various formats:
/// - `node` (Unix)
Expand All @@ -36,11 +41,33 @@ pub const CORE_SHIM_TOOLS: &[&str] = &["node", "npm", "npx"];
/// - `C:\path\node.exe` (Windows full path)
pub fn extract_tool_name(argv0: &str) -> String {
let path = std::path::Path::new(argv0);
let stem = path.file_stem().unwrap_or_default().to_string_lossy();

// Handle Windows: strip .exe, .cmd extensions if present in stem
// (file_stem already strips the extension)
stem.to_lowercase()
let stem = path.file_stem().unwrap_or_default().to_string_lossy().to_string();
if cfg!(target_os = "linux") {
stem
} else {
let bin_dir = get_bin_dir();
if let Ok(bin_dir) = bin_dir {
if let Ok(read_dir) = fs::read_dir(&bin_dir) {
for bin in read_dir.flatten() {
if bin.path().file_stem().unwrap_or_default().to_string_lossy().to_lowercase()
== stem.to_lowercase()
{
return bin
.path()
.file_stem()
.unwrap_or_default()
.to_string_lossy()
.to_string();
}
}
}
}

stem
}
}

/// Check if the given tool name is a core shim tool (node/npm/npx).
Expand Down Expand Up @@ -140,10 +167,10 @@ pub fn detect_shim_tool(argv0: &str) -> Option<String> {
// (so argv[0] would be "vp"), but the env var carries the real tool name.
if let Some(tool) = env_tool {
if !tool.is_empty() {
let tool_lower = tool.to_lowercase();
let tool = extract_tool_name(&tool);
// Accept any tool from env var (could be core or package binary)
if tool_lower != "vp" {
return Some(tool_lower);
if tool != "vp" {
return Some(tool);
Comment thread
liangmiQwQ marked this conversation as resolved.
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('mixed-case bin executed');
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "env-install-mixed-case-bin",
"version": "1.0.0",
"bin": {
"MixedCaseBin": "./cli.js"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
> vp install -g .
info: Installing 1 global package with Node.js <semver>
✓ Installed env-install-mixed-case-bin <semver>
Bins: MixedCaseBin

> MixedCaseBin
mixed-case bin executed
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"commands": ["vp install -g .", "MixedCaseBin"],
"after": ["vp remove -g env-install-mixed-case-bin"]
}
Loading