Spaces:
Paused
Paused
| import path from "node:path"; | |
| import * as p from "@clack/prompts"; | |
| import pc from "picocolors"; | |
| import { formatDatabaseBackupResult, runDatabaseBackup } from "@paperclipai/db"; | |
| import { | |
| expandHomePrefix, | |
| resolveDefaultBackupDir, | |
| resolvePaperclipInstanceId, | |
| } from "../config/home.js"; | |
| import { readConfig, resolveConfigPath } from "../config/store.js"; | |
| import { printPaperclipCliBanner } from "../utils/banner.js"; | |
| type DbBackupOptions = { | |
| config?: string; | |
| dir?: string; | |
| retentionDays?: number; | |
| filenamePrefix?: string; | |
| json?: boolean; | |
| }; | |
| function resolveConnectionString(configPath?: string): { value: string; source: string } { | |
| const envUrl = process.env.DATABASE_URL?.trim(); | |
| if (envUrl) return { value: envUrl, source: "DATABASE_URL" }; | |
| const config = readConfig(configPath); | |
| if (config?.database.mode === "postgres" && config.database.connectionString?.trim()) { | |
| return { value: config.database.connectionString.trim(), source: "config.database.connectionString" }; | |
| } | |
| const port = config?.database.embeddedPostgresPort ?? 54329; | |
| return { | |
| value: `postgres://paperclip:paperclip@127.0.0.1:${port}/paperclip`, | |
| source: `embedded-postgres@${port}`, | |
| }; | |
| } | |
| function normalizeRetentionDays(value: number | undefined, fallback: number): number { | |
| const candidate = value ?? fallback; | |
| if (!Number.isInteger(candidate) || candidate < 1) { | |
| throw new Error(`Invalid retention days '${String(candidate)}'. Use a positive integer.`); | |
| } | |
| return candidate; | |
| } | |
| function resolveBackupDir(raw: string): string { | |
| return path.resolve(expandHomePrefix(raw.trim())); | |
| } | |
| export async function dbBackupCommand(opts: DbBackupOptions): Promise<void> { | |
| printPaperclipCliBanner(); | |
| p.intro(pc.bgCyan(pc.black(" paperclip db:backup "))); | |
| const configPath = resolveConfigPath(opts.config); | |
| const config = readConfig(opts.config); | |
| const connection = resolveConnectionString(opts.config); | |
| const defaultDir = resolveDefaultBackupDir(resolvePaperclipInstanceId()); | |
| const configuredDir = opts.dir?.trim() || config?.database.backup.dir || defaultDir; | |
| const backupDir = resolveBackupDir(configuredDir); | |
| const retentionDays = normalizeRetentionDays( | |
| opts.retentionDays, | |
| config?.database.backup.retentionDays ?? 30, | |
| ); | |
| const filenamePrefix = opts.filenamePrefix?.trim() || "paperclip"; | |
| p.log.message(pc.dim(`Config: ${configPath}`)); | |
| p.log.message(pc.dim(`Connection source: ${connection.source}`)); | |
| p.log.message(pc.dim(`Backup dir: ${backupDir}`)); | |
| p.log.message(pc.dim(`Retention: ${retentionDays} day(s)`)); | |
| const spinner = p.spinner(); | |
| spinner.start("Creating database backup..."); | |
| try { | |
| const result = await runDatabaseBackup({ | |
| connectionString: connection.value, | |
| backupDir, | |
| retentionDays, | |
| filenamePrefix, | |
| }); | |
| spinner.stop(`Backup saved: ${formatDatabaseBackupResult(result)}`); | |
| if (opts.json) { | |
| console.log( | |
| JSON.stringify( | |
| { | |
| backupFile: result.backupFile, | |
| sizeBytes: result.sizeBytes, | |
| prunedCount: result.prunedCount, | |
| backupDir, | |
| retentionDays, | |
| connectionSource: connection.source, | |
| }, | |
| null, | |
| 2, | |
| ), | |
| ); | |
| } | |
| p.outro(pc.green("Backup completed.")); | |
| } catch (err) { | |
| spinner.stop(pc.red("Backup failed.")); | |
| throw err; | |
| } | |
| } | |