claude-flow-multilang
Version:
Revolutionary multilingual AI orchestration framework with cultural awareness and DDD architecture
543 lines (456 loc) • 14 kB
JavaScript
// backup-manager.js - Backup creation and management
// Node.js compatible import
import fs from 'fs';
import { errors } from '../../../node-compat.js';
// Polyfill for Deno's ensureDirSync
function ensureDirSync(dirPath) {
try {
fs.mkdirSync(dirPath, { recursive: true });
} catch (error) {
if (error.code !== 'EEXIST') throw error;
}
}
export class BackupManager {
constructor(workingDir) {
this.workingDir = workingDir;
this.backupDir = `${workingDir}/.claude-flow-backups`;
}
/**
* Create a backup of the current state
*/
async createBackup(type = 'manual', description = '') {
const result = {
success: true,
id: null,
location: null,
errors: [],
warnings: [],
files: [],
};
try {
// Generate backup ID
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupId = `${type}-${timestamp}`;
result.id = backupId;
// Create backup directory
const backupPath = `${this.backupDir}/${backupId}`;
result.location = backupPath;
await this.ensureBackupDir();
await Deno.mkdir(backupPath, { recursive: true });
// Create backup manifest
const manifest = {
id: backupId,
type,
description,
timestamp: Date.now(),
workingDir: this.workingDir,
files: [],
directories: [],
};
// Backup critical files
const criticalFiles = await this.getCriticalFiles();
for (const file of criticalFiles) {
const backupResult = await this.backupFile(file, backupPath);
if (backupResult.success) {
manifest.files.push(backupResult.fileInfo);
result.files.push(file);
} else {
result.warnings.push(`Failed to backup file: ${file}`);
}
}
// Backup critical directories
const criticalDirs = await this.getCriticalDirectories();
for (const dir of criticalDirs) {
const backupResult = await this.backupDirectory(dir, backupPath);
if (backupResult.success) {
manifest.directories.push(backupResult.dirInfo);
} else {
result.warnings.push(`Failed to backup directory: ${dir}`);
}
}
// Save manifest
await Deno.writeTextFile(`${backupPath}/manifest.json`, JSON.stringify(manifest, null, 2));
// Create backup metadata
const metadata = {
created: Date.now(),
size: await this.calculateBackupSize(backupPath),
fileCount: manifest.files.length,
dirCount: manifest.directories.length,
};
await Deno.writeTextFile(`${backupPath}/metadata.json`, JSON.stringify(metadata, null, 2));
console.log(` ✓ Backup created: ${backupId}`);
console.log(` 📁 Files backed up: ${result.files.length}`);
} catch (error) {
result.success = false;
result.errors.push(`Backup creation failed: ${error.message}`);
}
return result;
}
/**
* Restore from backup
*/
async restoreBackup(backupId) {
const result = {
success: true,
errors: [],
warnings: [],
restored: [],
};
try {
const backupPath = `${this.backupDir}/${backupId}`;
// Check if backup exists
try {
await Deno.stat(backupPath);
} catch {
result.success = false;
result.errors.push(`Backup not found: ${backupId}`);
return result;
}
// Read manifest
const manifestPath = `${backupPath}/manifest.json`;
const manifestContent = await fs.readFile(manifestPath, 'utf8');
const manifest = JSON.parse(manifestContent);
// Restore files
for (const fileInfo of manifest.files) {
const restoreResult = await this.restoreFile(fileInfo, backupPath);
if (restoreResult.success) {
result.restored.push(fileInfo.originalPath);
} else {
result.warnings.push(`Failed to restore file: ${fileInfo.originalPath}`);
}
}
// Restore directories
for (const dirInfo of manifest.directories) {
const restoreResult = await this.restoreDirectory(dirInfo, backupPath);
if (restoreResult.success) {
result.restored.push(dirInfo.originalPath);
} else {
result.warnings.push(`Failed to restore directory: ${dirInfo.originalPath}`);
}
}
console.log(` ✓ Backup restored: ${backupId}`);
console.log(` 📁 Items restored: ${result.restored.length}`);
} catch (error) {
result.success = false;
result.errors.push(`Backup restoration failed: ${error.message}`);
}
return result;
}
/**
* List available backups
*/
async listBackups() {
const backups = [];
try {
await this.ensureBackupDir();
for await (const entry of Deno.readDir(this.backupDir)) {
if (entry.isDirectory) {
try {
const metadataPath = `${this.backupDir}/${entry.name}/metadata.json`;
const manifestPath = `${this.backupDir}/${entry.name}/manifest.json`;
const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf8'));
const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
backups.push({
id: entry.name,
type: manifest.type,
description: manifest.description,
created: metadata.created,
size: metadata.size,
fileCount: metadata.fileCount,
dirCount: metadata.dirCount,
});
} catch {
// Skip invalid backup directories
}
}
}
} catch {
// Backup directory doesn't exist or can't be read
}
return backups.sort((a, b) => b.created - a.created);
}
/**
* Delete a backup
*/
async deleteBackup(backupId) {
const result = {
success: true,
errors: [],
};
try {
const backupPath = `${this.backupDir}/${backupId}`;
await Deno.remove(backupPath, { recursive: true });
console.log(` 🗑️ Deleted backup: ${backupId}`);
} catch (error) {
result.success = false;
result.errors.push(`Failed to delete backup: ${error.message}`);
}
return result;
}
/**
* Clean up old backups
*/
async cleanupOldBackups(keepCount = 5) {
const result = {
success: true,
cleaned: [],
errors: [],
};
try {
const backups = await this.listBackups();
if (backups.length > keepCount) {
const toDelete = backups.slice(keepCount);
for (const backup of toDelete) {
const deleteResult = await this.deleteBackup(backup.id);
if (deleteResult.success) {
result.cleaned.push(backup.id);
} else {
result.errors.push(...deleteResult.errors);
}
}
}
} catch (error) {
result.success = false;
result.errors.push(`Cleanup failed: ${error.message}`);
}
return result;
}
/**
* Validate backup system
*/
async validateBackupSystem() {
const result = {
success: true,
errors: [],
warnings: [],
};
try {
// Check backup directory
await this.ensureBackupDir();
// Test backup creation
const testBackup = await this.createTestBackup();
if (!testBackup.success) {
result.success = false;
result.errors.push('Cannot create test backup');
} else {
// Clean up test backup
await this.deleteBackup(testBackup.id);
}
// Check disk space
const spaceCheck = await this.checkBackupDiskSpace();
if (!spaceCheck.adequate) {
result.warnings.push('Low disk space for backups');
}
} catch (error) {
result.success = false;
result.errors.push(`Backup system validation failed: ${error.message}`);
}
return result;
}
// Helper methods
async ensureBackupDir() {
try {
await Deno.mkdir(this.backupDir, { recursive: true });
} catch (error) {
if (!(error instanceof errors.AlreadyExists)) {
throw error;
}
}
}
async getCriticalFiles() {
const files = [];
const potentialFiles = [
'CLAUDE.md',
'memory-bank.md',
'coordination.md',
'package.json',
'package-lock.json',
'.roomodes',
'claude-flow',
'memory/claude-flow-data.json',
];
for (const file of potentialFiles) {
try {
const stat = await Deno.stat(`${this.workingDir}/${file}`);
if (stat.isFile) {
files.push(file);
}
} catch {
// File doesn't exist
}
}
return files;
}
async getCriticalDirectories() {
const dirs = [];
const potentialDirs = ['.claude', '.roo', 'memory/agents', 'memory/sessions', 'coordination'];
for (const dir of potentialDirs) {
try {
const stat = await Deno.stat(`${this.workingDir}/${dir}`);
if (stat.isDirectory) {
dirs.push(dir);
}
} catch {
// Directory doesn't exist
}
}
return dirs;
}
async backupFile(relativePath, backupPath) {
const result = {
success: true,
fileInfo: null,
};
try {
const sourcePath = `${this.workingDir}/${relativePath}`;
const destPath = `${backupPath}/${relativePath}`;
// Ensure destination directory exists
const destDir = destPath.split('/').slice(0, -1).join('/');
await Deno.mkdir(destDir, { recursive: true });
// Copy file
await Deno.copyFile(sourcePath, destPath);
// Get file info
const stat = await Deno.stat(sourcePath);
result.fileInfo = {
originalPath: relativePath,
backupPath: destPath,
size: stat.size,
modified: stat.mtime?.getTime() || 0,
};
} catch (error) {
result.success = false;
result.error = error.message;
}
return result;
}
async backupDirectory(relativePath, backupPath) {
const result = {
success: true,
dirInfo: null,
};
try {
const sourcePath = `${this.workingDir}/${relativePath}`;
const destPath = `${backupPath}/${relativePath}`;
// Create destination directory
await Deno.mkdir(destPath, { recursive: true });
// Copy directory contents recursively
await this.copyDirectoryRecursive(sourcePath, destPath);
result.dirInfo = {
originalPath: relativePath,
backupPath: destPath,
};
} catch (error) {
result.success = false;
result.error = error.message;
}
return result;
}
async copyDirectoryRecursive(source, dest) {
for await (const entry of Deno.readDir(source)) {
const sourcePath = `${source}/${entry.name}`;
const destPath = `${dest}/${entry.name}`;
if (entry.isFile) {
await Deno.copyFile(sourcePath, destPath);
} else if (entry.isDirectory) {
await Deno.mkdir(destPath, { recursive: true });
await this.copyDirectoryRecursive(sourcePath, destPath);
}
}
}
async restoreFile(fileInfo, backupPath) {
const result = {
success: true,
};
try {
const sourcePath = fileInfo.backupPath;
const destPath = `${this.workingDir}/${fileInfo.originalPath}`;
// Ensure destination directory exists
const destDir = destPath.split('/').slice(0, -1).join('/');
await Deno.mkdir(destDir, { recursive: true });
// Copy file back
await Deno.copyFile(sourcePath, destPath);
} catch (error) {
result.success = false;
result.error = error.message;
}
return result;
}
async restoreDirectory(dirInfo, backupPath) {
const result = {
success: true,
};
try {
const sourcePath = dirInfo.backupPath;
const destPath = `${this.workingDir}/${dirInfo.originalPath}`;
// Remove existing directory if it exists
try {
await Deno.remove(destPath, { recursive: true });
} catch {
// Directory might not exist
}
// Create destination directory
await Deno.mkdir(destPath, { recursive: true });
// Copy directory contents back
await this.copyDirectoryRecursive(sourcePath, destPath);
} catch (error) {
result.success = false;
result.error = error.message;
}
return result;
}
async calculateBackupSize(backupPath) {
let totalSize = 0;
try {
for await (const entry of Deno.readDir(backupPath)) {
const entryPath = `${backupPath}/${entry.name}`;
const stat = await Deno.stat(entryPath);
if (stat.isFile) {
totalSize += stat.size;
} else if (stat.isDirectory) {
totalSize += await this.calculateBackupSize(entryPath);
}
}
} catch {
// Error calculating size
}
return totalSize;
}
async createTestBackup() {
try {
return await this.createBackup('test', 'System validation test');
} catch (error) {
return {
success: false,
error: error.message,
};
}
}
async checkBackupDiskSpace() {
const result = {
adequate: true,
available: 0,
};
try {
const command = new Deno.Command('df', {
args: ['-k', this.backupDir],
stdout: 'piped',
});
const { stdout, success } = await command.output();
if (success) {
const output = new TextDecoder().decode(stdout);
const lines = output.trim().split('\n');
if (lines.length >= 2) {
const parts = lines[1].split(/\s+/);
if (parts.length >= 4) {
result.available = parseInt(parts[3]) / 1024; // MB
result.adequate = result.available > 500; // At least 500MB for backups
}
}
}
} catch {
// Can't check - assume adequate
result.adequate = true;
}
return result;
}
}