UNPKG

veriqa

Version:

๐ŸŽฏ Smart Manual QA Test Advisor with AI-Powered Regression Suggestions and Advanced Git Integration

909 lines (787 loc) โ€ข 34 kB
#!/usr/bin/env node /** * VeriQA v3.0 - Smart Manual QA Test Advisor * Fresh, Modern, AI-Powered Regression Testing Tool * ๐Ÿ‡ฎ๐Ÿ‡ณ Proudly Made in India ๐Ÿ‡ฎ๐Ÿ‡ณ */ const { Command } = require('commander'); const chalk = require('chalk'); const path = require('path'); const fs = require('fs'); const ora = require('ora'); const gradient = require('gradient-string'); const figlet = require('figlet'); const boxen = require('boxen'); // Import modules const ProjectScanner = require('./core/ProjectScanner'); const GitAnalyzer = require('./core/GitAnalyzer'); const AIService = require('./core/AIService'); const ReportGenerator = require('./core/ReportGenerator'); const ConfigManager = require('./utils/ConfigManager'); const UpdateChecker = require('./utils/UpdateChecker'); const SmartEnvManager = require('./utils/SimplifiedEnvManager'); class VeriQA { constructor() { this.configPath = path.join(process.cwd(), '.veriqa.json'); this.configManager = new ConfigManager(this.configPath); this.updateChecker = new UpdateChecker(); } // ๐ŸŽฏ Phase 1: Project Setup & Scan async init() { // Check for updates (non-blocking) setTimeout(() => this.updateChecker.checkForUpdates(), 1000); // Check for old version migration this.updateChecker.detectOldVersion(); this.updateChecker.migrateOldConfig(); // Clear console for fresh start console.clear(); // Beautiful Animated ASCII Header const veriQATitle = figlet.textSync('VeriQA', { font: 'Speed' }); console.log(gradient(['#00f5ff', '#fc00ff'])(veriQATitle)); // Animated subtitle const subtitle = 'โœจ Smart Manual QA Test Advisor โœจ'; console.log(gradient(['#ff6b6b', '#4ecdc4'])(subtitle.padStart((veriQATitle.split('\n')[0].length + subtitle.length) / 2))); // Made in India Header with enhanced styling const madeInIndiaBox = boxen( chalk.white.bold('๐Ÿ‡ฎ๐Ÿ‡ณ PROUDLY MADE IN INDIA ๐Ÿ‡ฎ๐Ÿ‡ณ\n') + chalk.yellow('Supporting Indian Innovation in AI & Testing') + chalk.white('\n๐Ÿš€ Advanced AI-Powered Testing ๐Ÿš€'), { padding: 1, margin: 1, borderStyle: 'double', borderColor: 'cyan', backgroundColor: 'blue', textAlignment: 'center' } ); console.log(madeInIndiaBox); // Project Initialization Box with gradient const initBox = boxen( gradient(['#ffeaa7', '#fab1a0'])('๐Ÿ”ง INITIALIZING PROJECT SCAN') + chalk.white('\nโšก Scanning for testing frameworks...') + chalk.gray('\n๐Ÿ“ Analyzing project structure...'), { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'yellow', textAlignment: 'center' } ); console.log(initBox); const spinner = ora({ text: chalk.cyan('๐Ÿ” Scanning project structure...'), spinner: 'dots12', color: 'cyan' }).start(); try { // Setup directories spinner.text = chalk.cyan('๐Ÿ“ Creating directories...'); await this.setupDirectories(); // Scan project spinner.text = chalk.cyan('๐Ÿงช Detecting testing frameworks...'); const scanner = new ProjectScanner(); const projectData = await scanner.scan(); // Setup AI if not configured spinner.text = chalk.cyan('๐Ÿค– Setting up AI configuration...'); await this.setupAI(); // Save configuration spinner.text = chalk.cyan('๐Ÿ’พ Saving configuration...'); await this.configManager.save({ ...projectData, setupDate: new Date().toISOString(), version: '3.0.2' }); spinner.succeed(chalk.green.bold('โœ… Project initialized successfully!')); // Enhanced Project Summary with animations const summaryBox = boxen( chalk.white.bold('๐Ÿ“Š PROJECT SUMMARY\n\n') + chalk.cyan('๐Ÿ“ Project: ') + chalk.white.bold(projectData.name) + '\n' + chalk.cyan('๐Ÿงช Frameworks: ') + chalk.yellow.bold(projectData.frameworks.join(', ')) + '\n' + chalk.cyan('๐Ÿ“„ Test Files: ') + chalk.green.bold(projectData.testFiles.length) + '\n' + chalk.cyan('๐Ÿ“„ Source Files: ') + chalk.blue.bold(projectData.sourceFiles.length) + '\n' + chalk.cyan('โฐ Setup Date: ') + chalk.magenta.bold(new Date().toLocaleDateString()), { padding: 1, margin: 1, borderStyle: 'classic', borderColor: 'green', backgroundColor: 'black', textAlignment: 'left' } ); console.log(summaryBox); // Next Steps with enhanced styling const nextStepsBox = boxen( gradient(['#a29bfe', '#fd79a8'])('๐Ÿš€ NEXT STEPS') + '\n\n' + chalk.white('Run: ') + chalk.green.bold('veriqa analyze <old-commit> <new-commit>') + '\n' + chalk.gray(' โ””โ”€ Compare commits and get AI suggestions') + '\n\n' + chalk.white('Then: ') + chalk.blue.bold('veriqa report') + '\n' + chalk.gray(' โ””โ”€ Generate beautiful HTML/PDF reports'), { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'magenta', textAlignment: 'left' } ); console.log(nextStepsBox); } catch (error) { spinner.fail(chalk.red('โŒ Initialization failed')); console.error(chalk.red.bold('Error:'), error.message); process.exit(1); } } // ๐Ÿ” Phase 2: Git Analysis & AI Suggestions async analyze(oldCommit, newCommit) { console.clear(); // Beautiful header for analysis const analysisTitle = figlet.textSync('ANALYSIS', { font: 'Small' }); console.log(gradient(['#74b9ff', '#0984e3'])(analysisTitle)); const analysisBox = boxen( gradient(['#fdcb6e', '#e17055'])('๐Ÿ” ANALYZING COMMITS FOR REGRESSION') + '\n' + chalk.white('๐Ÿ“Š Git Diff Analysis + AI Powered Suggestions') + '\n' + chalk.gray('๐Ÿค– Using Google Gemini AI for smart recommendations'), { padding: 1, margin: 1, borderStyle: 'double', borderColor: 'blue', textAlignment: 'center' } ); console.log(analysisBox); if (!oldCommit || !newCommit) { const errorBox = boxen( chalk.red.bold('โŒ MISSING COMMIT IDs') + '\n\n' + chalk.white('Usage: ') + chalk.green.bold('veriqa analyze <old-commit> <new-commit>') + '\n\n' + chalk.yellow('Example:') + '\n' + chalk.gray('veriqa analyze abc123 def456'), { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'red', textAlignment: 'left' } ); console.log(errorBox); process.exit(1); } const analysisSpinner = ora({ text: chalk.cyan('๐Ÿ”„ Initializing git analysis...'), spinner: 'aesthetic', color: 'cyan' }).start(); try { const config = await this.configManager.load(); // Git analysis with enhanced progress analysisSpinner.text = chalk.blue('๐Ÿ“Š Analyzing git differences...'); const gitAnalyzer = new GitAnalyzer(); const changes = await gitAnalyzer.analyzeCommits(oldCommit, newCommit); // Get code metrics (like git diff --stat) analysisSpinner.text = chalk.cyan('๐Ÿ“ˆ Calculating code metrics...'); const codeMetrics = await gitAnalyzer.getCodeMetrics(oldCommit, newCommit); analysisSpinner.text = chalk.magenta('๐Ÿค– Generating AI suggestions...'); // AI-powered suggestions const aiService = new AIService(); const suggestions = await aiService.generateSuggestions(changes, config); analysisSpinner.text = chalk.green('๐Ÿ’พ Saving analysis results...'); // Save analysis const analysis = { oldCommit, newCommit, changes, codeMetrics, suggestions, analysisDate: new Date().toISOString() }; await this.configManager.saveAnalysis(analysis); analysisSpinner.succeed(chalk.green.bold('โœ… Analysis completed successfully!')); // Enhanced Results Display const resultsBox = boxen( chalk.white.bold('๐Ÿ“Š ANALYSIS RESULTS\n\n') + chalk.blue('๐Ÿ“ˆ Commits Compared: ') + chalk.yellow.bold(`${oldCommit.substring(0,7)} โ†’ ${newCommit.substring(0,7)}`) + '\n' + chalk.blue('๐Ÿ“ Files Changed: ') + chalk.green.bold(codeMetrics.filesChanged) + '\n' + chalk.blue('๐Ÿ“ˆ Insertions: ') + chalk.green.bold(`+${codeMetrics.totalInsertions}`) + '\n' + chalk.blue('๐Ÿ“‰ Deletions: ') + chalk.red.bold(`-${codeMetrics.totalDeletions}`) + '\n' + chalk.blue('๐Ÿค– AI Suggestions: ') + chalk.magenta.bold(suggestions.length) + '\n' + chalk.blue('โฐ Analysis Date: ') + chalk.cyan.bold(new Date().toLocaleString()) + '\n\n' + chalk.gray('๐ŸŽฏ Regression test suggestions ready!'), { padding: 1, margin: 1, borderStyle: 'classic', borderColor: 'green', backgroundColor: 'black', textAlignment: 'left' } ); console.log(resultsBox); // Display code metrics like git diff --stat gitAnalyzer.displayCodeMetrics(codeMetrics); // Top Suggestions Preview if (suggestions.length > 0) { const topSuggestions = suggestions.slice(0, 3); const previewBox = boxen( chalk.white.bold('๐ŸŽฏ TOP REGRESSION TEST SUGGESTIONS\n\n') + topSuggestions.map((s, i) => chalk.yellow(`${i + 1}. `) + chalk.white(s.title) + '\n' + chalk.gray(` ${s.description.substring(0, 60)}...`) ).join('\n\n'), { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'yellow', textAlignment: 'left' } ); console.log(previewBox); } // Next Steps const nextStepsBox = boxen( gradient(['#a29bfe', '#fd79a8'])('๐Ÿš€ NEXT STEPS') + '\n\n' + chalk.white('Generate Reports: ') + chalk.green.bold('veriqa report') + '\n' + chalk.gray(' โ””โ”€ Create HTML, PDF, and JSON reports') + '\n\n' + chalk.white('View Results: ') + chalk.blue.bold('./veriqa-reports/') + '\n' + chalk.gray(' โ””โ”€ Access detailed analysis and suggestions'), { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'magenta', textAlignment: 'left' } ); console.log(nextStepsBox); } catch (error) { analysisSpinner.fail(chalk.red('โŒ Analysis failed')); console.error(chalk.red.bold('Error:'), error.message); process.exit(1); } } // ๐Ÿ“Š Phase 3: Generate Beautiful Report async report() { console.clear(); // Beautiful header for reports const reportTitle = figlet.textSync('REPORTS', { font: 'Small' }); console.log(gradient(['#00cec9', '#55a3ff'])(reportTitle)); const reportBox = boxen( gradient(['#fd79a8', '#fdcb6e'])('๐Ÿ“Š GENERATING COMPREHENSIVE REPORTS') + '\n' + chalk.white('๐ŸŽจ Beautiful HTML Reports with Charts') + '\n' + chalk.white('๐Ÿ“„ Professional PDF Documents') + '\n' + chalk.white('๐Ÿ“Š Structured JSON Data Export'), { padding: 1, margin: 1, borderStyle: 'double', borderColor: 'cyan', textAlignment: 'center' } ); console.log(reportBox); const reportSpinner = ora({ text: chalk.cyan('๐Ÿ” Loading analysis data...'), spinner: 'bouncingBar', color: 'cyan' }).start(); try { const analysis = await this.configManager.loadLatestAnalysis(); if (!analysis) { reportSpinner.fail(chalk.red('โŒ No analysis found')); const noAnalysisBox = boxen( chalk.red.bold('โŒ NO ANALYSIS DATA FOUND') + '\n\n' + chalk.white('Please run analysis first:') + '\n' + chalk.green.bold('veriqa analyze <old-commit> <new-commit>') + '\n\n' + chalk.gray('Then generate reports with:') + '\n' + chalk.blue.bold('veriqa report'), { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'red', textAlignment: 'left' } ); console.log(noAnalysisBox); process.exit(1); } reportSpinner.text = chalk.magenta('๐ŸŽจ Generating HTML report...'); const reportGenerator = new ReportGenerator(); // Simulate report generation steps with detailed progress reportSpinner.text = chalk.blue('๐Ÿ“„ Creating PDF document...'); await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate work reportSpinner.text = chalk.yellow('๐Ÿ“Š Exporting JSON data...'); await new Promise(resolve => setTimeout(resolve, 800)); // Simulate work reportSpinner.text = chalk.green('๐ŸŽฏ Finalizing reports...'); const reports = await reportGenerator.generate(analysis); reportSpinner.succeed(chalk.green.bold('๐ŸŽ‰ All reports generated successfully!')); // Enhanced Report Summary const reportSummaryBox = boxen( chalk.white.bold('๐Ÿ“‹ GENERATED REPORTS\n\n') + chalk.green('โœ… HTML Report: ') + chalk.cyan.bold('Ready') + '\n' + chalk.gray(' โ””โ”€ Interactive charts and detailed analysis') + '\n\n' + chalk.green('โœ… PDF Report: ') + chalk.cyan.bold(reports.pdf ? 'Ready' : 'Failed') + '\n' + chalk.gray(' โ””โ”€ Professional document for sharing') + '\n\n' + chalk.green('โœ… JSON Data: ') + chalk.cyan.bold('Ready') + '\n' + chalk.gray(' โ””โ”€ Structured data for integration') + '\n\n' + chalk.yellow('๐Ÿ“Š Analysis Summary:') + '\n' + chalk.gray(` โ””โ”€ ${(analysis.suggestions || analysis.comparison?.suggestions || []).length} AI suggestions generated`) + '\n' + chalk.gray(` โ””โ”€ ${(analysis.changes?.files || analysis.comparison?.files || []).length} files analyzed`), { padding: 1, margin: 1, borderStyle: 'classic', borderColor: 'green', backgroundColor: 'black', textAlignment: 'left' } ); console.log(reportSummaryBox); // File Locations Box const locationsBox = boxen( chalk.white.bold('๐Ÿ“‚ REPORT LOCATIONS\n\n') + chalk.blue('HTML: ') + chalk.gray(reports.html) + '\n' + (reports.pdf ? chalk.blue('PDF: ') + chalk.gray(reports.pdf) + '\n' : '') + chalk.blue('JSON: ') + chalk.gray(reports.json) + '\n\n' + chalk.yellow('๐Ÿ’ก Tip: ') + chalk.white('Open HTML report in browser for best experience'), { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'blue', textAlignment: 'left' } ); console.log(locationsBox); // Success message with celebration console.log('\n' + gradient(['#00b894', '#00cec9'])('๐ŸŽŠ Reports are ready! Share your analysis with the team! ๐ŸŽŠ')); } catch (error) { reportSpinner.fail(chalk.red('โŒ Report generation failed')); console.error(chalk.red.bold('Error:'), error.message); process.exit(1); } } // ๐Ÿ”„ Update VeriQA to latest version async update() { console.log('\n' + chalk.yellow.bold('โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”')); console.log(chalk.yellow.bold('โ”‚') + chalk.cyan.bold(' ๐Ÿ”„ CHECKING FOR VERIQA UPDATES ') + chalk.yellow.bold('โ”‚')); console.log(chalk.yellow.bold('โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜')); console.log(''); try { const hasUpdate = await this.updateChecker.checkForUpdates(); if (hasUpdate) { console.log(chalk.cyan('๐Ÿš€ Proceeding with auto-upgrade...')); await this.updateChecker.autoUpgrade(); } else { console.log(chalk.green('โœ… You are already on the latest version!')); console.log(chalk.blue(`Current version: v${require('../package.json').version}`)); } } catch (error) { console.error(chalk.red('โŒ Update check failed:'), error.message); process.exit(1); } } // ๐ŸŒฟ NEW: Git Status with Enhanced Information async status() { console.clear(); const statusTitle = figlet.textSync('STATUS', { font: 'Small' }); console.log(gradient(['#00b894', '#00cec9'])(statusTitle)); const statusSpinner = ora({ text: chalk.cyan('๐Ÿ” Getting project status...'), spinner: 'dots', color: 'cyan' }).start(); try { const gitAnalyzer = new GitAnalyzer(); const projectStatus = await gitAnalyzer.getProjectStatus(); statusSpinner.succeed(chalk.green.bold('โœ… Project status retrieved!')); // Git Status Box const gitStatusBox = boxen( chalk.white.bold('๐ŸŒฟ GIT STATUS\n\n') + chalk.cyan('Current Branch: ') + chalk.yellow.bold(projectStatus.currentBranch) + '\n' + chalk.cyan('Repository: ') + chalk.white.bold(projectStatus.isClean ? 'โœ… Clean' : 'โš ๏ธ Has Changes') + '\n' + chalk.cyan('Ahead: ') + chalk.green.bold(projectStatus.ahead || 0) + ' ' + chalk.cyan('Behind: ') + chalk.red.bold(projectStatus.behind || 0) + '\n' + chalk.cyan('Staged: ') + chalk.blue.bold(projectStatus.staged || 0) + ' ' + chalk.cyan('Modified: ') + chalk.yellow.bold(projectStatus.modified || 0) + ' ' + chalk.cyan('Untracked: ') + chalk.gray.bold(projectStatus.untracked || 0) + '\n' + (projectStatus.tracking ? chalk.cyan('Tracking: ') + chalk.white(projectStatus.tracking) : ''), { padding: 1, margin: 1, borderStyle: 'classic', borderColor: 'green', textAlignment: 'left' } ); console.log(gitStatusBox); // Stash Information if (projectStatus.stash && projectStatus.stash.hasStash) { const stashBox = boxen( chalk.white.bold('๐Ÿ“ฆ STASH INFORMATION\n\n') + chalk.yellow('โš ๏ธ You have ') + chalk.red.bold(projectStatus.stash.stashCount) + chalk.yellow(' stashed changes') + '\n\n' + chalk.gray('Recent stashes:') + '\n' + projectStatus.stash.stashes.slice(0, 3).map(stash => chalk.gray(` โ€ข ${stash.message}`) ).join('\n') + '\n\n' + chalk.yellow('๐Ÿ’ก Consider applying stash before analysis'), { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'yellow', textAlignment: 'left' } ); console.log(stashBox); } } catch (error) { statusSpinner.fail(chalk.red('โŒ Failed to get status')); console.error(chalk.red.bold('Error:'), error.message); } } // ๐ŸŒฟ NEW: Compare Branches (HOTFIX: Enhanced error handling) async compareBranches(branch1, branch2) { console.clear(); const compareTitle = figlet.textSync('COMPARE', { font: 'Small' }); console.log(gradient(['#fd79a8', '#fdcb6e'])(compareTitle)); const compareBox = boxen( gradient(['#a29bfe', '#6c5ce7'])('๐Ÿ”€ BRANCH COMPARISON') + '\n' + chalk.white(`Comparing ${branch1} vs ${branch2}`) + '\n' + chalk.gray('๐ŸŽฏ Generate regression testing suggestions'), { padding: 1, margin: 1, borderStyle: 'double', borderColor: 'magenta', textAlignment: 'center' } ); console.log(compareBox); try { const gitAnalyzer = new GitAnalyzer(); const comparison = await gitAnalyzer.compareBranches(branch1, branch2); // Branch Information const branchInfoBox = boxen( chalk.white.bold('๐ŸŒฟ BRANCH INFORMATION\n\n') + chalk.cyan('Branch 1: ') + chalk.yellow.bold(comparison.branch1.name) + '\n' + chalk.gray(` โ””โ”€ ${comparison.branch1.lastCommit?.message || 'No commits'}`) + '\n' + chalk.gray(` โ””โ”€ Author: ${comparison.branch1.lastCommit?.author || 'Unknown'}`) + '\n\n' + chalk.cyan('Branch 2: ') + chalk.yellow.bold(comparison.branch2.name) + '\n' + chalk.gray(` โ””โ”€ ${comparison.branch2.lastCommit?.message || 'No commits'}`) + '\n' + chalk.gray(` โ””โ”€ Author: ${comparison.branch2.lastCommit?.author || 'Unknown'}`) + '\n\n' + chalk.cyan('Merge Base: ') + chalk.white(comparison.mergeBase?.substring(0, 8) || 'Not found'), { padding: 1, margin: 1, borderStyle: 'classic', borderColor: 'blue', textAlignment: 'left' } ); console.log(branchInfoBox); // Comparison Results const resultsBox = boxen( chalk.white.bold('๐Ÿ“Š COMPARISON RESULTS\n\n') + chalk.blue('Files Changed: ') + chalk.green.bold(comparison.comparison.summary.filesChanged) + '\n' + chalk.blue('Insertions: ') + chalk.green.bold(comparison.comparison.summary.insertions) + '\n' + chalk.blue('Deletions: ') + chalk.red.bold(comparison.comparison.summary.deletions) + '\n\n' + chalk.gray('Use these results for targeted regression testing'), { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'green', textAlignment: 'left' } ); console.log(resultsBox); // Save comparison for report generation const analysis = { type: 'branch-comparison', branch1: comparison.branch1, branch2: comparison.branch2, comparison: comparison.comparison, analysisDate: new Date().toISOString() }; await this.configManager.saveAnalysis(analysis); console.log('\n' + gradient(['#00b894', '#00cec9'])('๐ŸŽฏ Branch comparison saved! Use "veriqa report" to generate detailed reports.')); } catch (error) { // HOTFIX: Enhanced error handling with troubleshooting tips const errorBox = boxen( chalk.red.bold('โŒ BRANCH COMPARISON FAILED\n\n') + chalk.white('Error: ') + chalk.yellow(error.message) + '\n\n' + chalk.cyan.bold('๐Ÿ”ง TROUBLESHOOTING TIPS:\n') + chalk.white('1. Check available branches: ') + chalk.gray('git branch -a') + '\n' + chalk.white('2. Verify branch names: ') + chalk.gray(`git show ${branch1} && git show ${branch2}`) + '\n' + chalk.white('3. Try with full refs: ') + chalk.gray(`origin/${branch1} origin/${branch2}`) + '\n' + chalk.white('4. Ensure branches exist: ') + chalk.gray('git fetch origin') + '\n\n' + chalk.yellow('๐Ÿ’ก Example: ') + chalk.green.bold('veriqa compare main origin/feature-branch'), { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'red', textAlignment: 'left' } ); console.log(errorBox); // Don't exit, just show error console.log(chalk.gray('\n๐Ÿ’ก Tip: Use "git branch -a" to see all available branches')); } } // ๐Ÿ”ง NEW: Install Pre-commit Hooks async installHooks() { console.clear(); const hooksTitle = figlet.textSync('HOOKS', { font: 'Small' }); console.log(gradient(['#e17055', '#fdcb6e'])(hooksTitle)); const hooksSpinner = ora({ text: chalk.cyan('๐Ÿ”ง Installing pre-commit hooks...'), spinner: 'arrow3', color: 'yellow' }).start(); try { const fs = require('fs').promises; const path = require('path'); // Check if .git directory exists const gitDir = path.join(process.cwd(), '.git'); try { await fs.access(gitDir); } catch (error) { hooksSpinner.fail(chalk.red('โŒ Not a git repository')); return; } // Create hooks directory if it doesn't exist const hooksDir = path.join(gitDir, 'hooks'); await fs.mkdir(hooksDir, { recursive: true }); // Create pre-commit hook content const hookContent = `#!/bin/bash # VeriQA Pre-commit Hook # Automatically runs VeriQA analysis before commits echo "๐ŸŽฏ VeriQA: Running regression analysis..." # Get current branch and last commit CURRENT_BRANCH=$(git branch --show-current) LAST_COMMIT=$(git rev-parse HEAD~1 2>/dev/null || git rev-parse HEAD) CURRENT_COMMIT=$(git rev-parse HEAD) # Check if we have commits to compare if [ "$LAST_COMMIT" = "$CURRENT_COMMIT" ]; then echo "๐Ÿ“ VeriQA: First commit detected, skipping analysis" exit 0 fi # Run VeriQA analysis quietly echo "๐Ÿ” Analyzing changes between $LAST_COMMIT and staged changes..." veriqa analyze $LAST_COMMIT HEAD --quiet > /dev/null 2>&1 if [ $? -eq 0 ]; then echo "โœ… VeriQA: Analysis completed successfully" echo "๐Ÿ’ก Run 'veriqa report' to view detailed suggestions" else echo "โš ๏ธ VeriQA: Analysis failed, but allowing commit to proceed" echo "๐Ÿ’ก Run 'veriqa analyze $LAST_COMMIT HEAD' manually to debug" fi echo "๐Ÿš€ VeriQA: Pre-commit hook completed" exit 0 `; // Write hook file const hookPath = path.join(hooksDir, 'pre-commit'); await fs.writeFile(hookPath, hookContent); // Make hook executable const { execSync } = require('child_process'); execSync(`chmod +x "${hookPath}"`); hooksSpinner.succeed(chalk.green.bold('โœ… Pre-commit hooks installed successfully!')); // Success Information const successBox = boxen( chalk.white.bold('๐Ÿ”ง HOOKS INSTALLED\n\n') + chalk.green('โœ… Pre-commit hook: ') + chalk.cyan('Installed') + '\n' + chalk.gray(' โ””โ”€ Runs VeriQA analysis before each commit') + '\n' + chalk.gray(' โ””โ”€ Provides regression testing suggestions') + '\n\n' + chalk.yellow('๐Ÿ“ Hook Location: ') + chalk.white('.git/hooks/pre-commit') + '\n\n' + chalk.cyan('๐Ÿ’ก Next commit will trigger automatic analysis!'), { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'green', textAlignment: 'left' } ); console.log(successBox); } catch (error) { hooksSpinner.fail(chalk.red('โŒ Hook installation failed')); console.error(chalk.red.bold('Error:'), error.message); } } // ๐Ÿ” NEW: Blame Analysis async blameAnalysis(filePath, lineNumber) { console.clear(); const blameTitle = figlet.textSync('BLAME', { font: 'Small' }); console.log(gradient(['#ff7675', '#fd79a8'])(blameTitle)); if (!filePath) { const errorBox = boxen( chalk.red.bold('โŒ MISSING FILE PATH') + '\n\n' + chalk.white('Usage: ') + chalk.green.bold('veriqa blame <file-path> [line-number]') + '\n\n' + chalk.yellow('Examples:') + '\n' + chalk.gray('veriqa blame src/app.js') + '\n' + chalk.gray('veriqa blame src/app.js 45'), { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'red', textAlignment: 'left' } ); console.log(errorBox); return; } const blameSpinner = ora({ text: chalk.cyan(`๐Ÿ” Analyzing blame info for ${filePath}...`), spinner: 'dots', color: 'cyan' }).start(); try { const gitAnalyzer = new GitAnalyzer(); const blameInfo = await gitAnalyzer.getBlameInfo(filePath, lineNumber); if (blameInfo) { blameSpinner.succeed(chalk.green.bold('โœ… Blame analysis completed!')); const blameBox = boxen( chalk.white.bold('๐Ÿ” BLAME INFORMATION\n\n') + chalk.cyan('File: ') + chalk.white.bold(filePath) + '\n' + (lineNumber ? chalk.cyan('Line: ') + chalk.yellow.bold(lineNumber) + '\n' : '') + chalk.cyan('Last Modified By: ') + chalk.green.bold(blameInfo.author) + '\n' + chalk.cyan('Commit: ') + chalk.yellow.bold(blameInfo.commit.substring(0, 8)) + '\n\n' + chalk.gray('๐Ÿ’ก Use this info for targeted test reviews'), { padding: 1, margin: 1, borderStyle: 'classic', borderColor: 'blue', textAlignment: 'left' } ); console.log(blameBox); } else { blameSpinner.warn(chalk.yellow('โš ๏ธ No blame information found')); } } catch (error) { blameSpinner.fail(chalk.red('โŒ Blame analysis failed')); console.error(chalk.red.bold('Error:'), error.message); } } async setupDirectories() { const reportsDir = path.join(process.cwd(), 'veriqa-reports'); if (!fs.existsSync(reportsDir)) { fs.mkdirSync(reportsDir, { recursive: true }); } } async setupAI() { const envManager = new SmartEnvManager(); // Simple environment setup - no prompts, just add placeholder const result = await envManager.setupGeminiAPIKey(); if (result.status === 'created') { const createdBox = boxen( chalk.green.bold('โœ… .env File Created!') + '\n\n' + chalk.white('๐Ÿ”‘ GEMINI_API_KEY placeholder added') + '\n' + chalk.cyan('๐Ÿ“ Edit .env file and add your API key') + '\n' + chalk.blue('๐Ÿ”— Get free key: https://ai.google.dev/') + '\n\n' + chalk.yellow('๐Ÿ’ก VeriQA works with fallback suggestions until configured'), { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'green', textAlignment: 'center' } ); console.log(createdBox); } else if (result.status === 'appended') { console.log(chalk.blue('๐Ÿ“ Added GEMINI_API_KEY to your existing .env file')); } else if (result.status === 'existing') { console.log(chalk.green('โœ… Environment already configured')); } } } // CLI Setup with Enhanced Styling const program = new Command(); const veriqa = new VeriQA(); // Custom help display program.configureHelp({ formatHelp: (cmd, helper) => { const header = figlet.textSync('VeriQA', { font: 'Speed' }); const coloredHeader = gradient(['#00f5ff', '#fc00ff'])(header); const helpBox = boxen( chalk.white.bold('๐ŸŽฏ VERIQA - Smart Manual QA Test Advisor') + '\n' + chalk.yellow('๐Ÿ‡ฎ๐Ÿ‡ณ Proudly Made in India with AI Power ๐Ÿ‡ฎ๐Ÿ‡ณ') + '\n\n' + chalk.cyan.bold('COMMANDS:') + '\n' + chalk.white(' ๐Ÿ”ง init Initialize project scan') + '\n' + chalk.white(' ๐Ÿ” analyze <old> <new> Analyze git commits') + '\n' + chalk.white(' ๐Ÿ“Š report Generate reports') + '\n' + chalk.white(' ๐Ÿ”„ update Update to latest version') + '\n' + chalk.white(' ๐ŸŒฟ status Get git status') + '\n' + chalk.white(' ๐ŸŒฟ compare <branch1> <branch2> Compare branches') + '\n' + chalk.white(' ๐Ÿ”ง hooks Install pre-commit hooks') + '\n' + chalk.white(' ๐Ÿ” blame <file> [line] Perform blame analysis') + '\n\n' + chalk.cyan.bold('EXAMPLES:') + '\n' + chalk.gray(' veriqa init') + '\n' + chalk.gray(' veriqa analyze abc123 def456') + '\n' + chalk.gray(' veriqa report') + '\n' + chalk.gray(' veriqa status') + '\n' + chalk.gray(' veriqa compare main feature-branch') + '\n' + chalk.gray(' veriqa hooks') + '\n' + chalk.gray(' veriqa blame src/app.js 45') + '\n\n' + chalk.yellow('โœจ AI-powered regression testing made simple! โœจ'), { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'cyan', textAlignment: 'left' } ); return coloredHeader + '\n' + helpBox + '\n'; } }); program .name('veriqa') .description('๐ŸŽฏ VeriQA - Smart Manual QA Test Advisor with Advanced Git Integration') .version(require('../package.json').version); program .command('init') .description('๐Ÿ”ง Initialize project and scan for testing frameworks') .action(async () => await veriqa.init()); program .command('analyze <old-commit> <new-commit>') .description('๐Ÿ” Analyze commits and generate AI-powered test suggestions') .action(async (oldCommit, newCommit) => await veriqa.analyze(oldCommit, newCommit)); program .command('report') .description('๐Ÿ“Š Generate comprehensive HTML/PDF report') .action(async () => await veriqa.report()); program .command('update') .description('๐Ÿ”„ Update VeriQA to the latest version') .action(async () => await veriqa.update()); program .command('status') .description('๐ŸŒฟ Get git status with enhanced information') .action(async () => await veriqa.status()); program .command('compare <branch1> <branch2>') .description('๐ŸŒฟ Compare branches and generate regression suggestions') .action(async (branch1, branch2) => await veriqa.compareBranches(branch1, branch2)); program .command('hooks') .description('๐Ÿ”ง Install pre-commit hooks for VeriQA') .action(async () => await veriqa.installHooks()); program .command('blame <file> [line]') .description('๐Ÿ” Perform blame analysis on a file or specific line') .action(async (file, line) => await veriqa.blameAnalysis(file, line)); // Only run if called directly if (require.main === module) { program.parse(); } module.exports = VeriQA;