filemayor 2.0.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.
- package/core/categories.js +235 -0
- package/core/cleaner.js +527 -0
- package/core/config.js +562 -0
- package/core/index.js +79 -0
- package/core/organizer.js +528 -0
- package/core/reporter.js +572 -0
- package/core/scanner.js +436 -0
- package/core/security.js +317 -0
- package/core/sop-parser.js +565 -0
- package/core/watcher.js +478 -0
- package/index.js +536 -0
- package/package.json +55 -0
package/core/cleaner.js
ADDED
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════
|
|
5
|
+
* FILEMAYOR CORE — CLEANER
|
|
6
|
+
* Junk file detection, size calculation, safe deletion with
|
|
7
|
+
* configurable patterns, category-based cleaning, and reports.
|
|
8
|
+
* ═══════════════════════════════════════════════════════════════════
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { validatePath, isDirSafe, canRead, canWrite } = require('./security');
|
|
16
|
+
const { formatBytes } = require('./scanner');
|
|
17
|
+
|
|
18
|
+
// ─── Junk Definitions ─────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
const JUNK_CATEGORIES = {
|
|
21
|
+
temp: {
|
|
22
|
+
label: 'Temporary Files',
|
|
23
|
+
icon: 'Clock',
|
|
24
|
+
color: '#fb923c',
|
|
25
|
+
extensions: [
|
|
26
|
+
'.tmp', '.temp', '.swp', '.swo', '.swn',
|
|
27
|
+
'.bak', '.backup', '.old', '.orig', '.save',
|
|
28
|
+
'.crdownload', '.part', '.partial', '.download',
|
|
29
|
+
'~', '.~lock'
|
|
30
|
+
],
|
|
31
|
+
names: []
|
|
32
|
+
},
|
|
33
|
+
cache: {
|
|
34
|
+
label: 'Cache Files',
|
|
35
|
+
icon: 'HardDrive',
|
|
36
|
+
color: '#38bdf8',
|
|
37
|
+
extensions: [
|
|
38
|
+
'.cache', '.cached'
|
|
39
|
+
],
|
|
40
|
+
names: [],
|
|
41
|
+
directories: [
|
|
42
|
+
'__pycache__', '.cache', '.sass-cache',
|
|
43
|
+
'.parcel-cache', '.turbo', '.eslintcache',
|
|
44
|
+
'.stylelintcache', '.prettiercache'
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
system: {
|
|
48
|
+
label: 'System Junk',
|
|
49
|
+
icon: 'Shield',
|
|
50
|
+
color: '#a78bfa',
|
|
51
|
+
extensions: [],
|
|
52
|
+
names: [
|
|
53
|
+
'Thumbs.db', 'ehthumbs.db', 'ehthumbs_vista.db',
|
|
54
|
+
'.DS_Store', '._.DS_Store', '._*',
|
|
55
|
+
'desktop.ini', 'Desktop.ini',
|
|
56
|
+
'.Spotlight-V100', '.Trashes', '.fseventsd',
|
|
57
|
+
'.TemporaryItems', '.apdisk',
|
|
58
|
+
'Icon\r', '.directory',
|
|
59
|
+
'$RECYCLE.BIN', 'RECYCLER',
|
|
60
|
+
'pagefile.sys', 'swapfile.sys', 'hiberfil.sys'
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
logs: {
|
|
64
|
+
label: 'Log Files',
|
|
65
|
+
icon: 'FileText',
|
|
66
|
+
color: '#fbbf24',
|
|
67
|
+
extensions: [
|
|
68
|
+
'.log', '.logs'
|
|
69
|
+
],
|
|
70
|
+
names: [
|
|
71
|
+
'npm-debug.log', 'yarn-debug.log', 'yarn-error.log',
|
|
72
|
+
'lerna-debug.log', 'pnpm-debug.log',
|
|
73
|
+
'debug.log', 'error.log', 'access.log'
|
|
74
|
+
],
|
|
75
|
+
patterns: [
|
|
76
|
+
'npm-debug.log*', 'yarn-debug.log*', 'yarn-error.log*',
|
|
77
|
+
'*.log.*', 'crash-*.log'
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
build: {
|
|
81
|
+
label: 'Build Artifacts',
|
|
82
|
+
icon: 'Package',
|
|
83
|
+
color: '#34d399',
|
|
84
|
+
extensions: [
|
|
85
|
+
'.o', '.obj', '.pyc', '.pyo', '.class',
|
|
86
|
+
'.elc', '.beam'
|
|
87
|
+
],
|
|
88
|
+
names: [],
|
|
89
|
+
directories: [
|
|
90
|
+
'dist', 'build', 'out', 'output', 'target',
|
|
91
|
+
'.next', '.nuxt', '.output', '.vercel',
|
|
92
|
+
'coverage', '.nyc_output', '__snapshots__'
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
dependencies: {
|
|
96
|
+
label: 'Dependencies',
|
|
97
|
+
icon: 'Layers',
|
|
98
|
+
color: '#f472b6',
|
|
99
|
+
extensions: [],
|
|
100
|
+
names: [],
|
|
101
|
+
directories: [
|
|
102
|
+
'node_modules', '.venv', 'venv', '__pypackages__',
|
|
103
|
+
'vendor', 'bower_components', '.bundle',
|
|
104
|
+
'Pods', 'Carthage'
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
ide: {
|
|
108
|
+
label: 'IDE Artifacts',
|
|
109
|
+
icon: 'Code',
|
|
110
|
+
color: '#94a3b8',
|
|
111
|
+
extensions: [],
|
|
112
|
+
names: [],
|
|
113
|
+
directories: [
|
|
114
|
+
'.idea', '.vscode', '.vs', '.eclipse',
|
|
115
|
+
'.settings', '.project', '.classpath',
|
|
116
|
+
'*.sublime-workspace', '.atom'
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// ─── Cleaner Class ────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
class Cleaner {
|
|
124
|
+
constructor(options = {}) {
|
|
125
|
+
this.options = {
|
|
126
|
+
maxDepth: options.maxDepth || 10,
|
|
127
|
+
categories: options.categories || Object.keys(JUNK_CATEGORIES),
|
|
128
|
+
includeDirectories: options.includeDirectories !== false,
|
|
129
|
+
customPatterns: options.customPatterns || {},
|
|
130
|
+
onProgress: options.onProgress || null,
|
|
131
|
+
onError: options.onError || null,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
this.stats = {
|
|
135
|
+
filesFound: 0,
|
|
136
|
+
dirsFound: 0,
|
|
137
|
+
totalSize: 0,
|
|
138
|
+
dirsScanned: 0,
|
|
139
|
+
errors: [],
|
|
140
|
+
startTime: null,
|
|
141
|
+
endTime: null,
|
|
142
|
+
byCategory: {}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Scan for junk files in a directory
|
|
148
|
+
* @param {string} dirPath - Directory to scan
|
|
149
|
+
* @returns {{ junk: Object[], stats: Object }}
|
|
150
|
+
*/
|
|
151
|
+
findJunk(dirPath) {
|
|
152
|
+
const validation = validatePath(dirPath);
|
|
153
|
+
if (!validation.valid) {
|
|
154
|
+
throw new Error(`Invalid path: ${validation.error}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const safeCheck = isDirSafe(validation.resolved);
|
|
158
|
+
if (!safeCheck.safe) {
|
|
159
|
+
throw new Error(`Unsafe directory: ${safeCheck.reason}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!canRead(validation.resolved)) {
|
|
163
|
+
throw new Error(`Permission denied: cannot read "${validation.resolved}"`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.stats.startTime = Date.now();
|
|
167
|
+
const junk = [];
|
|
168
|
+
|
|
169
|
+
this._scanForJunk(validation.resolved, validation.resolved, 0, junk);
|
|
170
|
+
|
|
171
|
+
this.stats.endTime = Date.now();
|
|
172
|
+
this.stats.duration = this.stats.endTime - this.stats.startTime;
|
|
173
|
+
this.stats.totalSizeHuman = formatBytes(this.stats.totalSize);
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
root: validation.resolved,
|
|
177
|
+
junk,
|
|
178
|
+
stats: { ...this.stats }
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Internal recursive junk scanner
|
|
184
|
+
*/
|
|
185
|
+
_scanForJunk(dir, rootPath, depth, results) {
|
|
186
|
+
if (depth > this.options.maxDepth) return;
|
|
187
|
+
|
|
188
|
+
this.stats.dirsScanned++;
|
|
189
|
+
|
|
190
|
+
if (this.options.onProgress) {
|
|
191
|
+
this.options.onProgress({
|
|
192
|
+
phase: 'scanning',
|
|
193
|
+
currentDir: dir,
|
|
194
|
+
found: this.stats.filesFound + this.stats.dirsFound,
|
|
195
|
+
depth
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let items;
|
|
200
|
+
try {
|
|
201
|
+
items = fs.readdirSync(dir, { withFileTypes: true });
|
|
202
|
+
} catch (err) {
|
|
203
|
+
this.stats.errors.push({
|
|
204
|
+
path: dir,
|
|
205
|
+
code: err.code,
|
|
206
|
+
message: err.message
|
|
207
|
+
});
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
for (const item of items) {
|
|
212
|
+
const fullPath = path.join(dir, item.name);
|
|
213
|
+
|
|
214
|
+
if (item.isFile()) {
|
|
215
|
+
const junkCategory = this._classifyFile(item.name, fullPath);
|
|
216
|
+
if (junkCategory) {
|
|
217
|
+
try {
|
|
218
|
+
const stats = fs.statSync(fullPath);
|
|
219
|
+
const entry = {
|
|
220
|
+
name: item.name,
|
|
221
|
+
path: fullPath,
|
|
222
|
+
relativePath: path.relative(rootPath, fullPath),
|
|
223
|
+
size: stats.size,
|
|
224
|
+
sizeHuman: formatBytes(stats.size),
|
|
225
|
+
modified: stats.mtime,
|
|
226
|
+
category: junkCategory,
|
|
227
|
+
categoryLabel: JUNK_CATEGORIES[junkCategory]?.label || junkCategory,
|
|
228
|
+
type: 'file'
|
|
229
|
+
};
|
|
230
|
+
results.push(entry);
|
|
231
|
+
this.stats.filesFound++;
|
|
232
|
+
this.stats.totalSize += stats.size;
|
|
233
|
+
|
|
234
|
+
// Track by category
|
|
235
|
+
if (!this.stats.byCategory[junkCategory]) {
|
|
236
|
+
this.stats.byCategory[junkCategory] = { count: 0, size: 0 };
|
|
237
|
+
}
|
|
238
|
+
this.stats.byCategory[junkCategory].count++;
|
|
239
|
+
this.stats.byCategory[junkCategory].size += stats.size;
|
|
240
|
+
} catch (err) {
|
|
241
|
+
this.stats.errors.push({ path: fullPath, message: err.message });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
} else if (item.isDirectory()) {
|
|
245
|
+
// Check if the directory itself is junk
|
|
246
|
+
const dirJunkCategory = this._classifyDirectory(item.name);
|
|
247
|
+
if (dirJunkCategory && this.options.includeDirectories) {
|
|
248
|
+
try {
|
|
249
|
+
const dirSize = this._getDirSize(fullPath);
|
|
250
|
+
const entry = {
|
|
251
|
+
name: item.name,
|
|
252
|
+
path: fullPath,
|
|
253
|
+
relativePath: path.relative(rootPath, fullPath),
|
|
254
|
+
size: dirSize.size,
|
|
255
|
+
sizeHuman: formatBytes(dirSize.size),
|
|
256
|
+
fileCount: dirSize.files,
|
|
257
|
+
category: dirJunkCategory,
|
|
258
|
+
categoryLabel: JUNK_CATEGORIES[dirJunkCategory]?.label || dirJunkCategory,
|
|
259
|
+
type: 'directory'
|
|
260
|
+
};
|
|
261
|
+
results.push(entry);
|
|
262
|
+
this.stats.dirsFound++;
|
|
263
|
+
this.stats.totalSize += dirSize.size;
|
|
264
|
+
|
|
265
|
+
if (!this.stats.byCategory[dirJunkCategory]) {
|
|
266
|
+
this.stats.byCategory[dirJunkCategory] = { count: 0, size: 0 };
|
|
267
|
+
}
|
|
268
|
+
this.stats.byCategory[dirJunkCategory].count++;
|
|
269
|
+
this.stats.byCategory[dirJunkCategory].size += dirSize.size;
|
|
270
|
+
} catch (err) {
|
|
271
|
+
this.stats.errors.push({ path: fullPath, message: err.message });
|
|
272
|
+
}
|
|
273
|
+
// Don't recurse into junk directories
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Normal recursion
|
|
278
|
+
if (!item.name.startsWith('.') || this.options.categories.includes('system')) {
|
|
279
|
+
this._scanForJunk(fullPath, rootPath, depth + 1, results);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Check for empty directories
|
|
285
|
+
if (this.options.categories.includes('temp')) {
|
|
286
|
+
try {
|
|
287
|
+
const remaining = fs.readdirSync(dir);
|
|
288
|
+
if (remaining.length === 0 && dir !== rootPath) {
|
|
289
|
+
results.push({
|
|
290
|
+
name: path.basename(dir),
|
|
291
|
+
path: dir,
|
|
292
|
+
relativePath: path.relative(rootPath, dir),
|
|
293
|
+
size: 0,
|
|
294
|
+
sizeHuman: '0 B',
|
|
295
|
+
category: 'temp',
|
|
296
|
+
categoryLabel: 'Empty Directory',
|
|
297
|
+
type: 'empty_directory'
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
} catch { /* ignore */ }
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Classify a file as junk (or null if not junk)
|
|
306
|
+
* @returns {string|null} Category name or null
|
|
307
|
+
*/
|
|
308
|
+
_classifyFile(filename, fullPath) {
|
|
309
|
+
const ext = path.extname(filename).toLowerCase();
|
|
310
|
+
|
|
311
|
+
for (const catName of this.options.categories) {
|
|
312
|
+
const cat = JUNK_CATEGORIES[catName];
|
|
313
|
+
if (!cat) continue;
|
|
314
|
+
|
|
315
|
+
// Check extensions
|
|
316
|
+
if (cat.extensions && cat.extensions.includes(ext)) return catName;
|
|
317
|
+
|
|
318
|
+
// Check exact names
|
|
319
|
+
if (cat.names && cat.names.includes(filename)) return catName;
|
|
320
|
+
|
|
321
|
+
// Check patterns
|
|
322
|
+
if (cat.patterns) {
|
|
323
|
+
for (const pattern of cat.patterns) {
|
|
324
|
+
if (this._matchPattern(pattern, filename)) return catName;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Check custom patterns
|
|
330
|
+
for (const [catName, patterns] of Object.entries(this.options.customPatterns)) {
|
|
331
|
+
for (const pattern of patterns) {
|
|
332
|
+
if (this._matchPattern(pattern, filename)) return catName;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Classify a directory as junk
|
|
341
|
+
* @returns {string|null} Category name or null
|
|
342
|
+
*/
|
|
343
|
+
_classifyDirectory(dirname) {
|
|
344
|
+
for (const catName of this.options.categories) {
|
|
345
|
+
const cat = JUNK_CATEGORIES[catName];
|
|
346
|
+
if (!cat || !cat.directories) continue;
|
|
347
|
+
if (cat.directories.includes(dirname)) return catName;
|
|
348
|
+
}
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Simple pattern match
|
|
354
|
+
*/
|
|
355
|
+
_matchPattern(pattern, str) {
|
|
356
|
+
const regex = pattern
|
|
357
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
358
|
+
.replace(/\*/g, '.*')
|
|
359
|
+
.replace(/\?/g, '.');
|
|
360
|
+
return new RegExp(`^${regex}$`, 'i').test(str);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Calculate total size of a directory
|
|
365
|
+
*/
|
|
366
|
+
_getDirSize(dir, depth = 0) {
|
|
367
|
+
let size = 0;
|
|
368
|
+
let files = 0;
|
|
369
|
+
|
|
370
|
+
if (depth > 5) return { size, files };
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
374
|
+
for (const item of items) {
|
|
375
|
+
const fullPath = path.join(dir, item.name);
|
|
376
|
+
if (item.isFile()) {
|
|
377
|
+
try {
|
|
378
|
+
const stats = fs.statSync(fullPath);
|
|
379
|
+
size += stats.size;
|
|
380
|
+
files++;
|
|
381
|
+
} catch { /* skip */ }
|
|
382
|
+
} else if (item.isDirectory()) {
|
|
383
|
+
const sub = this._getDirSize(fullPath, depth + 1);
|
|
384
|
+
size += sub.size;
|
|
385
|
+
files += sub.files;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
} catch { /* skip */ }
|
|
389
|
+
|
|
390
|
+
return { size, files };
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ─── Deletion ─────────────────────────────────────────────────────
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Delete junk files and directories
|
|
398
|
+
* @param {Object[]} junkItems - Items from findJunk
|
|
399
|
+
* @param {Object} options - Deletion options
|
|
400
|
+
* @returns {{ deleted: number, freed: number, freedHuman: string, errors: Object[] }}
|
|
401
|
+
*/
|
|
402
|
+
function deleteJunk(junkItems, options = {}) {
|
|
403
|
+
const {
|
|
404
|
+
onProgress = null,
|
|
405
|
+
onError = null,
|
|
406
|
+
filterCategories = null, // Only delete these categories
|
|
407
|
+
} = options;
|
|
408
|
+
|
|
409
|
+
let deleted = 0;
|
|
410
|
+
let freed = 0;
|
|
411
|
+
const errors = [];
|
|
412
|
+
|
|
413
|
+
// Filter by category if specified
|
|
414
|
+
const toDelete = filterCategories
|
|
415
|
+
? junkItems.filter(item => filterCategories.includes(item.category))
|
|
416
|
+
: junkItems;
|
|
417
|
+
|
|
418
|
+
// Delete files first, then directories (bottom-up)
|
|
419
|
+
const files = toDelete.filter(i => i.type === 'file');
|
|
420
|
+
const dirs = toDelete.filter(i => i.type === 'directory' || i.type === 'empty_directory');
|
|
421
|
+
|
|
422
|
+
// Delete files
|
|
423
|
+
for (let i = 0; i < files.length; i++) {
|
|
424
|
+
const item = files[i];
|
|
425
|
+
|
|
426
|
+
if (onProgress) {
|
|
427
|
+
onProgress({
|
|
428
|
+
current: i + 1,
|
|
429
|
+
total: toDelete.length,
|
|
430
|
+
percent: Math.round(((i + 1) / toDelete.length) * 100),
|
|
431
|
+
file: item.name,
|
|
432
|
+
phase: 'deleting'
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
try {
|
|
437
|
+
if (!canWrite(path.dirname(item.path))) {
|
|
438
|
+
throw new Error('Permission denied');
|
|
439
|
+
}
|
|
440
|
+
fs.unlinkSync(item.path);
|
|
441
|
+
deleted++;
|
|
442
|
+
freed += item.size;
|
|
443
|
+
} catch (err) {
|
|
444
|
+
const errorInfo = { path: item.path, message: err.message };
|
|
445
|
+
errors.push(errorInfo);
|
|
446
|
+
if (onError) onError(errorInfo);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Delete directories (sort by depth descending — deepest first)
|
|
451
|
+
const sortedDirs = dirs.sort((a, b) => {
|
|
452
|
+
const depthA = a.path.split(path.sep).length;
|
|
453
|
+
const depthB = b.path.split(path.sep).length;
|
|
454
|
+
return depthB - depthA;
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
for (const dir of sortedDirs) {
|
|
458
|
+
try {
|
|
459
|
+
if (dir.type === 'empty_directory') {
|
|
460
|
+
fs.rmdirSync(dir.path);
|
|
461
|
+
} else {
|
|
462
|
+
fs.rmSync(dir.path, { recursive: true, force: true });
|
|
463
|
+
}
|
|
464
|
+
deleted++;
|
|
465
|
+
freed += dir.size;
|
|
466
|
+
} catch (err) {
|
|
467
|
+
errors.push({ path: dir.path, message: err.message });
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return {
|
|
472
|
+
deleted,
|
|
473
|
+
freed,
|
|
474
|
+
freedHuman: formatBytes(freed),
|
|
475
|
+
errors,
|
|
476
|
+
total: toDelete.length
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// ─── Convenience Functions ────────────────────────────────────────
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Quick scan for junk files
|
|
484
|
+
*/
|
|
485
|
+
function findJunk(dirPath, options = {}) {
|
|
486
|
+
const cleaner = new Cleaner(options);
|
|
487
|
+
return cleaner.findJunk(dirPath);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Quick clean: scan + delete in one call
|
|
492
|
+
*/
|
|
493
|
+
function clean(dirPath, options = {}) {
|
|
494
|
+
const { dryRun = false, filterCategories = null, ...scanOptions } = options;
|
|
495
|
+
|
|
496
|
+
const result = findJunk(dirPath, scanOptions);
|
|
497
|
+
|
|
498
|
+
if (dryRun) {
|
|
499
|
+
return {
|
|
500
|
+
dryRun: true,
|
|
501
|
+
junk: result.junk,
|
|
502
|
+
stats: result.stats,
|
|
503
|
+
deleteResult: null
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const deleteResult = deleteJunk(result.junk, {
|
|
508
|
+
filterCategories,
|
|
509
|
+
onProgress: scanOptions.onProgress,
|
|
510
|
+
onError: scanOptions.onError,
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
return {
|
|
514
|
+
dryRun: false,
|
|
515
|
+
junk: result.junk,
|
|
516
|
+
stats: result.stats,
|
|
517
|
+
deleteResult
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
module.exports = {
|
|
522
|
+
JUNK_CATEGORIES,
|
|
523
|
+
Cleaner,
|
|
524
|
+
findJunk,
|
|
525
|
+
deleteJunk,
|
|
526
|
+
clean
|
|
527
|
+
};
|