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

480 lines (479 loc) 21.1 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; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.configBackupManager = exports.ConfigBackupManager = void 0; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const os = __importStar(require("os")); const chalk_1 = __importDefault(require("chalk")); const error_handler_1 = require("./error-handler"); const config_1 = require("./config"); // Configuration backup manager class ConfigBackupManager { constructor(backupDir) { this.backups = new Map(); this.backupDir = backupDir || path.join(os.homedir(), '.re-shell', 'backups'); this.metadataFile = path.join(this.backupDir, 'metadata.json'); } // Initialize backup system async initialize() { await fs.ensureDir(this.backupDir); await this.loadMetadata(); } // Create a full backup of all configurations async createFullBackup(name, description, tags = []) { await this.initialize(); const backupId = this.generateBackupId(); const timestamp = new Date().toISOString(); // Collect all configurations const global = await config_1.configManager.loadGlobalConfig(); const project = await config_1.configManager.loadProjectConfig().catch(() => null); // Find workspace configurations const workspaces = {}; try { // Search for workspace configs in common locations const searchPaths = [ path.join(process.cwd(), 'apps'), path.join(process.cwd(), 'packages'), path.join(process.cwd(), 'libs'), path.join(process.cwd(), 'tools') ]; for (const searchPath of searchPaths) { if (await fs.pathExists(searchPath)) { const dirs = await fs.readdir(searchPath); for (const dir of dirs) { const workspacePath = path.join(searchPath, dir); const workspace = await config_1.configManager.loadWorkspaceConfig(workspacePath).catch(() => null); if (workspace) { workspaces[path.relative(process.cwd(), workspacePath)] = workspace; } } } } } catch (error) { // Ignore workspace collection errors } // Collect templates const templates = []; try { const { templateEngine } = require('./template-engine'); templates.push(...await templateEngine.listTemplates()); } catch (error) { // Ignore template collection errors } // Create backup data const backupData = { metadata: { id: backupId, name, description: description || `Full backup created on ${new Date().toLocaleDateString()}`, createdAt: timestamp, size: 0, // Will be calculated after serialization type: 'full', version: global.version || '1.0.0', contents: { global: true, project: !!project, workspaces: Object.keys(workspaces), templates: templates.length > 0, environments: false // TODO: Implement environment backup }, checksum: '', tags }, configurations: { global, project: project || undefined, workspaces: Object.keys(workspaces).length > 0 ? workspaces : undefined, templates: templates.length > 0 ? templates : undefined } }; return this.saveBackup(backupData); } // Create selective backup async createSelectiveBackup(name, options, description, tags = []) { await this.initialize(); const backupId = this.generateBackupId(); const timestamp = new Date().toISOString(); const configurations = {}; // Collect specified configurations if (options.global) { configurations.global = await config_1.configManager.loadGlobalConfig(); } if (options.project) { const project = await config_1.configManager.loadProjectConfig().catch(() => null); if (project) configurations.project = project; } if (options.workspaces && options.workspaces.length > 0) { configurations.workspaces = {}; for (const workspacePath of options.workspaces) { const workspace = await config_1.configManager.loadWorkspaceConfig(workspacePath).catch(() => null); if (workspace) { configurations.workspaces[workspacePath] = workspace; } } } if (options.templates) { try { const { templateEngine } = require('./template-engine'); configurations.templates = await templateEngine.listTemplates(); } catch (error) { // Ignore template collection errors } } const backupData = { metadata: { id: backupId, name, description: description || `Selective backup created on ${new Date().toLocaleDateString()}`, createdAt: timestamp, size: 0, type: 'selective', version: configurations.global?.version || '1.0.0', contents: { global: options.global, project: options.project && !!configurations.project, workspaces: options.workspaces || [], templates: options.templates, environments: options.environments }, checksum: '', tags }, configurations }; return this.saveBackup(backupData); } // List all backups async listBackups() { await this.initialize(); return Array.from(this.backups.values()).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); } // Get backup by ID async getBackup(backupId) { await this.initialize(); const metadata = this.backups.get(backupId); if (!metadata) return null; const backupFile = path.join(this.backupDir, `${backupId}.backup.json`); if (!(await fs.pathExists(backupFile))) return null; const content = await fs.readFile(backupFile, 'utf8'); return JSON.parse(content); } // Delete backup async deleteBackup(backupId) { await this.initialize(); const metadata = this.backups.get(backupId); if (!metadata) { throw new error_handler_1.ValidationError(`Backup '${backupId}' not found`); } const backupFile = path.join(this.backupDir, `${backupId}.backup.json`); if (await fs.pathExists(backupFile)) { await fs.unlink(backupFile); } this.backups.delete(backupId); await this.saveMetadata(); } // Restore from backup async restoreFromBackup(backupId, options = {}) { await this.initialize(); const backup = await this.getBackup(backupId); if (!backup) { throw new error_handler_1.ValidationError(`Backup '${backupId}' not found`); } // Create backup before restore if requested if (options.createBackupBeforeRestore) { const preRestoreBackupId = await this.createFullBackup(`pre-restore-${backup.metadata.name}`, `Automatic backup before restoring '${backup.metadata.name}'`, ['auto', 'pre-restore']); console.log(chalk_1.default.cyan(`Created pre-restore backup: ${preRestoreBackupId}`)); } if (options.dryRun) { console.log(chalk_1.default.yellow('DRY RUN - No changes will be made')); this.showRestorePreview(backup, options); return; } // Perform restoration await this.performRestore(backup, options); } // Get backup statistics async getBackupStats() { await this.initialize(); const backups = Array.from(this.backups.values()); const totalSize = backups.reduce((sum, backup) => sum + backup.size, 0); const backupsByType = {}; for (const backup of backups) { backupsByType[backup.type] = (backupsByType[backup.type] || 0) + 1; } const sortedByDate = backups.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()); return { totalBackups: backups.length, totalSize, oldestBackup: sortedByDate[0], newestBackup: sortedByDate[sortedByDate.length - 1], backupsByType, averageSize: backups.length > 0 ? totalSize / backups.length : 0 }; } // Cleanup old backups async cleanup(options = {}) { await this.initialize(); const backups = await this.listBackups(); const toDelete = []; // Filter by count if (options.keepCount && backups.length > options.keepCount) { const excess = backups.slice(options.keepCount); toDelete.push(...excess.map(b => b.id)); } // Filter by age if (options.keepDays) { const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - options.keepDays); const oldBackups = backups.filter(backup => new Date(backup.createdAt) < cutoffDate && !toDelete.includes(backup.id)); toDelete.push(...oldBackups.map(b => b.id)); } // Filter by type (keep specified types) if (options.keepTypes) { const typeFilteredBackups = backups.filter(backup => !options.keepTypes.includes(backup.type) && !toDelete.includes(backup.id)); toDelete.push(...typeFilteredBackups.map(b => b.id)); } if (options.dryRun) { return toDelete; } // Perform deletion for (const backupId of toDelete) { await this.deleteBackup(backupId); } return toDelete; } // Export backup to file async exportBackup(backupId, outputPath) { const backup = await this.getBackup(backupId); if (!backup) { throw new error_handler_1.ValidationError(`Backup '${backupId}' not found`); } await fs.ensureDir(path.dirname(outputPath)); await fs.writeFile(outputPath, JSON.stringify(backup, null, 2)); } // Import backup from file async importBackup(filePath) { if (!(await fs.pathExists(filePath))) { throw new error_handler_1.ValidationError(`Backup file not found: ${filePath}`); } const content = await fs.readFile(filePath, 'utf8'); const backup = JSON.parse(content); // Validate backup structure if (!backup.metadata || !backup.configurations) { throw new error_handler_1.ValidationError('Invalid backup file format'); } // Generate new ID to avoid conflicts const newId = this.generateBackupId(); backup.metadata.id = newId; backup.metadata.tags = [...(backup.metadata.tags || []), 'imported']; return this.saveBackup(backup); } // Private methods async saveBackup(backupData) { const content = JSON.stringify(backupData, null, 2); const size = Buffer.byteLength(content, 'utf8'); const checksum = require('crypto').createHash('md5').update(content).digest('hex'); // Update metadata with calculated values backupData.metadata.size = size; backupData.metadata.checksum = checksum; // Save backup file const backupFile = path.join(this.backupDir, `${backupData.metadata.id}.backup.json`); await fs.writeFile(backupFile, content); // Update metadata index this.backups.set(backupData.metadata.id, backupData.metadata); await this.saveMetadata(); return backupData.metadata.id; } async loadMetadata() { if (await fs.pathExists(this.metadataFile)) { try { const content = await fs.readFile(this.metadataFile, 'utf8'); const metadata = JSON.parse(content); for (const [id, data] of Object.entries(metadata)) { this.backups.set(id, data); } } catch (error) { // If metadata is corrupted, rebuild from backup files await this.rebuildMetadata(); } } } async saveMetadata() { const metadata = Object.fromEntries(this.backups); await fs.writeFile(this.metadataFile, JSON.stringify(metadata, null, 2)); } async rebuildMetadata() { this.backups.clear(); if (!(await fs.pathExists(this.backupDir))) return; const files = await fs.readdir(this.backupDir); const backupFiles = files.filter(file => file.endsWith('.backup.json')); for (const file of backupFiles) { try { const filePath = path.join(this.backupDir, file); const content = await fs.readFile(filePath, 'utf8'); const backup = JSON.parse(content); if (backup.metadata) { this.backups.set(backup.metadata.id, backup.metadata); } } catch (error) { // Skip corrupted backup files console.warn(`Skipping corrupted backup file: ${file}`); } } await this.saveMetadata(); } generateBackupId() { const timestamp = Date.now(); const random = Math.random().toString(36).substring(2, 8); return `backup-${timestamp}-${random}`; } async performRestore(backup, options) { const { configurations } = backup; const selective = options.selective; // Restore global configuration if (configurations.global && (!selective || selective.global)) { if (options.mergeStrategy === 'skip-existing') { const existing = await config_1.configManager.loadGlobalConfig().catch(() => null); if (existing) { console.log(chalk_1.default.yellow('Skipping global config (already exists)')); } else { await config_1.configManager.saveGlobalConfig(configurations.global); console.log(chalk_1.default.green('✅ Restored global configuration')); } } else if (options.mergeStrategy === 'merge') { const existing = await config_1.configManager.loadGlobalConfig().catch(() => null); if (existing) { const merged = { ...configurations.global, ...existing }; await config_1.configManager.saveGlobalConfig(merged); console.log(chalk_1.default.green('✅ Merged global configuration')); } else { await config_1.configManager.saveGlobalConfig(configurations.global); console.log(chalk_1.default.green('✅ Restored global configuration')); } } else { await config_1.configManager.saveGlobalConfig(configurations.global); console.log(chalk_1.default.green('✅ Restored global configuration')); } } // Restore project configuration if (configurations.project && (!selective || selective.project)) { if (options.mergeStrategy === 'skip-existing') { const existing = await config_1.configManager.loadProjectConfig().catch(() => null); if (existing) { console.log(chalk_1.default.yellow('Skipping project config (already exists)')); } else { await config_1.configManager.saveProjectConfig(configurations.project); console.log(chalk_1.default.green('✅ Restored project configuration')); } } else { await config_1.configManager.saveProjectConfig(configurations.project); console.log(chalk_1.default.green('✅ Restored project configuration')); } } // Restore workspace configurations if (configurations.workspaces) { for (const [workspacePath, workspaceConfig] of Object.entries(configurations.workspaces)) { if (selective && selective.workspaces && !selective.workspaces.includes(workspacePath)) { continue; } const fullPath = path.resolve(workspacePath); await fs.ensureDir(fullPath); await config_1.configManager.saveWorkspaceConfig(workspaceConfig, fullPath); console.log(chalk_1.default.green(`✅ Restored workspace configuration: ${workspacePath}`)); } } // Restore templates if (configurations.templates && (!selective || selective.templates)) { try { const { templateEngine } = require('./template-engine'); for (const template of configurations.templates) { await templateEngine.saveTemplate(template); } console.log(chalk_1.default.green(`✅ Restored ${configurations.templates.length} templates`)); } catch (error) { console.warn(chalk_1.default.yellow('Warning: Failed to restore templates')); } } } showRestorePreview(backup, options) { console.log(chalk_1.default.cyan(`\\n📋 Restore Preview for: ${backup.metadata.name}`)); console.log(chalk_1.default.gray('═'.repeat(50))); const { configurations } = backup; const selective = options.selective; if (configurations.global && (!selective || selective.global)) { console.log(chalk_1.default.green(' ✓ Global configuration')); } if (configurations.project && (!selective || selective.project)) { console.log(chalk_1.default.green(' ✓ Project configuration')); } if (configurations.workspaces) { const workspacesToRestore = selective?.workspaces ? Object.keys(configurations.workspaces).filter(w => selective.workspaces.includes(w)) : Object.keys(configurations.workspaces); for (const workspace of workspacesToRestore) { console.log(chalk_1.default.green(` ✓ Workspace: ${workspace}`)); } } if (configurations.templates && (!selective || selective.templates)) { console.log(chalk_1.default.green(` ✓ Templates (${configurations.templates.length})`)); } console.log(chalk_1.default.gray(`\\nMerge strategy: ${options.mergeStrategy || 'replace'}`)); } } exports.ConfigBackupManager = ConfigBackupManager; // Export singleton instance exports.configBackupManager = new ConfigBackupManager();