@pnpm/resolving.npm-resolver 1004.4.1
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/LICENSE +22 -0
- package/README.md +65 -0
- package/lib/fetch.d.ts +23 -0
- package/lib/fetch.js +92 -0
- package/lib/index.d.ts +99 -0
- package/lib/index.js +547 -0
- package/lib/normalizeRegistryUrl.d.ts +4 -0
- package/lib/normalizeRegistryUrl.js +12 -0
- package/lib/parseBareSpecifier.d.ts +11 -0
- package/lib/parseBareSpecifier.js +55 -0
- package/lib/pickPackage.d.ts +34 -0
- package/lib/pickPackage.js +278 -0
- package/lib/pickPackageFromMeta.d.ts +20 -0
- package/lib/pickPackageFromMeta.js +202 -0
- package/lib/toRaw.d.ts +2 -0
- package/lib/toRaw.js +4 -0
- package/lib/trustChecks.d.ts +9 -0
- package/lib/trustChecks.js +89 -0
- package/lib/whichVersionIsPinned.d.ts +2 -0
- package/lib/whichVersionIsPinned.js +30 -0
- package/lib/workspacePrefToNpm.d.ts +1 -0
- package/lib/workspacePrefToNpm.js +13 -0
- package/package.json +90 -0
|
@@ -0,0 +1,278 @@
|
|
|
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 { 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 { pick } from 'ramda';
|
|
12
|
+
import { renameOverwrite } from 'rename-overwrite';
|
|
13
|
+
import semver from 'semver';
|
|
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
|
+
const pickPackageFromMetaUsingTimeStrict = pickPackageFromMeta.bind(null, pickVersionByVersionRange);
|
|
42
|
+
function pickPackageFromMetaUsingTime(opts, spec, meta) {
|
|
43
|
+
const pickedPackage = pickPackageFromMeta(pickVersionByVersionRange, opts, spec, meta);
|
|
44
|
+
if (pickedPackage)
|
|
45
|
+
return pickedPackage;
|
|
46
|
+
return pickPackageFromMeta(pickLowestVersionByVersionRange, {
|
|
47
|
+
preferredVersionSelectors: opts.preferredVersionSelectors,
|
|
48
|
+
}, spec, meta);
|
|
49
|
+
}
|
|
50
|
+
export async function pickPackage(ctx, spec, opts) {
|
|
51
|
+
opts = opts || {};
|
|
52
|
+
const pickPackageFromMetaBySpec = (opts.publishedBy
|
|
53
|
+
? (ctx.strictPublishedByCheck ? pickPackageFromMetaUsingTimeStrict : pickPackageFromMetaUsingTime)
|
|
54
|
+
: (pickPackageFromMeta.bind(null, opts.pickLowestVersion ? pickLowestVersionByVersionRange : pickVersionByVersionRange))).bind(null, {
|
|
55
|
+
preferredVersionSelectors: opts.preferredVersionSelectors,
|
|
56
|
+
publishedBy: opts.publishedBy,
|
|
57
|
+
publishedByExclude: opts.publishedByExclude,
|
|
58
|
+
});
|
|
59
|
+
let _pickPackageFromMeta;
|
|
60
|
+
if (opts.updateToLatest) {
|
|
61
|
+
_pickPackageFromMeta = (meta) => {
|
|
62
|
+
const latestStableSpec = { ...spec, type: 'tag', fetchSpec: 'latest' };
|
|
63
|
+
const latestStable = pickPackageFromMetaBySpec(latestStableSpec, meta);
|
|
64
|
+
const current = pickPackageFromMetaBySpec(spec, meta);
|
|
65
|
+
if (!latestStable)
|
|
66
|
+
return current;
|
|
67
|
+
if (!current)
|
|
68
|
+
return latestStable;
|
|
69
|
+
if (semver.lt(latestStable.version, current.version))
|
|
70
|
+
return current;
|
|
71
|
+
return latestStable;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
_pickPackageFromMeta = pickPackageFromMetaBySpec.bind(null, spec);
|
|
76
|
+
}
|
|
77
|
+
validatePackageName(spec.name);
|
|
78
|
+
// Use full metadata for optional dependencies to get libc field.
|
|
79
|
+
// See: https://github.com/pnpm/pnpm/issues/9950
|
|
80
|
+
const fullMetadata = opts.optional === true || ctx.fullMetadata === true;
|
|
81
|
+
const metaDir = fullMetadata
|
|
82
|
+
? (ctx.filterMetadata ? FULL_FILTERED_META_DIR : FULL_META_DIR)
|
|
83
|
+
: ABBREVIATED_META_DIR;
|
|
84
|
+
// Cache key includes fullMetadata to avoid returning abbreviated metadata when full metadata is requested.
|
|
85
|
+
const cacheKey = fullMetadata ? `${spec.name}:full` : spec.name;
|
|
86
|
+
const cachedMeta = ctx.metaCache.get(cacheKey);
|
|
87
|
+
if (cachedMeta != null) {
|
|
88
|
+
return {
|
|
89
|
+
meta: cachedMeta,
|
|
90
|
+
pickedPackage: _pickPackageFromMeta(cachedMeta),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const registryName = getRegistryName(opts.registry);
|
|
94
|
+
const pkgMirror = path.join(ctx.cacheDir, metaDir, registryName, `${encodePkgName(spec.name)}.json`);
|
|
95
|
+
return runLimited(pkgMirror, async (limit) => {
|
|
96
|
+
let metaCachedInStore;
|
|
97
|
+
if (ctx.offline === true || ctx.preferOffline === true || opts.pickLowestVersion) {
|
|
98
|
+
metaCachedInStore = await limit(async () => loadMeta(pkgMirror));
|
|
99
|
+
if (ctx.offline) {
|
|
100
|
+
if (metaCachedInStore != null)
|
|
101
|
+
return {
|
|
102
|
+
meta: metaCachedInStore,
|
|
103
|
+
pickedPackage: _pickPackageFromMeta(metaCachedInStore),
|
|
104
|
+
};
|
|
105
|
+
throw new PnpmError('NO_OFFLINE_META', `Failed to resolve ${toRaw(spec)} in package mirror ${pkgMirror}`);
|
|
106
|
+
}
|
|
107
|
+
if (metaCachedInStore != null) {
|
|
108
|
+
const pickedPackage = _pickPackageFromMeta(metaCachedInStore);
|
|
109
|
+
if (pickedPackage) {
|
|
110
|
+
return {
|
|
111
|
+
meta: metaCachedInStore,
|
|
112
|
+
pickedPackage,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (!opts.updateToLatest && spec.type === 'version') {
|
|
118
|
+
metaCachedInStore = metaCachedInStore ?? await limit(async () => loadMeta(pkgMirror));
|
|
119
|
+
// use the cached meta only if it has the required package version
|
|
120
|
+
// otherwise it is probably out of date
|
|
121
|
+
if ((metaCachedInStore?.versions?.[spec.fetchSpec]) != null) {
|
|
122
|
+
try {
|
|
123
|
+
const pickedPackage = _pickPackageFromMeta(metaCachedInStore);
|
|
124
|
+
if (pickedPackage) {
|
|
125
|
+
return {
|
|
126
|
+
meta: metaCachedInStore,
|
|
127
|
+
pickedPackage,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
if (ctx.strictPublishedByCheck) {
|
|
133
|
+
throw err;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (opts.publishedBy) {
|
|
139
|
+
metaCachedInStore = metaCachedInStore ?? await limit(async () => loadMeta(pkgMirror));
|
|
140
|
+
if (metaCachedInStore?.cachedAt && new Date(metaCachedInStore.cachedAt) >= opts.publishedBy) {
|
|
141
|
+
try {
|
|
142
|
+
const pickedPackage = _pickPackageFromMeta(metaCachedInStore);
|
|
143
|
+
if (pickedPackage) {
|
|
144
|
+
return {
|
|
145
|
+
meta: metaCachedInStore,
|
|
146
|
+
pickedPackage,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
if (ctx.strictPublishedByCheck) {
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const fetchResult = await ctx.fetch(spec.name, {
|
|
159
|
+
authHeaderValue: opts.authHeaderValue,
|
|
160
|
+
fullMetadata,
|
|
161
|
+
registry: opts.registry,
|
|
162
|
+
});
|
|
163
|
+
const cachedAt = Date.now();
|
|
164
|
+
let meta = fetchResult.meta;
|
|
165
|
+
let jsonToSave;
|
|
166
|
+
if (ctx.filterMetadata) {
|
|
167
|
+
meta = clearMeta(meta);
|
|
168
|
+
}
|
|
169
|
+
else if (typeof fetchResult.jsonText === 'string') {
|
|
170
|
+
// Reuse the raw JSON text from the registry response to avoid re-stringifying.
|
|
171
|
+
// Inject cachedAt at the start of the JSON object. To be robust against BOMs or
|
|
172
|
+
// leading whitespace/newlines, locate the first '{' and splice after it.
|
|
173
|
+
const jsonText = fetchResult.jsonText;
|
|
174
|
+
const firstBraceIndex = jsonText.indexOf('{');
|
|
175
|
+
if (firstBraceIndex !== -1) {
|
|
176
|
+
jsonToSave = `{"cachedAt":${cachedAt},${jsonText.slice(firstBraceIndex + 1)}`;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
meta.cachedAt = cachedAt;
|
|
180
|
+
// only save meta to cache, when it is fresh
|
|
181
|
+
ctx.metaCache.set(cacheKey, meta);
|
|
182
|
+
if (!opts.dryRun) {
|
|
183
|
+
const jsonForDisk = jsonToSave ?? JSON.stringify(meta);
|
|
184
|
+
runLimited(pkgMirror, (limit) => limit(async () => {
|
|
185
|
+
try {
|
|
186
|
+
await saveMeta(pkgMirror, jsonForDisk);
|
|
187
|
+
}
|
|
188
|
+
catch (err) { // eslint-disable-line
|
|
189
|
+
// We don't care if this file was not written to the cache
|
|
190
|
+
}
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
meta,
|
|
195
|
+
pickedPackage: _pickPackageFromMeta(meta),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
catch (err) { // eslint-disable-line
|
|
199
|
+
err.spec = spec;
|
|
200
|
+
const meta = await loadMeta(pkgMirror); // TODO: add test for this usecase
|
|
201
|
+
if (meta == null)
|
|
202
|
+
throw err;
|
|
203
|
+
logger.error(err, err);
|
|
204
|
+
logger.debug({ message: `Using cached meta from ${pkgMirror}` });
|
|
205
|
+
return {
|
|
206
|
+
meta,
|
|
207
|
+
pickedPackage: _pickPackageFromMeta(meta),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
function clearMeta(pkg) {
|
|
213
|
+
const versions = {};
|
|
214
|
+
for (const [version, info] of Object.entries(pkg.versions)) {
|
|
215
|
+
// The list taken from https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md#abbreviated-version-object
|
|
216
|
+
// with the addition of 'libc'
|
|
217
|
+
versions[version] = pick([
|
|
218
|
+
'name',
|
|
219
|
+
'version',
|
|
220
|
+
'bin',
|
|
221
|
+
'directories',
|
|
222
|
+
'devDependencies',
|
|
223
|
+
'optionalDependencies',
|
|
224
|
+
'dependencies',
|
|
225
|
+
'peerDependencies',
|
|
226
|
+
'dist',
|
|
227
|
+
'engines',
|
|
228
|
+
'peerDependenciesMeta',
|
|
229
|
+
'cpu',
|
|
230
|
+
'os',
|
|
231
|
+
'libc',
|
|
232
|
+
'deprecated',
|
|
233
|
+
'bundleDependencies',
|
|
234
|
+
'bundledDependencies',
|
|
235
|
+
'hasInstallScript',
|
|
236
|
+
'_npmUser',
|
|
237
|
+
], info);
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
name: pkg.name,
|
|
241
|
+
'dist-tags': pkg['dist-tags'],
|
|
242
|
+
versions,
|
|
243
|
+
time: pkg.time,
|
|
244
|
+
cachedAt: pkg.cachedAt,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function encodePkgName(pkgName) {
|
|
248
|
+
if (pkgName !== pkgName.toLowerCase()) {
|
|
249
|
+
return `${pkgName}_${createHexHash(pkgName)}`;
|
|
250
|
+
}
|
|
251
|
+
return pkgName;
|
|
252
|
+
}
|
|
253
|
+
async function loadMeta(pkgMirror) {
|
|
254
|
+
try {
|
|
255
|
+
const data = await gfs.readFile(pkgMirror, 'utf8');
|
|
256
|
+
return JSON.parse(data);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const createdDirs = new Set();
|
|
263
|
+
async function saveMeta(pkgMirror, json) {
|
|
264
|
+
const dir = path.dirname(pkgMirror);
|
|
265
|
+
if (!createdDirs.has(dir)) {
|
|
266
|
+
await fs.mkdir(dir, { recursive: true });
|
|
267
|
+
createdDirs.add(dir);
|
|
268
|
+
}
|
|
269
|
+
const temp = pathTemp(pkgMirror);
|
|
270
|
+
await gfs.writeFile(temp, json, 'utf8');
|
|
271
|
+
await renameOverwrite(temp, pkgMirror);
|
|
272
|
+
}
|
|
273
|
+
function validatePackageName(pkgName) {
|
|
274
|
+
if (pkgName.includes('/') && pkgName[0] !== '@') {
|
|
275
|
+
throw new PnpmError('INVALID_PACKAGE_NAME', `Package name ${pkgName} is invalid, it should have a @scope`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
//# sourceMappingURL=pickPackage.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
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, spec: RegistryPackageSpec, meta: PackageMeta): 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;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import util from 'node:util';
|
|
2
|
+
import { PnpmError } from '@pnpm/error';
|
|
3
|
+
import { filterPkgMetadataByPublishDate } from '@pnpm/resolving.registry.pkg-metadata-filter';
|
|
4
|
+
import semver from 'semver';
|
|
5
|
+
export function pickPackageFromMeta(pickVersionByVersionRangeFn, { preferredVersionSelectors, publishedBy, publishedByExclude, }, spec, meta) {
|
|
6
|
+
if (publishedBy) {
|
|
7
|
+
const excludeResult = publishedByExclude?.(meta.name) ?? false;
|
|
8
|
+
if (excludeResult !== true) {
|
|
9
|
+
assertMetaHasTime(meta);
|
|
10
|
+
const trustedVersions = Array.isArray(excludeResult) ? excludeResult : undefined;
|
|
11
|
+
meta = filterPkgMetadataByPublishDate(meta, publishedBy, trustedVersions);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
if ((!meta.versions || Object.keys(meta.versions).length === 0) && !publishedBy) {
|
|
15
|
+
// Unfortunately, the npm registry doesn't return the time field in the abbreviated metadata.
|
|
16
|
+
// So we won't always know if the package was unpublished.
|
|
17
|
+
if (meta.time?.unpublished?.versions?.length) {
|
|
18
|
+
throw new PnpmError('UNPUBLISHED_PKG', `No versions available for ${spec.name} because it was unpublished`);
|
|
19
|
+
}
|
|
20
|
+
throw new PnpmError('NO_VERSIONS', `No versions available for ${spec.name}. The package may be unpublished.`);
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
let version;
|
|
24
|
+
switch (spec.type) {
|
|
25
|
+
case 'version':
|
|
26
|
+
version = spec.fetchSpec;
|
|
27
|
+
break;
|
|
28
|
+
case 'tag':
|
|
29
|
+
version = meta['dist-tags'][spec.fetchSpec];
|
|
30
|
+
break;
|
|
31
|
+
case 'range':
|
|
32
|
+
version = pickVersionByVersionRangeFn({
|
|
33
|
+
meta,
|
|
34
|
+
versionRange: spec.fetchSpec,
|
|
35
|
+
preferredVersionSelectors,
|
|
36
|
+
publishedBy,
|
|
37
|
+
});
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
if (!version)
|
|
41
|
+
return null;
|
|
42
|
+
const manifest = meta.versions[version];
|
|
43
|
+
if (manifest && meta['name']) {
|
|
44
|
+
// Packages that are published to the GitHub registry are always published with a scope.
|
|
45
|
+
// However, the name in the package.json for some reason may omit the scope.
|
|
46
|
+
// So the package published to the GitHub registry will be published under @foo/bar
|
|
47
|
+
// but the name in package.json will be just bar.
|
|
48
|
+
// In order to avoid issues, we consider that the real name of the package is the one with the scope.
|
|
49
|
+
manifest.name = meta['name'];
|
|
50
|
+
}
|
|
51
|
+
return manifest;
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
if (util.types.isNativeError(err) &&
|
|
55
|
+
'code' in err &&
|
|
56
|
+
typeof err.code === 'string' &&
|
|
57
|
+
err.code.startsWith('ERR_PNPM_')) {
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
throw new PnpmError('MALFORMED_METADATA', `Received malformed metadata for "${spec.name}"`, { hint: 'This might mean that the package was unpublished from the registry' });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export function assertMetaHasTime(meta) {
|
|
64
|
+
if (meta.time == null) {
|
|
65
|
+
throw new PnpmError('MISSING_TIME', `The metadata of ${meta.name} is missing the "time" field`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const semverRangeCache = new Map();
|
|
69
|
+
// This is a performance optimization; working with string-ish semver
|
|
70
|
+
// causes lots of allocations and repeated work, but caching the Range
|
|
71
|
+
// and ensuring we give it a SemVer instance greatly speeds things up.
|
|
72
|
+
function semverSatisfiesLoose(version, range) {
|
|
73
|
+
let semverRange = semverRangeCache.get(range);
|
|
74
|
+
if (semverRange === undefined) {
|
|
75
|
+
try {
|
|
76
|
+
semverRange = new semver.Range(range, true);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
semverRange = null;
|
|
80
|
+
}
|
|
81
|
+
semverRangeCache.set(range, semverRange);
|
|
82
|
+
}
|
|
83
|
+
if (semverRange) {
|
|
84
|
+
try {
|
|
85
|
+
return semverRange.test(new semver.SemVer(version, true));
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
export function pickLowestVersionByVersionRange({ meta, versionRange, preferredVersionSelectors }) {
|
|
94
|
+
if (preferredVersionSelectors != null && Object.keys(preferredVersionSelectors).length > 0) {
|
|
95
|
+
const prioritizedPreferredVersions = prioritizePreferredVersions(meta, versionRange, preferredVersionSelectors);
|
|
96
|
+
for (const preferredVersions of prioritizedPreferredVersions) {
|
|
97
|
+
const preferredVersion = semver.minSatisfying(preferredVersions, versionRange, true);
|
|
98
|
+
if (preferredVersion) {
|
|
99
|
+
return preferredVersion;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (versionRange === '*') {
|
|
104
|
+
return Object.keys(meta.versions).sort(semver.compare)[0];
|
|
105
|
+
}
|
|
106
|
+
return semver.minSatisfying(Object.keys(meta.versions), versionRange, true);
|
|
107
|
+
}
|
|
108
|
+
export function pickVersionByVersionRange({ meta, versionRange, preferredVersionSelectors }) {
|
|
109
|
+
const latest = meta['dist-tags'].latest;
|
|
110
|
+
if (preferredVersionSelectors != null && Object.keys(preferredVersionSelectors).length > 0) {
|
|
111
|
+
const prioritizedPreferredVersions = prioritizePreferredVersions(meta, versionRange, preferredVersionSelectors);
|
|
112
|
+
for (const preferredVersions of prioritizedPreferredVersions) {
|
|
113
|
+
if (preferredVersions.includes(latest) && semverSatisfiesLoose(latest, versionRange)) {
|
|
114
|
+
return latest;
|
|
115
|
+
}
|
|
116
|
+
const preferredVersion = semver.maxSatisfying(preferredVersions, versionRange, true);
|
|
117
|
+
if (preferredVersion) {
|
|
118
|
+
return preferredVersion;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const versions = Object.keys(meta.versions);
|
|
123
|
+
if (latest && (versionRange === '*' || semverSatisfiesLoose(latest, versionRange))) {
|
|
124
|
+
// Not using semver.satisfies in case of * because it does not select beta versions.
|
|
125
|
+
// E.g.: 1.0.0-beta.1. See issue: https://github.com/pnpm/pnpm/issues/865
|
|
126
|
+
return latest;
|
|
127
|
+
}
|
|
128
|
+
const maxVersion = semver.maxSatisfying(versions, versionRange, true);
|
|
129
|
+
// if the selected version is deprecated, try to find a non-deprecated one that satisfies the range
|
|
130
|
+
if (maxVersion && meta.versions[maxVersion].deprecated && versions.length > 1) {
|
|
131
|
+
const nonDeprecatedVersions = versions.map((version) => meta.versions[version])
|
|
132
|
+
.filter((versionMeta) => !versionMeta.deprecated)
|
|
133
|
+
.map((versionMeta) => versionMeta.version);
|
|
134
|
+
const maxNonDeprecatedVersion = semver.maxSatisfying(nonDeprecatedVersions, versionRange, true);
|
|
135
|
+
if (maxNonDeprecatedVersion)
|
|
136
|
+
return maxNonDeprecatedVersion;
|
|
137
|
+
}
|
|
138
|
+
return maxVersion;
|
|
139
|
+
}
|
|
140
|
+
function prioritizePreferredVersions(meta, versionRange, preferredVerSelectors) {
|
|
141
|
+
const preferredVerSelectorsArr = Object.entries(preferredVerSelectors ?? {});
|
|
142
|
+
const versionsPrioritizer = new PreferredVersionsPrioritizer();
|
|
143
|
+
// First, add all versions that satisfy versionRange with default weight 0
|
|
144
|
+
for (const version of Object.keys(meta.versions)) {
|
|
145
|
+
if (semverSatisfiesLoose(version, versionRange)) {
|
|
146
|
+
versionsPrioritizer.add(version, 0);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Then apply weights from preferred selectors
|
|
150
|
+
for (const [preferredSelector, preferredSelectorType] of preferredVerSelectorsArr) {
|
|
151
|
+
const { selectorType, weight } = typeof preferredSelectorType === 'string'
|
|
152
|
+
? { selectorType: preferredSelectorType, weight: 1 }
|
|
153
|
+
: preferredSelectorType;
|
|
154
|
+
if (preferredSelector === versionRange)
|
|
155
|
+
continue;
|
|
156
|
+
switch (selectorType) {
|
|
157
|
+
case 'tag': {
|
|
158
|
+
versionsPrioritizer.add(meta['dist-tags'][preferredSelector], weight);
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
case 'range': {
|
|
162
|
+
const versions = Object.keys(meta.versions);
|
|
163
|
+
for (const version of versions) {
|
|
164
|
+
if (semverSatisfiesLoose(version, preferredSelector)) {
|
|
165
|
+
versionsPrioritizer.add(version, weight);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case 'version': {
|
|
171
|
+
if (meta.versions[preferredSelector]) {
|
|
172
|
+
versionsPrioritizer.add(preferredSelector, weight);
|
|
173
|
+
}
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return versionsPrioritizer.versionsByPriority();
|
|
179
|
+
}
|
|
180
|
+
class PreferredVersionsPrioritizer {
|
|
181
|
+
preferredVersions = {};
|
|
182
|
+
add(version, weight) {
|
|
183
|
+
if (!this.preferredVersions[version]) {
|
|
184
|
+
this.preferredVersions[version] = weight;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
this.preferredVersions[version] += weight;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
versionsByPriority() {
|
|
191
|
+
const versionsByWeight = Object.entries(this.preferredVersions)
|
|
192
|
+
.reduce((acc, [version, weight]) => {
|
|
193
|
+
acc[weight] = acc[weight] ?? [];
|
|
194
|
+
acc[weight].push(version);
|
|
195
|
+
return acc;
|
|
196
|
+
}, {});
|
|
197
|
+
return Object.keys(versionsByWeight)
|
|
198
|
+
.sort((a, b) => parseInt(b, 10) - parseInt(a, 10))
|
|
199
|
+
.map((weight) => versionsByWeight[parseInt(weight, 10)]);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=pickPackageFromMeta.js.map
|
package/lib/toRaw.d.ts
ADDED
package/lib/toRaw.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PackageInRegistry, PackageMeta } from '@pnpm/resolving.registry.types';
|
|
2
|
+
import type { PackageVersionPolicy } from '@pnpm/types';
|
|
3
|
+
type TrustEvidence = 'provenance' | 'trustedPublisher';
|
|
4
|
+
export declare function failIfTrustDowngraded(meta: PackageMeta, version: string, opts?: {
|
|
5
|
+
trustPolicyExclude?: PackageVersionPolicy;
|
|
6
|
+
trustPolicyIgnoreAfter?: number;
|
|
7
|
+
}): void;
|
|
8
|
+
export declare function getTrustEvidence(manifest: PackageInRegistry): TrustEvidence | undefined;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { PnpmError } from '@pnpm/error';
|
|
2
|
+
import semver from 'semver';
|
|
3
|
+
import { assertMetaHasTime } from './pickPackageFromMeta.js';
|
|
4
|
+
const TRUST_RANK = {
|
|
5
|
+
trustedPublisher: 2,
|
|
6
|
+
provenance: 1,
|
|
7
|
+
};
|
|
8
|
+
export function failIfTrustDowngraded(meta, version, opts) {
|
|
9
|
+
if (opts?.trustPolicyExclude) {
|
|
10
|
+
const excludeResult = opts.trustPolicyExclude(meta.name);
|
|
11
|
+
if (excludeResult === true) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (Array.isArray(excludeResult) && excludeResult.includes(version)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
assertMetaHasTime(meta);
|
|
19
|
+
const versionPublishedAt = meta.time[version];
|
|
20
|
+
if (!versionPublishedAt) {
|
|
21
|
+
throw new PnpmError('TRUST_CHECK_FAIL', `Missing time for version ${version} of ${meta.name} in metadata`);
|
|
22
|
+
}
|
|
23
|
+
const versionDate = new Date(versionPublishedAt);
|
|
24
|
+
if (opts?.trustPolicyIgnoreAfter) {
|
|
25
|
+
const now = new Date();
|
|
26
|
+
const minutesSincePublish = (now.getTime() - versionDate.getTime()) / (1000 * 60);
|
|
27
|
+
if (minutesSincePublish > opts.trustPolicyIgnoreAfter) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const manifest = meta.versions[version];
|
|
32
|
+
if (!manifest) {
|
|
33
|
+
throw new PnpmError('TRUST_CHECK_FAIL', `Missing version object for version ${version} of ${meta.name} in metadata`);
|
|
34
|
+
}
|
|
35
|
+
const strongestEvidencePriorToRequestedVersion = detectStrongestTrustEvidenceBeforeDate(meta, versionDate, {
|
|
36
|
+
excludePrerelease: !semver.prerelease(version, true),
|
|
37
|
+
});
|
|
38
|
+
if (strongestEvidencePriorToRequestedVersion == null) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const currentTrustEvidence = getTrustEvidence(manifest);
|
|
42
|
+
if (currentTrustEvidence == null || TRUST_RANK[strongestEvidencePriorToRequestedVersion] > TRUST_RANK[currentTrustEvidence]) {
|
|
43
|
+
throw new PnpmError('TRUST_DOWNGRADE', `High-risk trust downgrade for "${meta.name}@${version}" (possible package takeover)`, {
|
|
44
|
+
hint: 'Trust checks are based solely on publish date, not semver. ' +
|
|
45
|
+
'A package cannot be installed if any earlier-published version had stronger trust evidence. ' +
|
|
46
|
+
`Earlier versions had ${prettyPrintTrustEvidence(strongestEvidencePriorToRequestedVersion)}, ` +
|
|
47
|
+
`but this version has ${prettyPrintTrustEvidence(currentTrustEvidence)}. ` +
|
|
48
|
+
'A trust downgrade may indicate a supply chain incident.',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function prettyPrintTrustEvidence(trustEvidence) {
|
|
53
|
+
switch (trustEvidence) {
|
|
54
|
+
case 'trustedPublisher': return 'trusted publisher';
|
|
55
|
+
case 'provenance': return 'provenance attestation';
|
|
56
|
+
default: return 'no trust evidence';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function detectStrongestTrustEvidenceBeforeDate(meta, beforeDate, options) {
|
|
60
|
+
let best;
|
|
61
|
+
for (const [version, manifest] of Object.entries(meta.versions)) {
|
|
62
|
+
if (options.excludePrerelease && semver.prerelease(version, true))
|
|
63
|
+
continue;
|
|
64
|
+
const ts = meta.time[version];
|
|
65
|
+
if (!ts)
|
|
66
|
+
continue;
|
|
67
|
+
const publishedAt = new Date(ts);
|
|
68
|
+
if (!(publishedAt < beforeDate))
|
|
69
|
+
continue;
|
|
70
|
+
const trustEvidence = getTrustEvidence(manifest);
|
|
71
|
+
if (!trustEvidence)
|
|
72
|
+
continue;
|
|
73
|
+
if (trustEvidence === 'trustedPublisher') {
|
|
74
|
+
return 'trustedPublisher';
|
|
75
|
+
}
|
|
76
|
+
best ||= 'provenance';
|
|
77
|
+
}
|
|
78
|
+
return best;
|
|
79
|
+
}
|
|
80
|
+
export function getTrustEvidence(manifest) {
|
|
81
|
+
if (manifest._npmUser?.trustedPublisher) {
|
|
82
|
+
return 'trustedPublisher';
|
|
83
|
+
}
|
|
84
|
+
if (manifest.dist?.attestations?.provenance) {
|
|
85
|
+
return 'provenance';
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=trustChecks.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { parseRange } from 'semver-utils';
|
|
2
|
+
export function whichVersionIsPinned(spec) {
|
|
3
|
+
const colonIndex = spec.indexOf(':');
|
|
4
|
+
if (colonIndex !== -1) {
|
|
5
|
+
spec = spec.substring(colonIndex + 1);
|
|
6
|
+
}
|
|
7
|
+
const index = spec.lastIndexOf('@');
|
|
8
|
+
if (index !== -1) {
|
|
9
|
+
spec = spec.slice(index + 1);
|
|
10
|
+
}
|
|
11
|
+
if (spec === '*')
|
|
12
|
+
return 'none';
|
|
13
|
+
const parsedRange = parseRange(spec);
|
|
14
|
+
if (parsedRange.length !== 1)
|
|
15
|
+
return undefined;
|
|
16
|
+
const versionObject = parsedRange[0];
|
|
17
|
+
switch (versionObject.operator) {
|
|
18
|
+
case '~': return 'minor';
|
|
19
|
+
case '^': return 'major';
|
|
20
|
+
case undefined:
|
|
21
|
+
if (versionObject.patch)
|
|
22
|
+
return 'patch';
|
|
23
|
+
if (versionObject.minor)
|
|
24
|
+
return 'minor';
|
|
25
|
+
if (versionObject.major)
|
|
26
|
+
return 'major';
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=whichVersionIsPinned.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function workspacePrefToNpm(workspaceBareSpecifier: string): string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { WorkspaceSpec } from '@pnpm/workspace.spec-parser';
|
|
2
|
+
export function workspacePrefToNpm(workspaceBareSpecifier) {
|
|
3
|
+
const parseResult = WorkspaceSpec.parse(workspaceBareSpecifier);
|
|
4
|
+
if (parseResult == null) {
|
|
5
|
+
throw new Error(`Invalid workspace spec: ${workspaceBareSpecifier}`);
|
|
6
|
+
}
|
|
7
|
+
const { alias, version } = parseResult;
|
|
8
|
+
const versionPart = version === '^' || version === '~' || version === '' ? '*' : version;
|
|
9
|
+
return alias
|
|
10
|
+
? `npm:${alias}@${versionPart}`
|
|
11
|
+
: versionPart;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=workspacePrefToNpm.js.map
|