node-janitor 1.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/README.md +259 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +337 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/cleaner.d.ts +10 -0
- package/dist/core/cleaner.d.ts.map +1 -0
- package/dist/core/cleaner.js +107 -0
- package/dist/core/cleaner.js.map +1 -0
- package/dist/core/deep-cleaner.d.ts +10 -0
- package/dist/core/deep-cleaner.d.ts.map +1 -0
- package/dist/core/deep-cleaner.js +228 -0
- package/dist/core/deep-cleaner.js.map +1 -0
- package/dist/core/scanner.d.ts +35 -0
- package/dist/core/scanner.d.ts.map +1 -0
- package/dist/core/scanner.js +172 -0
- package/dist/core/scanner.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +183 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/ui/progress.d.ts +20 -0
- package/dist/ui/progress.d.ts.map +1 -0
- package/dist/ui/progress.js +29 -0
- package/dist/ui/progress.js.map +1 -0
- package/dist/ui/prompts.d.ts +40 -0
- package/dist/ui/prompts.d.ts.map +1 -0
- package/dist/ui/prompts.js +106 -0
- package/dist/ui/prompts.js.map +1 -0
- package/dist/ui/spinner.d.ts +15 -0
- package/dist/ui/spinner.d.ts.map +1 -0
- package/dist/ui/spinner.js +30 -0
- package/dist/ui/spinner.js.map +1 -0
- package/dist/ui/table.d.ts +38 -0
- package/dist/ui/table.d.ts.map +1 -0
- package/dist/ui/table.js +117 -0
- package/dist/ui/table.js.map +1 -0
- package/dist/utils/formatter.d.ts +40 -0
- package/dist/utils/formatter.d.ts.map +1 -0
- package/dist/utils/formatter.js +103 -0
- package/dist/utils/formatter.js.map +1 -0
- package/dist/utils/fs-utils.d.ts +53 -0
- package/dist/utils/fs-utils.d.ts.map +1 -0
- package/dist/utils/fs-utils.js +155 -0
- package/dist/utils/fs-utils.js.map +1 -0
- package/dist/utils/logger.d.ts +26 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +77 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import pLimit from 'p-limit';
|
|
2
|
+
import { deleteFolder, deleteFolderFast, ensureDir, writeJson, getBackupsDir } from '../utils/fs-utils.js';
|
|
3
|
+
import { parseDuration, parseDurationRange, parseSize } from '../utils/formatter.js';
|
|
4
|
+
import { filterByAge, filterBySize, filterWithLockfile } from './scanner.js';
|
|
5
|
+
import logger from '../utils/logger.js';
|
|
6
|
+
import dayjs from 'dayjs';
|
|
7
|
+
/**
|
|
8
|
+
* Clean node_modules folders
|
|
9
|
+
*/
|
|
10
|
+
export async function cleanNodeModules(folders, options, onProgress) {
|
|
11
|
+
const result = {
|
|
12
|
+
deletedCount: 0,
|
|
13
|
+
freedBytes: 0,
|
|
14
|
+
deletedPaths: [],
|
|
15
|
+
errors: [],
|
|
16
|
+
};
|
|
17
|
+
// Apply filters
|
|
18
|
+
let filteredFolders = [...folders];
|
|
19
|
+
// Filter by age
|
|
20
|
+
if (options.olderThan) {
|
|
21
|
+
const days = parseDuration(options.olderThan);
|
|
22
|
+
filteredFolders = filterByAge(filteredFolders, days);
|
|
23
|
+
logger.debug(`After olderThan filter: ${filteredFolders.length} folders`);
|
|
24
|
+
}
|
|
25
|
+
if (options.between) {
|
|
26
|
+
const { min, max } = parseDurationRange(options.between);
|
|
27
|
+
filteredFolders = filterByAge(filteredFolders, min, max);
|
|
28
|
+
logger.debug(`After between filter: ${filteredFolders.length} folders`);
|
|
29
|
+
}
|
|
30
|
+
// Filter by size
|
|
31
|
+
if (options.minSize) {
|
|
32
|
+
const minBytes = parseSize(options.minSize);
|
|
33
|
+
filteredFolders = filterBySize(filteredFolders, minBytes);
|
|
34
|
+
logger.debug(`After minSize filter: ${filteredFolders.length} folders`);
|
|
35
|
+
}
|
|
36
|
+
if (options.maxSize) {
|
|
37
|
+
const maxBytes = parseSize(options.maxSize);
|
|
38
|
+
filteredFolders = filterBySize(filteredFolders, undefined, maxBytes);
|
|
39
|
+
logger.debug(`After maxSize filter: ${filteredFolders.length} folders`);
|
|
40
|
+
}
|
|
41
|
+
// Filter by lockfile
|
|
42
|
+
if (options.lockCheck) {
|
|
43
|
+
filteredFolders = filterWithLockfile(filteredFolders);
|
|
44
|
+
logger.debug(`After lockCheck filter: ${filteredFolders.length} folders`);
|
|
45
|
+
}
|
|
46
|
+
// Dry run - just return what would be deleted
|
|
47
|
+
if (options.dryRun) {
|
|
48
|
+
return {
|
|
49
|
+
deletedCount: filteredFolders.length,
|
|
50
|
+
freedBytes: filteredFolders.reduce((sum, f) => sum + f.size, 0),
|
|
51
|
+
deletedPaths: filteredFolders.map(f => f.path),
|
|
52
|
+
errors: [],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// Create backup if requested
|
|
56
|
+
if (options.backup) {
|
|
57
|
+
result.backupPath = await createBackup(filteredFolders);
|
|
58
|
+
logger.debug(`Backup created at: ${result.backupPath}`);
|
|
59
|
+
}
|
|
60
|
+
// Delete folders
|
|
61
|
+
const deleteFunc = options.fast ? deleteFolderFast : deleteFolder;
|
|
62
|
+
const parallelLimit = options.parallel || 1;
|
|
63
|
+
const limit = pLimit(parallelLimit);
|
|
64
|
+
const deletePromises = filteredFolders.map((folder, index) => limit(async () => {
|
|
65
|
+
try {
|
|
66
|
+
onProgress?.(index + 1, filteredFolders.length, folder.path);
|
|
67
|
+
await deleteFunc(folder.path);
|
|
68
|
+
result.deletedCount++;
|
|
69
|
+
result.freedBytes += folder.size;
|
|
70
|
+
result.deletedPaths.push(folder.path);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
result.errors.push({
|
|
74
|
+
path: folder.path,
|
|
75
|
+
message: error instanceof Error ? error.message : String(error),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}));
|
|
79
|
+
await Promise.all(deletePromises);
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Create a backup of folder info before deletion
|
|
84
|
+
*/
|
|
85
|
+
async function createBackup(folders) {
|
|
86
|
+
const backupsDir = getBackupsDir();
|
|
87
|
+
await ensureDir(backupsDir);
|
|
88
|
+
const timestamp = dayjs().format('YYYY_MM_DD_HHmmss');
|
|
89
|
+
const filename = `backup_${timestamp}.json`;
|
|
90
|
+
const filepath = `${backupsDir}/${filename}`;
|
|
91
|
+
const backupData = {
|
|
92
|
+
timestamp: new Date().toISOString(),
|
|
93
|
+
version: '1.0.0',
|
|
94
|
+
folders: folders.map(f => ({
|
|
95
|
+
path: f.path,
|
|
96
|
+
size: f.size,
|
|
97
|
+
packageJsonPath: `${f.projectPath}/package.json`,
|
|
98
|
+
lockfileType: f.hasPackageLock ? 'npm' : f.hasYarnLock ? 'yarn' : f.hasPnpmLock ? 'pnpm' : undefined,
|
|
99
|
+
})),
|
|
100
|
+
totalSize: folders.reduce((sum, f) => sum + f.size, 0),
|
|
101
|
+
totalFolders: folders.length,
|
|
102
|
+
};
|
|
103
|
+
await writeJson(filepath, backupData);
|
|
104
|
+
return filepath;
|
|
105
|
+
}
|
|
106
|
+
export default { cleanNodeModules };
|
|
107
|
+
//# sourceMappingURL=cleaner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleaner.js","sourceRoot":"","sources":["../../src/core/cleaner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DeepCleanOptions, DeepCleanResult } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Deep clean a node_modules folder by removing unnecessary files
|
|
4
|
+
*/
|
|
5
|
+
export declare function deepClean(nodeModulesPath: string, options?: DeepCleanOptions, onProgress?: (packageName: string) => void): Promise<DeepCleanResult>;
|
|
6
|
+
declare const _default: {
|
|
7
|
+
deepClean: typeof deepClean;
|
|
8
|
+
};
|
|
9
|
+
export default _default;
|
|
10
|
+
//# sourceMappingURL=deep-cleaner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deep-cleaner.d.ts","sourceRoot":"","sources":["../../src/core/deep-cleaner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AA+E3E;;GAEG;AACH,wBAAsB,SAAS,CAC3B,eAAe,EAAE,MAAM,EACvB,OAAO,GAAE,gBAAqB,EAC9B,UAAU,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,GAC3C,OAAO,CAAC,eAAe,CAAC,CA2C1B;;;;AAyHD,wBAA6B"}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import logger from '../utils/logger.js';
|
|
4
|
+
// Patterns for files/folders to delete in deep clean mode
|
|
5
|
+
const DEEP_CLEAN_PATTERNS = {
|
|
6
|
+
// Documentation files
|
|
7
|
+
files: [
|
|
8
|
+
'README.md',
|
|
9
|
+
'README.markdown',
|
|
10
|
+
'README.txt',
|
|
11
|
+
'readme.md',
|
|
12
|
+
'readme.txt',
|
|
13
|
+
'CHANGELOG.md',
|
|
14
|
+
'CHANGELOG.txt',
|
|
15
|
+
'CHANGES.md',
|
|
16
|
+
'HISTORY.md',
|
|
17
|
+
'LICENSE',
|
|
18
|
+
'LICENSE.md',
|
|
19
|
+
'LICENSE.txt',
|
|
20
|
+
'license',
|
|
21
|
+
'LICENCE',
|
|
22
|
+
'LICENCE.md',
|
|
23
|
+
'COPYING',
|
|
24
|
+
'AUTHORS',
|
|
25
|
+
'CONTRIBUTORS',
|
|
26
|
+
'.npmignore',
|
|
27
|
+
'.gitignore',
|
|
28
|
+
'.eslintrc',
|
|
29
|
+
'.eslintrc.js',
|
|
30
|
+
'.eslintrc.json',
|
|
31
|
+
'.prettierrc',
|
|
32
|
+
'.prettierrc.js',
|
|
33
|
+
'.prettierrc.json',
|
|
34
|
+
'.babelrc',
|
|
35
|
+
'.editorconfig',
|
|
36
|
+
'tsconfig.json',
|
|
37
|
+
'tslint.json',
|
|
38
|
+
'.travis.yml',
|
|
39
|
+
'appveyor.yml',
|
|
40
|
+
'Makefile',
|
|
41
|
+
'Gulpfile.js',
|
|
42
|
+
'Gruntfile.js',
|
|
43
|
+
'rollup.config.js',
|
|
44
|
+
'webpack.config.js',
|
|
45
|
+
],
|
|
46
|
+
// File extensions to remove
|
|
47
|
+
extensions: [
|
|
48
|
+
'.md',
|
|
49
|
+
'.markdown',
|
|
50
|
+
'.map',
|
|
51
|
+
'.ts.map',
|
|
52
|
+
'.js.map',
|
|
53
|
+
'.min.map',
|
|
54
|
+
],
|
|
55
|
+
// Directories to remove
|
|
56
|
+
directories: [
|
|
57
|
+
'test',
|
|
58
|
+
'tests',
|
|
59
|
+
'__tests__',
|
|
60
|
+
'spec',
|
|
61
|
+
'specs',
|
|
62
|
+
'example',
|
|
63
|
+
'examples',
|
|
64
|
+
'doc',
|
|
65
|
+
'docs',
|
|
66
|
+
'documentation',
|
|
67
|
+
'coverage',
|
|
68
|
+
'.nyc_output',
|
|
69
|
+
'.github',
|
|
70
|
+
'.vscode',
|
|
71
|
+
'.idea',
|
|
72
|
+
'benchmark',
|
|
73
|
+
'benchmarks',
|
|
74
|
+
'fixtures',
|
|
75
|
+
'__fixtures__',
|
|
76
|
+
'__mocks__',
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Deep clean a node_modules folder by removing unnecessary files
|
|
81
|
+
*/
|
|
82
|
+
export async function deepClean(nodeModulesPath, options = {}, onProgress) {
|
|
83
|
+
const result = {
|
|
84
|
+
deletedFileCount: 0,
|
|
85
|
+
processedFolders: 0,
|
|
86
|
+
freedBytes: 0,
|
|
87
|
+
deletedFiles: options.verbose ? [] : undefined,
|
|
88
|
+
};
|
|
89
|
+
// Get all package directories
|
|
90
|
+
const packages = await getPackageDirectories(nodeModulesPath);
|
|
91
|
+
result.processedFolders = packages.length;
|
|
92
|
+
for (const packagePath of packages) {
|
|
93
|
+
const packageName = path.basename(packagePath);
|
|
94
|
+
onProgress?.(packageName);
|
|
95
|
+
// Clean files
|
|
96
|
+
for (const filename of DEEP_CLEAN_PATTERNS.files) {
|
|
97
|
+
const filePath = path.join(packagePath, filename);
|
|
98
|
+
const freed = await safeDelete(filePath, options.dryRun);
|
|
99
|
+
if (freed > 0) {
|
|
100
|
+
result.deletedFileCount++;
|
|
101
|
+
result.freedBytes += freed;
|
|
102
|
+
result.deletedFiles?.push(filePath);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Clean by extension
|
|
106
|
+
await cleanByExtension(packagePath, DEEP_CLEAN_PATTERNS.extensions, result, options);
|
|
107
|
+
// Clean directories
|
|
108
|
+
for (const dirName of DEEP_CLEAN_PATTERNS.directories) {
|
|
109
|
+
const dirPath = path.join(packagePath, dirName);
|
|
110
|
+
const freed = await safeDeleteDir(dirPath, options.dryRun);
|
|
111
|
+
if (freed > 0) {
|
|
112
|
+
result.deletedFileCount++;
|
|
113
|
+
result.freedBytes += freed;
|
|
114
|
+
result.deletedFiles?.push(dirPath);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get all package directories (including scoped packages)
|
|
122
|
+
*/
|
|
123
|
+
async function getPackageDirectories(nodeModulesPath) {
|
|
124
|
+
const packages = [];
|
|
125
|
+
try {
|
|
126
|
+
const items = await fs.readdir(nodeModulesPath);
|
|
127
|
+
for (const item of items) {
|
|
128
|
+
if (item.startsWith('.'))
|
|
129
|
+
continue;
|
|
130
|
+
const itemPath = path.join(nodeModulesPath, item);
|
|
131
|
+
const stats = await fs.stat(itemPath);
|
|
132
|
+
if (!stats.isDirectory())
|
|
133
|
+
continue;
|
|
134
|
+
if (item.startsWith('@')) {
|
|
135
|
+
// Scoped package - get subpackages
|
|
136
|
+
const scopedItems = await fs.readdir(itemPath);
|
|
137
|
+
for (const scopedItem of scopedItems) {
|
|
138
|
+
packages.push(path.join(itemPath, scopedItem));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
packages.push(itemPath);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
logger.debug(`Error reading ${nodeModulesPath}`);
|
|
148
|
+
}
|
|
149
|
+
return packages;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Clean files by extension in a directory
|
|
153
|
+
*/
|
|
154
|
+
async function cleanByExtension(dirPath, extensions, result, options) {
|
|
155
|
+
try {
|
|
156
|
+
const items = await fs.readdir(dirPath);
|
|
157
|
+
for (const item of items) {
|
|
158
|
+
for (const ext of extensions) {
|
|
159
|
+
if (item.endsWith(ext)) {
|
|
160
|
+
const filePath = path.join(dirPath, item);
|
|
161
|
+
const freed = await safeDelete(filePath, options.dryRun);
|
|
162
|
+
if (freed > 0) {
|
|
163
|
+
result.deletedFileCount++;
|
|
164
|
+
result.freedBytes += freed;
|
|
165
|
+
result.deletedFiles?.push(filePath);
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// Ignore errors
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Safely delete a file and return its size
|
|
178
|
+
*/
|
|
179
|
+
async function safeDelete(filePath, dryRun = false) {
|
|
180
|
+
try {
|
|
181
|
+
const stats = await fs.stat(filePath);
|
|
182
|
+
if (!stats.isFile())
|
|
183
|
+
return 0;
|
|
184
|
+
const size = stats.size;
|
|
185
|
+
if (!dryRun) {
|
|
186
|
+
await fs.remove(filePath);
|
|
187
|
+
}
|
|
188
|
+
return size;
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return 0;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Safely delete a directory and return its size
|
|
196
|
+
*/
|
|
197
|
+
async function safeDeleteDir(dirPath, dryRun = false) {
|
|
198
|
+
try {
|
|
199
|
+
const stats = await fs.stat(dirPath);
|
|
200
|
+
if (!stats.isDirectory())
|
|
201
|
+
return 0;
|
|
202
|
+
// Calculate size
|
|
203
|
+
let size = 0;
|
|
204
|
+
const calculateSize = async (dir) => {
|
|
205
|
+
const items = await fs.readdir(dir);
|
|
206
|
+
for (const item of items) {
|
|
207
|
+
const itemPath = path.join(dir, item);
|
|
208
|
+
const itemStats = await fs.stat(itemPath);
|
|
209
|
+
if (itemStats.isDirectory()) {
|
|
210
|
+
await calculateSize(itemPath);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
size += itemStats.size;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
await calculateSize(dirPath);
|
|
218
|
+
if (!dryRun) {
|
|
219
|
+
await fs.remove(dirPath);
|
|
220
|
+
}
|
|
221
|
+
return size;
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return 0;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
export default { deepClean };
|
|
228
|
+
//# sourceMappingURL=deep-cleaner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deep-cleaner.js","sourceRoot":"","sources":["../../src/core/deep-cleaner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { NodeModulesInfo, ScanOptions } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Scan for node_modules folders
|
|
4
|
+
*/
|
|
5
|
+
export declare function scanNodeModules(options: ScanOptions, onProgress?: (current: number, found: number, path: string) => void): Promise<NodeModulesInfo[]>;
|
|
6
|
+
/**
|
|
7
|
+
* Filter folders by age
|
|
8
|
+
*/
|
|
9
|
+
export declare function filterByAge(folders: NodeModulesInfo[], minAgeDays?: number, maxAgeDays?: number): NodeModulesInfo[];
|
|
10
|
+
/**
|
|
11
|
+
* Filter folders by size
|
|
12
|
+
*/
|
|
13
|
+
export declare function filterBySize(folders: NodeModulesInfo[], minSize?: number, maxSize?: number): NodeModulesInfo[];
|
|
14
|
+
/**
|
|
15
|
+
* Filter folders that have lockfile
|
|
16
|
+
*/
|
|
17
|
+
export declare function filterWithLockfile(folders: NodeModulesInfo[]): NodeModulesInfo[];
|
|
18
|
+
/**
|
|
19
|
+
* Calculate totals
|
|
20
|
+
*/
|
|
21
|
+
export declare function calculateTotals(folders: NodeModulesInfo[]): {
|
|
22
|
+
totalFolders: number;
|
|
23
|
+
totalSize: number;
|
|
24
|
+
oldestAge: number;
|
|
25
|
+
newestAge: number;
|
|
26
|
+
};
|
|
27
|
+
declare const _default: {
|
|
28
|
+
scanNodeModules: typeof scanNodeModules;
|
|
29
|
+
filterByAge: typeof filterByAge;
|
|
30
|
+
filterBySize: typeof filterBySize;
|
|
31
|
+
filterWithLockfile: typeof filterWithLockfile;
|
|
32
|
+
calculateTotals: typeof calculateTotals;
|
|
33
|
+
};
|
|
34
|
+
export default _default;
|
|
35
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/core/scanner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAwBtE;;GAEG;AACH,wBAAsB,eAAe,CACjC,OAAO,EAAE,WAAW,EACpB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GACpE,OAAO,CAAC,eAAe,EAAE,CAAC,CA4E5B;AA2CD;;GAEG;AACH,wBAAgB,WAAW,CACvB,OAAO,EAAE,eAAe,EAAE,EAC1B,UAAU,CAAC,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,GACpB,eAAe,EAAE,CAUnB;AAED;;GAEG;AACH,wBAAgB,YAAY,CACxB,OAAO,EAAE,eAAe,EAAE,EAC1B,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,GACjB,eAAe,EAAE,CAUnB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,eAAe,EAAE,CAIhF;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG;IACzD,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACrB,CAYA;;;;;;;;AAED,wBAME"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getFolderSizeFast, getLastModified, countPackages, fileExists, } from '../utils/fs-utils.js';
|
|
4
|
+
import { getAgeDays } from '../utils/formatter.js';
|
|
5
|
+
import logger from '../utils/logger.js';
|
|
6
|
+
// Folders to skip when scanning
|
|
7
|
+
const SKIP_FOLDERS = new Set([
|
|
8
|
+
'.git',
|
|
9
|
+
'.svn',
|
|
10
|
+
'.hg',
|
|
11
|
+
'.cache',
|
|
12
|
+
'.npm',
|
|
13
|
+
'.yarn',
|
|
14
|
+
'.pnpm-store',
|
|
15
|
+
'Library',
|
|
16
|
+
'Applications',
|
|
17
|
+
'.Trash',
|
|
18
|
+
]);
|
|
19
|
+
/**
|
|
20
|
+
* Scan for node_modules folders
|
|
21
|
+
*/
|
|
22
|
+
export async function scanNodeModules(options, onProgress) {
|
|
23
|
+
const results = [];
|
|
24
|
+
let scannedCount = 0;
|
|
25
|
+
async function scan(dir, depth) {
|
|
26
|
+
if (options.depth !== undefined && depth > options.depth) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
let items;
|
|
30
|
+
try {
|
|
31
|
+
items = await fs.readdir(dir);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// Permission denied or other error
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
for (const item of items) {
|
|
38
|
+
// Skip hidden and system folders
|
|
39
|
+
if (item.startsWith('.') || SKIP_FOLDERS.has(item)) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const itemPath = path.join(dir, item);
|
|
43
|
+
// Check exclusion patterns
|
|
44
|
+
if (options.excludePatterns?.some(pattern => {
|
|
45
|
+
if (pattern.includes('*')) {
|
|
46
|
+
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
|
|
47
|
+
return regex.test(itemPath);
|
|
48
|
+
}
|
|
49
|
+
return itemPath.includes(pattern);
|
|
50
|
+
})) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
let stats;
|
|
54
|
+
try {
|
|
55
|
+
stats = await fs.stat(itemPath);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (!stats.isDirectory()) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (item === 'node_modules') {
|
|
64
|
+
scannedCount++;
|
|
65
|
+
const projectPath = dir;
|
|
66
|
+
// Report progress
|
|
67
|
+
onProgress?.(scannedCount, results.length, projectPath);
|
|
68
|
+
// Get folder info
|
|
69
|
+
const info = await getNodeModulesInfo(itemPath, projectPath, options.quick);
|
|
70
|
+
results.push(info);
|
|
71
|
+
// Don't scan inside node_modules
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
// Recursively scan subdirectories
|
|
75
|
+
await scan(itemPath, depth + 1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const startPath = path.resolve(options.path);
|
|
79
|
+
logger.debug(`Starting scan from: ${startPath} `);
|
|
80
|
+
await scan(startPath, 0);
|
|
81
|
+
// Sort by size descending
|
|
82
|
+
results.sort((a, b) => b.size - a.size);
|
|
83
|
+
return results;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get detailed info about a node_modules folder
|
|
87
|
+
*/
|
|
88
|
+
async function getNodeModulesInfo(nodeModulesPath, projectPath, quick = false) {
|
|
89
|
+
// Get basic info
|
|
90
|
+
const lastModified = await getLastModified(nodeModulesPath);
|
|
91
|
+
const ageDays = getAgeDays(lastModified);
|
|
92
|
+
// Get size (optional for quick mode)
|
|
93
|
+
let size = 0;
|
|
94
|
+
if (!quick) {
|
|
95
|
+
size = await getFolderSizeFast(nodeModulesPath);
|
|
96
|
+
}
|
|
97
|
+
// Get package count
|
|
98
|
+
const packageCount = await countPackages(nodeModulesPath);
|
|
99
|
+
// Check for lockfiles
|
|
100
|
+
const [hasPackageLock, hasYarnLock, hasPnpmLock] = await Promise.all([
|
|
101
|
+
fileExists(path.join(projectPath, 'package-lock.json')),
|
|
102
|
+
fileExists(path.join(projectPath, 'yarn.lock')),
|
|
103
|
+
fileExists(path.join(projectPath, 'pnpm-lock.yaml')),
|
|
104
|
+
]);
|
|
105
|
+
return {
|
|
106
|
+
path: nodeModulesPath,
|
|
107
|
+
projectPath,
|
|
108
|
+
size,
|
|
109
|
+
lastModified,
|
|
110
|
+
packageCount,
|
|
111
|
+
hasPackageLock,
|
|
112
|
+
hasYarnLock,
|
|
113
|
+
hasPnpmLock,
|
|
114
|
+
ageDays,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Filter folders by age
|
|
119
|
+
*/
|
|
120
|
+
export function filterByAge(folders, minAgeDays, maxAgeDays) {
|
|
121
|
+
return folders.filter(folder => {
|
|
122
|
+
if (minAgeDays !== undefined && folder.ageDays < minAgeDays) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
if (maxAgeDays !== undefined && folder.ageDays > maxAgeDays) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
return true;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Filter folders by size
|
|
133
|
+
*/
|
|
134
|
+
export function filterBySize(folders, minSize, maxSize) {
|
|
135
|
+
return folders.filter(folder => {
|
|
136
|
+
if (minSize !== undefined && folder.size < minSize) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
if (maxSize !== undefined && folder.size > maxSize) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Filter folders that have lockfile
|
|
147
|
+
*/
|
|
148
|
+
export function filterWithLockfile(folders) {
|
|
149
|
+
return folders.filter(folder => folder.hasPackageLock || folder.hasYarnLock || folder.hasPnpmLock);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Calculate totals
|
|
153
|
+
*/
|
|
154
|
+
export function calculateTotals(folders) {
|
|
155
|
+
if (folders.length === 0) {
|
|
156
|
+
return { totalFolders: 0, totalSize: 0, oldestAge: 0, newestAge: 0 };
|
|
157
|
+
}
|
|
158
|
+
const totalFolders = folders.length;
|
|
159
|
+
const totalSize = folders.reduce((sum, f) => sum + f.size, 0);
|
|
160
|
+
const ages = folders.map(f => f.ageDays);
|
|
161
|
+
const oldestAge = Math.max(...ages);
|
|
162
|
+
const newestAge = Math.min(...ages);
|
|
163
|
+
return { totalFolders, totalSize, oldestAge, newestAge };
|
|
164
|
+
}
|
|
165
|
+
export default {
|
|
166
|
+
scanNodeModules,
|
|
167
|
+
filterByAge,
|
|
168
|
+
filterBySize,
|
|
169
|
+
filterWithLockfile,
|
|
170
|
+
calculateTotals,
|
|
171
|
+
};
|
|
172
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../../src/core/scanner.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,OAAO,GAAG,aAAa,EAAE,CAAC;AAChC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
|