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