#!/usr/bin/env node /** * SQLite Backup CLI Tool * * Command-line interface for the SQLite Backup Library */ const { SQLiteBackup, BackupUtils } = require('../lib/index.js'); const path = require('path'); const fs = require('fs'); function showHelp() { console.log(` SQLite Backup CLI Tool Usage: sqlite-backup [options] Commands: create Create a backup of the specified database list List all backups for the specified database cleanup Clean up old backups restore Restore a backup to a database verify Verify backup integrity help Show this help message Options: --backup-dir Directory to store backups (default: /backups) --filename Custom filename for backup --no-timestamp Don't include timestamp in filename --no-verify Skip backup verification --method Backup method: backup, copy, vacuum (default: backup) --retention-days Number of days to keep backups for cleanup --max-backups Maximum number of backups to keep --target Target path for restore --include-checksums Include checksums when listing backups --verbose Enable verbose output Examples: sqlite-backup create ./data/app.db sqlite-backup create ./data/app.db --backup-dir ./backups --filename custom-backup sqlite-backup list ./data/app.db --include-checksums sqlite-backup cleanup ./data/app.db --retention-days 30 sqlite-backup restore ./backups/backup.db ./data/app.db sqlite-backup verify ./backups/backup.db `); } function parseArgs() { const args = process.argv.slice(2); if (args.length === 0 || args[0] === 'help' || args[0] === '--help' || args[0] === '-h') { showHelp(); process.exit(0); } const command = args[0]; const options = { verbose: false, includeTimestamp: true, verifyIntegrity: true, method: 'backup' }; let positionalArgs = []; for (let i = 1; i < args.length; i++) { const arg = args[i]; if (arg.startsWith('--')) { const key = arg.slice(2); switch (key) { case 'backup-dir': options.backupDirectory = args[++i]; break; case 'filename': options.filename = args[++i]; break; case 'no-timestamp': options.includeTimestamp = false; break; case 'no-verify': options.verifyIntegrity = false; break; case 'method': options.method = args[++i]; break; case 'retention-days': options.retentionDays = parseInt(args[++i]); break; case 'max-backups': options.maxBackups = parseInt(args[++i]); break; case 'target': options.targetPath = args[++i]; break; case 'include-checksums': options.includeChecksums = true; break; case 'verbose': options.verbose = true; break; default: console.error(`Unknown option: --${key}`); process.exit(1); } } else { positionalArgs.push(arg); } } return { command, args: positionalArgs, options }; } async function createBackup(databasePath, options) { try { if (options.verbose) { console.log(`๐Ÿš€ Creating backup for: ${databasePath}`); console.log(`๐Ÿ“ Options:`, options); } else { console.log(`๐Ÿš€ Creating backup for: ${path.basename(databasePath)}`); } const backup = new SQLiteBackup({ databasePath, backupDirectory: options.backupDirectory }); const result = await backup.createBackup({ filename: options.filename, includeTimestamp: options.includeTimestamp, verifyIntegrity: options.verifyIntegrity, method: options.method }); if (result.success) { console.log('โœ… Backup created successfully!'); console.log(`๐Ÿ“ Location: ${result.backupPath}`); console.log(`๐Ÿ“ Size: ${BackupUtils.formatSize(result.size)}`); console.log(`โฑ๏ธ Duration: ${BackupUtils.formatDuration(result.duration)}`); if (result.checksum) { console.log(`๐Ÿ” Checksum: ${result.checksum}`); } } else { console.error('โŒ Backup failed:', result.error); process.exit(1); } } catch (error) { console.error('โŒ Error:', error.message); process.exit(1); } } async function listBackups(databasePath, options) { try { console.log(`๐Ÿ“‹ Listing backups for: ${path.basename(databasePath)}`); const backup = new SQLiteBackup({ databasePath, backupDirectory: options.backupDirectory }); const backups = await backup.listBackups({ includeChecksums: options.includeChecksums }); if (backups.length === 0) { console.log('โ„น๏ธ No backups found'); return; } console.log(`\nFound ${backups.length} backup(s):\n`); backups.forEach((backup, index) => { console.log(`${index + 1}. ${backup.filename}`); console.log(` ๐Ÿ“ Path: ${backup.path}`); console.log(` ๐Ÿ“ Size: ${BackupUtils.formatSize(backup.size)}`); console.log(` ๐Ÿ“… Created: ${backup.created.toISOString()}`); if (options.includeChecksums) { console.log(` ๐Ÿ” Checksum: ${backup.checksum || 'N/A'}`); console.log(` โœ… Valid: ${backup.isValid !== null ? (backup.isValid ? 'Yes' : 'No') : 'Unknown'}`); } console.log(''); }); } catch (error) { console.error('โŒ Error:', error.message); process.exit(1); } } async function cleanupBackups(databasePath, options) { try { if (!options.retentionDays && !options.maxBackups) { console.error('โŒ Either --retention-days or --max-backups must be specified'); process.exit(1); } const retentionText = options.retentionDays ? `older than ${options.retentionDays} days` : `keeping only ${options.maxBackups} most recent`; console.log(`๐Ÿงน Cleaning up backups ${retentionText} for: ${path.basename(databasePath)}`); const backup = new SQLiteBackup({ databasePath, backupDirectory: options.backupDirectory }); const result = await backup.cleanup({ retentionDays: options.retentionDays, maxBackups: options.maxBackups }); if (result.success) { if (result.removed > 0) { console.log(`โœ… Removed ${result.removed} old backup(s)`); if (options.verbose && result.removedFiles.length > 0) { console.log('๐Ÿ“ Removed files:'); result.removedFiles.forEach(file => console.log(` - ${file}`)); } } else { console.log('โ„น๏ธ No old backups to remove'); } console.log(`๐Ÿ“Š Total backups: ${result.totalFiles}, Remaining: ${result.remainingFiles}`); if (result.errors.length > 0) { console.warn('โš ๏ธ Some errors occurred:'); result.errors.forEach(error => console.warn(` ${error}`)); } } else { console.error('โŒ Cleanup failed:', result.error); process.exit(1); } } catch (error) { console.error('โŒ Error:', error.message); process.exit(1); } } async function restoreBackup(backupPath, databasePath, options) { try { console.log(`๐Ÿ”„ Restoring backup: ${path.basename(backupPath)}`); console.log(`๐Ÿ“ Target: ${databasePath}`); const backup = new SQLiteBackup({ databasePath, backupDirectory: options.backupDirectory }); const result = await backup.restore(backupPath, { targetPath: options.targetPath || databasePath, verifyBefore: options.verifyIntegrity, createBackupBeforeRestore: true }); if (result.success) { console.log('โœ… Restore completed successfully!'); console.log(`๐Ÿ“ Restored to: ${result.restoredTo}`); if (result.preRestoreBackup) { console.log(`๐Ÿ’พ Pre-restore backup: ${result.preRestoreBackup}`); } } else { console.error('โŒ Restore failed:', result.error); process.exit(1); } } catch (error) { console.error('โŒ Error:', error.message); process.exit(1); } } async function verifyBackup(backupPath, options) { try { console.log(`๐Ÿ” Verifying backup: ${path.basename(backupPath)}`); const isValid = await BackupUtils.validateDatabase(backupPath); if (isValid) { console.log('โœ… Backup is valid'); } else { console.log('โŒ Backup is corrupted or invalid'); process.exit(1); } if (options.verbose) { const stats = fs.statSync(backupPath); console.log(`๐Ÿ“ Size: ${BackupUtils.formatSize(stats.size)}`); console.log(`๐Ÿ“… Modified: ${stats.mtime.toISOString()}`); } } catch (error) { console.error('โŒ Error:', error.message); process.exit(1); } } async function main() { const { command, args, options } = parseArgs(); try { switch (command) { case 'create': if (args.length !== 1) { console.error('โŒ Usage: sqlite-backup create '); process.exit(1); } await createBackup(args[0], options); break; case 'list': if (args.length !== 1) { console.error('โŒ Usage: sqlite-backup list '); process.exit(1); } await listBackups(args[0], options); break; case 'cleanup': if (args.length !== 1) { console.error('โŒ Usage: sqlite-backup cleanup '); process.exit(1); } await cleanupBackups(args[0], options); break; case 'restore': if (args.length !== 2) { console.error('โŒ Usage: sqlite-backup restore '); process.exit(1); } await restoreBackup(args[0], args[1], options); break; case 'verify': if (args.length !== 1) { console.error('โŒ Usage: sqlite-backup verify '); process.exit(1); } await verifyBackup(args[0], options); break; default: console.error(`โŒ Unknown command: ${command}`); showHelp(); process.exit(1); } } catch (error) { console.error('โŒ Command failed:', error.message); process.exit(1); } } // Handle errors process.on('unhandledRejection', (error) => { console.error('โŒ Unhandled promise rejection:', error); process.exit(1); }); process.on('uncaughtException', (error) => { console.error('โŒ Uncaught exception:', error); process.exit(1); }); // Run the CLI if (require.main === module) { main(); } module.exports = { main };