Replace PGlite with embedded-postgres and add startup banner
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>
This commit is contained in:
115
server/src/startup-banner.ts
Normal file
115
server/src/startup-banner.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
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"));
|
||||
}
|
||||
Reference in New Issue
Block a user