Spaces:
Paused
Paused
| 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<DatabaseConfig> { | |
| 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, | |
| }, | |
| }; | |
| } | |