/
pickPackageFromMeta.ts
111 lines (103 loc) · 4.04 KB
/
pickPackageFromMeta.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import PnpmError from '@pnpm/error'
import { VersionSelectors } from '@pnpm/resolver-base'
import semver from 'semver'
import { RegistryPackageSpec } from './parsePref'
import { PackageInRegistry, PackageMeta } from './pickPackage'
export default function (
spec: RegistryPackageSpec,
preferredVersionSelectors: VersionSelectors | undefined,
meta: PackageMeta
): PackageInRegistry | null {
try {
let version!: string | null
switch (spec.type) {
case 'version':
version = spec.fetchSpec
break
case 'tag':
version = meta['dist-tags'][spec.fetchSpec]
break
case 'range':
version = pickVersionByVersionRange(meta, spec.fetchSpec, preferredVersionSelectors)
break
}
if (!version) return null
const manifest = meta.versions[version]
if (manifest && meta['name']) {
// Packages that are published to the GitHub registry are always published with a scope.
// However, the name in the package.json for some reason may omit the scope.
// So the package published to the GitHub registry will be published under @foo/bar
// but the name in package.json will be just bar.
// In order to avoid issues, we consider that the real name of the package is the one with the scope.
manifest.name = meta['name']
}
return manifest
} catch (err: any) { // eslint-disable-line
throw new PnpmError('MALFORMED_METADATA',
`Received malformed metadata for "${spec.name}"`,
{ hint: 'This might mean that the package was unpublished from the registry' }
)
}
}
function pickVersionByVersionRange (
meta: PackageMeta,
versionRange: string,
preferredVerSels?: VersionSelectors
): string | null {
let versions: string[] | undefined
const latest = meta['dist-tags'].latest
const preferredVerSelsArr = Object.entries(preferredVerSels ?? {})
if (preferredVerSelsArr.length > 0) {
const preferredVersions: string[] = []
for (const [preferredSelector, preferredSelectorType] of preferredVerSelsArr) {
if (preferredSelector === versionRange) continue
switch (preferredSelectorType) {
case 'tag': {
preferredVersions.push(meta['dist-tags'][preferredSelector])
break
}
case 'range': {
// This might be slow if there are many versions
// and the package is an indirect dependency many times in the project.
// If it will create noticeable slowdown, then might be a good idea to add some caching
versions = Object.keys(meta.versions)
for (const version of versions) {
if (semver.satisfies(version, preferredSelector, true)) {
preferredVersions.push(version)
}
}
break
}
case 'version': {
if (meta.versions[preferredSelector]) {
preferredVersions.push(preferredSelector)
}
break
}
}
}
if (preferredVersions.includes(latest) && semver.satisfies(latest, versionRange, true)) {
return latest
}
const preferredVersion = semver.maxSatisfying(preferredVersions, versionRange, true)
if (preferredVersion) {
return preferredVersion
}
}
// Not using semver.satisfies in case of * because it does not select beta versions.
// E.g.: 1.0.0-beta.1. See issue: https://github.com/pnpm/pnpm/issues/865
if (versionRange === '*' || semver.satisfies(latest, versionRange, true)) {
return latest
}
versions = versions ?? Object.keys(meta.versions)
const maxVersion = semver.maxSatisfying(versions, versionRange, true)
// if the selected version is deprecated, try to find a non-deprecated one that satisfies the range
if (maxVersion && meta.versions[maxVersion].deprecated && versions.length > 1) {
const nonDeprecatedVersions = versions.map((version) => meta.versions[version])
.filter((versionMeta) => !versionMeta.deprecated)
.map((versionMeta) => versionMeta.version)
const maxNonDeprecatedVersion = semver.maxSatisfying(nonDeprecatedVersions, versionRange, true)
if (maxNonDeprecatedVersion) return maxNonDeprecatedVersion
}
return maxVersion
}