Skip to content

Commit 35b7365

Browse files
committed
feat(env): add corepack to the default shims tool list
vp env setup now creates a corepack shim alongside node/npm/npx. The shim resolves corepack in this order: a vp-managed global install (vp install -g corepack), the copy bundled with the project-resolved Node.js (Node.js <= 24), and finally an automatic managed install on Node.js 25+ where corepack is no longer bundled. The auto-install links only the corepack bin so it never conflicts with vp-managed package managers like an existing vp install -g pnpm. corepack enable/disable without an explicit --install-directory get --install-directory ~/.vite-plus/bin injected so the pnpm/yarn launchers land on PATH. Vite+-owned shims (npm/npx and vp install -g binaries) are restored with a warning if corepack removes or replaces them, and vp remove -g corepack keeps the default shim in place. Closes #1309 Closes #858
1 parent 491cd3f commit 35b7365

24 files changed

Lines changed: 673 additions & 69 deletions

File tree

crates/vite_global_cli/src/cli.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ impl Commands {
256256
#[command(after_help = "\
257257
Examples:
258258
Setup:
259-
vp env setup # Create shims for node, npm, npx
259+
vp env setup # Create shims for node, npm, npx, corepack
260260
vp env on # Use vite-plus managed Node.js
261261
vp env print # Print shell snippet for this session
262262
@@ -666,6 +666,7 @@ async fn managed_install(
666666
force,
667667
concurrency.unwrap_or(DEFAULT_GLOBAL_INSTALL_CONCURRENCY),
668668
false,
669+
None,
669670
)
670671
.await
671672
{
@@ -791,9 +792,15 @@ async fn managed_update(
791792
}
792793

793794
// Call reinstall logic
794-
if let Err((package_name, error)) =
795-
global::install::install(&to_update, Some(&current_node_version), false, concurrency, true)
796-
.await
795+
if let Err((package_name, error)) = global::install::install(
796+
&to_update,
797+
Some(&current_node_version),
798+
false,
799+
concurrency,
800+
true,
801+
None,
802+
)
803+
.await
797804
{
798805
output::error(&format!(
799806
"Failed to update {}: {error}",

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ mod off;
1515
mod on;
1616
pub mod package_metadata;
1717
mod pin;
18-
mod setup;
18+
pub(crate) mod setup;
1919
mod unpin;
2020
mod r#use;
2121
mod which;

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

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
//! Setup command implementation for creating bin directory and shims.
22
//!
33
//! Creates the following structure:
4-
//! - ~/.vite-plus/bin/ - Contains vp symlink and node/npm/npx shims
4+
//! - ~/.vite-plus/bin/ - Contains vp symlink and node/npm/npx/corepack shims
55
//! - ~/.vite-plus/current/ - Contains the actual vp CLI binary
66
//!
77
//! On Unix:
88
//! - bin/vp is a symlink to the active vp binary
9-
//! - bin/node, bin/npm, bin/npx are symlinks to the active vp binary
9+
//! - bin/node, bin/npm, bin/npx, bin/corepack are symlinks to the active vp binary
1010
//! - Symlinks preserve argv[0], allowing tool detection via the symlink name
1111
//!
1212
//! On Windows:
13-
//! - bin/vp.exe, bin/node.exe, bin/npm.exe, bin/npx.exe are trampoline executables
13+
//! - bin/vp.exe, bin/node.exe, bin/npm.exe, bin/npx.exe, bin/corepack.exe are trampoline executables
1414
//! - Each trampoline detects its tool name from its own filename and spawns
1515
//! current\bin\vp.exe with VP_SHIM_TOOL env var set
1616
//! - This avoids the "Terminate batch job (Y/N)?" prompt from .cmd wrappers
@@ -43,8 +43,8 @@ impl EnvShell {
4343
}
4444
}
4545

46-
/// Tools to create shims for (node, npm, npx, vpx, vpr)
47-
pub(crate) const SHIM_TOOLS: &[&str] = &["node", "npm", "npx", "vpx", "vpr"];
46+
/// Tools to create shims for (node, npm, npx, corepack, vpx, vpr)
47+
pub(crate) const SHIM_TOOLS: &[&str] = &["node", "npm", "npx", "corepack", "vpx", "vpr"];
4848

4949
fn accent_command(command: &str) -> String {
5050
if help::should_style_help() {
@@ -90,7 +90,7 @@ pub async fn execute(refresh: bool, env_only: bool) -> Result<ExitStatus, Error>
9090
// Create wrapper script in bin/
9191
setup_vp_wrapper(&current_exe, &bin_dir, refresh).await?;
9292

93-
// Create shims for node, npm, npx
93+
// Create shims for node, npm, npx, corepack
9494
let mut created = Vec::new();
9595
let mut skipped = Vec::new();
9696

@@ -228,10 +228,10 @@ pub(crate) async fn resolve_unix_vp_shim_target(
228228
Ok(current_exe.to_path_buf())
229229
}
230230

231-
/// Create a single shim for node/npm/npx.
231+
/// Create a single shim for a default shim tool (node/npm/npx/corepack/vpx/vpr).
232232
///
233233
/// Returns `true` if the shim was created, `false` if it already exists.
234-
async fn create_shim(
234+
pub(crate) async fn create_shim(
235235
source: &std::path::Path,
236236
bin_dir: &vite_path::AbsolutePath,
237237
tool: &str,
@@ -847,6 +847,13 @@ mod tests {
847847

848848
use super::*;
849849

850+
#[test]
851+
fn test_shim_tools_contains_default_shims() {
852+
// corepack is a default shim (#858, #1309); node/npm/npx resolve the
853+
// project Node.js version; vpx/vpr are command shorthands.
854+
assert_eq!(SHIM_TOOLS, &["node", "npm", "npx", "corepack", "vpx", "vpr"]);
855+
}
856+
850857
/// Helper: create a test_guard with user_home set to the given path.
851858
fn home_guard(home: impl Into<std::path::PathBuf>) -> vite_shared::TestEnvGuard {
852859
vite_shared::EnvConfig::test_guard(vite_shared::EnvConfig {

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

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
//!
33
//! Shows the path to the tool binary that would be executed.
44
//!
5-
//! For core tools (node, npm, npx), shows the resolved Node.js binary path
6-
//! along with version and resolution source.
5+
//! For core tools (node, npm, npx, corepack), shows the resolved Node.js
6+
//! binary path along with version and resolution source.
77
//! For global packages, shows the binary path plus package metadata.
88
99
use std::process::ExitStatus;
@@ -23,8 +23,8 @@ use super::{
2323
};
2424
use crate::error::Error;
2525

26-
/// Core tools (node, npm, npx)
27-
const CORE_TOOLS: &[&str] = &["node", "npm", "npx"];
26+
/// Core tools (node, npm, npx, corepack)
27+
const CORE_TOOLS: &[&str] = &["node", "npm", "npx", "corepack"];
2828

2929
/// Column width for left-side labels in aligned metadata output
3030
const LABEL_WIDTH: usize = 10;
@@ -37,6 +37,13 @@ pub async fn execute(cwd: AbsolutePathBuf, tool: &str) -> Result<ExitStatus, Err
3737

3838
// Check if this is a core tool
3939
if CORE_TOOLS.contains(&tool) {
40+
// corepack: a vp-managed global install wins over the Node-bundled
41+
// copy (mirrors the shim dispatch order).
42+
if tool == "corepack"
43+
&& let Some(metadata) = PackageMetadata::find_by_binary(tool).await?
44+
{
45+
return execute_package_binary(tool, &metadata).await;
46+
}
4047
return execute_core_tool(cwd, tool).await;
4148
}
4249

@@ -47,7 +54,7 @@ pub async fn execute(cwd: AbsolutePathBuf, tool: &str) -> Result<ExitStatus, Err
4754

4855
// Unknown tool
4956
output::error(&format!("tool '{}' not found", tool.bold()));
50-
eprintln!("Not a core tool (node, npm, npx) or installed global package.");
57+
eprintln!("Not a core tool (node, npm, npx, corepack) or installed global package.");
5158
eprintln!("Run 'vp list -g' to see installed packages.");
5259
Ok(exit_status(1))
5360
}
@@ -94,7 +101,7 @@ async fn execute_package_manager_tool(
94101
Ok(Some(ExitStatus::default()))
95102
}
96103

97-
/// Execute which for a core tool (node, npm, npx).
104+
/// Execute which for a core tool (node, npm, npx, corepack).
98105
async fn execute_core_tool(cwd: AbsolutePathBuf, tool: &str) -> Result<ExitStatus, Error> {
99106
// Resolve version for current directory
100107
let resolution = resolve_version(&cwd).await?;
@@ -115,9 +122,23 @@ async fn execute_core_tool(cwd: AbsolutePathBuf, tool: &str) -> Result<ExitStatu
115122

116123
// Check if the tool exists
117124
if !tokio::fs::try_exists(&tool_path).await.unwrap_or(false) {
125+
#[cfg(windows)]
126+
let node_path = home_dir.join("node.exe");
127+
#[cfg(not(windows))]
128+
let node_path = home_dir.join("bin").join("node");
129+
let node_installed = tokio::fs::try_exists(&node_path).await.unwrap_or(false);
130+
118131
output::error(&format!("{} not found", tool.bold()));
119-
eprintln!("Node.js {} is not installed.", resolution.version);
120-
eprintln!("Run 'vp env install {}' to install it.", resolution.version);
132+
if tool == "corepack" && node_installed {
133+
// corepack is no longer bundled starting with Node.js 25
134+
eprintln!("corepack is not bundled with Node.js {}.", resolution.version);
135+
eprintln!(
136+
"It is installed automatically on first use, or run 'vp install -g corepack'."
137+
);
138+
} else {
139+
eprintln!("Node.js {} is not installed.", resolution.version);
140+
eprintln!("Run 'vp env install {}' to install it.", resolution.version);
141+
}
121142
return Ok(exit_status(1));
122143
}
123144

crates/vite_global_cli/src/commands/global/install.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,16 @@ fn package_error(package_name: &str, error: impl Into<Error>) -> (Option<String>
5757
/// If `node_version` is provided, uses that version. Otherwise, resolves from current directory.
5858
/// If `force` is true, auto-uninstalls conflicting packages.
5959
/// Use `concurrency` to control the number of packages to install in parallel.
60+
/// If `only_bins` is provided, only those binaries are exposed as shims; other
61+
/// bins the package declares are ignored (used by the corepack shim
62+
/// auto-install, which must not link corepack's pnpm/yarn launchers).
6063
pub async fn install(
6164
package_specs: &[String],
6265
node_version: Option<&str>,
6366
force: bool,
6467
concurrency: usize,
6568
update: bool,
69+
only_bins: Option<&[&str]>,
6670
) -> Result<(), (Option<String>, Error)> {
6771
if package_specs.is_empty() {
6872
return Ok(());
@@ -194,6 +198,16 @@ pub async fn install(
194198
else {
195199
continue;
196200
};
201+
// Restrict exposed binaries when requested (e.g., the corepack shim
202+
// auto-install only links `corepack`, not the pnpm/yarn launchers
203+
// that `corepack enable` creates on demand).
204+
let (bin_names, js_bins) = match only_bins {
205+
Some(only) => (
206+
bin_names.into_iter().filter(|bin| only.contains(&bin.as_str())).collect(),
207+
js_bins.into_iter().filter(|bin| only.contains(&bin.as_str())).collect(),
208+
),
209+
None => (bin_names, js_bins),
210+
};
197211
let mut backup = backup;
198212
let stale_bin_names = match stale_bin_names_for_package(&package_name, &bin_names).await {
199213
Ok(bin_names) => bin_names,
@@ -751,7 +765,7 @@ fn is_javascript_binary(path: &AbsolutePath) -> bool {
751765
///
752766
/// On Unix: Creates a symlink to ../current/bin/vp
753767
/// On Windows: Creates a trampoline .exe that forwards to vp.exe
754-
async fn create_package_shim(
768+
pub(crate) async fn create_package_shim(
755769
bin_dir: &vite_path::AbsolutePath,
756770
bin_name: &str,
757771
package_name: &str,
@@ -819,8 +833,11 @@ async fn remove_package_shim(
819833
bin_dir: &vite_path::AbsolutePath,
820834
bin_name: &str,
821835
) -> Result<(), Error> {
822-
// Don't remove core shims
823-
if CORE_SHIMS.contains(&bin_name) {
836+
// Don't remove core shims or default env shims (e.g., `vp remove -g corepack`
837+
// must keep the default corepack shim so it falls back to the Node-bundled
838+
// or auto-installed corepack).
839+
if CORE_SHIMS.contains(&bin_name) || crate::commands::env::setup::SHIM_TOOLS.contains(&bin_name)
840+
{
824841
return Ok(());
825842
}
826843

crates/vite_global_cli/src/help.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ fn env_help_doc() -> HelpDoc {
543543
"Examples",
544544
vec![
545545
" Setup:",
546-
" vp env setup # Create shims for node, npm, npx",
546+
" vp env setup # Create shims for node, npm, npx, corepack",
547547
" vp env on # Use vite-plus managed Node.js",
548548
" vp env print # Print shell snippet for this session",
549549
"",

0 commit comments

Comments
 (0)