Switch from PGlite (WebAssembly) to embedded-postgres for zero-config local development — provides a real PostgreSQL server with full compatibility. Add startup banner with config summary on server boot. Improve server bootstrap with auto port detection, database creation, and migration on startup. Update DATABASE.md, DEVELOPING.md, and SPEC-implementation.md to reflect the change. Update CLI database check and prompts. Simplify OnboardingWizard database options. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
116 lines
4.3 KiB
TypeScript
116 lines
4.3 KiB
TypeScript
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 "<invalid DATABASE_URL>";
|
|
}
|
|
}
|
|
|
|
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"));
|
|
}
|