-
-
Notifications
You must be signed in to change notification settings - Fork 936
/
pickPackageFromMeta.ts
145 lines (135 loc) · 5.35 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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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 type PickVersionByVersionRange = (
meta: PackageMeta,
versionRange: string,
preferredVerSels?: VersionSelectors,
publishedBy?: Date
) => string | null
export function pickPackageFromMeta (
pickVersionByVersionRangeFn: PickVersionByVersionRange,
spec: RegistryPackageSpec,
preferredVersionSelectors: VersionSelectors | undefined,
meta: PackageMeta,
publishedBy?: Date
): PackageInRegistry | null {
if (!meta.versions || Object.keys(meta.versions).length === 0) {
// Unfortunately, the npm registry doesn't return the time field in the abbreviated metadata.
// So we won't always know if the package was unpublished.
if (meta.time?.unpublished?.versions?.length) {
throw new PnpmError('UNPUBLISHED_PKG', `No versions available for ${spec.name} because it was unpublished`)
}
throw new PnpmError('NO_VERSIONS', `No versions available for ${spec.name}. The package may be unpublished.`)
}
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 = pickVersionByVersionRangeFn(meta, spec.fetchSpec, preferredVersionSelectors, publishedBy)
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
// if (meta.unpublished) {
// throw new PnpmError('UNPUBLISHED_PKG', `Package ${spec.name} has been unpublished`)
// }
throw new PnpmError('MALFORMED_METADATA',
`Received malformed metadata for "${spec.name}"`,
{ hint: 'This might mean that the package was unpublished from the registry' }
)
}
}
export function pickLowestVersionByVersionRange (
meta: PackageMeta,
versionRange: string
) {
return semver.minSatisfying(Object.keys(meta.versions), versionRange, true)
}
export function pickVersionByVersionRange (
meta: PackageMeta,
versionRange: string,
preferredVerSels?: VersionSelectors,
publishedBy?: Date
) {
let versions: string[] | undefined
let latest: string | undefined = 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
}
}
versions = versions ?? Object.keys(meta.versions)
if (publishedBy) {
versions = versions.filter(version => new Date(meta.time![version]) <= publishedBy)
if (!versions.includes(latest)) {
latest = undefined
}
}
if (latest && (versionRange === '*' || semver.satisfies(latest, versionRange, true))) {
// 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
return latest
}
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
}