@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
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.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();