From 0f99bfb6571f3ce43b70c919593b390e93b22b3e Mon Sep 17 00:00:00 2001 From: Liang Date: Thu, 10 Sep 2026 22:49:55 +0800 Subject: [PATCH 1/4] fix(env): isolate direct shims from package manager selection --- .../check.cjs | 15 +++++++++++ .../package.json | 5 ++++ .../snapshots.toml | 11 ++++++++ ...election_override_does_not_change_shims.md | 25 +++++++++++++++++++ .../src/commands/env/package_manager.rs | 18 +++++++++++++ crates/vp_global_cli/src/shim/dispatch.rs | 2 +- docs/guide/env.md | 4 ++- 7 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/check.cjs create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/package.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/snapshots.toml create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/snapshots/selection_override_does_not_change_shims.md diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/check.cjs b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/check.cjs new file mode 100644 index 0000000000..03054f0883 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/check.cjs @@ -0,0 +1,15 @@ +const assert = require('node:assert/strict'); +const { execFileSync } = require('node:child_process'); +const { delimiter } = require('node:path'); + +// Start each child at the shim even though the parent Node process has injected tool paths. +const env = { + ...process.env, + PATH: [process.env.VP_HOME + '/bin', '/usr/bin', '/bin'].join(delimiter), + VP_PATH_INJECTED_TOOLS: '', +}; +const [tool, expected] = process.argv.slice(2); +const args = tool === 'vp' ? ['install', '--', '--version'] : ['--version']; +const actual = execFileSync(tool, args, { env, encoding: 'utf8' }).trim(); +assert.equal(actual, expected); +console.log(`${tool} uses the expected version`); diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/package.json new file mode 100644 index 0000000000..9447434d78 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/package.json @@ -0,0 +1,5 @@ +{ + "name": "shim-package-manager-version-overrides", + "private": true, + "packageManager": "pnpm@10.18.0" +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/snapshots.toml new file mode 100644 index 0000000000..88fe3a5ffc --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/snapshots.toml @@ -0,0 +1,11 @@ +[[case]] +name = "selection_override_does_not_change_shims" +vp = "global" +skip-platforms = ["windows"] +env = { VP_PACKAGE_MANAGER = "pnpm@10.19.0", npm_config_manage_package_manager_versions = "false" } +steps = [ + { argv = ["node", "check.cjs", "pnpm", "10.18.0"], comment = "The matching shim still uses the project pin" }, + { argv = ["node", "check.cjs", "vp", "10.19.0"], comment = "vp install still uses VP_PACKAGE_MANAGER" }, + { argv = ["node", "check.cjs", "pnpm", "10.18.0"], envs = [["VP_PACKAGE_MANAGER", "invalid"]], comment = "An invalid selection does not affect direct shims" }, +] + diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/snapshots/selection_override_does_not_change_shims.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/snapshots/selection_override_does_not_change_shims.md new file mode 100644 index 0000000000..377de28076 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/snapshots/selection_override_does_not_change_shims.md @@ -0,0 +1,25 @@ +# selection_override_does_not_change_shims + +## `node check.cjs pnpm 10.18.0` + +The matching shim still uses the project pin + +``` +pnpm uses the expected version +``` + +## `node check.cjs vp 10.19.0` + +vp install still uses VP_PACKAGE_MANAGER + +``` +vp uses the expected version +``` + +## `VP_PACKAGE_MANAGER=invalid node check.cjs pnpm 10.18.0` + +An invalid selection does not affect direct shims + +``` +pnpm uses the expected version +``` diff --git a/crates/vp_global_cli/src/commands/env/package_manager.rs b/crates/vp_global_cli/src/commands/env/package_manager.rs index 0559ded0bf..dfd823ef1a 100644 --- a/crates/vp_global_cli/src/commands/env/package_manager.rs +++ b/crates/vp_global_cli/src/commands/env/package_manager.rs @@ -13,6 +13,24 @@ pub(crate) async fn resolve_current( resolve_current_for(cwd, None).await } +/// Selecting a manager for vp commands must not change direct shim versions. +pub(crate) async fn resolve_shim_for( + cwd: &AbsolutePath, + expected: PackageManagerType, +) -> Result, Error> { + let session = config::read_session_package_manager().await; + let session = session.as_deref().map(parse_package_manager_spec_with_hash).transpose()?; + let default = configured_default_for(&config::load_config().await?, expected)?; + resolve_environment_package_manager( + cwd, + session.as_ref().map(|(kind, version, hash)| (*kind, version.as_str(), hash.as_deref())), + default.as_ref().map(|(kind, version, hash)| (*kind, version.as_str(), hash.as_deref())), + Some(expected), + ) + .await + .map_err(Error::from) +} + pub(crate) async fn resolve_current_for( cwd: &AbsolutePath, expected: Option, diff --git a/crates/vp_global_cli/src/shim/dispatch.rs b/crates/vp_global_cli/src/shim/dispatch.rs index 2fbd388e18..40ca462c49 100644 --- a/crates/vp_global_cli/src/shim/dispatch.rs +++ b/crates/vp_global_cli/src/shim/dispatch.rs @@ -661,7 +661,7 @@ async fn resolve_package_manager_tool( return Ok(None); }; - let resolution = package_manager::resolve_current_for(cwd, Some(expected_type)).await?; + let resolution = package_manager::resolve_shim_for(cwd, expected_type).await?; let (version, hash) = match resolution { Some(resolution) => (resolution.version, resolution.hash), None if expected_type == PackageManagerType::Npm => return Ok(None), diff --git a/docs/guide/env.md b/docs/guide/env.md index b20f41b688..f027b30204 100644 --- a/docs/guide/env.md +++ b/docs/guide/env.md @@ -54,7 +54,9 @@ Package-manager selection uses this priority: 6. The named package manager's global default version 7. The named shim's latest release -A selected manager controls only its named shims. For example, pnpm controls `pnpm` and `pnpx`; invoking `npm` still resolves npm independently. Alias pairs are `npm`/`npx`, `pnpm`/`pnpx`, `yarn`/`yarnpkg`, and `bun`/`bunx`. Without a matching project selection, a named shim uses its configured default version and otherwise uses the latest release without prompting. The resolved version is cached for one hour and an expired cache remains available when the registry cannot be reached. The directly invoked npm shim keeps its Node-bundled fallback, while an explicit `vp env ... npm` family scope uses standalone npm's latest release. +`VP_PACKAGE_MANAGER` selects the manager and version for commands such as `vp install`. Direct package-manager shims ignore this variable and continue to resolve their versions from the session file, project configuration, and family default. + +A project selection controls only its named shims. For example, pnpm controls `pnpm` and `pnpx`; invoking `npm` still resolves npm independently. Alias pairs are `npm`/`npx`, `pnpm`/`pnpx`, `yarn`/`yarnpkg`, and `bun`/`bunx`. Without a matching project selection, a named shim uses its configured default version and otherwise uses the latest release without prompting. The resolved version is cached for one hour and an expired cache remains available when the registry cannot be reached. The directly invoked npm shim keeps its Node-bundled fallback, while an explicit `vp env ... npm` family scope uses standalone npm's latest release. A fresh install uses the split platform layout by default. On Unix, Vite+ stores managed runtimes and related files in `~/.local/share/vite-plus`. It From 433aa8a9d4f7fce226e7ae54e1c03b22ef4376b4 Mon Sep 17 00:00:00 2001 From: Liang Date: Thu, 10 Sep 2026 23:08:11 +0800 Subject: [PATCH 2/4] chore(test): remove trailing blank line in shim fixture --- .../shim_package_manager_version_overrides/snapshots.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/snapshots.toml index 88fe3a5ffc..80b898dd49 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/snapshots.toml @@ -8,4 +8,3 @@ steps = [ { argv = ["node", "check.cjs", "vp", "10.19.0"], comment = "vp install still uses VP_PACKAGE_MANAGER" }, { argv = ["node", "check.cjs", "pnpm", "10.18.0"], envs = [["VP_PACKAGE_MANAGER", "invalid"]], comment = "An invalid selection does not affect direct shims" }, ] - From e64657051966734a9c1be2ab48f3a03b5d43f383 Mon Sep 17 00:00:00 2001 From: Liang Date: Fri, 11 Sep 2026 06:00:06 +0800 Subject: [PATCH 3/4] test: align npm path reset coverage with shim isolation --- .../shim_injected_tool_contracts/snapshots.toml | 12 +++++++++++- .../explicit_npm_after_partial_path_reset.md | 4 ++-- .../pinned_npm_after_partial_path_reset.md | 15 +++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_injected_tool_contracts/snapshots/pinned_npm_after_partial_path_reset.md diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_injected_tool_contracts/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_injected_tool_contracts/snapshots.toml index ba5b0bf731..07152bcd18 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_injected_tool_contracts/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_injected_tool_contracts/snapshots.toml @@ -156,5 +156,15 @@ name = "explicit_npm_after_partial_path_reset" vp = "global" skip-platforms = ["windows"] steps = [ - { argv = ["vp", "env", "exec", "--node", "22.18.0", "--npm", "10.5.0", "node", "assert-unix-shim-path.cjs", "partial", "10.5.0"], comment = "An explicit npm selection still takes priority over bundled npm after a PATH reset" }, + { argv = ["vp", "env", "exec", "--node", "22.18.0", "--npm", "10.5.0", "node", "assert-unix-shim-path.cjs", "partial"], comment = "After removing the injected npm from PATH, direct shims ignore VP_PACKAGE_MANAGER and use bundled npm" }, +] + +[[case]] +name = "pinned_npm_after_partial_path_reset" +vp = "global" +skip-platforms = ["windows"] +steps = [ + { argv = ["vpt", "json-edit", "package.json", "packageManager", "npm@10.5.0"], snapshot = false }, + { argv = ["vp", "env", "on", "npm"], snapshot = false }, + { argv = ["vp", "env", "exec", "--node", "22.18.0", "node", "assert-unix-shim-path.cjs", "partial", "10.5.0"], comment = "A project npm pin still takes priority over bundled npm after a PATH reset" }, ] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_injected_tool_contracts/snapshots/explicit_npm_after_partial_path_reset.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_injected_tool_contracts/snapshots/explicit_npm_after_partial_path_reset.md index 294e5368fc..00d3b20837 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_injected_tool_contracts/snapshots/explicit_npm_after_partial_path_reset.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_injected_tool_contracts/snapshots/explicit_npm_after_partial_path_reset.md @@ -1,8 +1,8 @@ # explicit_npm_after_partial_path_reset -## `vp env exec --node 22.18.0 --npm 10.5.0 node assert-unix-shim-path.cjs partial 10.5.0` +## `vp env exec --node 22.18.0 --npm 10.5.0 node assert-unix-shim-path.cjs partial` -An explicit npm selection still takes priority over bundled npm after a PATH reset +After removing the injected npm from PATH, direct shims ignore VP_PACKAGE_MANAGER and use bundled npm ``` Node and its tools survive partial PATH diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_injected_tool_contracts/snapshots/pinned_npm_after_partial_path_reset.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_injected_tool_contracts/snapshots/pinned_npm_after_partial_path_reset.md new file mode 100644 index 0000000000..5833c84221 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_injected_tool_contracts/snapshots/pinned_npm_after_partial_path_reset.md @@ -0,0 +1,15 @@ +# pinned_npm_after_partial_path_reset + +## `vpt json-edit package.json packageManager npm@10.5.0` + + +## `vp env on npm` + + +## `vp env exec --node 22.18.0 node assert-unix-shim-path.cjs partial 10.5.0` + +A project npm pin still takes priority over bundled npm after a PATH reset + +``` +Node and its tools survive partial PATH +``` From 5569accaffdbc4dfe5bf2de0886e03e5a5dd4150 Mon Sep 17 00:00:00 2001 From: Liang Date: Fri, 11 Sep 2026 06:02:16 +0800 Subject: [PATCH 4/4] test: remove obsolete package manager selection assertions --- .../snapshots.toml | 8 ------ .../explicit_npm_after_partial_path_reset.md | 9 ------- .../check.cjs | 15 ----------- .../package.json | 5 ---- .../snapshots.toml | 10 -------- ...election_override_does_not_change_shims.md | 25 ------------------- 6 files changed, 72 deletions(-) delete mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_injected_tool_contracts/snapshots/explicit_npm_after_partial_path_reset.md delete mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/check.cjs delete mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/package.json delete mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/snapshots.toml delete mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/snapshots/selection_override_does_not_change_shims.md diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_injected_tool_contracts/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_injected_tool_contracts/snapshots.toml index 07152bcd18..e27fd6c4a8 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_injected_tool_contracts/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_injected_tool_contracts/snapshots.toml @@ -151,14 +151,6 @@ steps = [ { argv = ["./system-shims/node", "assert-system-node-shim.cjs", "preload"], envs = [["PATH", "${VP_HOME}/bin${PATH_SEPARATOR}${workspace}/system-shims${PATH_SEPARATOR}/usr/bin${PATH_SEPARATOR}/bin"]], comment = "Preload output cannot corrupt the runtime probe, and npm/npx and their Node children still execute the preload" }, ] -[[case]] -name = "explicit_npm_after_partial_path_reset" -vp = "global" -skip-platforms = ["windows"] -steps = [ - { argv = ["vp", "env", "exec", "--node", "22.18.0", "--npm", "10.5.0", "node", "assert-unix-shim-path.cjs", "partial"], comment = "After removing the injected npm from PATH, direct shims ignore VP_PACKAGE_MANAGER and use bundled npm" }, -] - [[case]] name = "pinned_npm_after_partial_path_reset" vp = "global" diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_injected_tool_contracts/snapshots/explicit_npm_after_partial_path_reset.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_injected_tool_contracts/snapshots/explicit_npm_after_partial_path_reset.md deleted file mode 100644 index 00d3b20837..0000000000 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_injected_tool_contracts/snapshots/explicit_npm_after_partial_path_reset.md +++ /dev/null @@ -1,9 +0,0 @@ -# explicit_npm_after_partial_path_reset - -## `vp env exec --node 22.18.0 --npm 10.5.0 node assert-unix-shim-path.cjs partial` - -After removing the injected npm from PATH, direct shims ignore VP_PACKAGE_MANAGER and use bundled npm - -``` -Node and its tools survive partial PATH -``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/check.cjs b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/check.cjs deleted file mode 100644 index 03054f0883..0000000000 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/check.cjs +++ /dev/null @@ -1,15 +0,0 @@ -const assert = require('node:assert/strict'); -const { execFileSync } = require('node:child_process'); -const { delimiter } = require('node:path'); - -// Start each child at the shim even though the parent Node process has injected tool paths. -const env = { - ...process.env, - PATH: [process.env.VP_HOME + '/bin', '/usr/bin', '/bin'].join(delimiter), - VP_PATH_INJECTED_TOOLS: '', -}; -const [tool, expected] = process.argv.slice(2); -const args = tool === 'vp' ? ['install', '--', '--version'] : ['--version']; -const actual = execFileSync(tool, args, { env, encoding: 'utf8' }).trim(); -assert.equal(actual, expected); -console.log(`${tool} uses the expected version`); diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/package.json deleted file mode 100644 index 9447434d78..0000000000 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "shim-package-manager-version-overrides", - "private": true, - "packageManager": "pnpm@10.18.0" -} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/snapshots.toml deleted file mode 100644 index 80b898dd49..0000000000 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/snapshots.toml +++ /dev/null @@ -1,10 +0,0 @@ -[[case]] -name = "selection_override_does_not_change_shims" -vp = "global" -skip-platforms = ["windows"] -env = { VP_PACKAGE_MANAGER = "pnpm@10.19.0", npm_config_manage_package_manager_versions = "false" } -steps = [ - { argv = ["node", "check.cjs", "pnpm", "10.18.0"], comment = "The matching shim still uses the project pin" }, - { argv = ["node", "check.cjs", "vp", "10.19.0"], comment = "vp install still uses VP_PACKAGE_MANAGER" }, - { argv = ["node", "check.cjs", "pnpm", "10.18.0"], envs = [["VP_PACKAGE_MANAGER", "invalid"]], comment = "An invalid selection does not affect direct shims" }, -] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/snapshots/selection_override_does_not_change_shims.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/snapshots/selection_override_does_not_change_shims.md deleted file mode 100644 index 377de28076..0000000000 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/shim_package_manager_version_overrides/snapshots/selection_override_does_not_change_shims.md +++ /dev/null @@ -1,25 +0,0 @@ -# selection_override_does_not_change_shims - -## `node check.cjs pnpm 10.18.0` - -The matching shim still uses the project pin - -``` -pnpm uses the expected version -``` - -## `node check.cjs vp 10.19.0` - -vp install still uses VP_PACKAGE_MANAGER - -``` -vp uses the expected version -``` - -## `VP_PACKAGE_MANAGER=invalid node check.cjs pnpm 10.18.0` - -An invalid selection does not affect direct shims - -``` -pnpm uses the expected version -```