UNPKG

clearnpmod

Version:

A CLI tool to find and clean up node_modules directories recursively

185 lines (157 loc) • 5.38 kB
#!/usr/bin/env node /** * clearnpmod - A CLI tool to find and clean up node_modules directories * * Usage: * clearnpmod <path> * * Scans recursively for all "node_modules" folders under <path>, * presents them in a checkbox list (all checked by default), * and then deletes the ones you leave checked. */ const fs = require('fs'); const path = require('path'); /** * Recursively find all node_modules directories in the given directory * @param {string} dir - Directory to search * @param {string[]} results - Array to store found paths */ async function findNodeModules(dir, results) { let entries; try { entries = await fs.promises.readdir(dir, { withFileTypes: true }); } catch (err) { // Skip folders we can't read (e.g. permissions) return; } for (const entry of entries) { if (!entry.isDirectory()) continue; const fullPath = path.join(dir, entry.name); if (entry.name === 'node_modules') { results.push(fullPath); // Don't recurse into this node_modules; we want nested node_modules to be caught only by // higher levels (if they exist), but not traverse inside each one. continue; } // Skip common directories that shouldn't contain node_modules if (['.git', '.vscode', '.idea', 'node_modules'].includes(entry.name)) { continue; } await findNodeModules(fullPath, results); } } /** * Format file size in human readable format * @param {number} bytes - Size in bytes * @returns {string} Formatted size string */ function formatBytes(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } /** * Get directory size (approximation for display purposes) * @param {string} dirPath - Path to directory * @returns {Promise<number>} Size in bytes */ async function getDirectorySize(dirPath) { try { const stats = await fs.promises.stat(dirPath); return stats.size || 0; } catch { return 0; } } async function main() { const args = process.argv.slice(2); // Handle help flag if (args.includes('--help') || args.includes('-h')) { console.log(` clearnpmod - Clean up node_modules directories Usage: clearnpmod <path> Clean node_modules in specified directory clearnpmod --help, -h Show this help message clearnpmod --version, -v Show version Examples: clearnpmod . Clean current directory clearnpmod ~/Development Clean all projects in Development folder `); process.exit(0); } // Handle version flag if (args.includes('--version') || args.includes('-v')) { const packageJson = require('./package.json'); console.log(packageJson.version); process.exit(0); } if (args.length !== 1) { console.error('āŒ Error: Please provide exactly one path argument.'); console.error('Usage: clearnpmod <path>'); console.error('Run "clearnpmod --help" for more information.'); process.exit(1); } const basePath = path.resolve(args[0]); let stat; try { stat = await fs.promises.stat(basePath); } catch (err) { console.error(`āŒ Error: Path "${basePath}" does not exist or is not accessible.`); process.exit(1); } if (!stat.isDirectory()) { console.error(`āŒ Error: "${basePath}" is not a directory.`); process.exit(1); } console.log(`šŸ” Scanning for node_modules folders under:\n ${basePath}\nā³ This may take a moment...`); const nmPaths = []; await findNodeModules(basePath, nmPaths); if (nmPaths.length === 0) { console.log('✨ No node_modules folders found. Your directory is already clean!'); process.exit(0); } console.log(`\nšŸ“ Found ${nmPaths.length} node_modules director${nmPaths.length === 1 ? 'y' : 'ies'}:`); // Import inquirer dynamically for ES module compatibility const inquirer = (await import('inquirer')).default; // Prompt user with a checkbox list (all checked by default) const { toDelete } = await inquirer.prompt([ { type: 'checkbox', name: 'toDelete', message: 'Select which node_modules directories you want to delete:', choices: nmPaths.map(p => ({ name: p, value: p, checked: true })), pageSize: 15, loop: false, }, ]); if (toDelete.length === 0) { console.log('🚫 Nothing selected. Exiting without deleting anything.'); process.exit(0); } console.log(`\nšŸ—‘ļø Deleting ${toDelete.length} folder(s)...`); let successCount = 0; let failCount = 0; for (const dir of toDelete) { try { // Node 14+: fs.rm with recursive option await fs.promises.rm(dir, { recursive: true, force: true }); console.log(`āœ… Deleted: ${dir}`); successCount++; } catch (err) { console.error(`āŒ Failed to delete: ${dir}`); console.error(` Reason: ${err.message}`); failCount++; } } console.log(`\nšŸŽ‰ Done! Successfully deleted ${successCount} folder(s).`); if (failCount > 0) { console.log(`āš ļø Failed to delete ${failCount} folder(s).`); } console.log('šŸ’” Tip: You can reinstall dependencies with "npm install" or "pnpm install"'); } main().catch(err => { console.error('šŸ’„ Unexpected error:', err.message); process.exit(1); });