@pnpm/engine.runtime.node-resolver 1101.2.3 → 1101.3.0
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/CHANGELOG.md +13 -0
- package/lib/index.d.ts +9 -3
- package/lib/index.js +67 -14
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @pnpm/node.resolver
|
|
2
2
|
|
|
3
|
+
## 1101.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Authenticate Node.js runtime downloads from `nodeDownloadMirrors` with URL-scoped npm registry credentials, including bearer tokens, basic auth, and `tokenHelper` [pnpm/pnpm#14334](https://github.com/pnpm/pnpm/issues/14334).
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies:
|
|
12
|
+
- @pnpm/config.reader@1102.1.1
|
|
13
|
+
- @pnpm/crypto.shasums-file@1100.2.3
|
|
14
|
+
- @pnpm/error@1100.1.4
|
|
15
|
+
|
|
3
16
|
## 1101.2.3
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FetchFromRegistry } from '@pnpm/fetching.types';
|
|
1
|
+
import type { FetchFromRegistry, GetAuthHeader } from '@pnpm/fetching.types';
|
|
2
2
|
import type { LatestInfo, LatestQuery, ResolveOptions, ResolveResult, VariationsResolution, WantedDependency } from '@pnpm/resolving.resolver-base';
|
|
3
3
|
import { getNodeArtifactAddress } from './getNodeArtifactAddress.js';
|
|
4
4
|
import { getNodeMirror } from './getNodeMirror.js';
|
|
@@ -13,13 +13,19 @@ export interface NodeRuntimeResolveResult extends ResolveResult {
|
|
|
13
13
|
}
|
|
14
14
|
export declare function resolveNodeRuntime(ctx: {
|
|
15
15
|
fetchFromRegistry: FetchFromRegistry;
|
|
16
|
+
getAuthHeader?: GetAuthHeader;
|
|
16
17
|
nodeDownloadMirrors?: Record<string, string>;
|
|
17
18
|
offline?: boolean;
|
|
18
19
|
cacheDir?: string;
|
|
19
20
|
}, wantedDependency: WantedDependency, opts?: Partial<ResolveOptions>): Promise<NodeRuntimeResolveResult | null>;
|
|
20
21
|
export declare function resolveLatestNodeRuntime(ctx: {
|
|
21
22
|
fetchFromRegistry: FetchFromRegistry;
|
|
23
|
+
getAuthHeader?: GetAuthHeader;
|
|
22
24
|
nodeDownloadMirrors?: Record<string, string>;
|
|
23
25
|
}, query: LatestQuery, _opts: ResolveOptions): Promise<LatestInfo | undefined>;
|
|
24
|
-
export declare function resolveNodeVersion(fetch: FetchFromRegistry, versionSpec: string,
|
|
25
|
-
export declare function resolveNodeVersions(fetch: FetchFromRegistry, versionSpec?: string,
|
|
26
|
+
export declare function resolveNodeVersion(fetch: FetchFromRegistry, versionSpec: string, opts?: string | NodeVersionFetchOptions): Promise<string | null>;
|
|
27
|
+
export declare function resolveNodeVersions(fetch: FetchFromRegistry, versionSpec?: string, opts?: string | NodeVersionFetchOptions): Promise<string[]>;
|
|
28
|
+
export interface NodeVersionFetchOptions {
|
|
29
|
+
nodeMirrorBaseUrl?: string;
|
|
30
|
+
getAuthHeader?: GetAuthHeader;
|
|
31
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isIP } from 'node:net';
|
|
1
2
|
import { fetchShasumsFileCached, fetchVerifiedNodeShasumsFileCached } from '@pnpm/crypto.shasums-file';
|
|
2
3
|
import { PnpmError } from '@pnpm/error';
|
|
3
4
|
import semver from 'semver';
|
|
@@ -25,6 +26,7 @@ export async function resolveNodeRuntime(ctx, wantedDependency, opts) {
|
|
|
25
26
|
}
|
|
26
27
|
if (ctx.offline)
|
|
27
28
|
throw new PnpmError('NO_OFFLINE_NODEJS_RESOLUTION', 'Offline Node.js resolution is not supported');
|
|
29
|
+
const fetch = createAuthenticatedFetch(ctx.fetchFromRegistry, ctx.getAuthHeader);
|
|
28
30
|
const versionSpec = normalizeRuntimeSpec(wantedDependency.bareSpecifier.substring('runtime:'.length));
|
|
29
31
|
const { releaseChannel, versionSpecifier } = parseNodeSpecifier(versionSpec);
|
|
30
32
|
const nodeMirrorBaseUrl = getNodeMirror(ctx.nodeDownloadMirrors, releaseChannel);
|
|
@@ -34,14 +36,14 @@ export async function resolveNodeRuntime(ctx, wantedDependency, opts) {
|
|
|
34
36
|
const exactVersion = exactReleaseVersion(releaseChannel, versionSpecifier);
|
|
35
37
|
let version = exactVersion;
|
|
36
38
|
if (version == null) {
|
|
37
|
-
version = await resolveNodeVersion(
|
|
39
|
+
version = await resolveNodeVersion(fetch, versionSpecifier, nodeMirrorBaseUrl) ?? undefined;
|
|
38
40
|
if (!version) {
|
|
39
41
|
throw new PnpmError('NODEJS_VERSION_NOT_FOUND', `Could not find a Node.js version that satisfies ${versionSpec}`);
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
let variants;
|
|
43
45
|
try {
|
|
44
|
-
variants = await readNodeAssets(
|
|
46
|
+
variants = await readNodeAssets(fetch, { nodeMirrorBaseUrl, version, releaseChannel, cacheDir: ctx.cacheDir, getAuthHeader: ctx.getAuthHeader });
|
|
45
47
|
}
|
|
46
48
|
catch (err) {
|
|
47
49
|
// The exact-specifier pick skipped the release index, so a failed asset
|
|
@@ -49,7 +51,7 @@ export async function resolveNodeRuntime(ctx, wantedDependency, opts) {
|
|
|
49
51
|
// now, purely to raise the same NODEJS_VERSION_NOT_FOUND the index-first
|
|
50
52
|
// path raises for a nonexistent version; any other outcome re-raises the
|
|
51
53
|
// asset error unchanged.
|
|
52
|
-
if (exactVersion != null && await versionMissingFromIndex(
|
|
54
|
+
if (exactVersion != null && await versionMissingFromIndex(fetch, exactVersion, nodeMirrorBaseUrl)) {
|
|
53
55
|
throw new PnpmError('NODEJS_VERSION_NOT_FOUND', `Could not find a Node.js version that satisfies ${versionSpec}`);
|
|
54
56
|
}
|
|
55
57
|
throw err;
|
|
@@ -77,7 +79,10 @@ export async function resolveLatestNodeRuntime(ctx, query, _opts) {
|
|
|
77
79
|
const versionSpec = query.compatible ? normalizeRuntimeSpec(manifestSpec.substring('runtime:'.length)) : 'latest';
|
|
78
80
|
const { releaseChannel, versionSpecifier } = parseNodeSpecifier(versionSpec);
|
|
79
81
|
const nodeMirrorBaseUrl = getNodeMirror(ctx.nodeDownloadMirrors, releaseChannel);
|
|
80
|
-
const version = await resolveNodeVersion(ctx.fetchFromRegistry, versionSpecifier,
|
|
82
|
+
const version = await resolveNodeVersion(ctx.fetchFromRegistry, versionSpecifier, {
|
|
83
|
+
nodeMirrorBaseUrl,
|
|
84
|
+
getAuthHeader: ctx.getAuthHeader,
|
|
85
|
+
});
|
|
81
86
|
if (!version)
|
|
82
87
|
return {};
|
|
83
88
|
return { latestManifest: { name: 'node', version } };
|
|
@@ -120,20 +125,20 @@ async function versionMissingFromIndex(fetch, version, nodeMirrorBaseUrl) {
|
|
|
120
125
|
}
|
|
121
126
|
}
|
|
122
127
|
async function readNodeAssets(fetch, opts) {
|
|
123
|
-
const { nodeMirrorBaseUrl, version, releaseChannel, cacheDir } = opts;
|
|
128
|
+
const { nodeMirrorBaseUrl, version, releaseChannel, cacheDir, getAuthHeader } = opts;
|
|
124
129
|
// The mirror is repository-configurable, so the SHASUMS file's hashes are only
|
|
125
130
|
// trustworthy once its OpenPGP signature is verified against the Node.js
|
|
126
131
|
// release keys embedded in pnpm. Only the `release` channel publishes a signed
|
|
127
132
|
// SHASUMS256.txt; pre-release channels (rc, nightly, …) are unsigned by Node,
|
|
128
133
|
// so they cannot be verified this way.
|
|
129
|
-
const assets = await readNodeAssetsFromMirror(fetch, { nodeMirrorBaseUrl, version, muslOnly: false, verifySignature: releaseChannel === 'release', cacheDir });
|
|
134
|
+
const assets = await readNodeAssetsFromMirror(fetch, { nodeMirrorBaseUrl, version, muslOnly: false, verifySignature: releaseChannel === 'release', cacheDir, getAuthHeader });
|
|
130
135
|
// When using the default mirror, also fetch musl variants from unofficial-builds.nodejs.org,
|
|
131
136
|
// since musl builds are not available on the official mirror. That URL is hardcoded (not
|
|
132
137
|
// repository-configurable) and signed by a different (unofficial-builds) key, so it is trusted
|
|
133
138
|
// over TLS rather than verified against the official release keys.
|
|
134
139
|
if (nodeMirrorBaseUrl === DEFAULT_NODE_MIRROR_BASE_URL) {
|
|
135
140
|
try {
|
|
136
|
-
const muslAssets = await readNodeAssetsFromMirror(fetch, { nodeMirrorBaseUrl: UNOFFICIAL_NODE_MIRROR_BASE_URL, version, muslOnly: true, verifySignature: false, cacheDir });
|
|
141
|
+
const muslAssets = await readNodeAssetsFromMirror(fetch, { nodeMirrorBaseUrl: UNOFFICIAL_NODE_MIRROR_BASE_URL, version, muslOnly: true, verifySignature: false, cacheDir, getAuthHeader });
|
|
137
142
|
assets.push(...muslAssets);
|
|
138
143
|
}
|
|
139
144
|
catch {
|
|
@@ -143,13 +148,18 @@ async function readNodeAssets(fetch, opts) {
|
|
|
143
148
|
return assets;
|
|
144
149
|
}
|
|
145
150
|
async function readNodeAssetsFromMirror(fetch, opts) {
|
|
146
|
-
const { nodeMirrorBaseUrl, version, muslOnly, verifySignature,
|
|
151
|
+
const { nodeMirrorBaseUrl, version, muslOnly, verifySignature, getAuthHeader } = opts;
|
|
147
152
|
// The URL is pinned to one released version, which is what makes it
|
|
148
153
|
// eligible for the SHASUMS disk cache.
|
|
149
154
|
const integritiesFileUrl = `${nodeMirrorBaseUrl}v${version}/SHASUMS256.txt`;
|
|
155
|
+
const cacheOpts = {
|
|
156
|
+
cacheDir: opts.cacheDir,
|
|
157
|
+
skipCache: getSecureAuthHeader(getAuthHeader, integritiesFileUrl) != null ||
|
|
158
|
+
getSecureAuthHeader(getAuthHeader, `${integritiesFileUrl}.sig`) != null,
|
|
159
|
+
};
|
|
150
160
|
const shasumsFileItems = verifySignature
|
|
151
|
-
? await fetchVerifiedNodeShasumsFileCached(fetch, integritiesFileUrl,
|
|
152
|
-
: await fetchShasumsFileCached(fetch, integritiesFileUrl,
|
|
161
|
+
? await fetchVerifiedNodeShasumsFileCached(fetch, integritiesFileUrl, cacheOpts)
|
|
162
|
+
: await fetchShasumsFileCached(fetch, integritiesFileUrl, cacheOpts);
|
|
153
163
|
const escaped = version.replace(/\\/g, '\\\\').replace(/\./g, '\\.');
|
|
154
164
|
// The second capture group uses [^.-]+ to stop at a dash, so that the optional
|
|
155
165
|
// third group can capture the '-musl' suffix separately (e.g. 'x64' + '-musl').
|
|
@@ -201,8 +211,10 @@ const SEMVER_OPTS = {
|
|
|
201
211
|
includePrerelease: true,
|
|
202
212
|
loose: true,
|
|
203
213
|
};
|
|
204
|
-
|
|
205
|
-
|
|
214
|
+
const MAX_NODE_MIRROR_REDIRECTS = 20;
|
|
215
|
+
export async function resolveNodeVersion(fetch, versionSpec, opts) {
|
|
216
|
+
const { nodeMirrorBaseUrl, getAuthHeader } = normalizeNodeVersionFetchOptions(opts);
|
|
217
|
+
const allVersions = await fetchAllVersions(createAuthenticatedFetch(fetch, getAuthHeader), nodeMirrorBaseUrl);
|
|
206
218
|
versionSpec = normalizeRuntimeSpec(versionSpec);
|
|
207
219
|
if (versionSpec === 'latest') {
|
|
208
220
|
return allVersions[0].version;
|
|
@@ -210,8 +222,9 @@ export async function resolveNodeVersion(fetch, versionSpec, nodeMirrorBaseUrl)
|
|
|
210
222
|
const { versions, versionRange } = filterVersions(allVersions, versionSpec);
|
|
211
223
|
return semver.maxSatisfying(versions, versionRange, SEMVER_OPTS) ?? null;
|
|
212
224
|
}
|
|
213
|
-
export async function resolveNodeVersions(fetch, versionSpec,
|
|
214
|
-
const
|
|
225
|
+
export async function resolveNodeVersions(fetch, versionSpec, opts) {
|
|
226
|
+
const { nodeMirrorBaseUrl, getAuthHeader } = normalizeNodeVersionFetchOptions(opts);
|
|
227
|
+
const allVersions = await fetchAllVersions(createAuthenticatedFetch(fetch, getAuthHeader), nodeMirrorBaseUrl);
|
|
215
228
|
if (versionSpec == null) {
|
|
216
229
|
return allVersions.map(({ version }) => version);
|
|
217
230
|
}
|
|
@@ -233,6 +246,46 @@ async function fetchAllVersions(fetch, nodeMirrorBaseUrl) {
|
|
|
233
246
|
lts,
|
|
234
247
|
}));
|
|
235
248
|
}
|
|
249
|
+
function normalizeNodeVersionFetchOptions(opts) {
|
|
250
|
+
return typeof opts === 'string' ? { nodeMirrorBaseUrl: opts } : opts ?? {};
|
|
251
|
+
}
|
|
252
|
+
function createAuthenticatedFetch(fetch, getAuthHeader) {
|
|
253
|
+
if (getAuthHeader == null)
|
|
254
|
+
return fetch;
|
|
255
|
+
return async (url, opts) => {
|
|
256
|
+
let currentUrl = url;
|
|
257
|
+
for (let redirectCount = 0;; redirectCount++) {
|
|
258
|
+
// eslint-disable-next-line no-await-in-loop
|
|
259
|
+
const response = await fetch(currentUrl, {
|
|
260
|
+
...opts,
|
|
261
|
+
authHeaderValue: getSecureAuthHeader(getAuthHeader, currentUrl),
|
|
262
|
+
redirect: 'manual',
|
|
263
|
+
});
|
|
264
|
+
if (opts?.redirect === 'manual' || !isRedirectStatus(response.status) || redirectCount === MAX_NODE_MIRROR_REDIRECTS) {
|
|
265
|
+
return response;
|
|
266
|
+
}
|
|
267
|
+
const location = response.headers.get('location');
|
|
268
|
+
if (location == null)
|
|
269
|
+
return response;
|
|
270
|
+
currentUrl = new URL(location, currentUrl).toString();
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function getSecureAuthHeader(getAuthHeader, url) {
|
|
275
|
+
const authHeaderValue = getAuthHeader?.(url);
|
|
276
|
+
if (authHeaderValue == null)
|
|
277
|
+
return undefined;
|
|
278
|
+
const parsed = new URL(url);
|
|
279
|
+
if (parsed.protocol === 'https:' || isLoopbackHost(parsed.hostname))
|
|
280
|
+
return authHeaderValue;
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
function isLoopbackHost(hostname) {
|
|
284
|
+
return hostname === 'localhost' || hostname === '[::1]' || (isIP(hostname) === 4 && hostname.startsWith('127.'));
|
|
285
|
+
}
|
|
286
|
+
function isRedirectStatus(status) {
|
|
287
|
+
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
|
288
|
+
}
|
|
236
289
|
function getNodeBinsForCurrentOS(platform = process.platform) {
|
|
237
290
|
if (platform === 'win32') {
|
|
238
291
|
return { node: 'node.exe' };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/engine.runtime.node-resolver",
|
|
3
|
-
"version": "1101.
|
|
3
|
+
"version": "1101.3.0",
|
|
4
4
|
"description": "Resolves a Node.js version specifier to an exact Node.js version",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
"!*.map"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@pnpm/config.reader": "1102.1.
|
|
33
|
-
"@pnpm/crypto.shasums-file": "1100.2.
|
|
34
|
-
"@pnpm/error": "1100.1.
|
|
32
|
+
"@pnpm/config.reader": "1102.1.1",
|
|
33
|
+
"@pnpm/crypto.shasums-file": "1100.2.3",
|
|
34
|
+
"@pnpm/error": "1100.1.4",
|
|
35
35
|
"@pnpm/fetching.types": "1100.0.3",
|
|
36
36
|
"@pnpm/resolving.resolver-base": "1101.2.0",
|
|
37
37
|
"@pnpm/types": "1102.1.0",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@jest/globals": "30.4.1",
|
|
43
|
-
"@pnpm/engine.runtime.node-resolver": "1101.
|
|
44
|
-
"@pnpm/network.fetch": "1100.1.
|
|
43
|
+
"@pnpm/engine.runtime.node-resolver": "1101.3.0",
|
|
44
|
+
"@pnpm/network.fetch": "1100.1.15",
|
|
45
45
|
"@types/semver": "7.8.0"
|
|
46
46
|
},
|
|
47
47
|
"engines": {
|