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