@pnpm/engine.runtime.node-resolver 1001.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015-2016 Rico Sta. Cruz and other contributors
4
+ Copyright (c) 2016-2026 Zoltan Kochan and other contributors
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # @pnpm/engine.runtime.node-resolver
2
+
3
+ > Resolves a Node.js version specifier to an exact Node.js version
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@pnpm/engine.runtime.node-resolver.svg)](https://www.npmjs.com/package/@pnpm/engine.runtime.node-resolver)
6
+
7
+ ## Installation
8
+
9
+ ```sh
10
+ pnpm add @pnpm/engine.runtime.node-resolver
11
+ ```
12
+
13
+ ## License
14
+
15
+ MIT
@@ -0,0 +1,13 @@
1
+ export interface NodeArtifactAddress {
2
+ basename: string;
3
+ extname: string;
4
+ dirname: string;
5
+ }
6
+ export interface GetNodeArtifactAddressOptions {
7
+ version: string;
8
+ baseUrl: string;
9
+ platform: string;
10
+ arch: string;
11
+ libc?: string;
12
+ }
13
+ export declare function getNodeArtifactAddress({ version, baseUrl, platform, arch, libc }: GetNodeArtifactAddressOptions): NodeArtifactAddress;
@@ -0,0 +1,13 @@
1
+ import { getNormalizedArch } from './normalizeArch.js';
2
+ export function getNodeArtifactAddress({ version, baseUrl, platform, arch, libc, }) {
3
+ const isWindowsPlatform = platform === 'win32';
4
+ const normalizedPlatform = isWindowsPlatform ? 'win' : platform;
5
+ const normalizedArch = getNormalizedArch(platform, arch, version);
6
+ const archSuffix = libc === 'musl' ? '-musl' : '';
7
+ return {
8
+ dirname: `${baseUrl}v${version}`,
9
+ basename: `node-v${version}-${normalizedPlatform}-${normalizedArch}${archSuffix}`,
10
+ extname: isWindowsPlatform ? '.zip' : '.tar.gz',
11
+ };
12
+ }
13
+ //# sourceMappingURL=getNodeArtifactAddress.js.map
@@ -0,0 +1,2 @@
1
+ import type { Config } from '@pnpm/config.reader';
2
+ export declare function getNodeMirror(rawConfig: Config['rawConfig'], releaseChannel: string): string;
@@ -0,0 +1,10 @@
1
+ export function getNodeMirror(rawConfig, releaseChannel) {
2
+ // This is a dynamic lookup since the 'use-node-version' option is allowed to be '<releaseChannel>/<version>'
3
+ const configKey = `node-mirror:${releaseChannel}`;
4
+ const nodeMirror = rawConfig[configKey] ?? `https://nodejs.org/download/${releaseChannel}/`;
5
+ return normalizeNodeMirror(nodeMirror);
6
+ }
7
+ function normalizeNodeMirror(nodeMirror) {
8
+ return nodeMirror.endsWith('/') ? nodeMirror : `${nodeMirror}/`;
9
+ }
10
+ //# sourceMappingURL=getNodeMirror.js.map
package/lib/index.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import type { FetchFromRegistry } from '@pnpm/fetching.types';
2
+ import type { ResolveOptions, ResolveResult, VariationsResolution, WantedDependency } from '@pnpm/resolving.resolver-base';
3
+ import { getNodeArtifactAddress } from './getNodeArtifactAddress.js';
4
+ import { getNodeMirror } from './getNodeMirror.js';
5
+ import { parseNodeSpecifier } from './parseNodeSpecifier.js';
6
+ export { getNodeArtifactAddress, getNodeMirror, parseNodeSpecifier };
7
+ export declare const DEFAULT_NODE_MIRROR_BASE_URL = "https://nodejs.org/download/release/";
8
+ export declare const UNOFFICIAL_NODE_MIRROR_BASE_URL = "https://unofficial-builds.nodejs.org/download/release/";
9
+ export interface NodeRuntimeResolveResult extends ResolveResult {
10
+ resolution: VariationsResolution;
11
+ resolvedVia: 'nodejs.org';
12
+ }
13
+ export declare function resolveNodeRuntime(ctx: {
14
+ fetchFromRegistry: FetchFromRegistry;
15
+ rawConfig: Record<string, string>;
16
+ offline?: boolean;
17
+ }, wantedDependency: WantedDependency, opts?: Partial<ResolveOptions>): Promise<NodeRuntimeResolveResult | null>;
18
+ export declare function resolveNodeVersion(fetch: FetchFromRegistry, versionSpec: string, nodeMirrorBaseUrl?: string): Promise<string | null>;
19
+ export declare function resolveNodeVersions(fetch: FetchFromRegistry, versionSpec?: string, nodeMirrorBaseUrl?: string): Promise<string[]>;
package/lib/index.js ADDED
@@ -0,0 +1,168 @@
1
+ import { getNodeBinsForCurrentOS } from '@pnpm/constants';
2
+ import { fetchShasumsFile } from '@pnpm/crypto.shasums-file';
3
+ import { PnpmError } from '@pnpm/error';
4
+ import semver from 'semver';
5
+ import versionSelectorType from 'version-selector-type';
6
+ import { getNodeArtifactAddress } from './getNodeArtifactAddress.js';
7
+ import { getNodeMirror } from './getNodeMirror.js';
8
+ import { parseNodeSpecifier } from './parseNodeSpecifier.js';
9
+ export { getNodeArtifactAddress, getNodeMirror, parseNodeSpecifier };
10
+ export const DEFAULT_NODE_MIRROR_BASE_URL = 'https://nodejs.org/download/release/';
11
+ export const UNOFFICIAL_NODE_MIRROR_BASE_URL = 'https://unofficial-builds.nodejs.org/download/release/';
12
+ export async function resolveNodeRuntime(ctx, wantedDependency, opts) {
13
+ if (wantedDependency.alias !== 'node' || !wantedDependency.bareSpecifier?.startsWith('runtime:'))
14
+ return null;
15
+ if (opts?.currentPkg && !opts.update) {
16
+ return {
17
+ id: opts.currentPkg.id,
18
+ resolution: opts.currentPkg.resolution,
19
+ resolvedVia: 'nodejs.org',
20
+ };
21
+ }
22
+ if (ctx.offline)
23
+ throw new PnpmError('NO_OFFLINE_NODEJS_RESOLUTION', 'Offline Node.js resolution is not supported');
24
+ const versionSpec = wantedDependency.bareSpecifier.substring('runtime:'.length);
25
+ const { releaseChannel, versionSpecifier } = parseNodeSpecifier(versionSpec);
26
+ const nodeMirrorBaseUrl = getNodeMirror(ctx.rawConfig, releaseChannel);
27
+ const version = await resolveNodeVersion(ctx.fetchFromRegistry, versionSpecifier, nodeMirrorBaseUrl);
28
+ if (!version) {
29
+ throw new PnpmError('NODEJS_VERSION_NOT_FOUND', `Could not find a Node.js version that satisfies ${versionSpec}`);
30
+ }
31
+ const variants = await readNodeAssets(ctx.fetchFromRegistry, nodeMirrorBaseUrl, version);
32
+ const range = version === versionSpec ? version : `^${version}`;
33
+ return {
34
+ id: `node@runtime:${version}`,
35
+ normalizedBareSpecifier: `runtime:${range}`,
36
+ resolvedVia: 'nodejs.org',
37
+ manifest: {
38
+ name: 'node',
39
+ version,
40
+ bin: getNodeBinsForCurrentOS(),
41
+ },
42
+ resolution: {
43
+ type: 'variations',
44
+ variants,
45
+ },
46
+ };
47
+ }
48
+ async function readNodeAssets(fetch, nodeMirrorBaseUrl, version) {
49
+ const assets = await readNodeAssetsFromMirror(fetch, { nodeMirrorBaseUrl, version, muslOnly: false });
50
+ // When using the default mirror, also fetch musl variants from unofficial-builds.nodejs.org,
51
+ // since musl builds are not available on the official mirror.
52
+ if (nodeMirrorBaseUrl === DEFAULT_NODE_MIRROR_BASE_URL) {
53
+ try {
54
+ const muslAssets = await readNodeAssetsFromMirror(fetch, { nodeMirrorBaseUrl: UNOFFICIAL_NODE_MIRROR_BASE_URL, version, muslOnly: true });
55
+ assets.push(...muslAssets);
56
+ }
57
+ catch {
58
+ // Musl variants may not be available for all Node.js versions (e.g. very old ones)
59
+ }
60
+ }
61
+ return assets;
62
+ }
63
+ async function readNodeAssetsFromMirror(fetch, opts) {
64
+ const { nodeMirrorBaseUrl, version, muslOnly } = opts;
65
+ const integritiesFileUrl = `${nodeMirrorBaseUrl}v${version}/SHASUMS256.txt`;
66
+ const shasumsFileItems = await fetchShasumsFile(fetch, integritiesFileUrl);
67
+ const escaped = version.replace(/\\/g, '\\\\').replace(/\./g, '\\.');
68
+ // The second capture group uses [^.-]+ to stop at a dash, so that the optional
69
+ // third group can capture the '-musl' suffix separately (e.g. 'x64' + '-musl').
70
+ const pattern = new RegExp(`^node-v${escaped}-([^-.]+)-([^.-]+)(-musl)?\\.(?:tar\\.gz|zip)$`);
71
+ const assets = [];
72
+ for (const { integrity, fileName } of shasumsFileItems) {
73
+ const match = pattern.exec(fileName);
74
+ if (!match)
75
+ continue;
76
+ let [, platform, arch, muslSuffix] = match;
77
+ if (platform === 'win') {
78
+ platform = 'win32';
79
+ }
80
+ const isMusl = muslSuffix != null;
81
+ if (muslOnly && !isMusl)
82
+ continue;
83
+ const libc = isMusl ? 'musl' : undefined;
84
+ const address = getNodeArtifactAddress({
85
+ version,
86
+ baseUrl: nodeMirrorBaseUrl,
87
+ platform,
88
+ arch,
89
+ libc,
90
+ });
91
+ const url = `${address.dirname}/${address.basename}${address.extname}`;
92
+ const resolution = {
93
+ type: 'binary',
94
+ archive: address.extname === '.zip' ? 'zip' : 'tarball',
95
+ bin: getNodeBinsForCurrentOS(platform),
96
+ integrity,
97
+ url,
98
+ };
99
+ if (resolution.archive === 'zip') {
100
+ resolution.prefix = address.basename;
101
+ }
102
+ const target = {
103
+ os: platform,
104
+ cpu: arch,
105
+ ...(libc != null && { libc }),
106
+ };
107
+ assets.push({
108
+ targets: [target],
109
+ resolution,
110
+ });
111
+ }
112
+ return assets;
113
+ }
114
+ const SEMVER_OPTS = {
115
+ includePrerelease: true,
116
+ loose: true,
117
+ };
118
+ export async function resolveNodeVersion(fetch, versionSpec, nodeMirrorBaseUrl) {
119
+ const allVersions = await fetchAllVersions(fetch, nodeMirrorBaseUrl);
120
+ if (versionSpec === 'latest') {
121
+ return allVersions[0].version;
122
+ }
123
+ const { versions, versionRange } = filterVersions(allVersions, versionSpec);
124
+ return semver.maxSatisfying(versions, versionRange, SEMVER_OPTS) ?? null;
125
+ }
126
+ export async function resolveNodeVersions(fetch, versionSpec, nodeMirrorBaseUrl) {
127
+ const allVersions = await fetchAllVersions(fetch, nodeMirrorBaseUrl);
128
+ if (!versionSpec) {
129
+ return allVersions.map(({ version }) => version);
130
+ }
131
+ if (versionSpec === 'latest') {
132
+ return [allVersions[0].version];
133
+ }
134
+ const { versions, versionRange } = filterVersions(allVersions, versionSpec);
135
+ return versions.filter(version => semver.satisfies(version, versionRange, SEMVER_OPTS));
136
+ }
137
+ async function fetchAllVersions(fetch, nodeMirrorBaseUrl) {
138
+ const response = await fetch(`${nodeMirrorBaseUrl ?? 'https://nodejs.org/download/release/'}index.json`);
139
+ return (await response.json()).map(({ version, lts }) => ({
140
+ version: version.substring(1),
141
+ lts,
142
+ }));
143
+ }
144
+ function filterVersions(versions, versionSelector) {
145
+ if (versionSelector === 'lts') {
146
+ return {
147
+ versions: versions
148
+ .filter(({ lts }) => lts !== false)
149
+ .map(({ version }) => version),
150
+ versionRange: '*',
151
+ };
152
+ }
153
+ const vst = versionSelectorType(versionSelector);
154
+ if (vst?.type === 'tag') {
155
+ const wantedLtsVersion = vst.normalized.toLowerCase();
156
+ return {
157
+ versions: versions
158
+ .filter(({ lts }) => typeof lts === 'string' && lts.toLowerCase() === wantedLtsVersion)
159
+ .map(({ version }) => version),
160
+ versionRange: '*',
161
+ };
162
+ }
163
+ return {
164
+ versions: versions.map(({ version }) => version),
165
+ versionRange: versionSelector,
166
+ };
167
+ }
168
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ export declare function getNormalizedArch(platform: string, arch: string, nodeVersion?: string): string;
@@ -0,0 +1,16 @@
1
+ export function getNormalizedArch(platform, arch, nodeVersion) {
2
+ if (nodeVersion) {
3
+ const nodeMajorVersion = +nodeVersion.split('.')[0];
4
+ if ((platform === 'darwin' && arch === 'arm64' && (nodeMajorVersion < 16))) {
5
+ return 'x64';
6
+ }
7
+ }
8
+ if (platform === 'win32' && arch === 'ia32') {
9
+ return 'x86';
10
+ }
11
+ if (arch === 'arm') {
12
+ return 'armv7l';
13
+ }
14
+ return arch;
15
+ }
16
+ //# sourceMappingURL=normalizeArch.js.map
@@ -0,0 +1,5 @@
1
+ export interface NodeSpecifier {
2
+ releaseChannel: string;
3
+ versionSpecifier: string;
4
+ }
5
+ export declare function parseNodeSpecifier(specifier: string): NodeSpecifier;
@@ -0,0 +1,39 @@
1
+ import { PnpmError } from '@pnpm/error';
2
+ const RELEASE_CHANNELS = ['nightly', 'rc', 'test', 'v8-canary', 'release'];
3
+ const isStableVersion = (version) => /^\d+\.\d+\.\d+$/.test(version);
4
+ export function parseNodeSpecifier(specifier) {
5
+ // Handle "channel/version" format: "rc/18", "rc/18.0.0-rc.4", "release/22.0.0", "nightly/latest"
6
+ if (specifier.includes('/')) {
7
+ const [releaseChannel, versionSpecifier] = specifier.split('/', 2);
8
+ if (!RELEASE_CHANNELS.includes(releaseChannel)) {
9
+ throw new PnpmError('INVALID_NODE_RELEASE_CHANNEL', `"${releaseChannel}" is not a valid Node.js release channel`, {
10
+ hint: `Valid release channels are: ${RELEASE_CHANNELS.join(', ')}`,
11
+ });
12
+ }
13
+ return { releaseChannel, versionSpecifier };
14
+ }
15
+ // Exact prerelease version with a recognized release channel suffix.
16
+ // e.g. "22.0.0-rc.4", "22.0.0-nightly20250315d765e70802", "22.0.0-v8-canary2025..."
17
+ const prereleaseChannelMatch = specifier.match(/^\d+\.\d+\.\d+-(nightly|rc|test|v8-canary)/);
18
+ if (prereleaseChannelMatch != null) {
19
+ return { releaseChannel: prereleaseChannelMatch[1], versionSpecifier: specifier };
20
+ }
21
+ // Exact stable version: "22.0.0"
22
+ if (isStableVersion(specifier)) {
23
+ return { releaseChannel: 'release', versionSpecifier: specifier };
24
+ }
25
+ // Standalone release channel name means "latest from that channel".
26
+ // e.g. "nightly" → latest nightly, "rc" → latest rc, "release" → latest release
27
+ if (RELEASE_CHANNELS.includes(specifier)) {
28
+ return { releaseChannel: specifier, versionSpecifier: 'latest' };
29
+ }
30
+ // Well-known version aliases on the stable release channel
31
+ if (specifier === 'lts' || specifier === 'latest') {
32
+ return { releaseChannel: 'release', versionSpecifier: specifier };
33
+ }
34
+ // Semver ranges ("18", "^18", ">=18", "18.x") and LTS codenames ("argon", "iron", "hydrogen")
35
+ // are all passed through as versionSpecifier on the release channel.
36
+ // Any truly invalid input will fail at resolution time with NODEJS_VERSION_NOT_FOUND.
37
+ return { releaseChannel: 'release', versionSpecifier: specifier };
38
+ }
39
+ //# sourceMappingURL=parseNodeSpecifier.js.map
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@pnpm/engine.runtime.node-resolver",
3
+ "version": "1001.0.5",
4
+ "description": "Resolves a Node.js version specifier to an exact Node.js version",
5
+ "keywords": [
6
+ "pnpm",
7
+ "pnpm11",
8
+ "env",
9
+ "node.js"
10
+ ],
11
+ "license": "MIT",
12
+ "funding": "https://opencollective.com/pnpm",
13
+ "repository": "https://github.com/pnpm/pnpm/tree/main/engine/runtime/node-resolver",
14
+ "homepage": "https://github.com/pnpm/pnpm/tree/main/engine/runtime/node-resolver#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/pnpm/pnpm/issues"
17
+ },
18
+ "type": "module",
19
+ "main": "lib/index.js",
20
+ "types": "lib/index.d.ts",
21
+ "exports": {
22
+ ".": "./lib/index.js"
23
+ },
24
+ "files": [
25
+ "lib",
26
+ "!*.map"
27
+ ],
28
+ "dependencies": {
29
+ "semver": "^7.7.2",
30
+ "version-selector-type": "^3.0.0",
31
+ "@pnpm/config.reader": "1004.4.2",
32
+ "@pnpm/crypto.shasums-file": "1001.0.2",
33
+ "@pnpm/constants": "1001.3.1",
34
+ "@pnpm/error": "1000.0.5",
35
+ "@pnpm/fetching.types": "1000.2.0",
36
+ "@pnpm/types": "1000.9.0",
37
+ "@pnpm/resolving.resolver-base": "1005.1.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/semver": "7.7.1",
41
+ "@pnpm/engine.runtime.node-resolver": "1001.0.5",
42
+ "@pnpm/network.fetch": "1000.2.6"
43
+ },
44
+ "engines": {
45
+ "node": ">=22.13"
46
+ },
47
+ "jest": {
48
+ "preset": "@pnpm/jest-config"
49
+ },
50
+ "scripts": {
51
+ "lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
52
+ "_test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169\" jest",
53
+ "test": "pnpm run compile && pnpm run _test",
54
+ "compile": "tsgo --build && pnpm run lint --fix"
55
+ }
56
+ }