@pnpm/resolving.npm-resolver 1102.1.3 → 1102.1.4

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.
@@ -1,631 +0,0 @@
1
- import { promises as fs } from 'node:fs';
2
- import path from 'node:path';
3
- import { ABBREVIATED_META_DIR, FULL_FILTERED_META_DIR, FULL_META_DIR } from '@pnpm/constants';
4
- import { createHexHash } from '@pnpm/crypto.hash';
5
- import { PnpmError } from '@pnpm/error';
6
- import gfs from '@pnpm/fs.graceful-fs';
7
- import { globalWarn, logger } from '@pnpm/logger';
8
- import getRegistryName from 'encode-registry';
9
- import pLimit, {} from 'p-limit';
10
- import { fastPathTemp as pathTemp } from 'path-temp';
11
- import { renameOverwrite } from 'rename-overwrite';
12
- import semver from 'semver';
13
- import { clearMeta } from './clearMeta.js';
14
- import { pickLowestVersionByVersionRange, pickPackageFromMeta, pickVersionByVersionRange, } from './pickPackageFromMeta.js';
15
- import { toRaw } from './toRaw.js';
16
- /**
17
- * prevents simultaneous operations on the meta.json
18
- * otherwise it would cause EPERM exceptions
19
- */
20
- const metafileOperationLimits = {};
21
- /**
22
- * To prevent metafileOperationLimits from holding onto objects in memory on
23
- * the order of the number of packages, refcount the limiters and drop them
24
- * once they are no longer needed. Callers of this function should ensure
25
- * that the limiter is no longer referenced once fn's Promise has resolved.
26
- */
27
- async function runLimited(pkgMirror, fn) {
28
- let entry;
29
- try {
30
- entry = metafileOperationLimits[pkgMirror] ??= { count: 0, limit: pLimit(1) };
31
- entry.count++;
32
- return await fn(entry.limit);
33
- }
34
- finally {
35
- entry.count--;
36
- if (entry.count === 0) {
37
- metafileOperationLimits[pkgMirror] = undefined;
38
- }
39
- }
40
- }
41
- // When includeLatestTag is set, the "latest" dist-tag is added as a candidate
42
- // alongside the requested spec, and the higher-versioned pick wins.
43
- function runPicker(pickerOpts, spec, pickOne) {
44
- const currentPkg = pickOne(spec);
45
- if (!pickerOpts.includeLatestTag)
46
- return currentPkg;
47
- const latestPkg = pickOne({ ...spec, type: 'tag', fetchSpec: 'latest' });
48
- return pickMax(latestPkg, currentPkg);
49
- }
50
- // Returns whichever pick has the higher version, treating null as "no match".
51
- function pickMax(a, b) {
52
- if (!a)
53
- return b;
54
- if (!b)
55
- return a;
56
- return semver.lt(a.version, b.version) ? b : a;
57
- }
58
- const pickHighest = pickPackageFromMeta.bind(null, pickVersionByVersionRange);
59
- const pickLowest = pickPackageFromMeta.bind(null, pickLowestVersionByVersionRange);
60
- // When minimumReleaseAge is active: try the highest mature version; if none
61
- // satisfies the range, fall back to the lowest version regardless of maturity
62
- // so the resolver can report the violation inline and let the install layer
63
- // (or other caller) decide what to do — never throw at this layer.
64
- function pickRespectingMinReleaseAge(pickerOpts, spec, meta) {
65
- return runPicker(pickerOpts, spec, (targetSpec) => {
66
- const highest = pickHighest(pickerOpts, meta, targetSpec);
67
- if (highest)
68
- return highest;
69
- return pickLowest({
70
- preferredVersionSelectors: pickerOpts.preferredVersionSelectors,
71
- }, meta, targetSpec);
72
- });
73
- }
74
- // When minimumReleaseAge is not active: pick by pickLowestVersion preference.
75
- function pickIgnoringReleaseAge(pickerOpts, spec, meta) {
76
- const pickVersion = pickerOpts.pickLowestVersion ? pickLowest : pickHighest;
77
- return runPicker(pickerOpts, spec, (targetSpec) => pickVersion(pickerOpts, meta, targetSpec));
78
- }
79
- // Used in shortcut/fall-through paths: if it fails (including with
80
- // ERR_PNPM_MISSING_TIME), the caller falls through to the next path — e.g.
81
- // the network fetch that can upgrade abbreviated metadata to full.
82
- function pickMatchingVersionFast(pickerOpts, spec, meta) {
83
- return pickerOpts.publishedBy
84
- ? pickRespectingMinReleaseAge(pickerOpts, spec, meta)
85
- : pickIgnoringReleaseAge(pickerOpts, spec, meta);
86
- }
87
- // Used at terminal return sites where no further fallback path exists. When
88
- // metadata lacks the per-version `time` field and ignoreMissingTimeField is
89
- // enabled, skip the minimumReleaseAge filter with a warning instead of
90
- // failing hard.
91
- function pickMatchingVersionFinal(pickerOpts, spec, meta) {
92
- try {
93
- return pickMatchingVersionFast(pickerOpts, spec, meta);
94
- }
95
- catch (err) {
96
- if (pickerOpts.ignoreMissingTimeField && isMissingTimeError(err)) {
97
- warnMissingTimeFieldOnce(meta.name);
98
- return pickMatchingVersionFast({
99
- ...pickerOpts,
100
- publishedBy: undefined,
101
- publishedByExclude: undefined,
102
- }, spec, meta);
103
- }
104
- throw err;
105
- }
106
- }
107
- /**
108
- * Packuments promoted into the in-memory cache straight from the on-disk
109
- * mirror, without registry validation. The mirror may predate versions the
110
- * registry has, so when a cache hit on such an entry can't satisfy the
111
- * requested spec (and the resolver isn't offline), `pickPackage` falls
112
- * through to the regular flow — a conditional registry request — instead of
113
- * failing the pick, exactly as it would have before the entry was promoted.
114
- * Network-fetched and 304-revalidated packuments are never in this set, so
115
- * hits on them keep returning directly even when the pick fails (the caller
116
- * then falls back to workspace packages or reports no matching version).
117
- */
118
- const unverifiedDiskPackuments = new WeakSet();
119
- /**
120
- * Promote a packument parsed from the on-disk mirror into the in-memory
121
- * cache, so repeat resolutions of the same package (common across a large
122
- * dependency graph) don't re-read and re-parse the mirror. The entry is
123
- * remembered as disk-sourced (see {@link unverifiedDiskPackuments}) because it
124
- * never went through registry validation.
125
- */
126
- function cacheDiskLoadedMeta(metaCache, cacheKey, meta) {
127
- unverifiedDiskPackuments.add(meta);
128
- metaCache.set(cacheKey, meta);
129
- }
130
- export async function pickPackage(ctx, spec, opts) {
131
- opts = opts || {};
132
- const pickerOpts = {
133
- preferredVersionSelectors: opts.preferredVersionSelectors,
134
- publishedBy: opts.publishedBy,
135
- publishedByExclude: opts.publishedByExclude,
136
- pickLowestVersion: opts.pickLowestVersion,
137
- includeLatestTag: opts.includeLatestTag,
138
- ignoreMissingTimeField: ctx.ignoreMissingTimeField,
139
- };
140
- validatePackageName(spec.name);
141
- // Use full metadata for optional dependencies to get libc field.
142
- // See: https://github.com/pnpm/pnpm/issues/9950
143
- const fullMetadata = opts.optional === true || ctx.fullMetadata === true;
144
- const metaDir = fullMetadata
145
- ? (ctx.filterMetadata ? FULL_FILTERED_META_DIR : FULL_META_DIR)
146
- : ABBREVIATED_META_DIR;
147
- // Cache key includes the registry so a package of the same name served by two
148
- // registries in one install can't share a slot (which would resolve the wrong
149
- // tarball/integrity), plus fullMetadata/filterMetadata so a request is never
150
- // served a less-detailed or differently-stripped document than it asked for.
151
- const cacheKey = getPkgMetaCacheKey(opts.registry, spec.name, fullMetadata, ctx.filterMetadata === true);
152
- const pkgMirror = getPkgMirrorPath(ctx.cacheDir, metaDir, opts.registry, spec.name);
153
- // updateChecksums must reach the conditional registry request below, so it
154
- // can't be served from the in-memory cache — which may hold a disk-promoted
155
- // entry rather than a fresh network fetch (see the updateChecksums doc).
156
- const cachedMeta = opts.updateChecksums ? undefined : ctx.metaCache.get(cacheKey);
157
- if (cachedMeta != null) {
158
- // The in-memory cache may hold abbreviated metadata from an earlier call
159
- // that didn't need `time` (no publishedBy then). If this call has
160
- // publishedBy and the package was modified recently, upgrade to full
161
- // metadata so the maturity check runs properly.
162
- const upgrade = await maybeUpgradeAbbreviatedMetaForReleaseAge(ctx, spec, opts, cachedMeta);
163
- let metaForCache = upgrade.meta;
164
- if (upgrade.upgradedFrom != null) {
165
- // Persist the upgraded meta to disk too: the on-disk mirror still holds
166
- // the abbreviated form, so without this a fresh process would re-trigger
167
- // the upgrade fetch on its next install.
168
- metaForCache = opts.dryRun
169
- ? upgrade.meta
170
- : persistUpgradedMeta(ctx, pkgMirror, upgrade.upgradedFrom);
171
- ctx.metaCache.set(cacheKey, metaForCache);
172
- }
173
- const pickedPackage = pickMatchingVersionFinal(pickerOpts, spec, metaForCache);
174
- if (pickedPackage != null || ctx.offline === true || !unverifiedDiskPackuments.has(metaForCache)) {
175
- return {
176
- meta: metaForCache,
177
- pickedPackage,
178
- };
179
- }
180
- // Disk-promoted meta that can't satisfy the spec: fall through and
181
- // revalidate against the registry (see unverifiedDiskPackuments).
182
- }
183
- return runLimited(pkgMirror, async (limit) => {
184
- let metaCachedInStore;
185
- if (ctx.offline === true || ctx.preferOffline === true || opts.pickLowestVersion) {
186
- metaCachedInStore = await limit(async () => loadMeta(pkgMirror));
187
- if (ctx.offline) {
188
- if (metaCachedInStore != null) {
189
- // maybeUpgradeAbbreviatedMetaForReleaseAge short-circuits when
190
- // offline, so a later in-memory cache hit returns this same meta
191
- // without any network access.
192
- cacheDiskLoadedMeta(ctx.metaCache, cacheKey, metaCachedInStore);
193
- return {
194
- meta: metaCachedInStore,
195
- pickedPackage: pickMatchingVersionFinal(pickerOpts, spec, metaCachedInStore),
196
- };
197
- }
198
- throw new PnpmError('NO_OFFLINE_META', `Failed to resolve ${toRaw(spec)} in package mirror ${pkgMirror}`);
199
- }
200
- if (metaCachedInStore != null) {
201
- // Disk-cached meta may be abbreviated; upgrade for the maturity check
202
- // before letting pickMatchingVersionFinal warn-and-skip on missing time.
203
- const upgrade = await maybeUpgradeAbbreviatedMetaForReleaseAge(ctx, spec, opts, metaCachedInStore);
204
- metaCachedInStore = upgrade.meta;
205
- if (upgrade.upgradedFrom != null) {
206
- // Persist so the next install skips this upgrade fetch entirely.
207
- if (!opts.dryRun) {
208
- metaCachedInStore = persistUpgradedMeta(ctx, pkgMirror, upgrade.upgradedFrom);
209
- }
210
- ctx.metaCache.set(cacheKey, metaCachedInStore);
211
- }
212
- const pickedPackage = pickMatchingVersionFinal(pickerOpts, spec, metaCachedInStore);
213
- if (pickedPackage) {
214
- // A cache hit re-runs maybeUpgradeAbbreviatedMetaForReleaseAge, so
215
- // serving this meta from memory can't bypass the release-age
216
- // upgrade. When the upgrade branch above already cached the
217
- // registry-validated upgraded meta, don't overwrite it with a
218
- // disk-sourced marking.
219
- if (upgrade.upgradedFrom == null) {
220
- cacheDiskLoadedMeta(ctx.metaCache, cacheKey, metaCachedInStore);
221
- }
222
- return {
223
- meta: metaCachedInStore,
224
- pickedPackage,
225
- };
226
- }
227
- }
228
- }
229
- if (!opts.includeLatestTag && !opts.updateChecksums && spec.type === 'version') {
230
- metaCachedInStore = metaCachedInStore ?? await limit(async () => loadMeta(pkgMirror));
231
- // use the cached meta only if it has the required package version
232
- // otherwise it is probably out of date
233
- if ((metaCachedInStore?.versions?.[spec.fetchSpec]) != null) {
234
- try {
235
- const pickedPackage = pickMatchingVersionFast(pickerOpts, spec, metaCachedInStore);
236
- if (pickedPackage) {
237
- cacheDiskLoadedMeta(ctx.metaCache, cacheKey, metaCachedInStore);
238
- return {
239
- meta: metaCachedInStore,
240
- pickedPackage,
241
- };
242
- }
243
- }
244
- catch {
245
- // Swallow fast-path errors (e.g. ERR_PNPM_MISSING_TIME from
246
- // abbreviated meta) and fall through to the network fetch, which
247
- // can upgrade to full metadata and run the maturity check on
248
- // real `time` data.
249
- }
250
- }
251
- }
252
- if (opts.publishedBy && opts.publishedByExclude?.(spec.name) !== true) {
253
- const mtime = await limit(async () => getFileMtime(pkgMirror));
254
- if (mtime != null && mtime >= opts.publishedBy) {
255
- metaCachedInStore = metaCachedInStore ?? await limit(async () => loadMeta(pkgMirror));
256
- if (metaCachedInStore != null) {
257
- try {
258
- const pickedPackage = pickMatchingVersionFast(pickerOpts, spec, metaCachedInStore);
259
- if (pickedPackage) {
260
- return {
261
- meta: metaCachedInStore,
262
- pickedPackage,
263
- };
264
- }
265
- }
266
- catch {
267
- // Same as above — fall through to the network fetch.
268
- }
269
- }
270
- }
271
- }
272
- try {
273
- // Load only the cache headers (etag, modified) for conditional request headers.
274
- // This avoids reading and parsing the full metadata file (which can be megabytes)
275
- // when the registry returns 200 and the old metadata would be discarded anyway.
276
- const cacheHeaders = metaCachedInStore != null
277
- ? { etag: metaCachedInStore.etag, modified: metaCachedInStore.modified ?? metaCachedInStore.time?.modified }
278
- : await limit(async () => loadMetaHeaders(pkgMirror));
279
- let fetchResult = await ctx.fetch(spec.name, {
280
- authHeaderValue: opts.authHeaderValue,
281
- fullMetadata,
282
- etag: cacheHeaders?.etag,
283
- modified: cacheHeaders?.modified,
284
- registry: opts.registry,
285
- });
286
- // 304 Not Modified — registry confirmed local cache is still fresh.
287
- // Now we need the full metadata, so load it from disk.
288
- if (fetchResult.notModified) {
289
- metaCachedInStore = metaCachedInStore ?? await limit(async () => loadMeta(pkgMirror));
290
- if (metaCachedInStore != null) {
291
- // The registry just vouched that the cached packument equals its
292
- // current one, so the validation clock restarts now: bump the
293
- // mirror's mtime so the publishedBy freshness shortcut above can
294
- // fire again on the next install. Without this, a mirror older
295
- // than minimumReleaseAge re-validates on every subsequent
296
- // install — a 304 never rewrites the file. Fire-and-forget: a
297
- // read-only cache dir only costs another conditional request.
298
- if (!opts.dryRun) {
299
- const now = new Date();
300
- fs.utimes(pkgMirror, now, now).catch(() => { });
301
- }
302
- // The cached metadata may be abbreviated (no per-version `time`).
303
- // When minimumReleaseAge is active we need `time` for the maturity check,
304
- // so upgrade to full metadata via a follow-up fetch when warranted.
305
- // Without this, repeat installs of recently-modified packages would
306
- // silently bypass the maturity check via the warn-and-skip fallback.
307
- const upgrade = await maybeUpgradeAbbreviatedMetaForReleaseAge(ctx, spec, opts, metaCachedInStore);
308
- metaCachedInStore = upgrade.meta;
309
- if (upgrade.upgradedFrom != null && !opts.dryRun) {
310
- // Persist the upgraded full metadata to disk so subsequent installs
311
- // skip this upgrade fetch entirely (the cached meta will then have
312
- // `time` populated, so the upgrade condition won't trigger).
313
- metaCachedInStore = persistUpgradedMeta(ctx, pkgMirror, upgrade.upgradedFrom);
314
- }
315
- ctx.metaCache.set(cacheKey, metaCachedInStore);
316
- return {
317
- meta: metaCachedInStore,
318
- pickedPackage: pickMatchingVersionFinal(pickerOpts, spec, metaCachedInStore),
319
- };
320
- }
321
- throw new PnpmError('CACHE_MISSING_AFTER_304', `Metadata cache for ${spec.name} is unreadable after receiving 304 Not Modified`);
322
- }
323
- let meta = fetchResult.meta;
324
- let resultToSave = fetchResult;
325
- // When minimumReleaseAge is active and we fetched abbreviated metadata,
326
- // check if the package was recently modified and needs full metadata
327
- // for per-version time-based filtering.
328
- //
329
- // This two-step approach is intentional: abbreviated metadata is much smaller,
330
- // and most packages won't have been modified recently enough to need the full
331
- // document. We only upgrade to full metadata when the package's modification
332
- // date is recent enough that some versions might not yet be "mature."
333
- if (opts.publishedBy &&
334
- !fullMetadata &&
335
- meta.time == null &&
336
- opts.publishedByExclude?.(spec.name) !== true) {
337
- const modifiedDate = meta.modified ? new Date(meta.modified) : null;
338
- const isModifiedValid = modifiedDate != null && !Number.isNaN(modifiedDate.getTime());
339
- // Strict `>` (not `>=`) so the boundary case `modified == publishedBy`
340
- // takes the abbreviated fast path: `modified` is an upper bound on
341
- // every version's publish time, so when it equals the cutoff every
342
- // version passes the per-version `<=` filter in
343
- // `filterPkgMetadataByPublishDate` and a full re-fetch isn't needed.
344
- if (!isModifiedValid || modifiedDate > opts.publishedBy) {
345
- // Save the abbreviated metadata to the abbreviated cache before re-fetching full.
346
- if (!opts.dryRun) {
347
- const abbreviatedJson = prepareJsonForDisk(resultToSave.meta, resultToSave.etag, resultToSave.jsonText);
348
- // Fire-and-forget save to the abbreviated cache path (pkgMirror).
349
- runLimited(pkgMirror, (limit) => limit(async () => {
350
- try {
351
- await saveMeta(pkgMirror, abbreviatedJson);
352
- }
353
- catch (err) { // eslint-disable-line
354
- // We don't care if this file was not written to the cache
355
- }
356
- }));
357
- }
358
- const fullFetchResult = await ctx.fetch(spec.name, {
359
- authHeaderValue: opts.authHeaderValue,
360
- fullMetadata: true,
361
- registry: opts.registry,
362
- });
363
- if (!fullFetchResult.notModified) {
364
- resultToSave = fullFetchResult;
365
- meta = fullFetchResult.meta;
366
- }
367
- }
368
- }
369
- if (ctx.filterMetadata) {
370
- meta = clearMeta(meta);
371
- }
372
- if (!opts.dryRun) {
373
- // Serialize before setting meta.etag so it only lives in the headers line, not the body.
374
- const jsonForDisk = ctx.filterMetadata
375
- ? prepareJsonForDisk(meta, resultToSave.etag)
376
- : prepareJsonForDisk(resultToSave.meta, resultToSave.etag, resultToSave.jsonText);
377
- runLimited(pkgMirror, (limit) => limit(async () => {
378
- try {
379
- await saveMeta(pkgMirror, jsonForDisk);
380
- }
381
- catch (err) { // eslint-disable-line
382
- // We don't care if this file was not written to the cache
383
- }
384
- }));
385
- }
386
- meta.etag = resultToSave.etag;
387
- // only save meta to cache, when it is fresh
388
- ctx.metaCache.set(cacheKey, meta);
389
- return {
390
- meta,
391
- pickedPackage: pickMatchingVersionFinal(pickerOpts, spec, meta),
392
- };
393
- }
394
- catch (err) { // eslint-disable-line
395
- err.spec = spec;
396
- const meta = await loadMeta(pkgMirror); // TODO: add test for this usecase
397
- if (meta == null)
398
- throw err;
399
- logger.error(err, err);
400
- logger.debug({ message: `Using cached meta from ${pkgMirror}` });
401
- return {
402
- meta,
403
- pickedPackage: pickMatchingVersionFinal(pickerOpts, spec, meta),
404
- };
405
- }
406
- });
407
- }
408
- // When `minimumReleaseAge` is active and we have abbreviated metadata (which
409
- // the npm registry serves by default and which omits per-version `time`),
410
- // the maturity check can't run on the data we have. If the package has been
411
- // modified since the maturity cutoff, re-fetch with `fullMetadata: true` so
412
- // `time` is populated and the check can proceed properly. Without this,
413
- // `pickMatchingVersionFinal` would fall back to its warn-and-skip path,
414
- // silently bypassing the minimumReleaseAge guarantee for affected packages.
415
- //
416
- // Returns the original meta when no upgrade is needed. When an upgrade
417
- // happens, returns both the upgraded meta and the underlying fetch result
418
- // so callers can persist it to disk and avoid re-fetching on next install.
419
- async function maybeUpgradeAbbreviatedMetaForReleaseAge(ctx, spec, opts, meta) {
420
- if (ctx.offline === true ||
421
- !opts.publishedBy ||
422
- meta.time != null ||
423
- opts.publishedByExclude?.(spec.name) === true) {
424
- return { meta };
425
- }
426
- const modifiedDate = meta.modified ? new Date(meta.modified) : null;
427
- const isModifiedValid = modifiedDate != null && !Number.isNaN(modifiedDate.getTime());
428
- if (isModifiedValid && modifiedDate <= opts.publishedBy) {
429
- // The package was last modified at or before the maturity cutoff. Since
430
- // `modified` is an upper bound on every version's publish time, no version
431
- // can be newer than the cutoff, so the abbreviated form is fine.
432
- // Inclusive at the boundary on purpose: matches the per-version `<=` filter
433
- // in `filterPkgMetadataByPublishDate`.
434
- return { meta };
435
- }
436
- // When `modified` is missing or malformed we fall through to the upgrade
437
- // fetch: prefer correctness (run the maturity check on real `time` data)
438
- // over saving a network call when our cached freshness signal is unusable.
439
- // Forward etag/modified so the registry can answer 304 if the upgraded
440
- // representation hasn't actually changed (rare on the npm registry where
441
- // full and abbreviated have distinct etags, but cheap to support).
442
- const fullFetchResult = await ctx.fetch(spec.name, {
443
- authHeaderValue: opts.authHeaderValue,
444
- fullMetadata: true,
445
- etag: meta.etag,
446
- modified: meta.modified,
447
- registry: opts.registry,
448
- });
449
- if (fullFetchResult.notModified) {
450
- // Upgrade fetch came back 304: keep the abbreviated meta. The downstream
451
- // `pickMatchingVersionFinal` will fall through to its warn-and-skip path.
452
- return { meta };
453
- }
454
- return { meta: fullFetchResult.meta, upgradedFrom: fullFetchResult };
455
- }
456
- // Persists upgraded full metadata to the on-disk cache mirror and returns
457
- // the meta to store in the in-memory cache. When `filterMetadata` is on, the
458
- // in-memory and on-disk forms are both stripped via `clearMeta`; otherwise
459
- // the original raw response body is written and the unstripped meta is kept.
460
- function persistUpgradedMeta(ctx, pkgMirror, upgradedFrom) {
461
- const metaForCache = ctx.filterMetadata ? clearMeta(upgradedFrom.meta) : upgradedFrom.meta;
462
- const jsonForDisk = ctx.filterMetadata
463
- ? prepareJsonForDisk(metaForCache, upgradedFrom.etag)
464
- : prepareJsonForDisk(upgradedFrom.meta, upgradedFrom.etag, upgradedFrom.jsonText);
465
- runLimited(pkgMirror, (l) => l(async () => {
466
- try {
467
- await saveMeta(pkgMirror, jsonForDisk);
468
- }
469
- catch (err) { // eslint-disable-line
470
- // We don't care if this file was not written to the cache
471
- }
472
- }));
473
- return metaForCache;
474
- }
475
- export function encodePkgName(pkgName) {
476
- if (pkgName !== pkgName.toLowerCase()) {
477
- return `${pkgName}_${createHexHash(pkgName)}`;
478
- }
479
- return pkgName;
480
- }
481
- /**
482
- * Key for the in-memory `metaCache` holding a package's registry metadata. The
483
- * registry is part of the key so that a package of the same name served by two
484
- * registries in one install can't collide on a single slot (which would resolve
485
- * the wrong tarball/integrity). `fullMetadata` and `filterMetadata` keep the
486
- * abbreviated, full, and filtered-full documents in distinct slots, mirroring
487
- * the on-disk `metaDir` split: a `filterMetadata` resolver stores a `clearMeta`-
488
- * stripped packument, so it must not share a slot with an unfiltered full one
489
- * (reachable only when a `metaCache` is shared across resolvers with different
490
- * settings). `filterMetadata` only narrows the full slot — abbreviated metadata
491
- * shares one on-disk mirror regardless, so its key carries no filtered variant.
492
- * `\x00` can't appear in a registry URL or a package name, so it's an
493
- * unambiguous separator. The verifier reads this same cache and must build the
494
- * key with this function.
495
- *
496
- * The registry is canonicalized to its origin plus a trailing-slashed path, so
497
- * the resolver (which may pass a configured named-registry URL verbatim) and
498
- * the verifier (which routes through trailing-slashed prefixes) converge on one
499
- * key for the same logical registry instead of creating duplicate slots. Origin
500
- * and path are preserved, so two registries that genuinely differ never collapse.
501
- */
502
- export function getPkgMetaCacheKey(registry, pkgName, fullMetadata, filterMetadata) {
503
- const key = `${canonicalizeRegistry(registry)}\x00${pkgName}`;
504
- if (!fullMetadata)
505
- return key;
506
- return filterMetadata ? `${key}:full:filtered` : `${key}:full`;
507
- }
508
- function canonicalizeRegistry(registry) {
509
- try {
510
- const parsed = new URL(registry);
511
- const pathname = parsed.pathname.endsWith('/') ? parsed.pathname : `${parsed.pathname}/`;
512
- return `${parsed.origin}${pathname}`;
513
- }
514
- catch {
515
- return registry;
516
- }
517
- }
518
- /**
519
- * Path of the on-disk JSONL document where pnpm mirrors a package's registry
520
- * metadata. `metaDir` selects between abbreviated and full caches.
521
- */
522
- export function getPkgMirrorPath(cacheDir, metaDir, registry, pkgName) {
523
- return path.join(cacheDir, metaDir, getRegistryName(registry), `${encodePkgName(pkgName)}.jsonl`);
524
- }
525
- /**
526
- * Formats metadata for disk storage as two-line NDJSON:
527
- * Line 1: cache headers (etag, modified) — small, fast to read
528
- * Line 2: the full registry metadata JSON — unchanged from the registry response
529
- */
530
- export function prepareJsonForDisk(meta, etag, jsonText) {
531
- const modified = meta.modified ?? meta.time?.modified;
532
- const headers = JSON.stringify({ etag, modified });
533
- const body = jsonText ?? JSON.stringify(meta);
534
- return `${headers}\n${body}`;
535
- }
536
- function isMissingTimeError(err) {
537
- return (err != null &&
538
- typeof err === 'object' &&
539
- 'code' in err &&
540
- err.code === 'ERR_PNPM_MISSING_TIME');
541
- }
542
- // Cap the size so long-lived processes (daemons, store servers) can't leak
543
- // memory via this Set as they resolve ever more distinct packages.
544
- const MAX_WARNED_MISSING_TIME = 1024;
545
- const warnedMissingTimeFor = new Set();
546
- export function warnMissingTimeFieldOnce(pkgName) {
547
- if (warnedMissingTimeFor.has(pkgName))
548
- return;
549
- if (warnedMissingTimeFor.size >= MAX_WARNED_MISSING_TIME) {
550
- // Set preserves insertion order, so the first entry is the oldest.
551
- const oldest = warnedMissingTimeFor.values().next().value;
552
- if (oldest != null)
553
- warnedMissingTimeFor.delete(oldest);
554
- }
555
- warnedMissingTimeFor.add(pkgName);
556
- globalWarn(`The metadata of ${pkgName} is missing the "time" field; skipping the minimumReleaseAge check for this package.`);
557
- }
558
- async function getFileMtime(filePath) {
559
- try {
560
- const stat = await fs.stat(filePath);
561
- return stat.mtime;
562
- }
563
- catch {
564
- return null;
565
- }
566
- }
567
- /**
568
- * Reads only the first line of the cached NDJSON metadata file to extract
569
- * the cache headers (etag, modified). This avoids reading and
570
- * parsing the full metadata (which can be megabytes for popular packages)
571
- * when we only need conditional-request headers.
572
- */
573
- export async function loadMetaHeaders(pkgMirror) {
574
- let fh;
575
- try {
576
- fh = await fs.open(pkgMirror, 'r');
577
- // The first line (headers JSON) is typically ~100 bytes; 1 KB is plenty.
578
- const buf = Buffer.alloc(1024);
579
- const { bytesRead } = await fh.read(buf, 0, 1024, 0);
580
- if (bytesRead === 0)
581
- return null;
582
- const chunk = buf.toString('utf8', 0, bytesRead);
583
- const newlineIdx = chunk.indexOf('\n');
584
- if (newlineIdx === -1)
585
- return null;
586
- return JSON.parse(chunk.slice(0, newlineIdx));
587
- }
588
- catch {
589
- return null;
590
- }
591
- finally {
592
- await fh?.close();
593
- }
594
- }
595
- /**
596
- * Reads the full metadata from the cached NDJSON file.
597
- * Line 1: cache headers (etag, modified)
598
- * Line 2: registry metadata JSON
599
- */
600
- export async function loadMeta(pkgMirror) {
601
- try {
602
- const data = await gfs.readFile(pkgMirror, 'utf8');
603
- const newlineIdx = data.indexOf('\n');
604
- if (newlineIdx === -1)
605
- return null;
606
- const headers = JSON.parse(data.slice(0, newlineIdx));
607
- const meta = JSON.parse(data.slice(newlineIdx + 1));
608
- meta.etag = headers.etag;
609
- return meta;
610
- }
611
- catch {
612
- return null;
613
- }
614
- }
615
- const createdDirs = new Set();
616
- export async function saveMeta(pkgMirror, json) {
617
- const dir = path.dirname(pkgMirror);
618
- if (!createdDirs.has(dir)) {
619
- await fs.mkdir(dir, { recursive: true });
620
- createdDirs.add(dir);
621
- }
622
- const temp = pathTemp(pkgMirror);
623
- await gfs.writeFile(temp, json, 'utf8');
624
- await renameOverwrite(temp, pkgMirror);
625
- }
626
- function validatePackageName(pkgName) {
627
- if (pkgName.includes('/') && pkgName[0] !== '@') {
628
- throw new PnpmError('INVALID_PACKAGE_NAME', `Package name ${pkgName} is invalid, it should have a @scope`);
629
- }
630
- }
631
- //# sourceMappingURL=pickPackage.js.map
@@ -1,20 +0,0 @@
1
- import type { PackageInRegistry, PackageMeta, PackageMetaWithTime } from '@pnpm/resolving.registry.types';
2
- import type { VersionSelectors } from '@pnpm/resolving.resolver-base';
3
- import type { PackageVersionPolicy } from '@pnpm/types';
4
- import type { RegistryPackageSpec } from './parseBareSpecifier.js';
5
- export interface PickVersionByVersionRangeOptions {
6
- meta: PackageMeta;
7
- versionRange: string;
8
- preferredVersionSelectors?: VersionSelectors;
9
- publishedBy?: Date;
10
- }
11
- export type PickVersionByVersionRange = (options: PickVersionByVersionRangeOptions) => string | null;
12
- export interface PickPackageFromMetaOptions {
13
- preferredVersionSelectors: VersionSelectors | undefined;
14
- publishedBy?: Date;
15
- publishedByExclude?: PackageVersionPolicy;
16
- }
17
- export declare function pickPackageFromMeta(pickVersionByVersionRangeFn: PickVersionByVersionRange, { preferredVersionSelectors, publishedBy, publishedByExclude, }: PickPackageFromMetaOptions, meta: PackageMeta, spec: RegistryPackageSpec): PackageInRegistry | null;
18
- export declare function assertMetaHasTime(meta: PackageMeta): asserts meta is PackageMetaWithTime;
19
- export declare function pickLowestVersionByVersionRange({ meta, versionRange, preferredVersionSelectors }: PickVersionByVersionRangeOptions): string | null;
20
- export declare function pickVersionByVersionRange({ meta, versionRange, preferredVersionSelectors }: PickVersionByVersionRangeOptions): string | null;