Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
[[case]]
name = "pnpm_runtime_disabled_for_managed_node"
vp = "global"
seed-runtime = false
skip-platforms = ["windows"]
comment = "Vite+ owns Node.js runtime management independently from the selected pnpm binary."
steps = [
{ argv = ["vpt", "write-file", "package.json", "{\"name\":\"pnpm-runtime-management\",\"private\":true,\"packageManager\":\"pnpm@10.18.0\",\"devEngines\":{\"runtime\":{\"name\":\"node\",\"version\":\"20.18.0\",\"onFail\":\"download\"}}}\n"], snapshot = false },
{ argv = ["vpt", "write-file", "$VP_HOME/js_runtime/node/20.18.0/bin/node", "#!/bin/sh\n"], snapshot = false },
{ argv = ["vpt", "chmod", "+x", "$VP_HOME/js_runtime/node/20.18.0/bin/node"], snapshot = false },
{ argv = ["vpt", "chmod", "+x", "system-bin/pnpm"], snapshot = false },
{ argv = ["vp", "env", "on", "node"], snapshot = false },
{ argv = ["vp", "env", "off", "pnpm"], snapshot = false },
{ argv = ["pnpm", "install"], envs = [["PATH", "${VP_HOME}/bin${PATH_SEPARATOR}${workspace}/system-bin${PATH_SEPARATOR}${PATH}"], ["PNPM_CONFIG_RUNTIME", "from-user"]], comment = "direct system-first pnpm disables its duplicate Node.js runtime" },
{ argv = ["vp", "install"], envs = [["PATH", "${VP_HOME}/bin${PATH_SEPARATOR}${workspace}/system-bin${PATH_SEPARATOR}${PATH}"], ["PNPM_CONFIG_RUNTIME", "from-user"]], comment = "pnpm-backed vp install applies the same system-first policy" },
{ argv = ["vpt", "write-file", "$VP_HOME/package_manager/pnpm/10.18.0/pnpm/bin/pnpm", "#!/bin/sh\nif [ \"$1\" = \"--version\" ]; then printf '10.18.0\\n'; else printf 'PNPM_CONFIG_RUNTIME=%s\\n' \"${PNPM_CONFIG_RUNTIME-unset}\"; fi\n"], snapshot = false },
{ argv = ["vpt", "write-file", "$VP_HOME/package_manager/pnpm/10.18.0/pnpm/bin/pnpx", "#!/bin/sh\n"], snapshot = false },
{ argv = ["vpt", "chmod", "+x", "$VP_HOME/package_manager/pnpm/10.18.0/pnpm/bin/pnpm"], snapshot = false },
{ argv = ["vpt", "chmod", "+x", "$VP_HOME/package_manager/pnpm/10.18.0/pnpm/bin/pnpx"], snapshot = false },
{ argv = ["vp", "env", "on", "pnpm"], snapshot = false },
{ argv = ["pnpm", "install"], envs = [["PNPM_CONFIG_RUNTIME", "from-user"]], comment = "direct managed pnpm disables its duplicate Node.js runtime" },
{ argv = ["vp", "install"], envs = [["PNPM_CONFIG_RUNTIME", "from-user"]], comment = "pnpm-backed vp install applies the same managed-pnpm policy" },
]

[[case]]
name = "pnpm_runtime_preserved_for_mixed_runtimes"
vp = "global"
seed-runtime = false
skip-platforms = ["windows"]
comment = "Vite+ must not disable pnpm runtimes that it cannot manage."
steps = [
{ argv = ["vpt", "write-file", "package.json", "{\"name\":\"pnpm-runtime-management\",\"private\":true,\"packageManager\":\"pnpm@10.18.0\",\"devEngines\":{\"runtime\":[{\"name\":\"node\",\"version\":\"20.18.0\",\"onFail\":\"download\"},{\"name\":\"deno\",\"version\":\"2.0.0\",\"onFail\":\"download\"}]}}\n"], snapshot = false },
{ argv = ["vpt", "write-file", "$VP_HOME/js_runtime/node/20.18.0/bin/node", "#!/bin/sh\n"], snapshot = false },
{ argv = ["vpt", "chmod", "+x", "$VP_HOME/js_runtime/node/20.18.0/bin/node"], snapshot = false },
{ argv = ["vpt", "chmod", "+x", "system-bin/pnpm"], snapshot = false },
{ argv = ["vp", "env", "on", "node"], snapshot = false },
{ argv = ["vp", "env", "off", "pnpm"], snapshot = false },
{ argv = ["pnpm", "install"], envs = [["PATH", "${VP_HOME}/bin${PATH_SEPARATOR}${workspace}/system-bin${PATH_SEPARATOR}${PATH}"], ["PNPM_CONFIG_RUNTIME", "from-user"]], comment = "direct pnpm preserves runtime management for a mixed declaration" },
{ argv = ["vp", "install"], envs = [["PATH", "${VP_HOME}/bin${PATH_SEPARATOR}${workspace}/system-bin${PATH_SEPARATOR}${PATH}"], ["PNPM_CONFIG_RUNTIME", "from-user"]], comment = "vp install preserves the same mixed-runtime setting" },
]

[[case]]
name = "pnpm_runtime_preserved_for_system_first_node"
vp = "global"
seed-runtime = false
skip-platforms = ["windows"]
comment = "pnpm keeps runtime ownership when Vite+ does not manage Node.js."
steps = [
{ argv = ["vpt", "write-file", "package.json", "{\"name\":\"pnpm-runtime-management\",\"private\":true,\"packageManager\":\"pnpm@10.18.0\",\"devEngines\":{\"runtime\":{\"name\":\"node\",\"version\":\"20.18.0\",\"onFail\":\"download\"}}}\n"], snapshot = false },
{ argv = ["vpt", "chmod", "+x", "system-bin/node"], snapshot = false },
{ argv = ["vpt", "chmod", "+x", "system-bin/pnpm"], snapshot = false },
{ argv = ["vp", "env", "off", "node"], snapshot = false },
{ argv = ["vp", "env", "off", "pnpm"], snapshot = false },
{ argv = ["pnpm", "install"], envs = [["PATH", "${VP_HOME}/bin${PATH_SEPARATOR}${workspace}/system-bin${PATH_SEPARATOR}${PATH}"], ["PNPM_CONFIG_RUNTIME", "from-user"]], comment = "direct pnpm preserves runtime management with system-first Node.js" },
{ argv = ["vp", "install"], envs = [["PATH", "${VP_HOME}/bin${PATH_SEPARATOR}${workspace}/system-bin${PATH_SEPARATOR}${PATH}"], ["PNPM_CONFIG_RUNTIME", "from-user"]], comment = "vp install preserves the same system-first Node.js policy" },
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# pnpm_runtime_disabled_for_managed_node

Vite+ owns Node.js runtime management independently from the selected pnpm binary.

## `vpt write-file package.json '{"name":"pnpm-runtime-management","private":true,"packageManager":"pnpm@10.18.0","devEngines":{"runtime":{"name":"node","version":"20.18.0","onFail":"download"}}}
'`


## `vpt write-file $VP_HOME/js_runtime/node/20.18.0/bin/node '#'\!'/bin/sh
'`


## `vpt chmod +x $VP_HOME/js_runtime/node/20.18.0/bin/node`


## `vpt chmod +x system-bin/pnpm`


## `vp env on node`


## `vp env off pnpm`


## `PATH=${VP_HOME}/bin${PATH_SEPARATOR}${workspace}/system-bin${PATH_SEPARATOR}${PATH} PNPM_CONFIG_RUNTIME=from-user pnpm install`

direct system-first pnpm disables its duplicate Node.js runtime

```
PNPM_CONFIG_RUNTIME=false
```

## `PATH=${VP_HOME}/bin${PATH_SEPARATOR}${workspace}/system-bin${PATH_SEPARATOR}${PATH} PNPM_CONFIG_RUNTIME=from-user vp install`

pnpm-backed vp install applies the same system-first policy

```
VITE+ - The Unified Toolchain for the Web

PNPM_CONFIG_RUNTIME=false
```

## `vpt write-file $VP_HOME/package_manager/pnpm/10.18.0/pnpm/bin/pnpm '#'\!'/bin/sh
if [ "$1" = "--version" ]; then printf '\''10.18.0\n'\''; else printf '\''PNPM_CONFIG_RUNTIME=%s\n'\'' "${PNPM_CONFIG_RUNTIME-unset}"; fi
'`


## `vpt write-file $VP_HOME/package_manager/pnpm/10.18.0/pnpm/bin/pnpx '#'\!'/bin/sh
'`


## `vpt chmod +x $VP_HOME/package_manager/pnpm/10.18.0/pnpm/bin/pnpm`


## `vpt chmod +x $VP_HOME/package_manager/pnpm/10.18.0/pnpm/bin/pnpx`


## `vp env on pnpm`


## `PNPM_CONFIG_RUNTIME=from-user pnpm install`

direct managed pnpm disables its duplicate Node.js runtime

```
PNPM_CONFIG_RUNTIME=false
```

## `PNPM_CONFIG_RUNTIME=from-user vp install`

pnpm-backed vp install applies the same managed-pnpm policy

```
VITE+ - The Unified Toolchain for the Web

PNPM_CONFIG_RUNTIME=false
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# pnpm_runtime_preserved_for_mixed_runtimes

Vite+ must not disable pnpm runtimes that it cannot manage.

## `vpt write-file package.json '{"name":"pnpm-runtime-management","private":true,"packageManager":"pnpm@10.18.0","devEngines":{"runtime":[{"name":"node","version":"20.18.0","onFail":"download"},{"name":"deno","version":"2.0.0","onFail":"download"}]}}
'`


## `vpt write-file $VP_HOME/js_runtime/node/20.18.0/bin/node '#'\!'/bin/sh
'`


## `vpt chmod +x $VP_HOME/js_runtime/node/20.18.0/bin/node`


## `vpt chmod +x system-bin/pnpm`


## `vp env on node`


## `vp env off pnpm`


## `PATH=${VP_HOME}/bin${PATH_SEPARATOR}${workspace}/system-bin${PATH_SEPARATOR}${PATH} PNPM_CONFIG_RUNTIME=from-user pnpm install`

direct pnpm preserves runtime management for a mixed declaration

```
PNPM_CONFIG_RUNTIME=from-user
```

## `PATH=${VP_HOME}/bin${PATH_SEPARATOR}${workspace}/system-bin${PATH_SEPARATOR}${PATH} PNPM_CONFIG_RUNTIME=from-user vp install`

vp install preserves the same mixed-runtime setting

```
VITE+ - The Unified Toolchain for the Web

PNPM_CONFIG_RUNTIME=from-user
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# pnpm_runtime_preserved_for_system_first_node

pnpm keeps runtime ownership when Vite+ does not manage Node.js.

## `vpt write-file package.json '{"name":"pnpm-runtime-management","private":true,"packageManager":"pnpm@10.18.0","devEngines":{"runtime":{"name":"node","version":"20.18.0","onFail":"download"}}}
'`


## `vpt chmod +x system-bin/node`


## `vpt chmod +x system-bin/pnpm`


## `vp env off node`


## `vp env off pnpm`


## `PATH=${VP_HOME}/bin${PATH_SEPARATOR}${workspace}/system-bin${PATH_SEPARATOR}${PATH} PNPM_CONFIG_RUNTIME=from-user pnpm install`

direct pnpm preserves runtime management with system-first Node.js

```
PNPM_CONFIG_RUNTIME=from-user
```

## `PATH=${VP_HOME}/bin${PATH_SEPARATOR}${workspace}/system-bin${PATH_SEPARATOR}${PATH} PNPM_CONFIG_RUNTIME=from-user vp install`

vp install preserves the same system-first Node.js policy

```
VITE+ - The Unified Toolchain for the Web

PNPM_CONFIG_RUNTIME=from-user
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/sh
printf 'v20.18.0\n'
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/sh
if [ "$1" = "--version" ]; then
printf '10.18.0\n'
else
printf 'PNPM_CONFIG_RUNTIME=%s\n' "${PNPM_CONFIG_RUNTIME-unset}"
fi
39 changes: 33 additions & 6 deletions crates/vp_global_cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
//! This module defines the CLI structure using clap and routes commands
//! to their appropriate handlers.

use std::{collections::HashSet, ffi::OsStr, process::ExitStatus};
use std::{
collections::{BTreeMap, HashSet},
ffi::OsStr,
process::ExitStatus,
};

use clap::{CommandFactory, FromArgMatches, Parser, Subcommand};
use clap_complete::ArgValueCompleter;
Expand Down Expand Up @@ -675,22 +679,45 @@ async fn run_package_manager_command(

commands::prepend_js_runtime_to_path_env(&cwd).await?;
let selected = commands::env::package_manager::resolve_current_spec(&cwd).await?;
let config = commands::env::config::load_config().await?;
let mut package_manager_env = BTreeMap::new();
if matches!(&command, PackageManagerCommand::Install(_))
Comment thread
liangmiQwQ marked this conversation as resolved.
&& selected.as_ref().is_some_and(|selected| {
selected.package_manager_type == vp_pm_cli::PackageManagerType::Pnpm
})
&& commands::pnpm_runtime::should_disable(&cwd, config.node_shim_mode).await?
Comment thread
liangmiQwQ marked this conversation as resolved.
{
package_manager_env.insert(
commands::pnpm_runtime::PNPM_CONFIG_RUNTIME.to_string(),
commands::pnpm_runtime::PNPM_CONFIG_RUNTIME_DISABLED.to_string(),
);
}
let result = if let Some(selected) = selected.as_ref()
&& commands::env::config::load_config()
.await?
.package_manager_shim_mode_for(selected.package_manager_type)
&& config.package_manager_shim_mode_for(selected.package_manager_type)
== commands::env::config::ShimMode::SystemFirst
&& let Some(system_path) =
crate::shim::dispatch::find_system_tool(&selected.package_manager_type.to_string())
&& let Some(manager) =
system_package_manager(selected.package_manager_type, &system_path).await
{
vp_pm_cli::dispatch_with_resolved_package_manager(&cwd, command, manager).await?
vp_pm_cli::dispatch_with_resolved_package_manager_and_env(
&cwd,
command,
manager,
&package_manager_env,
)
.await?
} else {
let selected = commands::env::package_manager::resolve_current(&cwd).await?;
match selected {
Some(selected) => {
vp_pm_cli::dispatch_with_package_manager(&cwd, command, &selected).await?
vp_pm_cli::dispatch_with_package_manager_and_env(
&cwd,
command,
&selected,
&package_manager_env,
)
.await?
}
None => vp_pm_cli::dispatch_with_metadata(&cwd, command).await?,
}
Expand Down
1 change: 1 addition & 0 deletions crates/vp_global_cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ pub mod config;
pub mod create;
pub mod hooks;
pub mod migrate;
pub(crate) mod pnpm_runtime;
pub mod staged;
pub mod toolchain;
pub mod version;
Expand Down
33 changes: 33 additions & 0 deletions crates/vp_global_cli/src/commands/pnpm_runtime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use vp_shared::PackageJson;
use vt_path::AbsolutePath;

use super::env::config::ShimMode;
use crate::error::Error;

pub(crate) const PNPM_CONFIG_RUNTIME: &str = "PNPM_CONFIG_RUNTIME";
pub(crate) const PNPM_CONFIG_RUNTIME_DISABLED: &str = "false";
Comment thread
liangmiQwQ marked this conversation as resolved.

pub(crate) async fn should_disable(
cwd: &AbsolutePath,
node_shim_mode: ShimMode,
) -> Result<bool, Error> {
if node_shim_mode != ShimMode::Managed {
return Ok(false);
Comment thread
liangmiQwQ marked this conversation as resolved.
}

let workspace = match vt_workspace::find_workspace_root(cwd) {
Ok((workspace, _)) => workspace,
Err(vt_workspace::Error::PackageJsonNotFound(_)) => return Ok(false),
Err(error) => return Err(error.into()),
};
let content = tokio::fs::read_to_string(workspace.path.join("package.json")).await?;
let package_json: PackageJson = serde_json::from_str(&content)?;
Comment thread
liangmiQwQ marked this conversation as resolved.
Outdated
let Some(runtime) = package_json.dev_engines.and_then(|engines| engines.runtime) else {
Comment thread
liangmiQwQ marked this conversation as resolved.
Outdated
return Ok(false);
};
let entries = runtime.entries();

// pnpm's runtime opt-out covers Node.js, Bun, and Deno together, so Vite+
// can use it only when every declared runtime is one Vite+ manages.
Ok(!entries.is_empty() && entries.iter().all(|entry| entry.name == "node"))
Comment thread
liangmiQwQ marked this conversation as resolved.
Outdated
}
36 changes: 34 additions & 2 deletions crates/vp_global_cli/src/shim/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use crate::{
package_metadata::PackageMetadata,
},
global::install::is_protected_shim,
pnpm_runtime,
},
error::Error,
};
Expand Down Expand Up @@ -737,6 +738,37 @@ pub async fn dispatch(tool: &str, args: &[String]) -> i32 {

// Check shim mode from config
let shim_mode = load_shim_mode(tool).await;
let disable_pnpm_runtime =
if PackageManagerType::from_tool(tool) == Some(PackageManagerType::Pnpm) {
Comment thread
liangmiQwQ marked this conversation as resolved.
Outdated
let cwd = match current_dir() {
Comment thread
liangmiQwQ marked this conversation as resolved.
Ok(path) => path,
Err(error) => {
eprintln!("vp: Failed to get current directory: {error}");
return 1;
}
};
let node_shim_mode = match config::load_config().await {
Ok(config) => config.node_shim_mode,
Err(error) => {
eprintln!("vp: Failed to load Node.js shim mode: {error}");
return 1;
}
};
match pnpm_runtime::should_disable(&cwd, node_shim_mode).await {
Ok(disable) => disable,
Err(error) => {
eprintln!("vp: Failed to resolve pnpm runtime management: {error}");
return 1;
}
}
} else {
false
};
let pnpm_runtime_env = if disable_pnpm_runtime {
&[(pnpm_runtime::PNPM_CONFIG_RUNTIME, pnpm_runtime::PNPM_CONFIG_RUNTIME_DISABLED)][..]
} else {
&[]
};
if shim_mode == ShimMode::SystemFirst {
tracing::debug!("system-first mode enabled");
// In system-first mode, try to find system tool first
Expand Down Expand Up @@ -764,7 +796,7 @@ pub async fn dispatch(tool: &str, args: &[String]) -> i32 {
std::env::set_var(env_vars::VP_BYPASS, bypass_val);
}
}
return exec::exec_tool(&system_path, args);
return exec::exec_tool_with_env(&system_path, args, pnpm_runtime_env);
}
// Fall through to managed if system not found
}
Expand Down Expand Up @@ -930,7 +962,7 @@ pub async fn dispatch(tool: &str, args: &[String]) -> i32 {
}

// Execute the tool (normal path — exec replaces process on Unix)
exec::exec_tool(&tool_path, args)
exec::exec_tool_with_env(&tool_path, args, pnpm_runtime_env)
}

fn node_prefix_from_binary(node_path: &AbsolutePath) -> AbsolutePathBuf {
Expand Down
Loading
Loading