-
Notifications
You must be signed in to change notification settings - Fork 34
/
duplicatePackages.ts
84 lines (73 loc) · 2.82 KB
/
duplicatePackages.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
import {StatsCompilation} from 'webpack';
import {uniq, reject, isNil} from 'ramda';
import {readPackageConfig} from '@reskript/core';
import {BuildInspectSettings, SourceFilter} from '@reskript/settings';
import {RuleProcessor, isIncluded} from './utils.js';
const extractUniqueModules = (compilations: StatsCompilation[]): string[] => {
const modules = compilations.flatMap(c => c.modules);
const names = modules.map(m => m?.nameForCondition);
return uniq(reject(isNil, names));
};
interface LibraryInfo {
name: string;
path: string;
}
const parseName = (name: string): LibraryInfo | null => {
const lastIndex = name.lastIndexOf('node_modules/');
if (lastIndex < 0) {
return null;
}
const pathPrefix = name.slice(0, lastIndex + 'node_modules/'.length);
const segments = name.slice(pathPrefix.length).split('/');
const packageName = segments[0].startsWith('@') ? `${segments[0]}/${segments[1]}` : segments[0];
return {
name: packageName,
path: pathPrefix + packageName,
};
};
const versionOfPackage = async (location: string): Promise<string> => {
try {
const packageConfig = await readPackageConfig(location);
return packageConfig.version;
}
catch {
return 'unknown';
}
};
const toPackageImportDescription = async (location: string): Promise<string> => {
const version = await versionOfPackage(location);
return ` at ${location} (v${version})`;
};
export default (compilations: StatsCompilation[], settings: BuildInspectSettings['duplicatePackages']) => {
const processor: RuleProcessor<SourceFilter> = {
config: settings,
defaultConfigValue: {},
check: async ({includes, excludes}, {report}) => {
const names = extractUniqueModules(compilations);
const occurrences = names.reduce(
(occurrences, current) => {
const info = parseName(current);
if (!info) {
return occurrences;
}
if (occurrences.has(info.name)) {
occurrences.get(info.name)!.add(info.path);
}
else {
occurrences.set(info.name, new Set([info.path]));
}
return occurrences;
},
new Map<string, Set<string>>()
);
for (const [name, paths] of occurrences.entries()) {
if (paths.size > 1 && isIncluded(name, includes, excludes)) {
const locations = await Promise.all([...paths].map(toPackageImportDescription));
report(`Found duplicate package ${name}\n${locations.join('\n')}`);
}
}
return true;
},
};
return processor;
};