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

490 lines (489 loc) 19.2 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.WorkspaceBackupManager = void 0; exports.createWorkspaceBackupManager = createWorkspaceBackupManager; exports.createQuickBackup = createQuickBackup; exports.compareBackups = compareBackups; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const crypto = __importStar(require("crypto")); const glob_1 = require("glob"); const error_handler_1 = require("./error-handler"); const workspace_schema_1 = require("./workspace-schema"); const workspace_state_1 = require("./workspace-state"); // Workspace backup manager class WorkspaceBackupManager { constructor(rootPath = process.cwd()) { this.rootPath = rootPath; this.backupDir = path.join(rootPath, '.re-shell', 'backups'); this.indexPath = path.join(this.backupDir, 'index.json'); this.index = this.createDefaultIndex(); } // Initialize backup system async init() { await fs.ensureDir(this.backupDir); await this.loadIndex(); } // Create backup async createBackup(workspaceFile, options = {}) { const backupId = this.generateBackupId(); const timestamp = new Date().toISOString(); // Load workspace definition const workspace = await (0, workspace_schema_1.loadWorkspaceDefinition)(workspaceFile); // Build backup content const content = { metadata: { id: backupId, name: options.name || `backup-${timestamp.split('T')[0]}`, description: options.description, timestamp, workspaceFile: path.basename(workspaceFile), version: '1.0.0', size: 0, hash: '', tags: options.tags, includeState: options.includeState, includeCache: options.includeCache, includeTemplates: options.includeTemplates }, workspace }; // Include state if requested if (options.includeState) { try { const stateManager = await (0, workspace_state_1.createWorkspaceStateManager)(this.rootPath); await stateManager.loadState(); content.state = stateManager.getStateStatistics(); } catch (error) { console.warn('Failed to include state in backup:', error.message); } } // Include cache if requested if (options.includeCache) { try { const cacheDir = path.join(this.rootPath, '.re-shell', 'cache'); if (await fs.pathExists(cacheDir)) { content.cache = await this.backupDirectory(cacheDir); } } catch (error) { console.warn('Failed to include cache in backup:', error.message); } } // Include templates if requested if (options.includeTemplates) { try { const templatesDir = path.join(this.rootPath, '.re-shell', 'templates'); if (await fs.pathExists(templatesDir)) { content.templates = await this.backupDirectory(templatesDir); } } catch (error) { console.warn('Failed to include templates in backup:', error.message); } } // Include files if requested if (options.includeFiles) { try { const patterns = options.filePatterns || ['*.json', '*.yaml', '*.yml', '*.ts', '*.js']; content.files = await this.backupFiles(patterns); } catch (error) { console.warn('Failed to include files in backup:', error.message); } } // Calculate size and hash const contentStr = JSON.stringify(content); content.metadata.size = Buffer.byteLength(contentStr, 'utf8'); content.metadata.hash = crypto.createHash('sha256').update(contentStr).digest('hex'); // Save backup const backupPath = path.join(this.backupDir, `${backupId}.json`); await fs.writeJson(backupPath, content, { spaces: 2 }); // Update index this.index.backups[backupId] = content.metadata; await this.saveIndex(); return backupId; } // List backups async listBackups() { return Object.values(this.index.backups).sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); } // Get backup by ID async getBackup(id) { if (!this.index.backups[id]) { return null; } const backupPath = path.join(this.backupDir, `${id}.json`); try { if (await fs.pathExists(backupPath)) { return await fs.readJson(backupPath); } } catch (error) { console.warn(`Failed to load backup ${id}:`, error.message); } return null; } // Restore backup async restoreBackup(id, options = {}) { const backup = await this.getBackup(id); if (!backup) { throw new error_handler_1.ValidationError(`Backup '${id}' not found`); } const targetPath = options.targetPath || this.rootPath; await fs.ensureDir(targetPath); // Restore workspace definition const workspaceFile = path.join(targetPath, backup.metadata.workspaceFile); if (!options.force && await fs.pathExists(workspaceFile)) { throw new error_handler_1.ValidationError(`Workspace file ${backup.metadata.workspaceFile} already exists. Use --force to overwrite.`); } await this.saveWorkspaceDefinition(workspaceFile, backup.workspace); // Restore state if requested and available if (options.restoreState && backup.state) { try { const stateManager = await (0, workspace_state_1.createWorkspaceStateManager)(targetPath); // State restoration would be implemented here console.log('State restoration completed'); } catch (error) { console.warn('Failed to restore state:', error.message); } } // Restore cache if requested and available if (options.restoreCache && backup.cache) { try { const cacheDir = path.join(targetPath, '.re-shell', 'cache'); await this.restoreDirectory(cacheDir, backup.cache); console.log('Cache restoration completed'); } catch (error) { console.warn('Failed to restore cache:', error.message); } } // Restore templates if requested and available if (options.restoreTemplates && backup.templates) { try { const templatesDir = path.join(targetPath, '.re-shell', 'templates'); await this.restoreDirectory(templatesDir, backup.templates); console.log('Templates restoration completed'); } catch (error) { console.warn('Failed to restore templates:', error.message); } } // Restore files if requested and available if (options.restoreFiles && backup.files) { try { await this.restoreFiles(targetPath, backup.files, options.force); console.log('Files restoration completed'); } catch (error) { console.warn('Failed to restore files:', error.message); } } } // Delete backup async deleteBackup(id) { if (!this.index.backups[id]) { throw new error_handler_1.ValidationError(`Backup '${id}' not found`); } const backupPath = path.join(this.backupDir, `${id}.json`); await fs.remove(backupPath); delete this.index.backups[id]; await this.saveIndex(); } // Export backup to file async exportBackup(id, outputPath) { const backup = await this.getBackup(id); if (!backup) { throw new error_handler_1.ValidationError(`Backup '${id}' not found`); } await fs.ensureDir(path.dirname(outputPath)); await fs.writeJson(outputPath, backup, { spaces: 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 backup = await fs.readJson(filePath); // Validate backup structure if (!backup.metadata || !backup.workspace) { throw new error_handler_1.ValidationError('Invalid backup file format'); } // Generate new ID if needed const backupId = backup.metadata.id || this.generateBackupId(); backup.metadata.id = backupId; // Save backup const backupPath = path.join(this.backupDir, `${backupId}.json`); await fs.writeJson(backupPath, backup, { spaces: 2 }); // Update index this.index.backups[backupId] = backup.metadata; await this.saveIndex(); return backupId; } // Cleanup old backups async cleanupBackups(options = {}) { const backups = await this.listBackups(); const toDelete = []; let freedSpace = 0; // Keep count limit if (options.keepCount && backups.length > options.keepCount) { const excess = backups.slice(options.keepCount); toDelete.push(...excess.map(b => b.id)); } // Keep days limit if (options.keepDays) { const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - options.keepDays); const oldBackups = backups.filter(b => new Date(b.timestamp) < cutoffDate && !toDelete.includes(b.id)); toDelete.push(...oldBackups.map(b => b.id)); } // Calculate freed space for (const id of toDelete) { const backup = this.index.backups[id]; if (backup) { freedSpace += backup.size; } } // Delete backups if not dry run if (!options.dryRun) { for (const id of toDelete) { await this.deleteBackup(id); } } return { deletedCount: toDelete.length, freedSpace }; } // Get backup statistics getBackupStatistics() { const backups = Object.values(this.index.backups); const totalSize = backups.reduce((sum, b) => sum + b.size, 0); const sortedByDate = backups.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()); return { totalBackups: backups.length, totalSize, oldestBackup: sortedByDate[0]?.name, newestBackup: sortedByDate[sortedByDate.length - 1]?.name, averageSize: backups.length > 0 ? totalSize / backups.length : 0 }; } // Private helper methods generateBackupId() { return crypto.randomBytes(8).toString('hex'); } createDefaultIndex() { return { version: '1.0.0', backups: {}, metadata: { created: new Date().toISOString(), lastModified: new Date().toISOString(), totalBackups: 0, totalSize: 0 } }; } async loadIndex() { try { if (await fs.pathExists(this.indexPath)) { this.index = await fs.readJson(this.indexPath); } else { await this.saveIndex(); } } catch (error) { this.index = this.createDefaultIndex(); await this.saveIndex(); } } async saveIndex() { this.index.metadata.lastModified = new Date().toISOString(); this.index.metadata.totalBackups = Object.keys(this.index.backups).length; this.index.metadata.totalSize = Object.values(this.index.backups) .reduce((sum, b) => sum + b.size, 0); await fs.writeJson(this.indexPath, this.index, { spaces: 2 }); } async saveWorkspaceDefinition(filePath, definition) { const yaml = await Promise.resolve().then(() => __importStar(require('yaml'))); const content = yaml.stringify(definition); await fs.writeFile(filePath, content, 'utf8'); } async backupDirectory(dirPath) { const result = {}; try { const files = await fs.readdir(dirPath, { recursive: true }); for (const file of files) { const fileName = typeof file === 'string' ? file : file.toString(); const fullPath = path.join(dirPath, fileName); const stat = await fs.stat(fullPath); if (stat.isFile()) { try { if (fileName.endsWith('.json')) { result[fileName] = await fs.readJson(fullPath); } else { result[fileName] = await fs.readFile(fullPath, 'utf8'); } } catch (error) { console.warn(`Failed to backup file ${fileName}:`, error.message); } } } } catch (error) { console.warn(`Failed to backup directory ${dirPath}:`, error.message); } return result; } async restoreDirectory(dirPath, content) { await fs.ensureDir(dirPath); for (const [fileName, fileContent] of Object.entries(content)) { const fullPath = path.join(dirPath, fileName); await fs.ensureDir(path.dirname(fullPath)); try { if (typeof fileContent === 'object') { await fs.writeJson(fullPath, fileContent, { spaces: 2 }); } else { await fs.writeFile(fullPath, fileContent, 'utf8'); } } catch (error) { console.warn(`Failed to restore file ${fileName}:`, error.message); } } } async backupFiles(patterns) { const result = {}; for (const pattern of patterns) { try { const files = glob_1.glob.sync(pattern, { cwd: this.rootPath, absolute: false }); for (const file of files) { const fullPath = path.join(this.rootPath, file); try { const content = await fs.readFile(fullPath, 'utf8'); result[file] = content; } catch (error) { console.warn(`Failed to backup file ${file}:`, error.message); } } } catch (error) { console.warn(`Failed to process pattern ${pattern}:`, error.message); } } return result; } async restoreFiles(targetPath, files, force = false) { for (const [fileName, content] of Object.entries(files)) { const fullPath = path.join(targetPath, fileName); if (!force && await fs.pathExists(fullPath)) { console.warn(`Skipping existing file: ${fileName} (use --force to overwrite)`); continue; } await fs.ensureDir(path.dirname(fullPath)); try { await fs.writeFile(fullPath, content, 'utf8'); } catch (error) { console.warn(`Failed to restore file ${fileName}:`, error.message); } } } } exports.WorkspaceBackupManager = WorkspaceBackupManager; // Utility functions async function createWorkspaceBackupManager(rootPath) { const manager = new WorkspaceBackupManager(rootPath); await manager.init(); return manager; } // Quick backup function async function createQuickBackup(workspaceFile, name) { const manager = await createWorkspaceBackupManager(); return await manager.createBackup(workspaceFile, { name: name || `quick-backup-${new Date().toISOString().split('T')[0]}`, includeState: true, includeTemplates: true }); } async function compareBackups(manager, id1, id2) { const backup1 = await manager.getBackup(id1); const backup2 = await manager.getBackup(id2); if (!backup1 || !backup2) { throw new error_handler_1.ValidationError('One or both backups not found'); } const result = { added: [], removed: [], modified: [], unchanged: [] }; // Compare workspace definitions const ws1Keys = new Set(Object.keys(backup1.workspace.workspaces || {})); const ws2Keys = new Set(Object.keys(backup2.workspace.workspaces || {})); for (const key of ws1Keys) { if (!ws2Keys.has(key)) { result.removed.push(key); } else { const ws1 = JSON.stringify(backup1.workspace.workspaces[key]); const ws2 = JSON.stringify(backup2.workspace.workspaces[key]); if (ws1 !== ws2) { result.modified.push(key); } else { result.unchanged.push(key); } } } for (const key of ws2Keys) { if (!ws1Keys.has(key)) { result.added.push(key); } } return result; }