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.
@@ -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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"}
@@ -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;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,CA2F5B;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"}
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"}
@@ -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
  };