UNPKG

@happyvibess/node-cleaner

Version:

🧹 Interactive CLI tool to find and clean node_modules directories and free up space

179 lines (147 loc) • 5.04 kB
#!/usr/bin/env node import { Command } from 'commander'; import chalk from 'chalk'; import inquirer from 'inquirer'; import ora from 'ora'; import bytes from 'bytes'; import fs from 'fs-extra'; import { glob } from 'glob'; import { createRequire } from 'module'; import path from 'path'; import { SingleBar, Presets } from 'cli-progress'; import boxen from 'boxen'; import { fileURLToPath } from 'url'; import figures from 'figures'; const require = createRequire(import.meta.url); const packageJson = require('../package.json'); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const program = new Command(); // Beautiful CLI header console.log(boxen( chalk.bold.cyan(`Node Cleaner v${packageJson.version}`) + '\n' + chalk.dim('Free up space by removing node_modules'), { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'cyan' } )); program .version(packageJson.version) .description('🧹 Interactive tool to clean node_modules directories') .option('-p, --path <path>', 'Specify path to scan', process.cwd()) .option('-d, --depth <number>', 'Maximum depth to scan', '4') .option('-f, --force', 'Skip confirmation', false) .parse(process.argv); const options = program.opts(); async function calculateDirSize(dir) { const files = await glob('**/*', { cwd: dir, nodir: true }); let size = 0; for (const file of files) { const stats = await fs.stat(path.join(dir, file)); size += stats.size; } return size; } async function findNodeModules(scanPath, maxDepth) { const spinner = ora('Scanning for node_modules directories...').start(); try { const dirs = await glob('**/node_modules', { cwd: scanPath, ignore: ['**/node_modules/**/node_modules/**'], maxDepth: parseInt(maxDepth) }); if (dirs.length === 0) { spinner.fail('No node_modules directories found.'); process.exit(0); } spinner.succeed(`Found ${chalk.green(dirs.length)} node_modules ${dirs.length === 1 ? 'directory' : 'directories'}`); const progress = new SingleBar({ format: 'Calculating sizes |' + chalk.cyan('{bar}') + '| {percentage}% || {value}/{total} directories', barCompleteChar: '\u2588', barIncompleteChar: '\u2591', }, Presets.shades_classic); progress.start(dirs.length, 0); const dirDetails = await Promise.all( dirs.map(async (dir, index) => { const fullPath = path.join(scanPath, dir); const size = await calculateDirSize(fullPath); progress.update(index + 1); return { path: fullPath, size, formattedSize: bytes(size) }; }) ); progress.stop(); const totalSize = dirDetails.reduce((acc, dir) => acc + dir.size, 0); console.log('\n' + boxen( chalk.bold('Space usage summary:') + '\n\n' + chalk.cyan(`Total space used: ${bytes(totalSize)}`), { padding: 1, borderColor: 'yellow' } )); return dirDetails; } catch (error) { spinner.fail('Error scanning directories'); console.error(chalk.red(error)); process.exit(1); } } async function main() { const nodeModules = await findNodeModules(options.path, options.depth); if (!options.force) { const { selected } = await inquirer.prompt([ { type: 'checkbox', name: 'selected', message: 'Select node_modules directories to clean:', choices: nodeModules.map(dir => ({ name: `${dir.path} ${chalk.cyan(`(${dir.formattedSize})`)}`, value: dir.path, short: path.basename(path.dirname(dir.path)) })), pageSize: 15 } ]); if (selected.length === 0) { console.log(chalk.yellow('No directories selected. Exiting...')); process.exit(0); } const { confirm } = await inquirer.prompt([ { type: 'confirm', name: 'confirm', message: `Are you sure you want to delete ${chalk.red(selected.length)} node_modules ${selected.length === 1 ? 'directory' : 'directories'}?`, default: false } ]); if (!confirm) { console.log(chalk.yellow('Operation cancelled')); process.exit(0); } const progress = new SingleBar({ format: 'Cleaning |' + chalk.cyan('{bar}') + '| {percentage}% || {value}/{total} directories', barCompleteChar: '\u2588', barIncompleteChar: '\u2591', }, Presets.shades_classic); progress.start(selected.length, 0); for (let i = 0; i < selected.length; i++) { await fs.remove(selected[i]); progress.update(i + 1); } progress.stop(); console.log(boxen( chalk.green.bold(`${figures.tick} Cleanup complete!`) + '\n\n' + `${chalk.cyan(selected.length)} directories cleaned\n` + chalk.dim('Your project is now lighter and faster'), { padding: 1, borderColor: 'green' } )); } } main().catch(error => { console.error(chalk.red('Error:', error.message)); process.exit(1); });