@pnpm/resolving.npm-resolver 1004.4.1 → 1100.0.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/lib/fetch.d.ts +8 -1
- package/lib/fetch.js +22 -4
- package/lib/index.d.ts +3 -2
- package/lib/index.js +4 -3
- package/lib/pickPackage.d.ts +4 -2
- package/lib/pickPackage.js +156 -33
- package/lib/pickPackageFromMeta.js +25 -4
- package/package.json +31 -31
package/lib/fetch.d.ts
CHANGED
|
@@ -4,6 +4,11 @@ import type { PackageMeta } from '@pnpm/resolving.registry.types';
|
|
|
4
4
|
export interface FetchMetadataResult {
|
|
5
5
|
meta: PackageMeta;
|
|
6
6
|
jsonText: string;
|
|
7
|
+
etag?: string;
|
|
8
|
+
notModified?: false;
|
|
9
|
+
}
|
|
10
|
+
export interface FetchMetadataNotModifiedResult {
|
|
11
|
+
notModified: true;
|
|
7
12
|
}
|
|
8
13
|
export declare class RegistryResponseError extends FetchError {
|
|
9
14
|
readonly pkgName: string;
|
|
@@ -19,5 +24,7 @@ export interface FetchMetadataOptions {
|
|
|
19
24
|
registry: string;
|
|
20
25
|
authHeaderValue?: string;
|
|
21
26
|
fullMetadata?: boolean;
|
|
27
|
+
etag?: string;
|
|
28
|
+
modified?: string;
|
|
22
29
|
}
|
|
23
|
-
export declare function fetchMetadataFromFromRegistry(fetchOpts: FetchMetadataFromFromRegistryOptions, pkgName: string, { authHeaderValue, fullMetadata, registry }: FetchMetadataOptions): Promise<FetchMetadataResult>;
|
|
30
|
+
export declare function fetchMetadataFromFromRegistry(fetchOpts: FetchMetadataFromFromRegistryOptions, pkgName: string, { authHeaderValue, etag: cachedEtag, fullMetadata, modified: cachedModified, registry }: FetchMetadataOptions): Promise<FetchMetadataResult | FetchMetadataNotModifiedResult>;
|
package/lib/fetch.js
CHANGED
|
@@ -21,7 +21,7 @@ export class RegistryResponseError extends FetchError {
|
|
|
21
21
|
this.pkgName = pkgName;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
export async function fetchMetadataFromFromRegistry(fetchOpts, pkgName, { authHeaderValue, fullMetadata, registry, }) {
|
|
24
|
+
export async function fetchMetadataFromFromRegistry(fetchOpts, pkgName, { authHeaderValue, etag: cachedEtag, fullMetadata, modified: cachedModified, registry, }) {
|
|
25
25
|
const uri = toUri(pkgName, registry);
|
|
26
26
|
const op = retry.operation(fetchOpts.retry);
|
|
27
27
|
return new Promise((resolve, reject) => {
|
|
@@ -33,12 +33,18 @@ export async function fetchMetadataFromFromRegistry(fetchOpts, pkgName, { authHe
|
|
|
33
33
|
authHeaderValue,
|
|
34
34
|
compress: true,
|
|
35
35
|
fullMetadata,
|
|
36
|
+
ifNoneMatch: cachedEtag,
|
|
37
|
+
ifModifiedSince: cachedModified ? new Date(cachedModified).toUTCString() : undefined,
|
|
36
38
|
retry: fetchOpts.retry,
|
|
37
39
|
timeout: fetchOpts.timeout,
|
|
38
40
|
});
|
|
39
41
|
}
|
|
40
42
|
catch (error) { // eslint-disable-line
|
|
41
|
-
reject(new PnpmError('META_FETCH_FAIL', `GET ${uri}: ${error.message}`, { attempts: attempt }));
|
|
43
|
+
reject(new PnpmError('META_FETCH_FAIL', `GET ${uri}: ${error.message}`, { attempts: attempt, cause: error }));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (response.status === 304) {
|
|
47
|
+
resolve({ notModified: true });
|
|
42
48
|
return;
|
|
43
49
|
}
|
|
44
50
|
if (response.status >= 400) {
|
|
@@ -59,7 +65,11 @@ export async function fetchMetadataFromFromRegistry(fetchOpts, pkgName, { authHe
|
|
|
59
65
|
if (elapsedMs > fetchOpts.fetchWarnTimeoutMs) {
|
|
60
66
|
globalWarn(`Request took ${elapsedMs}ms: ${uri}`);
|
|
61
67
|
}
|
|
62
|
-
resolve({
|
|
68
|
+
resolve({
|
|
69
|
+
meta,
|
|
70
|
+
jsonText,
|
|
71
|
+
etag: response.headers.get('etag') ?? undefined,
|
|
72
|
+
});
|
|
63
73
|
}
|
|
64
74
|
catch (error) { // eslint-disable-line
|
|
65
75
|
const timeout = op.retry(new PnpmError('BROKEN_METADATA_JSON', error.message));
|
|
@@ -67,9 +77,17 @@ export async function fetchMetadataFromFromRegistry(fetchOpts, pkgName, { authHe
|
|
|
67
77
|
reject(op.mainError());
|
|
68
78
|
return;
|
|
69
79
|
}
|
|
80
|
+
// Extract error properties into a plain object because Error properties
|
|
81
|
+
// are non-enumerable and don't serialize well through the logging system
|
|
82
|
+
const errorInfo = {
|
|
83
|
+
name: error.name,
|
|
84
|
+
message: error.message,
|
|
85
|
+
code: error.code,
|
|
86
|
+
errno: error.errno,
|
|
87
|
+
};
|
|
70
88
|
requestRetryLogger.debug({
|
|
71
89
|
attempt,
|
|
72
|
-
error,
|
|
90
|
+
error: errorInfo,
|
|
73
91
|
maxRetries: fetchOpts.retry.retries,
|
|
74
92
|
method: 'GET',
|
|
75
93
|
timeout,
|
package/lib/index.d.ts
CHANGED
|
@@ -3,9 +3,10 @@ import type { FetchFromRegistry, GetAuthHeader, RetryTimeoutOptions } from '@pnp
|
|
|
3
3
|
import type { PackageMeta } from '@pnpm/resolving.registry.types';
|
|
4
4
|
import type { DirectoryResolution, PkgResolutionId, PreferredVersions, ResolveResult, TarballResolution, WantedDependency, WorkspacePackages } from '@pnpm/resolving.resolver-base';
|
|
5
5
|
import type { DependencyManifest, PackageVersionPolicy, PinnedVersion, Registries, TrustPolicy } from '@pnpm/types';
|
|
6
|
-
import { RegistryResponseError } from './fetch.js';
|
|
6
|
+
import { fetchMetadataFromFromRegistry, type FetchMetadataFromFromRegistryOptions, RegistryResponseError } from './fetch.js';
|
|
7
7
|
import { parseBareSpecifier, type RegistryPackageSpec } from './parseBareSpecifier.js';
|
|
8
8
|
import { type PackageMetaCache, pickPackage, type PickPackageOptions } from './pickPackage.js';
|
|
9
|
+
import { pickPackageFromMeta, pickVersionByVersionRange } from './pickPackageFromMeta.js';
|
|
9
10
|
import { workspacePrefToNpm } from './workspacePrefToNpm.js';
|
|
10
11
|
export interface NoMatchingVersionErrorOptions {
|
|
11
12
|
wantedDependency: WantedDependency;
|
|
@@ -19,7 +20,7 @@ export declare class NoMatchingVersionError extends PnpmError {
|
|
|
19
20
|
readonly immatureVersion?: string;
|
|
20
21
|
constructor(opts: NoMatchingVersionErrorOptions);
|
|
21
22
|
}
|
|
22
|
-
export { type PackageMeta, type PackageMetaCache, parseBareSpecifier, type RegistryPackageSpec, RegistryResponseError, workspacePrefToNpm, };
|
|
23
|
+
export { fetchMetadataFromFromRegistry, type FetchMetadataFromFromRegistryOptions, type PackageMeta, type PackageMetaCache, parseBareSpecifier, pickPackageFromMeta, pickVersionByVersionRange, type RegistryPackageSpec, RegistryResponseError, workspacePrefToNpm, };
|
|
23
24
|
export { whichVersionIsPinned } from './whichVersionIsPinned.js';
|
|
24
25
|
export interface ResolverFactoryOptions {
|
|
25
26
|
cacheDir: string;
|
package/lib/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { readPkgFromCafs, } from '@pnpm/worker';
|
|
|
6
6
|
import { resolveWorkspaceRange } from '@pnpm/workspace.range-resolver';
|
|
7
7
|
import { LRUCache } from 'lru-cache';
|
|
8
8
|
import normalize from 'normalize-path';
|
|
9
|
-
import pMemoize from 'p-memoize';
|
|
9
|
+
import pMemoize, { pMemoizeClear } from 'p-memoize';
|
|
10
10
|
import { clone } from 'ramda';
|
|
11
11
|
import semver from 'semver';
|
|
12
12
|
import ssri from 'ssri';
|
|
@@ -15,7 +15,7 @@ import { fetchMetadataFromFromRegistry, RegistryResponseError } from './fetch.js
|
|
|
15
15
|
import { normalizeRegistryUrl } from './normalizeRegistryUrl.js';
|
|
16
16
|
import { parseBareSpecifier, parseJsrSpecifierToRegistryPackageSpec, } from './parseBareSpecifier.js';
|
|
17
17
|
import { pickPackage, } from './pickPackage.js';
|
|
18
|
-
import { pickVersionByVersionRange } from './pickPackageFromMeta.js';
|
|
18
|
+
import { pickPackageFromMeta, pickVersionByVersionRange } from './pickPackageFromMeta.js';
|
|
19
19
|
import { failIfTrustDowngraded } from './trustChecks.js';
|
|
20
20
|
import { whichVersionIsPinned } from './whichVersionIsPinned.js';
|
|
21
21
|
import { workspacePrefToNpm } from './workspacePrefToNpm.js';
|
|
@@ -59,7 +59,7 @@ function formatTimeAgo(date) {
|
|
|
59
59
|
}
|
|
60
60
|
return `${diffMinutes} minute${diffMinutes === 1 ? '' : 's'} ago`;
|
|
61
61
|
}
|
|
62
|
-
export { parseBareSpecifier, RegistryResponseError, workspacePrefToNpm, };
|
|
62
|
+
export { fetchMetadataFromFromRegistry, parseBareSpecifier, pickPackageFromMeta, pickVersionByVersionRange, RegistryResponseError, workspacePrefToNpm, };
|
|
63
63
|
export { whichVersionIsPinned } from './whichVersionIsPinned.js';
|
|
64
64
|
export function createNpmResolver(fetchFromRegistry, getAuthHeader, opts) {
|
|
65
65
|
if (typeof opts.cacheDir !== 'string') {
|
|
@@ -124,6 +124,7 @@ export function createNpmResolver(fetchFromRegistry, getAuthHeader, opts) {
|
|
|
124
124
|
resolveFromJsr: resolveJsr.bind(null, ctx),
|
|
125
125
|
clearCache: () => {
|
|
126
126
|
metaCache.clear();
|
|
127
|
+
pMemoizeClear(fetch);
|
|
127
128
|
},
|
|
128
129
|
};
|
|
129
130
|
}
|
package/lib/pickPackage.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PackageInRegistry, PackageMeta } from '@pnpm/resolving.registry.types';
|
|
2
|
-
import type { FetchMetadataResult } from './fetch.js';
|
|
2
|
+
import type { FetchMetadataNotModifiedResult, FetchMetadataResult } from './fetch.js';
|
|
3
3
|
import type { RegistryPackageSpec } from './parseBareSpecifier.js';
|
|
4
4
|
import { type PickPackageFromMetaOptions } from './pickPackageFromMeta.js';
|
|
5
5
|
export interface PackageMetaCache {
|
|
@@ -20,7 +20,9 @@ export declare function pickPackage(ctx: {
|
|
|
20
20
|
registry: string;
|
|
21
21
|
authHeaderValue?: string;
|
|
22
22
|
fullMetadata?: boolean;
|
|
23
|
-
|
|
23
|
+
etag?: string;
|
|
24
|
+
modified?: string;
|
|
25
|
+
}) => Promise<FetchMetadataResult | FetchMetadataNotModifiedResult>;
|
|
24
26
|
fullMetadata?: boolean;
|
|
25
27
|
metaCache: PackageMetaCache;
|
|
26
28
|
cacheDir: string;
|
package/lib/pickPackage.js
CHANGED
|
@@ -91,7 +91,7 @@ export async function pickPackage(ctx, spec, opts) {
|
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
93
|
const registryName = getRegistryName(opts.registry);
|
|
94
|
-
const pkgMirror = path.join(ctx.cacheDir, metaDir, registryName, `${encodePkgName(spec.name)}.
|
|
94
|
+
const pkgMirror = path.join(ctx.cacheDir, metaDir, registryName, `${encodePkgName(spec.name)}.jsonl`);
|
|
95
95
|
return runLimited(pkgMirror, async (limit) => {
|
|
96
96
|
let metaCachedInStore;
|
|
97
97
|
if (ctx.offline === true || ctx.preferOffline === true || opts.pickLowestVersion) {
|
|
@@ -136,51 +136,106 @@ export async function pickPackage(ctx, spec, opts) {
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
if (opts.publishedBy) {
|
|
139
|
-
|
|
140
|
-
if (
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
139
|
+
const mtime = await limit(async () => getFileMtime(pkgMirror));
|
|
140
|
+
if (mtime != null && mtime >= opts.publishedBy) {
|
|
141
|
+
metaCachedInStore = metaCachedInStore ?? await limit(async () => loadMeta(pkgMirror));
|
|
142
|
+
if (metaCachedInStore != null) {
|
|
143
|
+
try {
|
|
144
|
+
const pickedPackage = _pickPackageFromMeta(metaCachedInStore);
|
|
145
|
+
if (pickedPackage) {
|
|
146
|
+
return {
|
|
147
|
+
meta: metaCachedInStore,
|
|
148
|
+
pickedPackage,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
148
151
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
152
|
+
catch (err) {
|
|
153
|
+
// Don't rethrow ERR_PNPM_MISSING_TIME from cached abbreviated metadata —
|
|
154
|
+
// let the code fall through to the network fetch path which will get full metadata.
|
|
155
|
+
if (ctx.strictPublishedByCheck &&
|
|
156
|
+
!(isMissingTimeError(err))) {
|
|
157
|
+
throw err;
|
|
158
|
+
}
|
|
153
159
|
}
|
|
154
160
|
}
|
|
155
161
|
}
|
|
156
162
|
}
|
|
157
163
|
try {
|
|
158
|
-
|
|
164
|
+
// Load only the cache headers (etag, modified) for conditional request headers.
|
|
165
|
+
// This avoids reading and parsing the full metadata file (which can be megabytes)
|
|
166
|
+
// when the registry returns 200 and the old metadata would be discarded anyway.
|
|
167
|
+
const cacheHeaders = metaCachedInStore != null
|
|
168
|
+
? { etag: metaCachedInStore.etag, modified: metaCachedInStore.modified ?? metaCachedInStore.time?.modified }
|
|
169
|
+
: await limit(async () => loadMetaHeaders(pkgMirror));
|
|
170
|
+
let fetchResult = await ctx.fetch(spec.name, {
|
|
159
171
|
authHeaderValue: opts.authHeaderValue,
|
|
160
172
|
fullMetadata,
|
|
173
|
+
etag: cacheHeaders?.etag,
|
|
174
|
+
modified: cacheHeaders?.modified,
|
|
161
175
|
registry: opts.registry,
|
|
162
176
|
});
|
|
163
|
-
|
|
177
|
+
// 304 Not Modified — registry confirmed local cache is still fresh.
|
|
178
|
+
// Now we need the full metadata, so load it from disk.
|
|
179
|
+
if (fetchResult.notModified) {
|
|
180
|
+
metaCachedInStore = metaCachedInStore ?? await limit(async () => loadMeta(pkgMirror));
|
|
181
|
+
if (metaCachedInStore != null) {
|
|
182
|
+
ctx.metaCache.set(cacheKey, metaCachedInStore);
|
|
183
|
+
return {
|
|
184
|
+
meta: metaCachedInStore,
|
|
185
|
+
pickedPackage: _pickPackageFromMeta(metaCachedInStore),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
throw new PnpmError('CACHE_MISSING_AFTER_304', `Metadata cache for ${spec.name} is unreadable after receiving 304 Not Modified`);
|
|
189
|
+
}
|
|
164
190
|
let meta = fetchResult.meta;
|
|
165
|
-
let
|
|
191
|
+
let resultToSave = fetchResult;
|
|
192
|
+
// When minimumReleaseAge is active and we fetched abbreviated metadata,
|
|
193
|
+
// check if the package was recently modified and needs full metadata
|
|
194
|
+
// for per-version time-based filtering.
|
|
195
|
+
//
|
|
196
|
+
// This two-step approach is intentional: abbreviated metadata is much smaller,
|
|
197
|
+
// and most packages won't have been modified recently enough to need the full
|
|
198
|
+
// document. We only upgrade to full metadata when the package's modification
|
|
199
|
+
// date is recent enough that some versions might not yet be "mature."
|
|
200
|
+
if (opts.publishedBy &&
|
|
201
|
+
!fullMetadata &&
|
|
202
|
+
meta.time == null &&
|
|
203
|
+
opts.publishedByExclude?.(spec.name) !== true) {
|
|
204
|
+
const modifiedDate = meta.modified ? new Date(meta.modified) : null;
|
|
205
|
+
const isModifiedValid = modifiedDate != null && !Number.isNaN(modifiedDate.getTime());
|
|
206
|
+
if (!isModifiedValid || modifiedDate >= opts.publishedBy) {
|
|
207
|
+
// Save the abbreviated metadata to the abbreviated cache before re-fetching full.
|
|
208
|
+
if (!opts.dryRun) {
|
|
209
|
+
const abbreviatedJson = prepareJsonForDisk(fetchResult.meta, fetchResult.etag, fetchResult.jsonText);
|
|
210
|
+
// Fire-and-forget save to the abbreviated cache path (pkgMirror).
|
|
211
|
+
runLimited(pkgMirror, (limit) => limit(async () => {
|
|
212
|
+
try {
|
|
213
|
+
await saveMeta(pkgMirror, abbreviatedJson);
|
|
214
|
+
}
|
|
215
|
+
catch (err) { // eslint-disable-line
|
|
216
|
+
// We don't care if this file was not written to the cache
|
|
217
|
+
}
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
const fullFetchResult = await ctx.fetch(spec.name, {
|
|
221
|
+
authHeaderValue: opts.authHeaderValue,
|
|
222
|
+
fullMetadata: true,
|
|
223
|
+
registry: opts.registry,
|
|
224
|
+
});
|
|
225
|
+
if (!fullFetchResult.notModified) {
|
|
226
|
+
resultToSave = fullFetchResult;
|
|
227
|
+
meta = fullFetchResult.meta;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
166
231
|
if (ctx.filterMetadata) {
|
|
167
232
|
meta = clearMeta(meta);
|
|
168
233
|
}
|
|
169
|
-
else if (typeof fetchResult.jsonText === 'string') {
|
|
170
|
-
// Reuse the raw JSON text from the registry response to avoid re-stringifying.
|
|
171
|
-
// Inject cachedAt at the start of the JSON object. To be robust against BOMs or
|
|
172
|
-
// leading whitespace/newlines, locate the first '{' and splice after it.
|
|
173
|
-
const jsonText = fetchResult.jsonText;
|
|
174
|
-
const firstBraceIndex = jsonText.indexOf('{');
|
|
175
|
-
if (firstBraceIndex !== -1) {
|
|
176
|
-
jsonToSave = `{"cachedAt":${cachedAt},${jsonText.slice(firstBraceIndex + 1)}`;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
meta.cachedAt = cachedAt;
|
|
180
|
-
// only save meta to cache, when it is fresh
|
|
181
|
-
ctx.metaCache.set(cacheKey, meta);
|
|
182
234
|
if (!opts.dryRun) {
|
|
183
|
-
|
|
235
|
+
// Serialize before setting meta.etag so it only lives in the headers line, not the body.
|
|
236
|
+
const jsonForDisk = ctx.filterMetadata
|
|
237
|
+
? prepareJsonForDisk(meta, resultToSave.etag)
|
|
238
|
+
: prepareJsonForDisk(resultToSave.meta, resultToSave.etag, resultToSave.jsonText);
|
|
184
239
|
runLimited(pkgMirror, (limit) => limit(async () => {
|
|
185
240
|
try {
|
|
186
241
|
await saveMeta(pkgMirror, jsonForDisk);
|
|
@@ -190,6 +245,9 @@ export async function pickPackage(ctx, spec, opts) {
|
|
|
190
245
|
}
|
|
191
246
|
}));
|
|
192
247
|
}
|
|
248
|
+
meta.etag = resultToSave.etag;
|
|
249
|
+
// only save meta to cache, when it is fresh
|
|
250
|
+
ctx.metaCache.set(cacheKey, meta);
|
|
193
251
|
return {
|
|
194
252
|
meta,
|
|
195
253
|
pickedPackage: _pickPackageFromMeta(meta),
|
|
@@ -241,7 +299,7 @@ function clearMeta(pkg) {
|
|
|
241
299
|
'dist-tags': pkg['dist-tags'],
|
|
242
300
|
versions,
|
|
243
301
|
time: pkg.time,
|
|
244
|
-
|
|
302
|
+
modified: pkg.modified,
|
|
245
303
|
};
|
|
246
304
|
}
|
|
247
305
|
function encodePkgName(pkgName) {
|
|
@@ -250,10 +308,75 @@ function encodePkgName(pkgName) {
|
|
|
250
308
|
}
|
|
251
309
|
return pkgName;
|
|
252
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* Formats metadata for disk storage as two-line NDJSON:
|
|
313
|
+
* Line 1: cache headers (etag, modified) — small, fast to read
|
|
314
|
+
* Line 2: the full registry metadata JSON — unchanged from the registry response
|
|
315
|
+
*/
|
|
316
|
+
function prepareJsonForDisk(meta, etag, jsonText) {
|
|
317
|
+
const modified = meta.modified ?? meta.time?.modified;
|
|
318
|
+
const headers = JSON.stringify({ etag, modified });
|
|
319
|
+
const body = jsonText ?? JSON.stringify(meta);
|
|
320
|
+
return `${headers}\n${body}`;
|
|
321
|
+
}
|
|
322
|
+
function isMissingTimeError(err) {
|
|
323
|
+
return (err != null &&
|
|
324
|
+
typeof err === 'object' &&
|
|
325
|
+
'code' in err &&
|
|
326
|
+
err.code === 'ERR_PNPM_MISSING_TIME');
|
|
327
|
+
}
|
|
328
|
+
async function getFileMtime(filePath) {
|
|
329
|
+
try {
|
|
330
|
+
const stat = await fs.stat(filePath);
|
|
331
|
+
return stat.mtime;
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Reads only the first line of the cached NDJSON metadata file to extract
|
|
339
|
+
* the cache headers (etag, modified). This avoids reading and
|
|
340
|
+
* parsing the full metadata (which can be megabytes for popular packages)
|
|
341
|
+
* when we only need conditional-request headers.
|
|
342
|
+
*/
|
|
343
|
+
async function loadMetaHeaders(pkgMirror) {
|
|
344
|
+
let fh;
|
|
345
|
+
try {
|
|
346
|
+
fh = await fs.open(pkgMirror, 'r');
|
|
347
|
+
// The first line (headers JSON) is typically ~100 bytes; 1 KB is plenty.
|
|
348
|
+
const buf = Buffer.alloc(1024);
|
|
349
|
+
const { bytesRead } = await fh.read(buf, 0, 1024, 0);
|
|
350
|
+
if (bytesRead === 0)
|
|
351
|
+
return null;
|
|
352
|
+
const chunk = buf.toString('utf8', 0, bytesRead);
|
|
353
|
+
const newlineIdx = chunk.indexOf('\n');
|
|
354
|
+
if (newlineIdx === -1)
|
|
355
|
+
return null;
|
|
356
|
+
return JSON.parse(chunk.slice(0, newlineIdx));
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
finally {
|
|
362
|
+
await fh?.close();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Reads the full metadata from the cached NDJSON file.
|
|
367
|
+
* Line 1: cache headers (etag, modified)
|
|
368
|
+
* Line 2: registry metadata JSON
|
|
369
|
+
*/
|
|
253
370
|
async function loadMeta(pkgMirror) {
|
|
254
371
|
try {
|
|
255
372
|
const data = await gfs.readFile(pkgMirror, 'utf8');
|
|
256
|
-
|
|
373
|
+
const newlineIdx = data.indexOf('\n');
|
|
374
|
+
if (newlineIdx === -1)
|
|
375
|
+
return null;
|
|
376
|
+
const headers = JSON.parse(data.slice(0, newlineIdx));
|
|
377
|
+
const meta = JSON.parse(data.slice(newlineIdx + 1));
|
|
378
|
+
meta.etag = headers.etag;
|
|
379
|
+
return meta;
|
|
257
380
|
}
|
|
258
381
|
catch {
|
|
259
382
|
return null;
|
|
@@ -6,9 +6,22 @@ export function pickPackageFromMeta(pickVersionByVersionRangeFn, { preferredVers
|
|
|
6
6
|
if (publishedBy) {
|
|
7
7
|
const excludeResult = publishedByExclude?.(meta.name) ?? false;
|
|
8
8
|
if (excludeResult !== true) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
if (meta.time != null) {
|
|
10
|
+
// Full metadata with per-version timestamps: filter normally
|
|
11
|
+
assertMetaHasTime(meta);
|
|
12
|
+
const trustedVersions = Array.isArray(excludeResult) ? excludeResult : undefined;
|
|
13
|
+
meta = filterPkgMetadataByPublishDate(meta, publishedBy, trustedVersions);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
const modifiedDate = parseModifiedDate(meta.modified);
|
|
17
|
+
if (modifiedDate == null || modifiedDate >= publishedBy) {
|
|
18
|
+
// Abbreviated metadata without per-version timestamps, and the package
|
|
19
|
+
// was recently modified (or has no/invalid modified field). We cannot determine
|
|
20
|
+
// which individual versions are mature enough — need full metadata.
|
|
21
|
+
assertMetaHasTime(meta);
|
|
22
|
+
}
|
|
23
|
+
// else: meta.modified < publishedBy — all versions are old enough, no filtering needed
|
|
24
|
+
}
|
|
12
25
|
}
|
|
13
26
|
}
|
|
14
27
|
if ((!meta.versions || Object.keys(meta.versions).length === 0) && !publishedBy) {
|
|
@@ -57,7 +70,7 @@ export function pickPackageFromMeta(pickVersionByVersionRangeFn, { preferredVers
|
|
|
57
70
|
err.code.startsWith('ERR_PNPM_')) {
|
|
58
71
|
throw err;
|
|
59
72
|
}
|
|
60
|
-
throw new PnpmError('MALFORMED_METADATA', `Received malformed metadata for "${spec.name}"`, { hint: 'This might mean that the package was unpublished from the registry' });
|
|
73
|
+
throw new PnpmError('MALFORMED_METADATA', `Received malformed metadata for "${spec.name}"`, { hint: 'This might mean that the package was unpublished from the registry', cause: err });
|
|
61
74
|
}
|
|
62
75
|
}
|
|
63
76
|
export function assertMetaHasTime(meta) {
|
|
@@ -65,6 +78,14 @@ export function assertMetaHasTime(meta) {
|
|
|
65
78
|
throw new PnpmError('MISSING_TIME', `The metadata of ${meta.name} is missing the "time" field`);
|
|
66
79
|
}
|
|
67
80
|
}
|
|
81
|
+
function parseModifiedDate(modified) {
|
|
82
|
+
if (!modified)
|
|
83
|
+
return null;
|
|
84
|
+
const date = new Date(modified);
|
|
85
|
+
if (Number.isNaN(date.getTime()))
|
|
86
|
+
return null;
|
|
87
|
+
return date;
|
|
88
|
+
}
|
|
68
89
|
const semverRangeCache = new Map();
|
|
69
90
|
// This is a performance optimization; working with string-ish semver
|
|
70
91
|
// causes lots of allocations and repeated work, but caching the Range
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/resolving.npm-resolver",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1100.0.0",
|
|
4
4
|
"description": "Resolver for npm-hosted packages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -28,52 +28,52 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@zkochan/retry": "^0.2.0",
|
|
30
30
|
"encode-registry": "^3.0.1",
|
|
31
|
-
"lru-cache": "^11.
|
|
31
|
+
"lru-cache": "^11.2.7",
|
|
32
32
|
"normalize-path": "^3.0.0",
|
|
33
33
|
"p-limit": "^7.1.0",
|
|
34
34
|
"p-memoize": "8.0.0",
|
|
35
35
|
"parse-npm-tarball-url": "^4.0.0",
|
|
36
36
|
"path-temp": "^3.0.0",
|
|
37
37
|
"ramda": "npm:@pnpm/ramda@0.28.1",
|
|
38
|
-
"rename-overwrite": "^7.0.
|
|
38
|
+
"rename-overwrite": "^7.0.1",
|
|
39
39
|
"semver": "^7.7.2",
|
|
40
40
|
"semver-utils": "^1.1.4",
|
|
41
|
-
"ssri": "13.0.
|
|
41
|
+
"ssri": "13.0.1",
|
|
42
42
|
"version-selector-type": "^3.0.0",
|
|
43
|
-
"@pnpm/
|
|
44
|
-
"@pnpm/
|
|
45
|
-
"@pnpm/
|
|
46
|
-
"@pnpm/
|
|
47
|
-
"@pnpm/
|
|
48
|
-
"@pnpm/
|
|
49
|
-
"@pnpm/
|
|
50
|
-
"@pnpm/
|
|
51
|
-
"@pnpm/resolving.registry.
|
|
52
|
-
"@pnpm/resolving.
|
|
53
|
-
"@pnpm/resolving.
|
|
54
|
-
"@pnpm/
|
|
55
|
-
"@pnpm/store.index": "
|
|
56
|
-
"@pnpm/
|
|
57
|
-
"@pnpm/workspace.spec-parser": "
|
|
58
|
-
"@pnpm/workspace.range-resolver": "
|
|
43
|
+
"@pnpm/constants": "1100.0.0",
|
|
44
|
+
"@pnpm/config.pick-registry-for-package": "1100.0.0",
|
|
45
|
+
"@pnpm/core-loggers": "1100.0.0",
|
|
46
|
+
"@pnpm/crypto.hash": "1100.0.0",
|
|
47
|
+
"@pnpm/error": "1100.0.0",
|
|
48
|
+
"@pnpm/fetching.types": "1100.0.0",
|
|
49
|
+
"@pnpm/fs.graceful-fs": "1100.0.0",
|
|
50
|
+
"@pnpm/resolving.jsr-specifier-parser": "1100.0.0",
|
|
51
|
+
"@pnpm/resolving.registry.types": "1100.0.0",
|
|
52
|
+
"@pnpm/resolving.registry.pkg-metadata-filter": "1100.0.0",
|
|
53
|
+
"@pnpm/resolving.resolver-base": "1100.0.0",
|
|
54
|
+
"@pnpm/types": "1100.0.0",
|
|
55
|
+
"@pnpm/store.index": "1100.0.0",
|
|
56
|
+
"@pnpm/store.cafs": "1100.0.0",
|
|
57
|
+
"@pnpm/workspace.spec-parser": "1100.0.0",
|
|
58
|
+
"@pnpm/workspace.range-resolver": "1100.0.0"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"@pnpm/logger": ">=1001.0.0 <1002.0.0",
|
|
62
|
-
"@pnpm/worker": "^
|
|
62
|
+
"@pnpm/worker": "^1100.0.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@types/normalize-path": "^3.0.2",
|
|
66
|
-
"@types/ramda": "0.
|
|
66
|
+
"@types/ramda": "0.31.1",
|
|
67
67
|
"@types/semver": "7.7.1",
|
|
68
68
|
"@types/ssri": "^7.1.5",
|
|
69
69
|
"load-json-file": "^7.0.1",
|
|
70
|
-
"nock": "13.3.4",
|
|
71
70
|
"tempy": "3.0.0",
|
|
72
|
-
"@pnpm/config.version-policy": "
|
|
73
|
-
"@pnpm/
|
|
74
|
-
"@pnpm/
|
|
75
|
-
"@pnpm/
|
|
76
|
-
"@pnpm/
|
|
71
|
+
"@pnpm/config.version-policy": "1100.0.0",
|
|
72
|
+
"@pnpm/logger": "1100.0.0",
|
|
73
|
+
"@pnpm/network.fetch": "1100.0.0",
|
|
74
|
+
"@pnpm/testing.mock-agent": "1100.0.0",
|
|
75
|
+
"@pnpm/resolving.npm-resolver": "1100.0.0",
|
|
76
|
+
"@pnpm/test-fixtures": "1100.0.0"
|
|
77
77
|
},
|
|
78
78
|
"engines": {
|
|
79
79
|
"node": ">=22.13"
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
},
|
|
84
84
|
"scripts": {
|
|
85
85
|
"lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"
|
|
86
|
+
"test": "pn compile && pn .test",
|
|
87
|
+
"compile": "tsgo --build && pn lint --fix",
|
|
88
|
+
".test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169\" jest"
|
|
89
89
|
}
|
|
90
90
|
}
|