Skip to content

fix(cli): stamp package-manager lifecycle env for vp run scripts - #2385

Open
tarikermis wants to merge 7 commits into
voidzero-dev:mainfrom
tarikermis:fix/pm-lifecycle-env
Open

fix(cli): stamp package-manager lifecycle env for vp run scripts#2385
tarikermis wants to merge 7 commits into
voidzero-dev:mainfrom
tarikermis:fix/pm-lifecycle-env

Conversation

@tarikermis

@tarikermis tarikermis commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Closes #2317

What changed

vp run and vpr now set the session-level package-manager lifecycle variables before vt::Session::init snapshots the environment:

  • npm_execpath
  • npm_config_user_agent
  • INIT_CWD
  • npm_node_execpath / NODE

This mirrors the values pnpm, npm, and Yarn set when they run package scripts themselves.

Why

vp run currently adds node_modules/.bin to PATH and sets VP_RUN=1, but leaves the lifecycle environment empty. Child runners such as npm-run-all2 then fall back to npm run inside pnpm projects, which can fail with EBADDEVENGINES when devEngines.packageManager requires pnpm.

The new helper in vp_pm_cli resolves the JS CLI entry used by the package-manager shims and builds the matching user-agent string. The CLI binding stamps those values before the session starts. Node's real process.version and process.execPath are passed through napi so version-manager symlinks are not resolved to the wrong executable.

Per-script fields such as npm_lifecycle_event, npm_lifecycle_script, npm_package_*, and PNPM_SCRIPT_SRC_DIR are deliberately left out because they cannot be set correctly once per session. Bun is also left unchanged because I could not verify its lifecycle contract.

Verification

The repro pins pnpm in devEngines.packageManager, runs npm-run-all2, and places a failing npm stub on PATH.

  • On main, the lifecycle variables are unset and npm-run-all2 invokes the npm stub.
  • With this change, the variables match pnpm's values and the child scripts run through pnpm successfully.
  • cargo test -p vp_pm_cli: 746 passed.
  • cargo test -p vite-plus-cli: 37 passed.
  • Cargo clippy and format checks pass.
  • pnpm fmt and pnpm lint report no findings in changed files. The repo-wide lint still reports 18 existing errors in untouched docs/.vitepress/* files.

Notes

Windows was not available for a local run, but the JS-entry lookup and pnpm native .exe fallback are covered by platform-aware tests. vp and vpr share the same run() call site, so both use the new environment.

Best Regards, Tarik

@netlify

netlify Bot commented Aug 9, 2026

Copy link
Copy Markdown

Deploy Preview for viteplus-preview ready!

Name Link
🔨 Latest commit 7fbaed6
🔍 Latest deploy log https://app.netlify.com/projects/viteplus-preview/deploys/6a87fd93c62e740008626538
😎 Deploy Preview https://deploy-preview-2385--viteplus-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@fengmk2
fengmk2 requested a review from wan9chi August 10, 2026 06:24
@fengmk2 fengmk2 self-assigned this Aug 10, 2026
@fengmk2

fengmk2 commented Aug 10, 2026

Copy link
Copy Markdown
Member

@tarikermis can you also add a new snapshot test to cover this bug fix?

@tarikermis

Copy link
Copy Markdown
Contributor 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.
@fengmk2

fengmk2 commented Aug 10, 2026

Copy link
Copy Markdown
Member

@tarikermis I mean this snapshot tests https://github.com/voidzero-dev/vite-plus/tree/main/crates/vp_cli_snapshots/tests/cli_snapshots .
Sorry I didn't explain clearly.

@tarikermis

Copy link
Copy Markdown
Contributor 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.

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()));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there source documentation for the naming of these environment variables? Or reference code? Need to add code comments to explain them.

@tarikermis

Copy link
Copy Markdown
Contributor 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.
# Conflicts:
#	packages/cli/binding/index.d.cts
#	packages/cli/binding/src/cli/mod.rs
#	packages/cli/binding/src/lib.rs
let mut vars = vec![
("npm_execpath", self.lifecycle_exec_path().as_path().as_os_str().to_os_string()),
(
"npm_config_user_agent",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to emulate the package manager lifecycle environment this broadly to fix #2317? The original compatibility issue is primarily package-manager detection through npm_execpath. Every additional emulated variable becomes a compatibility contract that we need to keep aligned with npm/pnpm/Yarn. Could we keep this helper minimal unless another variable is required by an actual compatibility case?

@simulacre7

Copy link
Copy Markdown
Contributor

While digging into #2317 myself I traced the full spawn path through the pinned vite-task rev (d05b1dc) and hit one gap in this approach that the snapshot fixture can't catch — sharing since it seems cheap to address before merge.

Cache-enabled tasks strip the stamped env again. Stamping the process env before Session::init works for plain package.json scripts because they default to cache: scripts: false, and the full session env flows to the child. But when a task is cache-enabled (cache: { scripts: true }, or run.tasks from vite.config.ts, which cache by default), plan_spawn_execution filters the spawn env down to the configured patterns first:

  • vt_plan/src/plan.rsEnvFingerprints::resolve(&mut spawn_envs, &cache_config.env_config) keeps only names matching untracked_env + env;
  • DEFAULT_UNTRACKED_ENV (vt_graph/src/config/mod.rs) contains no lowercase npm_* names (only NPM_CONFIG_STORE_DIR), and matching is case-sensitive on Unix (vt_glob/src/env.rs).

So for a cached task, npm_execpath / npm_config_user_agent / npm_node_execpath / INIT_CWD get silently dropped, and npm-run-all2 falls back to npm again — exactly the #2317 symptom, now only in the cached case. The VP_RUN marker had the same problem and vite-task solves it by re-inserting the marker after the filter (vt_plan/src/plan.rs, next to the MARKER_ENV_NAME insert, with a comment explaining this exact reasoning).

Possible directions, both upstream in vite-task: add the lifecycle names to DEFAULT_UNTRACKED_ENV, or re-insert them post-filter like the marker. Either way it might be worth a note in this PR (or a follow-up issue) so the cached-task case doesn't regress the fix.

Caveat: this is from reading the pinned vite-task sources, not from running a cached task against this branch — happy to be corrected if the filtering doesn't apply where I think it does.

@simulacre7

Copy link
Copy Markdown
Contributor

Filed the upstream half as voidzero-dev/vite-task#692 (post-filter re-insertion for cached tasks + per-task npm_lifecycle_event), so the cached-task gap has a tracking home either way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

vpr appears not to preserve the pnpm lifecycle environment, causing npm-run-all2 to fall back to npm

4 participants