diff --git a/.changeset/engine-strict-devengines-runtime.md b/.changeset/engine-strict-devengines-runtime.md new file mode 100644 index 0000000000..e8bf0066b0 --- /dev/null +++ b/.changeset/engine-strict-devengines-runtime.md @@ -0,0 +1,6 @@ +--- +"@pnpm/config": patch +"pnpm": patch +--- + +Engine validation now uses the Node.js version from `devEngines.runtime` (or `engines.runtime`) instead of the system Node.js version [#10033](https://github.com/pnpm/pnpm/issues/10033). diff --git a/config/config/src/index.ts b/config/config/src/index.ts index e451c10130..a3f9463630 100644 --- a/config/config/src/index.ts +++ b/config/config/src/index.ts @@ -402,6 +402,9 @@ export async function getConfig (opts: { if (pnpmConfig.rootProjectManifest) { Object.assign(pnpmConfig, getOptionsFromRootManifest(pnpmConfig.rootProjectManifestDir, pnpmConfig.rootProjectManifest)) } + if (pnpmConfig.nodeVersion == null) { + pnpmConfig.nodeVersion = getNodeVersionFromEnginesRuntime(pnpmConfig.rootProjectManifest) + } } if (pnpmConfig.workspaceDir != null) { @@ -721,6 +724,21 @@ function parseDevEnginesPackageManager (devEngines?: DevEngines): EngineDependen } } +function getNodeVersionFromEnginesRuntime (manifest: ProjectManifest): string | undefined { + for (const enginesFieldName of ['devEngines', 'engines'] as const) { + const enginesRuntime = manifest[enginesFieldName]?.runtime + if (enginesRuntime == null) continue + const runtimes: EngineDependency[] = Array.isArray(enginesRuntime) ? enginesRuntime : [enginesRuntime] + const nodeRuntime = runtimes.find((r) => r.name === 'node') + if (nodeRuntime?.version == null) continue + const minVersion = semver.minVersion(nodeRuntime.version) + if (minVersion != null) { + return minVersion.version + } + } + return undefined +} + function addSettingsFromWorkspaceManifestToConfig (pnpmConfig: Config, { configFromCliOpts, projectManifest, diff --git a/config/config/test/index.ts b/config/config/test/index.ts index bf9b186be1..0172e1c892 100644 --- a/config/config/test/index.ts +++ b/config/config/test/index.ts @@ -56,6 +56,57 @@ test('getConfig()', async () => { expect(config.nodeVersion).toBeUndefined() }) +test.each([ + { field: 'devEngines' as const, version: '22.20.0', onFail: 'download' as const, expected: '22.20.0' }, + { field: 'devEngines' as const, version: '22.20.0', onFail: 'error' as const, expected: '22.20.0' }, + { field: 'devEngines' as const, version: '^22.0.0', onFail: 'download' as const, expected: '22.0.0' }, + { field: 'engines' as const, version: '22.20.0', onFail: 'download' as const, expected: '22.20.0' }, +])('when $field is $version and onFail is $onFail, nodeVersion is set to $expected', async ({ field, version, onFail, expected }) => { + prepare({ + [field]: { + runtime: { + name: 'node', + version, + onFail, + }, + }, + }) + + const { config } = await getConfig({ + cliOptions: {}, + packageManager: { + name: 'pnpm', + version: '1.0.0', + }, + }) + + expect(config.nodeVersion).toBe(expected) +}) + +test('nodeVersion from config takes priority over devEngines.runtime', async () => { + prepare({ + devEngines: { + runtime: { + name: 'node', + version: '22.20.0', + onFail: 'download', + }, + }, + }) + + const { config } = await getConfig({ + cliOptions: { + 'node-version': '20.0.0', + }, + packageManager: { + name: 'pnpm', + version: '1.0.0', + }, + }) + + expect(config.nodeVersion).toBe('20.0.0') +}) + test('throw error if --link-workspace-packages is used with --global', async () => { await expect(getConfig({ cliOptions: {