Files
fusion/packages/cli/src/commands/backup.ts
gsxdsm a269f4829f refactor: remove legacy kb compatibility
Drops the .kb/kb.db migration path, legacy backup filename handling, and
backward-compat test suites. Renames internal kbDir identifiers to
fusionDir and hasKbProject/isValidKbProject to their fusion equivalents.

- Remove needsCentralMigration, autoMigrateToCentral, and the
  "needs-migration" FirstRunState; checkAndMigrate and KB_SKIP_MIGRATION
  env var are gone
- Remove LEGACY_BACKUP_DIR and canonicalizeBackupDir; listBackups no
  longer matches kb-* filenames
- Delete backward-compat.test.ts and store-backward-compat.test.ts;
  update remaining tests to new 3-state first-run model

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 23:02:34 -07:00

137 lines
3.9 KiB
TypeScript

import {
BackupManager,
createBackupManager,
runBackupCommand,
TaskStore,
} from "@fusion/core";
import { resolveProject } from "../project-context.js";
async function resolveBackupStore(projectName?: string): Promise<TaskStore> {
try {
return (await resolveProject(projectName)).store;
} catch {
const store = new TaskStore(process.cwd());
await store.init();
return store;
}
}
/**
* Find the project root and create a backup manager.
*/
async function getBackupManager(projectName?: string): Promise<{
manager: BackupManager;
store: TaskStore;
fusionDir: string;
}> {
const store = await resolveBackupStore(projectName);
// Access the private fusionDir property via type assertion
const fusionDir = (store as unknown as { fusionDir: string }).fusionDir;
const settings = await store.getSettings();
const manager = createBackupManager(fusionDir, settings);
return { manager, store, fusionDir };
}
/**
* Create a database backup immediately.
* Usage: fn backup --create
*/
export async function runBackupCreate(projectName?: string): Promise<void> {
const { fusionDir, store } = await getBackupManager(projectName);
const settings = await store.getSettings();
console.log("Creating database backup...");
const result = await runBackupCommand(fusionDir, settings);
if (result.success) {
console.log(result.output);
process.exit(0);
} else {
console.error(result.output);
process.exit(1);
}
}
/**
* List all database backups.
* Usage: fn backup --list
*/
export async function runBackupList(projectName?: string): Promise<void> {
const { manager } = await getBackupManager(projectName);
const backups = await manager.listBackups();
if (backups.length === 0) {
console.log("No backups found.");
return;
}
console.log(`Found ${backups.length} backup(s):\n`);
// Calculate total size
const totalSize = backups.reduce((sum, b) => sum + b.size, 0);
const formattedTotal = formatBytes(totalSize);
console.log("Date Size Filename");
console.log("-".repeat(60));
for (const backup of backups) {
const date = new Date(backup.createdAt).toLocaleString();
const size = formatBytes(backup.size).padEnd(8);
console.log(`${date} ${size} ${backup.filename}`);
}
console.log("-".repeat(60));
console.log(`Total: ${formattedTotal}`);
}
/**
* Restore database from a backup file.
* Usage: fn backup --restore <filename>
*/
export async function runBackupRestore(filename: string, projectName?: string): Promise<void> {
const { manager } = await getBackupManager(projectName);
console.log(`Restoring backup: ${filename}`);
console.log("A pre-restore backup will be created first.\n");
try {
await manager.restoreBackup(filename, { createPreRestoreBackup: true });
console.log(`Successfully restored from ${filename}`);
console.log("Note: The pre-restore backup was saved in case you need to undo this operation.");
} catch (err) {
console.error(`Restore failed: ${(err as Error).message}`);
process.exit(1);
}
}
/**
* Remove old backups exceeding retention limit.
* Usage: fn backup --cleanup
*/
export async function runBackupCleanup(projectName?: string): Promise<void> {
const { manager } = await getBackupManager(projectName);
console.log("Cleaning up old backups...");
const deletedCount = await manager.cleanupOldBackups();
if (deletedCount > 0) {
console.log(`Removed ${deletedCount} old backup(s).`);
} else {
console.log("No backups to clean up (within retention limit).");
}
}
/**
* Format bytes as human-readable string.
*/
function formatBytes(bytes: number): string {
if (bytes === 0) return "0 B";
const k = 1024;
const sizes = ["B", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
}