qleaner 1.2.0 → 1.3.0

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.
@@ -11,13 +11,15 @@ const {
11
11
 
12
12
  async function init(chalk) {
13
13
  const responses = await prompts(QUESTIONS);
14
- const ISROOT = responses.alias ? false : true;
15
- const ALIAS = responses.alias ? true : false;
14
+ const ISROOT = responses.alias === "relative" ? true : false;
15
+ const ALIAS = responses.alias === "public" ? true : false;
16
16
  const configFile = path.join(process.cwd(), "qleaner.config.json");
17
17
  fs.writeFileSync(
18
18
  configFile,
19
19
  JSON.stringify(
20
20
  {
21
+ codeAlias: responses.codeAlias,
22
+ paths: {},
21
23
  packageManager: responses.packageManager || "npm",
22
24
  excludeDir: DIRECTORIES_TO_EXCLUDE, // Exclude directories from the scan
23
25
  excludeFile: ["payload-types.ts", "payload-types.js"], // Exclude files from the scan
@@ -1,6 +1,7 @@
1
- const { unUsedFiles, hydrateGraph } = require("../command");
1
+ const { unUsedFiles } = require("../command");
2
+ const { hydrateGraph } = require("../utils/graphUtils");
2
3
  const Table = require('cli-table3');
3
- const { askDeleteFiles } = require("../utils/utils");
4
+ const { askDeleteFiles, loadTSConfig } = require("../utils/utils");
4
5
  const fs = require('fs');
5
6
  const path = require('path');
6
7
  const { summarizeAll, getTop10LargestFiles, dependenciesSummary, formatFilePath } = require("./summary");
@@ -17,7 +18,7 @@ const { query, unusedDependencies: findUnusedDeps } = require("./query");
17
18
  let cache;
18
19
  try {
19
20
  cache = JSON.parse(fs.readFileSync(cachePath, "utf8"));
20
- } catch (error) {
21
+ } catch {
21
22
  console.log(chalk.red('⚠️ Error reading cache file. Please run "qleaner scan" again.'));
22
23
  return;
23
24
  }
@@ -77,7 +78,7 @@ async function unusedDependencies(chalk, directoryPath = process.cwd(), options
77
78
  let packageJson;
78
79
  try {
79
80
  packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
80
- } catch (error) {
81
+ } catch {
81
82
  console.log(chalk.red('⚠️ Error reading package.json file.'));
82
83
  return;
83
84
  }
@@ -99,7 +100,7 @@ async function unusedDependencies(chalk, directoryPath = process.cwd(), options
99
100
  let cache;
100
101
  try {
101
102
  cache = JSON.parse(fs.readFileSync(cachePath, "utf8"));
102
- } catch (error) {
103
+ } catch {
103
104
  console.log(chalk.red('⚠️ Error reading cache file. Please run "qleaner scan" again.'));
104
105
  return;
105
106
  }
@@ -155,7 +156,9 @@ async function summary(chalk, options) {
155
156
  }
156
157
  }
157
158
 
158
- async function scan(ora, chalk, filePath, options) {
159
+
160
+ async function scan(ora, chalk, pathToScan, options) {
161
+ let pathConfig = {};
159
162
  // check if qleaner.config.json exists
160
163
  if (fs.existsSync(path.join(process.cwd(), "qleaner.config.json"))) {
161
164
  const config = JSON.parse(
@@ -165,9 +168,20 @@ async function scan(ora, chalk, filePath, options) {
165
168
  ...config,
166
169
  ...options,
167
170
  };
171
+ // config file will take precedence over the tsconfig.json file
172
+ if(config.paths && Object.entries(config.paths).length > 0){
173
+ pathConfig = config.paths;
174
+ }else if(config.codeAlias){
175
+ pathConfig = loadTSConfig(pathToScan, config.codeAlias).paths;
176
+ if(!pathConfig){
177
+ console.log(chalk.red('⚠️ Error reading config file. Please run "qleaner scan" again.'));
178
+ pathConfig = {}
179
+ }
180
+ }
168
181
  }
182
+
169
183
  // read qleaner.config.json
170
- const unusedFiles = await unUsedFiles(ora, chalk,filePath, options);
184
+ const unusedFiles = await unUsedFiles(ora, chalk,pathToScan, pathConfig, options);
171
185
 
172
186
  let totalSize = 0;
173
187
  const fileArray = Array.from(unusedFiles.values());
@@ -1,7 +1,7 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
3
  const Table = require("cli-table3");
4
- const { hydrateGraph } = require("../command");
4
+ const { hydrateGraph } = require("../utils/graphUtils");
5
5
  const {
6
6
  getDeadLinks,
7
7
  getTotalImageSize,
@@ -13,8 +13,9 @@ const {
13
13
  getTop10FilesWithLightDependencies,
14
14
  getTop10FilesHotspots,
15
15
  getTotalImageFiles,
16
- getTop10FilesCodeDependencies,
17
16
  getTop10FilesDependenciesHotspots,
17
+ getTop10FilesWithMostReexports,
18
+ getTop10FilesWithMostReexportedBy,
18
19
  } = require("../utils/summary");
19
20
 
20
21
  function formatFilePath(filePath, maxLength = 70) {
@@ -29,7 +30,7 @@ function formatFilePath(filePath, maxLength = 70) {
29
30
  relativePath = filePath; // Use absolute if outside project
30
31
  }
31
32
  }
32
- } catch (e) {
33
+ } catch {
33
34
  // If conversion fails, use original path
34
35
  }
35
36
 
@@ -196,7 +197,7 @@ function getTop10LargestFiles(chalk) {
196
197
  colWidths: [90, 15],
197
198
  style: { head: [], border: [] },
198
199
  });
199
- top10CodeFiles.forEach((file, index) => {
200
+ top10CodeFiles.forEach((file) => {
200
201
  codeTable.push([
201
202
  chalk.white(formatFilePath(file[0], 85)),
202
203
  chalk.magenta((file[1] / 1024).toFixed(2) + " KB"),
@@ -279,10 +280,146 @@ function getTop10LargestFiles(chalk) {
279
280
  );
280
281
  }
281
282
 
282
- function dependenciesSummary(chalk) {
283
- const { codeGraph, imageGraph } = readCacheAndHydrateGraph();
283
+ function printSectionHeader(chalk, title) {
284
+ console.log(chalk.green.bold(`\n${title}`));
285
+ console.log(chalk.green("════════════════════════════════════════════════"));
286
+ }
284
287
 
285
- // Check if data is available
288
+ function printSectionFooter(chalk) {
289
+ console.log(chalk.green("════════════════════════════════════════════════"));
290
+ }
291
+
292
+ function displayTableSection(chalk, data, config) {
293
+ const { title, emptyMessage, headers, colWidths, getRowData, emptyMessageColor } = config;
294
+
295
+ printSectionHeader(chalk, title);
296
+
297
+ if (!data || data.length === 0) {
298
+ const colorFn = emptyMessageColor || chalk.yellow;
299
+ console.log(colorFn(` ${emptyMessage}`));
300
+ } else {
301
+ const table = new Table({
302
+ head: headers.map(h => chalk.cyan(h)),
303
+ colWidths,
304
+ style: { head: [], border: [] },
305
+ });
306
+
307
+ data.forEach((item) => {
308
+ table.push(getRowData(item));
309
+ });
310
+
311
+ console.log(table.toString());
312
+ }
313
+
314
+ printSectionFooter(chalk);
315
+ }
316
+
317
+ function displayHeavyDependencies(chalk, data) {
318
+ displayTableSection(chalk, data, {
319
+ title: "🔴 Top 10 Files with Heavy Dependencies",
320
+ emptyMessage: "No files with heavy dependencies found.",
321
+ headers: ["File Path", "Imports"],
322
+ colWidths: [90, 12],
323
+ getRowData: ([filePath, node]) => [
324
+ chalk.white(formatFilePath(filePath, 85)),
325
+ chalk.red((node.imports?.size || 0).toString()),
326
+ ],
327
+ });
328
+ }
329
+
330
+ function displayLightDependencies(chalk, data) {
331
+ displayTableSection(chalk, data, {
332
+ title: "🟢 Top 10 Files with Light Dependencies",
333
+ emptyMessage: "No files with light dependencies found.",
334
+ headers: ["File Path", "Imports"],
335
+ colWidths: [90, 12],
336
+ getRowData: ([filePath, node]) => [
337
+ chalk.white(formatFilePath(filePath, 85)),
338
+ chalk.green((node.imports?.size || 0).toString()),
339
+ ],
340
+ });
341
+ }
342
+
343
+ function displayFileHotspots(chalk, data) {
344
+ displayTableSection(chalk, data, {
345
+ title: "🔥 Top 10 File Hotspots (Most Imported)",
346
+ emptyMessage: "No file hotspots found.",
347
+ headers: ["File Path", "Imported By"],
348
+ colWidths: [90, 15],
349
+ getRowData: ([filePath, node]) => [
350
+ chalk.white(formatFilePath(filePath, 85)),
351
+ chalk.magenta((node.importedBy?.size || 0).toString()),
352
+ ],
353
+ });
354
+ }
355
+
356
+ function displayDependencyHotspots(chalk, data) {
357
+ displayTableSection(chalk, data, {
358
+ title: "📦 Top 10 Files Dependencies Hotspots",
359
+ emptyMessage: "No dependency hotspots found.",
360
+ headers: ["File Path", "Dependencies"],
361
+ colWidths: [90, 15],
362
+ getRowData: ([filePath, node]) => [
363
+ chalk.white(formatFilePath(filePath, 85)),
364
+ chalk.yellow((node.importedBy?.size || 0).toString()),
365
+ ],
366
+ });
367
+ }
368
+
369
+ function displayReexports(chalk, data) {
370
+ displayTableSection(chalk, data, {
371
+ title: "📤 Top 10 Files that Export the Most Files",
372
+ emptyMessage: "No files with re-exports found.",
373
+ headers: ["File Path", "Re-exports"],
374
+ colWidths: [90, 15],
375
+ getRowData: ([filePath, node]) => [
376
+ chalk.white(formatFilePath(filePath, 85)),
377
+ chalk.blue((node.reExported?.size || 0).toString()),
378
+ ],
379
+ });
380
+ }
381
+
382
+ function displayReexportedBy(chalk, data) {
383
+ displayTableSection(chalk, data, {
384
+ title: "📥 Top 10 Files that Have Been Exported Most",
385
+ emptyMessage: "No files re-exported by others found.",
386
+ headers: ["File Path", "Re-exported By"],
387
+ colWidths: [90, 15],
388
+ getRowData: ([filePath, node]) => [
389
+ chalk.white(formatFilePath(filePath, 85)),
390
+ chalk.cyan((node.reExportedBy?.size || 0).toString()),
391
+ ],
392
+ });
393
+ }
394
+
395
+ function displayDeadImageHotspots(chalk, data) {
396
+ displayTableSection(chalk, data, {
397
+ title: "🖼️ Top 10 Dead Image Hotspots",
398
+ emptyMessage: "✓ No dead image hotspots found.",
399
+ headers: ["File Path", "Referenced By"],
400
+ colWidths: [90, 15],
401
+ getRowData: ([filePath, node]) => [
402
+ chalk.white(formatFilePath(filePath, 85)),
403
+ chalk.red((node.importedBy?.size || 0).toString()),
404
+ ],
405
+ emptyMessageColor: chalk.green,
406
+ });
407
+ }
408
+
409
+ function displayAliveImageHotspots(chalk, data) {
410
+ displayTableSection(chalk, data, {
411
+ title: "✅ Top 10 Alive Image Hotspots",
412
+ emptyMessage: "No alive image hotspots found.",
413
+ headers: ["File Path", "Referenced By"],
414
+ colWidths: [90, 15],
415
+ getRowData: ([filePath, node]) => [
416
+ chalk.white(formatFilePath(filePath, 85)),
417
+ chalk.green((node.importedBy?.size || 0).toString()),
418
+ ],
419
+ });
420
+ }
421
+
422
+ function checkDataAvailability(chalk, codeGraph, imageGraph) {
286
423
  const hasData = codeGraph.graph.size > 0 || imageGraph.graph.size > 0;
287
424
  if (!hasData) {
288
425
  console.log(chalk.yellow.bold("\n⚠️ No Project Data Found"));
@@ -293,9 +430,12 @@ function dependenciesSummary(chalk) {
293
430
  console.log(
294
431
  chalk.yellow(" Use the scan command to generate project data.\n")
295
432
  );
296
- return;
433
+ return false;
297
434
  }
435
+ return true;
436
+ }
298
437
 
438
+ function collectDependencyData(codeGraph, imageGraph) {
299
439
  const top10FilesWithHeavyDependencies = getTop10FilesWithHeavyDependencies(
300
440
  codeGraph.graph
301
441
  );
@@ -304,153 +444,46 @@ function dependenciesSummary(chalk) {
304
444
  );
305
445
  const top10FilesHotspots = getTop10FilesDependenciesHotspots(codeGraph.graph, false);
306
446
  const top10FilesDependenciesHotspots = getTop10FilesDependenciesHotspots(codeGraph.graph);
307
- // image graph
447
+ const top10FilesWithMostReexports = getTop10FilesWithMostReexports(codeGraph.graph);
448
+ const top10FilesWithMostReexportedBy = getTop10FilesWithMostReexportedBy(codeGraph.graph);
449
+
308
450
  const { deadLinks, aliveLinks } = getDeadLinks(imageGraph.graph);
309
451
  const top10FilesHotspotsDeadImage = getTop10FilesHotspots(deadLinks);
310
452
  const top10FilesHotspotsAliveImage = getTop10FilesHotspots(aliveLinks, false);
311
453
 
312
- console.log(chalk.green.bold("\n📊 Dependencies Summary"));
313
- console.log(chalk.green("════════════════════════════════════════════════"));
454
+ return {
455
+ top10FilesWithHeavyDependencies,
456
+ top10FilesWithLightDependencies,
457
+ top10FilesHotspots,
458
+ top10FilesDependenciesHotspots,
459
+ top10FilesWithMostReexports,
460
+ top10FilesWithMostReexportedBy,
461
+ top10FilesHotspotsDeadImage,
462
+ top10FilesHotspotsAliveImage,
463
+ };
464
+ }
314
465
 
315
- // Top 10 Files with Heavy Dependencies
316
- console.log(chalk.green.bold("\n🔴 Top 10 Files with Heavy Dependencies"));
317
- console.log(chalk.green("════════════════════════════════════════════════"));
318
- if (
319
- !top10FilesWithHeavyDependencies ||
320
- top10FilesWithHeavyDependencies.length === 0
321
- ) {
322
- console.log(chalk.yellow(" No files with heavy dependencies found."));
323
- } else {
324
- const heavyDepsTable = new Table({
325
- head: [chalk.cyan("File Path"), chalk.cyan("Imports")],
326
- colWidths: [90, 12],
327
- style: { head: [], border: [] },
328
- });
329
- top10FilesWithHeavyDependencies.forEach(([filePath, node]) => {
330
- heavyDepsTable.push([
331
- chalk.white(formatFilePath(filePath, 85)),
332
- chalk.red((node.imports?.size || 0).toString()),
333
- ]);
334
- });
335
- console.log(heavyDepsTable.toString());
336
- }
337
- console.log(chalk.green("════════════════════════════════════════════════"));
466
+ function dependenciesSummary(chalk) {
467
+ const { codeGraph, imageGraph } = readCacheAndHydrateGraph();
338
468
 
339
- // Top 10 Files with Light Dependencies
340
- console.log(chalk.green.bold("\n🟢 Top 10 Files with Light Dependencies"));
341
- console.log(chalk.green("════════════════════════════════════════════════"));
342
- if (
343
- !top10FilesWithLightDependencies ||
344
- top10FilesWithLightDependencies.length === 0
345
- ) {
346
- console.log(chalk.yellow(" No files with light dependencies found."));
347
- } else {
348
- const lightDepsTable = new Table({
349
- head: [chalk.cyan("File Path"), chalk.cyan("Imports")],
350
- colWidths: [90, 12],
351
- style: { head: [], border: [] },
352
- });
353
- top10FilesWithLightDependencies.forEach(([filePath, node]) => {
354
- lightDepsTable.push([
355
- chalk.white(formatFilePath(filePath, 85)),
356
- chalk.green((node.imports?.size || 0).toString()),
357
- ]);
358
- });
359
- console.log(lightDepsTable.toString());
469
+ if (!checkDataAvailability(chalk, codeGraph, imageGraph)) {
470
+ return;
360
471
  }
361
- console.log(chalk.green("════════════════════════════════════════════════"));
362
472
 
363
- // Top 10 Files Hotspots (Most Imported Files)
364
- console.log(chalk.green.bold("\n🔥 Top 10 File Hotspots (Most Imported)"));
365
- console.log(chalk.green("════════════════════════════════════════════════"));
366
- if (!top10FilesHotspots || top10FilesHotspots.length === 0) {
367
- console.log(chalk.yellow(" No file hotspots found."));
368
- } else {
369
- const hotspotsTable = new Table({
370
- head: [chalk.cyan("File Path"), chalk.cyan("Imported By")],
371
- colWidths: [90, 15],
372
- style: { head: [], border: [] },
373
- });
374
- top10FilesHotspots.forEach(([filePath, node]) => {
375
- hotspotsTable.push([
376
- chalk.white(formatFilePath(filePath, 85)),
377
- chalk.magenta((node.importedBy?.size || 0).toString()),
378
- ]);
379
- });
380
- console.log(hotspotsTable.toString());
381
- }
382
- console.log(chalk.green("════════════════════════════════════════════════"));
473
+ const data = collectDependencyData(codeGraph, imageGraph);
383
474
 
384
- // Top 10 Files Dependencies Hotspots
385
- console.log(chalk.green.bold("\n📦 Top 10 Files Dependencies Hotspots"));
386
- console.log(chalk.green("════════════════════════════════════════════════"));
387
- if (
388
- !top10FilesDependenciesHotspots ||
389
- top10FilesDependenciesHotspots.length === 0
390
- ) {
391
- console.log(chalk.yellow(" No dependency hotspots found."));
392
- } else {
393
- const dependenciesHotspotsTable = new Table({
394
- head: [chalk.cyan("File Path"), chalk.cyan("Dependencies")],
395
- colWidths: [90, 15],
396
- style: { head: [], border: [] },
397
- });
398
- top10FilesDependenciesHotspots.forEach(([filePath, node]) => {
399
- dependenciesHotspotsTable.push([
400
- chalk.white(formatFilePath(filePath, 85)),
401
- chalk.yellow((node.importedBy?.size || 0).toString()),
402
- ]);
403
- });
404
- console.log(dependenciesHotspotsTable.toString());
405
- }
475
+ console.log(chalk.green.bold("\n📊 Dependencies Summary"));
406
476
  console.log(chalk.green("════════════════════════════════════════════════"));
407
477
 
408
- // Image Hotspots - Dead Links
409
- console.log(chalk.green.bold("\n🖼️ Top 10 Dead Image Hotspots"));
410
- console.log(chalk.green("════════════════════════════════════════════════"));
411
- if (
412
- !top10FilesHotspotsDeadImage ||
413
- top10FilesHotspotsDeadImage.length === 0
414
- ) {
415
- console.log(chalk.green(" ✓ No dead image hotspots found."));
416
- } else {
417
- const deadImageHotspotsTable = new Table({
418
- head: [chalk.cyan("File Path"), chalk.cyan("Referenced By")],
419
- colWidths: [90, 15],
420
- style: { head: [], border: [] },
421
- });
422
- top10FilesHotspotsDeadImage.forEach(([filePath, node]) => {
423
- deadImageHotspotsTable.push([
424
- chalk.white(formatFilePath(filePath, 85)),
425
- chalk.red((node.importedBy?.size || 0).toString()),
426
- ]);
427
- });
428
- console.log(deadImageHotspotsTable.toString());
429
- }
430
- console.log(chalk.green("════════════════════════════════════════════════"));
478
+ displayHeavyDependencies(chalk, data.top10FilesWithHeavyDependencies);
479
+ displayLightDependencies(chalk, data.top10FilesWithLightDependencies);
480
+ displayFileHotspots(chalk, data.top10FilesHotspots);
481
+ displayDependencyHotspots(chalk, data.top10FilesDependenciesHotspots);
482
+ displayReexports(chalk, data.top10FilesWithMostReexports);
483
+ displayReexportedBy(chalk, data.top10FilesWithMostReexportedBy);
484
+ displayDeadImageHotspots(chalk, data.top10FilesHotspotsDeadImage);
485
+ displayAliveImageHotspots(chalk, data.top10FilesHotspotsAliveImage);
431
486
 
432
- // Image Hotspots - Alive Links
433
- console.log(chalk.green.bold("\n✅ Top 10 Alive Image Hotspots"));
434
- console.log(chalk.green("════════════════════════════════════════════════"));
435
- if (
436
- !top10FilesHotspotsAliveImage ||
437
- top10FilesHotspotsAliveImage.length === 0
438
- ) {
439
- console.log(chalk.yellow(" No alive image hotspots found."));
440
- } else {
441
- const aliveImageHotspotsTable = new Table({
442
- head: [chalk.cyan("File Path"), chalk.cyan("Referenced By")],
443
- colWidths: [90, 15],
444
- style: { head: [], border: [] },
445
- });
446
- top10FilesHotspotsAliveImage.forEach(([filePath, node]) => {
447
- aliveImageHotspotsTable.push([
448
- chalk.white(formatFilePath(filePath, 85)),
449
- chalk.green((node.importedBy?.size || 0).toString()),
450
- ]);
451
- });
452
- console.log(aliveImageHotspotsTable.toString());
453
- }
454
487
  console.log(
455
488
  chalk.green("════════════════════════════════════════════════\n")
456
489
  );
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "qleaner",
3
3
  "description": "A CLI tool for analyzing and identifying unused files, images, imports, and dead code in React, TypeScript, and JavaScript projects to help optimize bundle size and maintain clean codebases",
4
- "version": "1.2.0",
4
+ "version": "1.3.0",
5
5
  "license": "MIT",
6
6
  "main": "command.js",
7
7
  "bin": "bin/cli.js",
@@ -60,7 +60,7 @@
60
60
  "@types/node": "^20.10.5",
61
61
  "eslint": "^9.39.2",
62
62
  "globals": "^16.5.0",
63
- "typescript": "^5.3.3"
63
+ "typescript": "^5.9.3"
64
64
  },
65
65
  "dependencies": {
66
66
  "@babel/parser": "^7.28.5",
@@ -75,5 +75,3 @@
75
75
  "prompts": "^2.4.2"
76
76
  }
77
77
  }
78
-
79
-
@@ -1,4 +1,6 @@
1
1
  {
2
+ "codeAlias": null,
3
+ "paths": {},
2
4
  "packageManager": "yarn",
3
5
  "excludeDir": [
4
6
  "node_modules",
@@ -29,7 +31,20 @@
29
31
  "test.tsx",
30
32
  "test.ts",
31
33
  "test.js",
32
- "test.jsx"
34
+ "test.jsx",
35
+ "spec.tsx",
36
+ "spec.ts",
37
+ "spec.js",
38
+ "spec.jsx",
39
+ "d.ts",
40
+ "config.js",
41
+ "config.cjs",
42
+ "config.mjs",
43
+ "config.ts",
44
+ "setupTests.ts",
45
+ "setupTests.js",
46
+ "setup.ts",
47
+ "setup.js"
33
48
  ],
34
49
  "excludeFilePrint": [
35
50
  "page.tsx",
@@ -53,7 +68,16 @@
53
68
  "_document.tsx",
54
69
  "_document.jsx",
55
70
  "_error.tsx",
56
- "_error.jsx"
71
+ "_error.jsx",
72
+ "robots.ts",
73
+ "robots.js",
74
+ "sitemap.ts",
75
+ "sitemap.js",
76
+ "manifest.ts",
77
+ "manifest.js",
78
+ "robots.txt",
79
+ "not-found.tsx",
80
+ "not-found.jsx"
57
81
  ],
58
82
  "excludeDirAssets": [],
59
83
  "excludeFileAssets": [],
package/test.js ADDED
@@ -0,0 +1,2 @@
1
+ const fs = require('fs');
2
+ console.log(fs.existsSync('src/afxm-iatf-frontent'));