import { mkdir, readFile, rm, writeFile } from "node:fs/promises" import { dirname, join } from "node:path" import { pathToFileURL } from "node:url" import { docLocales } from "../src/content/types.ts" type StaticPage = { html: string locale: (typeof docLocales)[number] metadata: { description: string; title: string } } type ServerEntry = { getStaticDocRoutes: () => readonly string[] getStaticSearchIndex: (locale: (typeof docLocales)[number]) => unknown renderStaticPage: (pathname: string) => Promise } const docsDirectory = dirname(dirname(import.meta.filename)) const outputDirectory = join(docsDirectory, "dist") const serverDirectory = join(docsDirectory, ".ssr") const serverEntryUrl = pathToFileURL( join(serverDirectory, "entry-server.js") ).href const server = (await import(serverEntryUrl)) as ServerEntry const template = await readFile(join(outputDirectory, "index.html"), "utf8") await Promise.all( server.getStaticDocRoutes().map(async (route) => { const page = await server.renderStaticPage(route) const filename = getRouteFilename(route) await mkdir(dirname(filename), { recursive: true }) await writeFile(filename, createHtml(template, page), "utf8") }) ) const notFoundPage = await server.renderStaticPage("/404") await writeFile( join(outputDirectory, "404.html"), createHtml(template, notFoundPage), "utf8" ) const searchDirectory = join(outputDirectory, "search") await mkdir(searchDirectory, { recursive: true }) await Promise.all( docLocales.map((locale) => writeFile( join(searchDirectory, `${locale}.json`), JSON.stringify(server.getStaticSearchIndex(locale)), "utf8" ) ) ) await rm(serverDirectory, { recursive: true }) function getRouteFilename(route: string) { return route === "/" ? join(outputDirectory, "index.html") : join(outputDirectory, route.slice(1), "index.html") } function createHtml( source: string, page: Awaited> ) { const description = `` return source .replace(//, ``) .replace( /[^<]*<\/title>/, `<title>${escapeText(page.metadata.title)}\n ${description}` ) .replace('
', `
${page.html}
`) } function escapeText(value: string) { return value .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") } function escapeAttribute(value: string) { return escapeText(value).replaceAll('"', """) }