fix(cli): stamp package-manager lifecycle env for vp run scripts - #2385
Open
tarikermis wants to merge 4 commits into
Open
fix(cli): stamp package-manager lifecycle env for vp run scripts#2385tarikermis wants to merge 4 commits into
tarikermis wants to merge 4 commits into
Conversation
✅ Deploy Preview for viteplus-preview canceled.
|
Member
|
@tarikermis can you also add a new snapshot test to cover this bug fix? |
Author
|
Good call, on it - adding a snapshot test for the fix now. Best Regards, Tarik |
Covers voidzero-dev#2317: snapshot the session-constant lifecycle env computed for fixture package-manager install layouts (pnpm/npm/yarn JS CLI entries, native pnpm binary, shim fallback, and bun's empty stamp) so a regression that drops the stamp or changes exec-path resolution fails the test.
Member
|
@tarikermis I mean this snapshot tests https://github.com/voidzero-dev/vite-plus/tree/main/crates/vp_cli_snapshots/tests/cli_snapshots . |
Author
|
Ah my bad, got it - adding a CLI snapshot test in vp_cli_snapshots now. Best Regards, Tarik |
CLI-level regression test for voidzero-dev#2317: a fixture pnpm project runs a package.json script via vp run that surfaces npm_execpath, npm_config_user_agent, and INIT_CWD. Pre-fix all three were undefined in the script process, so child tooling like npm-run-all fell back to npm. A fake managed pnpm install under VP_HOME keeps the case offline.
fengmk2
reviewed
Aug 10, 2026
| --- | ||
| source: crates/vp_pm_cli/src/lifecycle_env.rs | ||
| expression: "render_lifecycle_stamp(PackageManagerType::Pnpm, \"11.20.0\", &[])" | ||
| --- |
fengmk2
reviewed
Aug 10, 2026
|
|
||
| if let Some(node_execpath) = &context.node_execpath { | ||
| vars.push(("npm_node_execpath", node_execpath.as_os_str().to_os_string())); | ||
| vars.push(("NODE", node_execpath.as_os_str().to_os_string())); |
Member
There was a problem hiding this comment.
Is there source documentation for the naming of these environment variables? Or reference code? Need to add code comments to explain them.
Author
|
Sure - reverting the insta snapshots and adding source comments explaining the env var naming. Best Regards, Tarik |
Revert the crate-level insta snapshot tests (the CLI-level vp_cli_snapshots case covers the bug fix end to end), and document where each lifecycle env var name and format comes from: npm's set-envs.js and user-agent definition, pnpm's @pnpm/npm-lifecycle and config userAgent, verified against pnpm 11.21.0 and npm 10.9.8.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
vp run/vprnow stamp the package-manager lifecycle environment (npm_execpath,npm_config_user_agent,INIT_CWD,npm_node_execpath/NODE) into the process env beforevt::Session::initsnapshots it, mirroring what pnpm/npm/yarn set when they runpackage.jsonscripts themselves.Closes #2317
Why
When pnpm runs a script it stamps these variables so child tooling can tell which package manager owns the run.
vp runexecutes scripts via vite-task, which only prependsnode_modules/.bintoPATHand setsVP_RUN=1— the lifecycle variables stay unset. As a result, child runners such as npm-run-all2 (lib/run-task.jsdetects the package manager fromnpm_execpath/npm_config_user_agent) fall back to spawningnpm runinside pnpm projects, which fails withEBADDEVENGINESwhendevEngines.packageManagerrequires pnpm.How
crates/vp_pm_cli/src/lifecycle_env.rs(new):PackageManager::lifecycle_exec_path()andPackageManager::lifecycle_env_vars()build the session-constant subset of the lifecycle env from the resolvedPackageManager.npm_execpathprefers the JS CLI entry (bin/pnpm.cjs,bin/npm-cli.js,bin/yarn.js— the same filescreate_shim_filespoints the shims at) because child runners execute.js/.cjsvalues through the current Node.js binary, which works on every platform, unlike extensionless shims on Windows; it falls back topnpm.native[.exe]for pnpm ≥ 12 native installs and then to the bin shim (with adebug!log, since that path re-exposes the Windows problem). The user agent matches what the package managers emit —pnpm/11.20.0 npm/? node/v22.23.1 linux x64,npm/10.9.8 node/v22.23.1 linux x64 workspaces/false— verified against real pnpm 11.20.0 / npm 10.9.8 stamps. npm'sworkspaces/flag staysfalse(it tracks npm's--workspacesflag, whichvp runhas no analogue of; checked against npm inside a workspace root). bun stamps nothing: whatbun runsets is unverified, so its env is left untouched rather than guessed.packages/cli/binding/src/cli/lifecycle_env.rs(new): stamps the vars into the process env inexecute_vite_task_command, inside the existingPackageManagerblock that already prepends the PM bin dir toPATH, i.e. beforeSession::initsnapshots the environment.INIT_CWDis the real process cwd (pnpm's semantics — unaffected by--cwd). PM resolution failure now logs at debug level instead of silently skipping.nodeVersion: process.versionandnodeExecPath: process.execPathare passed through the napiCliOptionsso the user agent andnpm_node_execpath/NODEcarry the real host Node values (rather than deriving them fromcurrent_exe(), which would resolve version-manager symlinks and break under any non-napi entry point).Deliberately out of scope: per-script variables (
npm_lifecycle_event,npm_lifecycle_script,npm_package_*,PNPM_SCRIPT_SRC_DIR) name the running script or its owning package — they can't be stamped correctly once per session, so they belong in the task engine (vite-task), not here.Verification
Repro project:
package.jsonwithdevEngines.packageManagerpinned to pnpm, adumpenvscript echoing the lifecycle variables, andcheck: run-p child:a child:b(npm-run-all2), plus a stubbednpmonPATHthat fails loudly if invoked.main: all variables<unset>;run-pspawned the npm stub for its children.npm_execpath=…/pnpm/11.20.0/pnpm/bin/pnpm.cjs,npm_config_user_agent=pnpm/11.20.0 npm/? node/v22.23.1 linux x64, …);run-pruns children throughpnpm.cjsandchecksucceeds.Checks run locally:
cargo test -p vp_pm_cli— 746 passed, 0 failed (10 new tests: exec-path selection incl. native pnpm, per-PM var sets, user-agent formats, bun opt-out, platform/arch spellings)cargo test -p vite-plus-cli— 37 passed, 0 failedcargo clippy -p vp_pm_cli -p vite-plus-cli --all-targets --all-features -- --deny warnings(with thejust lintallow-list) — cleancargo fmt --all -- --check— cleanpnpm fmt/pnpm lint— no findings in changed files (repo-widevp lintreports 18 pre-existing type errors in untoucheddocs/.vitepress/*files in this environment)process.execPath,PNPM_SCRIPT_SRC_DIRis per-script and was dropped, silent fallbacks, SAFETY-comment wording, Windows test paths) were addressed; round two came back LGTM.Notes / limitations
vpandvprshare the singlerun()call site (bin/vp/bin/vprboth loaddist/bin.js), so both get the stamped env.npm_execpathis specifically motivated by Windows shim execution, the pnpm ≥ 12 native path handles.exe, and path-sensitive tests pick Windows/Unix expectations viacfg!(windows).PNPM_SCRIPT_SRC_DIR) need vite-task (separate repo). Related follow-up: whenvp runis itself launched from an outer PM lifecycle script, the outernpm_lifecycle_*/npm_package_*vars survive next to the rewrittennpm_execpath— overwriting those also belongs to vite-task.unsafe { set_var }call inherits the risk profile of the existingPATHprepend right above it (same startup window, before task threads); the SAFETY comment says as much. Threading the values throughSessionConfigas an explicit env overlay instead would remove the unsafe block, but that's a vite-task change.OsString, so non-UTF-8 install paths survive unmangled.