@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
714 lines (713 loc) • 26 kB
JavaScript
;
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.manageWorkspaceBackup = manageWorkspaceBackup;
const chalk_1 = __importDefault(require("chalk"));
const prompts_1 = __importDefault(require("prompts"));
const fs = __importStar(require("fs-extra"));
const workspace_backup_1 = require("../utils/workspace-backup");
const error_handler_1 = require("../utils/error-handler");
const DEFAULT_WORKSPACE_FILE = 're-shell.workspaces.yaml';
async function manageWorkspaceBackup(options = {}) {
const { spinner, verbose, json } = options;
try {
if (options.create) {
await createBackup(options, spinner);
return;
}
if (options.list) {
await listBackups(options, spinner);
return;
}
if (options.restore) {
await restoreBackup(options, spinner);
return;
}
if (options.delete) {
await deleteBackup(options, spinner);
return;
}
if (options.export) {
await exportBackup(options, spinner);
return;
}
if (options.import) {
await importBackup(options, spinner);
return;
}
if (options.cleanup) {
await cleanupBackups(options, spinner);
return;
}
if (options.show) {
await showBackup(options, spinner);
return;
}
if (options.compare) {
await compareBackupsCommand(options, spinner);
return;
}
if (options.interactive) {
await interactiveBackupManagement(options, spinner);
return;
}
// Default: list backups
await listBackups(options, spinner);
}
catch (error) {
if (spinner)
spinner.fail(chalk_1.default.red('Backup operation failed'));
throw error;
}
}
async function createBackup(options, spinner) {
const workspaceFile = options.workspaceFile || DEFAULT_WORKSPACE_FILE;
if (!(await fs.pathExists(workspaceFile))) {
throw new error_handler_1.ValidationError(`Workspace file not found: ${workspaceFile}`);
}
if (spinner)
spinner.setText('Creating workspace backup...');
try {
const manager = await (0, workspace_backup_1.createWorkspaceBackupManager)();
const backupOptions = {
name: options.name,
description: options.description,
includeState: options.includeState ?? true,
includeCache: options.includeCache ?? false,
includeTemplates: options.includeTemplates ?? true,
includeFiles: options.includeFiles ?? false,
filePatterns: options.filePatterns ? options.filePatterns.split(',') : undefined,
tags: options.tags ? options.tags.split(',') : undefined
};
const backupId = await manager.createBackup(workspaceFile, backupOptions);
if (spinner)
spinner.stop();
if (options.json) {
console.log(JSON.stringify({ backupId }, null, 2));
}
else {
console.log(chalk_1.default.green('✅ Backup created successfully!'));
console.log(chalk_1.default.gray(`Backup ID: ${backupId}`));
console.log(chalk_1.default.gray(`Workspace: ${workspaceFile}`));
if (backupOptions.includeState) {
console.log(chalk_1.default.gray('✓ Included workspace state'));
}
if (backupOptions.includeTemplates) {
console.log(chalk_1.default.gray('✓ Included templates'));
}
if (backupOptions.includeCache) {
console.log(chalk_1.default.gray('✓ Included cache'));
}
if (backupOptions.includeFiles) {
console.log(chalk_1.default.gray('✓ Included files'));
}
}
}
catch (error) {
if (spinner)
spinner.fail(chalk_1.default.red('Failed to create backup'));
throw error;
}
}
async function listBackups(options, spinner) {
if (spinner)
spinner.setText('Loading backups...');
try {
const manager = await (0, workspace_backup_1.createWorkspaceBackupManager)();
const backups = await manager.listBackups();
if (spinner)
spinner.stop();
if (options.json) {
console.log(JSON.stringify(backups, null, 2));
return;
}
if (backups.length === 0) {
console.log(chalk_1.default.yellow('\n⚠️ No backups found'));
console.log(chalk_1.default.gray('Create one with: re-shell workspace-backup create'));
return;
}
console.log(chalk_1.default.cyan('\n💾 Workspace Backups'));
console.log(chalk_1.default.gray('═'.repeat(60)));
for (const backup of backups) {
const date = new Date(backup.timestamp).toLocaleString();
const size = formatBytes(backup.size);
console.log(`\n${chalk_1.default.bold(backup.name)} (${backup.id.substring(0, 8)})`);
console.log(` ${chalk_1.default.gray(`Created: ${date}`)}`);
console.log(` ${chalk_1.default.gray(`Size: ${size}`)}`);
console.log(` ${chalk_1.default.gray(`Workspace: ${backup.workspaceFile}`)}`);
if (backup.description) {
console.log(` ${chalk_1.default.gray(`Description: ${backup.description}`)}`);
}
if (backup.tags && backup.tags.length > 0) {
console.log(` ${chalk_1.default.blue(`Tags: ${backup.tags.join(', ')}`)}`);
}
const includes = [];
if (backup.includeState)
includes.push('state');
if (backup.includeCache)
includes.push('cache');
if (backup.includeTemplates)
includes.push('templates');
if (includes.length > 0) {
console.log(` ${chalk_1.default.green(`Includes: ${includes.join(', ')}`)}`);
}
if (options.verbose) {
console.log(` ${chalk_1.default.gray(`Hash: ${backup.hash.substring(0, 16)}...`)}`);
}
}
const stats = manager.getBackupStatistics();
console.log(chalk_1.default.cyan('\n📊 Statistics:'));
console.log(` Total backups: ${stats.totalBackups}`);
console.log(` Total size: ${formatBytes(stats.totalSize)}`);
console.log(` Average size: ${formatBytes(stats.averageSize)}`);
console.log(chalk_1.default.cyan('\n🛠️ Commands:'));
console.log(' • re-shell workspace-backup show <id>');
console.log(' • re-shell workspace-backup restore <id>');
console.log(' • re-shell workspace-backup export <id>');
}
catch (error) {
if (spinner)
spinner.fail(chalk_1.default.red('Failed to load backups'));
throw error;
}
}
async function restoreBackup(options, spinner) {
if (!options.name) {
throw new error_handler_1.ValidationError('Backup ID is required for restore');
}
if (spinner)
spinner.setText(`Restoring backup: ${options.name}`);
try {
const manager = await (0, workspace_backup_1.createWorkspaceBackupManager)();
const restoreOptions = {
force: options.force,
selective: options.selective,
restoreState: options.restoreState ?? true,
restoreCache: options.restoreCache ?? false,
restoreTemplates: options.restoreTemplates ?? true,
restoreFiles: options.restoreFiles ?? false,
targetPath: options.targetPath
};
await manager.restoreBackup(options.name, restoreOptions);
if (spinner)
spinner.stop();
console.log(chalk_1.default.green('✅ Backup restored successfully!'));
console.log(chalk_1.default.gray(`Backup ID: ${options.name}`));
if (options.targetPath) {
console.log(chalk_1.default.gray(`Target: ${options.targetPath}`));
}
if (restoreOptions.restoreState) {
console.log(chalk_1.default.gray('✓ Restored workspace state'));
}
if (restoreOptions.restoreTemplates) {
console.log(chalk_1.default.gray('✓ Restored templates'));
}
if (restoreOptions.restoreCache) {
console.log(chalk_1.default.gray('✓ Restored cache'));
}
if (restoreOptions.restoreFiles) {
console.log(chalk_1.default.gray('✓ Restored files'));
}
}
catch (error) {
if (spinner)
spinner.fail(chalk_1.default.red('Failed to restore backup'));
throw error;
}
}
async function deleteBackup(options, spinner) {
if (!options.name) {
throw new error_handler_1.ValidationError('Backup ID is required for deletion');
}
if (spinner)
spinner.setText(`Deleting backup: ${options.name}`);
try {
const manager = await (0, workspace_backup_1.createWorkspaceBackupManager)();
await manager.deleteBackup(options.name);
if (spinner)
spinner.stop();
console.log(chalk_1.default.green(`✅ Backup '${options.name}' deleted successfully!`));
}
catch (error) {
if (spinner)
spinner.fail(chalk_1.default.red('Failed to delete backup'));
throw error;
}
}
async function exportBackup(options, spinner) {
if (!options.name) {
throw new error_handler_1.ValidationError('Backup ID is required for export');
}
if (!options.output) {
throw new error_handler_1.ValidationError('Output file path is required for export');
}
if (spinner)
spinner.setText(`Exporting backup: ${options.name}`);
try {
const manager = await (0, workspace_backup_1.createWorkspaceBackupManager)();
await manager.exportBackup(options.name, options.output);
if (spinner)
spinner.stop();
console.log(chalk_1.default.green('✅ Backup exported successfully!'));
console.log(chalk_1.default.gray(`Exported to: ${options.output}`));
}
catch (error) {
if (spinner)
spinner.fail(chalk_1.default.red('Failed to export backup'));
throw error;
}
}
async function importBackup(options, spinner) {
if (!options.file) {
throw new error_handler_1.ValidationError('Backup file path is required for import');
}
if (spinner)
spinner.setText(`Importing backup from: ${options.file}`);
try {
const manager = await (0, workspace_backup_1.createWorkspaceBackupManager)();
const backupId = await manager.importBackup(options.file);
if (spinner)
spinner.stop();
console.log(chalk_1.default.green('✅ Backup imported successfully!'));
console.log(chalk_1.default.gray(`Backup ID: ${backupId}`));
console.log(chalk_1.default.gray(`Source: ${options.file}`));
}
catch (error) {
if (spinner)
spinner.fail(chalk_1.default.red('Failed to import backup'));
throw error;
}
}
async function cleanupBackups(options, spinner) {
if (spinner)
spinner.setText('Analyzing backups for cleanup...');
try {
const manager = await (0, workspace_backup_1.createWorkspaceBackupManager)();
const cleanupOptions = {
keepCount: options.keepCount,
keepDays: options.keepDays,
dryRun: options.dryRun
};
const result = await manager.cleanupBackups(cleanupOptions);
if (spinner)
spinner.stop();
if (options.json) {
console.log(JSON.stringify(result, null, 2));
return;
}
if (options.dryRun) {
console.log(chalk_1.default.cyan('🔍 Cleanup Preview (Dry Run)'));
}
else {
console.log(chalk_1.default.green('✅ Cleanup completed!'));
}
console.log(`Backups to delete: ${result.deletedCount}`);
console.log(`Space to free: ${formatBytes(result.freedSpace)}`);
if (result.deletedCount === 0) {
console.log(chalk_1.default.gray('No backups need cleanup'));
}
}
catch (error) {
if (spinner)
spinner.fail(chalk_1.default.red('Failed to cleanup backups'));
throw error;
}
}
async function showBackup(options, spinner) {
if (!options.name) {
throw new error_handler_1.ValidationError('Backup ID is required');
}
if (spinner)
spinner.setText(`Loading backup: ${options.name}`);
try {
const manager = await (0, workspace_backup_1.createWorkspaceBackupManager)();
const backup = await manager.getBackup(options.name);
if (!backup) {
throw new error_handler_1.ValidationError(`Backup '${options.name}' not found`);
}
if (spinner)
spinner.stop();
if (options.json) {
console.log(JSON.stringify(backup, null, 2));
return;
}
const metadata = backup.metadata;
console.log(chalk_1.default.cyan(`\n💾 Backup: ${metadata.name}`));
console.log(chalk_1.default.gray('═'.repeat(60)));
console.log(`ID: ${metadata.id}`);
console.log(`Created: ${new Date(metadata.timestamp).toLocaleString()}`);
console.log(`Size: ${formatBytes(metadata.size)}`);
console.log(`Workspace: ${metadata.workspaceFile}`);
console.log(`Version: ${metadata.version}`);
if (metadata.description) {
console.log(`Description: ${metadata.description}`);
}
if (metadata.tags && metadata.tags.length > 0) {
console.log(`Tags: ${metadata.tags.join(', ')}`);
}
console.log(`\n${chalk_1.default.blue('Included Components:')}`);
console.log(` Workspace Definition: ✓`);
console.log(` State: ${metadata.includeState ? '✓' : '✗'}`);
console.log(` Cache: ${metadata.includeCache ? '✓' : '✗'}`);
console.log(` Templates: ${metadata.includeTemplates ? '✓' : '✗'}`);
if (options.verbose) {
console.log(`\n${chalk_1.default.green('Hash:')} ${metadata.hash}`);
console.log(`\n${chalk_1.default.yellow('Workspace Summary:')}`);
const ws = backup.workspace;
console.log(` Name: ${ws.name}`);
console.log(` Version: ${ws.version}`);
console.log(` Workspaces: ${Object.keys(ws.workspaces).length}`);
console.log(` Types: ${Object.keys(ws.types).length}`);
}
}
catch (error) {
if (spinner)
spinner.fail(chalk_1.default.red('Failed to load backup'));
throw error;
}
}
async function compareBackupsCommand(options, spinner) {
if (!options.backup1 || !options.backup2) {
throw new error_handler_1.ValidationError('Two backup IDs are required for comparison');
}
if (spinner)
spinner.setText('Comparing backups...');
try {
const manager = await (0, workspace_backup_1.createWorkspaceBackupManager)();
const comparison = await (0, workspace_backup_1.compareBackups)(manager, options.backup1, options.backup2);
if (spinner)
spinner.stop();
if (options.json) {
console.log(JSON.stringify(comparison, null, 2));
return;
}
console.log(chalk_1.default.cyan('\n🔍 Backup Comparison'));
console.log(chalk_1.default.gray('═'.repeat(50)));
console.log(`Comparing: ${options.backup1} → ${options.backup2}`);
if (comparison.added.length > 0) {
console.log(`\n${chalk_1.default.green('Added workspaces:')} (${comparison.added.length})`);
for (const item of comparison.added) {
console.log(` + ${item}`);
}
}
if (comparison.removed.length > 0) {
console.log(`\n${chalk_1.default.red('Removed workspaces:')} (${comparison.removed.length})`);
for (const item of comparison.removed) {
console.log(` - ${item}`);
}
}
if (comparison.modified.length > 0) {
console.log(`\n${chalk_1.default.yellow('Modified workspaces:')} (${comparison.modified.length})`);
for (const item of comparison.modified) {
console.log(` ~ ${item}`);
}
}
if (comparison.unchanged.length > 0) {
console.log(`\n${chalk_1.default.gray('Unchanged workspaces:')} (${comparison.unchanged.length})`);
if (options.verbose) {
for (const item of comparison.unchanged) {
console.log(` = ${item}`);
}
}
}
}
catch (error) {
if (spinner)
spinner.fail(chalk_1.default.red('Failed to compare backups'));
throw error;
}
}
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: '📋 List backups', value: 'list' },
{ title: '💾 Create backup', value: 'create' },
{ title: '🔄 Restore backup', value: 'restore' },
{ title: '👁️ Show backup details', value: 'show' },
{ title: '📤 Export backup', value: 'export' },
{ title: '📥 Import backup', value: 'import' },
{ title: '🔍 Compare backups', value: 'compare' },
{ title: '🧹 Cleanup old backups', value: 'cleanup' },
{ title: '🗑️ Delete backup', value: 'delete' }
]
}
]);
if (!response.action)
return;
switch (response.action) {
case 'list':
await listBackups({ ...options, interactive: false });
break;
case 'create':
await createBackupInteractive(options);
break;
case 'restore':
await restoreBackupInteractive(options);
break;
case 'show':
const showResponse = await (0, prompts_1.default)({
type: 'text',
name: 'id',
message: 'Backup ID:'
});
if (showResponse.id) {
await showBackup({ ...options, name: showResponse.id, interactive: false });
}
break;
case 'export':
await exportBackupInteractive(options);
break;
case 'import':
const importResponse = await (0, prompts_1.default)({
type: 'text',
name: 'file',
message: 'Backup file path:'
});
if (importResponse.file) {
await importBackup({ ...options, file: importResponse.file, interactive: false });
}
break;
case 'compare':
await compareBackupsInteractive(options);
break;
case 'cleanup':
await cleanupBackupsInteractive(options);
break;
case 'delete':
await deleteBackupInteractive(options);
break;
}
}
async function createBackupInteractive(options) {
const response = await (0, prompts_1.default)([
{
type: 'text',
name: 'workspaceFile',
message: 'Workspace file:',
initial: 're-shell.workspaces.yaml'
},
{
type: 'text',
name: 'name',
message: 'Backup name (optional):'
},
{
type: 'text',
name: 'description',
message: 'Description (optional):'
},
{
type: 'confirm',
name: 'includeState',
message: 'Include workspace state?',
initial: true
},
{
type: 'confirm',
name: 'includeTemplates',
message: 'Include templates?',
initial: true
},
{
type: 'confirm',
name: 'includeCache',
message: 'Include cache?',
initial: false
},
{
type: 'confirm',
name: 'includeFiles',
message: 'Include additional files?',
initial: false
}
]);
if (!response.workspaceFile)
return;
await createBackup({
...options,
...response,
interactive: false
});
}
async function restoreBackupInteractive(options) {
const response = await (0, prompts_1.default)([
{
type: 'text',
name: 'name',
message: 'Backup ID to restore:'
},
{
type: 'text',
name: 'targetPath',
message: 'Target directory (optional):'
},
{
type: 'confirm',
name: 'force',
message: 'Overwrite existing files?',
initial: false
},
{
type: 'confirm',
name: 'restoreState',
message: 'Restore workspace state?',
initial: true
},
{
type: 'confirm',
name: 'restoreTemplates',
message: 'Restore templates?',
initial: true
}
]);
if (!response.name)
return;
await restoreBackup({
...options,
...response,
interactive: false
});
}
async function exportBackupInteractive(options) {
const response = await (0, prompts_1.default)([
{
type: 'text',
name: 'name',
message: 'Backup ID to export:'
},
{
type: 'text',
name: 'output',
message: 'Output file path:'
}
]);
if (!response.name || !response.output)
return;
await exportBackup({
...options,
...response,
interactive: false
});
}
async function compareBackupsInteractive(options) {
const response = await (0, prompts_1.default)([
{
type: 'text',
name: 'backup1',
message: 'First backup ID:'
},
{
type: 'text',
name: 'backup2',
message: 'Second backup ID:'
}
]);
if (!response.backup1 || !response.backup2)
return;
await compareBackupsCommand({
...options,
...response,
interactive: false
});
}
async function cleanupBackupsInteractive(options) {
const response = await (0, prompts_1.default)([
{
type: 'number',
name: 'keepCount',
message: 'Keep how many recent backups? (0 for no limit):',
initial: 10
},
{
type: 'number',
name: 'keepDays',
message: 'Keep backups newer than how many days? (0 for no limit):',
initial: 30
},
{
type: 'confirm',
name: 'dryRun',
message: 'Preview cleanup without deleting?',
initial: true
}
]);
await cleanupBackups({
...options,
keepCount: response.keepCount || undefined,
keepDays: response.keepDays || undefined,
dryRun: response.dryRun,
interactive: false
});
}
async function deleteBackupInteractive(options) {
const response = await (0, prompts_1.default)([
{
type: 'text',
name: 'name',
message: 'Backup ID to delete:'
},
{
type: 'confirm',
name: 'confirm',
message: 'Are you sure you want to delete this backup?',
initial: false
}
]);
if (!response.name || !response.confirm)
return;
await deleteBackup({
...options,
name: response.name,
interactive: false
});
}
// Utility functions
function formatBytes(bytes) {
if (bytes === 0)
return '0 B';
const units = ['B', 'KB', 'MB', 'GB'];
const k = 1024;
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${units[i]}`;
}