UNPKG

8.33 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.create = void 0;
4const chalk = require("chalk");
5const modules_1 = require("../interfaces/modules");
6const strings_1 = require("../util/strings");
7const base_1 = require("./base");
8/**
9 * Create map of `basename` -> `source` -> `IModule`.
10 *
11 * @param mods {IModule[]} array of module objects.
12 * @returns {IModulesByBaseNameBySource} map
13 */
14const modulesByBaseNameBySource = (mods) => {
15 // Mutable, empty object to group base names with.
16 const modsMap = {};
17 // Iterate node_modules modules and add to keyed object.
18 mods.forEach((mod) => {
19 if (!mod.isNodeModules) {
20 return;
21 }
22 // First level -- base name
23 if (mod.baseName === null) { // Programming error.
24 throw new Error(`Encountered non-node_modules null baseName: ${JSON.stringify(mod)}`);
25 }
26 const base = modsMap[mod.baseName] = modsMap[mod.baseName] || {};
27 // Second level -- source.
28 // Use token placeholder if synthetic.
29 const source = mod.isSynthetic ? modules_1.SYNTHETIC_SOURCE_TOKEN : mod.source;
30 if (source === null) { // Programming error.
31 throw new Error(`Encountered null source in non-synthetic module: ${JSON.stringify(mod)}`);
32 }
33 base[source] = (base[source] || []).concat(mod);
34 });
35 // Now, remove any single item keys (no duplicates).
36 Object.keys(modsMap).forEach((baseName) => {
37 const keys = Object.keys(modsMap[baseName]);
38 if (keys.length === 1 && modsMap[baseName][keys[0]].length === 1) {
39 delete modsMap[baseName];
40 }
41 });
42 return modsMap;
43};
44// Helper
45const createEmptySummary = () => ({
46 extraFiles: {
47 num: 0,
48 },
49 extraSources: {
50 bytes: 0,
51 num: 0,
52 },
53});
54class Duplicates extends base_1.Action {
55 shouldBail() {
56 return this.getData().then((data) => data.meta.extraFiles.num !== 0);
57 }
58 _getData() {
59 return Promise.resolve()
60 .then(() => {
61 const { assets } = this;
62 const assetNames = Object.keys(assets).sort(strings_1.sort);
63 // Get asset duplicates
64 const assetDups = {};
65 assetNames.forEach((name) => {
66 const modsMap = modulesByBaseNameBySource(assets[name].mods);
67 const files = {};
68 Object.keys(modsMap).forEach((baseName) => {
69 files[baseName] = {
70 meta: createEmptySummary(),
71 sources: Object
72 .keys(modsMap[baseName])
73 .sort(strings_1.sort)
74 .map((source) => ({
75 meta: createEmptySummary(),
76 modules: modsMap[baseName][source].map((mod) => ({
77 baseName: mod.baseName,
78 fileName: mod.identifier,
79 size: {
80 full: mod.size,
81 },
82 })),
83 })),
84 };
85 });
86 assetDups[name] = {
87 files,
88 meta: createEmptySummary(),
89 };
90 });
91 // Create real data object.
92 // Start without any summaries. Just raw object structure.
93 const data = {
94 assets: assetDups,
95 meta: createEmptySummary(),
96 };
97 assetNames.forEach((name) => {
98 const assetDup = data.assets[name];
99 Object.keys(assetDup.files).forEach((baseName) => {
100 const { sources, meta } = assetDup.files[baseName];
101 sources.forEach((sourceGroup) => {
102 // Then, replace per source group meta
103 sourceGroup.meta = {
104 extraFiles: {
105 num: 1,
106 },
107 extraSources: {
108 bytes: sourceGroup.modules.reduce((bytes, mod) => bytes + mod.size.full, 0),
109 num: sourceGroup.modules.length,
110 },
111 };
112 // Then, update per file group meta
113 meta.extraFiles.num += 1;
114 meta.extraSources.bytes += sourceGroup.meta.extraSources.bytes;
115 meta.extraSources.num += sourceGroup.meta.extraSources.num;
116 // Then, update asset meta
117 assetDup.meta.extraFiles.num += 1;
118 assetDup.meta.extraSources.bytes += sourceGroup.meta.extraSources.bytes;
119 assetDup.meta.extraSources.num += sourceGroup.meta.extraSources.num;
120 // Then, update total meta
121 data.meta.extraFiles.num += 1;
122 data.meta.extraSources.bytes += sourceGroup.meta.extraSources.bytes;
123 data.meta.extraSources.num += sourceGroup.meta.extraSources.num;
124 });
125 });
126 });
127 return data;
128 });
129 }
130 _createTemplate() {
131 return new DuplicatesTemplate({ action: this });
132 }
133}
134class DuplicatesTemplate extends base_1.Template {
135 text() {
136 return Promise.resolve()
137 .then(() => this.action.getData())
138 .then(({ meta, assets }) => {
139 const dupAsset = (name) => chalk `{gray ## \`${name}\`}`;
140 const dupFiles = (name) => Object.keys(assets[name].files)
141 .map((baseName) => {
142 const { files } = assets[name];
143 const base = files[baseName];
144 const inlineMeta = (obj) => `Files ${strings_1.numF(obj.extraFiles.num)}, ` +
145 `Sources ${strings_1.numF(obj.extraSources.num)}, ` +
146 `Bytes ${strings_1.numF(obj.extraSources.bytes)}`;
147 const sources = files[baseName].sources
148 .map((sourceGroup, i) => this.trim(`
149 ${i}. (${inlineMeta(sourceGroup.meta)})
150 ${sourceGroup.modules
151 .map((mod) => `(${mod.size.full}) ${chalk.gray(mod.fileName)}`)
152 .join("\n ")}
153 `, 14))
154 .join("\n ");
155 return this.trim(chalk `
156 * {green ${baseName}}
157 * Meta: ${inlineMeta(base.meta)}
158 ${sources}
159 `, 14);
160 })
161 .join("\n");
162 const duplicates = (name) => `${dupAsset(name)}\n${dupFiles(name)}\n`;
163 const report = this.trim(chalk `
164 {cyan inspectpack --action=duplicates}
165 {gray ===============================}
166
167 {gray ## Summary}
168 * Extra Files (unique): ${strings_1.numF(meta.extraFiles.num)}
169 * Extra Sources (non-unique): ${strings_1.numF(meta.extraSources.num)}
170 * Extra Bytes (non-unique): ${strings_1.numF(meta.extraSources.bytes)}
171
172 ${Object.keys(assets)
173 .filter((name) => Object.keys(assets[name].files).length)
174 .map(duplicates).join("\n")}
175 `, 10);
176 return report;
177 });
178 }
179 tsv() {
180 return Promise.resolve()
181 .then(() => this.action.getData())
182 .then(({ assets }) => ["Asset\tFull Name\tShort Name\tGroup Index\tSize"]
183 .concat(Object.keys(assets)
184 .filter((name) => Object.keys(assets[name].files).length)
185 .map((name) => Object.keys(assets[name].files)
186 .map((baseName) => assets[name].files[baseName].sources
187 .map((sourceGroup, i) => sourceGroup.modules
188 .map((mod) => [
189 name,
190 mod.fileName,
191 mod.baseName,
192 i,
193 mod.size.full,
194 ].join("\t"))
195 .join("\n"))
196 .join("\n"))
197 .join("\n"))
198 .join("\n"))
199 .join("\n"));
200 }
201}
202const create = (opts) => {
203 return new Duplicates(opts);
204};
205exports.create = create;