From 60f56367dfc1e3c5095d034bed4fb4f572e32b55 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Wed, 27 May 2026 23:17:18 +0900 Subject: refactor(packaging): split into dispatch/dispatch-systemd/dispatch-s6, separate dispatch-electron, add bun-based frontend serve PKGBUILD is now a split package producing three .pkg.tar.zst files in one makepkg run: - dispatch base application files (/opt/dispatch), CLI wrappers (dispatch-api, dispatch-frontend), env configs (/etc/dispatch/dispatch-api.conf, /etc/dispatch/dispatch-frontend.conf) - dispatch-systemd systemd user units for dispatch-api + dispatch-frontend (conflicts with dispatch-s6) - dispatch-s6 s6-rc service pipelines (-srv + -log) for both services (conflicts with dispatch-systemd) The Electron desktop wrapper moved to its own self-contained PKGBUILD at packaging/electron/, producing dispatch-electron. It bundles its own copy of the frontend dist + electron entry points under /opt/dispatch-electron and does not depend on the base dispatch package, so users can install it standalone and point VITE_API_URL at any backend at build time. Files under packaging/ are now read directly from ${_projectdir}/packaging/ rather than via source=(); the two s6 service dirs share basenames (run, type) which would collide inside ${srcdir}. New frontend static server: packages/frontend/serve.ts uses Bun's built-in HTTP server (no extra runtime deps) with SPA fallback to index.html and path-traversal protection. PORT/HOST/DIST_DIR overridable via env. Exposed as 'bun run --cwd packages/frontend serve' and via /usr/bin/dispatch-frontend. Build scripts: - bin/build-pkg now prints the freshest built packages - bin/install-pkg installs dispatch + dispatch-systemd by default; accepts package names or --all; searches both packaging/ and packaging/electron/ - bin/build-pkg-electron new, builds the electron split - bin/build-pkg-windows replaces bin/windows-pkg; drops the hard-coded WSL copy step, just prints the win-unpacked path .gitignore extended to cover packaging/*.tar.zst and packaging/electron/{src,pkg,*.pkg.tar.zst,*.tar.zst}. --- packages/frontend/package.json | 1 + packages/frontend/serve.ts | 66 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 packages/frontend/serve.ts (limited to 'packages') diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 453df07..1d11524 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -8,6 +8,7 @@ "dev": "vite", "build": "vite build", "preview": "vite preview", + "serve": "bun serve.ts", "test": "vitest run", "test:watch": "vitest", "typecheck": "svelte-check --tsconfig ./tsconfig.json", diff --git a/packages/frontend/serve.ts b/packages/frontend/serve.ts new file mode 100644 index 0000000..75b0a9f --- /dev/null +++ b/packages/frontend/serve.ts @@ -0,0 +1,66 @@ +// Static file server for the built frontend (packages/frontend/dist). +// Uses Bun's built-in HTTP server — no extra runtime dependencies. +// +// Environment variables: +// PORT — port to listen on (default: 18391) +// HOST — interface to bind to (default: 0.0.0.0) +// DIST_DIR — absolute path to the static assets directory +// (default: /dist) +// +// SPA fallback: requests that don't match a file on disk fall back to +// index.html so client-side routing works. + +import { existsSync, statSync } from "node:fs"; +import { join, normalize, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const moduleDir = (() => { + try { + // @ts-expect-error — Bun provides import.meta.dir + return import.meta.dir as string; + } catch { + return resolve(fileURLToPath(import.meta.url), ".."); + } +})(); + +const port = Number(process.env.PORT) || 18391; +const host = process.env.HOST || "0.0.0.0"; +const distDir = resolve(process.env.DIST_DIR || join(moduleDir, "dist")); + +if (!existsSync(distDir) || !statSync(distDir).isDirectory()) { + console.error(`[dispatch-frontend] dist directory not found: ${distDir}`); + console.error("[dispatch-frontend] Build the frontend first (bun run --cwd packages/frontend build)"); + process.exit(1); +} + +const indexPath = join(distDir, "index.html"); + +function safeResolve(urlPath: string): string | null { + // Normalize and prevent path-traversal outside distDir. + const decoded = decodeURIComponent(urlPath); + const candidate = resolve(distDir, "." + normalize(decoded)); + if (!candidate.startsWith(distDir)) return null; + return candidate; +} + +Bun.serve({ + port, + hostname: host, + async fetch(req) { + const url = new URL(req.url); + let pathname = url.pathname; + if (pathname === "/" || pathname === "") pathname = "/index.html"; + + const resolved = safeResolve(pathname); + if (resolved && existsSync(resolved) && statSync(resolved).isFile()) { + return new Response(Bun.file(resolved)); + } + + // SPA fallback — serve index.html for unknown routes + return new Response(Bun.file(indexPath), { + headers: { "content-type": "text/html; charset=utf-8" }, + }); + }, +}); + +console.log(`[dispatch-frontend] serving ${distDir} on http://${host}:${port}`); -- cgit v1.2.3