UNPKG

figma-restoration-mcp-vue-tools

Version:

Professional Figma Component Restoration Kit - MCP tools with snapDOM-powered high-quality screenshots, intelligent shadow detection, and advanced diff analysis for Vue component restoration. Features enhanced figma_compare with color-coded region analysi

448 lines (384 loc) 13.8 kB
import { TimeoutManager } from './timeout-manager.js'; import { ErrorHandler } from './error-handler.js'; import { OperationLogger } from './operation-logger.js'; import { globalConfig } from './config-manager.js'; import { ValidationError } from './error-handler.js'; import { ErrorResponseSystem } from './error-response-system.js'; import crypto from 'crypto'; /** * ReliabilityWrapper - Base class for enhancing existing tools with reliability features * * This wrapper provides: * - Timeout protection for all operations * - Comprehensive error handling and categorization * - Parameter validation and enhancement * - Performance monitoring and logging * - Backward compatibility with existing tool interfaces */ export class ReliabilityWrapper { constructor(originalTool, config = {}) { if (!originalTool) { throw new Error('Original tool is required for ReliabilityWrapper'); } this.originalTool = originalTool; this.toolName = originalTool.constructor.name; // Merge configuration with defaults this.config = this.mergeConfig(config); // Initialize logger with tool-specific settings this.logger = new OperationLogger(this.toolName, { verbose: this.config.verbose, logLevel: this.config.logLevel, includePerformance: this.config.enableMetrics }); // Preserve original tool properties for backward compatibility this.description = originalTool.description || `Enhanced ${this.toolName}`; this.inputSchema = { ...originalTool.inputSchema } || {}; // Add reliability-specific schema properties this.enhanceInputSchema(); } /** * Enhanced execute method with reliability features * @param {Object} args - Tool arguments * @returns {Promise<Object>} - Tool result with reliability enhancements */ async execute(args) { const operationId = this.generateOperationId(); const startTime = Date.now(); try { // Pre-execution logging this.logger.logStart(operationId, 'execute', args); // Enhanced parameter validation const validatedArgs = await this.validateAndEnhanceArgs(args); // Get timeout configuration for this operation const timeoutConfig = this.getTimeoutConfig(validatedArgs); // Execute with timeout and error handling const result = await TimeoutManager.withTimeout( () => this.originalTool.execute(validatedArgs), { ...timeoutConfig, operationName: `${this.toolName}.execute`, cleanupFn: () => this.cleanup(operationId, validatedArgs) } ); // Post-execution logging and metrics const duration = Date.now() - startTime; const metrics = this.collectMetrics(result, duration, validatedArgs); this.logger.logSuccess(operationId, result, metrics); // Return enhanced result return this.enhanceResult(result, { operationId, duration, metrics, toolName: this.toolName }); } catch (error) { // Enhanced error handling with standardized response system const duration = Date.now() - startTime; this.logger.logError(operationId, error, { tool: this.toolName, operation: 'execute', duration, operationId }); // Return standardized error response return ErrorResponseSystem.createErrorResponse(error, { tool: this.toolName, operation: 'execute', operationId, duration, args: args }); } } /** * Validate and enhance input arguments * @param {Object} args - Original arguments * @returns {Object} - Validated and enhanced arguments */ async validateAndEnhanceArgs(args) { if (!args || typeof args !== 'object') { throw new ValidationError('Arguments must be a valid object'); } const validatedArgs = { ...args }; // Basic schema validation if available if (this.originalTool.inputSchema) { this.validateAgainstSchema(validatedArgs, this.originalTool.inputSchema); } // Add reliability-specific enhancements validatedArgs._reliability = { wrapperVersion: '1.0.0', enhancedAt: new Date().toISOString(), toolName: this.toolName }; // Path validation and normalization await this.validatePaths(validatedArgs); // Resource availability checks await this.checkResourceAvailability(validatedArgs); return validatedArgs; } /** * Get timeout configuration for the current operation * @param {Object} args - Operation arguments * @returns {Object} - Timeout configuration */ getTimeoutConfig(args) { // Check for operation-specific timeout in args if (args.timeout && typeof args.timeout === 'number') { return { timeoutMs: args.timeout, retryCount: args.retryCount || 0, retryDelay: args.retryDelay || 1000 }; } // Get tool-specific timeout from global config const toolTimeouts = globalConfig.get('timeouts', this.toolName.toLowerCase().replace('tool', '')); const defaultTimeout = toolTimeouts?.default || globalConfig.get('global', 'timeout'); // Determine operation type for specific timeout let operationType = 'default'; if (args.inputPath || args.outputPath || args.projectPath) { operationType = 'fileOperations'; } if (args.componentName && this.toolName.includes('Screenshot')) { operationType = 'screenshot'; } if (this.toolName.includes('Compare')) { operationType = 'imageProcessing'; } const specificTimeout = toolTimeouts?.[operationType] || defaultTimeout; return { timeoutMs: specificTimeout, retryCount: globalConfig.get('global', 'retryCount'), retryDelay: globalConfig.get('global', 'retryDelay') }; } /** * Cleanup function called on timeout or failure * @param {string} operationId - Operation identifier * @param {Object} args - Operation arguments */ async cleanup(operationId, args) { try { this.logger.logDebug(`Executing cleanup for operation ${operationId}`); // Tool-specific cleanup logic can be overridden await this.performCleanup(args); this.logger.logDebug(`Cleanup completed for operation ${operationId}`); } catch (cleanupError) { this.logger.logWarning(`Cleanup failed for operation ${operationId}`, { error: cleanupError.message }); } } /** * Collect performance and operation metrics * @param {Object} result - Operation result * @param {number} duration - Operation duration in ms * @param {Object} args - Operation arguments * @returns {Object} - Collected metrics */ collectMetrics(result, duration, args) { const metrics = { duration, timestamp: new Date().toISOString(), success: result.success !== false, toolName: this.toolName }; // Add tool-specific metrics if (result.matchPercentage !== undefined) { metrics.matchPercentage = result.matchPercentage; } if (result.fileSize !== undefined) { metrics.fileSize = result.fileSize; } if (args.threshold !== undefined) { metrics.threshold = args.threshold; } return metrics; } /** * Enhance the result with reliability metadata * @param {Object} result - Original result * @param {Object} metadata - Reliability metadata * @returns {Object} - Enhanced result */ enhanceResult(result, metadata) { // If the original result is already an error response, preserve it if (result && typeof result === 'object' && result.success === false) { return result; } // Use standardized success response format for successful results return ErrorResponseSystem.createSuccessResponse(result, metadata); } /** * Merge configuration with defaults * @param {Object} config - User configuration * @returns {Object} - Merged configuration */ mergeConfig(config) { const defaults = { verbose: globalConfig.get('global', 'verbose'), logLevel: globalConfig.get('global', 'logLevel'), enableMetrics: globalConfig.get('global', 'enableMetrics'), validatePaths: true, checkResources: true }; return { ...defaults, ...config }; } /** * Enhance input schema with reliability options */ enhanceInputSchema() { if (!this.inputSchema.properties) { this.inputSchema.properties = {}; } // Add reliability-specific properties this.inputSchema.properties.timeout = { type: 'number', description: 'Operation timeout in milliseconds (optional)', minimum: 100 }; this.inputSchema.properties.retryCount = { type: 'number', description: 'Number of retry attempts (optional)', minimum: 0, maximum: 5 }; this.inputSchema.properties.retryDelay = { type: 'number', description: 'Delay between retries in milliseconds (optional)', minimum: 100 }; this.inputSchema.properties.verbose = { type: 'boolean', description: 'Enable verbose logging for this operation (optional)' }; } /** * Validate arguments against schema * @param {Object} args - Arguments to validate * @param {Object} schema - JSON schema */ validateAgainstSchema(args, schema) { if (!schema.properties) return; // Check required properties if (schema.required) { for (const requiredProp of schema.required) { if (args[requiredProp] === undefined || args[requiredProp] === null) { throw new ValidationError(`Required property '${requiredProp}' is missing`); } } } // Basic type validation for (const [propName, propSchema] of Object.entries(schema.properties)) { const value = args[propName]; if (value !== undefined && propSchema.type) { if (!this.validateType(value, propSchema.type)) { throw new ValidationError(`Property '${propName}' must be of type ${propSchema.type}`); } // Additional validations if (propSchema.minimum !== undefined && typeof value === 'number' && value < propSchema.minimum) { throw new ValidationError(`Property '${propName}' must be >= ${propSchema.minimum}`); } if (propSchema.maximum !== undefined && typeof value === 'number' && value > propSchema.maximum) { throw new ValidationError(`Property '${propName}' must be <= ${propSchema.maximum}`); } } } } /** * Validate type of a value * @param {*} value - Value to validate * @param {string} expectedType - Expected type * @returns {boolean} - True if type is valid */ validateType(value, expectedType) { switch (expectedType) { case 'string': return typeof value === 'string'; case 'number': return typeof value === 'number' && !isNaN(value); case 'boolean': return typeof value === 'boolean'; case 'object': return typeof value === 'object' && value !== null && !Array.isArray(value); case 'array': return Array.isArray(value); default: return true; } } /** * Validate and normalize file paths * @param {Object} args - Arguments containing paths */ async validatePaths(args) { if (!this.config.validatePaths) return; const pathProperties = ['inputPath', 'outputPath', 'projectPath']; for (const pathProp of pathProperties) { if (args[pathProp]) { try { // Normalize path args[pathProp] = path.resolve(args[pathProp]); // Check if input paths exist (except outputPath which might be created) if (pathProp !== 'outputPath') { await fs.access(args[pathProp]); } } catch (error) { throw new ValidationError(`Invalid ${pathProp}: ${error.message}`); } } } } /** * Check resource availability (disk space, memory, etc.) * @param {Object} args - Operation arguments */ async checkResourceAvailability(args) { if (!this.config.checkResources) return; // Basic resource checks can be implemented here // For now, we'll do a simple check try { // Check if we can write to temp directory const tempDir = process.env.TMPDIR || '/tmp'; await fs.access(tempDir, fs.constants.W_OK); } catch (error) { throw new ValidationError('Insufficient system resources or permissions'); } } /** * Tool-specific cleanup logic (to be overridden by subclasses) * @param {Object} args - Operation arguments */ async performCleanup(args) { // Default implementation - can be overridden by specific tool wrappers this.logger.logDebug('Performing default cleanup'); } /** * Generate unique operation ID * @returns {string} - Unique operation identifier */ generateOperationId() { return `${this.toolName}_${Date.now()}_${crypto.randomBytes(4).toString('hex')}`; } /** * Get tool statistics * @returns {Object} - Tool usage statistics */ getStatistics() { return this.logger.getStatistics(); } /** * Update wrapper configuration * @param {Object} newConfig - New configuration options */ updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; this.logger.setOptions({ verbose: this.config.verbose, logLevel: this.config.logLevel, includePerformance: this.config.enableMetrics }); } } // Import fs and path modules import fs from 'fs/promises'; import path from 'path';