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.
@@ -1,389 +1,89 @@
1
1
  const fg = require("fast-glob");
2
2
  const fs = require("fs");
3
3
  const path = require("path");
4
- const parser = require("@babel/parser");
4
+ const { parseCode } = require("../utils/astParser");
5
5
  const traverse = require("@babel/traverse").default;
6
- const Table = require("cli-table3");
7
6
  const { getCssImages } = require("../utils/cssImages");
8
- const { askDeleteFiles, createStepBar } = require("../utils/utils");
7
+ const { createStepBar } = require("../utils/utils");
9
8
  const { initializeCache } = require("../command");
10
9
  const { needsRebuild, getFileHash, saveCache } = require("../utils/cache");
11
10
  const { normalize } = require("../utils/resolver");
12
- const { URL_EXTRACT_REGEX, IMAGE_REGEX } = require("../utils/constants");
13
- const { formatFilePath } = require("./summary");
11
+ const { buildImagePaths, buildCodePaths } = require("../utils/imagePathBuilder");
12
+ const { createASTTraverser } = require("../utils/imageAstParser");
13
+ const { addToImageGraph } = require("../utils/imageGraphUtils");
14
+ const { displayUnusedImages, handleImageDeletion } = require("../utils/imageDisplay");
14
15
 
15
16
  /**
16
- * Extracts all strings in a tagged template literal (styled-components, css``)
17
+ * Creates a file node in the image graph for a code file
18
+ * @param {string} filePath - The file path
19
+ * @param {Map} imageGraph - The image graph Map
17
20
  */
18
- function extractFromTemplateLiteral(quasis) {
19
- const results = [];
20
- quasis.forEach((q) => {
21
- const matches = [...q.value.raw.matchAll(URL_EXTRACT_REGEX)];
22
- for (const m of matches) {
23
- results.push(m[2]);
24
- }
25
- if (IMAGE_REGEX.test(q.value.raw)) {
26
- results.push(q.value.raw);
27
- }
21
+ function createCodeFileNode(filePath, imageGraph) {
22
+ imageGraph.set(filePath, {
23
+ file: filePath,
24
+ size: fs.statSync(filePath).size,
25
+ hash: getFileHash(fs.readFileSync(filePath, "utf8")),
26
+ imports: new Set(),
27
+ importedBy: new Set(),
28
+ isImage: false,
29
+ lastModified: fs.statSync(filePath).mtime.getTime(),
28
30
  });
29
- return results;
30
31
  }
31
32
 
32
33
  /**
33
- * Extract images from JSX style object: style={{ backgroundImage: "url('/img/a.png')" }}
34
+ * Processes a single import and adds it to the image graph
35
+ * @param {Object} importInfo - Import information object
36
+ * @param {string} imageDirectory - The base image directory
37
+ * @param {Map} imageGraph - The image graph Map
38
+ * @param {Object} options - Options object
34
39
  */
35
- function extractFromJSXStyle(node, file, imports) {
36
- if (!node || node.type !== "JSXExpressionContainer") return;
37
-
38
- const expr = node.expression;
39
- if (!expr || expr.type !== "ObjectExpression") return;
40
-
41
- expr.properties.forEach((prop) => {
42
- if (prop.type !== "ObjectProperty" || !prop.key || !prop.value) return;
43
-
44
- const keyName = prop.key.name || prop.key.value;
45
-
46
- if (!keyName) return;
47
-
48
- const isImageField =
49
- keyName.toLowerCase().includes("background") ||
50
- keyName.toLowerCase().includes("image") ||
51
- keyName.toLowerCase().includes("mask");
52
-
53
- if (!isImageField) return;
54
-
55
- // String literal
56
- if (prop.value.type === "StringLiteral") {
57
- const raw = prop.value.value;
58
- const matches = [...raw.matchAll(URL_EXTRACT_REGEX)];
59
- if (matches.length > 0) {
60
- matches.forEach((m) => {
61
- imports.push({
62
- file: file,
63
- source: m[2],
64
- });
65
- });
66
- } else if (IMAGE_REGEX.test(raw)) {
67
- imports.push({
68
- file: file,
69
- source: raw,
70
- });
71
- }
72
- }
40
+ function processImageImport(importInfo, imageDirectory, imageGraph, options) {
41
+ const filePath = path.resolve(importInfo.file);
42
+ let importPath = normalize(importInfo.source, imageDirectory, {
43
+ alias: options.alias ? true : false,
44
+ isRootFolderReferenced: options.isRootFolderReferenced ? true : false,
45
+ });
73
46
 
74
- // Template literal
75
- if (prop.value.type === "TemplateLiteral") {
76
- extractFromTemplateLiteral(prop.value.quasis).forEach((v) => {
77
- imports.push({
78
- file: file,
79
- source: v,
80
- });
47
+ if (importPath && !fs.existsSync(importPath)) {
48
+ if (options.alias) {
49
+ importPath = normalize(importInfo.source, imageDirectory, {
50
+ alias: false,
51
+ isRootFolderReferenced: true,
52
+ });
53
+ } else {
54
+ importPath = normalize(importInfo.source, imageDirectory, {
55
+ alias: true,
56
+ isRootFolderReferenced: false,
81
57
  });
82
58
  }
83
- });
84
- }
85
-
86
- /**
87
- * Build glob patterns for image files with exclusions
88
- */
89
- function buildImagePaths(imageDirectory, options) {
90
- const imagePaths = [`${imageDirectory}/**/*.{png,jpg,jpeg,svg,gif,webp}`];
91
-
92
- if (options.excludeDirAssets && options.excludeDirAssets.length > 0) {
93
- options.excludeDirAssets.forEach((dir) => {
94
- imagePaths.push(`!${dir}/**`);
95
- });
96
- }
97
- if (options.excludeFileAssets && options.excludeFileAssets.length > 0) {
98
- options.excludeFileAssets.forEach((file) => {
99
- imagePaths.push(`!${imageDirectory}/**/${file}`);
100
- });
101
- }
102
-
103
- return imagePaths;
104
- }
105
-
106
- /**
107
- * Build glob patterns for code files with exclusions
108
- */
109
- function buildCodePaths(codeDirectory, options) {
110
- const codePaths = [`${codeDirectory}/**/*.{js,jsx,ts,tsx}`];
111
-
112
- if (options.excludeDirCode && options.excludeDirCode.length > 0) {
113
- options.excludeDirCode.forEach((dir) => {
114
- codePaths.push(`!${dir}/**`);
115
- });
59
+ addToImageGraph(importPath, imageGraph);
60
+ } else {
61
+ addToImageGraph(importPath, imageGraph);
116
62
  }
117
- if (options.excludeFileCode && options.excludeFileCode.length > 0) {
118
- options.excludeFileCode.forEach((file) => {
119
- codePaths.push(`!${codeDirectory}/**/${file}`);
120
- });
63
+ if (importPath) {
64
+ imageGraph.get(importPath).importedBy.add(filePath);
65
+ imageGraph.get(filePath).imports.add(importPath);
121
66
  }
122
-
123
- return codePaths;
124
67
  }
125
68
 
126
69
  /**
127
- * Create AST traverser with handlers for extracting image references
70
+ * Processes all collected image imports
71
+ * @param {Array} imports - Array of import information objects
72
+ * @param {Function} createStepBar - Function to create progress bar
73
+ * @param {string} imageDirectory - The base image directory
74
+ * @param {Map} imageGraph - The image graph Map
75
+ * @param {Object} options - Options object
76
+ * @param {Object} chalk - Chalk instance for colored output
128
77
  */
129
- function createASTTraverser(file, imports) {
130
- return {
131
- /**
132
- * import logo from './img/a.png'
133
- */
134
- ImportDeclaration(pathNode) {
135
- const val = pathNode.node.source.value;
136
- if (IMAGE_REGEX.test(val)) {
137
- imports.push({
138
- file: file,
139
- source: val,
140
- });
141
- }
142
- },
143
-
144
- /**
145
- * require('./img/a.png')
146
- */
147
- CallExpression(pathNode) {
148
- const callee = pathNode.node.callee;
149
- const args = pathNode.node.arguments;
150
-
151
- if (
152
- callee.type === "Identifier" &&
153
- callee.name === "require" &&
154
- args.length &&
155
- args[0].type === "StringLiteral"
156
- ) {
157
- const val = args[0].value;
158
- if (IMAGE_REGEX.test(val)) {
159
- imports.push({
160
- file: file,
161
- source: val,
162
- });
163
- }
164
- }
165
-
166
- // dynamic import("./a.png")
167
- if (
168
- callee.type === "Import" &&
169
- args.length &&
170
- args[0].type === "StringLiteral"
171
- ) {
172
- const val = args[0].value;
173
- if (IMAGE_REGEX.test(val)) {
174
- imports.push({
175
- file: file,
176
- source: val,
177
- });
178
- }
179
- }
180
- },
181
-
182
- /**
183
- * <img src="...">
184
- */
185
- JSXAttribute(attr) {
186
- if (attr.node.name.name === "src") {
187
- const v = attr.node.value;
188
- if (!v) return;
189
-
190
- if (v.type === "StringLiteral") {
191
- if (IMAGE_REGEX.test(v.value)) {
192
- imports.push({
193
- file: file,
194
- source: v.value,
195
- });
196
- }
197
- }
198
-
199
- if (v.type === "JSXExpressionContainer") {
200
- const expr = v.expression;
201
-
202
- if (expr.type === "StringLiteral") {
203
- if (IMAGE_REGEX.test(expr.value)) {
204
- imports.push({
205
- file: file,
206
- source: expr.value,
207
- });
208
- }
209
- }
210
-
211
- if (expr.type === "TemplateLiteral") {
212
- extractFromTemplateLiteral(expr.quasis).forEach((v) => {
213
- imports.push({
214
- file: file,
215
- source: v,
216
- });
217
- });
218
- }
219
- }
220
- }
221
-
222
- // Handle style={{ backgroundImage: "url(...)" }}
223
- if (attr.node.name.name === "style") {
224
- extractFromJSXStyle(attr.node.value, file, imports);
225
- }
226
- },
227
-
228
- /**
229
- * Array expressions: const images = ['/img/a.png', '/img/b.png']
230
- * This explicitly handles arrays of image paths, including spread elements
231
- */
232
- ArrayExpression(p) {
233
- p.node.elements.forEach((element) => {
234
- if (!element) return; // Skip null/undefined elements (e.g., [1, , 3])
235
-
236
- // Handle string literals in arrays
237
- if (element.type === "StringLiteral") {
238
- const val = element.value;
239
- if (IMAGE_REGEX.test(val)) {
240
- imports.push({
241
- file: file,
242
- source: val,
243
- });
244
- }
245
- // detect url("...") inside strings
246
- const matches = [...val.matchAll(URL_EXTRACT_REGEX)];
247
- matches.forEach((m) =>
248
- imports.push({
249
- file: file,
250
- source: m[2],
251
- })
252
- );
253
- }
254
-
255
- // Handle template literals in arrays
256
- if (element.type === "TemplateLiteral") {
257
- extractFromTemplateLiteral(element.quasis).forEach((v) => {
258
- imports.push({
259
- file: file,
260
- source: v,
261
- });
262
- });
263
- }
264
-
265
- // Handle spread elements: [...otherArray, '/img/a.png']
266
- if (element.type === "SpreadElement" && element.argument) {
267
- // The spread element's argument will be visited by other handlers
268
- // but we can explicitly check if it's an array with string literals
269
- if (element.argument.type === "ArrayExpression") {
270
- element.argument.elements.forEach((spreadEl) => {
271
- if (spreadEl && spreadEl.type === "StringLiteral") {
272
- const val = spreadEl.value;
273
- if (IMAGE_REGEX.test(val)) {
274
- imports.push({
275
- file: file,
276
- source: val,
277
- });
278
- }
279
- }
280
- });
281
- }
282
- }
283
- });
284
- },
285
-
286
- /**
287
- * String Literals anywhere
288
- */
289
- StringLiteral(p) {
290
- const val = p.node.value;
291
-
292
- if (IMAGE_REGEX.test(val)) {
293
- imports.push({
294
- file: file,
295
- source: val,
296
- });
297
- }
298
-
299
- // detect url("...") inside strings (e.g., Tailwind)
300
- const matches = [...val.matchAll(URL_EXTRACT_REGEX)];
301
- matches.forEach((m) =>
302
- imports.push({
303
- file: file,
304
- source: m[2],
305
- })
306
- );
307
- },
308
-
309
- /**
310
- * Template Literals anywhere
311
- */
312
- TemplateLiteral(p) {
313
- extractFromTemplateLiteral(p.node.quasis).forEach((v) => {
314
- imports.push({
315
- file: file,
316
- source: v,
317
- });
318
- });
319
- },
320
-
321
- /**
322
- * styled-components & css`` blocks
323
- */
324
- TaggedTemplateExpression(p) {
325
- const quasi = p.node.quasi;
326
- const extracted = extractFromTemplateLiteral(quasi.quasis);
327
- extracted.forEach((v) =>
328
- imports.push({
329
- file: file,
330
- source: v,
331
- })
332
- );
333
- },
334
- };
335
- }
336
-
337
- /**
338
- * Scan code files and extract all image references
339
- */
340
- async function scanCodeFilesForImages(
341
- chalk,
342
- codeFiles,
343
- codeDirectory,
78
+ async function processImageImports(
79
+ imports,
80
+ createStepBar,
344
81
  imageDirectory,
345
82
  imageGraph,
346
- options
83
+ options,
84
+ chalk
347
85
  ) {
348
- const imports = [];
349
- const oldPaths = new Map();
350
- const scanBar = createStepBar(
351
- `1/${codeFiles.length}`,
352
- codeFiles.length,
353
- "Scanning code files",
354
- chalk
355
- );
356
- for (const file of codeFiles) {
357
- await new Promise((resolve) => setTimeout(resolve, 50));
358
- scanBar.increment();
359
- const filePath = path.resolve(file);
360
- const code = fs.readFileSync(filePath, "utf8");
361
- if (needsRebuild(filePath, code, imageGraph)) {
362
- const ast = parser.parse(code, {
363
- sourceType: "module",
364
- plugins: ["jsx", "typescript", "decorators-legacy"],
365
- });
366
- if (imageGraph.has(filePath)) {
367
- const oldFiles = new Set(imageGraph.get(filePath).imports);
368
- // Create a copy of the Set to avoid it being cleared/modified when imageGraph is updated
369
- oldPaths.set(filePath, new Set(oldFiles));
370
- }
371
- imageGraph.set(filePath, {
372
- file: filePath,
373
- size: fs.statSync(filePath).size,
374
- hash: getFileHash(fs.readFileSync(filePath, "utf8")),
375
- imports: new Set(),
376
- importedBy: new Set(),
377
- isImage: false,
378
- lastModified: fs.statSync(filePath).mtime.getTime(),
379
- });
380
- const traverser = createASTTraverser(filePath, imports);
381
- traverse(ast, traverser);
382
- }
383
- }
384
- scanBar.stop();
385
86
  let packingBar = null;
386
- // check if imports is empty
387
87
  if (imports.length > 0) {
388
88
  packingBar = createStepBar(
389
89
  `1/${imports.length}`,
@@ -392,42 +92,32 @@ async function scanCodeFilesForImages(
392
92
  chalk
393
93
  );
394
94
  }
395
- // Process all collected imports once after scanning all files
95
+
396
96
  for (const importInfo of imports) {
397
97
  if (packingBar) {
398
98
  await new Promise((resolve) => setTimeout(resolve, 20));
399
99
  packingBar.increment();
400
100
  }
401
- const filePath = path.resolve(importInfo.file);
402
- let importPath = normalize(importInfo.source, imageDirectory, {
403
- alias: options.alias ? true : false,
404
- isRootFolderReferenced: options.isRootFolderReferenced ? true : false,
405
- });
406
- if (importPath && !fs.existsSync(importPath)) {
407
- if(options.alias) {
408
- importPath = normalize(importInfo.source, imageDirectory, {
409
- alias: false,
410
- isRootFolderReferenced: true,
411
- });
412
- }else {
413
- importPath = normalize(importInfo.source, imageDirectory, {
414
- alias: true,
415
- isRootFolderReferenced: false,
416
- });
417
- }
418
- addToImageGraph(importPath, imageGraph);
419
- } else {
420
- addToImageGraph(importPath, imageGraph);
421
- }
422
- importPath && imageGraph.get(importPath).importedBy.add(filePath);
423
- importPath && imageGraph.get(filePath).imports.add(importPath);
101
+ processImageImport(importInfo, imageDirectory, imageGraph, options);
424
102
  }
425
103
 
426
104
  if (packingBar) {
427
105
  packingBar.stop();
428
106
  }
107
+ }
429
108
 
109
+ /**
110
+ * Compares old import paths with new paths and updates the graph
111
+ * @param {Map} oldPaths - Map of file paths to their old import sets
112
+ * @param {Function} createStepBar - Function to create progress bar
113
+ * @param {Map} imageGraph - The image graph Map
114
+ * @param {Object} chalk - Chalk instance for colored output
115
+ * @returns {number} Number of files with removed imports
116
+ */
117
+ async function compareCodePaths(oldPaths, createStepBar, imageGraph, chalk) {
118
+ let removedFilesCount = 0;
430
119
  let compareBar = null;
120
+
431
121
  if (oldPaths.size > 0) {
432
122
  compareBar = createStepBar(
433
123
  `1/${oldPaths.size}`,
@@ -437,9 +127,6 @@ async function scanCodeFilesForImages(
437
127
  );
438
128
  }
439
129
 
440
- // count the number of removed files
441
- let removedFilesCount = 0;
442
- // compare old paths with new paths
443
130
  for (const [filePath, oldFiles] of oldPaths) {
444
131
  if (compareBar) {
445
132
  await new Promise((resolve) => setTimeout(resolve, 20));
@@ -459,9 +146,78 @@ async function scanCodeFilesForImages(
459
146
  removedFilesCount++;
460
147
  }
461
148
  }
149
+
462
150
  if (compareBar) {
463
151
  compareBar.stop();
464
152
  }
153
+
154
+ return removedFilesCount;
155
+ }
156
+
157
+ /**
158
+ * Scan code files and extract all image references
159
+ * @param {Object} chalk - Chalk instance for colored output
160
+ * @param {Array<string>} codeFiles - Array of code file paths
161
+ * @param {string} codeDirectory - The base code directory
162
+ * @param {string} imageDirectory - The base image directory
163
+ * @param {Map} imageGraph - The image graph Map
164
+ * @param {Object} options - Options object
165
+ * @returns {Promise<number>} Number indicating if any changes were made
166
+ */
167
+ async function scanCodeFilesForImages(
168
+ chalk,
169
+ codeFiles,
170
+ codeDirectory,
171
+ imageDirectory,
172
+ imageGraph,
173
+ options
174
+ ) {
175
+ const imports = [];
176
+ const oldPaths = new Map();
177
+ const scanBar = createStepBar(
178
+ `1/${codeFiles.length}`,
179
+ codeFiles.length,
180
+ "Scanning code files",
181
+ chalk
182
+ );
183
+
184
+ for (const file of codeFiles) {
185
+ await new Promise((resolve) => setTimeout(resolve, 50));
186
+ scanBar.increment();
187
+ const filePath = path.resolve(file);
188
+ const code = fs.readFileSync(filePath, "utf8");
189
+
190
+ if (needsRebuild(filePath, code, imageGraph)) {
191
+ const ast = parseCode(code);
192
+ if (imageGraph.has(filePath)) {
193
+ const oldFiles = new Set(imageGraph.get(filePath).imports);
194
+ // Create a copy of the Set to avoid it being cleared/modified when imageGraph is updated
195
+ oldPaths.set(filePath, new Set(oldFiles));
196
+ }
197
+ createCodeFileNode(filePath, imageGraph);
198
+ const traverser = createASTTraverser(filePath, imports);
199
+ traverse(ast, traverser);
200
+ }
201
+ }
202
+
203
+ scanBar.stop();
204
+
205
+ await processImageImports(
206
+ imports,
207
+ createStepBar,
208
+ imageDirectory,
209
+ imageGraph,
210
+ options,
211
+ chalk
212
+ );
213
+
214
+ const removedFilesCount = await compareCodePaths(
215
+ oldPaths,
216
+ createStepBar,
217
+ imageGraph,
218
+ chalk
219
+ );
220
+
465
221
  // Add CSS images
466
222
  const numberOfCssFiles = await getCssImages(
467
223
  codeDirectory,
@@ -471,11 +227,16 @@ async function scanCodeFilesForImages(
471
227
  options,
472
228
  chalk
473
229
  );
230
+
474
231
  return imports.length || removedFilesCount || numberOfCssFiles;
475
232
  }
476
233
 
477
234
  /**
478
235
  * Find unused images by comparing image files with normalized used images
236
+ * @param {Object} imageParentGraph - Object containing imageGraph and unusedImages
237
+ * @param {Array<string>} imageFiles - Array of image file paths
238
+ * @param {Object} options - Options object
239
+ * @returns {Set} Set of unused images
479
240
  */
480
241
  function findUnusedImages(imageParentGraph, imageFiles, options) {
481
242
  for (const imageFile of imageFiles) {
@@ -504,7 +265,7 @@ function findUnusedImages(imageParentGraph, imageFiles, options) {
504
265
  return imageParentGraph.unusedImages;
505
266
  }
506
267
  // find unused images in imageGraph
507
- for (const [filePath, image] of imageParentGraph.imageGraph) {
268
+ for (const [, image] of imageParentGraph.imageGraph) {
508
269
  if ((image.importedBy.size === 0 || image.size === 0) && image.isImage) {
509
270
  imageParentGraph.unusedImages.add({
510
271
  ...image,
@@ -517,104 +278,6 @@ function findUnusedImages(imageParentGraph, imageFiles, options) {
517
278
  return imageParentGraph.unusedImages;
518
279
  }
519
280
 
520
- /**
521
- * Display unused images in table or list format
522
- */
523
- function displayUnusedImages(unusedImages, options, chalk) {
524
- let totalSize = 0;
525
-
526
- if (unusedImages.length === 0) {
527
- console.log(chalk.green.bold("\n✓ No unused images found!"));
528
- console.log(chalk.green("════════════════════════════════════════════════\n"));
529
- return;
530
- }
531
-
532
- if (options.dryRun) {
533
- console.log(chalk.cyan.bold('\n[DRY RUN MODE] No images will be deleted\n'));
534
- }
535
-
536
- if (options.table) {
537
- const table = new Table({
538
- head: options.dryRun
539
- ? [
540
- chalk.cyan("Unused Images (Would Delete)"),
541
- chalk.cyan("In Code"),
542
- chalk.cyan("Exists"),
543
- chalk.cyan("Size"),
544
- ]
545
- : [
546
- chalk.cyan("Unused Images"),
547
- chalk.cyan("In Code"),
548
- chalk.cyan("Exists"),
549
- chalk.cyan("Size"),
550
- ],
551
- colWidths: [75, 10, 10, 15],
552
- style: { head: [], border: [] }
553
- });
554
-
555
- unusedImages.forEach((img) => {
556
- const inCode = img.hash === null;
557
- const exists = img.hash !== null;
558
- const size = img.size > 0 ? (img.size / (1024 * 1024)).toFixed(2) + " MB" : "N/A";
559
-
560
- table.push([
561
- chalk.white(formatFilePath(img.file, 70)),
562
- inCode ? chalk.red("Yes") : chalk.green("No"),
563
- exists ? chalk.green("Yes") : chalk.red("No"),
564
- chalk.yellow(size),
565
- ]);
566
- totalSize += img.size;
567
- });
568
- console.log(table.toString());
569
- } else {
570
- unusedImages.forEach((img) => {
571
- const inCode = img.hash === null;
572
- const exists = img.hash !== null;
573
- const size = img.size > 0 ? (img.size / (1024 * 1024)).toFixed(2) + " MB" : "N/A";
574
-
575
- console.log(
576
- chalk.white("🖼️ ") +
577
- chalk.blue(formatFilePath(img.file, 85)) +
578
- " - " +
579
- chalk.cyan("in code: ") +
580
- (inCode ? chalk.red("Yes") : chalk.green("No")) +
581
- " - " +
582
- chalk.cyan("exists: ") +
583
- (exists ? chalk.green("Yes") : chalk.red("No")) +
584
- " - " +
585
- chalk.cyan("size: ") +
586
- chalk.yellow(size)
587
- );
588
- totalSize += img.size;
589
- });
590
- }
591
-
592
- // Summary section
593
- console.log(chalk.green("\n════════════════════════════════════════════════"));
594
- console.log(
595
- chalk.yellow.bold("Total Size: ") +
596
- chalk.yellow((totalSize / (1024 * 1024)).toFixed(2) + " MB")
597
- );
598
- console.log(
599
- chalk.magenta.bold("Total Images: ") +
600
- chalk.magenta(unusedImages.size)
601
- );
602
- console.log(chalk.green("════════════════════════════════════════════════\n"));
603
- }
604
-
605
- /**
606
- * Handle deletion logic for unused images
607
- */
608
- function handleImageDeletion(unusedImages, options, chalk) {
609
- if (options.dryRun) {
610
- console.log(
611
- chalk.cyan(`\n[DRY RUN] Would delete ${unusedImages.size} file(s)`)
612
- );
613
- } else if (unusedImages.size > 0) {
614
- askDeleteFiles(unusedImages, false);
615
- }
616
- }
617
-
618
281
  /**
619
282
  * MAIN FUNCTION
620
283
  */
@@ -712,31 +375,6 @@ async function getUnusedImages(
712
375
  spinner.succeed(`Completed Qleaner scan`);
713
376
  }
714
377
 
715
- function addToImageGraph(importPath, imageGraph) {
716
- if (importPath && !imageGraph.has(importPath)) {
717
- let size = 0;
718
- let hash = null;
719
- let lastModified = null;
720
- try {
721
- size = fs.statSync(importPath).size;
722
- hash = getFileHash(fs.readFileSync(importPath, "utf8"));
723
- lastModified = fs.statSync(importPath).mtime.getTime();
724
- } catch (error) {
725
- size = 0;
726
- hash = null;
727
- lastModified = null;
728
- }
729
- imageGraph.set(importPath, {
730
- file: importPath,
731
- size: size,
732
- hash: hash,
733
- imports: new Set(),
734
- importedBy: new Set(),
735
- lastModified: lastModified,
736
- isImage: true,
737
- });
738
- }
739
- }
740
378
 
741
379
 
742
380
  module.exports = {