clone

a personal forge on plain git: static pages, stacked-diff reviews, signed local ci, repo sites and releases

main
forge / demo / serve.ts
serve.ts 2.8K
1// Local preview of the demo forge: static pages from demo/root/.forge/www2// plus live events, the way Caddy and lib/events-server.ts serve them, with3// the same cache headers (bin/forge server caddyfile) and, like Caddy, byte4// ranges, which code search reads code.db with (lib/static.ts). A path with5// no file in www goes to the private server's handler, as Caddy sends it6// there: private repos' pages for their readers, and the 404. Sign-in uses7// FORGE_BASE_URL, http://localhost:<port> unless set.8// node demo/serve.ts [port]9import http from 'node:http';10import path from 'node:path';11import { fileURLToPath } from 'node:url';12import { serveEvents } from '../lib/events-server.ts';13import { privateHandler } from '../lib/private-server.ts';14import { DOWNLOAD, DOWNLOAD_HEADERS, HASHED, resolve, sendFile } from '../lib/static.ts';1516const here = path.dirname(fileURLToPath(import.meta.url));17const state = process.env.FORGE_STATE || path.join(here, 'root/.forge');18const www = path.join(state, 'www');19const port = Number(process.argv[2] || 4817);20const eventsPort = port + 1;21serveEvents(state, eventsPort);22const misses = privateHandler({23 root: path.dirname(state), state, priv: process.env.FORGE_PRIVATE_WWW || path.join(state, 'private', 'www'),24 baseUrl: (process.env.FORGE_BASE_URL || `http://localhost:${port}`).replace(/\/$/, ''),25});2627// Hashed assets never change; pages and the indexes they fetch revalidate,28// and get a 304 when unchanged. Sites keep their own caching.29const cacheControl = (p: string): Record<string, string> =>30 HASHED.test(p) ? { 'Cache-Control': 'public, max-age=31536000, immutable' } : /^\/(sites|previews)\//.test(p) ? {} : { 'Cache-Control': 'no-cache' };31// Release downloads are saved, not opened, on the forge's origin.32const headersFor = (p: string): Record<string, string> => (DOWNLOAD.test(p) ? { ...cacheControl(p), ...DOWNLOAD_HEADERS } : cacheControl(p));3334http.createServer((req, res) => {35 const url = new URL(req.url ?? '/', 'http://x');36 if (url.pathname.startsWith('/events/')) {37 const up = http.request({ host: '127.0.0.1', port: eventsPort, path: url.pathname, method: 'GET', headers: { cookie: req.headers.cookie ?? '' } }, (r) => {38 res.writeHead(r.statusCode ?? 502, r.headers);39 r.pipe(res);40 });41 req.on('close', () => up.destroy());42 up.on('error', () => {43 if (!res.headersSent) res.writeHead(502);44 res.end();45 });46 up.end();47 return;48 }49 const file = resolve(www, url.pathname);50 if (file === 'redirect') res.writeHead(308, { Location: url.pathname + '/' + url.search }).end();51 else if (file && (req.method === 'GET' || req.method === 'HEAD')) sendFile(req, res, file, headersFor(url.pathname));52 else misses(req, res);53}).listen(port, '127.0.0.1', () => console.log(`demo forge on http://localhost:${port}/`));