-
-
Notifications
You must be signed in to change notification settings - Fork 944
/
satisfiesPackageManifest.ts
106 lines (103 loc) · 3.89 KB
/
satisfiesPackageManifest.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
import { type ProjectSnapshot } from '@pnpm/lockfile-types'
import {
DEPENDENCIES_FIELDS,
type ProjectManifest,
} from '@pnpm/types'
import equals from 'ramda/src/equals'
import pickBy from 'ramda/src/pickBy'
import omit from 'ramda/src/omit'
export function satisfiesPackageManifest (
opts: {
autoInstallPeers?: boolean
excludeLinksFromLockfile?: boolean
},
importer: ProjectSnapshot | undefined,
pkg: ProjectManifest
): { satisfies: boolean, detailedReason?: string } {
if (!importer) return { satisfies: false, detailedReason: 'no importer' }
let existingDeps: Record<string, string> = { ...pkg.devDependencies, ...pkg.dependencies, ...pkg.optionalDependencies }
if (opts?.autoInstallPeers) {
pkg = {
...pkg,
dependencies: {
...omit(Object.keys(existingDeps), pkg.peerDependencies),
...pkg.dependencies,
},
}
existingDeps = {
...pkg.peerDependencies,
...existingDeps,
}
}
const pickNonLinkedDeps = pickBy((spec) => !spec.startsWith('link:'))
let specs = importer.specifiers
if (opts?.excludeLinksFromLockfile) {
existingDeps = pickNonLinkedDeps(existingDeps)
specs = pickNonLinkedDeps(specs)
}
if (!equals(existingDeps, specs)) {
return {
satisfies: false,
detailedReason: `specifiers in the lockfile (${JSON.stringify(specs)}) don't match specs in package.json (${JSON.stringify(existingDeps)})`,
}
}
if (importer.publishDirectory !== pkg.publishConfig?.directory) {
return {
satisfies: false,
detailedReason: `"publishDirectory" in the lockfile (${importer.publishDirectory ?? 'undefined'}) doesn't match "publishConfig.directory" in package.json (${pkg.publishConfig?.directory ?? 'undefined'})`,
}
}
if (!equals(pkg.dependenciesMeta ?? {}, importer.dependenciesMeta ?? {})) {
return {
satisfies: false,
detailedReason: `importer dependencies meta (${JSON.stringify(importer.dependenciesMeta)}) doesn't match package manifest dependencies meta (${JSON.stringify(pkg.dependenciesMeta)})`,
}
}
for (const depField of DEPENDENCIES_FIELDS) {
const importerDeps = importer[depField] ?? {}
let pkgDeps: Record<string, string> = pkg[depField] ?? {}
if (opts?.excludeLinksFromLockfile) {
pkgDeps = pickNonLinkedDeps(pkgDeps)
}
let pkgDepNames!: string[]
switch (depField) {
case 'optionalDependencies':
pkgDepNames = Object.keys(pkgDeps)
break
case 'devDependencies':
pkgDepNames = Object.keys(pkgDeps)
.filter((depName) =>
((pkg.optionalDependencies == null) || !pkg.optionalDependencies[depName]) &&
((pkg.dependencies == null) || !pkg.dependencies[depName])
)
break
case 'dependencies':
pkgDepNames = Object.keys(pkgDeps)
.filter((depName) => (pkg.optionalDependencies == null) || !pkg.optionalDependencies[depName])
break
default:
throw new Error(`Unknown dependency type "${depField as string}"`)
}
if (
pkgDepNames.length !== Object.keys(importerDeps).length &&
pkgDepNames.length !== countOfNonLinkedDeps(importerDeps)
) {
return {
satisfies: false,
detailedReason: `"${depField}" in the lockfile (${JSON.stringify(importerDeps)}) doesn't match the same field in package.json (${JSON.stringify(pkgDeps)})`,
}
}
for (const depName of pkgDepNames) {
if (!importerDeps[depName] || importer.specifiers?.[depName] !== pkgDeps[depName]) {
return {
satisfies: false,
detailedReason: `importer ${depField}.${depName} specifier ${importer.specifiers[depName]} don't match package manifest specifier (${pkgDeps[depName]})`,
}
}
}
}
return { satisfies: true }
}
function countOfNonLinkedDeps (lockfileDeps: { [depName: string]: string }): number {
return Object.values(lockfileDeps).filter((ref) => !ref.includes('link:') && !ref.includes('file:')).length
}