@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
JavaScript
;
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}`));
}
});
}