1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.create = exports._packageName = exports._packageRoots = exports._requireSort = void 0;
|
4 | const chalk = require("chalk");
|
5 | const path_1 = require("path");
|
6 | const semverCompare = require("semver-compare");
|
7 | const dependencies_1 = require("../util/dependencies");
|
8 | const files_1 = require("../util/files");
|
9 | const promise_1 = require("../util/promise");
|
10 | const strings_1 = require("../util/strings");
|
11 | const base_1 = require("./base");
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 | const _requireSort = (vals) => {
|
21 | return vals.sort();
|
22 | };
|
23 | exports._requireSort = _requireSort;
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | const _packageRoots = (mods) => {
|
40 | const depRoots = [];
|
41 | const appRoots = [];
|
42 |
|
43 | mods
|
44 | .filter((mod) => mod.isNodeModules)
|
45 | .forEach((mod) => {
|
46 | const parts = base_1._normalizeWebpackPath(mod.identifier).split(path_1.sep);
|
47 | const nmIndex = parts.indexOf("node_modules");
|
48 | const candidate = parts.slice(0, nmIndex).join(path_1.sep);
|
49 | if (depRoots.indexOf(candidate) === -1) {
|
50 |
|
51 | depRoots.push(candidate);
|
52 | }
|
53 | });
|
54 |
|
55 |
|
56 | if (!depRoots.length) {
|
57 | return Promise.resolve(depRoots);
|
58 | }
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 | mods
|
65 | .filter((mod) => !mod.isNodeModules && !mod.isSynthetic)
|
66 | .forEach((mod) => {
|
67 |
|
68 |
|
69 |
|
70 | let curPath = base_1._normalizeWebpackPath(mod.identifier);
|
71 |
|
72 | const depRootMinLength = depRoots
|
73 | .map((depRoot) => depRoot.length)
|
74 | .reduce((memo, len) => memo > 0 && memo < len ? memo : len, 0);
|
75 |
|
76 |
|
77 | while (curPath = curPath && path_1.dirname(curPath)) {
|
78 |
|
79 |
|
80 | if (depRootMinLength > curPath.length ||
|
81 | depRoots.indexOf(curPath) > -1 ||
|
82 | !depRoots.some((d) => !!curPath && curPath.indexOf(d) === 0)) {
|
83 | curPath = null;
|
84 | }
|
85 | else if (appRoots.indexOf(curPath) === -1) {
|
86 |
|
87 | appRoots.push(curPath);
|
88 | }
|
89 | }
|
90 | });
|
91 |
|
92 |
|
93 |
|
94 | const roots = depRoots.concat(appRoots);
|
95 | return Promise.all(roots.map((rootPath) => files_1.exists(path_1.join(rootPath, "package.json"))))
|
96 | .then((rootExists) => {
|
97 | const foundRoots = roots.filter((_, i) => rootExists[i]);
|
98 | return exports._requireSort(foundRoots);
|
99 | });
|
100 | };
|
101 | exports._packageRoots = _packageRoots;
|
102 |
|
103 | const _packageName = (baseName) => {
|
104 | const base = files_1.toPosixPath(baseName.trim());
|
105 | if (!base) {
|
106 | throw new Error(`No package name was provided`);
|
107 | }
|
108 | const parts = base.split("/");
|
109 | if (parts[0].startsWith("@")) {
|
110 | if (parts.length >= 2) {
|
111 |
|
112 | return [parts[0], parts[1]].join("/");
|
113 | }
|
114 | throw new Error(`${baseName} is scoped, but is missing package name`);
|
115 | }
|
116 | return parts[0];
|
117 | };
|
118 | exports._packageName = _packageName;
|
119 |
|
120 |
|
121 | const allPackages = (mods) => {
|
122 |
|
123 | const pkgs = {};
|
124 | mods
|
125 | .filter((mod) => mod.isNodeModules)
|
126 | .forEach((mod) => {
|
127 |
|
128 |
|
129 | const parts = base_1.nodeModulesParts(mod.identifier)
|
130 |
|
131 | .filter((part, i) => i > 0 && part !== "/" && part !== "node_modules");
|
132 |
|
133 | const lastIdx = parts.length - 1;
|
134 | parts[lastIdx] = exports._packageName(parts[lastIdx]);
|
135 | parts.forEach((pkgName) => {
|
136 | pkgs[pkgName] = true;
|
137 | });
|
138 | });
|
139 |
|
140 | return Object.keys(pkgs).sort(strings_1.sort);
|
141 | };
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 | const modulesByPackageNameByPackagePath = (mods) => {
|
149 |
|
150 | const modsMap = {};
|
151 |
|
152 | mods.forEach((mod) => {
|
153 | if (!mod.isNodeModules) {
|
154 | return;
|
155 | }
|
156 | if (mod.baseName === null) {
|
157 | throw new Error(`Encountered non-node_modules null baseName: ${JSON.stringify(mod)}`);
|
158 | }
|
159 |
|
160 | const pkgName = exports._packageName(mod.baseName);
|
161 | modsMap[pkgName] = modsMap[pkgName] || {};
|
162 |
|
163 | const pkgMap = modsMap[pkgName];
|
164 | const modParts = base_1._normalizeWebpackPath(mod.identifier).split(path_1.sep);
|
165 | const nmIndex = modParts.lastIndexOf("node_modules");
|
166 | const pkgPath = modParts
|
167 |
|
168 | .slice(0, nmIndex + 1)
|
169 |
|
170 | .concat(pkgName.split("/"))
|
171 |
|
172 | .join(path_1.sep);
|
173 | pkgMap[pkgPath] = (pkgMap[pkgPath] || []).concat(mod);
|
174 | });
|
175 |
|
176 | Object.keys(modsMap).forEach((pkgName) => {
|
177 | if (Object.keys(modsMap[pkgName]).length === 1) {
|
178 | delete modsMap[pkgName];
|
179 | }
|
180 | });
|
181 | return modsMap;
|
182 | };
|
183 | const createEmptyMeta = () => ({
|
184 | depended: {
|
185 | num: 0,
|
186 | },
|
187 | files: {
|
188 | num: 0,
|
189 | },
|
190 | installed: {
|
191 | num: 0,
|
192 | },
|
193 | packages: {
|
194 | num: 0,
|
195 | },
|
196 | resolved: {
|
197 | num: 0,
|
198 | },
|
199 | });
|
200 | const createEmptyAsset = () => ({
|
201 | meta: createEmptyMeta(),
|
202 | packages: {},
|
203 | });
|
204 | const createEmptyData = () => ({
|
205 | assets: {},
|
206 | meta: Object.assign(createEmptyMeta(), {
|
207 | commonRoot: null,
|
208 | packageRoots: [],
|
209 | }),
|
210 | });
|
211 |
|
212 | const commonPath = (val1, val2) => {
|
213 |
|
214 | let i = 0;
|
215 | while (i < val1.length && val1.charAt(i) === val2.charAt(i)) {
|
216 | i++;
|
217 | }
|
218 | let candidate = val1.substring(0, i);
|
219 |
|
220 | const parts = candidate.split(path_1.sep);
|
221 | const nmIndex = parts.indexOf("node_modules");
|
222 | if (nmIndex > -1) {
|
223 | candidate = parts.slice(0, nmIndex).join(path_1.sep);
|
224 | }
|
225 | return candidate;
|
226 | };
|
227 | const getAssetData = (commonRoot, allDeps, mods) => {
|
228 |
|
229 | const data = createEmptyAsset();
|
230 | const modsMap = modulesByPackageNameByPackagePath(mods);
|
231 | allDeps.forEach((deps) => {
|
232 |
|
233 | if (deps === null) {
|
234 | return;
|
235 | }
|
236 |
|
237 |
|
238 | const depsToPackageName = dependencies_1.mapDepsToPackageName(deps);
|
239 |
|
240 | Object.keys(modsMap).sort(strings_1.sort).forEach((name) => {
|
241 |
|
242 | const modsToFilePath = modsMap[name] || {};
|
243 | Object.keys(depsToPackageName[name] || {}).sort(semverCompare).forEach((version) => {
|
244 |
|
245 |
|
246 | const depsForPkgVers = depsToPackageName[name][version] || {};
|
247 | Object.keys(depsForPkgVers).sort(strings_1.sort).forEach((filePath) => {
|
248 |
|
249 | const modules = (modsToFilePath[filePath] || []).map((mod) => ({
|
250 | baseName: mod.baseName,
|
251 | fileName: mod.identifier,
|
252 | size: {
|
253 | full: mod.size,
|
254 | },
|
255 | }));
|
256 |
|
257 | if (!modules.length) {
|
258 | return;
|
259 | }
|
260 |
|
261 | const relPath = files_1.toPosixPath(path_1.relative(commonRoot, filePath));
|
262 |
|
263 | data.packages[name] = data.packages[name] || {};
|
264 | const dataVers = data.packages[name][version] = data.packages[name][version] || {};
|
265 | const dataObj = dataVers[relPath] = dataVers[relPath] || {};
|
266 | dataObj.skews = (dataObj.skews || []).concat(depsForPkgVers[filePath].skews);
|
267 | dataObj.modules = dataObj.modules || [];
|
268 |
|
269 |
|
270 |
|
271 | const newMods = modules
|
272 | .filter((newMod) => !dataObj.modules.some((mod) => mod.fileName === newMod.fileName));
|
273 | dataObj.modules = dataObj.modules.concat(newMods);
|
274 | });
|
275 | });
|
276 | });
|
277 | });
|
278 | return data;
|
279 | };
|
280 | class Versions extends base_1.Action {
|
281 | shouldBail() {
|
282 | return this.getData().then((data) => data.meta.packages.num !== 0);
|
283 | }
|
284 | _getData() {
|
285 | const mods = this.modules;
|
286 |
|
287 | const pkgMap = {};
|
288 |
|
289 |
|
290 |
|
291 |
|
292 |
|
293 | return exports._packageRoots(mods).then((pkgRoots) => {
|
294 |
|
295 |
|
296 | if (!pkgRoots.length) {
|
297 | return Promise.resolve(createEmptyData());
|
298 | }
|
299 |
|
300 |
|
301 | const pkgsFilter = allPackages(mods);
|
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 | let allDeps;
|
308 | return promise_1.serial(pkgRoots.map((pkgRoot) => () => dependencies_1.dependencies(pkgRoot, pkgsFilter, pkgMap)))
|
309 |
|
310 | .then((all) => { allDeps = all; })
|
311 |
|
312 | .then(() => Promise.all(allDeps.map((deps) => {
|
313 |
|
314 |
|
315 |
|
316 | if (deps !== null && !deps.dependencies.length) {
|
317 | return Promise.all(pkgRoots.map((pkgRoot) => files_1.exists(path_1.join(pkgRoot, "node_modules"))))
|
318 | .then((pkgRootsExist) => {
|
319 | if (pkgRootsExist.indexOf(true) === -1) {
|
320 | throw new Error(`Found ${mods.length} bundled files in a project ` +
|
321 | `'node_modules' directory, but none found on disk. ` +
|
322 | `Do you need to run 'npm install'?`);
|
323 | }
|
324 | });
|
325 | }
|
326 | return Promise.resolve();
|
327 | })))
|
328 |
|
329 | .then(() => {
|
330 |
|
331 |
|
332 |
|
333 | if (!allDeps.length || allDeps.every((deps) => deps === null)) {
|
334 | return createEmptyData();
|
335 | }
|
336 | const { assets } = this;
|
337 | const assetNames = Object.keys(assets).sort(strings_1.sort);
|
338 |
|
339 |
|
340 | const commonRoot = pkgRoots.reduce((memo, pkgRoot) => commonPath(memo, pkgRoot));
|
341 |
|
342 | const assetsData = {};
|
343 | assetNames.forEach((assetName) => {
|
344 | assetsData[assetName] = getAssetData(commonRoot, allDeps, assets[assetName].mods);
|
345 | });
|
346 | const data = Object.assign(createEmptyData(), {
|
347 | assets: assetsData,
|
348 | });
|
349 |
|
350 | data.meta.packageRoots = pkgRoots;
|
351 | data.meta.commonRoot = commonRoot;
|
352 |
|
353 | assetNames.forEach((assetName) => {
|
354 | const { packages, meta } = data.assets[assetName];
|
355 | Object.keys(packages).forEach((pkgName) => {
|
356 | const pkgVersions = Object.keys(packages[pkgName]);
|
357 | meta.packages.num += 1;
|
358 | meta.resolved.num += pkgVersions.length;
|
359 | data.meta.packages.num += 1;
|
360 | data.meta.resolved.num += pkgVersions.length;
|
361 | pkgVersions.forEach((version) => {
|
362 | const pkgVers = packages[pkgName][version];
|
363 | Object.keys(pkgVers).forEach((filePath) => {
|
364 | meta.files.num += pkgVers[filePath].modules.length;
|
365 | meta.depended.num += pkgVers[filePath].skews.length;
|
366 | meta.installed.num += 1;
|
367 | data.meta.files.num += pkgVers[filePath].modules.length;
|
368 | data.meta.depended.num += pkgVers[filePath].skews.length;
|
369 | data.meta.installed.num += 1;
|
370 | });
|
371 | });
|
372 | });
|
373 | });
|
374 | return data;
|
375 | });
|
376 | });
|
377 | }
|
378 | _createTemplate() {
|
379 | return new VersionsTemplate({ action: this });
|
380 | }
|
381 | }
|
382 |
|
383 | const shortPath = (filePath) => filePath.replace(/node_modules/g, "~");
|
384 |
|
385 | const pkgNamePath = (pkgParts) => pkgParts.reduce((m, part) => `${m}${m ? " -> " : ""}${part.name}@${part.range}`, "");
|
386 | class VersionsTemplate extends base_1.Template {
|
387 | text() {
|
388 | return Promise.resolve()
|
389 | .then(() => this.action.getData())
|
390 | .then(({ meta, assets }) => {
|
391 | const versAsset = (name) => chalk `{gray ## \`${name}\`}`;
|
392 | const versPkgs = (name) => Object.keys(assets[name].packages)
|
393 | .sort(strings_1.sort)
|
394 | .map((pkgName) => this.trim(chalk `
|
395 | * {cyan ${pkgName}}
|
396 | ${Object.keys(assets[name].packages[pkgName])
|
397 | .sort(semverCompare)
|
398 | .map((version) => this.trim(chalk `
|
399 | * {gray ${version}}
|
400 | ${Object.keys(assets[name].packages[pkgName][version])
|
401 | .sort(strings_1.sort)
|
402 | .map((filePath) => {
|
403 | const { skews, modules, } = assets[name].packages[pkgName][version][filePath];
|
404 | return this.trim(chalk `
|
405 | * {green ${shortPath(filePath)}}
|
406 | * Num deps: ${strings_1.numF(skews.length)}, files: ${strings_1.numF(modules.length)}
|
407 | ${skews
|
408 | .map((pkgParts) => pkgParts.map((part, i) => Object.assign({}, part, {
|
409 | name: chalk[i < pkgParts.length - 1 ? "gray" : "cyan"](part.name),
|
410 | })))
|
411 | .map(pkgNamePath)
|
412 | .sort(strings_1.sort)
|
413 | .map((pkgStr) => this.trim(`
|
414 | * ${pkgStr}
|
415 | `, 24))
|
416 | .join("\n ")}
|
417 | `, 20);
|
418 | })
|
419 | .join("\n ")}
|
420 | `, 16))
|
421 | .join("\n ")}
|
422 | `, 12))
|
423 | .join("\n");
|
424 | const versions = (name) => `${versAsset(name)}\n${versPkgs(name)}\n`;
|
425 | const report = this.trim(chalk `
|
426 | {cyan inspectpack --action=versions}
|
427 | {gray =============================}
|
428 |
|
429 | {gray ## Summary}
|
430 | * Packages with skews: ${strings_1.numF(meta.packages.num)}
|
431 | * Total resolved versions: ${strings_1.numF(meta.resolved.num)}
|
432 | * Total installed packages: ${strings_1.numF(meta.installed.num)}
|
433 | * Total depended packages: ${strings_1.numF(meta.depended.num)}
|
434 | * Total bundled files: ${strings_1.numF(meta.files.num)}
|
435 |
|
436 | ${Object.keys(assets)
|
437 | .filter((name) => Object.keys(assets[name].packages).length)
|
438 | .map(versions)
|
439 | .join("\n")}
|
440 | `, 10);
|
441 | return report;
|
442 | });
|
443 | }
|
444 | tsv() {
|
445 | return Promise.resolve()
|
446 | .then(() => this.action.getData())
|
447 | .then(({ assets }) => ["Asset\tPackage\tVersion\tInstalled Path\tDependency Path"]
|
448 | .concat(Object.keys(assets)
|
449 | .filter((name) => Object.keys(assets[name].packages).length)
|
450 | .map((name) => Object.keys(assets[name].packages)
|
451 | .sort(strings_1.sort)
|
452 | .map((pkgName) => Object.keys(assets[name].packages[pkgName])
|
453 | .sort(semverCompare)
|
454 | .map((version) => Object.keys(assets[name].packages[pkgName][version])
|
455 | .sort(strings_1.sort)
|
456 | .map((filePath) => assets[name].packages[pkgName][version][filePath].skews
|
457 | .map(pkgNamePath)
|
458 | .sort(strings_1.sort)
|
459 | .map((pkgStr) => [
|
460 | name,
|
461 | pkgName,
|
462 | version,
|
463 | shortPath(filePath),
|
464 | pkgStr,
|
465 | ].join("\t"))
|
466 | .join("\n"))
|
467 | .join("\n"))
|
468 | .join("\n"))
|
469 | .join("\n"))
|
470 | .join("\n"))
|
471 | .join("\n"));
|
472 | }
|
473 | }
|
474 | const create = (opts) => {
|
475 | return new Versions(opts);
|
476 | };
|
477 | exports.create = create;
|