@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
JavaScript
;
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] || '๐';
}