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

589 lines (588 loc) 21.8 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.WorkspaceTemplateManager = void 0; exports.createWorkspaceTemplateManager = createWorkspaceTemplateManager; exports.exportWorkspaceAsTemplate = exportWorkspaceAsTemplate; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const yaml = __importStar(require("yaml")); const error_handler_1 = require("./error-handler"); // Workspace template manager class WorkspaceTemplateManager { constructor(rootPath = process.cwd()) { this.templateCache = new Map(); this.templatesPath = path.join(rootPath, '.re-shell', 'templates'); this.registry = this.createDefaultRegistry(); } // Initialize template system async init() { await fs.ensureDir(this.templatesPath); await this.loadRegistry(); await this.loadBuiltInTemplates(); } // Load template registry async loadRegistry() { const registryPath = path.join(this.templatesPath, 'registry.json'); try { if (await fs.pathExists(registryPath)) { this.registry = await fs.readJson(registryPath); } else { await this.saveRegistry(); } } catch (error) { this.registry = this.createDefaultRegistry(); } } // Save template registry async saveRegistry() { const registryPath = path.join(this.templatesPath, 'registry.json'); this.registry.metadata.modified = new Date().toISOString(); this.registry.metadata.count = Object.keys(this.registry.templates).length; await fs.writeJson(registryPath, this.registry, { spaces: 2 }); } // Create new template async createTemplate(template) { // Validate template this.validateTemplate(template); // Check for existing template if (this.registry.templates[template.name]) { throw new error_handler_1.ValidationError(`Template '${template.name}' already exists`); } // Save template file const templatePath = path.join(this.templatesPath, `${template.name}.yaml`); await fs.writeFile(templatePath, yaml.stringify(template)); // Update registry this.registry.templates[template.name] = template; await this.saveRegistry(); // Clear cache this.templateCache.delete(template.name); } // Get template by name async getTemplate(name) { // Check cache first if (this.templateCache.has(name)) { return this.templateCache.get(name); } // Check registry if (!this.registry.templates[name]) { return null; } // Load template file const templatePath = path.join(this.templatesPath, `${name}.yaml`); try { if (await fs.pathExists(templatePath)) { const content = await fs.readFile(templatePath, 'utf8'); const template = yaml.parse(content); // Cache for future use this.templateCache.set(name, template); return template; } } catch (error) { console.warn(`Failed to load template '${name}': ${error.message}`); } return null; } // List all templates async listTemplates() { const templates = []; for (const name of Object.keys(this.registry.templates)) { const template = await this.getTemplate(name); if (template) { templates.push(template); } } return templates; } // Delete template async deleteTemplate(name) { if (!this.registry.templates[name]) { throw new error_handler_1.ValidationError(`Template '${name}' not found`); } // Check if other templates depend on this one const dependents = await this.findDependentTemplates(name); if (dependents.length > 0) { throw new error_handler_1.ValidationError(`Cannot delete template '${name}': used by ${dependents.join(', ')}`); } // Delete template file const templatePath = path.join(this.templatesPath, `${name}.yaml`); await fs.remove(templatePath); // Update registry delete this.registry.templates[name]; await this.saveRegistry(); // Clear cache this.templateCache.delete(name); } // Apply template with variable substitution async applyTemplate(templateName, context) { const template = await this.getTemplate(templateName); if (!template) { throw new error_handler_1.ValidationError(`Template '${templateName}' not found`); } // Resolve inheritance chain const chain = await this.resolveInheritanceChain(templateName); // Validate variables against template requirements this.validateVariables(chain.variables, context.variables); // Apply template with inheritance const result = this.applyTemplateWithInheritance(chain.merged, context); return result; } // Resolve template inheritance chain async resolveInheritanceChain(templateName) { const templates = []; const variables = {}; const visited = new Set(); // Build inheritance chain let currentName = templateName; while (currentName) { // Check for circular inheritance if (visited.has(currentName)) { throw new error_handler_1.ValidationError(`Circular inheritance detected: ${currentName}`); } visited.add(currentName); const template = await this.getTemplate(currentName); if (!template) { throw new error_handler_1.ValidationError(`Template '${currentName}' not found in inheritance chain`); } templates.unshift(template); // Add to beginning (parent first) // Merge variables (child overrides parent) if (template.variables) { for (const variable of template.variables) { variables[variable.name] = { ...variables[variable.name], ...variable }; } } currentName = template.extends; } // Merge templates (child overrides parent) const merged = this.mergeTemplates(templates); return { templates, variables, merged }; } // Merge templates in inheritance chain mergeTemplates(templates) { let merged = { name: templates[templates.length - 1].name, version: templates[templates.length - 1].version }; for (const template of templates) { merged = { ...merged, ...template, workspaceDefaults: { ...merged.workspaceDefaults, ...template.workspaceDefaults }, typeDefaults: { ...merged.typeDefaults, ...template.typeDefaults }, patterns: [...(merged.patterns || []), ...(template.patterns || [])], dependencies: { ...merged.dependencies, ...template.dependencies }, scripts: { ...merged.scripts, ...template.scripts }, metadata: { ...merged.metadata, ...template.metadata } }; } // Remove duplicates from arrays if (merged.patterns) { merged.patterns = Array.from(new Set(merged.patterns)); } return merged; } // Apply template with context applyTemplateWithInheritance(template, context) { const result = {}; // Apply workspace defaults if creating new workspace if (context.workspace && template.workspaceDefaults) { Object.assign(context.workspace, this.substituteVariables(template.workspaceDefaults, context.variables)); } // Apply type defaults if (template.typeDefaults) { result.types = this.substituteVariables(template.typeDefaults, context.variables); } // Apply patterns if (template.patterns) { result.patterns = template.patterns.map(pattern => this.substituteString(pattern, context.variables)); } // Apply dependencies if (template.dependencies) { result.dependencies = this.substituteVariables(template.dependencies, context.variables); } // Apply scripts if (template.scripts) { result.scripts = Object.entries(template.scripts).reduce((acc, [key, value]) => { acc[key] = typeof value === 'string' ? this.substituteString(value, context.variables) : value; return acc; }, {}); } return result; } // Validate template validateTemplate(template) { if (!template.name) { throw new error_handler_1.ValidationError('Template name is required'); } if (!template.version) { throw new error_handler_1.ValidationError('Template version is required'); } // Validate variable definitions if (template.variables) { for (const variable of template.variables) { this.validateVariableDefinition(variable); } } // Validate inheritance if (template.extends && template.extends === template.name) { throw new error_handler_1.ValidationError('Template cannot extend itself'); } } // Validate variable definition validateVariableDefinition(variable) { if (!variable.name) { throw new error_handler_1.ValidationError('Variable name is required'); } if (!['string', 'number', 'boolean', 'array', 'object'].includes(variable.type)) { throw new error_handler_1.ValidationError(`Invalid variable type: ${variable.type}`); } if (variable.enum && variable.default) { if (!variable.enum.includes(variable.default)) { throw new error_handler_1.ValidationError(`Default value '${variable.default}' not in enum values`); } } if (variable.pattern && variable.type !== 'string') { throw new error_handler_1.ValidationError('Pattern validation only applies to string variables'); } } // Validate variables against requirements validateVariables(definitions, values) { for (const [name, definition] of Object.entries(definitions)) { const value = values[name] ?? definition.default; // Check required if (definition.required && value === undefined) { throw new error_handler_1.ValidationError(`Required variable '${name}' not provided`); } if (value !== undefined) { // Check type if (!this.isValidType(value, definition.type)) { throw new error_handler_1.ValidationError(`Variable '${name}' must be of type ${definition.type}`); } // Check enum if (definition.enum && !definition.enum.includes(value)) { throw new error_handler_1.ValidationError(`Variable '${name}' must be one of: ${definition.enum.join(', ')}`); } // Check pattern if (definition.pattern && definition.type === 'string') { const regex = new RegExp(definition.pattern); if (!regex.test(value)) { throw new error_handler_1.ValidationError(`Variable '${name}' does not match pattern: ${definition.pattern}`); } } } } } // Type validation isValidType(value, type) { switch (type) { case 'string': return typeof value === 'string'; case 'number': return typeof value === 'number'; case 'boolean': return typeof value === 'boolean'; case 'array': return Array.isArray(value); case 'object': return typeof value === 'object' && !Array.isArray(value); default: return false; } } // Variable substitution substituteVariables(obj, variables) { if (typeof obj === 'string') { return this.substituteString(obj, variables); } if (Array.isArray(obj)) { return obj.map(item => this.substituteVariables(item, variables)); } if (typeof obj === 'object' && obj !== null) { const result = {}; for (const [key, value] of Object.entries(obj)) { result[key] = this.substituteVariables(value, variables); } return result; } return obj; } // String variable substitution substituteString(str, variables) { return str.replace(/\{\{(\w+)\}\}/g, (match, varName) => { if (varName in variables) { return String(variables[varName]); } return match; }); } // Find templates that depend on a given template async findDependentTemplates(templateName) { const dependents = []; for (const template of await this.listTemplates()) { if (template.extends === templateName) { dependents.push(template.name); } } return dependents; } // Load built-in templates async loadBuiltInTemplates() { const builtInTemplates = [ this.createMicrofrontendTemplate(), this.createLibraryTemplate(), this.createServiceTemplate(), this.createMonorepoTemplate() ]; for (const template of builtInTemplates) { if (!this.registry.templates[template.name]) { try { await this.createTemplate(template); } catch (error) { // Ignore if template already exists } } } } // Built-in template definitions createMicrofrontendTemplate() { return { name: 'microfrontend', description: 'Standard microfrontend application template', version: '1.0.0', variables: [ { name: 'name', type: 'string', required: true, description: 'Microfrontend name', pattern: '^[a-z][a-z0-9-]*$' }, { name: 'framework', type: 'string', default: 'react', enum: ['react', 'vue', 'angular', 'svelte'], description: 'Frontend framework' }, { name: 'port', type: 'number', default: 5173, description: 'Development server port' } ], workspaceDefaults: { type: 'app' }, scripts: { dev: 'vite', build: 'vite build', preview: 'vite preview', test: 'vitest', lint: 'eslint src --ext ts,tsx' }, dependencies: { 'react': '^18.0.0', 'react-dom': '^18.0.0', 'vite': '^4.0.0' } }; } createLibraryTemplate() { return { name: 'library', description: 'Shared library template', version: '1.0.0', variables: [ { name: 'name', type: 'string', required: true, description: 'Library name' }, { name: 'type', type: 'string', default: 'utils', enum: ['utils', 'components', 'hooks', 'services'], description: 'Library type' } ], workspaceDefaults: { type: 'lib' }, scripts: { build: 'tsc', test: 'vitest', lint: 'eslint src --ext ts,tsx' } }; } createServiceTemplate() { return { name: 'service', description: 'Backend service template', version: '1.0.0', extends: 'base', variables: [ { name: 'name', type: 'string', required: true, description: 'Service name' }, { name: 'runtime', type: 'string', default: 'node', enum: ['node', 'deno', 'bun'], description: 'JavaScript runtime' } ], workspaceDefaults: { type: 'service' }, scripts: { dev: 'nodemon src/index.ts', build: 'tsc', start: 'node dist/index.js' } }; } createMonorepoTemplate() { return { name: 'monorepo', description: 'Full monorepo setup template', version: '1.0.0', variables: [ { name: 'name', type: 'string', required: true, description: 'Project name' }, { name: 'packageManager', type: 'string', default: 'pnpm', enum: ['npm', 'yarn', 'pnpm'], description: 'Package manager' } ], patterns: [ 'apps/*', 'packages/*', 'services/*' ], scripts: { dev: '{{packageManager}} run dev', build: '{{packageManager}} run build', test: '{{packageManager}} run test', lint: '{{packageManager}} run lint' }, typeDefaults: { app: { framework: 'react', build: { command: 'vite build' } }, lib: { framework: 'typescript', build: { command: 'tsc' } }, service: { framework: 'node', build: { command: 'esbuild src/index.ts --bundle --platform=node --outfile=dist/index.js' } } } }; } // Utility methods createDefaultRegistry() { return { version: '1.0.0', templates: {}, metadata: { created: new Date().toISOString(), modified: new Date().toISOString(), count: 0 } }; } } exports.WorkspaceTemplateManager = WorkspaceTemplateManager; // Utility functions async function createWorkspaceTemplateManager(rootPath) { const manager = new WorkspaceTemplateManager(rootPath); await manager.init(); return manager; } // Export template from workspace definition async function exportWorkspaceAsTemplate(definition, templateName, variables) { const template = { name: templateName, description: `Template exported from ${definition.name}`, version: '1.0.0', variables: variables || [], patterns: definition.patterns, typeDefaults: definition.types, scripts: definition.scripts || {}, metadata: { exportedFrom: definition.name, exportedAt: new Date().toISOString() } }; return template; }