UNPKG

@puberty-labs/refuctor

Version:

AI-powered, snark-fueled technical debt cleansing suite with automatic snarky language detection that turns code cleanup into a darkly humorous financial metaphor.

462 lines (391 loc) 16.5 kB
const fs = require('fs-extra'); const path = require('path'); // Color utilities for console output const colors = { red: (text) => `\x1b[31m${text}\x1b[0m`, green: (text) => `\x1b[32m${text}\x1b[0m`, yellow: (text) => `\x1b[33m${text}\x1b[0m`, blue: (text) => `\x1b[34m${text}\x1b[0m`, magenta: (text) => `\x1b[35m${text}\x1b[0m`, cyan: (text) => `\x1b[36m${text}\x1b[0m`, gray: (text) => `\x1b[90m${text}\x1b[0m`, bold: (text) => `\x1b[1m${text}\x1b[0m` }; const glob = require('glob'); const moment = require('moment'); const { techDebtManager } = require('./techdebt-manager'); const { DebtIgnoreParser } = require('./debt-ignore-parser'); const SnarkySpellHandler = require('./snarky-spell-handler'); /** * Refuctor Automated Setup Wizard * Comprehensive project analysis and debt management infrastructure setup */ class SetupWizard { constructor() { this.projectAnalysis = null; this.setupResults = { techDebtCreated: false, spellCheckSetup: false, debtIgnoreCreated: false, workspaceConfigured: false, configsGenerated: [] }; } /** * Run the complete automated setup wizard * @param {string} projectPath - Path to project root * @param {Object} options - Setup options * @returns {Object} Setup results */ async runSetupWizard(projectPath, options = {}) { this.projectAnalysis = await this.analyzeProject(projectPath); this.displayProjectAnalysis(); await this.generateConfigurations(projectPath, options); await this.setupSpellChecking(projectPath, options); await this.setupDebtIgnore(projectPath, options); await this.setupIDEIntegration(projectPath, options); await this.setupTechDebtTracking(projectPath, options); return this.setupResults; } /** * Analyze project structure and detect frameworks/configurations * @param {string} projectPath - Path to project root * @returns {Object} Project analysis results */ async analyzeProject(projectPath) { const analysis = { projectName: path.basename(projectPath), projectType: 'unknown', frameworks: [], languages: [], configs: [], files: { total: 0, code: [], docs: [], configs: [] }, hasGit: false, hasPackageJson: false, hasReadme: false, buildTools: [], dependencies: [] }; try { // Basic file detection const allFiles = glob.sync('**/*', { cwd: projectPath, ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**'], nodir: true }); analysis.files.total = allFiles.length; // Categorize files const codeFiles = allFiles.filter(file => /\.(js|ts|jsx|tsx|py|java|cpp|c|cs|php|rb|go|rs|swift|kt)$/i.test(file)); const docFiles = allFiles.filter(file => /\.(md|rst|txt|doc|docx|pdf)$/i.test(file)); const configFiles = allFiles.filter(file => /\.(json|yml|yaml|toml|ini|conf|config|xml)$/i.test(file)); analysis.files.code = codeFiles; analysis.files.docs = docFiles; analysis.files.configs = configFiles; // Detect languages const jsFiles = codeFiles.filter(f => /\.(js|jsx)$/i.test(f)); const tsFiles = codeFiles.filter(f => /\.(ts|tsx)$/i.test(f)); const pyFiles = codeFiles.filter(f => /\.py$/i.test(f)); const mdFiles = docFiles.filter(f => /\.md$/i.test(f)); if (jsFiles.length > 0) analysis.languages.push('JavaScript'); if (tsFiles.length > 0) analysis.languages.push('TypeScript'); if (pyFiles.length > 0) analysis.languages.push('Python'); if (mdFiles.length > 0) analysis.languages.push('Markdown'); // Check for key files analysis.hasGit = await fs.pathExists(path.join(projectPath, '.git')); analysis.hasPackageJson = await fs.pathExists(path.join(projectPath, 'package.json')); analysis.hasReadme = allFiles.some(f => /^readme\.(md|txt|rst)$/i.test(f)); // Analyze package.json if exists if (analysis.hasPackageJson) { try { const packageJson = await fs.readJson(path.join(projectPath, 'package.json')); analysis.projectName = packageJson.name || analysis.projectName; // Detect frameworks and build tools const deps = { ...packageJson.dependencies, ...packageJson.devDependencies }; if (deps.react || deps['@types/react']) { analysis.frameworks.push('React'); } if (deps.vue || deps['@vue/cli']) { analysis.frameworks.push('Vue'); } if (deps.angular || deps['@angular/cli']) { analysis.frameworks.push('Angular'); } if (deps.next || deps.nuxt) { analysis.frameworks.push('Next.js/Nuxt'); } if (deps.express || deps.fastify || deps.koa) { analysis.frameworks.push('Node.js Backend'); } if (deps.webpack) { analysis.buildTools.push('Webpack'); } if (deps.vite) { analysis.buildTools.push('Vite'); } if (deps.parcel) { analysis.buildTools.push('Parcel'); } if (deps.rollup) { analysis.buildTools.push('Rollup'); } analysis.dependencies = Object.keys(deps); } catch (error) { console.warn('Warning: Could not parse package.json'); } } // Detect config files const configFilePatterns = [ '.eslintrc*', 'eslint.config.*', '.prettierrc*', 'prettier.config.*', 'tsconfig.json', 'jsconfig.json', 'webpack.config.*', 'vite.config.*', '.babelrc*', 'babel.config.*', 'jest.config.*', 'vitest.config.*', 'tailwind.config.*', 'postcss.config.*' ]; for (const pattern of configFilePatterns) { const matches = glob.sync(pattern, { cwd: projectPath }); if (matches.length > 0) { analysis.configs.push(...matches); } } // Determine project type if (analysis.frameworks.includes('React')) { analysis.projectType = 'React App'; } else if (analysis.frameworks.includes('Vue')) { analysis.projectType = 'Vue App'; } else if (analysis.frameworks.includes('Angular')) { analysis.projectType = 'Angular App'; } else if (analysis.frameworks.includes('Node.js Backend')) { analysis.projectType = 'Node.js Server'; } else if (analysis.languages.includes('TypeScript')) { analysis.projectType = 'TypeScript Project'; } else if (analysis.languages.includes('JavaScript')) { analysis.projectType = 'JavaScript Project'; } else if (analysis.languages.includes('Python')) { analysis.projectType = 'Python Project'; } else if (analysis.files.docs.length > analysis.files.code.length) { analysis.projectType = 'Documentation Project'; } } catch (error) { console.warn(`Project analysis warning: ${error.message}`); } return analysis; } /** * Display project analysis results */ displayProjectAnalysis() { const analysis = this.projectAnalysis; console.log(` 💬 Languages: ${analysis.languages.join(', ') || 'None detected'}`); console.log(` 🚀 Frameworks: ${analysis.frameworks.join(', ') || 'None detected'}`); console.log(` 🔧 Build Tools: ${analysis.buildTools.join(', ') || 'None detected'}`); } /** * Generate project-specific configurations */ async generateConfigurations(projectPath, options) { // This is a placeholder for future config generation // Could include .cursorrules, .gitignore enhancements, etc. console.log(' 📝 Configuration generation (future enhancement)'); } /** * Setup intelligent spell checking with project-specific dictionary */ async setupSpellChecking(projectPath, options) { try { const spellHandler = new SnarkySpellHandler(); const configPath = path.join(projectPath, 'cspell.json'); // Create base config if doesn't exist if (!await fs.pathExists(configPath)) { const projectSpecificWords = this.generateProjectSpecificWords(); await spellHandler.updateProjectDictionary(projectPath, projectSpecificWords); this.setupResults.spellCheckSetup = true; this.setupResults.configsGenerated.push('cspell.json'); } else { console.log(colors.gray(' 📝 Spell check setup skipped (cspell.json already exists)')); } } catch (error) { console.error(colors.red(`❌ Spell check setup failed: ${error.message}`)); this.setupResults.errors.push(`Spell check setup: ${error.message}`); } } /** * Setup debt ignore patterns based on project analysis */ async setupDebtIgnore(projectPath, options) { try { const ignoreParser = new DebtIgnoreParser(); const ignoreFilePath = path.join(projectPath, '.debtignore'); if (!await fs.pathExists(ignoreFilePath)) { // Get base patterns const basePatterns = DebtIgnoreParser.getSampleContent(); // Add project-specific patterns const projectPatterns = this.generateProjectSpecificIgnorePatterns(); const combinedPatterns = basePatterns + '\n# Project-specific patterns\n' + projectPatterns.join('\n'); await fs.writeFile(ignoreFilePath, combinedPatterns, 'utf8'); this.setupResults.debtIgnoreCreated = true; this.setupResults.configsGenerated.push('.debtignore'); } else { console.log(colors.gray(' 🚫 Debt ignore setup skipped (.debtignore already exists)')); } } catch (error) { console.error(colors.red(`❌ Debt ignore setup failed: ${error.message}`)); this.setupResults.errors.push(`Debt ignore setup: ${error.message}`); } } /** * Setup IDE integration (Cursor workspace configuration) */ async setupIDEIntegration(projectPath, options) { try { // Check if this is a Cursor workspace const workspaceFile = glob.sync('*.code-workspace', { cwd: projectPath })[0]; if (workspaceFile) { console.log(' 💻 IDE integration ready (workspace detected)'); this.setupResults.workspaceConfigured = true; } else { console.log(colors.gray(' 💻 IDE integration setup skipped (no workspace file found)')); } } catch (error) { console.error(colors.red(`❌ IDE integration setup failed: ${error.message}`)); this.setupResults.errors.push(`IDE integration setup: ${error.message}`); } } /** * Setup TECHDEBT.md with project-specific context */ async setupTechDebtTracking(projectPath, options) { try { const result = await techDebtManager.initializeProject(projectPath, options.force); if (result.created) { // Enhance the TECHDEBT.md with project-specific context await this.enhanceTechDebtWithContext(projectPath); this.setupResults.techDebtCreated = true; } else if (result.exists) { console.log(colors.gray(' 📋 Tech debt tracking setup skipped (TECHDEBT.md already exists)')); } } catch (error) { console.error(colors.red(`❌ Tech debt tracking setup failed: ${error.message}`)); this.setupResults.errors.push(`Tech debt tracking setup: ${error.message}`); } } /** * Generate project-specific dictionary words */ generateProjectSpecificWords() { const words = ['refuctor', 'refuctoring', 'techdebt']; // Base Refuctor terms // Add project-specific terms based on analysis if (this.projectAnalysis.frameworks.includes('React')) { words.push('jsx', 'tsx', 'useState', 'useEffect', 'componentDidMount'); } if (this.projectAnalysis.frameworks.includes('Vue')) { words.push('vuex', 'nuxt', 'vue-router'); } if (this.projectAnalysis.languages.includes('TypeScript')) { words.push('tsconfig', 'typeof', 'readonly', 'keyof'); } if (this.projectAnalysis.buildTools.includes('Webpack')) { words.push('webpack', 'bundler', 'loaders'); } if (this.projectAnalysis.buildTools.includes('Vite')) { words.push('vite', 'esm', 'hmr'); } // Add project name as valid word if (this.projectAnalysis.projectName) { words.push(this.projectAnalysis.projectName.toLowerCase()); } return words; } /** * Generate project-specific ignore patterns */ generateProjectSpecificIgnorePatterns() { const patterns = []; // Add patterns based on detected frameworks and build tools if (this.projectAnalysis.frameworks.includes('React')) { patterns.push('build/**', 'dist/**', '.next/**'); } if (this.projectAnalysis.frameworks.includes('Vue')) { patterns.push('dist/**', '.nuxt/**'); } if (this.projectAnalysis.buildTools.includes('Webpack')) { patterns.push('dist/**', 'build/**'); } if (this.projectAnalysis.buildTools.includes('Vite')) { patterns.push('dist/**', '.vite/**'); } if (this.projectAnalysis.languages.includes('TypeScript')) { patterns.push('*.d.ts', 'lib/**'); } // Add common patterns for detected project types if (this.projectAnalysis.projectType === 'Node.js Server') { patterns.push('logs/**', 'tmp/**', 'uploads/**'); } if (this.projectAnalysis.projectType === 'Documentation Project') { patterns.push('_site/**', '.jekyll-cache/**'); } return patterns; } /** * Enhance TECHDEBT.md with project-specific context */ async enhanceTechDebtWithContext(projectPath) { const techDebtPath = path.join(projectPath, 'TECHDEBT.md'); try { let content = await fs.readFile(techDebtPath, 'utf8'); // Add project-specific context section const projectContext = ` ## 🏗️ Project Context (Auto-Generated) **Project Type**: ${this.projectAnalysis.projectType} **Languages**: ${this.projectAnalysis.languages.join(', ')} **Frameworks**: ${this.projectAnalysis.frameworks.join(', ') || 'None detected'} **Build Tools**: ${this.projectAnalysis.buildTools.join(', ') || 'None detected'} **Total Files**: ${this.projectAnalysis.files.total} (${this.projectAnalysis.files.code.length} code files) **Setup Date**: ${moment().format('YYYY-MM-DD HH:mm:ss')} ### 🎯 Project-Specific Debt Monitoring ${this.generateProjectSpecificDebtMonitoring()} --- `; // Insert project context after the philosophy section const philosophyEnd = content.indexOf('## 🚨 Active Debt'); if (philosophyEnd > -1) { content = content.slice(0, philosophyEnd) + projectContext + content.slice(philosophyEnd); await fs.writeFile(techDebtPath, content, 'utf8'); } } catch (error) { console.warn(`Could not enhance TECHDEBT.md: ${error.message}`); } } /** * Generate project-specific debt monitoring recommendations */ generateProjectSpecificDebtMonitoring() { const recommendations = []; if (this.projectAnalysis.languages.includes('JavaScript') || this.projectAnalysis.languages.includes('TypeScript')) { recommendations.push('- **Debug Statement Detection**: Monitor for forgotten debug statements'); recommendations.push('- **ESLint Integration**: Ensure ESLint rules are properly configured'); } if (this.projectAnalysis.frameworks.includes('React')) { recommendations.push('- **React Hooks**: Watch for improper dependency arrays and lifecycle issues'); recommendations.push('- **Component Complexity**: Monitor component size and prop drilling'); } if (this.projectAnalysis.hasPackageJson) { recommendations.push('- **Dependency Audit**: Regular `npm audit` for security vulnerabilities'); recommendations.push('- **Bundle Size**: Monitor for unnecessary dependencies bloating bundle'); } if (this.projectAnalysis.files.docs.length > 0) { recommendations.push('- **Documentation Drift**: Ensure docs stay current with code changes'); } if (recommendations.length === 0) { recommendations.push('- **General Monitoring**: Follow standard debt detection practices'); } return recommendations.join('\n'); } } module.exports = SetupWizard;