@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/CHANGELOG.md +93 -0
- package/lib/createNpmResolutionVerifier.d.ts +13 -10
- package/lib/createNpmResolutionVerifier.js +171 -45
- package/lib/fetch.js +5 -3
- package/lib/index.d.ts +21 -7
- package/lib/index.js +224 -38
- package/lib/parseBareSpecifier.d.ts +2 -1
- package/lib/parseBareSpecifier.js +31 -7
- package/lib/pickPackage.d.ts +20 -1
- package/lib/pickPackage.js +100 -13
- package/lib/pickPackageFromMeta.d.ts +7 -1
- package/lib/pickPackageFromMeta.js +82 -3
- package/lib/publishTimes.d.ts +24 -0
- package/lib/publishTimes.js +35 -0
- package/lib/trustChecks.d.ts +14 -0
- package/lib/trustChecks.js +5 -0
- package/lib/violationCodes.d.ts +1 -0
- package/lib/violationCodes.js +1 -0
- package/package.json +22 -21
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 {
|
|
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 {
|
|
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
|
|
118
|
-
const namedRegistryNames = new Set(Object.keys(
|
|
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
|
-
|
|
132
|
-
|
|
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.
|
|
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.
|
|
308
|
-
: ctx.
|
|
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
|
|
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,
|
|
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:
|
|
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.
|
|
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(
|
|
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 = { ...
|
|
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: '
|
|
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
|
-
// `
|
|
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.
|
|
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(
|
|
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
|
|
628
|
-
|
|
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:
|
|
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(
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
package/lib/pickPackage.d.ts
CHANGED
|
@@ -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
|
-
|
|
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;
|