mirror of
https://github.com/pnpm/pnpm.git
synced 2026-06-05 06:54:48 -04:00
Closes #11690. A dependency that declares `engines.runtime` in its manifest carries the desugared `dependencies.node: 'runtime:<version>'` pin in the lockfile, and pnpm's bin linker spawns that dep's lifecycle scripts through the pinned Node downloaded into `<pkgDir>/node_modules/node/`. The GVS hash and the side-effects-cache key prefix were still anchored to the install-wide runtime — so the pinning snapshot's slot encoded the wrong Node major, and a reinstall on the same host could read the cached side-effects under a key whose `<platform>;<arch>;node<major>` triple disagreed with the Node the build actually ran on. Per-snapshot resolution now matches what `bins/linker` already does on a per-package basis: a snapshot's own pin wins; the install-wide value (from #11689's `findRuntimeNodeVersion`) is the fallback. ### TypeScript - `deps/graph-hasher/src/index.ts:72-77` — adds `readSnapshotRuntimePin(children)`: pulls the bare Node version from a graph node's `children.node` entry when that points at a `node@runtime:<version>` snapshot. Factors out a small `extractRuntimeNodeVersion(snapshotKey)` parser shared with `findRuntimeNodeVersion`. - `deps/graph-hasher/src/index.ts:115-116,245-246` — `calcDepState` and `calcGraphNodeHash` consult `readSnapshotRuntimePin(graph[depPath].children)` first and only fall back to the install-wide `nodeVersion` parameter when the snapshot doesn't pin its own Node. No caller changes required — install-wide fallback continues to be computed via `findRuntimeNodeVersion(Object.keys(graph))` at each call site. - **Refactor (separate commit):** `findRuntimeNodeVersion` moved from `@pnpm/engine.runtime.system-node-version` to `@pnpm/deps.graph-hasher` (along with the new `readSnapshotRuntimePin`). `system-node-version` is about probing the *host* Node — `getSystemNodeVersion`, `engineName`. The lockfile-shape parsers fit better next to the package that actually composes the engine string. Every caller already depended on graph-hasher, so no new deps; six packages drop the now-unused dependency on `system-node-version`. ### Pacquet - `pacquet/crates/package-manager/src/install_frozen_lockfile.rs:1309-1345` — new `find_own_runtime_node_major(snapshot)` reads a snapshot's `dependencies` for a `node` entry with `Prefix::Runtime`, returning the bare major. - `pacquet/crates/package-manager/src/virtual_store_layout.rs:178-205` — `VirtualStoreLayout::new` resolves engine per-snapshot inside the hash loop via `engine_name(own_major, None, None)` when the snapshot pins, otherwise inherits the install-wide `engine` argument. ### Migration Snapshots of dependencies that declare their own `engines.runtime` re-hash under that dep's pinned Node instead of the install-wide value. Old slots become prune-eligible on next install.
56 lines
2.4 KiB
TypeScript
56 lines
2.4 KiB
TypeScript
import { detectIfCurrentPkgIsExecutable } from '@pnpm/cli.meta'
|
|
import * as execa from 'execa'
|
|
import mem from 'memoize'
|
|
|
|
export function getSystemNodeVersionNonCached (): string | undefined {
|
|
if (detectIfCurrentPkgIsExecutable()) {
|
|
try {
|
|
return execa.sync('node', ['--version']).stdout?.toString()
|
|
} catch {
|
|
// Node.js is not installed on the system
|
|
return undefined
|
|
}
|
|
}
|
|
return process.version
|
|
}
|
|
|
|
export const getSystemNodeVersion = mem(getSystemNodeVersionNonCached)
|
|
|
|
/**
|
|
* The `<platform>;<arch>;node<major>` string used as the side-effects
|
|
* cache-key prefix and the engine portion of the global-virtual-store
|
|
* hash. Identifies the runtime environment that built (or will build)
|
|
* a package's lifecycle scripts — so two installs that materialize the
|
|
* same package on the same host produce the same key.
|
|
*
|
|
* The Node version is resolved in this order:
|
|
*
|
|
* 1. `nodeVersion` argument when provided. Callers use this to thread
|
|
* a project-pinned runtime (`engines.runtime` / `devEngines.runtime`)
|
|
* through to the hash — see `findRuntimeNodeVersion` /
|
|
* `readSnapshotRuntimePin` in `@pnpm/deps.path` for the helpers
|
|
* that extract the value from a lockfile or graph node.
|
|
* 2. {@link getSystemNodeVersion} — the `node` on the user's `PATH`,
|
|
* or `process.version` when not SEA-bundled.
|
|
* 3. `process.version` as a last-resort fallback when the host has
|
|
* no `node` on `PATH` (rare: SEA pnpm with no separately-installed
|
|
* Node). Scripts cannot run in that scenario regardless, so the
|
|
* cache key is effectively unused — the fallback exists only to
|
|
* keep the value deterministic.
|
|
*
|
|
* Anchoring to a project-pinned or script-runner Node — not to pnpm's
|
|
* own `process.version` — matters most when pnpm ships via the
|
|
* `@pnpm/exe` SEA bundle, which has an embedded Node distinct from
|
|
* the one that actually runs lifecycle scripts. Without the override,
|
|
* a project with `devEngines.runtime: node@22` would still hash under
|
|
* the SEA-runner's Node major, splitting the cache across two pnpm
|
|
* installations on the same machine even though both run scripts on
|
|
* the same pinned Node.
|
|
*/
|
|
export function engineName (nodeVersion?: string): string {
|
|
const version = nodeVersion ?? getSystemNodeVersion() ?? process.version
|
|
const stripped = version.startsWith('v') ? version.slice(1) : version
|
|
const major = stripped.split('.')[0]
|
|
return `${process.platform};${process.arch};node${major}`
|
|
}
|