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:
Forgotten
2026-02-18 11:45:43 -06:00
parent 0d436911cd
commit cc24722090
18 changed files with 738 additions and 101 deletions

View File

@@ -1,5 +1,6 @@
import express, { Router } from "express";
import path from "node:path";
import fs from "node:fs";
import { fileURLToPath } from "node:url";
import type { Db } from "@paperclip/db";
import { httpLogger, errorHandler } from "./middleware/index.js";
@@ -15,7 +16,9 @@ import { costRoutes } from "./routes/costs.js";
import { activityRoutes } from "./routes/activity.js";
import { dashboardRoutes } from "./routes/dashboard.js";
export function createApp(db: Db, opts: { serveUi: boolean }) {
type UiMode = "none" | "static" | "vite-dev";
export async function createApp(db: Db, opts: { uiMode: UiMode }) {
const app = express();
app.use(express.json());
@@ -36,16 +39,40 @@ export function createApp(db: Db, opts: { serveUi: boolean }) {
api.use(dashboardRoutes(db));
app.use("/api", api);
// SPA fallback for serving the UI build
if (opts.serveUi) {
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const __dirname = path.dirname(fileURLToPath(import.meta.url));
if (opts.uiMode === "static") {
// Serve built UI from ui/dist in production.
const uiDist = path.resolve(__dirname, "../../ui/dist");
app.use(express.static(uiDist));
app.get("*", (_req, res) => {
app.get(/.*/, (_req, res) => {
res.sendFile(path.join(uiDist, "index.html"));
});
}
if (opts.uiMode === "vite-dev") {
const uiRoot = path.resolve(__dirname, "../../ui");
const { createServer: createViteServer } = await import("vite");
const vite = await createViteServer({
root: uiRoot,
appType: "spa",
server: {
middlewareMode: true,
},
});
app.use(vite.middlewares);
app.get(/.*/, async (req, res, next) => {
try {
const templatePath = path.resolve(uiRoot, "index.html");
const template = fs.readFileSync(templatePath, "utf-8");
const html = await vite.transformIndexHtml(req.originalUrl, template);
res.status(200).set({ "Content-Type": "text/html" }).end(html);
} catch (err) {
next(err);
}
});
}
app.use(errorHandler);
return app;