/
validatePeerDeps.js
132 lines (103 loc) · 4.17 KB
/
validatePeerDeps.js
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
const { getWhyCommand, isPnpm } = require('./packageManager');
const { readFile } = require('node:fs/promises');
const { fdir: Fdir } = require('fdir');
const semver = require('semver');
const chalk = require('chalk');
const banner = require('./banner');
const track = require('../telemetry');
const { getPathFromCwd } = require('../lib/cwd');
const { paths } = require('../context');
/**
* @param {unknown[]} list
* @param {Function} fn
*/
const asyncMap = (list, fn) => {
return Promise.all(list.map((item) => fn(item)));
};
const singletonPackages = ['@vanilla-extract/css'];
module.exports = async () => {
if (isPnpm) {
// pnpm doesn't nest dependencies in the same way as yarn or npm, so the method used below won't
// work for detecting duplicate packages
return;
}
try {
/** @type {string[]} */
const duplicatePackages = [];
const packagesToCheck = [...paths.compilePackages, ...singletonPackages];
const packagePatterns = packagesToCheck.map((packageName) => [
packageName,
`node_modules/${packageName}/package.json`,
]);
const patterns = packagePatterns.map(([, pattern]) => pattern);
const results = await new Fdir()
.withBasePath()
.filter((file) => patterns.some((pattern) => file.endsWith(pattern)))
.crawl('./node_modules')
.withPromise();
for (const [packageName, pattern] of packagePatterns) {
const resultsForPackage = results.filter((result) =>
result.endsWith(pattern),
);
if (resultsForPackage.length > 1) {
const messages = [
chalk`Multiple copies of {bold ${packageName}} are present in node_modules. This is likely to cause errors, but even if it works, it will probably result in an unnecessarily large bundle size.`,
];
messages.push(
resultsForPackage
.map((depLocation) => {
const { version } = require(getPathFromCwd(depLocation));
return chalk`${depLocation.replace(
'/package.json',
'',
)} ({bold ${version}})`;
})
.join('\n'),
);
messages.push(
chalk`Try running "{blue.bold ${getWhyCommand()}} {bold ${packageName}}" to diagnose the issue`,
);
track.count('duplicate_compile_package', {
compile_package: packageName,
});
banner('error', 'Error: Duplicate packages detected', messages);
duplicatePackages.push(...resultsForPackage);
}
}
const compilePackages = new Map();
await asyncMap(duplicatePackages, async (p) => {
const contents = await readFile(getPathFromCwd(p), {
encoding: 'utf8',
});
const { name, version, peerDependencies = {} } = JSON.parse(contents);
const peers = new Map();
Object.keys(peerDependencies)
.filter((dep) => paths.compilePackages.includes(dep))
.forEach((dep) => {
peers.set(dep, peerDependencies[dep]);
});
compilePackages.set(name, { version, p, peers });
});
for (const [packageName, { peers }] of compilePackages.entries()) {
for (const [peerName, peerVersionRange] of peers.entries()) {
const dep = compilePackages.get(peerName);
if (dep && !semver.satisfies(dep.version, peerVersionRange)) {
track.count('peer_dep_version_mismatch', {
compile_package: packageName,
});
const peerIsBehind = semver.gtr(dep.version, peerVersionRange);
const errorMessage = chalk`{bold ${packageName}} expected to find {bold ${peerName}} {yellow ${peerVersionRange}} but found {yellow ${dep.version}}.`;
const peerBehindMessage = chalk`The best way to fix this is for {bold ${packageName}} to update its peer dependency on {bold ${peerName}}.`;
const peerAheadMessage = chalk`The best way to fix this is to update your dependency on {bold ${peerName}}.`;
banner('warning', 'Warning: Package version mismatch', [
errorMessage,
peerIsBehind ? peerBehindMessage : peerAheadMessage,
]);
}
}
}
} catch (e) {
console.log('Error validating peer dependencies');
console.error(e);
}
};