import { resolve } from "node:path"; type UiMode = "none" | "static" | "vite-dev"; type ExternalPostgresInfo = { mode: "external-postgres"; connectionString: string; }; type EmbeddedPostgresInfo = { mode: "embedded-postgres"; dataDir: string; port: number; }; type StartupBannerOptions = { requestedPort: number; listenPort: number; uiMode: UiMode; db: ExternalPostgresInfo | EmbeddedPostgresInfo; migrationSummary: string; heartbeatSchedulerEnabled: boolean; heartbeatSchedulerIntervalMs: number; }; const ansi = { reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m", cyan: "\x1b[36m", green: "\x1b[32m", yellow: "\x1b[33m", magenta: "\x1b[35m", blue: "\x1b[34m", }; function color(text: string, c: keyof typeof ansi): string { return `${ansi[c]}${text}${ansi.reset}`; } function row(label: string, value: string): string { return `${color(label.padEnd(16), "dim")} ${value}`; } function redactConnectionString(raw: string): string { try { const u = new URL(raw); const user = u.username || "user"; const auth = `${user}:***@`; return `${u.protocol}//${auth}${u.host}${u.pathname}`; } catch { return ""; } } export function printStartupBanner(opts: StartupBannerOptions): void { const baseUrl = `http://localhost:${opts.listenPort}`; const apiUrl = `${baseUrl}/api`; const uiUrl = opts.uiMode === "none" ? "disabled" : baseUrl; const configPath = process.env.PAPERCLIP_CONFIG ? resolve(process.env.PAPERCLIP_CONFIG) : resolve(process.cwd(), ".paperclip/config.json"); const dbMode = opts.db.mode === "embedded-postgres" ? color("embedded-postgres", "green") : color("external-postgres", "yellow"); const uiMode = opts.uiMode === "vite-dev" ? color("vite-dev-middleware", "cyan") : opts.uiMode === "static" ? color("static-ui", "magenta") : color("headless-api", "yellow"); const portValue = opts.requestedPort === opts.listenPort ? `${opts.listenPort}` : `${opts.listenPort} ${color(`(requested ${opts.requestedPort})`, "dim")}`; const dbDetails = opts.db.mode === "embedded-postgres" ? `${opts.db.dataDir} ${color(`(pg:${opts.db.port})`, "dim")}` : redactConnectionString(opts.db.connectionString); const heartbeat = opts.heartbeatSchedulerEnabled ? `enabled ${color(`(${opts.heartbeatSchedulerIntervalMs}ms)`, "dim")}` : color("disabled", "yellow"); const art = [ color("██████╗ █████╗ ██████╗ ███████╗██████╗ ██████╗██╗ ██╗██████╗ ", "cyan"), color("██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔══██╗██╔════╝██║ ██║██╔══██╗", "cyan"), color("██████╔╝███████║██████╔╝█████╗ ██████╔╝██║ ██║ ██║██████╔╝", "cyan"), color("██╔═══╝ ██╔══██║██╔═══╝ ██╔══╝ ██╔══██╗██║ ██║ ██║██╔═══╝ ", "cyan"), color("██║ ██║ ██║██║ ███████╗██║ ██║╚██████╗███████╗██║██║ ", "cyan"), color("╚═╝ ╚═╝ ╚═╝╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝╚══════╝╚═╝╚═╝ ", "cyan"), ]; const lines = [ "", ...art, color(" ───────────────────────────────────────────────────────", "blue"), row("Mode", `${dbMode} | ${uiMode}`), row("Server", portValue), row("API", `${apiUrl} ${color(`(health: ${apiUrl}/health)`, "dim")}`), row("UI", uiUrl), row("Database", dbDetails), row("Migrations", opts.migrationSummary), row("Heartbeat", heartbeat), row("Config", configPath), color(" ───────────────────────────────────────────────────────", "blue"), "", ]; console.log(lines.join("\n")); }