@pnpm/resolving.npm-resolver 1103.2.1 → 1104.1.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/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import path from 'node:path';
2
2
  import { pickRegistryForPackage } from '@pnpm/config.pick-registry-for-package';
3
3
  import { isWellFormedRegistryName, RESERVED_VERSION_PREFIXES } from '@pnpm/deps.path';
4
- import { PnpmError } from '@pnpm/error';
4
+ import { PnpmError, redactUrlForDisplay } from '@pnpm/error';
5
5
  import { globalWarn } from '@pnpm/logger';
6
6
  import { calcVersionRange, inferRangeSpecStyle, rangeSpecGranularity, versionWithRangeSpecStyle } from '@pnpm/pkg-manifest.utils';
7
7
  import { EXISTING_VERSION_SELECTOR_WEIGHT, } from '@pnpm/resolving.resolver-base';
8
+ import { isIntegrityAddressedRegistryTarballUrl, isValidTarballRevision, } from '@pnpm/resolving.tarball-url';
8
9
  import { storeIndexKey } from '@pnpm/store.index';
9
10
  import { readPkgFromCafs, } from '@pnpm/worker';
10
11
  import { resolveWorkspaceRange } from '@pnpm/workspace.range-resolver';
@@ -18,7 +19,7 @@ import { clearMeta, retainsFullMeta } from './clearMeta.js';
18
19
  import { fetchMetadataFromFromRegistry, RegistryResponseError } from './fetch.js';
19
20
  import { memoizeFetchMetadata } from './memoizeFetchMetadata.js';
20
21
  import { normalizeRegistryUrl } from './normalizeRegistryUrl.js';
21
- import { BUILTIN_NAMED_REGISTRIES, parseBareSpecifier, parseJsrSpecifierToRegistryPackageSpec, parseNamedRegistrySpecifierToRegistryPackageSpec, } from './parseBareSpecifier.js';
22
+ import { BUILTIN_REGISTRIES_BY_PREFIX, parseBareSpecifier, parseJsrSpecifierToRegistryPackageSpec, parseNamedRegistrySpecifierToRegistryPackageSpec, } from './parseBareSpecifier.js';
22
23
  import { pickPackage, } from './pickPackage.js';
23
24
  import { applyPublishedByPolicy, pickPackageFromMeta, pickVersionByVersionRange } from './pickPackageFromMeta.js';
24
25
  import { failIfTrustDowngraded } from './trustChecks.js';
@@ -63,7 +64,7 @@ export function formatTimeAgo(date) {
63
64
  return `${diffMin} minute${diffMin === 1 ? '' : 's'} ago`;
64
65
  return 'a few seconds ago';
65
66
  }
66
- export { BUILTIN_NAMED_REGISTRIES, fetchMetadataFromFromRegistry, parseBareSpecifier, pickPackageFromMeta, pickVersionByVersionRange, RegistryResponseError, workspacePrefToNpm, };
67
+ export { BUILTIN_REGISTRIES_BY_PREFIX, fetchMetadataFromFromRegistry, parseBareSpecifier, pickPackageFromMeta, pickVersionByVersionRange, RegistryResponseError, workspacePrefToNpm, };
67
68
  export { createNpmResolutionVerifier } from './createNpmResolutionVerifier.js';
68
69
  export { MINIMUM_RELEASE_AGE_VIOLATION_CODE, TRUST_DOWNGRADE_VIOLATION_CODE, } from './violationCodes.js';
69
70
  export function createNpmResolver(fetchFromRegistry, getAuthHeader, opts) {
@@ -88,6 +89,10 @@ export function createNpmResolver(fetchFromRegistry, getAuthHeader, opts) {
88
89
  // using.
89
90
  const ownsMetaCache = opts.metaCache == null;
90
91
  const metaCache = opts.metaCache ?? createDefaultPackageMetaCache();
92
+ // This marker is intentionally resolver-scoped rather than attached to
93
+ // `metaCache`: callers may reuse one metadata cache across installs, and a
94
+ // later install must retry a full-metadata upgrade that previously got 304.
95
+ const releaseAgeUpgradeCheckedPackuments = new WeakSet();
91
96
  // Create peek function if storeDir is provided
92
97
  const storeDir = opts.storeDir;
93
98
  const peekLockerForPeek = new Map();
@@ -114,31 +119,34 @@ export function createNpmResolver(fetchFromRegistry, getAuthHeader, opts) {
114
119
  return request;
115
120
  };
116
121
  }
117
- const namedRegistries = mergeNamedRegistries(opts.namedRegistries);
118
- const namedRegistryNames = new Set(Object.keys(namedRegistries));
122
+ const registriesByPrefix = mergeNamedRegistries(opts.registriesByPrefix);
123
+ const namedRegistryNames = new Set(Object.keys(registriesByPrefix));
119
124
  const ctx = {
120
125
  getAuthHeaderValueByURI: getAuthHeader,
121
126
  pickPackage: pickPackage.bind(null, {
122
127
  fetch,
123
128
  fullMetadata: opts.fullMetadata,
129
+ needsFullMetadataFor: opts.needsFullMetadataFor,
124
130
  filterMetadata: opts.filterMetadata,
125
131
  metaCache,
126
132
  offline: opts.offline,
127
133
  preferOffline: opts.preferOffline,
128
134
  cacheDir: opts.cacheDir,
129
135
  ignoreMissingTimeField: opts.ignoreMissingTimeField,
136
+ releaseAgeUpgradeCheckedPackuments,
130
137
  }),
131
- registries: opts.registries,
132
- namedRegistries,
138
+ registriesByScope: opts.registriesByScope,
139
+ registriesByPrefix,
133
140
  namedRegistryNames,
134
141
  saveWorkspaceProtocol: opts.saveWorkspaceProtocol,
142
+ ignoreMissingTimeField: opts.ignoreMissingTimeField,
135
143
  peekManifestFromStore,
136
144
  warnedHeldBackUpdates: new Set(),
137
145
  };
138
146
  const boundResolveFromNpm = resolveNpm.bind(null, ctx);
139
147
  const boundResolveFromJsr = resolveJsr.bind(null, ctx);
140
148
  const boundResolveFromNamedRegistry = resolveFromNamedRegistry.bind(null, ctx);
141
- const defaultRegistry = opts.registries.default;
149
+ const defaultRegistry = opts.registriesByScope.default;
142
150
  return {
143
151
  resolveFromNpm: boundResolveFromNpm,
144
152
  resolveFromJsr: boundResolveFromJsr,
@@ -304,8 +312,8 @@ function createResolveLatest(resolve, matches) {
304
312
  async function resolveNpm(ctx, wantedDependency, opts) {
305
313
  const defaultTag = opts.defaultTag ?? 'latest';
306
314
  const registry = wantedDependency.alias
307
- ? pickRegistryForPackage(ctx.registries, wantedDependency.alias, wantedDependency.bareSpecifier)
308
- : ctx.registries.default;
315
+ ? pickRegistryForPackage(ctx.registriesByScope, wantedDependency.alias, wantedDependency.bareSpecifier)
316
+ : ctx.registriesByScope.default;
309
317
  if (wantedDependency.bareSpecifier?.startsWith('workspace:')) {
310
318
  if (wantedDependency.bareSpecifier.startsWith('workspace:.'))
311
319
  return null;
@@ -325,12 +333,15 @@ async function resolveNpm(ctx, wantedDependency, opts) {
325
333
  return resolvedFromWorkspace;
326
334
  }
327
335
  }
328
- const workspacePackages = opts.alwaysTryWorkspacePackages !== false ? opts.workspacePackages : undefined;
336
+ const canKeepWorkspaceResolution = opts.currentPkg == null || opts.currentPkg.resolution.type === 'directory';
329
337
  const spec = wantedDependency.bareSpecifier
330
338
  ? parseBareSpecifier(wantedDependency.bareSpecifier, wantedDependency.alias, defaultTag, registry)
331
339
  : defaultTagForAlias(wantedDependency.alias, defaultTag);
332
340
  if (spec == null)
333
341
  return null;
342
+ const workspacePackages = spec.revision == null && (!opts.updatePatches || canKeepWorkspaceResolution) && opts.alwaysTryWorkspacePackages !== false
343
+ ? opts.workspacePackages
344
+ : undefined;
334
345
  // Fast path: if we have a current resolution with integrity, try to peek the manifest from the store.
335
346
  // This avoids the expensive metadata fetch from the registry.
336
347
  // We do this AFTER ensuring the spec is valid for this resolver to avoids hijacking other resolvers.
@@ -340,10 +351,12 @@ async function resolveNpm(ctx, wantedDependency, opts) {
340
351
  if (ctx.peekManifestFromStore &&
341
352
  opts.currentPkg?.resolution &&
342
353
  !opts.update &&
354
+ !opts.updatePatches &&
355
+ spec.revision == null &&
343
356
  (opts.publishedBy == null || opts.currentPkg.publishedAt != null)) {
344
357
  const currentResolution = opts.currentPkg.resolution;
345
358
  // Only use this optimization for tarball resolutions with integrity (npm packages)
346
- if ('tarball' in currentResolution && currentResolution.integrity) {
359
+ if ('tarball' in currentResolution && typeof currentResolution.integrity === 'string') {
347
360
  const manifest = await ctx.peekManifestFromStore({
348
361
  id: opts.currentPkg.id,
349
362
  integrity: currentResolution.integrity,
@@ -379,6 +392,34 @@ async function resolveNpm(ctx, wantedDependency, opts) {
379
392
  }
380
393
  }
381
394
  }
395
+ // This runs *after* the store peek because a tag-specified dep whose only local copy is a
396
+ // prerelease reaches here with `update: false` (`wantedDepIsLocallyAvailable` ignores
397
+ // prereleases for tags, `pickMatchingLocalVersionOrNull` does not), and the peek must keep
398
+ // winning there. `update` is deliberately absent from the guard: that same helper forces it
399
+ // on for exactly these deps, so excluding it would make this block unreachable.
400
+ if (opts.preferWorkspacePackages === true &&
401
+ workspacePackages != null &&
402
+ opts.projectDir &&
403
+ opts.trustPolicy !== 'no-downgrade' &&
404
+ !opts.updateChecksums &&
405
+ opts.injectWorkspacePackages !== true &&
406
+ !wantedDependency.injected) {
407
+ const workspacePkgsMatchingName = spec.revision == null ? workspacePackages.get(spec.name) : undefined;
408
+ if (workspacePkgsMatchingName?.size === 1) {
409
+ const localVersion = pickMatchingLocalVersionOrNull(workspacePkgsMatchingName, spec);
410
+ if (localVersion != null) {
411
+ return resolveFromLocalPackage(workspacePkgsMatchingName.get(localVersion), spec, {
412
+ wantedDependency,
413
+ projectDir: opts.projectDir,
414
+ lockfileDir: opts.lockfileDir,
415
+ hardLinkLocalPackages: false,
416
+ saveWorkspaceProtocol: ctx.saveWorkspaceProtocol,
417
+ calcSpecifier: opts.calcSpecifier,
418
+ rangeSpecStyle: opts.rangeSpecStyle,
419
+ });
420
+ }
421
+ }
422
+ }
382
423
  const authHeaderValue = ctx.getAuthHeaderValueByURI(registry, { pkgName: spec.name });
383
424
  let pickResult;
384
425
  try {
@@ -391,8 +432,9 @@ async function resolveNpm(ctx, wantedDependency, opts) {
391
432
  preferredVersionSelectors: preferredVersionSelectorsFor(opts, spec.name),
392
433
  registry,
393
434
  includeLatestTag: opts.update === 'latest',
394
- updateChecksums: opts.updateChecksums,
435
+ updateChecksums: opts.updateChecksums || opts.updatePatches,
395
436
  optional: wantedDependency.optional,
437
+ trustPolicy: opts.trustPolicy,
396
438
  });
397
439
  }
398
440
  catch (err) { // eslint-disable-line
@@ -448,10 +490,14 @@ async function resolveNpm(ctx, wantedDependency, opts) {
448
490
  throw new NoMatchingVersionError({ wantedDependency, packageMeta: meta, registry });
449
491
  }
450
492
  else if (opts.trustPolicy === 'no-downgrade') {
451
- failIfTrustDowngraded(meta, pickedPackage.version, opts);
493
+ failIfTrustDowngraded(meta, pickedPackage.version, {
494
+ trustPolicyExclude: opts.trustPolicyExclude,
495
+ trustPolicyIgnoreAfter: opts.trustPolicyIgnoreAfter,
496
+ ignoreMissingTimeField: ctx.ignoreMissingTimeField,
497
+ });
452
498
  }
453
499
  const latest = latestAllowedByPolicy(meta, opts);
454
- const workspacePkgsMatchingName = workspacePackages?.get(pickedPackage.name);
500
+ const workspacePkgsMatchingName = spec.revision == null ? workspacePackages?.get(pickedPackage.name) : undefined;
455
501
  if (workspacePkgsMatchingName && opts.projectDir) {
456
502
  const matchedPkg = workspacePkgsMatchingName.get(pickedPackage.version);
457
503
  if (matchedPkg) {
@@ -485,11 +531,9 @@ async function resolveNpm(ctx, wantedDependency, opts) {
485
531
  }
486
532
  }
487
533
  warnOnceOnHeldBackUpdate(ctx, opts, spec, meta, pickedPackage.version);
534
+ const selectedPackage = selectPackageRevision(pickedPackage, spec, registry);
488
535
  const id = `${pickedPackage.name}@${pickedPackage.version}`;
489
- const resolution = {
490
- integrity: getIntegrity(pickedPackage.dist),
491
- tarball: normalizeRegistryUrl(pickedPackage.dist.tarball),
492
- };
536
+ const resolution = createRegistryTarballResolution(selectedPackage.dist, registry);
493
537
  let normalizedBareSpecifier;
494
538
  if (opts.calcSpecifier) {
495
539
  normalizedBareSpecifier = spec.normalizedBareSpecifier ?? calcSpecifier({
@@ -503,7 +547,7 @@ async function resolveNpm(ctx, wantedDependency, opts) {
503
547
  return {
504
548
  id,
505
549
  latest,
506
- manifest: pickedPackage,
550
+ manifest: selectedPackage,
507
551
  resolution,
508
552
  resolvedVia: 'npm-registry',
509
553
  publishedAt,
@@ -524,11 +568,18 @@ async function resolveJsr(ctx, wantedDependency, opts) {
524
568
  const spec = parseJsrSpecifierToRegistryPackageSpec(wantedDependency.bareSpecifier, wantedDependency.alias, opts.defaultTag ?? 'latest');
525
569
  if (spec == null)
526
570
  return null;
527
- const picked = await pickFromSimpleRegistry(ctx, wantedDependency, opts, spec, ctx.registries['@jsr']); // '@jsr' is always defined
571
+ const picked = await pickFromSimpleRegistry(ctx, wantedDependency, opts, spec, ctx.registriesByScope['@jsr']); // '@jsr' is always defined
528
572
  return {
529
573
  ...picked,
530
574
  normalizedBareSpecifier: opts.calcSpecifier
531
- ? calcPrefixedSpecifier('jsr:', spec.jsrPkgName, wantedDependency, picked.manifest.version, opts.rangeSpecStyle)
575
+ ? calcPrefixedSpecifier({
576
+ prefix: 'jsr:',
577
+ pkgName: spec.jsrPkgName,
578
+ wantedDependency,
579
+ version: picked.manifest.version,
580
+ revision: spec.revision,
581
+ defaultRangeSpecStyle: opts.rangeSpecStyle,
582
+ })
532
583
  : undefined,
533
584
  resolvedVia: 'jsr-registry',
534
585
  alias: spec.jsrPkgName,
@@ -543,14 +594,14 @@ async function resolveJsr(ctx, wantedDependency, opts) {
543
594
  // another specifier scheme (e.g. `git`, `github`, `jsr`) is silently shadowed
544
595
  // by that scheme's dedicated resolver — no cross-resolver knowledge needed.
545
596
  function mergeNamedRegistries(userDefined) {
546
- const merged = { ...BUILTIN_NAMED_REGISTRIES };
597
+ const merged = { ...BUILTIN_REGISTRIES_BY_PREFIX };
547
598
  if (!userDefined)
548
599
  return merged;
549
600
  for (const [alias, url] of Object.entries(userDefined)) {
550
601
  if (RESERVED_VERSION_PREFIXES.has(alias) || !isWellFormedRegistryName(alias)) {
551
602
  throw new PnpmError('RESERVED_NAMED_REGISTRY_NAME', RESERVED_VERSION_PREFIXES.has(alias)
552
603
  ? `'${alias}' cannot be used as a named registry alias: it is a reserved dependency specifier prefix.`
553
- : `'${alias}' cannot be used as a named registry alias: aliases must start with a letter and contain only letters, digits, ".", "_", and "-".`, { hint: 'Rename the entry in the namedRegistries setting.' });
604
+ : `'${alias}' cannot be used as a named registry alias: aliases must start with a letter and contain only letters, digits, ".", "_", and "-".`, { hint: 'Change the prefix on the corresponding registries entry.' });
554
605
  }
555
606
  if (typeof url !== 'string' || !isValidHttpUrl(url)) {
556
607
  throw new PnpmError('INVALID_NAMED_REGISTRY_URL', `The named registry alias '${alias}' is mapped to '${String(url)}', which is not a valid http(s) URL.`, { hint: 'Provide a URL that starts with http:// or https://, e.g. https://npm.pkg.example.com/' });
@@ -571,7 +622,7 @@ function isValidHttpUrl(url) {
571
622
  // Resolves a `<alias>:` specifier from one of the configured named registries.
572
623
  // The `gh:` alias ships as a built-in default pointing at the GitHub Packages
573
624
  // npm registry; additional aliases come from pnpm-workspace.yaml's
574
- // `namedRegistries` field. Auth tokens are looked up by the resolved registry
625
+ // `registriesByPrefix` field. Auth tokens are looked up by the resolved registry
575
626
  // URL, so a `//npm.pkg.github.com/:_authToken=...` entry in `.npmrc` is
576
627
  // picked up automatically for `gh:` specifiers (and analogously for any user-
577
628
  // configured alias).
@@ -581,7 +632,7 @@ async function resolveFromNamedRegistry(ctx, wantedDependency, opts) {
581
632
  const spec = parseNamedRegistrySpecifierToRegistryPackageSpec(wantedDependency.bareSpecifier, ctx.namedRegistryNames, wantedDependency.alias, opts.defaultTag ?? 'latest');
582
633
  if (spec == null)
583
634
  return null;
584
- const registry = ctx.namedRegistries[spec.registryName];
635
+ const registry = ctx.registriesByPrefix[spec.registryName];
585
636
  if (!registry)
586
637
  return null; // defensive: should never trigger because parse checks the alias set
587
638
  const picked = await pickFromSimpleRegistry(ctx, wantedDependency, opts, spec, registry);
@@ -593,7 +644,14 @@ async function resolveFromNamedRegistry(ctx, wantedDependency, opts) {
593
644
  // decides the tarball both consumers get.
594
645
  id: `${picked.manifest.name}@${spec.registryName}:${picked.manifest.version}`,
595
646
  normalizedBareSpecifier: opts.calcSpecifier
596
- ? calcPrefixedSpecifier(`${spec.registryName}:`, spec.name, wantedDependency, picked.manifest.version, opts.rangeSpecStyle)
647
+ ? calcPrefixedSpecifier({
648
+ prefix: `${spec.registryName}:`,
649
+ pkgName: spec.name,
650
+ wantedDependency,
651
+ version: picked.manifest.version,
652
+ revision: spec.revision,
653
+ defaultRangeSpecStyle: opts.rangeSpecStyle,
654
+ })
597
655
  : undefined,
598
656
  resolvedVia: 'named-registry',
599
657
  registryName: spec.registryName,
@@ -617,22 +675,21 @@ async function pickFromSimpleRegistry(ctx, wantedDependency, opts, spec, registr
617
675
  preferredVersionSelectors: preferredVersionSelectorsFor(opts, spec.name),
618
676
  registry,
619
677
  includeLatestTag: opts.update === 'latest',
620
- updateChecksums: opts.updateChecksums,
678
+ updateChecksums: opts.updateChecksums || opts.updatePatches,
621
679
  optional: wantedDependency.optional,
680
+ trustPolicy: opts.trustPolicy,
622
681
  });
623
682
  if (pickedPackage == null) {
624
683
  throw new NoMatchingVersionError({ wantedDependency, packageMeta: meta, registry });
625
684
  }
626
685
  warnOnceOnHeldBackUpdate(ctx, opts, spec, meta, pickedPackage.version);
627
- const resolution = {
628
- integrity: getIntegrity(pickedPackage.dist),
629
- tarball: normalizeRegistryUrl(pickedPackage.dist.tarball),
630
- };
686
+ const selectedPackage = selectPackageRevision(pickedPackage, spec, registry);
687
+ const resolution = createRegistryTarballResolution(selectedPackage.dist, registry);
631
688
  const publishedAt = meta.time?.[pickedPackage.version];
632
689
  return {
633
690
  id: `${pickedPackage.name}@${pickedPackage.version}`,
634
691
  latest: latestAllowedByPolicy(meta, opts),
635
- manifest: pickedPackage,
692
+ manifest: selectedPackage,
636
693
  resolution,
637
694
  publishedAt,
638
695
  policyViolation: detectMinReleaseAgeViolation({
@@ -649,13 +706,25 @@ async function pickFromSimpleRegistry(ctx, wantedDependency, opts, spec, registr
649
706
  // when the dependency alias matches the package name). Shared between the
650
707
  // jsr and named-registry resolvers since they only differ in `prefix` and
651
708
  // which spec field holds the package name.
652
- function calcPrefixedSpecifier(prefix, pkgName, wantedDependency, version, defaultRangeSpecStyle) {
653
- const range = calcRange(version, wantedDependency, defaultRangeSpecStyle);
654
- if (!wantedDependency.alias || pkgName === wantedDependency.alias)
655
- return `${prefix}${range}`;
656
- return `${prefix}${pkgName}@${range}`;
709
+ function calcPrefixedSpecifier(opts) {
710
+ if (opts.revision != null) {
711
+ const target = `${opts.version}+r${opts.revision}`;
712
+ if (!opts.wantedDependency.alias || opts.pkgName === opts.wantedDependency.alias)
713
+ return `${opts.prefix}${target}`;
714
+ return `${opts.prefix}${opts.pkgName}@${target}`;
715
+ }
716
+ const range = calcRange(opts.version, opts.wantedDependency, opts.defaultRangeSpecStyle);
717
+ if (!opts.wantedDependency.alias || opts.pkgName === opts.wantedDependency.alias)
718
+ return `${opts.prefix}${range}`;
719
+ return `${opts.prefix}${opts.pkgName}@${range}`;
657
720
  }
658
721
  function calcSpecifier({ wantedDependency, spec, version, defaultRangeSpecStyle, }) {
722
+ if (spec.revision != null) {
723
+ const target = `${version}+r${spec.revision}`;
724
+ if (!wantedDependency.alias || spec.name === wantedDependency.alias)
725
+ return target;
726
+ return `npm:${spec.name}@${target}`;
727
+ }
659
728
  if (wantedDependency.prevSpecifier === wantedDependency.bareSpecifier && wantedDependency.prevSpecifier && versionSelectorType(wantedDependency.prevSpecifier)?.type === 'tag') {
660
729
  return wantedDependency.prevSpecifier;
661
730
  }
@@ -875,6 +944,123 @@ function getIntegrity(dist) {
875
944
  }
876
945
  return integrity.toString();
877
946
  }
947
+ function createRegistryTarballResolution(dist, registry) {
948
+ const integrity = getIntegrity(dist);
949
+ const tarball = normalizeRegistryUrl(dist.tarball);
950
+ if (dist.revision == null) {
951
+ return { integrity, tarball };
952
+ }
953
+ if (!isValidTarballRevision(dist.revision)) {
954
+ throw new PnpmError('MALFORMED_METADATA', `Tarball "${redactUrlForDisplay(dist.tarball)}" has an invalid revision in its metadata: ${String(dist.revision)}`);
955
+ }
956
+ if (integrity == null ||
957
+ !isIntegrityAddressedRegistryTarballUrl(tarball, integrity, registry)) {
958
+ throw new PnpmError('MALFORMED_METADATA', `Tarball "${redactUrlForDisplay(dist.tarball)}" has revision ${dist.revision} but is not addressed by its complete integrity.`);
959
+ }
960
+ return {
961
+ integrity,
962
+ revision: dist.revision,
963
+ tarball,
964
+ };
965
+ }
966
+ const REVISION_MANIFEST_FIELDS = [
967
+ 'bin',
968
+ 'bundleDependencies',
969
+ 'bundledDependencies',
970
+ 'cpu',
971
+ 'dependencies',
972
+ 'engines',
973
+ 'hasInstallScript',
974
+ 'libc',
975
+ 'optionalDependencies',
976
+ 'os',
977
+ 'peerDependencies',
978
+ 'peerDependenciesMeta',
979
+ ];
980
+ function selectPackageRevision(pickedPackage, spec, registry) {
981
+ validateCurrentPackageRevision(pickedPackage, registry);
982
+ if (spec.revision == null)
983
+ return pickedPackage;
984
+ const revisions = pickedPackage.dist.revisions;
985
+ if (revisions == null) {
986
+ if (spec.revision === 0 && pickedPackage.dist.revision == null)
987
+ return pickedPackage;
988
+ throw new PnpmError('NO_MATCHING_REVISION', `No revision ${spec.revision} is advertised for ${pickedPackage.name}@${pickedPackage.version}`);
989
+ }
990
+ if (!Array.isArray(revisions)) {
991
+ throw malformedRevisionHistory(pickedPackage, 'the revisions field is not an array');
992
+ }
993
+ const matches = revisions.filter((entry) => isRevisionNumber(entry?.revision) && entry.revision === spec.revision);
994
+ if (matches.length === 0) {
995
+ throw new PnpmError('NO_MATCHING_REVISION', `No revision ${spec.revision} is advertised for ${pickedPackage.name}@${pickedPackage.version}`);
996
+ }
997
+ if (matches.length !== 1) {
998
+ throw malformedRevisionHistory(pickedPackage, `revision ${spec.revision} is advertised more than once`);
999
+ }
1000
+ const selectedRevision = matches[0];
1001
+ validatePackageRevision(pickedPackage, selectedRevision, registry);
1002
+ const selectedPackage = { ...pickedPackage };
1003
+ for (const field of REVISION_MANIFEST_FIELDS) {
1004
+ delete selectedPackage[field];
1005
+ const value = selectedRevision.manifest[field];
1006
+ if (value !== undefined) {
1007
+ selectedPackage[field] = value;
1008
+ }
1009
+ }
1010
+ selectedPackage.dist = {
1011
+ ...pickedPackage.dist,
1012
+ integrity: selectedRevision.integrity,
1013
+ tarball: selectedRevision.tarball,
1014
+ };
1015
+ if (spec.revision === 0) {
1016
+ delete selectedPackage.dist.revision;
1017
+ }
1018
+ else {
1019
+ selectedPackage.dist.revision = spec.revision;
1020
+ }
1021
+ return selectedPackage;
1022
+ }
1023
+ function validateCurrentPackageRevision(pickedPackage, registry) {
1024
+ const revision = pickedPackage.dist.revision;
1025
+ if (revision == null)
1026
+ return;
1027
+ if (!isValidTarballRevision(revision)) {
1028
+ throw malformedRevisionHistory(pickedPackage, `current revision ${String(revision)} is not a canonical positive safe integer`);
1029
+ }
1030
+ const revisions = pickedPackage.dist.revisions;
1031
+ if (!Array.isArray(revisions)) {
1032
+ throw malformedRevisionHistory(pickedPackage, 'the current revision has no revision history');
1033
+ }
1034
+ const matches = revisions.filter(entry => entry?.revision === revision);
1035
+ if (matches.length !== 1) {
1036
+ throw malformedRevisionHistory(pickedPackage, `current revision ${revision} does not have exactly one history entry`);
1037
+ }
1038
+ const current = matches[0];
1039
+ validatePackageRevision(pickedPackage, current, registry);
1040
+ if (pickedPackage.dist.integrity !== current.integrity ||
1041
+ normalizeRegistryUrl(pickedPackage.dist.tarball) !== normalizeRegistryUrl(current.tarball)) {
1042
+ throw malformedRevisionHistory(pickedPackage, `revision ${revision} does not match the current artifact`);
1043
+ }
1044
+ }
1045
+ function validatePackageRevision(pickedPackage, revision, registry) {
1046
+ if (!isRevisionNumber(revision.revision)) {
1047
+ throw malformedRevisionHistory(pickedPackage, `revision ${String(revision.revision)} is not a canonical safe integer`);
1048
+ }
1049
+ if (typeof revision.integrity !== 'string' ||
1050
+ typeof revision.tarball !== 'string' ||
1051
+ !isIntegrityAddressedRegistryTarballUrl(normalizeRegistryUrl(revision.tarball), revision.integrity, registry)) {
1052
+ throw malformedRevisionHistory(pickedPackage, `revision ${revision.revision} is not addressed by its complete sha512 integrity`);
1053
+ }
1054
+ if (revision.manifest == null || typeof revision.manifest !== 'object' || Array.isArray(revision.manifest)) {
1055
+ throw malformedRevisionHistory(pickedPackage, `revision ${revision.revision} has an invalid manifest`);
1056
+ }
1057
+ }
1058
+ function isRevisionNumber(revision) {
1059
+ return revision === 0 || isValidTarballRevision(revision);
1060
+ }
1061
+ function malformedRevisionHistory(pickedPackage, reason) {
1062
+ return new PnpmError('MALFORMED_METADATA', `The revision history for ${pickedPackage.name}@${pickedPackage.version} is invalid: ${reason}.`);
1063
+ }
878
1064
  /**
879
1065
  * Construct the LRU `PackageMetaCache` instance the resolver uses by
880
1066
  * default. Exported so the install layer can build one cache and hand
@@ -2,6 +2,7 @@ export interface RegistryPackageSpec {
2
2
  type: 'tag' | 'version' | 'range';
3
3
  name: string;
4
4
  fetchSpec: string;
5
+ revision?: number;
5
6
  normalizedBareSpecifier?: string;
6
7
  }
7
8
  export declare function parseBareSpecifier(bareSpecifier: string, alias: string | undefined, defaultTag: string, registry: string): RegistryPackageSpec | null;
@@ -9,7 +10,7 @@ export interface JsrRegistryPackageSpec extends RegistryPackageSpec {
9
10
  jsrPkgName: string;
10
11
  }
11
12
  export declare function parseJsrSpecifierToRegistryPackageSpec(rawSpecifier: string, alias: string | undefined, defaultTag: string): JsrRegistryPackageSpec | null;
12
- export { BUILTIN_NAMED_REGISTRIES } from '@pnpm/constants';
13
+ export { BUILTIN_REGISTRIES_BY_PREFIX } from '@pnpm/constants';
13
14
  export interface NamedRegistryPackageSpec extends RegistryPackageSpec {
14
15
  registryName: string;
15
16
  }
@@ -31,9 +31,8 @@ export function parseBareSpecifier(bareSpecifier, alias, defaultTag, registry) {
31
31
  const selector = getVersionSelectorType(bareSpecifier);
32
32
  if (selector != null) {
33
33
  return {
34
- fetchSpec: selector.normalized,
34
+ ...parseRevisionSelector(selector, bareSpecifier),
35
35
  name,
36
- type: selector.type,
37
36
  };
38
37
  }
39
38
  }
@@ -58,13 +57,12 @@ export function parseJsrSpecifierToRegistryPackageSpec(rawSpecifier, alias, defa
58
57
  if (selector == null)
59
58
  return null;
60
59
  return {
61
- fetchSpec: selector.normalized,
60
+ ...parseRevisionSelector(selector, spec.versionSelector ?? defaultTag),
62
61
  name: spec.npmPkgName,
63
- type: selector.type,
64
62
  jsrPkgName: spec.jsrPkgName,
65
63
  };
66
64
  }
67
- export { BUILTIN_NAMED_REGISTRIES } from '@pnpm/constants';
65
+ export { BUILTIN_REGISTRIES_BY_PREFIX } from '@pnpm/constants';
68
66
  // Parses a named-registry specifier of the shape `<alias>:<body>` into a
69
67
  // RegistryPackageSpec. Returns `null` when the specifier does not use one of
70
68
  // the configured aliases, so the caller can fall through to other resolvers.
@@ -132,10 +130,36 @@ export function parseNamedRegistrySpecifierToRegistryPackageSpec(rawSpecifier, k
132
130
  if (selector == null)
133
131
  return null;
134
132
  return {
135
- fetchSpec: selector.normalized,
133
+ ...parseRevisionSelector(selector, versionSelector ?? defaultTag),
136
134
  name: pkgName,
137
- type: selector.type,
138
135
  registryName,
139
136
  };
140
137
  }
138
+ function parseRevisionSelector(selector, rawSelector) {
139
+ if (selector.type !== 'version') {
140
+ return { fetchSpec: selector.normalized, type: selector.type };
141
+ }
142
+ const normalizedInput = rawSelector.trim();
143
+ const buildIndex = normalizedInput.indexOf('+');
144
+ if (buildIndex === -1) {
145
+ return { fetchSpec: selector.normalized, type: selector.type };
146
+ }
147
+ const build = normalizedInput.slice(buildIndex + 1);
148
+ if (build.length < 2 || build[0] !== 'r' || build.includes('.')) {
149
+ return { fetchSpec: selector.normalized, type: selector.type };
150
+ }
151
+ const digits = build.slice(1);
152
+ if (![...digits].every((char) => char >= '0' && char <= '9')) {
153
+ return { fetchSpec: selector.normalized, type: selector.type };
154
+ }
155
+ const revision = Number(digits);
156
+ if ((digits.length > 1 && digits[0] === '0') || !Number.isSafeInteger(revision)) {
157
+ throw new PnpmError('INVALID_REVISION_SPEC', `Invalid registry revision in version specifier "${rawSelector}"`);
158
+ }
159
+ return {
160
+ fetchSpec: selector.normalized,
161
+ revision,
162
+ type: selector.type,
163
+ };
164
+ }
141
165
  //# sourceMappingURL=parseBareSpecifier.js.map
@@ -1,4 +1,5 @@
1
1
  import type { PackageInRegistry, PackageMeta } from '@pnpm/resolving.registry.types';
2
+ import type { TrustPolicy } from '@pnpm/types';
2
3
  import { type FetchMetadataNotModifiedResult, type FetchMetadataResult } from './fetch.js';
3
4
  import type { RegistryPackageSpec } from './parseBareSpecifier.js';
4
5
  import { type PickPackageFromMetaOptions } from './pickPackageFromMeta.js';
@@ -22,6 +23,7 @@ export interface PickPackageOptions extends PickPackageFromMetaOptions {
22
23
  dryRun: boolean;
23
24
  includeLatestTag?: boolean;
24
25
  optional?: boolean;
26
+ trustPolicy?: TrustPolicy;
25
27
  /**
26
28
  * When true, force a conditional registry request so a stale on-disk
27
29
  * packument can't satisfy the call: the on-disk exact-version fast
@@ -44,12 +46,23 @@ export declare function pickPackage(ctx: {
44
46
  modified?: string;
45
47
  }) => Promise<FetchMetadataResult | FetchMetadataNotModifiedResult>;
46
48
  fullMetadata?: boolean;
49
+ /**
50
+ * Whether a time-based resolution has to read the full metadata of
51
+ * `registry`, because that registry's abbreviated form carries no `time`.
52
+ * Asked per registry: the answer differs between the public registry and a
53
+ * proxy that does carry it, and the mirror path and cache key below are
54
+ * already keyed by registry, so two registries may disagree within one
55
+ * install.
56
+ */
57
+ needsFullMetadataFor?: (registry: string) => boolean;
47
58
  metaCache: PackageMetaCache;
48
59
  cacheDir: string;
49
60
  offline?: boolean;
50
61
  preferOffline?: boolean;
51
62
  filterMetadata?: boolean;
52
63
  ignoreMissingTimeField?: boolean;
64
+ /** Packuments whose release-age upgrade fetch already answered 304 in this resolver. */
65
+ releaseAgeUpgradeCheckedPackuments?: WeakSet<PackageMeta>;
53
66
  }, spec: RegistryPackageSpec, opts: PickPackageOptions): Promise<{
54
67
  meta: PackageMeta;
55
68
  pickedPackage: PackageInRegistry | null;
@@ -91,7 +104,13 @@ export declare function getPkgMirrorPath(cacheDir: string, metaDir: string, regi
91
104
  * there), so a `meta` that carries one is serialized without it.
92
105
  */
93
106
  export declare function prepareJsonForDisk(meta: PackageMeta, etag: string | undefined, jsonText?: string): string;
94
- export declare function warnMissingTimeFieldOnce(pkgName: string): void;
107
+ /**
108
+ * At most one warning per package per check. `minimumReleaseAge` and
109
+ * `trustPolicy` both go dark on the same missing field, so keying by package
110
+ * alone would let whichever check ran first silence the other and leave the
111
+ * user told about only one of the two skips.
112
+ */
113
+ export declare function warnMissingTimeFieldOnce(pkgName: string, skippedCheck: 'minimumReleaseAge' | 'trustPolicy'): void;
95
114
  interface MetaHeaders {
96
115
  etag?: string;
97
116
  modified?: string;