import * as fs from "node:fs"; import * as posix from "node:path/posix"; import { prettify, htmlDocument } from "antihtml"; import { pages } from "./content/pages.js"; import type { Page } from "./content/types.js"; import { resolveRefs } from "./utils/resolve-refs.js"; import { createServer } from "./utils/http-server.js"; function pageToHtml(page: Page) { if (!page.ref.startsWith("/")) { throw new Error(`ref "${page.ref}" for "${page.title}" is not absolute.`); } return htmlDocument( prettify( resolveRefs( page.content, posix.dirname(page.ref), ) ) ) } function assembleResources() { const webDir = "public"; const resources = new Map(); for (const entry of fs.readdirSync(webDir, { recursive: true, withFileTypes: true })) { if (!entry.isFile()) continue; const parentPath = entry.parentPath.replace(/\\/g, "/"); const ref = `${parentPath.slice(webDir.length)}/${entry.name}`; resources.set(ref, `${parentPath}/${entry.name}`); } for (const page of pages) { if (resources.has(page.ref)) { const existing = resources.get(page.ref)!; const other = typeof existing === "string" ? "a static resource" : `"${existing.title}"`; throw new Error(`ref "${page.ref}" is taken up by both "${page.title}" and ${other}`) } resources.set(page.ref, page); } return resources; } function build() { const outDir = "build/web"; const dirsCreated = new Set() for (const [ref, resource] of assembleResources()) { const refDir = `${outDir}${posix.dirname(ref)}`; if (!dirsCreated.has(refDir)) { fs.mkdirSync(refDir, { recursive: true }); } console.log(`writing ${outDir}${ref}`); let content; if (typeof resource === "string") { content = fs.readFileSync(resource); } else { content = pageToHtml(resource); } fs.writeFileSync(`${outDir}${ref}`, content); } } function serve() { const resources = assembleResources(); const server = createServer( (ref) => { const resource = resources.get(ref); if (resource === undefined) return undefined; if (typeof resource === "string") return fs.readFileSync(resource); return Buffer.from(pageToHtml(resource)); } ); server.listen(8080); console.log("Listening on http://localhost:8080"); } function printUsage() { console.log("Usage: cli.js "); console.log(" build - Copy resources and generated pages to build directory."); console.log(" serve - Host website on localhost:8080 for development purposes."); } function main(runtime: string, script: string, args: string[]) { if (!args.length) { printUsage(); process.exitCode = 1; return; } const [command, ...commandArgs] = args; if (command === "build") { build(); } else if (command === "serve") { serve(); } else { console.log(`Error: Unkown sub-command ${command}`); printUsage(); process.exitCode = 1; } } const [runtime, script, ...args] = process.argv; main(runtime, script, args)