@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
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.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();