diff --git a/crates/vite_global_cli/src/shim/dispatch.rs b/crates/vite_global_cli/src/shim/dispatch.rs index c4c1e7f606..7ef124d155 100644 --- a/crates/vite_global_cli/src/shim/dispatch.rs +++ b/crates/vite_global_cli/src/shim/dispatch.rs @@ -714,6 +714,23 @@ async fn resolve_matching_package_manager_tool( Ok(Some(package_manager_bin_path(&install_dir, bin_name))) } +async fn prepend_js_child_process_path_env( + cwd: &AbsolutePath, + node_bin_dir: &AbsolutePath, +) -> Result<(), Error> { + let _ = prepend_to_path_env(node_bin_dir, PrependOptions::default()); + + let Some(npm_path) = resolve_matching_package_manager_tool(cwd, "npm").await? else { + return Ok(()); + }; + if let Some(pm_bin_dir) = npm_path.parent() + && pm_bin_dir != node_bin_dir + { + let _ = prepend_to_path_env(pm_bin_dir, PrependOptions::default()); + } + Ok(()) +} + /// Main shim dispatch entry point. /// /// Called when the binary is invoked as node, npm, npx, corepack, or a @@ -861,7 +878,10 @@ pub async fn dispatch(tool: &str, args: &[String]) -> i32 { // version was selected from `packageManager`, put that PM bin dir first so // nested invocations see the same PM version while recursion prevention is set. let node_bin_dir = node_path.parent().expect("Node has no parent directory"); - let _ = prepend_to_path_env(node_bin_dir, PrependOptions::default()); + if let Err(e) = prepend_js_child_process_path_env(&cwd, node_bin_dir).await { + eprintln!("vp: Failed to resolve package manager for child process PATH: {e}"); + return 1; + } if let Some(pm_bin_dir) = tool_path.parent() && pm_bin_dir != node_bin_dir { @@ -960,10 +980,15 @@ async fn dispatch_package_binary(tool: &str, args: &[String]) -> i32 { match ensure_installed(&node_version).await { Ok(node_path) => { if let Some(node_bin_dir) = node_path.parent() { - let _ = prepend_to_path_env( - node_bin_dir, - PrependOptions::default(), - ); + if let Err(e) = + prepend_js_child_process_path_env(&cwd, node_bin_dir).await + { + eprintln!( + "vp: Failed to resolve package manager for child \ + process PATH: {e}" + ); + return 1; + } } } Err(e) => { @@ -1066,7 +1091,13 @@ pub(crate) async fn package_binary_invocation( // Prepare environment for recursive invocations let node_bin_dir = node_path.parent().ok_or_else(|| "Node has no parent directory".to_string())?; - let _ = prepend_to_path_env(node_bin_dir, PrependOptions::default()); + if let Ok(cwd) = current_dir() { + prepend_js_child_process_path_env(&cwd, node_bin_dir).await.map_err(|e| { + format!("Failed to resolve package manager for child process PATH: {e}") + })?; + } else { + let _ = prepend_to_path_env(node_bin_dir, PrependOptions::default()); + } // JS binaries (determined at install time and stored in metadata) run // through node; native executables run directly. diff --git a/packages/cli/snap-tests-global/env-node-child-process-npm/node/package.json b/packages/cli/snap-tests-global/env-node-child-process-npm/node/package.json new file mode 100644 index 0000000000..9ee9eb71be --- /dev/null +++ b/packages/cli/snap-tests-global/env-node-child-process-npm/node/package.json @@ -0,0 +1,13 @@ +{ + "name": "@env-node-child-process-npm/node", + "version": "1.0.0", + "private": true, + "type": "module", + "devEngines": { + "runtime": { + "name": "node", + "version": "20.18.0", + "onFail": "download" + } + } +} diff --git a/packages/cli/snap-tests-global/env-node-child-process-npm/package.json b/packages/cli/snap-tests-global/env-node-child-process-npm/package.json new file mode 100644 index 0000000000..3dbc1ca591 --- /dev/null +++ b/packages/cli/snap-tests-global/env-node-child-process-npm/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/packages/cli/snap-tests-global/env-node-child-process-npm/print-path.js b/packages/cli/snap-tests-global/env-node-child-process-npm/print-path.js new file mode 100644 index 0000000000..0312f10ab1 --- /dev/null +++ b/packages/cli/snap-tests-global/env-node-child-process-npm/print-path.js @@ -0,0 +1,6 @@ +import { execFileSync } from 'node:child_process'; + +const npmPath = execFileSync('which', ['npm'], { encoding: 'utf8' }).trim(); +const normalizedNpmPath = npmPath.split('/').join('/'); + +console.log(normalizedNpmPath); diff --git a/packages/cli/snap-tests-global/env-node-child-process-npm/print-version.js b/packages/cli/snap-tests-global/env-node-child-process-npm/print-version.js new file mode 100644 index 0000000000..f47dedf3d0 --- /dev/null +++ b/packages/cli/snap-tests-global/env-node-child-process-npm/print-version.js @@ -0,0 +1,6 @@ +import { execFileSync } from 'node:child_process'; + +const versionOutput = execFileSync('npm', ['--version'], { encoding: 'utf8' }).trim(); +const version = versionOutput.split(/\r?\n/).at(-1); + +console.log(version); diff --git a/packages/cli/snap-tests-global/env-node-child-process-npm/snap.txt b/packages/cli/snap-tests-global/env-node-child-process-npm/snap.txt new file mode 100644 index 0000000000..fec9abd1ed --- /dev/null +++ b/packages/cli/snap-tests-global/env-node-child-process-npm/snap.txt @@ -0,0 +1,5 @@ +> cd node && test "$(npm --version)" = "$(node ../print-version.js)" && node ../print-path.js +/js_runtime/node//bin/npm + +> cd specific && test "$(npm --version)" = "$(node ../print-version.js)" && test "$(node ../print-version.js)" = "$(node ./print-package-json-pm-version.js)" && node ../print-path.js +/package_manager/npm//npm/bin/npm diff --git a/packages/cli/snap-tests-global/env-node-child-process-npm/specific/package.json b/packages/cli/snap-tests-global/env-node-child-process-npm/specific/package.json new file mode 100644 index 0000000000..7c485a7d28 --- /dev/null +++ b/packages/cli/snap-tests-global/env-node-child-process-npm/specific/package.json @@ -0,0 +1,18 @@ +{ + "name": "@env-node-child-process-npm/specific", + "version": "1.0.0", + "private": true, + "type": "module", + "devEngines": { + "packageManager": { + "name": "npm", + "version": "11.17.0", + "onFail": "download" + }, + "runtime": { + "name": "node", + "version": "20.18.0", + "onFail": "download" + } + } +} diff --git a/packages/cli/snap-tests-global/env-node-child-process-npm/specific/print-package-json-pm-version.js b/packages/cli/snap-tests-global/env-node-child-process-npm/specific/print-package-json-pm-version.js new file mode 100644 index 0000000000..910648d9f2 --- /dev/null +++ b/packages/cli/snap-tests-global/env-node-child-process-npm/specific/print-package-json-pm-version.js @@ -0,0 +1,3 @@ +import packageJson from './package.json' assert { type: 'json' }; + +console.log(packageJson.devEngines.packageManager.version); diff --git a/packages/cli/snap-tests-global/env-node-child-process-npm/steps.json b/packages/cli/snap-tests-global/env-node-child-process-npm/steps.json new file mode 100644 index 0000000000..25c1117544 --- /dev/null +++ b/packages/cli/snap-tests-global/env-node-child-process-npm/steps.json @@ -0,0 +1,7 @@ +{ + "ignoredPlatforms": ["win32"], + "commands": [ + "cd node && test \"$(npm --version)\" = \"$(node ../print-version.js)\" && node ../print-path.js", + "cd specific && test \"$(npm --version)\" = \"$(node ../print-version.js)\" && test \"$(node ../print-version.js)\" = \"$(node ./print-package-json-pm-version.js)\" && node ../print-path.js" + ] +}