node-janitor 1.0.1 → 1.2.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 +118 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +119 -7
- package/dist/cli.js.map +1 -1
- package/dist/commands/schedule.d.ts +60 -0
- package/dist/commands/schedule.d.ts.map +1 -0
- package/dist/commands/schedule.js +139 -0
- package/dist/commands/schedule.js.map +1 -0
- package/dist/commands/watch.d.ts +57 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +138 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/core/config.d.ts +21 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +103 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/fast-scanner.d.ts +16 -0
- package/dist/core/fast-scanner.d.ts.map +1 -0
- package/dist/core/fast-scanner.js +150 -0
- package/dist/core/fast-scanner.js.map +1 -0
- package/dist/core/scanner.d.ts +6 -0
- package/dist/core/scanner.d.ts.map +1 -1
- package/dist/core/scanner.js +40 -0
- package/dist/core/scanner.js.map +1 -1
- package/dist/tui/live-view.d.ts +13 -0
- package/dist/tui/live-view.d.ts.map +1 -0
- package/dist/tui/live-view.js +243 -0
- package/dist/tui/live-view.js.map +1 -0
- package/dist/utils/git-utils.d.ts +40 -0
- package/dist/utils/git-utils.d.ts.map +1 -0
- package/dist/utils/git-utils.js +122 -0
- package/dist/utils/git-utils.js.map +1 -0
- package/dist/utils/i18n.d.ts +60 -0
- package/dist/utils/i18n.d.ts.map +1 -0
- package/dist/utils/i18n.js +302 -0
- package/dist/utils/i18n.js.map +1 -0
- package/package.json +7 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { NodeModulesInfo } from '../types/index.js';
|
|
2
|
+
export interface WatchOptions {
|
|
3
|
+
path: string;
|
|
4
|
+
depth?: number;
|
|
5
|
+
olderThan?: string;
|
|
6
|
+
dryRun?: boolean;
|
|
7
|
+
interval?: number;
|
|
8
|
+
onClean?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface WatchState {
|
|
11
|
+
isWatching: boolean;
|
|
12
|
+
lastScan: Date | null;
|
|
13
|
+
foldersFound: number;
|
|
14
|
+
totalCleaned: number;
|
|
15
|
+
totalFreed: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Create initial watch state
|
|
19
|
+
*/
|
|
20
|
+
export declare function createInitialWatchState(): WatchState;
|
|
21
|
+
/**
|
|
22
|
+
* Parse watch interval with default
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseWatchInterval(interval?: number): number;
|
|
25
|
+
/**
|
|
26
|
+
* Parse olderThan to days
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseOlderThanDays(olderThan?: string): number | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Start watching a directory for node_modules folders
|
|
31
|
+
*/
|
|
32
|
+
export declare function startWatch(options: WatchOptions): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Perform a scan and optionally clean (exported for testing)
|
|
35
|
+
*/
|
|
36
|
+
export declare function performScan(watchPath: string, options: WatchOptions, olderThanDays: number | undefined, state: WatchState): Promise<{
|
|
37
|
+
found: number;
|
|
38
|
+
cleaned: number;
|
|
39
|
+
freed: number;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Auto-clean found folders (exported for testing)
|
|
43
|
+
*/
|
|
44
|
+
export declare function performAutoClean(folders: NodeModulesInfo[], state: WatchState): Promise<{
|
|
45
|
+
cleaned: number;
|
|
46
|
+
freed: number;
|
|
47
|
+
}>;
|
|
48
|
+
declare const _default: {
|
|
49
|
+
startWatch: typeof startWatch;
|
|
50
|
+
performScan: typeof performScan;
|
|
51
|
+
performAutoClean: typeof performAutoClean;
|
|
52
|
+
createInitialWatchState: typeof createInitialWatchState;
|
|
53
|
+
parseWatchInterval: typeof parseWatchInterval;
|
|
54
|
+
parseOlderThanDays: typeof parseOlderThanDays;
|
|
55
|
+
};
|
|
56
|
+
export default _default;
|
|
57
|
+
//# sourceMappingURL=watch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/commands/watch.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,MAAM,WAAW,YAAY;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACvB,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,UAAU,CAQpD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEzE;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCrE;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC7B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,YAAY,EACrB,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,KAAK,EAAE,UAAU,GAClB,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CA8C5D;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAClC,OAAO,EAAE,eAAe,EAAE,EAC1B,KAAK,EAAE,UAAU,GAClB,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAa7C;;;;;;;;;AAED,wBAOE"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { scanNodeModules, calculateTotals } from '../core/scanner.js';
|
|
3
|
+
import { cleanNodeModules } from '../core/cleaner.js';
|
|
4
|
+
import { formatBytes, parseDuration } from '../utils/formatter.js';
|
|
5
|
+
import logger from '../utils/logger.js';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
/**
|
|
8
|
+
* Create initial watch state
|
|
9
|
+
*/
|
|
10
|
+
export function createInitialWatchState() {
|
|
11
|
+
return {
|
|
12
|
+
isWatching: true,
|
|
13
|
+
lastScan: null,
|
|
14
|
+
foldersFound: 0,
|
|
15
|
+
totalCleaned: 0,
|
|
16
|
+
totalFreed: 0,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parse watch interval with default
|
|
21
|
+
*/
|
|
22
|
+
export function parseWatchInterval(interval) {
|
|
23
|
+
return interval || 60000; // Default 1 minute
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Parse olderThan to days
|
|
27
|
+
*/
|
|
28
|
+
export function parseOlderThanDays(olderThan) {
|
|
29
|
+
return olderThan ? parseDuration(olderThan) : undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Start watching a directory for node_modules folders
|
|
33
|
+
*/
|
|
34
|
+
export async function startWatch(options) {
|
|
35
|
+
const watchPath = path.resolve(options.path);
|
|
36
|
+
const interval = parseWatchInterval(options.interval);
|
|
37
|
+
const olderThanDays = parseOlderThanDays(options.olderThan);
|
|
38
|
+
const state = createInitialWatchState();
|
|
39
|
+
console.log(chalk.cyan('🔄 Watch Mode Started'));
|
|
40
|
+
console.log(chalk.gray(`Watching: ${watchPath}`));
|
|
41
|
+
console.log(chalk.gray(`Interval: ${interval / 1000}s`));
|
|
42
|
+
if (olderThanDays) {
|
|
43
|
+
console.log(chalk.gray(`Age filter: >${olderThanDays} days`));
|
|
44
|
+
}
|
|
45
|
+
if (options.onClean) {
|
|
46
|
+
console.log(chalk.yellow('⚠️ Auto-clean enabled'));
|
|
47
|
+
}
|
|
48
|
+
console.log(chalk.gray('Press Ctrl+C to stop\n'));
|
|
49
|
+
// Initial scan
|
|
50
|
+
await performScan(watchPath, options, olderThanDays, state);
|
|
51
|
+
// Set up interval for periodic scanning
|
|
52
|
+
const intervalId = setInterval(async () => {
|
|
53
|
+
await performScan(watchPath, options, olderThanDays, state);
|
|
54
|
+
}, interval);
|
|
55
|
+
// Handle graceful shutdown
|
|
56
|
+
process.on('SIGINT', () => {
|
|
57
|
+
clearInterval(intervalId);
|
|
58
|
+
console.log();
|
|
59
|
+
logger.success('Watch mode stopped');
|
|
60
|
+
console.log(chalk.gray(`Total cleaned: ${state.totalCleaned} folders`));
|
|
61
|
+
console.log(chalk.gray(`Total freed: ${formatBytes(state.totalFreed)}`));
|
|
62
|
+
process.exit(0);
|
|
63
|
+
});
|
|
64
|
+
// Keep the process running
|
|
65
|
+
await new Promise(() => { }); // Never resolves, wait for SIGINT
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Perform a scan and optionally clean (exported for testing)
|
|
69
|
+
*/
|
|
70
|
+
export async function performScan(watchPath, options, olderThanDays, state) {
|
|
71
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
72
|
+
process.stdout.write(chalk.gray(`[${timestamp}] Scanning... `));
|
|
73
|
+
try {
|
|
74
|
+
let folders = await scanNodeModules({
|
|
75
|
+
path: watchPath,
|
|
76
|
+
depth: options.depth,
|
|
77
|
+
quick: true, // Use quick mode for watching
|
|
78
|
+
});
|
|
79
|
+
// Apply age filter
|
|
80
|
+
if (olderThanDays !== undefined) {
|
|
81
|
+
folders = folders.filter(f => f.ageDays >= olderThanDays);
|
|
82
|
+
}
|
|
83
|
+
state.lastScan = new Date();
|
|
84
|
+
state.foldersFound = folders.length;
|
|
85
|
+
if (folders.length === 0) {
|
|
86
|
+
console.log(chalk.green('✓ No matching folders'));
|
|
87
|
+
return { found: 0, cleaned: 0, freed: 0 };
|
|
88
|
+
}
|
|
89
|
+
const totals = calculateTotals(folders);
|
|
90
|
+
console.log(chalk.yellow(`Found ${folders.length} folders (${formatBytes(totals.totalSize)})`));
|
|
91
|
+
// Auto-clean if enabled
|
|
92
|
+
if (options.onClean && !options.dryRun) {
|
|
93
|
+
const cleanResult = await performAutoClean(folders, state);
|
|
94
|
+
return { found: folders.length, ...cleanResult };
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// Just list folders
|
|
98
|
+
folders.slice(0, 5).forEach(f => {
|
|
99
|
+
console.log(chalk.gray(` → ${f.projectPath} (${f.ageDays}d)`));
|
|
100
|
+
});
|
|
101
|
+
if (folders.length > 5) {
|
|
102
|
+
console.log(chalk.gray(` ... and ${folders.length - 5} more`));
|
|
103
|
+
}
|
|
104
|
+
return { found: folders.length, cleaned: 0, freed: 0 };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
console.log(chalk.red('✗ Scan failed'));
|
|
109
|
+
logger.debug(error instanceof Error ? error.message : String(error));
|
|
110
|
+
return { found: 0, cleaned: 0, freed: 0 };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Auto-clean found folders (exported for testing)
|
|
115
|
+
*/
|
|
116
|
+
export async function performAutoClean(folders, state) {
|
|
117
|
+
try {
|
|
118
|
+
const result = await cleanNodeModules(folders, { fast: true });
|
|
119
|
+
state.totalCleaned += result.deletedCount;
|
|
120
|
+
state.totalFreed += result.freedBytes;
|
|
121
|
+
console.log(chalk.green(` ✓ Cleaned ${result.deletedCount} folders (${formatBytes(result.freedBytes)})`));
|
|
122
|
+
return { cleaned: result.deletedCount, freed: result.freedBytes };
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
console.log(chalk.red(' ✗ Clean failed'));
|
|
126
|
+
logger.debug(error instanceof Error ? error.message : String(error));
|
|
127
|
+
return { cleaned: 0, freed: 0 };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export default {
|
|
131
|
+
startWatch,
|
|
132
|
+
performScan,
|
|
133
|
+
performAutoClean,
|
|
134
|
+
createInitialWatchState,
|
|
135
|
+
parseWatchInterval,
|
|
136
|
+
parseOlderThanDays,
|
|
137
|
+
};
|
|
138
|
+
//# sourceMappingURL=watch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch.js","sourceRoot":"","sources":["../../src/commands/watch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Config } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Load configuration from .janitorrc, .janitorrc.json, .janitorrc.yaml,
|
|
4
|
+
* janitor.config.js, or package.json "janitor" key
|
|
5
|
+
*/
|
|
6
|
+
export declare function loadConfig(configPath?: string): Promise<Config>;
|
|
7
|
+
/**
|
|
8
|
+
* Get default configuration
|
|
9
|
+
*/
|
|
10
|
+
export declare function getDefaultConfig(): Config;
|
|
11
|
+
/**
|
|
12
|
+
* Merge CLI options with config file options (CLI takes precedence)
|
|
13
|
+
*/
|
|
14
|
+
export declare function mergeOptions(cliOptions: Record<string, unknown>, config: Config): Record<string, unknown>;
|
|
15
|
+
declare const _default: {
|
|
16
|
+
loadConfig: typeof loadConfig;
|
|
17
|
+
getDefaultConfig: typeof getDefaultConfig;
|
|
18
|
+
mergeOptions: typeof mergeOptions;
|
|
19
|
+
};
|
|
20
|
+
export default _default;
|
|
21
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAKhD;;;GAGG;AACH,wBAAsB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgCrE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CASzC;AAwBD;;GAEG;AACH,wBAAgB,YAAY,CACxB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,MAAM,EAAE,MAAM,GACf,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA0BzB;;;;;;AAED,wBAIE"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { cosmiconfig } from 'cosmiconfig';
|
|
2
|
+
import logger from '../utils/logger.js';
|
|
3
|
+
const MODULE_NAME = 'janitor';
|
|
4
|
+
/**
|
|
5
|
+
* Load configuration from .janitorrc, .janitorrc.json, .janitorrc.yaml,
|
|
6
|
+
* janitor.config.js, or package.json "janitor" key
|
|
7
|
+
*/
|
|
8
|
+
export async function loadConfig(configPath) {
|
|
9
|
+
const explorer = cosmiconfig(MODULE_NAME, {
|
|
10
|
+
searchPlaces: [
|
|
11
|
+
'package.json',
|
|
12
|
+
`.${MODULE_NAME}rc`,
|
|
13
|
+
`.${MODULE_NAME}rc.json`,
|
|
14
|
+
`.${MODULE_NAME}rc.yaml`,
|
|
15
|
+
`.${MODULE_NAME}rc.yml`,
|
|
16
|
+
`.${MODULE_NAME}rc.js`,
|
|
17
|
+
`.${MODULE_NAME}rc.cjs`,
|
|
18
|
+
`${MODULE_NAME}.config.js`,
|
|
19
|
+
`${MODULE_NAME}.config.cjs`,
|
|
20
|
+
],
|
|
21
|
+
});
|
|
22
|
+
try {
|
|
23
|
+
let result;
|
|
24
|
+
if (configPath) {
|
|
25
|
+
result = await explorer.load(configPath);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
result = await explorer.search();
|
|
29
|
+
}
|
|
30
|
+
if (result && result.config) {
|
|
31
|
+
logger.debug(`Loaded config from: ${result.filepath}`);
|
|
32
|
+
return validateConfig(result.config);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
logger.debug(`Config load error: ${error instanceof Error ? error.message : String(error)}`);
|
|
37
|
+
}
|
|
38
|
+
return getDefaultConfig();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get default configuration
|
|
42
|
+
*/
|
|
43
|
+
export function getDefaultConfig() {
|
|
44
|
+
return {
|
|
45
|
+
exclude: [],
|
|
46
|
+
excludePattern: [],
|
|
47
|
+
defaultOlderThan: undefined,
|
|
48
|
+
defaultPath: undefined,
|
|
49
|
+
defaultDepth: undefined,
|
|
50
|
+
lang: 'en',
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Validate and normalize config
|
|
55
|
+
*/
|
|
56
|
+
function validateConfig(config) {
|
|
57
|
+
const defaultConfig = getDefaultConfig();
|
|
58
|
+
if (typeof config !== 'object' || config === null) {
|
|
59
|
+
return defaultConfig;
|
|
60
|
+
}
|
|
61
|
+
const cfg = config;
|
|
62
|
+
return {
|
|
63
|
+
exclude: Array.isArray(cfg.exclude) ? cfg.exclude.filter(s => typeof s === 'string') : defaultConfig.exclude,
|
|
64
|
+
excludePattern: Array.isArray(cfg.excludePattern) ? cfg.excludePattern.filter(s => typeof s === 'string') : defaultConfig.excludePattern,
|
|
65
|
+
defaultOlderThan: typeof cfg.defaultOlderThan === 'string' ? cfg.defaultOlderThan : defaultConfig.defaultOlderThan,
|
|
66
|
+
defaultPath: typeof cfg.defaultPath === 'string' ? cfg.defaultPath : defaultConfig.defaultPath,
|
|
67
|
+
defaultDepth: typeof cfg.defaultDepth === 'number' ? cfg.defaultDepth : defaultConfig.defaultDepth,
|
|
68
|
+
lang: typeof cfg.lang === 'string' ? cfg.lang : defaultConfig.lang,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Merge CLI options with config file options (CLI takes precedence)
|
|
73
|
+
*/
|
|
74
|
+
export function mergeOptions(cliOptions, config) {
|
|
75
|
+
const merged = { ...cliOptions };
|
|
76
|
+
// Apply config defaults only if CLI option is not provided
|
|
77
|
+
if (!merged.path && config.defaultPath) {
|
|
78
|
+
merged.path = config.defaultPath;
|
|
79
|
+
}
|
|
80
|
+
if (!merged.depth && config.defaultDepth) {
|
|
81
|
+
merged.depth = config.defaultDepth;
|
|
82
|
+
}
|
|
83
|
+
if (!merged.olderThan && config.defaultOlderThan) {
|
|
84
|
+
merged.olderThan = config.defaultOlderThan;
|
|
85
|
+
}
|
|
86
|
+
if (!merged.lang && config.lang) {
|
|
87
|
+
merged.lang = config.lang;
|
|
88
|
+
}
|
|
89
|
+
// Merge exclude patterns
|
|
90
|
+
if (config.exclude && config.exclude.length > 0) {
|
|
91
|
+
const cliExclude = merged.exclude || '';
|
|
92
|
+
const cliPatterns = cliExclude ? cliExclude.split(',').map(s => s.trim()) : [];
|
|
93
|
+
const allPatterns = [...new Set([...cliPatterns, ...config.exclude])];
|
|
94
|
+
merged.exclude = allPatterns.join(',');
|
|
95
|
+
}
|
|
96
|
+
return merged;
|
|
97
|
+
}
|
|
98
|
+
export default {
|
|
99
|
+
loadConfig,
|
|
100
|
+
getDefaultConfig,
|
|
101
|
+
mergeOptions,
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { NodeModulesInfo, ScanOptions } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Ultra-fast scanner using native OS commands
|
|
4
|
+
* Uses `find` on Unix for blazing fast directory discovery
|
|
5
|
+
*/
|
|
6
|
+
export declare function scanNodeModulesFast(options: ScanOptions, onProgress?: (current: number, found: number, path: string) => void): Promise<NodeModulesInfo[]>;
|
|
7
|
+
/**
|
|
8
|
+
* Use native find command for blazing fast discovery
|
|
9
|
+
*/
|
|
10
|
+
declare function findNodeModulesNative(startPath: string, maxDepth?: number): string[];
|
|
11
|
+
declare const _default: {
|
|
12
|
+
scanNodeModulesFast: typeof scanNodeModulesFast;
|
|
13
|
+
findNodeModulesNative: typeof findNodeModulesNative;
|
|
14
|
+
};
|
|
15
|
+
export default _default;
|
|
16
|
+
//# sourceMappingURL=fast-scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fast-scanner.d.ts","sourceRoot":"","sources":["../../src/core/fast-scanner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAUtE;;;GAGG;AACH,wBAAsB,mBAAmB,CACrC,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,CA8E5B;AAED;;GAEG;AACH,iBAAS,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CA4B7E;;;;;AAsDD,wBAGE"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import pLimit from 'p-limit';
|
|
4
|
+
import { getFolderSizeFast, getLastModified, countPackages, fileExists, } from '../utils/fs-utils.js';
|
|
5
|
+
import { getAgeDays } from '../utils/formatter.js';
|
|
6
|
+
import logger from '../utils/logger.js';
|
|
7
|
+
/**
|
|
8
|
+
* Ultra-fast scanner using native OS commands
|
|
9
|
+
* Uses `find` on Unix for blazing fast directory discovery
|
|
10
|
+
*/
|
|
11
|
+
export async function scanNodeModulesFast(options, onProgress) {
|
|
12
|
+
const startPath = path.resolve(options.path);
|
|
13
|
+
logger.debug(`Fast scan starting from: ${startPath}`);
|
|
14
|
+
// Use native find command for discovery
|
|
15
|
+
const nodeModulesPaths = findNodeModulesNative(startPath, options.depth);
|
|
16
|
+
logger.debug(`Found ${nodeModulesPaths.length} node_modules via native find`);
|
|
17
|
+
if (nodeModulesPaths.length === 0) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
// Apply exclusion patterns and skip hidden/system folders
|
|
21
|
+
let filteredPaths = nodeModulesPaths.filter(p => {
|
|
22
|
+
// Skip paths containing hidden folders (starting with .)
|
|
23
|
+
const parts = p.split(path.sep);
|
|
24
|
+
const hasHidden = parts.some(part => part.startsWith('.') && part !== '.' && part !== '..');
|
|
25
|
+
if (hasHidden)
|
|
26
|
+
return false;
|
|
27
|
+
// Skip system folders
|
|
28
|
+
const skipFolders = ['Library', 'Applications', '.Trash', '.npm', '.yarn', '.pnpm-store'];
|
|
29
|
+
const hasSystemFolder = parts.some(part => skipFolders.includes(part));
|
|
30
|
+
if (hasSystemFolder)
|
|
31
|
+
return false;
|
|
32
|
+
return true;
|
|
33
|
+
});
|
|
34
|
+
// Apply user exclusion patterns
|
|
35
|
+
if (options.excludePatterns && options.excludePatterns.length > 0) {
|
|
36
|
+
filteredPaths = filteredPaths.filter(p => {
|
|
37
|
+
return !options.excludePatterns.some(pattern => {
|
|
38
|
+
if (pattern.includes('*')) {
|
|
39
|
+
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
|
|
40
|
+
return regex.test(p);
|
|
41
|
+
}
|
|
42
|
+
return p.includes(pattern);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
// Collect metadata in parallel
|
|
47
|
+
const limit = pLimit(10); // 10 concurrent operations
|
|
48
|
+
const results = [];
|
|
49
|
+
let completed = 0;
|
|
50
|
+
const promises = filteredPaths.map(nodeModulesPath => limit(async () => {
|
|
51
|
+
const projectPath = path.dirname(nodeModulesPath);
|
|
52
|
+
const info = await getNodeModulesInfoFast(nodeModulesPath, projectPath, options.quick);
|
|
53
|
+
completed++;
|
|
54
|
+
onProgress?.(completed, filteredPaths.length, projectPath);
|
|
55
|
+
return info;
|
|
56
|
+
}));
|
|
57
|
+
const collected = await Promise.all(promises);
|
|
58
|
+
results.push(...collected);
|
|
59
|
+
// Apply include pattern filter
|
|
60
|
+
let finalResults = results;
|
|
61
|
+
if (options.includePatterns && options.includePatterns.length > 0) {
|
|
62
|
+
finalResults = results.filter(folder => {
|
|
63
|
+
return options.includePatterns.some(pattern => {
|
|
64
|
+
if (pattern.includes('*')) {
|
|
65
|
+
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
|
|
66
|
+
return regex.test(folder.projectPath) || regex.test(folder.path);
|
|
67
|
+
}
|
|
68
|
+
return folder.projectPath.includes(pattern) || folder.path.includes(pattern);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// Sort by size descending
|
|
73
|
+
finalResults.sort((a, b) => b.size - a.size);
|
|
74
|
+
return finalResults;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Use native find command for blazing fast discovery
|
|
78
|
+
*/
|
|
79
|
+
function findNodeModulesNative(startPath, maxDepth) {
|
|
80
|
+
try {
|
|
81
|
+
// Build find command
|
|
82
|
+
let cmd;
|
|
83
|
+
const depthArg = maxDepth !== undefined ? `-maxdepth ${maxDepth + 1}` : '';
|
|
84
|
+
if (process.platform === 'win32') {
|
|
85
|
+
// Windows: use dir command
|
|
86
|
+
cmd = `dir /s /b /ad "${startPath}" 2>nul | findstr /i "\\\\node_modules$"`;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// Unix: use find with prune for efficiency
|
|
90
|
+
cmd = `find "${startPath}" ${depthArg} -type d -name "node_modules" -prune 2>/dev/null`;
|
|
91
|
+
}
|
|
92
|
+
const output = execSync(cmd, {
|
|
93
|
+
encoding: 'utf-8',
|
|
94
|
+
maxBuffer: 100 * 1024 * 1024, // 100MB buffer
|
|
95
|
+
timeout: 60000, // 60s timeout
|
|
96
|
+
});
|
|
97
|
+
return output
|
|
98
|
+
.trim()
|
|
99
|
+
.split('\n')
|
|
100
|
+
.filter(line => line.length > 0);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
logger.debug(`Native find failed, falling back to JS: ${error}`);
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get folder info with optimized parallel operations
|
|
109
|
+
*/
|
|
110
|
+
async function getNodeModulesInfoFast(nodeModulesPath, projectPath, quick = false) {
|
|
111
|
+
// Run independent operations in parallel
|
|
112
|
+
const [lastModified, hasPackageLock, hasYarnLock, hasPnpmLock,] = await Promise.all([
|
|
113
|
+
getLastModified(nodeModulesPath),
|
|
114
|
+
fileExists(path.join(projectPath, 'package-lock.json')),
|
|
115
|
+
fileExists(path.join(projectPath, 'yarn.lock')),
|
|
116
|
+
fileExists(path.join(projectPath, 'pnpm-lock.yaml')),
|
|
117
|
+
]);
|
|
118
|
+
const ageDays = getAgeDays(lastModified);
|
|
119
|
+
// Size and package count - only if needed
|
|
120
|
+
let size = 0;
|
|
121
|
+
let packageCount = 0;
|
|
122
|
+
if (!quick) {
|
|
123
|
+
// Run size and package count in parallel
|
|
124
|
+
[size, packageCount] = await Promise.all([
|
|
125
|
+
getFolderSizeFast(nodeModulesPath),
|
|
126
|
+
countPackages(nodeModulesPath),
|
|
127
|
+
]);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
// Quick mode: only get package count (fast)
|
|
131
|
+
packageCount = await countPackages(nodeModulesPath);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
path: nodeModulesPath,
|
|
135
|
+
projectPath,
|
|
136
|
+
size,
|
|
137
|
+
lastModified,
|
|
138
|
+
packageCount,
|
|
139
|
+
hasPackageLock,
|
|
140
|
+
hasYarnLock,
|
|
141
|
+
hasPnpmLock,
|
|
142
|
+
ageDays,
|
|
143
|
+
// Skip git status in fast mode - it's expensive
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
export default {
|
|
147
|
+
scanNodeModulesFast,
|
|
148
|
+
findNodeModulesNative,
|
|
149
|
+
};
|
|
150
|
+
//# sourceMappingURL=fast-scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fast-scanner.js","sourceRoot":"","sources":["../../src/core/fast-scanner.ts"],"names":[],"mappings":"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"}
|
package/dist/core/scanner.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { NodeModulesInfo, ScanOptions } from '../types/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Scan for node_modules folders
|
|
4
|
+
* Uses fast native scanner by default, falls back to JS scanner on error
|
|
4
5
|
*/
|
|
5
6
|
export declare function scanNodeModules(options: ScanOptions, onProgress?: (current: number, found: number, path: string) => void): Promise<NodeModulesInfo[]>;
|
|
6
7
|
/**
|
|
@@ -15,6 +16,10 @@ export declare function filterBySize(folders: NodeModulesInfo[], minSize?: numbe
|
|
|
15
16
|
* Filter folders that have lockfile
|
|
16
17
|
*/
|
|
17
18
|
export declare function filterWithLockfile(folders: NodeModulesInfo[]): NodeModulesInfo[];
|
|
19
|
+
/**
|
|
20
|
+
* Filter folders by git status (skip dirty repos)
|
|
21
|
+
*/
|
|
22
|
+
export declare function filterByGitStatus(folders: NodeModulesInfo[], skipDirty?: boolean, onlyInGitRepo?: boolean): NodeModulesInfo[];
|
|
18
23
|
/**
|
|
19
24
|
* Calculate totals
|
|
20
25
|
*/
|
|
@@ -28,6 +33,7 @@ declare const _default: {
|
|
|
28
33
|
scanNodeModules: typeof scanNodeModules;
|
|
29
34
|
filterByAge: typeof filterByAge;
|
|
30
35
|
filterBySize: typeof filterBySize;
|
|
36
|
+
filterByGitStatus: typeof filterByGitStatus;
|
|
31
37
|
filterWithLockfile: typeof filterWithLockfile;
|
|
32
38
|
calculateTotals: typeof calculateTotals;
|
|
33
39
|
};
|
|
@@ -1 +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;
|
|
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;AA0BtE;;;GAGG;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,CAe5B;AAkJD;;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,iBAAiB,CAC7B,OAAO,EAAE,eAAe,EAAE,EAC1B,SAAS,UAAQ,EACjB,aAAa,UAAQ,GACtB,eAAe,EAAE,CAUnB;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,wBAOE"}
|
package/dist/core/scanner.js
CHANGED
|
@@ -2,7 +2,9 @@ import fs from 'fs-extra';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { getFolderSizeFast, getLastModified, countPackages, fileExists, } from '../utils/fs-utils.js';
|
|
4
4
|
import { getAgeDays } from '../utils/formatter.js';
|
|
5
|
+
import { getGitStatus } from '../utils/git-utils.js';
|
|
5
6
|
import logger from '../utils/logger.js';
|
|
7
|
+
import { scanNodeModulesFast } from './fast-scanner.js';
|
|
6
8
|
// Folders to skip when scanning
|
|
7
9
|
const SKIP_FOLDERS = new Set([
|
|
8
10
|
'.git',
|
|
@@ -18,8 +20,28 @@ const SKIP_FOLDERS = new Set([
|
|
|
18
20
|
]);
|
|
19
21
|
/**
|
|
20
22
|
* Scan for node_modules folders
|
|
23
|
+
* Uses fast native scanner by default, falls back to JS scanner on error
|
|
21
24
|
*/
|
|
22
25
|
export async function scanNodeModules(options, onProgress) {
|
|
26
|
+
// Try fast scanner first (uses native find command)
|
|
27
|
+
try {
|
|
28
|
+
const fastResults = await scanNodeModulesFast(options, onProgress);
|
|
29
|
+
if (fastResults.length > 0 || options.quick) {
|
|
30
|
+
return fastResults;
|
|
31
|
+
}
|
|
32
|
+
// If no results, try JS scanner as fallback
|
|
33
|
+
logger.debug('Fast scanner returned 0 results, trying JS scanner');
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
logger.debug(`Fast scanner failed, using JS fallback: ${error}`);
|
|
37
|
+
}
|
|
38
|
+
// Fallback to JS scanner
|
|
39
|
+
return scanNodeModulesJS(options, onProgress);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Original JS-based scanner (fallback)
|
|
43
|
+
*/
|
|
44
|
+
async function scanNodeModulesJS(options, onProgress) {
|
|
23
45
|
const results = [];
|
|
24
46
|
let scannedCount = 0;
|
|
25
47
|
async function scan(dir, depth) {
|
|
@@ -116,6 +138,8 @@ async function getNodeModulesInfo(nodeModulesPath, projectPath, quick = false) {
|
|
|
116
138
|
fileExists(path.join(projectPath, 'yarn.lock')),
|
|
117
139
|
fileExists(path.join(projectPath, 'pnpm-lock.yaml')),
|
|
118
140
|
]);
|
|
141
|
+
// Get git status
|
|
142
|
+
const gitStatus = await getGitStatus(projectPath);
|
|
119
143
|
return {
|
|
120
144
|
path: nodeModulesPath,
|
|
121
145
|
projectPath,
|
|
@@ -126,6 +150,7 @@ async function getNodeModulesInfo(nodeModulesPath, projectPath, quick = false) {
|
|
|
126
150
|
hasYarnLock,
|
|
127
151
|
hasPnpmLock,
|
|
128
152
|
ageDays,
|
|
153
|
+
gitStatus,
|
|
129
154
|
};
|
|
130
155
|
}
|
|
131
156
|
/**
|
|
@@ -162,6 +187,20 @@ export function filterBySize(folders, minSize, maxSize) {
|
|
|
162
187
|
export function filterWithLockfile(folders) {
|
|
163
188
|
return folders.filter(folder => folder.hasPackageLock || folder.hasYarnLock || folder.hasPnpmLock);
|
|
164
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Filter folders by git status (skip dirty repos)
|
|
192
|
+
*/
|
|
193
|
+
export function filterByGitStatus(folders, skipDirty = false, onlyInGitRepo = false) {
|
|
194
|
+
return folders.filter(folder => {
|
|
195
|
+
if (onlyInGitRepo && !folder.gitStatus?.isGitRepo) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
if (skipDirty && folder.gitStatus?.isDirty) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
return true;
|
|
202
|
+
});
|
|
203
|
+
}
|
|
165
204
|
/**
|
|
166
205
|
* Calculate totals
|
|
167
206
|
*/
|
|
@@ -180,6 +219,7 @@ export default {
|
|
|
180
219
|
scanNodeModules,
|
|
181
220
|
filterByAge,
|
|
182
221
|
filterBySize,
|
|
222
|
+
filterByGitStatus,
|
|
183
223
|
filterWithLockfile,
|
|
184
224
|
calculateTotals,
|
|
185
225
|
};
|