UNPKG

ctrlshiftleft

Version:

AI-powered toolkit for embedding QA and security testing into development workflows

492 lines (479 loc) • 19.3 kB
"use strict"; /** * Error Handler * * Comprehensive error handling and recovery utilities for ctrl.shift.left tools. * Provides standardized error formatting, recovery strategies, and user guidance. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.errorHandler = exports.ErrorHandler = exports.ErrorCategory = void 0; const path = __importStar(require("path")); const platformUtils_1 = require("./platformUtils"); /** * Error code categories used for documentation linking and recovery strategies */ var ErrorCategory; (function (ErrorCategory) { ErrorCategory["FILESYSTEM"] = "filesystem"; ErrorCategory["NETWORK"] = "network"; ErrorCategory["CONFIG"] = "configuration"; ErrorCategory["VALIDATION"] = "validation"; ErrorCategory["GENERATION"] = "generation"; ErrorCategory["EXECUTION"] = "execution"; ErrorCategory["SECURITY"] = "security"; ErrorCategory["FRAMEWORK"] = "framework"; ErrorCategory["PERMISSION"] = "permission"; ErrorCategory["UNKNOWN"] = "unknown"; })(ErrorCategory || (exports.ErrorCategory = ErrorCategory = {})); /** * Fallback templates for various operations when generation fails */ const FALLBACK_TEMPLATES = { 'test': ` import { test, expect } from '@playwright/test'; /** * Fallback test generated by ctrl.shift.left * * This is a basic test template created when full test generation failed. * You can use this as a starting point to write your own tests. */ test.describe('Basic Component Test', () => { test('should render without errors', async ({ page }) => { // Navigate to your component or page await page.goto('/'); // Wait for component to be visible await page.waitForSelector('.your-component-class'); // Basic assertion await expect(page.locator('.your-component-class')).toBeVisible(); }); }); `, 'checklist': ` { "name": "QA Checklist", "version": "1.0.0", "timestamp": "${new Date().toISOString()}", "items": [ { "category": "Functionality", "checks": [ { "name": "Component renders without errors", "status": "pending" }, { "name": "All interactive elements work as expected", "status": "pending" }, { "name": "Error states are handled appropriately", "status": "pending" } ] }, { "category": "Security", "checks": [ { "name": "Input validation is implemented", "status": "pending" }, { "name": "No sensitive data is exposed", "status": "pending" }, { "name": "Authentication controls are enforced", "status": "pending" } ] } ] } `, 'security-report': ` # Security Analysis *Generated by ctrl.shift.left (Fallback Template)* ## Summary This is a fallback security report generated when the full security analysis could not be completed. ## Recommended Manual Checks 1. **Check for XSS vulnerabilities** - Review any areas where user input is displayed in the UI - Ensure proper encoding and sanitization is applied 2. **Check for authentication issues** - Verify that authentication controls are properly implemented - Ensure sensitive operations require authentication 3. **Check for data exposure** - Review API responses for sensitive information - Ensure proper authorization checks are in place ## Next Steps 1. Run a manual security review 2. Consider using dedicated security scanning tools 3. Fix any issues found and regenerate the security report ` }; /** * Error handler class for standardizing error handling across ctrl.shift.left */ class ErrorHandler { constructor(options = {}) { this.logPath = options.logPath || path.join(process.cwd(), '.ctrlshiftleft', 'logs', 'error.log'); this.verbose = options.verbose !== undefined ? options.verbose : true; this.autoRecover = options.autoRecover !== undefined ? options.autoRecover : true; // Ensure log directory exists platformUtils_1.PathUtils.ensureDir(path.dirname(this.logPath)); } /** * Process and enhance an error with additional context * @param error Original error * @param operation Operation that caused the error * @param context Additional context information * @returns Enhanced error object */ handleError(error, operation, context = {}) { // Get error message string const errorMessage = typeof error === 'string' ? error : error.message; const originalError = typeof error === 'string' ? undefined : error; // Categorize the error const category = this.categorizeError(errorMessage, operation); // Generate an error code const code = this.generateErrorCode(category, operation); // Get recovery steps const recovery = this.getRecoverySteps(category, operation, context); // Generate documentation link const docLink = this.getDocumentationLink(category, code); // Log the error this.logError({ message: errorMessage, originalError, code, category, recovery, docLink, context }); return { message: errorMessage, originalError, code, category, recovery, docLink, context }; } /** * Display a user-friendly error message * @param error Enhanced error object */ displayError(error) { console.error(`\nāŒ Error: ${error.message}`); console.error(` Code: ${error.code} (${error.category})`); if (this.verbose) { console.error('\nšŸ“‹ Recovery steps:'); error.recovery.forEach((step, index) => { console.error(` ${index + 1}. ${step}`); }); if (error.docLink) { console.error(`\nšŸ“š For more information, visit: ${error.docLink}`); } if (error.context && Object.keys(error.context).length > 0) { console.error('\nšŸ” Context:'); Object.entries(error.context).forEach(([key, value]) => { console.error(` ${key}: ${value}`); }); } } else { console.error(`\nšŸ’” Use --verbose for detailed recovery steps`); console.error(` Or visit: ${error.docLink}`); } } /** * Attempt to recover from an error * @param error Enhanced error object * @param fallbackType Type of fallback to generate * @param outputPath Path to write fallback output * @returns Success status and path to fallback if created */ attemptRecovery(error, fallbackType, outputPath) { // Only attempt recovery if enabled if (!this.autoRecover) { return { success: false }; } console.log('\nšŸ”„ Attempting recovery...'); // Try to recover based on error category switch (error.category) { case ErrorCategory.FILESYSTEM: return this.recoverFromFilesystemError(error, fallbackType, outputPath); case ErrorCategory.NETWORK: return this.recoverFromNetworkError(error); case ErrorCategory.GENERATION: return this.recoverFromGenerationError(error, fallbackType, outputPath); default: console.log('āŒ No automatic recovery available for this error type'); return { success: false }; } } /** * Recover from filesystem errors */ recoverFromFilesystemError(error, fallbackType, outputPath) { // Check if the error is about a directory not existing if (error.message.includes('no such file or directory') || error.message.includes('cannot find') || error.message.includes('ENOENT')) { // Try to create the directory if (error.context?.path) { const dirPath = error.context.isDirectory ? error.context.path : path.dirname(error.context.path); console.log(`šŸ“ Creating directory: ${dirPath}`); const success = platformUtils_1.PathUtils.ensureDir(dirPath); if (success) { console.log('āœ… Directory created successfully'); return { success: true }; } } } // If we have a fallback type and output path, generate a fallback file if (fallbackType && outputPath && FALLBACK_TEMPLATES[fallbackType]) { console.log(`šŸ“„ Generating fallback ${fallbackType} file`); const result = platformUtils_1.FileUtils.writeFile(outputPath, FALLBACK_TEMPLATES[fallbackType]); if (result) { console.log(`āœ… Fallback file created at: ${outputPath}`); return { success: true, fallbackPath: outputPath }; } } return { success: false }; } /** * Recover from network errors */ recoverFromNetworkError(error) { console.log('šŸ”„ Network error recovery not yet implemented'); return { success: false }; } /** * Recover from generation errors */ recoverFromGenerationError(error, fallbackType, outputPath) { // If we have a fallback type and output path, generate a fallback file if (fallbackType && outputPath && FALLBACK_TEMPLATES[fallbackType]) { console.log(`šŸ“„ Generating fallback ${fallbackType} template`); // Ensure output directory exists platformUtils_1.PathUtils.ensureDir(path.dirname(outputPath)); // Write fallback template const result = platformUtils_1.FileUtils.writeFile(outputPath, FALLBACK_TEMPLATES[fallbackType]); if (result) { console.log(`āœ… Fallback template created at: ${outputPath}`); return { success: true, fallbackPath: outputPath }; } } return { success: false }; } /** * Log an error to the error log file */ logError(error) { try { // Create log entry const logEntry = { timestamp: new Date().toISOString(), ...error, stack: error.originalError?.stack }; // Format log entry as JSON const logJson = JSON.stringify(logEntry, null, 2); // Append to log file platformUtils_1.FileUtils.appendFile(this.logPath, `${logJson}\n${'-'.repeat(80)}\n`); } catch (logError) { // Don't let logging errors disrupt the application console.error(`Warning: Could not log error to ${this.logPath}`); } } /** * Categorize an error based on its message and context */ categorizeError(errorMessage, operation) { const msg = errorMessage.toLowerCase(); // Filesystem errors if (msg.includes('enoent') || msg.includes('no such file') || msg.includes('could not find') || msg.includes('directory') || msg.includes('path')) { return ErrorCategory.FILESYSTEM; } // Network errors if (msg.includes('fetch') || msg.includes('network') || msg.includes('connection') || msg.includes('timeout') || msg.includes('econnrefused') || msg.includes('socket')) { return ErrorCategory.NETWORK; } // Configuration errors if (msg.includes('config') || msg.includes('settings') || msg.includes('options') || msg.includes('.env') || msg.includes('environment')) { return ErrorCategory.CONFIG; } // Permission errors if (msg.includes('permission') || msg.includes('access denied') || msg.includes('unauthorized') || msg.includes('eacces')) { return ErrorCategory.PERMISSION; } // Generation errors if (operation.includes('generate') || operation.includes('create') || operation.includes('build')) { return ErrorCategory.GENERATION; } // Execution errors if (operation.includes('run') || operation.includes('execute') || operation.includes('test')) { return ErrorCategory.EXECUTION; } // Security errors if (operation.includes('security') || operation.includes('analyze') || operation.includes('scan')) { return ErrorCategory.SECURITY; } // Framework errors if (msg.includes('react') || msg.includes('angular') || msg.includes('vue') || msg.includes('next') || msg.includes('framework')) { return ErrorCategory.FRAMEWORK; } // Default to unknown return ErrorCategory.UNKNOWN; } /** * Generate a unique error code for tracking and documentation */ generateErrorCode(category, operation) { const categoryPrefix = category.substring(0, 3).toUpperCase(); const operationHash = this.hashString(operation) % 1000; const timestamp = Date.now() % 10000; return `${categoryPrefix}-${operationHash.toString().padStart(3, '0')}-${timestamp}`; } /** * Get recovery steps based on error category and context */ getRecoverySteps(category, operation, context) { switch (category) { case ErrorCategory.FILESYSTEM: return [ 'Check if the file or directory exists', 'Ensure you have permission to access the location', 'Try specifying an absolute path instead of a relative path', 'Create any missing directories manually' ]; case ErrorCategory.NETWORK: return [ 'Check your internet connection', 'Verify the API endpoint is correct and accessible', 'Check if authentication credentials are valid', 'Try increasing the timeout value', 'Check if a proxy or firewall is blocking the connection' ]; case ErrorCategory.CONFIG: return [ 'Verify your .ctrlshiftleft/config.js file exists and is valid', 'Check environment variables required by the operation', 'Ensure configuration paths are correct for your platform', 'Try running with --reset-config to use default configuration' ]; case ErrorCategory.PERMISSION: return [ 'Check file and directory permissions', 'Try running with elevated privileges if appropriate', 'Ensure your user account has access to the resources', 'On Windows, check if files are locked by another process' ]; case ErrorCategory.GENERATION: return [ 'Check if the input file exists and contains valid code', 'Ensure the component follows standard patterns', 'Try simplifying the component if it is complex', 'Use --output to specify a writable location for generated files' ]; case ErrorCategory.EXECUTION: return [ 'Check if the test framework is properly installed', 'Ensure required browsers or drivers are available', 'Check for environment-specific configuration issues', 'Try running with --verbose for more detailed output' ]; case ErrorCategory.SECURITY: return [ 'Check if the file to analyze exists and is readable', 'Ensure OPENAI_API_KEY is set if using AI features', 'Try running with --pattern-only to use only pattern-based analysis', 'Use --output to specify a writable location for the report' ]; case ErrorCategory.FRAMEWORK: return [ 'Ensure your project uses a supported framework', 'Check if framework-specific configuration exists', 'Try running the framework-specific setup command', 'Update to the latest version of the framework' ]; default: return [ 'Check the command syntax and arguments', 'Try updating ctrl.shift.left to the latest version', 'Run with --verbose for more detailed error information', 'Check the documentation for usage examples' ]; } } /** * Get documentation link for error category */ getDocumentationLink(category, code) { return `https://github.com/johngaspar/ctrlshiftleft/docs/troubleshooting#${category}-${code.toLowerCase()}`; } /** * Simple string hash function for generating error codes */ hashString(str) { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32bit integer } return Math.abs(hash); } } exports.ErrorHandler = ErrorHandler; // Export singleton instance exports.errorHandler = new ErrorHandler(); //# sourceMappingURL=errorHandler.js.map