filemayor 3.6.0 → 4.0.7
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/LICENSE +90 -90
- package/README.md +96 -96
- package/core/analyzer.js +163 -163
- package/core/categories.js +235 -235
- package/core/cleaner.js +527 -527
- package/core/config.js +562 -562
- package/core/fs-abstraction.js +110 -38
- package/core/index.js +135 -135
- package/core/intent-interpreter.js +228 -66
- package/core/license.js +403 -339
- package/core/organizer.js +414 -414
- package/core/reporter.js +783 -783
- package/core/scanner.js +466 -466
- package/core/security.js +370 -370
- package/core/sop-parser.js +564 -564
- package/core/telemetry.js +74 -0
- package/core/vault.js +83 -69
- package/core/watcher.js +478 -478
- package/index.js +895 -877
- package/package.json +3 -3
package/core/analyzer.js
CHANGED
|
@@ -1,163 +1,163 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* ═══════════════════════════════════════════════════════════════════
|
|
5
|
-
* FILEMAYOR CORE — ANALYZER
|
|
6
|
-
* Deep filesystem insights: duplicate detection, bloat mapping,
|
|
7
|
-
* and potential savings calculation.
|
|
8
|
-
* ═══════════════════════════════════════════════════════════════════
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
'use strict';
|
|
12
|
-
|
|
13
|
-
const fs = require('fs');
|
|
14
|
-
const path = require('path');
|
|
15
|
-
const crypto = require('crypto');
|
|
16
|
-
const { scan } = require('./scanner');
|
|
17
|
-
const { findJunk } = require('./cleaner');
|
|
18
|
-
const { formatBytes } = require('./scanner');
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Analyze a directory for duplicates, bloat, and junk
|
|
22
|
-
* @param {string} dirPath - Root directory to analyze
|
|
23
|
-
* @param {Object} options - Analysis options
|
|
24
|
-
* @returns {Object} Deep analysis results
|
|
25
|
-
*/
|
|
26
|
-
function analyzeDirectory(dirPath, options = {}) {
|
|
27
|
-
const {
|
|
28
|
-
maxDepth = 10,
|
|
29
|
-
minSize = 1024, // Ignore files smaller than 1KB for duplicate analysis
|
|
30
|
-
} = options;
|
|
31
|
-
|
|
32
|
-
// 1. Initial Scan
|
|
33
|
-
const scanResult = scan(dirPath, { maxDepth, includeDirectories: true });
|
|
34
|
-
const { files, stats } = scanResult;
|
|
35
|
-
|
|
36
|
-
// 2. Duplicate Detection (Size-first hashing)
|
|
37
|
-
const sizeMap = new Map();
|
|
38
|
-
const duplicates = [];
|
|
39
|
-
|
|
40
|
-
// Group by size first (fast)
|
|
41
|
-
for (const file of files) {
|
|
42
|
-
if (file.size < minSize) continue;
|
|
43
|
-
if (!sizeMap.has(file.size)) {
|
|
44
|
-
sizeMap.set(file.size, []);
|
|
45
|
-
}
|
|
46
|
-
sizeMap.get(file.size).push(file);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Hash only those with identical sizes
|
|
50
|
-
for (const [size, candidateFiles] of sizeMap.entries()) {
|
|
51
|
-
if (candidateFiles.length < 2) continue;
|
|
52
|
-
|
|
53
|
-
const hashMap = new Map();
|
|
54
|
-
for (const file of candidateFiles) {
|
|
55
|
-
try {
|
|
56
|
-
// Partial hash (first 16KB) for efficiency
|
|
57
|
-
const hash = getFileHash(file.path, 16384);
|
|
58
|
-
if (!hashMap.has(hash)) {
|
|
59
|
-
hashMap.set(hash, []);
|
|
60
|
-
}
|
|
61
|
-
hashMap.get(hash).push(file);
|
|
62
|
-
} catch (err) {
|
|
63
|
-
// Skip files we can't read
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
for (const [hash, dupeFiles] of hashMap.entries()) {
|
|
68
|
-
if (dupeFiles.length > 1) {
|
|
69
|
-
duplicates.push({
|
|
70
|
-
size,
|
|
71
|
-
sizeHuman: formatBytes(size),
|
|
72
|
-
files: dupeFiles.map(f => ({ name: f.name, path: f.path, relativePath: f.relativePath })),
|
|
73
|
-
wastedSpace: size * (dupeFiles.length - 1),
|
|
74
|
-
wastedSpaceHuman: formatBytes(size * (dupeFiles.length - 1))
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
// 3. Bloat Mapping (Top Directories) & Progress Reporting
|
|
80
|
-
const dirMap = new Map();
|
|
81
|
-
for (let i = 0; i < files.length; i++) {
|
|
82
|
-
const file = files[i];
|
|
83
|
-
|
|
84
|
-
// Progress callback
|
|
85
|
-
if (options.onProgress) {
|
|
86
|
-
options.onProgress({
|
|
87
|
-
current: i + 1,
|
|
88
|
-
total: files.length,
|
|
89
|
-
percent: Math.round(((i + 1) / files.length) * 100),
|
|
90
|
-
file: file.name
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const parts = file.relativePath.split(path.sep);
|
|
95
|
-
let currentPath = '';
|
|
96
|
-
// Only track top-level and second-level folders for bloat mapping
|
|
97
|
-
for (let j = 0; j < Math.min(parts.length - 1, 2); j++) {
|
|
98
|
-
currentPath = currentPath ? path.join(currentPath, parts[j]) : parts[j];
|
|
99
|
-
if (!dirMap.has(currentPath)) {
|
|
100
|
-
dirMap.set(currentPath, { size: 0, count: 0 });
|
|
101
|
-
}
|
|
102
|
-
const info = dirMap.get(currentPath);
|
|
103
|
-
info.size += file.size;
|
|
104
|
-
info.count += 1;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const largestDirs = Array.from(dirMap.entries())
|
|
109
|
-
.map(([name, info]) => ({ name, ...info, sizeHuman: formatBytes(info.size) }))
|
|
110
|
-
.sort((a, b) => b.size - a.size)
|
|
111
|
-
.slice(0, 5);
|
|
112
|
-
|
|
113
|
-
// 4. Junk Detection Integration
|
|
114
|
-
const junkResult = findJunk(dirPath, { maxDepth });
|
|
115
|
-
const totalJunkSize = junkResult.stats.totalSize;
|
|
116
|
-
const totalDuplicateWasted = duplicates.reduce((sum, d) => sum + d.wastedSpace, 0);
|
|
117
|
-
|
|
118
|
-
return {
|
|
119
|
-
root: dirPath,
|
|
120
|
-
timestamp: Date.now(),
|
|
121
|
-
summary: {
|
|
122
|
-
totalFiles: stats.filesFound,
|
|
123
|
-
totalSize: stats.totalSize,
|
|
124
|
-
totalSizeHuman: formatBytes(stats.totalSize),
|
|
125
|
-
potentialSavings: totalJunkSize + totalDuplicateWasted,
|
|
126
|
-
potentialSavingsHuman: formatBytes(totalJunkSize + totalDuplicateWasted)
|
|
127
|
-
},
|
|
128
|
-
duplicates: {
|
|
129
|
-
sets: duplicates.length,
|
|
130
|
-
totalWasted: totalDuplicateWasted,
|
|
131
|
-
totalWastedHuman: formatBytes(totalDuplicateWasted),
|
|
132
|
-
details: duplicates.sort((a, b) => b.wastedSpace - a.wastedSpace).slice(0, 10)
|
|
133
|
-
},
|
|
134
|
-
largestDirs,
|
|
135
|
-
junk: {
|
|
136
|
-
count: junkResult.junk.length,
|
|
137
|
-
totalSize: totalJunkSize,
|
|
138
|
-
totalSizeHuman: formatBytes(totalJunkSize),
|
|
139
|
-
categories: junkResult.stats.byCategory
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Get hash of file content (partial or full)
|
|
146
|
-
*/
|
|
147
|
-
function getFileHash(filePath, limit = null) {
|
|
148
|
-
const buffer = limit
|
|
149
|
-
? Buffer.alloc(limit)
|
|
150
|
-
: fs.readFileSync(filePath);
|
|
151
|
-
|
|
152
|
-
if (limit) {
|
|
153
|
-
const fd = fs.openSync(filePath, 'r');
|
|
154
|
-
fs.readSync(fd, buffer, 0, limit, 0);
|
|
155
|
-
fs.closeSync(fd);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return crypto.createHash('md5').update(buffer).digest('hex');
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
module.exports = {
|
|
162
|
-
analyzeDirectory
|
|
163
|
-
};
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════
|
|
5
|
+
* FILEMAYOR CORE — ANALYZER
|
|
6
|
+
* Deep filesystem insights: duplicate detection, bloat mapping,
|
|
7
|
+
* and potential savings calculation.
|
|
8
|
+
* ═══════════════════════════════════════════════════════════════════
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const crypto = require('crypto');
|
|
16
|
+
const { scan } = require('./scanner');
|
|
17
|
+
const { findJunk } = require('./cleaner');
|
|
18
|
+
const { formatBytes } = require('./scanner');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Analyze a directory for duplicates, bloat, and junk
|
|
22
|
+
* @param {string} dirPath - Root directory to analyze
|
|
23
|
+
* @param {Object} options - Analysis options
|
|
24
|
+
* @returns {Object} Deep analysis results
|
|
25
|
+
*/
|
|
26
|
+
function analyzeDirectory(dirPath, options = {}) {
|
|
27
|
+
const {
|
|
28
|
+
maxDepth = 10,
|
|
29
|
+
minSize = 1024, // Ignore files smaller than 1KB for duplicate analysis
|
|
30
|
+
} = options;
|
|
31
|
+
|
|
32
|
+
// 1. Initial Scan
|
|
33
|
+
const scanResult = scan(dirPath, { maxDepth, includeDirectories: true });
|
|
34
|
+
const { files, stats } = scanResult;
|
|
35
|
+
|
|
36
|
+
// 2. Duplicate Detection (Size-first hashing)
|
|
37
|
+
const sizeMap = new Map();
|
|
38
|
+
const duplicates = [];
|
|
39
|
+
|
|
40
|
+
// Group by size first (fast)
|
|
41
|
+
for (const file of files) {
|
|
42
|
+
if (file.size < minSize) continue;
|
|
43
|
+
if (!sizeMap.has(file.size)) {
|
|
44
|
+
sizeMap.set(file.size, []);
|
|
45
|
+
}
|
|
46
|
+
sizeMap.get(file.size).push(file);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Hash only those with identical sizes
|
|
50
|
+
for (const [size, candidateFiles] of sizeMap.entries()) {
|
|
51
|
+
if (candidateFiles.length < 2) continue;
|
|
52
|
+
|
|
53
|
+
const hashMap = new Map();
|
|
54
|
+
for (const file of candidateFiles) {
|
|
55
|
+
try {
|
|
56
|
+
// Partial hash (first 16KB) for efficiency
|
|
57
|
+
const hash = getFileHash(file.path, 16384);
|
|
58
|
+
if (!hashMap.has(hash)) {
|
|
59
|
+
hashMap.set(hash, []);
|
|
60
|
+
}
|
|
61
|
+
hashMap.get(hash).push(file);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
// Skip files we can't read
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (const [hash, dupeFiles] of hashMap.entries()) {
|
|
68
|
+
if (dupeFiles.length > 1) {
|
|
69
|
+
duplicates.push({
|
|
70
|
+
size,
|
|
71
|
+
sizeHuman: formatBytes(size),
|
|
72
|
+
files: dupeFiles.map(f => ({ name: f.name, path: f.path, relativePath: f.relativePath })),
|
|
73
|
+
wastedSpace: size * (dupeFiles.length - 1),
|
|
74
|
+
wastedSpaceHuman: formatBytes(size * (dupeFiles.length - 1))
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// 3. Bloat Mapping (Top Directories) & Progress Reporting
|
|
80
|
+
const dirMap = new Map();
|
|
81
|
+
for (let i = 0; i < files.length; i++) {
|
|
82
|
+
const file = files[i];
|
|
83
|
+
|
|
84
|
+
// Progress callback
|
|
85
|
+
if (options.onProgress) {
|
|
86
|
+
options.onProgress({
|
|
87
|
+
current: i + 1,
|
|
88
|
+
total: files.length,
|
|
89
|
+
percent: Math.round(((i + 1) / files.length) * 100),
|
|
90
|
+
file: file.name
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const parts = file.relativePath.split(path.sep);
|
|
95
|
+
let currentPath = '';
|
|
96
|
+
// Only track top-level and second-level folders for bloat mapping
|
|
97
|
+
for (let j = 0; j < Math.min(parts.length - 1, 2); j++) {
|
|
98
|
+
currentPath = currentPath ? path.join(currentPath, parts[j]) : parts[j];
|
|
99
|
+
if (!dirMap.has(currentPath)) {
|
|
100
|
+
dirMap.set(currentPath, { size: 0, count: 0 });
|
|
101
|
+
}
|
|
102
|
+
const info = dirMap.get(currentPath);
|
|
103
|
+
info.size += file.size;
|
|
104
|
+
info.count += 1;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const largestDirs = Array.from(dirMap.entries())
|
|
109
|
+
.map(([name, info]) => ({ name, ...info, sizeHuman: formatBytes(info.size) }))
|
|
110
|
+
.sort((a, b) => b.size - a.size)
|
|
111
|
+
.slice(0, 5);
|
|
112
|
+
|
|
113
|
+
// 4. Junk Detection Integration
|
|
114
|
+
const junkResult = findJunk(dirPath, { maxDepth });
|
|
115
|
+
const totalJunkSize = junkResult.stats.totalSize;
|
|
116
|
+
const totalDuplicateWasted = duplicates.reduce((sum, d) => sum + d.wastedSpace, 0);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
root: dirPath,
|
|
120
|
+
timestamp: Date.now(),
|
|
121
|
+
summary: {
|
|
122
|
+
totalFiles: stats.filesFound,
|
|
123
|
+
totalSize: stats.totalSize,
|
|
124
|
+
totalSizeHuman: formatBytes(stats.totalSize),
|
|
125
|
+
potentialSavings: totalJunkSize + totalDuplicateWasted,
|
|
126
|
+
potentialSavingsHuman: formatBytes(totalJunkSize + totalDuplicateWasted)
|
|
127
|
+
},
|
|
128
|
+
duplicates: {
|
|
129
|
+
sets: duplicates.length,
|
|
130
|
+
totalWasted: totalDuplicateWasted,
|
|
131
|
+
totalWastedHuman: formatBytes(totalDuplicateWasted),
|
|
132
|
+
details: duplicates.sort((a, b) => b.wastedSpace - a.wastedSpace).slice(0, 10)
|
|
133
|
+
},
|
|
134
|
+
largestDirs,
|
|
135
|
+
junk: {
|
|
136
|
+
count: junkResult.junk.length,
|
|
137
|
+
totalSize: totalJunkSize,
|
|
138
|
+
totalSizeHuman: formatBytes(totalJunkSize),
|
|
139
|
+
categories: junkResult.stats.byCategory
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get hash of file content (partial or full)
|
|
146
|
+
*/
|
|
147
|
+
function getFileHash(filePath, limit = null) {
|
|
148
|
+
const buffer = limit
|
|
149
|
+
? Buffer.alloc(limit)
|
|
150
|
+
: fs.readFileSync(filePath);
|
|
151
|
+
|
|
152
|
+
if (limit) {
|
|
153
|
+
const fd = fs.openSync(filePath, 'r');
|
|
154
|
+
fs.readSync(fd, buffer, 0, limit, 0);
|
|
155
|
+
fs.closeSync(fd);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return crypto.createHash('md5').update(buffer).digest('hex');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = {
|
|
162
|
+
analyzeDirectory
|
|
163
|
+
};
|