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

583 lines (582 loc) 22.4 kB
"use strict"; 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.configValidator = exports.ConfigurationValidator = exports.ENVIRONMENT_CONFIG_SCHEMA = exports.PROJECT_CONFIG_SCHEMA = exports.GLOBAL_CONFIG_SCHEMA = void 0; exports.validateGlobalConfig = validateGlobalConfig; exports.validateProjectConfig = validateProjectConfig; exports.validateEnvironmentConfig = validateEnvironmentConfig; exports.validateConfigFile = validateConfigFile; const fs = __importStar(require("fs-extra")); const yaml = __importStar(require("yaml")); const error_handler_1 = require("./error-handler"); // Configuration schema definitions exports.GLOBAL_CONFIG_SCHEMA = [ { field: 'version', type: 'required', message: 'Configuration version is required for migration tracking' }, { field: 'version', type: 'pattern', message: 'Version must follow semantic versioning (e.g., 1.0.0)', details: { pattern: /^\d+\.\d+\.\d+$/ } }, { field: 'packageManager', type: 'required', message: 'Package manager specification is required' }, { field: 'packageManager', type: 'enum', message: 'Package manager must be one of: npm, yarn, pnpm, bun', details: { allowedValues: ['npm', 'yarn', 'pnpm', 'bun'] } }, { field: 'defaultFramework', type: 'required', message: 'Default framework must be specified' }, { field: 'defaultFramework', type: 'enum', message: 'Framework must be one of: react, react-ts, vue, vue-ts, svelte, svelte-ts, angular, angular-ts', details: { allowedValues: ['react', 'react-ts', 'vue', 'vue-ts', 'svelte', 'svelte-ts', 'angular', 'angular-ts'] } }, { field: 'cli.autoUpdate', type: 'type', message: 'Auto-update setting must be a boolean value', details: { expectedType: 'boolean' } }, { field: 'cli.telemetry', type: 'type', message: 'Telemetry setting must be a boolean value', details: { expectedType: 'boolean' } }, { field: 'cli.theme', type: 'enum', message: 'Theme must be one of: auto, light, dark', details: { allowedValues: ['auto', 'light', 'dark'] } }, { field: 'paths.templates', type: 'required', message: 'Templates directory path is required' }, { field: 'paths.cache', type: 'required', message: 'Cache directory path is required' }, { field: 'plugins.enabled', type: 'type', message: 'Enabled plugins must be an array of plugin names', details: { expectedType: 'array' } }, { field: 'plugins.marketplace.registry', type: 'pattern', message: 'Plugin registry must be a valid URL', details: { pattern: /^https?:\/\/.+/ } } ]; exports.PROJECT_CONFIG_SCHEMA = [ { field: 'name', type: 'required', message: 'Project name is required' }, { field: 'name', type: 'pattern', message: 'Project name must be lowercase, alphanumeric with hyphens only', details: { pattern: /^[a-z0-9-]+$/ } }, { field: 'version', type: 'required', message: 'Project version is required' }, { field: 'version', type: 'pattern', message: 'Version must follow semantic versioning (e.g., 1.0.0)', details: { pattern: /^\d+\.\d+\.\d+/ } }, { field: 'type', type: 'enum', message: 'Project type must be either "monorepo" or "standalone"', details: { allowedValues: ['monorepo', 'standalone'] } }, { field: 'workspaces.patterns', type: 'type', message: 'Workspace patterns must be an array of glob patterns', details: { expectedType: 'array' } }, { field: 'environments', type: 'custom', message: 'At least one environment configuration is required', validator: (value) => value && typeof value === 'object' && Object.keys(value).length > 0 }, { field: 'dev.port', type: 'range', message: 'Development port must be between 1024 and 65535', details: { min: 1024, max: 65535 } }, { field: 'quality.coverage.threshold', type: 'range', message: 'Coverage threshold must be between 0 and 100', details: { min: 0, max: 100 } } ]; exports.ENVIRONMENT_CONFIG_SCHEMA = [ { field: 'name', type: 'required', message: 'Environment name is required' }, { field: 'build.mode', type: 'enum', message: 'Build mode must be one of: development, staging, production', details: { allowedValues: ['development', 'staging', 'production'] } }, { field: 'deployment.provider', type: 'enum', message: 'Deployment provider must be one of: vercel, netlify, aws, azure, gcp, docker, custom', details: { allowedValues: ['vercel', 'netlify', 'aws', 'azure', 'gcp', 'docker', 'custom'] } } ]; // Enhanced validation engine class ConfigurationValidator { constructor() { this.errors = []; this.warnings = []; this.suggestions = []; } // Validate configuration against schema validateConfiguration(config, schema, context = '') { this.reset(); for (const rule of schema) { const value = this.getNestedValue(config, rule.field); const fieldContext = context ? `${context}.${rule.field}` : rule.field; this.validateField(value, rule, fieldContext, config); } // Add additional contextual validations this.performContextualValidations(config, context); return { valid: this.errors.filter(e => e.severity === 'error').length === 0, errors: this.errors, warnings: this.warnings, suggestions: this.suggestions }; } // Validate individual field validateField(value, rule, fieldPath, fullConfig) { switch (rule.type) { case 'required': this.validateRequired(value, rule, fieldPath); break; case 'type': this.validateType(value, rule, fieldPath); break; case 'enum': this.validateEnum(value, rule, fieldPath); break; case 'pattern': this.validatePattern(value, rule, fieldPath); break; case 'range': this.validateRange(value, rule, fieldPath); break; case 'custom': this.validateCustom(value, rule, fieldPath, fullConfig); break; } } validateRequired(value, rule, fieldPath) { if (value === undefined || value === null || value === '') { this.addError({ field: fieldPath, message: rule.message, severity: 'error', code: 'REQUIRED_FIELD_MISSING', value, suggestions: this.generateRequiredFieldSuggestions(fieldPath) }); } } validateType(value, rule, fieldPath) { if (value === undefined || value === null) return; const expectedType = rule.details?.expectedType; let actualType = typeof value; if (expectedType === 'array' && !Array.isArray(value)) { actualType = 'non-array'; } if ((expectedType === 'array' && !Array.isArray(value)) || (expectedType !== 'array' && actualType !== expectedType)) { this.addError({ field: fieldPath, message: rule.message, severity: 'error', code: 'INVALID_TYPE', value, expectedValue: expectedType, suggestions: this.generateTypeSuggestions(value, expectedType) }); } } validateEnum(value, rule, fieldPath) { if (value === undefined || value === null) return; const allowedValues = rule.details?.allowedValues || []; if (!allowedValues.includes(value)) { this.addError({ field: fieldPath, message: rule.message, severity: 'error', code: 'INVALID_ENUM_VALUE', value, expectedValue: allowedValues, suggestions: this.generateEnumSuggestions(value, allowedValues) }); } } validatePattern(value, rule, fieldPath) { if (value === undefined || value === null) return; const pattern = rule.details?.pattern; if (pattern && typeof value === 'string' && !pattern.test(value)) { this.addError({ field: fieldPath, message: rule.message, severity: 'error', code: 'PATTERN_MISMATCH', value, suggestions: this.generatePatternSuggestions(value, pattern, fieldPath) }); } } validateRange(value, rule, fieldPath) { if (value === undefined || value === null) return; const { min, max } = rule.details || {}; if (typeof value === 'number') { if (min !== undefined && value < min) { this.addError({ field: fieldPath, message: `Value ${value} is below minimum ${min}`, severity: 'error', code: 'VALUE_BELOW_MINIMUM', value, expectedValue: `>= ${min}`, suggestions: [`Use a value >= ${min}`] }); } if (max !== undefined && value > max) { this.addError({ field: fieldPath, message: `Value ${value} exceeds maximum ${max}`, severity: 'error', code: 'VALUE_ABOVE_MAXIMUM', value, expectedValue: `<= ${max}`, suggestions: [`Use a value <= ${max}`] }); } } } validateCustom(value, rule, fieldPath, fullConfig) { if (rule.validator && !rule.validator(value, fullConfig)) { this.addError({ field: fieldPath, message: rule.message, severity: 'error', code: 'CUSTOM_VALIDATION_FAILED', value, suggestions: this.generateCustomSuggestions(fieldPath, value) }); } } // Contextual validations performContextualValidations(config, context) { if (context === 'global') { this.validateGlobalContextual(config); } else if (context === 'project') { this.validateProjectContextual(config); } } validateGlobalContextual(config) { // Check if directories exist if (config.paths) { for (const [key, dirPath] of Object.entries(config.paths)) { if (typeof dirPath === 'string') { const expandedPath = dirPath.replace(/^~/, process.env.HOME || '~'); if (!fs.existsSync(expandedPath)) { this.addWarning({ field: `paths.${key}`, message: `Directory does not exist: ${dirPath}`, suggestion: `Create directory or update path`, impact: 'medium' }); this.addSuggestion({ field: `paths.${key}`, suggestion: `Create missing directory: ${dirPath}`, reason: 'Required directory for Re-Shell operation', autoFixable: true, autoFixValue: dirPath }); } } } } // Check plugin registry accessibility if (config.plugins?.marketplace?.registry) { this.addSuggestion({ field: 'plugins.marketplace.registry', suggestion: 'Verify registry URL is accessible', reason: 'Ensure plugin marketplace functionality', autoFixable: false }); } } validateProjectContextual(config) { // Validate workspace patterns if (config.workspaces?.patterns) { for (const pattern of config.workspaces.patterns) { if (typeof pattern === 'string' && pattern.includes('..')) { this.addWarning({ field: 'workspaces.patterns', message: `Pattern "${pattern}" may expose parent directories`, suggestion: 'Use patterns within project boundaries', impact: 'high' }); } } } // Validate environment consistency if (config.environments) { const envNames = Object.keys(config.environments); const requiredEnvs = ['development', 'staging', 'production']; for (const required of requiredEnvs) { if (!envNames.includes(required)) { this.addWarning({ field: 'environments', message: `Missing recommended environment: ${required}`, suggestion: `Add ${required} environment configuration`, impact: 'medium' }); } } } // Check port conflicts if (config.dev?.port) { const commonPorts = [3000, 8080, 8000, 5000, 3001]; if (commonPorts.includes(config.dev.port)) { this.addWarning({ field: 'dev.port', message: `Port ${config.dev.port} is commonly used and may cause conflicts`, suggestion: 'Consider using a different port', impact: 'low' }); } } } // Suggestion generation methods generateRequiredFieldSuggestions(fieldPath) { const suggestions = { 'version': ['Use "1.0.0" for new configurations'], 'name': ['Use lowercase project name with hyphens'], 'packageManager': ['Use "pnpm" (recommended)', 'Use "npm", "yarn", or "bun"'], 'defaultFramework': ['Use "react-ts" for TypeScript React', 'Use "vue-ts" for TypeScript Vue'] }; return suggestions[fieldPath] || [`Provide a value for ${fieldPath}`]; } generateTypeSuggestions(value, expectedType) { if (expectedType === 'array' && !Array.isArray(value)) { return [`Convert to array: [${JSON.stringify(value)}]`, 'Use empty array: []']; } if (expectedType === 'boolean') { return ['Use true or false', 'Remove quotes if using string']; } if (expectedType === 'number') { return ['Remove quotes from numeric value', 'Use valid number format']; } return [`Convert to ${expectedType}`]; } generateEnumSuggestions(value, allowedValues) { // Find close matches using simple string similarity if (typeof value === 'string') { const closeMatches = allowedValues .filter(allowed => typeof allowed === 'string') .map(allowed => ({ value: allowed, similarity: this.calculateSimilarity(value, allowed) })) .filter(match => match.similarity > 0.5) .sort((a, b) => b.similarity - a.similarity) .slice(0, 3) .map(match => match.value); if (closeMatches.length > 0) { return [`Did you mean: ${closeMatches.join(', ')}?`, ...allowedValues.map(v => `Use: ${v}`)]; } } return allowedValues.map(v => `Use: ${v}`); } generatePatternSuggestions(value, pattern, fieldPath) { const suggestions = { 'version': ['Use format: 1.0.0', 'Follow semantic versioning'], 'name': ['Use lowercase letters, numbers, and hyphens only', 'Example: my-project-name'], 'plugins.marketplace.registry': ['Use HTTPS URL', 'Example: https://registry.npmjs.org'] }; return suggestions[fieldPath] || [`Match pattern: ${pattern.source}`]; } generateCustomSuggestions(fieldPath, value) { if (fieldPath === 'environments') { return ['Add at least one environment (development, staging, or production)']; } return ['Fix validation error']; } // Utility methods getNestedValue(obj, path) { return path.split('.').reduce((current, key) => current?.[key], obj); } calculateSimilarity(str1, str2) { const longer = str1.length > str2.length ? str1 : str2; const shorter = str1.length > str2.length ? str2 : str1; const editDistance = this.levenshteinDistance(longer, shorter); return (longer.length - editDistance) / longer.length; } levenshteinDistance(str1, str2) { const matrix = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null)); for (let i = 0; i <= str1.length; i++) matrix[0][i] = i; for (let j = 0; j <= str2.length; j++) matrix[j][0] = j; for (let j = 1; j <= str2.length; j++) { for (let i = 1; i <= str1.length; i++) { const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1; matrix[j][i] = Math.min(matrix[j][i - 1] + 1, matrix[j - 1][i] + 1, matrix[j - 1][i - 1] + indicator); } } return matrix[str2.length][str1.length]; } addError(error) { this.errors.push(error); } addWarning(warning) { this.warnings.push(warning); } addSuggestion(suggestion) { this.suggestions.push(suggestion); } reset() { this.errors = []; this.warnings = []; this.suggestions = []; } } exports.ConfigurationValidator = ConfigurationValidator; // Export singleton instance and helper functions exports.configValidator = new ConfigurationValidator(); function validateGlobalConfig(config) { return exports.configValidator.validateConfiguration(config, exports.GLOBAL_CONFIG_SCHEMA, 'global'); } function validateProjectConfig(config) { return exports.configValidator.validateConfiguration(config, exports.PROJECT_CONFIG_SCHEMA, 'project'); } function validateEnvironmentConfig(config) { return exports.configValidator.validateConfiguration(config, exports.ENVIRONMENT_CONFIG_SCHEMA, 'environment'); } function validateConfigFile(filePath, configType) { return new Promise(async (resolve, reject) => { try { if (!await fs.pathExists(filePath)) { resolve({ valid: false, errors: [{ field: 'file', message: `Configuration file not found: ${filePath}`, severity: 'error', code: 'FILE_NOT_FOUND', suggestions: [`Create configuration file at ${filePath}`] }], warnings: [], suggestions: [] }); return; } const content = await fs.readFile(filePath, 'utf8'); let config; try { config = yaml.parse(content); } catch (parseError) { resolve({ valid: false, errors: [{ field: 'syntax', message: `Invalid YAML syntax: ${parseError.message}`, severity: 'error', code: 'YAML_PARSE_ERROR', suggestions: ['Check YAML syntax', 'Validate indentation', 'Check for special characters'] }], warnings: [], suggestions: [] }); return; } const schema = configType === 'global' ? exports.GLOBAL_CONFIG_SCHEMA : exports.PROJECT_CONFIG_SCHEMA; const result = exports.configValidator.validateConfiguration(config, schema, configType); resolve(result); } catch (error) { reject(new error_handler_1.ValidationError(`Failed to validate configuration: ${error.message}`)); } }); }