UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

689 lines (688 loc) 28.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PluginCommandValidator = exports.ValidationSeverity = exports.TransformationType = exports.ValidationRuleType = void 0; exports.createCommandValidator = createCommandValidator; exports.createValidationSchema = createValidationSchema; exports.formatValidationResult = formatValidationResult; const events_1 = require("events"); const chalk_1 = __importDefault(require("chalk")); const error_handler_1 = require("./error-handler"); // Validation rule types var ValidationRuleType; (function (ValidationRuleType) { ValidationRuleType["REQUIRED"] = "required"; ValidationRuleType["TYPE"] = "type"; ValidationRuleType["RANGE"] = "range"; ValidationRuleType["LENGTH"] = "length"; ValidationRuleType["PATTERN"] = "pattern"; ValidationRuleType["ENUM"] = "enum"; ValidationRuleType["CUSTOM"] = "custom"; ValidationRuleType["CONDITIONAL"] = "conditional"; ValidationRuleType["DEPENDENCY"] = "dependency"; ValidationRuleType["EXCLUSION"] = "exclusion"; })(ValidationRuleType || (exports.ValidationRuleType = ValidationRuleType = {})); // Parameter transformation types var TransformationType; (function (TransformationType) { TransformationType["CASE"] = "case"; TransformationType["TRIM"] = "trim"; TransformationType["PARSE"] = "parse"; TransformationType["FORMAT"] = "format"; TransformationType["NORMALIZE"] = "normalize"; TransformationType["CONVERT"] = "convert"; TransformationType["SANITIZE"] = "sanitize"; TransformationType["EXPAND"] = "expand"; TransformationType["RESOLVE"] = "resolve"; TransformationType["CUSTOM"] = "custom"; })(TransformationType || (exports.TransformationType = TransformationType = {})); // Validation severity levels var ValidationSeverity; (function (ValidationSeverity) { ValidationSeverity["ERROR"] = "error"; ValidationSeverity["WARNING"] = "warning"; ValidationSeverity["INFO"] = "info"; })(ValidationSeverity || (exports.ValidationSeverity = ValidationSeverity = {})); // Plugin command validator class PluginCommandValidator extends events_1.EventEmitter { constructor() { super(); this.schemas = new Map(); this.validationCache = new Map(); this.globalValidationConfig = { enableCaching: true, cacheSize: 1000, enableMetrics: true, strictMode: false }; this.initializeBuiltInRules(); this.initializeBuiltInTransformations(); } // Initialize built-in validation rules initializeBuiltInRules() { this.builtInRules = { required: (message = 'Field is required') => ({ type: ValidationRuleType.REQUIRED, severity: ValidationSeverity.ERROR, message, validator: (value) => value !== undefined && value !== null && value !== '' }), type: (type, message) => ({ type: ValidationRuleType.TYPE, severity: ValidationSeverity.ERROR, message: message || `Field must be of type ${type}`, validator: (value) => { switch (type) { case 'string': return typeof value === 'string'; case 'number': return typeof value === 'number' && !isNaN(value); case 'boolean': return typeof value === 'boolean'; case 'array': return Array.isArray(value); case 'object': return value !== null && typeof value === 'object' && !Array.isArray(value); default: return true; } } }), minLength: (min, message) => ({ type: ValidationRuleType.LENGTH, severity: ValidationSeverity.ERROR, message: message || `Field must be at least ${min} characters long`, validator: (value) => typeof value === 'string' && value.length >= min }), maxLength: (max, message) => ({ type: ValidationRuleType.LENGTH, severity: ValidationSeverity.ERROR, message: message || `Field must be no more than ${max} characters long`, validator: (value) => typeof value === 'string' && value.length <= max }), min: (min, message) => ({ type: ValidationRuleType.RANGE, severity: ValidationSeverity.ERROR, message: message || `Field must be at least ${min}`, validator: (value) => typeof value === 'number' && value >= min }), max: (max, message) => ({ type: ValidationRuleType.RANGE, severity: ValidationSeverity.ERROR, message: message || `Field must be no more than ${max}`, validator: (value) => typeof value === 'number' && value <= max }), pattern: (pattern, message) => ({ type: ValidationRuleType.PATTERN, severity: ValidationSeverity.ERROR, message: message || `Field must match pattern ${pattern.source}`, validator: (value) => typeof value === 'string' && pattern.test(value) }), enum: (values, message) => ({ type: ValidationRuleType.ENUM, severity: ValidationSeverity.ERROR, message: message || `Field must be one of: ${values.join(', ')}`, validator: (value) => values.includes(value) }), email: (message = 'Field must be a valid email address') => ({ type: ValidationRuleType.PATTERN, severity: ValidationSeverity.ERROR, message, validator: (value) => { const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return typeof value === 'string' && emailPattern.test(value); } }), url: (message = 'Field must be a valid URL') => ({ type: ValidationRuleType.PATTERN, severity: ValidationSeverity.ERROR, message, validator: (value) => { try { new URL(value); return true; } catch { return false; } } }), path: (mustExist = false, message) => ({ type: ValidationRuleType.CUSTOM, severity: ValidationSeverity.ERROR, message: message || (mustExist ? 'Path must exist' : 'Field must be a valid path'), validator: (value) => { if (typeof value !== 'string') return false; if (!mustExist) return true; try { const fs = require('fs'); return fs.existsSync(value); } catch { return false; } } }), json: (message = 'Field must be valid JSON') => ({ type: ValidationRuleType.CUSTOM, severity: ValidationSeverity.ERROR, message, validator: (value) => { if (typeof value !== 'string') return false; try { JSON.parse(value); return true; } catch { return false; } } }), custom: (validator, message = 'Field is invalid') => ({ type: ValidationRuleType.CUSTOM, severity: ValidationSeverity.ERROR, message, validator }) }; } // Initialize built-in transformations initializeBuiltInTransformations() { this.builtInTransformations = { trim: (options = { start: true, end: true }) => ({ type: TransformationType.TRIM, order: 1, transformer: (value) => { if (typeof value !== 'string') return value; if (options.start && options.end) return value.trim(); if (options.start) return value.replace(/^\s+/, ''); if (options.end) return value.replace(/\s+$/, ''); return value; } }), lowercase: () => ({ type: TransformationType.CASE, order: 2, transformer: (value) => typeof value === 'string' ? value.toLowerCase() : value }), uppercase: () => ({ type: TransformationType.CASE, order: 2, transformer: (value) => typeof value === 'string' ? value.toUpperCase() : value }), camelCase: () => ({ type: TransformationType.CASE, order: 2, transformer: (value) => { if (typeof value !== 'string') return value; return value.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : ''); } }), kebabCase: () => ({ type: TransformationType.CASE, order: 2, transformer: (value) => { if (typeof value !== 'string') return value; return value.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`).replace(/^-/, ''); } }), snakeCase: () => ({ type: TransformationType.CASE, order: 2, transformer: (value) => { if (typeof value !== 'string') return value; return value.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`).replace(/^_/, ''); } }), parseNumber: (options = { float: true, base: 10 }) => ({ type: TransformationType.PARSE, order: 3, transformer: (value) => { if (typeof value === 'number') return value; if (typeof value !== 'string') return value; const parsed = options.float ? parseFloat(value) : parseInt(value, options.base); return isNaN(parsed) ? value : parsed; } }), parseBoolean: () => ({ type: TransformationType.PARSE, order: 3, transformer: (value) => { if (typeof value === 'boolean') return value; if (typeof value !== 'string') return value; const lower = value.toLowerCase(); if (['true', '1', 'yes', 'on'].includes(lower)) return true; if (['false', '0', 'no', 'off'].includes(lower)) return false; return value; } }), parseJSON: () => ({ type: TransformationType.PARSE, order: 3, transformer: (value) => { if (typeof value !== 'string') return value; try { return JSON.parse(value); } catch { return value; } } }), expandPath: (options = {}) => ({ type: TransformationType.EXPAND, order: 4, transformer: (value) => { if (typeof value !== 'string') return value; const path = require('path'); const os = require('os'); // Expand ~ to home directory if (value.startsWith('~/')) { value = path.join(os.homedir(), value.slice(2)); } // Resolve relative to specific directory if (options.relative) { value = path.resolve(options.relative, value); } return value; } }), resolvePath: () => ({ type: TransformationType.RESOLVE, order: 5, transformer: (value) => { if (typeof value !== 'string') return value; const path = require('path'); return path.resolve(value); } }), sanitizeHtml: () => ({ type: TransformationType.SANITIZE, order: 6, transformer: (value) => { if (typeof value !== 'string') return value; return value.replace(/<[^>]*>/g, ''); } }), normalizeUrl: () => ({ type: TransformationType.NORMALIZE, order: 6, transformer: (value) => { if (typeof value !== 'string') return value; try { const url = new URL(value); return url.toString(); } catch { return value; } } }), custom: (transformer, order = 10) => ({ type: TransformationType.CUSTOM, order, transformer }) }; } // Register validation schema for a command registerSchema(commandId, schema) { this.schemas.set(commandId, schema); this.emit('schema-registered', { commandId, schema }); } // Remove validation schema removeSchema(commandId) { const removed = this.schemas.delete(commandId); if (removed) { this.validationCache.delete(commandId); this.emit('schema-removed', { commandId }); } return removed; } // Validate and transform command parameters async validateAndTransform(commandId, args, options, context) { const cacheKey = this.generateCacheKey(commandId, args, options); if (this.globalValidationConfig.enableCaching && this.validationCache.has(cacheKey)) { return this.validationCache.get(cacheKey); } const schema = this.schemas.get(commandId); if (!schema) { // No schema means no validation/transformation return { valid: true, errors: [], warnings: [], info: [], transformedArgs: { ...args }, transformedOptions: { ...options } }; } const result = { valid: true, errors: [], warnings: [], info: [], transformedArgs: { ...args }, transformedOptions: { ...options } }; this.emit('validation-started', { commandId, args, options }); try { // Apply global transformations first if (schema.transformations) { await this.applyTransformations(schema.transformations, result.transformedArgs, result.transformedOptions, context); } // Validate and transform arguments if (schema.arguments) { await this.validateAndTransformArguments(schema.arguments, result.transformedArgs, result.transformedOptions, result, context); } // Validate and transform options if (schema.options) { await this.validateAndTransformOptions(schema.options, result.transformedArgs, result.transformedOptions, result, context); } // Apply global validation rules if (schema.globalRules) { await this.applyGlobalRules(schema.globalRules, result.transformedArgs, result.transformedOptions, result, context); } // Check validation result result.valid = result.errors.length === 0; // Fail fast if configured and there are errors if (schema.failFast && result.errors.length > 0) { throw new error_handler_1.ValidationError(`Validation failed: ${result.errors[0].message}`); } // Cache result if enabled if (this.globalValidationConfig.enableCaching) { this.addToCache(cacheKey, result); } this.emit('validation-completed', { commandId, result }); } catch (error) { this.emit('validation-error', { commandId, error }); throw error; } return result; } // Validate and transform arguments async validateAndTransformArguments(argumentSchemas, args, options, result, context) { for (const [argName, argSchema] of Object.entries(argumentSchemas)) { const value = args[argName]; // Apply transformations if (argSchema.transformations) { args[argName] = await this.applyTransformationChain(argSchema.transformations, value, args, options, context); } // Apply validation rules for (const rule of argSchema.rules) { await this.applyValidationRule(rule, argName, args[argName], args, options, result, context); } // Check dependencies if (argSchema.dependencies) { this.checkDependencies(argName, args[argName], argSchema.dependencies, args, result); } // Check conflicts if (argSchema.conflicts) { this.checkConflicts(argName, args[argName], argSchema.conflicts, args, result); } } } // Validate and transform options async validateAndTransformOptions(optionSchemas, args, options, result, context) { for (const [optionName, optionSchema] of Object.entries(optionSchemas)) { const value = options[optionName]; // Apply transformations if (optionSchema.transformations) { options[optionName] = await this.applyTransformationChain(optionSchema.transformations, value, args, options, context); } // Apply validation rules for (const rule of optionSchema.rules) { await this.applyValidationRule(rule, optionName, options[optionName], args, options, result, context); } // Check dependencies if (optionSchema.dependencies) { this.checkDependencies(optionName, options[optionName], optionSchema.dependencies, options, result); } // Check conflicts if (optionSchema.conflicts) { this.checkConflicts(optionName, options[optionName], optionSchema.conflicts, options, result); } // Check implications if (optionSchema.implies) { this.checkImplications(optionName, options[optionName], optionSchema.implies, options, result); } } } // Apply transformation chain async applyTransformationChain(transformations, value, args, options, context) { // Sort transformations by order const sortedTransformations = transformations.sort((a, b) => a.order - b.order); let transformedValue = value; for (const transformation of sortedTransformations) { // Check condition if specified if (transformation.condition && !transformation.condition(transformedValue, args, options, context)) { continue; } transformedValue = transformation.transformer(transformedValue, args, options, context); } return transformedValue; } // Apply transformations to all parameters async applyTransformations(transformations, args, options, context) { const sortedTransformations = transformations.sort((a, b) => a.order - b.order); for (const transformation of sortedTransformations) { // Apply to all arguments for (const [argName, value] of Object.entries(args)) { if (!transformation.condition || transformation.condition(value, args, options, context)) { args[argName] = transformation.transformer(value, args, options, context); } } // Apply to all options for (const [optionName, value] of Object.entries(options)) { if (!transformation.condition || transformation.condition(value, args, options, context)) { options[optionName] = transformation.transformer(value, args, options, context); } } } } // Apply validation rule async applyValidationRule(rule, fieldName, value, args, options, result, context) { // Check condition if specified if (rule.condition && !rule.condition(value, args, options, context)) { return; } let isValid = true; let message = rule.message || 'Validation failed'; if (rule.validator) { const validationResult = rule.validator(value, args, options, context); if (typeof validationResult === 'boolean') { isValid = validationResult; } else if (typeof validationResult === 'string') { isValid = false; message = validationResult; } } if (!isValid) { const issue = { field: fieldName, type: rule.type, severity: rule.severity, message, value, rule }; switch (rule.severity) { case ValidationSeverity.ERROR: result.errors.push(issue); break; case ValidationSeverity.WARNING: result.warnings.push(issue); break; case ValidationSeverity.INFO: result.info.push(issue); break; } } } // Apply global validation rules async applyGlobalRules(rules, args, options, result, context) { for (const rule of rules) { // Global rules apply to the entire parameter set await this.applyValidationRule(rule, '__global__', { args, options }, args, options, result, context); } } // Check parameter dependencies checkDependencies(fieldName, value, dependencies, params, result) { if (value !== undefined && value !== null) { for (const dependency of dependencies) { if (params[dependency] === undefined || params[dependency] === null) { result.errors.push({ field: fieldName, type: ValidationRuleType.DEPENDENCY, severity: ValidationSeverity.ERROR, message: `Field '${fieldName}' requires '${dependency}' to be specified`, value }); } } } } // Check parameter conflicts checkConflicts(fieldName, value, conflicts, params, result) { if (value !== undefined && value !== null) { for (const conflict of conflicts) { if (params[conflict] !== undefined && params[conflict] !== null) { result.errors.push({ field: fieldName, type: ValidationRuleType.EXCLUSION, severity: ValidationSeverity.ERROR, message: `Field '${fieldName}' conflicts with '${conflict}'`, value }); } } } } // Check parameter implications checkImplications(fieldName, value, implications, params, result) { if (value !== undefined && value !== null) { for (const implication of implications) { if (params[implication] === undefined || params[implication] === null) { result.errors.push({ field: fieldName, type: ValidationRuleType.DEPENDENCY, severity: ValidationSeverity.ERROR, message: `Field '${fieldName}' requires '${implication}' to be specified`, value }); } } } } // Generate cache key generateCacheKey(commandId, args, options) { const data = JSON.stringify({ commandId, args, options }); // Simple hash function for cache key let hash = 0; for (let i = 0; i < data.length; i++) { const char = data.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32-bit integer } return hash.toString(); } // Add result to cache addToCache(key, result) { if (this.validationCache.size >= this.globalValidationConfig.cacheSize) { // Remove oldest entry const firstKey = this.validationCache.keys().next().value; if (firstKey !== undefined) { this.validationCache.delete(firstKey); } } this.validationCache.set(key, result); } // Get built-in validation rules getBuiltInRules() { return this.builtInRules; } // Get built-in transformations getBuiltInTransformations() { return this.builtInTransformations; } // Get validation statistics getValidationStats() { return { totalSchemas: this.schemas.size, cacheSize: this.validationCache.size, cacheHitRate: 0, // Would track hits vs misses validationCount: 0, // Would track total validations errorCount: 0, // Would track total errors warningCount: 0, // Would track total warnings averageValidationTime: 0 // Would track performance }; } // Clear validation cache clearCache() { this.validationCache.clear(); this.emit('cache-cleared'); } // Update global configuration updateConfiguration(config) { this.globalValidationConfig = { ...this.globalValidationConfig, ...config }; this.emit('configuration-updated', this.globalValidationConfig); } } exports.PluginCommandValidator = PluginCommandValidator; // Utility functions function createCommandValidator() { return new PluginCommandValidator(); } function createValidationSchema(config = {}) { return { arguments: {}, options: {}, globalRules: [], transformations: [], strict: false, allowUnknown: true, failFast: false, ...config }; } function formatValidationResult(result) { let output = ''; if (result.errors.length > 0) { output += chalk_1.default.red('Validation Errors:\n'); result.errors.forEach(error => { output += chalk_1.default.red(` ✗ ${error.field}: ${error.message}\n`); }); } if (result.warnings.length > 0) { output += chalk_1.default.yellow('Validation Warnings:\n'); result.warnings.forEach(warning => { output += chalk_1.default.yellow(` ⚠ ${warning.field}: ${warning.message}\n`); }); } if (result.info.length > 0) { output += chalk_1.default.blue('Validation Info:\n'); result.info.forEach(info => { output += chalk_1.default.blue(` ℹ ${info.field}: ${info.message}\n`); }); } return output; }