ctrlshiftleft
Version:
AI-powered toolkit for embedding QA and security testing into development workflows
492 lines (479 loc) ⢠19.3 kB
JavaScript
"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