Something went wrong. Try again.
Dedicated SSR (Server-side-rendered) Astro native Headless CMS, build from the ground up for the Astro community and by Astro community memb github.com/withstudiocms/studiocms
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200#!/usr/bin/env node
import { execSync } from 'node:child_process';import fs from 'node:fs';import path from 'node:path';
class NodeModulesCleaner { constructor(rootPath = process.cwd()) { this.rootPath = path.resolve(rootPath); this.foundDirectories = []; this.deletedDirectories = []; this.errors = []; }
async findNodeModules(dir = this.rootPath) { try { const entries = await fs.promises.readdir(dir, { withFileTypes: true });
for (const entry of entries) { const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) { if (entry.name === 'node_modules') { this.foundDirectories.push(fullPath); } else { // Skip common directories that shouldn't contain node_modules const skipDirs = ['.git', '.vscode', 'dist', 'build', 'coverage']; if (!skipDirs.includes(entry.name)) { await this.findNodeModules(fullPath); } } } } } catch (error) { this.errors.push({ path: dir, error: error.message }); } }
async deleteDirectory(dirPath) { try { await fs.promises.rm(dirPath, { recursive: true, force: true }); this.deletedDirectories.push(dirPath); return true; } catch (error) { this.errors.push({ path: dirPath, error: error.message }); return false; } }
async clean(dryRun = false) { console.log(`🔍 Scanning for node_modules directories in: ${this.rootPath}`);
await this.findNodeModules();
if (this.foundDirectories.length === 0) { console.log('✅ No node_modules directories found.'); return; }
console.log(`\n📁 Found ${this.foundDirectories.length} node_modules directories:`); this.foundDirectories.forEach((dir, index) => { console.log(` ${index + 1}. ${path.relative(this.rootPath, dir)}`); });
if (dryRun) { console.log('\n🔍 Dry run mode - no directories will be deleted.'); return; }
console.log('\n🗑️ Deleting directories...');
for (const dir of this.foundDirectories) { const relativePath = path.relative(this.rootPath, dir); process.stdout.write(` Deleting ${relativePath}... `);
const success = await this.deleteDirectory(dir); console.log(success ? '✅' : '❌'); }
// Summary console.log('\n📊 Summary:'); console.log(` ✅ Successfully deleted: ${this.deletedDirectories.length}`); console.log(` ❌ Failed to delete: ${this.errors.length}`);
if (this.errors.length > 0) { console.log('\n❌ Errors:');
for (const { path, error } of this.errors) { console.log(` ${path}: ${error}`); } } }
async getSizeInfo() { const sizes = []; const isWindows = process.platform === 'win32';
for (const dir of this.foundDirectories) { try { let size; if (isWindows) { // Use PowerShell on Windows const sizeOutput = execSync( `powershell -Command "(Get-ChildItem -Path '${dir}' -Recurse -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum"`, { encoding: 'utf8' } ); const bytes = Number.parseInt(sizeOutput.trim(), 10) || 0; size = bytes > 0 ? `${(bytes / 1024 / 1024).toFixed(1)}M` : '0B'; } else { // Use du command on Unix-like systems const sizeOutput = execSync(`du -sh "${dir}" 2>/dev/null || echo "0B"`, { encoding: 'utf8', }); size = sizeOutput.trim().split('\t')[0]; } sizes.push({ path: dir, size }); } catch (_error) { sizes.push({ path: dir, size: 'Unknown' }); } }
return sizes; }}
// CLI interfaceasync function main() { const args = process.argv.slice(2); const dryRun = args.includes('--dry-run') || args.includes('-n'); const showHelp = args.includes('--help') || args.includes('-h'); const showSizes = args.includes('--sizes') || args.includes('-s');
if (showHelp) { console.log(`Node Modules Cleaner - Tool for wiping 'node_module' folders recursively from a monorepo====================
Usage: node clean-node-modules.mjs [options] [path]
Options: --dry-run, -n Show what would be deleted without actually deleting --sizes, -s Show size information for each node_modules directory --help, -h Show this help message
Examples: node clean-node-modules.mjs # Clean current directory node clean-node-modules.mjs /path/to/monorepo # Clean specific directory node clean-node-modules.mjs --dry-run # Preview what would be deleted node clean-node-modules.mjs --sizes # Show sizes before cleaning`); return; }
// Get target directory (default to current directory) const targetPath = args.find((arg) => !arg.startsWith('--') && !arg.startsWith('-')) || process.cwd();
if (!fs.existsSync(targetPath)) { console.error(`❌ Error: Directory '${targetPath}' does not exist.`); process.exit(1); }
if (!fs.lstatSync(targetPath).isDirectory()) { console.error(`❌ Error: '${targetPath}' is not a directory.`); process.exit(1); }
const cleaner = new NodeModulesCleaner(targetPath);
if (showSizes) { console.log('📊 Calculating sizes...'); await cleaner.findNodeModules(); const sizes = await cleaner.getSizeInfo();
if (sizes.length === 0) { console.log('✅ No node_modules directories found.'); return; }
console.log('\n📁 Node modules directories and their sizes:');
for (const { path, size } of sizes) { console.log(` ${size.padStart(8)} ${path}`); }
console.log(`\n📊 Total directories found: ${sizes.length}`); return; }
await cleaner.clean(dryRun);}
// Run the scriptmain().catch((error) => { console.error('❌ Unexpected error:', error.message); process.exit(1);});
export default NodeModulesCleaner;