UNPKG

smart-renamer

Version:

šŸš€ Intelligent file naming suggestions based on project-specific naming conventions. Interactive CLI tool that asks yes/no for each file renaming.

384 lines • 17.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createProgram = createProgram; const commander_1 = require("commander"); const config_manager_1 = require("../config/config-manager"); const naming_validator_1 = require("../core/naming-validator"); const suggestion_engine_1 = require("../core/suggestion-engine"); const file_watcher_1 = require("../core/file-watcher"); const inquirer_1 = __importDefault(require("inquirer")); const fs_1 = require("fs"); const path_1 = require("path"); function createProgram() { const program = new commander_1.Command(); program .name('renamer') .description('Intelligent file naming suggestions based on project conventions') .version('1.0.0'); program .command('init') .description('Initialize naming convention for the current project') .action(initCommand); program .command('set-convention <convention>') .description('Set the naming convention for the project') .action(setConventionCommand); program .command('watch') .description('Monitor for new files and suggest names') .option('-p, --patterns <patterns>', 'File patterns to watch (comma-separated)', '**/*') .action(watchCommand); program .command('validate') .description('Validate existing file names against the project convention') .option('-f, --fix', 'Show suggested fixes for invalid names') .action(validateCommand); program .command('suggest <filename>') .description('Get naming suggestions for a specific file') .action(suggestCommand); program .command('analyze') .description('Analyze project structure and naming patterns') .action(analyzeCommand); program .command('add-exception <name>') .description('Add a file name to the exceptions list (excluded from convention checks)') .action(addExceptionCommand); program .command('rename') .description('Rename files to follow the project naming convention') .option('-d, --dry-run', 'Show what would be renamed without actually renaming') .option('-f, --force', 'Rename all files without individual prompts') .option('-i, --interactive', 'Ask yes/no for each file individually (default)') .option('--keep <files>', 'Comma-separated list of files to keep unchanged') .action(renameCommand); return program; } async function initCommand() { console.log('šŸš€ Initializing Renamer for your project...\n'); const configManager = new config_manager_1.ConfigManager(); if (configManager.configExists()) { const { overwrite } = await inquirer_1.default.prompt([{ type: 'confirm', name: 'overwrite', message: 'naming.config already exists. Do you want to overwrite it?', default: false }]); if (!overwrite) { console.log('āœ… Keeping existing configuration.'); return; } } const suggestionEngine = new suggestion_engine_1.SuggestionEngine(); const detectedConvention = suggestionEngine.detectProjectConvention(); const questions = [ { type: 'list', name: 'convention', message: 'Choose your preferred naming convention:', choices: [ 'camelCase', 'snake_case', 'kebab-case', 'PascalCase', 'UPPER_SNAKE_CASE' ], default: detectedConvention || 'camelCase' }, { type: 'input', name: 'files', message: 'File patterns to apply convention to:', default: '*.ts,*.js' }, { type: 'list', name: 'folders', message: 'Folder naming convention:', choices: [ 'camelCase', 'snake_case', 'kebab-case', 'PascalCase', 'UPPER_SNAKE_CASE' ], default: 'kebab-case' }, { type: 'input', name: 'exceptions', message: 'Files to exclude from convention (comma-separated):', default: 'index,main,app' } ]; const answers = await inquirer_1.default.prompt(questions); const config = { convention: answers.convention, files: answers.files.split(',').map((s) => s.trim()), folders: answers.folders, exceptions: answers.exceptions.split(',').map((s) => s.trim()).filter(Boolean) }; configManager.saveConfig(config); console.log('\nāœ… Configuration saved to naming.config'); console.log(`šŸ“ Primary convention: ${config.convention}`); console.log(`šŸ“ Folder convention: ${config.folders}`); if (detectedConvention && detectedConvention !== config.convention) { console.log(`\nšŸ’” Note: Detected '${detectedConvention}' in existing files, but you chose '${config.convention}'`); } } async function setConventionCommand(convention) { const validConventions = ['camelCase', 'snake_case', 'kebab-case', 'PascalCase', 'UPPER_SNAKE_CASE']; if (!validConventions.includes(convention)) { console.error(`āŒ Invalid convention '${convention}'. Valid options: ${validConventions.join(', ')}`); process.exit(1); } const configManager = new config_manager_1.ConfigManager(); const config = configManager.loadConfig(); config.convention = convention; configManager.saveConfig(config); console.log(`āœ… Convention set to '${convention}'`); } async function watchCommand(options) { const configManager = new config_manager_1.ConfigManager(); const config = configManager.loadConfig(); const suggestionEngine = new suggestion_engine_1.SuggestionEngine(); const patterns = options.patterns.split(',').map(p => p.trim()); const watcher = new file_watcher_1.FileWatcher(process.cwd(), patterns); console.log(`šŸ‘€ Watching for new files with convention: ${config.convention}`); console.log(`šŸ“‚ Patterns: ${patterns.join(', ')}\n`); watcher.on('fileAdded', (fileInfo) => { const suggestion = suggestionEngine.suggestName(fileInfo.name, config.convention, config.exceptions); if (suggestion.original !== suggestion.suggested) { console.log(`\nšŸ“ New file detected: ${fileInfo.name}`); console.log(`šŸ’” Suggested name: ${suggestion.suggested}`); console.log(`šŸŽÆ Convention: ${suggestion.convention}`); console.log(`šŸ“Š Confidence: ${(suggestion.confidence * 100).toFixed(1)}%`); } }); watcher.on('error', (error) => { console.error('āŒ File watcher error:', error); }); watcher.start(); process.on('SIGINT', () => { console.log('\nšŸ›‘ Stopping file watcher...'); watcher.stop(); process.exit(0); }); } async function validateCommand(options) { const configManager = new config_manager_1.ConfigManager(); const config = configManager.loadConfig(); const suggestionEngine = new suggestion_engine_1.SuggestionEngine(); console.log(`šŸ” Validating files against '${config.convention}' convention...\n`); const files = suggestionEngine['getAllProjectFiles'](); const invalidFiles = []; for (const file of files) { if (file.isDirectory) continue; const validation = naming_validator_1.NamingValidator.validateName(file.name, config.convention, config.exceptions); if (!validation.isValid && validation.expectedName) { invalidFiles.push({ name: file.name, suggested: validation.expectedName, path: file.path }); console.log(`āŒ ${file.name}`); console.log(` šŸ’” Should be: ${validation.expectedName}`); console.log(` šŸ“ Path: ${file.path}\n`); } } if (invalidFiles.length === 0) { console.log('āœ… All files follow the naming convention!'); } else { console.log(`\nšŸ“Š Found ${invalidFiles.length} files that don't follow the convention.`); if (options.fix) { console.log('\nšŸ”§ Suggested fixes:'); for (const file of invalidFiles) { console.log(`mv "${file.name}" "${file.suggested}"`); } } } } async function suggestCommand(filename) { const configManager = new config_manager_1.ConfigManager(); const config = configManager.loadConfig(); const suggestionEngine = new suggestion_engine_1.SuggestionEngine(); console.log(`šŸ’­ Generating suggestions for: ${filename}\n`); const suggestions = suggestionEngine.suggestMultipleNames(filename, config.convention, config.exceptions); for (const suggestion of suggestions) { const icon = suggestion.convention === config.convention ? '⭐' : ' '; console.log(`${icon} ${suggestion.convention.padEnd(20)} ${suggestion.suggested.padEnd(30)} (${(suggestion.confidence * 100).toFixed(1)}%)`); } } async function analyzeCommand() { const suggestionEngine = new suggestion_engine_1.SuggestionEngine(); const analysis = suggestionEngine.analyzeProjectStructure(); console.log('šŸ“Š Project Analysis\n'); console.log(`šŸ“ Total files: ${analysis.totalFiles}`); console.log(`šŸŽÆ Most common convention: ${analysis.mostCommon || 'None detected'}`); console.log(`šŸ“ˆ Consistency: ${(analysis.consistency * 100).toFixed(1)}%\n`); console.log('šŸ“‹ Convention breakdown:'); for (const [convention, count] of Object.entries(analysis.conventions)) { const percentage = analysis.totalFiles > 0 ? (count / analysis.totalFiles * 100).toFixed(1) : '0.0'; console.log(` ${convention.padEnd(20)} ${count.toString().padStart(3)} files (${percentage}%)`); } } async function renameCommand(options) { const configManager = new config_manager_1.ConfigManager(); const config = configManager.loadConfig(); const suggestionEngine = new suggestion_engine_1.SuggestionEngine(); // Parse keep list const keepFiles = options.keep ? options.keep.split(',').map(f => f.trim()) : []; // Default exclusions - system files, config files, and common files const defaultExclusions = [ 'package.json', 'tsconfig.json', 'bun.lockb', 'naming.config', 'package-lock.json', 'yarn.lock', '.gitignore', 'LICENSE', // Config files '.eslintrc.js', '.eslintrc.json', '.prettierrc', '.prettierrc.json', 'babel.config.js', 'webpack.config.js', 'vite.config.js', 'rollup.config.js', 'jest.config.js', 'vitest.config.js', '.env.example', 'Dockerfile', 'docker-compose.yml', 'docker-compose.yaml' ]; // File extensions to exclude by default const excludedExtensions = [ // Image files '.jpg', '.jpeg', '.png', '.gif', '.bmp', '.svg', '.webp', '.ico', '.tiff', // Markdown files '.md', '.markdown', // Declaration files (all languages) '.d.ts', '.d.mts', '.d.cts', // TypeScript declarations '.h', '.hpp', '.hxx', // C/C++ headers '.hi', // Haskell interface files '.pyi', // Python stub files '.rbi', // Ruby interface files '.rei', // ReasonML interface files '.mli', // OCaml interface files '.sig', // Standard ML signature files '.fsi', // F# signature files '.spec', // RPM spec files '.def', // Definition files (various languages) // Config file extensions '.config.js', '.config.ts', '.config.json', '.yml', '.yaml', '.toml', '.ini' ]; console.log(`šŸ”„ Finding files to rename to '${config.convention}' convention...\n`); const files = suggestionEngine['getAllProjectFiles'](); const filesToRename = []; for (const file of files) { if (file.isDirectory) continue; // Skip files that should be ignored if (file.name.startsWith('.') && file.name !== '.gitignore') continue; if (defaultExclusions.includes(file.name)) continue; if (keepFiles.includes(file.name)) continue; // Check if file extension should be excluded const fileExtension = file.extension.toLowerCase(); const hasExcludedExtension = excludedExtensions.some(ext => file.name.toLowerCase().endsWith(ext.toLowerCase())); if (hasExcludedExtension) { const fileType = fileExtension.match(/\.(jpg|jpeg|png|gif|bmp|svg|webp|ico|tiff)$/i) ? 'image' : fileExtension.match(/\.(md|markdown)$/i) ? 'markdown' : file.name.match(/\.d\.(ts|mts|cts)$/i) ? 'TypeScript declaration' : fileExtension.match(/\.(h|hpp|hxx)$/i) ? 'C/C++ header' : fileExtension.match(/\.(hi|pyi|rbi|rei|mli|sig|fsi|spec|def)$/i) ? 'declaration' : 'config'; console.log(`šŸ“„ Skipping ${file.name} (${fileType} files excluded by default)`); continue; } // Skip files containing 'config' in the filename if (file.name.toLowerCase().includes('config')) { console.log(`šŸ“„ Skipping ${file.name} (config files excluded by default)`); continue; } const validation = naming_validator_1.NamingValidator.validateName(file.name, config.convention, config.exceptions); if (!validation.isValid && validation.expectedName) { const newPath = (0, path_1.join)((0, path_1.dirname)(file.path), validation.expectedName); filesToRename.push({ oldPath: file.path, newPath, oldName: file.name, newName: validation.expectedName }); } } if (filesToRename.length === 0) { console.log('āœ… All files already follow the naming convention!'); return; } if (options.dryRun) { console.log(`šŸ“ Would rename ${filesToRename.length} files:\n`); for (const file of filesToRename) { console.log(` ${file.oldName} → ${file.newName}`); } console.log('\nšŸ” Dry run complete. No files were actually renamed.'); return; } // Interactive mode (default) - ask for each file individually if (!options.force) { console.log(`šŸ“ Found ${filesToRename.length} files that can be renamed:\n`); let successCount = 0; let errorCount = 0; let skippedCount = 0; for (const file of filesToRename) { const { shouldRename } = await inquirer_1.default.prompt([{ type: 'confirm', name: 'shouldRename', message: `Rename "${file.oldName}" to "${file.newName}"?`, default: true }]); if (shouldRename) { try { (0, fs_1.renameSync)(file.oldPath, file.newPath); console.log(`āœ… ${file.oldName} → ${file.newName}`); successCount++; } catch (error) { console.log(`āŒ Failed to rename ${file.oldName}: ${error}`); errorCount++; } } else { console.log(`ā­ļø Skipped ${file.oldName}`); skippedCount++; } console.log(''); // Empty line for readability } console.log(`šŸ“Š Completed: ${successCount} renamed, ${skippedCount} skipped, ${errorCount} failed`); return; } // Force mode - rename all without asking console.log(`šŸ”„ Renaming ${filesToRename.length} files...\n`); let successCount = 0; let errorCount = 0; for (const file of filesToRename) { try { (0, fs_1.renameSync)(file.oldPath, file.newPath); console.log(`āœ… ${file.oldName} → ${file.newName}`); successCount++; } catch (error) { console.log(`āŒ Failed to rename ${file.oldName}: ${error}`); errorCount++; } } console.log(`\nšŸ“Š Completed: ${successCount} renamed, ${errorCount} failed`); } async function addExceptionCommand(name) { const configManager = new config_manager_1.ConfigManager(); const config = configManager.loadConfig(); const normalized = name.trim().toLowerCase(); if (config.exceptions?.includes(normalized)) { console.log(`ā„¹ļø '${normalized}' is already in the exceptions list.`); return; } config.exceptions = [...(config.exceptions ?? []), normalized]; configManager.saveConfig(config); console.log(`āœ… Added '${normalized}' to exceptions.`); console.log(`šŸ“‹ Current exceptions: ${config.exceptions.join(', ')}`); } //# sourceMappingURL=commands.js.map