mirror of
https://github.com/pnpm/pnpm.git
synced 2026-05-30 03:26:43 -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.
61 lines
2.2 KiB
TypeScript
61 lines
2.2 KiB
TypeScript
import { expect, jest, test } from '@jest/globals'
|
|
let isSea = false
|
|
|
|
jest.unstable_mockModule('@pnpm/cli.meta', () => ({
|
|
detectIfCurrentPkgIsExecutable: jest.fn(() => isSea),
|
|
}))
|
|
|
|
jest.unstable_mockModule('execa', () => ({
|
|
sync: jest.fn(() => ({
|
|
stdout: 'v10.0.0',
|
|
})),
|
|
}))
|
|
|
|
const { getSystemNodeVersionNonCached, engineName } = await import('../lib/index.js')
|
|
const execa = await import('execa')
|
|
|
|
test('getSystemNodeVersion() executed from an executable pnpm CLI', () => {
|
|
isSea = true
|
|
expect(getSystemNodeVersionNonCached()).toBe('v10.0.0')
|
|
expect(execa.sync).toHaveBeenCalledWith('node', ['--version'])
|
|
})
|
|
|
|
test('getSystemNodeVersion() from a non-executable pnpm CLI', () => {
|
|
isSea = false
|
|
expect(getSystemNodeVersionNonCached()).toBe(process.version)
|
|
})
|
|
|
|
test('getSystemNodeVersion() returns undefined if execa.sync throws an error', () => {
|
|
// Mock execa.sync to throw an error
|
|
jest.mocked(execa.sync).mockImplementationOnce(() => {
|
|
throw new Error('not found: node')
|
|
})
|
|
|
|
isSea = true
|
|
expect(getSystemNodeVersionNonCached()).toBeUndefined()
|
|
expect(execa.sync).toHaveBeenCalledWith('node', ['--version'])
|
|
})
|
|
|
|
test('engineName() honours an explicit nodeVersion over the host probe', () => {
|
|
// The pinned-runtime override path: when a project's
|
|
// `engines.runtime` / `devEngines.runtime` resolves to a specific
|
|
// Node version, the caller forwards it to `engineName(version)`
|
|
// and the result reflects that pinned Node — not whatever pnpm
|
|
// itself is running on. Format-stable across `v`-prefixed and
|
|
// bare versions.
|
|
const major22 = `${process.platform};${process.arch};node22`
|
|
expect(engineName('22.11.0')).toBe(major22)
|
|
expect(engineName('v22.11.0')).toBe(major22)
|
|
})
|
|
|
|
test('engineName() falls back to the host Node when no override is provided', () => {
|
|
// No-arg call mirrors the pre-runtime-pin behaviour: anchor to
|
|
// `getSystemNodeVersion()` (which itself prefers shell `node` over
|
|
// `process.version` only when running as a SEA bundle — covered
|
|
// by the tests above). Non-SEA test environment, so the system
|
|
// version equals `process.version`.
|
|
isSea = false
|
|
const major = process.version.replace(/^v/, '').split('.')[0]
|
|
expect(engineName()).toBe(`${process.platform};${process.arch};node${major}`)
|
|
})
|