diff --git a/crates/vite_global_cli/src/commands/env/doctor.rs b/crates/vite_global_cli/src/commands/env/doctor.rs index 168b04fa71..f4fd4adb81 100644 --- a/crates/vite_global_cli/src/commands/env/doctor.rs +++ b/crates/vite_global_cli/src/commands/env/doctor.rs @@ -63,7 +63,7 @@ const KNOWN_VERSION_MANAGERS: &[(&str, &str)] = &[ ]; /// Tools that should have shims -const SHIM_TOOLS: &[&str] = &["node", "npm", "npx", "vpx"]; +const SHIM_TOOLS: &[&str] = &["node", "npm", "npx", "vpx", "vpr"]; /// Column width for left-side keys in aligned output const KEY_WIDTH: usize = 18; diff --git a/crates/vite_global_cli/src/commands/env/setup.rs b/crates/vite_global_cli/src/commands/env/setup.rs index d00a0e4a98..22fe2c94d5 100644 --- a/crates/vite_global_cli/src/commands/env/setup.rs +++ b/crates/vite_global_cli/src/commands/env/setup.rs @@ -23,8 +23,8 @@ use owo_colors::OwoColorize; use super::config::{get_bin_dir, get_vp_home}; use crate::{cli::Args, error::Error, help}; -/// Tools to create shims for (node, npm, npx, vpx) -const SHIM_TOOLS: &[&str] = &["node", "npm", "npx", "vpx"]; +/// Tools to create shims for (node, npm, npx, vpx, vpr) +const SHIM_TOOLS: &[&str] = &["node", "npm", "npx", "vpx", "vpr"]; fn accent_command(command: &str) -> String { if help::should_style_help() { diff --git a/crates/vite_global_cli/src/commands/mod.rs b/crates/vite_global_cli/src/commands/mod.rs index 2b3af82c2f..51a53cadfe 100644 --- a/crates/vite_global_cli/src/commands/mod.rs +++ b/crates/vite_global_cli/src/commands/mod.rs @@ -172,7 +172,8 @@ pub mod version; // Category D: Environment Management pub mod env; -// Standalone binary command +// Standalone binary commands +pub mod vpr; pub mod vpx; // Self-Management diff --git a/crates/vite_global_cli/src/commands/vpr.rs b/crates/vite_global_cli/src/commands/vpr.rs new file mode 100644 index 0000000000..c925d06354 --- /dev/null +++ b/crates/vite_global_cli/src/commands/vpr.rs @@ -0,0 +1,26 @@ +//! `vpr` command implementation. +//! +//! Standalone shorthand for `vp run`. Executes tasks via the same +//! run-or-delegate logic: delegates to local vite-plus CLI when +//! vite-plus is a dependency, otherwise falls back to ` run`. + +use vite_path::AbsolutePath; +use vite_shared::output; + +/// Main entry point for vpr execution. +/// +/// Called from shim dispatch when `argv[0]` is `vpr`. +pub async fn execute_vpr(args: &[String], cwd: &AbsolutePath) -> i32 { + if crate::help::maybe_print_unified_delegate_help("run", args, true) { + return 0; + } + + let cwd_buf = cwd.to_absolute_path_buf(); + match super::run_or_delegate::execute(cwd_buf, args).await { + Ok(status) => status.code().unwrap_or(1), + Err(e) => { + output::error(&e.to_string()); + 1 + } + } +} diff --git a/crates/vite_global_cli/src/help.rs b/crates/vite_global_cli/src/help.rs index 5cbf9ad7d9..e3e1e956f8 100644 --- a/crates/vite_global_cli/src/help.rs +++ b/crates/vite_global_cli/src/help.rs @@ -449,7 +449,7 @@ pub fn top_level_help_doc() -> HelpDoc { section_rows( "Execute", vec![ - row("run", "Run tasks"), + row("run", "Run tasks (also available as standalone `vpr`)"), row("exec", "Execute a command from local node_modules/.bin"), row("dlx", "Execute a package binary without installing it as a dependency"), row("cache", "Manage the task cache"), diff --git a/crates/vite_global_cli/src/shim/dispatch.rs b/crates/vite_global_cli/src/shim/dispatch.rs index 5c2b1fb5db..8dd5b2446c 100644 --- a/crates/vite_global_cli/src/shim/dispatch.rs +++ b/crates/vite_global_cli/src/shim/dispatch.rs @@ -673,6 +673,18 @@ pub async fn dispatch(tool: &str, args: &[String]) -> i32 { return crate::commands::vpx::execute_vpx(args, &cwd).await; } + // Handle vpr — standalone shorthand for `vp run` + if tool == "vpr" { + let cwd = match current_dir() { + Ok(path) => path, + Err(e) => { + eprintln!("vp: Failed to get current directory: {e}"); + return 1; + } + }; + return crate::commands::vpr::execute_vpr(args, &cwd).await; + } + // Check recursion prevention - if already in a shim context, passthrough directly // Only applies to core tools (node/npm/npx) whose bin dir is prepended to PATH. // Package binaries are always resolved via metadata lookup, so they can't loop. diff --git a/crates/vite_global_cli/src/shim/mod.rs b/crates/vite_global_cli/src/shim/mod.rs index 29807ea439..90e12466b5 100644 --- a/crates/vite_global_cli/src/shim/mod.rs +++ b/crates/vite_global_cli/src/shim/mod.rs @@ -148,6 +148,9 @@ pub fn detect_shim_tool(argv0: &str) -> Option { if argv0_tool == "vpx" { return Some("vpx".to_string()); } + if argv0_tool == "vpr" { + return Some("vpr".to_string()); + } // Fall back to argv[0] detection (Unix symlinks) if is_shim_tool(&argv0_tool) { Some(argv0_tool) } else { None } @@ -264,4 +267,23 @@ mod tests { let result = detect_shim_tool("vpx.exe"); assert_eq!(result, Some("vpx".to_string())); } + + #[test] + fn test_detect_shim_tool_vpr() { + // vpr should be detected via the argv0 check, same pattern as vpx + // SAFETY: We're in a test + unsafe { + std::env::remove_var(SHIM_TOOL_ENV_VAR); + } + let result = detect_shim_tool("vpr"); + assert_eq!(result, Some("vpr".to_string())); + + // Also works with full path + let result = detect_shim_tool("/home/user/.vite-plus/bin/vpr"); + assert_eq!(result, Some("vpr".to_string())); + + // Also works with .exe extension (Windows) + let result = detect_shim_tool("vpr.exe"); + assert_eq!(result, Some("vpr".to_string())); + } } diff --git a/crates/vite_global_cli/src/tips/use_vpx_or_run.rs b/crates/vite_global_cli/src/tips/use_vpx_or_run.rs index dcf809deca..2d2cfd3366 100644 --- a/crates/vite_global_cli/src/tips/use_vpx_or_run.rs +++ b/crates/vite_global_cli/src/tips/use_vpx_or_run.rs @@ -11,7 +11,7 @@ impl Tip for UseVpxOrRun { } fn message(&self) -> &'static str { - "Execute a package binary with `vpx `, or a script with `vp run