UNPKG

duq-cli

Version:

CLI tool for interacting with Amazon Q using different prompt templates

220 lines (190 loc) 7.39 kB
const fs = require('fs-extra'); const path = require('path'); const os = require('os'); const crypto = require('crypto'); /** * Manages file backups for the duq CLI */ class BackupManager { constructor() { // Create a central backup directory in the user's home directory this.backupDir = path.join(os.homedir(), '.duq', 'backups'); this.indexFile = path.join(os.homedir(), '.duq', 'backup-index.json'); // Ensure the backup directory exists fs.ensureDirSync(this.backupDir); // Load or initialize the backup index this.loadIndex(); } /** * Load the backup index from disk or create a new one */ loadIndex() { try { if (fs.existsSync(this.indexFile)) { this.index = JSON.parse(fs.readFileSync(this.indexFile, 'utf8')); } else { this.index = { files: {}, history: [] }; this.saveIndex(); } } catch (error) { console.error(`Error loading backup index: ${error.message}`); this.index = { files: {}, history: [] }; } } /** * Save the backup index to disk */ saveIndex() { try { fs.ensureDirSync(path.dirname(this.indexFile)); fs.writeFileSync(this.indexFile, JSON.stringify(this.index, null, 2), 'utf8'); } catch (error) { console.error(`Error saving backup index: ${error.message}`); } } /** * Create a backup of a file * @param {string} filePath - Path to the file to backup * @param {string} operation - The operation being performed (e.g., 'docstrings', 'refactor') * @returns {string} - The ID of the backup */ createBackup(filePath, operation) { try { const absolutePath = path.resolve(filePath); // Generate a unique ID for this backup const timestamp = new Date().toISOString(); const fileHash = crypto.createHash('md5').update(absolutePath).digest('hex'); const backupId = `${fileHash}-${timestamp}`; // Create the backup file path const backupPath = path.join(this.backupDir, backupId); // Copy the file to the backup location fs.copySync(absolutePath, backupPath); // Update the index if (!this.index.files[absolutePath]) { this.index.files[absolutePath] = []; } // Add to file's history this.index.files[absolutePath].unshift({ id: backupId, timestamp, operation }); // Add to global history this.index.history.unshift({ id: backupId, filePath: absolutePath, timestamp, operation }); // Limit history to last 100 entries if (this.index.history.length > 100) { this.index.history = this.index.history.slice(0, 100); } // Limit per-file history to last 10 entries if (this.index.files[absolutePath].length > 10) { // Get IDs of backups to remove const toRemove = this.index.files[absolutePath].slice(10); // Remove the backup files toRemove.forEach(backup => { try { fs.removeSync(path.join(this.backupDir, backup.id)); } catch (e) { // Ignore errors when removing old backups } }); // Trim the array this.index.files[absolutePath] = this.index.files[absolutePath].slice(0, 10); } // Save the updated index this.saveIndex(); return backupId; } catch (error) { console.error(`Error creating backup: ${error.message}`); return null; } } /** * Restore a file from backup * @param {string} filePath - Path to the file to restore (or null for most recent) * @param {string} backupId - Specific backup ID to restore (or null for most recent) * @returns {boolean} - Whether the restore was successful */ restoreBackup(filePath = null, backupId = null) { try { let backupToRestore; if (filePath) { const absolutePath = path.resolve(filePath); // Check if we have backups for this file if (!this.index.files[absolutePath] || this.index.files[absolutePath].length === 0) { console.error(`No backups found for ${absolutePath}`); return false; } if (backupId) { // Find the specific backup backupToRestore = this.index.files[absolutePath].find(b => b.id === backupId); if (!backupToRestore) { console.error(`Backup ID ${backupId} not found for ${absolutePath}`); return false; } } else { // Use the most recent backup for this file backupToRestore = this.index.files[absolutePath][0]; } } else { // No file specified, use the most recent backup from history if (this.index.history.length === 0) { console.error('No backup history found'); return false; } backupToRestore = this.index.history[0]; filePath = backupToRestore.filePath; } // Check if the backup file exists const backupPath = path.join(this.backupDir, backupToRestore.id); if (!fs.existsSync(backupPath)) { console.error(`Backup file not found: ${backupPath}`); return false; } // Check if the target file exists if (!fs.existsSync(filePath)) { console.error(`Target file no longer exists: ${filePath}`); return false; } // Copy the backup back to the original location fs.copySync(backupPath, filePath); return { filePath, timestamp: backupToRestore.timestamp, operation: backupToRestore.operation }; } catch (error) { console.error(`Error restoring backup: ${error.message}`); return false; } } /** * List available backups for a file * @param {string} filePath - Path to the file * @returns {Array} - Array of backup information */ listBackups(filePath = null) { try { if (filePath) { const absolutePath = path.resolve(filePath); return this.index.files[absolutePath] || []; } else { return this.index.history; } } catch (error) { console.error(`Error listing backups: ${error.message}`); return []; } } } module.exports = new BackupManager();