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

630 lines (629 loc) โ€ข 24.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.manageBackups = manageBackups; const chalk_1 = __importDefault(require("chalk")); const prompts_1 = __importDefault(require("prompts")); const config_backup_1 = require("../utils/config-backup"); const error_handler_1 = require("../utils/error-handler"); async function manageBackups(options = {}) { const { spinner, verbose, json } = options; try { if (options.create) { await createBackup(options, spinner); return; } if (options.restore) { await restoreBackup(options.restore, options, spinner); return; } if (options.list) { await listBackups(options, spinner); return; } if (options.delete) { await deleteBackup(options.delete, options, spinner); return; } if (options.export) { await exportBackup(options.export, options, spinner); return; } if (options.import) { await importBackup(options.import, options, spinner); return; } if (options.cleanup) { await cleanupBackups(options, spinner); return; } if (options.stats) { await showBackupStats(options, spinner); return; } if (options.interactive) { await interactiveBackupManagement(options, spinner); return; } // Default: show backup status await showBackupStatus(options, spinner); } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Backup operation failed')); throw error; } } async function createBackup(options, spinner) { if (options.full) { await createFullBackup(options, spinner); } else if (options.selective) { await createSelectiveBackup(options, spinner); } else if (options.interactive) { await interactiveCreateBackup(spinner); } else { // Default to full backup await createFullBackup(options, spinner); } } async function createFullBackup(options, spinner) { if (spinner) spinner.setText('Creating full configuration backup...'); const name = options.name || `full-backup-${new Date().toISOString().split('T')[0]}`; const description = options.description; const tags = options.tags ? options.tags.split(',').map(t => t.trim()) : []; const backupId = await config_backup_1.configBackupManager.createFullBackup(name, description, tags); if (spinner) { spinner.succeed(chalk_1.default.green('Full backup created successfully!')); } console.log(chalk_1.default.cyan(`\\n๐Ÿ“ฆ Backup Created`)); console.log(`ID: ${chalk_1.default.yellow(backupId)}`); console.log(`Name: ${name}`); if (description) console.log(`Description: ${description}`); if (tags.length > 0) console.log(`Tags: ${tags.join(', ')}`); } async function createSelectiveBackup(options, spinner) { if (spinner) spinner.setText('Creating selective configuration backup...'); // For CLI usage, we need to determine what to back up const backupOptions = { global: true, // Always include global by default project: true, // Include project if exists workspaces: [], // TODO: Add workspace detection templates: true, environments: false }; const name = options.name || `selective-backup-${new Date().toISOString().split('T')[0]}`; const description = options.description; const tags = options.tags ? options.tags.split(',').map(t => t.trim()) : ['selective']; const backupId = await config_backup_1.configBackupManager.createSelectiveBackup(name, backupOptions, description, tags); if (spinner) { spinner.succeed(chalk_1.default.green('Selective backup created successfully!')); } console.log(chalk_1.default.cyan(`\\n๐Ÿ“ฆ Selective Backup Created`)); console.log(`ID: ${chalk_1.default.yellow(backupId)}`); console.log(`Name: ${name}`); console.log(`Contents: ${Object.entries(backupOptions) .filter(([_, value]) => value === true || (Array.isArray(value) && value.length > 0)) .map(([key]) => key) .join(', ')}`); } async function interactiveCreateBackup(spinner) { if (spinner) spinner.stop(); const response = await (0, prompts_1.default)([ { type: 'select', name: 'type', message: 'What type of backup would you like to create?', choices: [ { title: '๐Ÿ“ฆ Full backup (all configurations)', value: 'full' }, { title: '๐ŸŽฏ Selective backup (choose what to include)', value: 'selective' } ] }, { type: 'text', name: 'name', message: 'Backup name:', initial: `backup-${new Date().toISOString().split('T')[0]}`, validate: (value) => value.trim() ? true : 'Backup name is required' }, { type: 'text', name: 'description', message: 'Description (optional):', }, { type: 'list', name: 'tags', message: 'Tags (comma-separated, optional):', separator: ',' } ]); if (!response.name) return; if (response.type === 'selective') { const selectiveResponse = await (0, prompts_1.default)([ { type: 'multiselect', name: 'contents', message: 'What would you like to include in the backup?', choices: [ { title: '๐ŸŒ Global configuration', value: 'global', selected: true }, { title: '๐Ÿ“ Project configuration', value: 'project', selected: true }, { title: '๐Ÿ—๏ธ Workspace configurations', value: 'workspaces', selected: true }, { title: '๐Ÿ“‹ Configuration templates', value: 'templates', selected: true }, { title: '๐ŸŒ Environment configurations', value: 'environments', selected: false } ] } ]); const backupOptions = { global: selectiveResponse.contents.includes('global'), project: selectiveResponse.contents.includes('project'), workspaces: selectiveResponse.contents.includes('workspaces') ? [] : undefined, templates: selectiveResponse.contents.includes('templates'), environments: selectiveResponse.contents.includes('environments') }; const backupId = await config_backup_1.configBackupManager.createSelectiveBackup(response.name, backupOptions, response.description, response.tags || []); console.log(chalk_1.default.green(`\\nโœ… Selective backup created: ${backupId}`)); } else { const backupId = await config_backup_1.configBackupManager.createFullBackup(response.name, response.description, response.tags || []); console.log(chalk_1.default.green(`\\nโœ… Full backup created: ${backupId}`)); } } async function restoreBackup(backupId, options, spinner) { if (spinner) spinner.setText(`Restoring from backup: ${backupId}`); const backup = await config_backup_1.configBackupManager.getBackup(backupId); if (!backup) { if (spinner) spinner.fail(chalk_1.default.red(`Backup '${backupId}' not found`)); return; } if (spinner) spinner.stop(); // Show backup info console.log(chalk_1.default.cyan(`\\n๐Ÿ”„ Restoring from Backup`)); console.log(`Name: ${backup.metadata.name}`); console.log(`Created: ${new Date(backup.metadata.createdAt).toLocaleDateString()}`); console.log(`Type: ${backup.metadata.type}`); // Confirm restoration unless force is used if (!options.force && !options.dryRun) { const confirmation = await (0, prompts_1.default)([ { type: 'confirm', name: 'confirmed', message: 'Are you sure you want to restore from this backup? This will overwrite current configurations.', initial: false } ]); if (!confirmation.confirmed) { console.log(chalk_1.default.yellow('Restoration cancelled.')); return; } } const restoreOptions = { force: options.force, createBackupBeforeRestore: options.preBackup !== false, dryRun: options.dryRun, mergeStrategy: options.mergeStrategy || 'replace' }; await config_backup_1.configBackupManager.restoreFromBackup(backupId, restoreOptions); if (!options.dryRun) { console.log(chalk_1.default.green('\\nโœ… Configuration restored successfully!')); } } async function listBackups(options, spinner) { if (spinner) spinner.setText('Loading backups...'); const backups = await config_backup_1.configBackupManager.listBackups(); if (spinner) spinner.stop(); if (backups.length === 0) { console.log(chalk_1.default.yellow('No backups found.')); console.log(chalk_1.default.gray('Create your first backup with: re-shell backup create')); return; } if (options.json) { console.log(JSON.stringify(backups, null, 2)); return; } console.log(chalk_1.default.cyan(`\\n๐Ÿ“ฆ Configuration Backups (${backups.length})`)); console.log(chalk_1.default.gray('โ•'.repeat(60))); for (const backup of backups) { const age = formatAge(backup.createdAt); const size = formatBytes(backup.size); console.log(chalk_1.default.cyan(`\\n๐Ÿ“„ ${backup.name}`)); console.log(` ID: ${chalk_1.default.yellow(backup.id)}`); console.log(` Type: ${getTypeIcon(backup.type)} ${backup.type}`); console.log(` Created: ${age} (${size})`); if (backup.description) { console.log(` Description: ${chalk_1.default.gray(backup.description)}`); } if (backup.tags.length > 0) { console.log(` Tags: ${backup.tags.map(tag => chalk_1.default.blue(tag)).join(', ')}`); } // Show contents const contents = []; if (backup.contents.global) contents.push('global'); if (backup.contents.project) contents.push('project'); if (backup.contents.workspaces && backup.contents.workspaces.length > 0) { contents.push(`workspaces(${backup.contents.workspaces.length})`); } if (backup.contents.templates) contents.push('templates'); if (backup.contents.environments) contents.push('environments'); console.log(` Contents: ${contents.join(', ')}`); if (options.verbose) { console.log(` Checksum: ${backup.checksum}`); console.log(` Version: ${backup.version}`); } } } async function deleteBackup(backupId, options, spinner) { if (spinner) spinner.setText(`Deleting backup: ${backupId}`); const backup = await config_backup_1.configBackupManager.getBackup(backupId); if (!backup) { if (spinner) spinner.fail(chalk_1.default.red(`Backup '${backupId}' not found`)); return; } if (spinner) spinner.stop(); // Confirm deletion unless force is used if (!options.force) { const confirmation = await (0, prompts_1.default)([ { type: 'confirm', name: 'confirmed', message: `Are you sure you want to delete backup '${backup.metadata.name}'?`, initial: false } ]); if (!confirmation.confirmed) { console.log(chalk_1.default.yellow('Deletion cancelled.')); return; } } await config_backup_1.configBackupManager.deleteBackup(backupId); console.log(chalk_1.default.green(`โœ… Backup '${backup.metadata.name}' deleted successfully!`)); } async function exportBackup(backupId, options, spinner) { if (!options.output) { throw new error_handler_1.ValidationError('Output file path is required for export (--output)'); } if (spinner) spinner.setText(`Exporting backup: ${backupId}`); await config_backup_1.configBackupManager.exportBackup(backupId, options.output); if (spinner) { spinner.succeed(chalk_1.default.green('Backup exported successfully!')); } console.log(chalk_1.default.cyan(`\\n๐Ÿ“ค Backup Exported`)); console.log(`File: ${options.output}`); } async function importBackup(filePath, options, spinner) { if (spinner) spinner.setText(`Importing backup from: ${filePath}`); const backupId = await config_backup_1.configBackupManager.importBackup(filePath); if (spinner) { spinner.succeed(chalk_1.default.green('Backup imported successfully!')); } console.log(chalk_1.default.cyan(`\\n๐Ÿ“ฅ Backup Imported`)); console.log(`ID: ${chalk_1.default.yellow(backupId)}`); } async function cleanupBackups(options, spinner) { if (spinner) spinner.setText('Analyzing backups for cleanup...'); const cleanupOptions = { keepCount: options.keepCount, keepDays: options.keepDays, dryRun: options.dryRun || false }; const deleted = await config_backup_1.configBackupManager.cleanup(cleanupOptions); if (spinner) spinner.stop(); if (deleted.length === 0) { console.log(chalk_1.default.green('โœ… No backups need cleanup')); return; } if (options.dryRun) { console.log(chalk_1.default.yellow(`\\n๐Ÿงน Cleanup Preview (${deleted.length} backups would be deleted):`)); } else { console.log(chalk_1.default.green(`\\n๐Ÿงน Cleanup Complete (${deleted.length} backups deleted):`)); } for (const backupId of deleted) { console.log(` โ€ข ${backupId}`); } } async function showBackupStats(options, spinner) { if (spinner) spinner.setText('Calculating backup statistics...'); const stats = await config_backup_1.configBackupManager.getBackupStats(); if (spinner) spinner.stop(); if (options.json) { console.log(JSON.stringify(stats, null, 2)); return; } console.log(chalk_1.default.cyan('\\n๐Ÿ“Š Backup Statistics')); console.log(chalk_1.default.gray('โ•'.repeat(40))); console.log(`\\nTotal backups: ${chalk_1.default.yellow(stats.totalBackups)}`); console.log(`Total size: ${chalk_1.default.yellow(formatBytes(stats.totalSize))}`); console.log(`Average size: ${chalk_1.default.yellow(formatBytes(stats.averageSize))}`); if (stats.oldestBackup && stats.newestBackup) { console.log(`\\nOldest: ${stats.oldestBackup.name} (${formatAge(stats.oldestBackup.createdAt)})`); console.log(`Newest: ${stats.newestBackup.name} (${formatAge(stats.newestBackup.createdAt)})`); } console.log(`\\nBackups by type:`); for (const [type, count] of Object.entries(stats.backupsByType)) { console.log(` ${getTypeIcon(type)} ${type}: ${count}`); } } async function showBackupStatus(options, spinner) { if (spinner) spinner.setText('Checking backup system status...'); const stats = await config_backup_1.configBackupManager.getBackupStats(); if (spinner) spinner.stop(); console.log(chalk_1.default.cyan('\\n๐Ÿ’พ Backup System Status')); console.log(chalk_1.default.gray('โ•'.repeat(40))); if (stats.totalBackups === 0) { console.log(chalk_1.default.yellow('\\nโš ๏ธ No backups found')); console.log(chalk_1.default.gray('Consider creating your first backup to protect your configurations.')); } else { console.log(`\\nโœ… ${stats.totalBackups} backup(s) available`); console.log(`๐Ÿ“ฆ Total size: ${formatBytes(stats.totalSize)}`); if (stats.newestBackup) { const age = formatAge(stats.newestBackup.createdAt); console.log(`๐Ÿ• Latest backup: ${age}`); } } console.log(chalk_1.default.cyan('\\n๐Ÿ› ๏ธ Available Operations:')); console.log(' โ€ข re-shell backup create --full'); console.log(' โ€ข re-shell backup create --selective'); console.log(' โ€ข re-shell backup list'); console.log(' โ€ข re-shell backup restore <id>'); console.log(' โ€ข re-shell backup interactive'); } async function interactiveBackupManagement(options, spinner) { if (spinner) spinner.stop(); const response = await (0, prompts_1.default)([ { type: 'select', name: 'action', message: 'What would you like to do?', choices: [ { title: '๐Ÿ“ฆ Create new backup', value: 'create' }, { title: '๐Ÿ“‹ List all backups', value: 'list' }, { title: '๐Ÿ”„ Restore from backup', value: 'restore' }, { title: '๐Ÿ“Š Show backup statistics', value: 'stats' }, { title: '๐Ÿงน Cleanup old backups', value: 'cleanup' }, { title: '๐Ÿ“ค Export backup', value: 'export' }, { title: '๐Ÿ“ฅ Import backup', value: 'import' }, { title: '๐Ÿ—‘๏ธ Delete backup', value: 'delete' } ] } ]); if (!response.action) return; switch (response.action) { case 'create': await interactiveCreateBackup(); break; case 'list': await listBackups(options); break; case 'restore': await interactiveRestore(); break; case 'stats': await showBackupStats(options); break; case 'cleanup': await interactiveCleanup(); break; case 'export': await interactiveExport(); break; case 'import': await interactiveImport(); break; case 'delete': await interactiveDelete(); break; } } async function interactiveRestore() { const backups = await config_backup_1.configBackupManager.listBackups(); if (backups.length === 0) { console.log(chalk_1.default.yellow('No backups available for restoration.')); return; } const response = await (0, prompts_1.default)([ { type: 'select', name: 'backupId', message: 'Select backup to restore:', choices: backups.map(backup => ({ title: `${backup.name} - ${backup.type} (${formatAge(backup.createdAt)})`, value: backup.id })) }, { type: 'select', name: 'strategy', message: 'Merge strategy:', choices: [ { title: 'Replace existing configurations', value: 'replace' }, { title: 'Merge with existing (keep both)', value: 'merge' }, { title: 'Skip if already exists', value: 'skip-existing' } ] }, { type: 'toggle', name: 'preBackup', message: 'Create backup before restoration?', initial: true, active: 'yes', inactive: 'no' }, { type: 'toggle', name: 'dryRun', message: 'Dry run (preview only)?', initial: false, active: 'yes', inactive: 'no' } ]); if (!response.backupId) return; await restoreBackup(response.backupId, { mergeStrategy: response.strategy, preBackup: response.preBackup, dryRun: response.dryRun, force: false }); } async function interactiveCleanup() { const response = await (0, prompts_1.default)([ { type: 'number', name: 'keepCount', message: 'Keep how many recent backups? (0 = no limit)', initial: 10, min: 0 }, { type: 'number', name: 'keepDays', message: 'Keep backups newer than how many days? (0 = no limit)', initial: 30, min: 0 }, { type: 'toggle', name: 'dryRun', message: 'Dry run (preview only)?', initial: true, active: 'yes', inactive: 'no' } ]); await cleanupBackups({ keepCount: response.keepCount || undefined, keepDays: response.keepDays || undefined, dryRun: response.dryRun }); } async function interactiveExport() { const backups = await config_backup_1.configBackupManager.listBackups(); if (backups.length === 0) { console.log(chalk_1.default.yellow('No backups available for export.')); return; } const response = await (0, prompts_1.default)([ { type: 'select', name: 'backupId', message: 'Select backup to export:', choices: backups.map(backup => ({ title: `${backup.name} - ${backup.type} (${formatAge(backup.createdAt)})`, value: backup.id })) }, { type: 'text', name: 'output', message: 'Export file path:', initial: 'backup-export.json', validate: (value) => value.trim() ? true : 'Output path is required' } ]); if (!response.backupId || !response.output) return; await exportBackup(response.backupId, { output: response.output }); } async function interactiveImport() { const response = await (0, prompts_1.default)([ { type: 'text', name: 'filePath', message: 'Backup file path to import:', validate: (value) => value.trim() ? true : 'File path is required' } ]); if (!response.filePath) return; await importBackup(response.filePath, {}); } async function interactiveDelete() { const backups = await config_backup_1.configBackupManager.listBackups(); if (backups.length === 0) { console.log(chalk_1.default.yellow('No backups available for deletion.')); return; } const response = await (0, prompts_1.default)([ { type: 'select', name: 'backupId', message: 'Select backup to delete:', choices: backups.map(backup => ({ title: `${backup.name} - ${backup.type} (${formatAge(backup.createdAt)})`, value: backup.id })) } ]); if (!response.backupId) return; await deleteBackup(response.backupId, { force: false }); } // Utility functions function formatBytes(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`; } function formatAge(dateString) { const now = new Date(); const date = new Date(dateString); const diffMs = now.getTime() - date.getTime(); const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); const diffMinutes = Math.floor(diffMs / (1000 * 60)); if (diffDays > 0) return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`; if (diffHours > 0) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`; if (diffMinutes > 0) return `${diffMinutes} minute${diffMinutes > 1 ? 's' : ''} ago`; return 'just now'; } function getTypeIcon(type) { const icons = { full: '๐Ÿ“ฆ', selective: '๐ŸŽฏ', global: '๐ŸŒ', project: '๐Ÿ“', workspace: '๐Ÿ—๏ธ' }; return icons[type] || '๐Ÿ“„'; }