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

481 lines (480 loc) 19.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.templateEngine = exports.TemplateHelpers = exports.ConfigTemplateEngine = void 0; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const yaml = __importStar(require("yaml")); const error_handler_1 = require("./error-handler"); // Template engine for configuration substitution class ConfigTemplateEngine { constructor(templatesDir) { this.templates = new Map(); this.templatesDir = templatesDir || path.join(process.cwd(), '.re-shell', 'templates'); } // Load template from file async loadTemplate(templatePath) { try { const content = await fs.readFile(templatePath, 'utf8'); const template = yaml.parse(content); this.validateTemplate(template); return template; } catch (error) { throw new error_handler_1.ValidationError(`Failed to load template: ${error.message}`); } } // Save template to file async saveTemplate(template, templatePath) { try { this.validateTemplate(template); const fileName = templatePath || `${template.name}.template.yaml`; const fullPath = path.isAbsolute(fileName) ? fileName : path.join(this.templatesDir, fileName); await fs.ensureDir(path.dirname(fullPath)); template.updatedAt = new Date().toISOString(); const content = yaml.stringify(template); await fs.writeFile(fullPath, content, 'utf8'); this.templates.set(template.name, template); return fullPath; } catch (error) { throw new error_handler_1.ValidationError(`Failed to save template: ${error.message}`); } } // List available templates async listTemplates() { try { await fs.ensureDir(this.templatesDir); const files = await fs.readdir(this.templatesDir); const templateFiles = files.filter(file => file.endsWith('.template.yaml')); const templates = []; for (const file of templateFiles) { try { const template = await this.loadTemplate(path.join(this.templatesDir, file)); templates.push(template); } catch (error) { console.warn(`Failed to load template ${file}: ${error.message}`); } } return templates.sort((a, b) => a.name.localeCompare(b.name)); } catch (error) { throw new error_handler_1.ValidationError(`Failed to list templates: ${error.message}`); } } // Get template by name async getTemplate(name) { if (this.templates.has(name)) { return this.templates.get(name); } try { const templatePath = path.join(this.templatesDir, `${name}.template.yaml`); if (await fs.pathExists(templatePath)) { const template = await this.loadTemplate(templatePath); this.templates.set(name, template); return template; } } catch (error) { // Template not found or invalid } return null; } // Render template with variables async renderTemplate(templateName, variables, context) { const template = await this.getTemplate(templateName); if (!template) { throw new error_handler_1.ValidationError(`Template '${templateName}' not found`); } // Validate required variables this.validateVariables(template, variables); // Build full context const fullContext = this.buildContext(variables, context); // Perform substitution return this.substituteVariables(template.template, fullContext); } // Create a new template from existing configuration async createTemplate(name, config, variables, options = {}) { const template = { name, version: options.version || '1.0.0', description: options.description || `Configuration template for ${name}`, author: options.author, tags: options.tags || [], variables, template: config, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; await this.saveTemplate(template); return template; } // Delete template async deleteTemplate(name) { try { const templatePath = path.join(this.templatesDir, `${name}.template.yaml`); if (await fs.pathExists(templatePath)) { await fs.unlink(templatePath); this.templates.delete(name); } else { throw new error_handler_1.ValidationError(`Template '${name}' not found`); } } catch (error) { throw new error_handler_1.ValidationError(`Failed to delete template: ${error.message}`); } } // Variable substitution engine substituteVariables(obj, context) { if (typeof obj === 'string') { return this.substituteString(obj, context); } else if (Array.isArray(obj)) { return obj.map(item => this.substituteVariables(item, context)); } else if (obj && typeof obj === 'object') { const result = {}; for (const [key, value] of Object.entries(obj)) { // Allow template key substitution const newKey = this.substituteString(key, context); result[newKey] = this.substituteVariables(value, context); } return result; } return obj; } // String substitution with multiple syntaxes substituteString(str, context) { let result = str; // Handle different template syntaxes // 1. Simple variable substitution: ${varName} result = result.replace(/\$\{([^}]+)\}/g, (match, varPath) => { const value = this.getVariableValue(varPath.trim(), context); return value !== undefined ? String(value) : match; }); // 2. Mustache-style: {{varName}} result = result.replace(/\{\{([^}]+)\}\}/g, (match, varPath) => { const value = this.getVariableValue(varPath.trim(), context); return value !== undefined ? String(value) : match; }); // 3. Expression syntax: ${{expression}} result = result.replace(/\$\{\{([^}]+)\}\}/g, (match, expression) => { try { const value = this.evaluateExpression(expression.trim(), context); return value !== undefined ? String(value) : match; } catch (error) { return match; // Keep original if evaluation fails } }); // 4. Conditional substitution: ${varName:defaultValue} result = result.replace(/\$\{([^:}]+):([^}]*)\}/g, (match, varPath, defaultValue) => { const value = this.getVariableValue(varPath.trim(), context); return value !== undefined ? String(value) : defaultValue; }); // Try to parse as JSON if it looks like an object/array if ((result.startsWith('{') && result.endsWith('}')) || (result.startsWith('[') && result.endsWith(']'))) { try { return JSON.parse(result); } catch { // If parsing fails, return as string } } // Try to parse as number or boolean if (result === 'true') return true; if (result === 'false') return false; if (result === 'null') return null; if (result === 'undefined') return undefined; const numberValue = Number(result); if (!isNaN(numberValue) && result === numberValue.toString()) { return numberValue; } return result; } // Get variable value from context with dot notation support getVariableValue(path, context) { const keys = path.split('.'); let current = context; for (const key of keys) { if (current && typeof current === 'object' && key in current) { current = current[key]; } else { return undefined; } } return current; } // Simple expression evaluator (basic arithmetic and logic) evaluateExpression(expression, context) { // Replace variables in expression const substituted = expression.replace(/([a-zA-Z_][a-zA-Z0-9_.]*)/g, (match) => { const value = this.getVariableValue(match, context); if (value === undefined) return match; return typeof value === 'string' ? `"${value}"` : String(value); }); // Basic arithmetic and comparison operations try { // Use Function constructor for safer evaluation (still limited) // Only allow basic operations if (!/^[0-9+\-*/.()!&|=<> "'"`\s]+$/.test(substituted)) { throw new Error('Invalid expression'); } return Function(`"use strict"; return (${substituted});`)(); } catch (error) { throw new error_handler_1.ValidationError(`Invalid expression: ${expression}`); } } // Build complete context for substitution buildContext(variables, partial) { const now = new Date(); return { variables, environment: process.env, projectInfo: partial?.projectInfo || {}, userInfo: partial?.userInfo || {}, timestamp: { iso: now.toISOString(), unix: Math.floor(now.getTime() / 1000), formatted: now.toLocaleDateString() }, ...partial }; } // Validate template structure validateTemplate(template) { if (!template.name || typeof template.name !== 'string') { throw new error_handler_1.ValidationError('Template must have a valid name'); } if (!template.version || typeof template.version !== 'string') { throw new error_handler_1.ValidationError('Template must have a valid version'); } if (!template.description || typeof template.description !== 'string') { throw new error_handler_1.ValidationError('Template must have a description'); } if (!Array.isArray(template.variables)) { throw new error_handler_1.ValidationError('Template must have a variables array'); } if (!template.template) { throw new error_handler_1.ValidationError('Template must have a template object'); } // Validate variables for (const variable of template.variables) { this.validateVariable(variable); } } // Validate individual variable definition validateVariable(variable) { if (!variable.name || typeof variable.name !== 'string') { throw new error_handler_1.ValidationError('Variable must have a valid name'); } if (!variable.type || !['string', 'number', 'boolean', 'array', 'object'].includes(variable.type)) { throw new error_handler_1.ValidationError('Variable must have a valid type'); } if (!variable.description || typeof variable.description !== 'string') { throw new error_handler_1.ValidationError('Variable must have a description'); } } // Validate variables against template requirements validateVariables(template, variables) { for (const varDef of template.variables) { const value = variables[varDef.name]; // Check required variables if (varDef.required && (value === undefined || value === null)) { throw new error_handler_1.ValidationError(`Required variable '${varDef.name}' is missing`); } // Skip validation for optional undefined variables if (value === undefined) continue; // Type validation this.validateVariableType(varDef, value); // Custom validation rules if (varDef.validation) { this.validateVariableRules(varDef, value); } } } // Validate variable type validateVariableType(varDef, value) { const actualType = Array.isArray(value) ? 'array' : typeof value; if (actualType !== varDef.type) { throw new error_handler_1.ValidationError(`Variable '${varDef.name}' must be of type ${varDef.type}, got ${actualType}`); } } // Validate variable against custom rules validateVariableRules(varDef, value) { const rules = varDef.validation; if (rules.pattern && typeof value === 'string') { const regex = new RegExp(rules.pattern); if (!regex.test(value)) { throw new error_handler_1.ValidationError(`Variable '${varDef.name}' must match pattern: ${rules.pattern}`); } } if (rules.min !== undefined) { if (typeof value === 'number' && value < rules.min) { throw new error_handler_1.ValidationError(`Variable '${varDef.name}' must be at least ${rules.min}`); } if (typeof value === 'string' && value.length < rules.min) { throw new error_handler_1.ValidationError(`Variable '${varDef.name}' must be at least ${rules.min} characters`); } } if (rules.max !== undefined) { if (typeof value === 'number' && value > rules.max) { throw new error_handler_1.ValidationError(`Variable '${varDef.name}' must be at most ${rules.max}`); } if (typeof value === 'string' && value.length > rules.max) { throw new error_handler_1.ValidationError(`Variable '${varDef.name}' must be at most ${rules.max} characters`); } } if (rules.options && !rules.options.includes(value)) { throw new error_handler_1.ValidationError(`Variable '${varDef.name}' must be one of: ${rules.options.join(', ')}`); } } } exports.ConfigTemplateEngine = ConfigTemplateEngine; // Built-in template helpers exports.TemplateHelpers = { // Generate common configuration templates createProjectTemplate(projectName, framework, packageManager) { return { name: `${framework}-project`, version: '1.0.0', description: `${framework} project configuration template`, tags: [framework, 'project', packageManager], variables: [ { name: 'projectName', type: 'string', description: 'Name of the project', required: true, validation: { pattern: '^[a-z0-9-]+$' } }, { name: 'port', type: 'number', description: 'Development server port', default: 3000, validation: { min: 1000, max: 65535 } }, { name: 'enableTesting', type: 'boolean', description: 'Enable testing setup', default: true } ], template: { name: '${projectName}', type: 'monorepo', packageManager, framework, dev: { port: '${port}', host: 'localhost', open: false, hmr: true }, quality: { linting: true, testing: '${enableTesting}', coverage: { enabled: '${enableTesting}', threshold: 80 } } }, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; }, // Create workspace template createWorkspaceTemplate(type) { return { name: `${type}-workspace`, version: '1.0.0', description: `${type} workspace configuration template`, tags: [type, 'workspace'], variables: [ { name: 'workspaceName', type: 'string', description: 'Name of the workspace', required: true, validation: { pattern: '^[a-z0-9-]+$' } }, { name: 'framework', type: 'string', description: 'Framework for the workspace', default: 'react-ts', validation: { options: ['react', 'react-ts', 'vue', 'vue-ts', 'svelte', 'svelte-ts'] } } ], template: { name: '${workspaceName}', type, framework: '${framework}', build: { target: 'es2020', optimize: true, analyze: false } }, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; } }; // Export singleton instance exports.templateEngine = new ConfigTemplateEngine();