@pnpm/deps.inspection.peers-checker 1100.0.21 → 1100.0.23
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 +26 -0
- package/lib/checkPeerDependencies.d.ts +7 -0
- package/lib/checkPeerDependencies.js +197 -0
- package/lib/index.d.ts +1 -0
- package/package.json +11 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @pnpm/deps.inspection.peers-checker
|
|
2
2
|
|
|
3
|
+
## 1100.0.23
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Republished every package: the tarballs published by the v11.13.1 through v11.16.0 releases were missing most of their compiled files due to a packing bug [#13164](https://github.com/pnpm/pnpm/issues/13164).
|
|
8
|
+
|
|
9
|
+
- Updated dependencies:
|
|
10
|
+
- @pnpm/config.matcher@1100.0.2
|
|
11
|
+
- @pnpm/config.parse-overrides@1100.1.1
|
|
12
|
+
- @pnpm/deps.path@1100.0.11
|
|
13
|
+
- @pnpm/deps.peer-range@1100.1.1
|
|
14
|
+
- @pnpm/error@1100.1.0
|
|
15
|
+
- @pnpm/lockfile.fs@1100.1.14
|
|
16
|
+
- @pnpm/lockfile.walker@1100.0.16
|
|
17
|
+
- @pnpm/types@1101.6.0
|
|
18
|
+
|
|
19
|
+
## 1100.0.22
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- Updated dependencies:
|
|
24
|
+
- @pnpm/deps.path@1100.0.10
|
|
25
|
+
- @pnpm/lockfile.fs@1100.1.13
|
|
26
|
+
- @pnpm/lockfile.walker@1100.0.15
|
|
27
|
+
- @pnpm/types@1101.5.0
|
|
28
|
+
|
|
3
29
|
## 1100.0.21
|
|
4
30
|
|
|
5
31
|
### Patch Changes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PeerDependencyIssuesByProjects, PeerDependencyRules } from '@pnpm/types';
|
|
2
|
+
export declare function checkPeerDependencies(projectPaths: string[], opts: {
|
|
3
|
+
lockfileDir: string;
|
|
4
|
+
checkWantedLockfileOnly?: boolean;
|
|
5
|
+
modulesDir?: string;
|
|
6
|
+
peerDependencyRules?: PeerDependencyRules;
|
|
7
|
+
}): Promise<PeerDependencyIssuesByProjects>;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { createMatcher } from '@pnpm/config.matcher';
|
|
3
|
+
import { parseOverrides } from '@pnpm/config.parse-overrides';
|
|
4
|
+
import { parse as parseDependencyPath, refToRelative } from '@pnpm/deps.path';
|
|
5
|
+
import { getPeerVersionRange } from '@pnpm/deps.peer-range';
|
|
6
|
+
import { PnpmError } from '@pnpm/error';
|
|
7
|
+
import { getLockfileImporterId, readCurrentLockfile, readWantedLockfile, } from '@pnpm/lockfile.fs';
|
|
8
|
+
import { lockfileWalkerGroupImporterSteps } from '@pnpm/lockfile.walker';
|
|
9
|
+
import semver from 'semver';
|
|
10
|
+
import { intersect } from 'semver-range-intersect';
|
|
11
|
+
export async function checkPeerDependencies(projectPaths, opts) {
|
|
12
|
+
const modulesDir = opts.modulesDir ?? 'node_modules';
|
|
13
|
+
const lockfile = opts.checkWantedLockfileOnly
|
|
14
|
+
? await readWantedLockfile(opts.lockfileDir, { ignoreIncompatible: false })
|
|
15
|
+
: await readCurrentLockfile(path.join(opts.lockfileDir, modulesDir, '.pnpm'), { ignoreIncompatible: false })
|
|
16
|
+
?? await readWantedLockfile(opts.lockfileDir, { ignoreIncompatible: false });
|
|
17
|
+
if (!lockfile)
|
|
18
|
+
return {};
|
|
19
|
+
const issues = checkPeerDependenciesFromLockfile(projectPaths, lockfile, opts.lockfileDir);
|
|
20
|
+
if (opts.peerDependencyRules) {
|
|
21
|
+
return filterPeerDependencyIssues(issues, opts.peerDependencyRules);
|
|
22
|
+
}
|
|
23
|
+
return issues;
|
|
24
|
+
}
|
|
25
|
+
function checkPeerDependenciesFromLockfile(projectPaths, lockfile, lockfileDir) {
|
|
26
|
+
const packages = lockfile.packages ?? {};
|
|
27
|
+
const importerIds = projectPaths.map((p) => getLockfileImporterId(lockfileDir, p));
|
|
28
|
+
const walkerSteps = lockfileWalkerGroupImporterSteps(lockfile, importerIds);
|
|
29
|
+
const result = {};
|
|
30
|
+
for (const { importerId, step } of walkerSteps) {
|
|
31
|
+
const projectIssues = {
|
|
32
|
+
bad: {},
|
|
33
|
+
missing: {},
|
|
34
|
+
conflicts: [],
|
|
35
|
+
intersections: {},
|
|
36
|
+
};
|
|
37
|
+
walkStep(step, packages, [], projectIssues);
|
|
38
|
+
const merged = mergePeers(projectIssues.missing);
|
|
39
|
+
projectIssues.conflicts = merged.conflicts;
|
|
40
|
+
projectIssues.intersections = merged.intersections;
|
|
41
|
+
result[importerId] = projectIssues;
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
function walkStep(step, packages, parents, issues) {
|
|
46
|
+
for (const { depPath, pkgSnapshot, next } of step.dependencies) {
|
|
47
|
+
const parsed = parseDependencyPath(depPath);
|
|
48
|
+
const pkgName = parsed.name ?? depPath;
|
|
49
|
+
const pkgVersion = pkgSnapshot.version ?? parsed.version ?? '';
|
|
50
|
+
const currentParents = [...parents, { name: pkgName, version: pkgVersion }];
|
|
51
|
+
if (pkgSnapshot.peerDependencies) {
|
|
52
|
+
for (const [peerName, rawPeerRange] of Object.entries(pkgSnapshot.peerDependencies)) {
|
|
53
|
+
const peerRange = getPeerVersionRange(rawPeerRange);
|
|
54
|
+
const isOptional = pkgSnapshot.peerDependenciesMeta?.[peerName]?.optional === true;
|
|
55
|
+
const resolvedPeerRef = pkgSnapshot.dependencies?.[peerName] ?? pkgSnapshot.optionalDependencies?.[peerName];
|
|
56
|
+
if (!resolvedPeerRef) {
|
|
57
|
+
if (!isOptional) {
|
|
58
|
+
if (!issues.missing[peerName])
|
|
59
|
+
issues.missing[peerName] = [];
|
|
60
|
+
issues.missing[peerName].push({
|
|
61
|
+
parents: currentParents,
|
|
62
|
+
optional: isOptional,
|
|
63
|
+
wantedRange: peerRange,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
const peerVersion = extractVersion(resolvedPeerRef, peerName, packages);
|
|
69
|
+
if (peerVersion && !satisfies(peerVersion, peerRange)) {
|
|
70
|
+
if (!issues.bad[peerName])
|
|
71
|
+
issues.bad[peerName] = [];
|
|
72
|
+
issues.bad[peerName].push({
|
|
73
|
+
parents: currentParents,
|
|
74
|
+
optional: isOptional,
|
|
75
|
+
wantedRange: peerRange,
|
|
76
|
+
foundVersion: peerVersion,
|
|
77
|
+
resolvedFrom: [],
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
walkStep(next(), packages, currentParents, issues);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function extractVersion(ref, alias, packages) {
|
|
87
|
+
const depPath = refToRelative(ref, alias);
|
|
88
|
+
if (depPath && packages[depPath]) {
|
|
89
|
+
const parsed = parseDependencyPath(depPath);
|
|
90
|
+
return packages[depPath].version ?? parsed.version;
|
|
91
|
+
}
|
|
92
|
+
const parsed = parseDependencyPath(`${alias}@${ref}`);
|
|
93
|
+
return parsed.version;
|
|
94
|
+
}
|
|
95
|
+
function satisfies(version, range) {
|
|
96
|
+
if (range === '*')
|
|
97
|
+
return true;
|
|
98
|
+
return semver.satisfies(version, range, { includePrerelease: true, loose: true });
|
|
99
|
+
}
|
|
100
|
+
function filterPeerDependencyIssues(peerDependencyIssuesByProjects, rules) {
|
|
101
|
+
const ignoreMissingMatcher = createMatcher([...new Set(rules.ignoreMissing ?? [])]);
|
|
102
|
+
const allowAnyMatcher = createMatcher([...new Set(rules.allowAny ?? [])]);
|
|
103
|
+
const { matchAll: allowedVersionsMatchAll, byParent: allowedVersionsByParent } = parseAllowedVersions(rules.allowedVersions ?? {});
|
|
104
|
+
const result = {};
|
|
105
|
+
for (const [projectId, { bad, missing, conflicts, intersections }] of Object.entries(peerDependencyIssuesByProjects)) {
|
|
106
|
+
const filteredMissing = {};
|
|
107
|
+
const filteredBad = {};
|
|
108
|
+
const filteredIntersections = {};
|
|
109
|
+
for (const [peerName, issues] of Object.entries(missing)) {
|
|
110
|
+
if (ignoreMissingMatcher(peerName) || issues.every(({ optional }) => optional))
|
|
111
|
+
continue;
|
|
112
|
+
filteredMissing[peerName] = issues;
|
|
113
|
+
if (intersections[peerName]) {
|
|
114
|
+
filteredIntersections[peerName] = intersections[peerName];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
for (const [peerName, issues] of Object.entries(bad)) {
|
|
118
|
+
if (allowAnyMatcher(peerName))
|
|
119
|
+
continue;
|
|
120
|
+
const remaining = issues.filter((issue) => {
|
|
121
|
+
if (allowedVersionsMatchAll[peerName]?.some((range) => semver.satisfies(issue.foundVersion, range)))
|
|
122
|
+
return false;
|
|
123
|
+
const declaringParent = issue.parents.at(-1);
|
|
124
|
+
if (declaringParent && allowedVersionsByParent[declaringParent.name]?.[peerName]?.some((range) => semver.satisfies(issue.foundVersion, range)))
|
|
125
|
+
return false;
|
|
126
|
+
return true;
|
|
127
|
+
});
|
|
128
|
+
if (remaining.length > 0) {
|
|
129
|
+
filteredBad[peerName] = remaining;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
result[projectId] = {
|
|
133
|
+
bad: filteredBad,
|
|
134
|
+
missing: filteredMissing,
|
|
135
|
+
conflicts: conflicts.filter((peerName) => filteredMissing[peerName] != null),
|
|
136
|
+
intersections: filteredIntersections,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
function parseAllowedVersions(allowedVersions) {
|
|
142
|
+
let overrides;
|
|
143
|
+
try {
|
|
144
|
+
overrides = parseOverrides(allowedVersions);
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
throw new PnpmError('INVALID_ALLOWED_VERSION_SELECTOR', `${err.message} in pnpm.peerDependencyRules.allowedVersions`);
|
|
148
|
+
}
|
|
149
|
+
const matchAll = {};
|
|
150
|
+
const byParent = {};
|
|
151
|
+
for (const { parentPkg, targetPkg, newBareSpecifier } of overrides) {
|
|
152
|
+
const ranges = newBareSpecifier.split('||').map((v) => v.trim());
|
|
153
|
+
if (parentPkg) {
|
|
154
|
+
if (!byParent[parentPkg.name])
|
|
155
|
+
byParent[parentPkg.name] = {};
|
|
156
|
+
byParent[parentPkg.name][targetPkg.name] = ranges;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
matchAll[targetPkg.name] = ranges;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return { matchAll, byParent };
|
|
163
|
+
}
|
|
164
|
+
function mergePeers(missingPeers) {
|
|
165
|
+
const conflicts = [];
|
|
166
|
+
const intersections = {};
|
|
167
|
+
for (const [peerName, issues] of Object.entries(missingPeers)) {
|
|
168
|
+
if (issues.every(({ optional }) => optional))
|
|
169
|
+
continue;
|
|
170
|
+
if (issues.length === 1) {
|
|
171
|
+
intersections[peerName] = issues[0].wantedRange;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const ranges = [...new Set(issues.map(({ wantedRange }) => wantedRange))];
|
|
175
|
+
if (ranges.length === 1) {
|
|
176
|
+
intersections[peerName] = ranges[0];
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const intersection = safeIntersect(ranges);
|
|
180
|
+
if (intersection === null) {
|
|
181
|
+
conflicts.push(peerName);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
intersections[peerName] = intersection;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return { conflicts, intersections };
|
|
188
|
+
}
|
|
189
|
+
function safeIntersect(ranges) {
|
|
190
|
+
try {
|
|
191
|
+
return intersect(...ranges);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=checkPeerDependencies.js.map
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { checkPeerDependencies } from './checkPeerDependencies.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/deps.inspection.peers-checker",
|
|
3
|
-
"version": "1100.0.
|
|
3
|
+
"version": "1100.0.23",
|
|
4
4
|
"description": "Check for unmet and missing peer dependency issues from the lockfile",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -28,21 +28,21 @@
|
|
|
28
28
|
"!*.map"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@pnpm/config.matcher": "1100.0.
|
|
32
|
-
"@pnpm/config.parse-overrides": "1100.1.
|
|
33
|
-
"@pnpm/deps.path": "1100.0.
|
|
34
|
-
"@pnpm/deps.peer-range": "1100.1.
|
|
35
|
-
"@pnpm/error": "1100.0
|
|
36
|
-
"@pnpm/lockfile.fs": "1100.1.
|
|
37
|
-
"@pnpm/lockfile.walker": "1100.0.
|
|
38
|
-
"@pnpm/types": "1101.
|
|
31
|
+
"@pnpm/config.matcher": "1100.0.2",
|
|
32
|
+
"@pnpm/config.parse-overrides": "1100.1.1",
|
|
33
|
+
"@pnpm/deps.path": "1100.0.11",
|
|
34
|
+
"@pnpm/deps.peer-range": "1100.1.1",
|
|
35
|
+
"@pnpm/error": "1100.1.0",
|
|
36
|
+
"@pnpm/lockfile.fs": "1100.1.14",
|
|
37
|
+
"@pnpm/lockfile.walker": "1100.0.16",
|
|
38
|
+
"@pnpm/types": "1101.6.0",
|
|
39
39
|
"semver": "^7.8.4",
|
|
40
40
|
"semver-range-intersect": "^0.3.1"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@jest/globals": "30.4.1",
|
|
44
|
-
"@pnpm/deps.inspection.peers-checker": "1100.0.
|
|
45
|
-
"@pnpm/test-fixtures": "1100.0.
|
|
44
|
+
"@pnpm/deps.inspection.peers-checker": "1100.0.23",
|
|
45
|
+
"@pnpm/test-fixtures": "1100.0.1",
|
|
46
46
|
"@types/semver": "7.7.1"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|