/** * Backup Prune Command * Remove old backups according to retention policies defined in module manifests. */ import { eq } from 'drizzle-orm'; import { getDb } from '../../db/client'; import { modules } from '../../db/schema'; import type { ModuleManifest } from '../../manifest/schema'; import { findBackupEligibleModules } from '../../services/backup-create'; import { effectiveBackupRetention, pruneBackupsForModule, prunesNothing, } from '../../services/backup-retention'; import { loadModuleConfigs } from '../../services/module-config'; import { celiloIntro, celiloOutro } from '../prompts'; import type { CommandResult } from '../types'; export async function handleBackupPrune( args: string[], flags: Record = {}, ): Promise { try { celiloIntro('Prune Old Backups'); const specificModule = args[0]; const dryRun = Boolean(flags['dry-run']); if (dryRun) { console.log('(dry run — no backups will be deleted)\n'); } let totalDeleted = 0; if (specificModule) { // Prune a specific module const db = getDb(); const mod = db.select().from(modules).where(eq(modules.id, specificModule)).get(); if (!mod) { return { success: false, error: `Module not found: ${specificModule}` }; } const manifest = mod.manifestData as unknown as ModuleManifest; const policy = effectiveBackupRetention(manifest, loadModuleConfigs(db, specificModule)); if (prunesNothing(policy)) { console.log( `Module '${specificModule}' has no retention policy — every backup is kept.\n` + `Set one with: celilo module config set ${specificModule} backup_retention_count `, ); return { success: true, message: 'No retention policy' }; } const result = await pruneBackupsForModule(specificModule, policy, dryRun); if (result.deleted > 0) { const verb = dryRun ? 'Would delete' : 'Deleted'; console.log(`${verb} ${result.deleted} backup(s) for ${specificModule}:`); for (const path of result.deletedPaths) { console.log(` - ${path}`); } } else { console.log(`No expired backups for ${specificModule}.`); } totalDeleted = result.deleted; } else { // Prune all modules with retention policies const eligible = findBackupEligibleModules(); for (const { module: mod, manifest, configs } of eligible) { const policy = effectiveBackupRetention(manifest, configs); if (prunesNothing(policy)) continue; const result = await pruneBackupsForModule(mod.id, policy, dryRun); if (result.deleted > 0) { const verb = dryRun ? 'Would delete' : 'Deleted'; console.log(`${verb} ${result.deleted} backup(s) for ${mod.id}`); totalDeleted += result.deleted; } } if (totalDeleted === 0) { console.log('No expired backups found.'); } } const verb = dryRun ? 'would be deleted' : 'deleted'; celiloOutro(totalDeleted > 0 ? `${totalDeleted} backup(s) ${verb}.` : 'No backups to prune.'); return { success: true, message: `Pruned ${totalDeleted} backup(s)` }; } catch (error) { return { success: false, error: `Failed to prune backups: ${error instanceof Error ? error.message : String(error)}`, }; } }