import * as p from "@clack/prompts"; import type { DatabaseConfig } from "../config/schema.js"; import { resolveDefaultBackupDir, resolveDefaultEmbeddedPostgresDir, resolvePaperclipInstanceId, } from "../config/home.js"; export async function promptDatabase(current?: DatabaseConfig): Promise { const instanceId = resolvePaperclipInstanceId(); const defaultEmbeddedDir = resolveDefaultEmbeddedPostgresDir(instanceId); const defaultBackupDir = resolveDefaultBackupDir(instanceId); const base: DatabaseConfig = current ?? { mode: "embedded-postgres", embeddedPostgresDataDir: defaultEmbeddedDir, embeddedPostgresPort: 54329, backup: { enabled: true, intervalMinutes: 60, retentionDays: 30, dir: defaultBackupDir, }, }; const mode = await p.select({ message: "Database mode", options: [ { value: "embedded-postgres" as const, label: "Embedded PostgreSQL (managed locally)", hint: "recommended" }, { value: "postgres" as const, label: "PostgreSQL (external server)" }, ], initialValue: base.mode, }); if (p.isCancel(mode)) { p.cancel("Setup cancelled."); process.exit(0); } let connectionString: string | undefined = base.connectionString; let embeddedPostgresDataDir = base.embeddedPostgresDataDir || defaultEmbeddedDir; let embeddedPostgresPort = base.embeddedPostgresPort || 54329; if (mode === "postgres") { const value = await p.text({ message: "PostgreSQL connection string", defaultValue: base.connectionString ?? "", placeholder: "postgres://user:pass@localhost:5432/paperclip", validate: (val) => { if (!val) return "Connection string is required for PostgreSQL mode"; if (!val.startsWith("postgres")) return "Must be a postgres:// or postgresql:// URL"; }, }); if (p.isCancel(value)) { p.cancel("Setup cancelled."); process.exit(0); } connectionString = value; } else { const dataDir = await p.text({ message: "Embedded PostgreSQL data directory", defaultValue: base.embeddedPostgresDataDir || defaultEmbeddedDir, placeholder: defaultEmbeddedDir, }); if (p.isCancel(dataDir)) { p.cancel("Setup cancelled."); process.exit(0); } embeddedPostgresDataDir = dataDir || defaultEmbeddedDir; const portValue = await p.text({ message: "Embedded PostgreSQL port", defaultValue: String(base.embeddedPostgresPort || 54329), placeholder: "54329", validate: (val) => { const n = Number(val); if (!Number.isInteger(n) || n < 1 || n > 65535) return "Port must be an integer between 1 and 65535"; }, }); if (p.isCancel(portValue)) { p.cancel("Setup cancelled."); process.exit(0); } embeddedPostgresPort = Number(portValue || "54329"); connectionString = undefined; } const backupEnabled = await p.confirm({ message: "Enable automatic database backups?", initialValue: base.backup.enabled, }); if (p.isCancel(backupEnabled)) { p.cancel("Setup cancelled."); process.exit(0); } const backupDirInput = await p.text({ message: "Backup directory", defaultValue: base.backup.dir || defaultBackupDir, placeholder: defaultBackupDir, validate: (val) => (!val || val.trim().length === 0 ? "Backup directory is required" : undefined), }); if (p.isCancel(backupDirInput)) { p.cancel("Setup cancelled."); process.exit(0); } const backupIntervalInput = await p.text({ message: "Backup interval (minutes)", defaultValue: String(base.backup.intervalMinutes || 60), placeholder: "60", validate: (val) => { const n = Number(val); if (!Number.isInteger(n) || n < 1) return "Interval must be a positive integer"; if (n > 10080) return "Interval must be 10080 minutes (7 days) or less"; return undefined; }, }); if (p.isCancel(backupIntervalInput)) { p.cancel("Setup cancelled."); process.exit(0); } const backupRetentionInput = await p.text({ message: "Backup retention (days)", defaultValue: String(base.backup.retentionDays || 30), placeholder: "30", validate: (val) => { const n = Number(val); if (!Number.isInteger(n) || n < 1) return "Retention must be a positive integer"; if (n > 3650) return "Retention must be 3650 days or less"; return undefined; }, }); if (p.isCancel(backupRetentionInput)) { p.cancel("Setup cancelled."); process.exit(0); } return { mode, connectionString, embeddedPostgresDataDir, embeddedPostgresPort, backup: { enabled: backupEnabled, intervalMinutes: Number(backupIntervalInput || "60"), retentionDays: Number(backupRetentionInput || "30"), dir: backupDirInput || defaultBackupDir, }, }; }