From c9d24a4fdd83f58535d2577577de06efeedb3060 Mon Sep 17 00:00:00 2001 From: clover caruso Date: Thu, 14 Aug 2025 20:35:33 -0700 Subject: [PATCH] feat: font subsetting --- flake.nix | 4 + framework/backend/entry-node.ts | 2 +- framework/bundle.ts | 1 - framework/font.ts | 192 ++++++++++++++++++++++++++++++ framework/generate.ts | 28 +++-- framework/lib/assets.ts | 75 ++++++++++-- framework/lib/error.ts | 47 ++++++++ framework/lib/meta.ts | 7 +- framework/lib/mime.txt | 199 ++++++++++++++++---------------- framework/lib/sitegen.ts | 18 +++ framework/lib/sqlite.ts | 9 +- framework/lib/subprocess.ts | 15 +++ framework/watch.ts | 1 + readme.md | 54 ++++++--- run.js | 35 +++--- src/admin.ts | 6 +- src/backend.ts | 14 +++ src/file-viewer/bin/scan3.ts | 18 +-- src/file-viewer/cache.ts | 8 +- src/file-viewer/paths.ts | 12 ++ src/site.ts | 100 ++++++++++++++-- src/source-of-truth.ts | 12 +- src/static/apple-touch-icon.png | Bin 0 -> 73219 bytes src/static/favicon.ico | Bin 0 -> 67758 bytes src/web.dockerfile | 19 +++ 25 files changed, 685 insertions(+), 191 deletions(-) create mode 100644 framework/font.ts create mode 100644 framework/lib/error.ts create mode 100644 framework/lib/subprocess.ts create mode 100644 src/file-viewer/paths.ts create mode 100644 src/static/apple-touch-icon.png create mode 100644 src/static/favicon.ico create mode 100644 src/web.dockerfile diff --git a/flake.nix b/flake.nix index e2f36c1..4b73e17 100644 --- a/flake.nix +++ b/flake.nix @@ -7,8 +7,12 @@ with inputs.nixpkgs.legacyPackages.${system}; { devShells.default = pkgs.mkShell { buildInputs = [ + # clover sitegen v3 pkgs.nodejs_24 # runtime pkgs.deno # formatter + pkgs.python3 # for font subsetting + + # paperclover.net (pkgs.ffmpeg.override { withOpus = true; withSvtav1 = true; diff --git a/framework/backend/entry-node.ts b/framework/backend/entry-node.ts index 4f4a249..3529272 100644 --- a/framework/backend/entry-node.ts +++ b/framework/backend/entry-node.ts @@ -9,7 +9,7 @@ const server = serve({ fetch: app.default.fetch, port: Number(process.env.PORT ?? 3000), }, ({ address, port }) => { - if (address === "::") address = "::1"; + if (address === "::") address = "localhost"; console.info(url.format({ protocol, hostname: address, diff --git a/framework/bundle.ts b/framework/bundle.ts index 3a4f3a1..01c9e02 100644 --- a/framework/bundle.ts +++ b/framework/bundle.ts @@ -94,7 +94,6 @@ export async function bundleClientJavaScript( const chunk = route.startsWith("/js/c."); if (!chunk) { const key = hot.getScriptId(toAbs(UNWRAP(entryPoint))); - console.log(route, key); route = "/js/" + key.replace(/\.client\.tsx?/, ".js"); scripts[key] = text; } diff --git a/framework/font.ts b/framework/font.ts new file mode 100644 index 0000000..7645a05 --- /dev/null +++ b/framework/font.ts @@ -0,0 +1,192 @@ +export async function buildFonts(fonts: sg.Font[]) { + if (fonts.length === 0) return; + + const temp = path.resolve(".clover/font"); + const venv = path.join(temp, "venv"); + const bin = path.join(venv, "bin"); + + if (!fs.existsSync(venv)) { + await subprocess.exec("python", ["-m", "venv", venv]); + } + if (!fs.existsSync(path.join(bin, "pyftsubset"))) { + await subprocess.exec(path.join(bin, "pip"), [ + "install", + "fonttools==4.38.0", + "brotli==1.0.7", + ]); + } + + const instances = new async.OnceMap(); + const subsets = new async.OnceMap(); + async function makeInstance(input: string, vars: sg.FontVars) { + const args = Object.entries(vars).map((x) => { + const lower = x[0].toLowerCase(); + const k = lower === "wght" || lower === "slnt" ? lower : x[0]; + const v = Array.isArray(x[1]) ? x[1].join(":") : x[1]; + return `${k}=${v}`; + }).sort(); + const hash = crypto + .createHash("sha256") + .update(`${input}${args.join(" ")}`) + .digest("hex") + .slice(0, 16) + .toLowerCase(); + const outfile = path.join(temp, `${hash}${path.extname(input)}`); + await instances.get(outfile, async () => { + await fs.rm(outfile, { force: true }); + await subprocess.exec(path.join(bin, "fonttools"), [ + "varLib.instancer", + input, + ...args, + `--output=${outfile}`, + ]); + ASSERT(fs.existsSync(outfile)); + }); + return outfile; + } + + await Promise.all(fonts.map((font) => + incr.work(async (io, font) => { + const baseFile = await fetchFont(font.name, font.sources); + await Promise.all(font.subsets.map(async (subset) => { + let file = baseFile; + if (subset.vars) { + file = await makeInstance(baseFile, subset.vars); + } + const unicodes = fontRangesToString( + Array.isArray(subset.unicodes) ? subset.unicodes : [subset.unicodes], + ); + const hash = crypto + .createHash("sha256") + .update(`${file}${unicodes}`) + .digest("hex") + .slice(0, 16) + .toLowerCase(); + const woff = path.join(temp, hash + ".woff2"); + await subprocess.exec(path.join(bin, "pyftsubset"), [ + file, + "--flavor=woff2", + `--output-file=${woff}`, + ...subset.layoutFeatures + ? [`--layout-features=${subset.layoutFeatures.join(",")}`] + : [], + `--unicodes=${unicodes}`, + ]); + await io.writeAsset({ + pathname: subset.asset.replace("[ext]", "woff2"), + buffer: await fs.readFile(woff), + }); + })); + }, font) + )); +} + +export async function fetchFont(name: string, sources: string[]) { + const errs = []; + + for (const source of sources) { + const cacheName = path.join("", name + path.extname(source)); + if (fs.existsSync(cacheName)) return cacheName; + + if (source.startsWith("https://")) { + const response = await fetch(source); + if (response.ok) { + await fs.writeMkdir( + cacheName, + Buffer.from(await response.arrayBuffer()), + ); + } else { + errs.push( + new Error( + `Fetching from ${source} failed: ${response.status} ${response.statusText}`, + ), + ); + continue; + } + } + + if (path.isAbsolute(source)) { + if (fs.existsSync(source)) return source; + errs.push(new Error(`Font not available at absolute path ${source}`)); + } else if (!source.includes("/") && !source.includes("\\")) { + const home = os.homedir(); + const bases = process.platform === "win32" + ? [ + "\\Windows\\Fonts", + path.win32.join(home, "AppData\\Local\\Microsoft\\Windows\\Fonts"), + ] + : process.platform === "darwin" + ? [ + "/Library/Fonts", + path.posix.join(home, "Library/Fonts"), + ] + : [ + "/usr/share/fonts", + "/usr/local/share/fonts", + path.posix.join(home, ".local/share/fonts"), + ]; + for (const base of bases) { + const found = fs.readDirRecOptionalSync(base) + .find((file) => path.basename(file) === source); + if (found) { + return path.join(base, found); + } + } + errs.push( + new Error( + `Font file ${source} not found in the following directories: ${ + bases.join(", ") + }`, + ), + ); + } + } + + throw new AggregateError(errs, `Failed to fetch the ${name} font`); +} + +function fontRangesToString(ranges: sg.FontRange[]): string { + const segments: Array<{ start: number; end: number }> = []; + + ranges.forEach((range) => { + if (typeof range === "string") { + for (let i = 0; i < range.length; i++) { + const cp = range.codePointAt(i) || 0; + segments.push({ start: cp, end: cp }); + } + } else if (typeof range === "number") { + segments.push({ start: range, end: range }); + } else { + segments.push({ start: range.start, end: range.end }); + } + }); + + segments.sort((a, b) => a.start - b.start); + + const merged: Array<{ start: number; end: number }> = []; + for (const seg of segments) { + const last = merged[merged.length - 1]; + if (last && seg.start <= last.end + 1) { + last.end = Math.max(last.end, seg.end); + } else { + merged.push({ ...seg }); + } + } + + return merged.map(({ start, end }) => + start === end + ? `U+${start.toString(16).toUpperCase().padStart(4, "0")}` + : `U+${start.toString(16).toUpperCase().padStart(4, "0")}-${ + end.toString(16).toUpperCase().padStart(4, "0") + }` + ).join(","); +} + +import * as fs from "#sitegen/fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import * as sg from "#sitegen"; +import * as subprocess from "#sitegen/subprocess"; +import * as incr from "./incremental.ts"; +import * as crypto from "node:crypto"; +import * as async from "#sitegen/async"; diff --git a/framework/generate.ts b/framework/generate.ts index 31ca14c..90fb65a 100644 --- a/framework/generate.ts +++ b/framework/generate.ts @@ -22,6 +22,9 @@ export async function generate() { // TODO: loadMarkoCache + // -- start font work -- + const builtFonts = fonts.buildFonts(siteConfig.fonts); + // -- perform build-time rendering -- const builtPages = pages.map((item) => incr.work(preparePage, item)); const builtViews = views.map((item) => incr.work(prepareView, item)); @@ -70,7 +73,12 @@ export async function generate() { assembleAndWritePage(page, styleMap, scriptMap) ); - await Promise.all([builtBackend, builtStaticFiles, ...pAssemblePages]); + await Promise.all([ + builtBackend, + builtStaticFiles, + ...pAssemblePages, + builtFonts, + ]); } export async function readManifest(io: Io) { @@ -80,6 +88,7 @@ export async function readManifest(io: Io) { root: toRel(section.root), })), backends: cfg.backends.map(toRel), + fonts: cfg.fonts, }; } @@ -324,14 +333,17 @@ export async function assembleAndWritePage( export type PageOrView = PreparedPage | PreparedView; +import * as path from "node:path"; + +import * as fs from "#sitegen/fs"; +import * as meta from "#sitegen/meta"; +import * as render from "#engine/render"; import * as sg from "#sitegen"; -import * as incr from "./incremental.ts"; -import { Io } from "./incremental.ts"; +import type { FileItem } from "#sitegen"; + import * as bundle from "./bundle.ts"; import * as css from "./css.ts"; -import * as render from "#engine/render"; +import * as fonts from "./font.ts"; import * as hot from "./hot.ts"; -import * as fs from "#sitegen/fs"; -import type { FileItem } from "#sitegen"; -import * as path from "node:path"; -import * as meta from "#sitegen/meta"; +import * as incr from "./incremental.ts"; +import { Io } from "./incremental.ts"; diff --git a/framework/lib/assets.ts b/framework/lib/assets.ts index ac77d49..fa5338d 100644 --- a/framework/lib/assets.ts +++ b/framework/lib/assets.ts @@ -2,6 +2,7 @@ // This module implements decoding and serving of the asset blobs, // but also implements patching of dynamic assets. The `Manifest` // is generated by `incremental.ts` +const debug = console.scoped("assets"); const root = import.meta.dirname; let current: Loaded | null = null; @@ -36,6 +37,7 @@ interface Loaded { export interface DynamicEntry extends AssetBase { buffer: Buffer; } +type CacheMode = "auto" | "long-term" | "temporary" | "no-cache"; export async function reload() { const map = await fs.readJson(path.join(root, "asset.json")); @@ -59,7 +61,7 @@ export async function reload() { export async function middleware(c: Context, next: Next) { if (!current) current = await reload(); const asset = current.map[c.req.path]; - if (asset) return assetInner(c, asset, 200); + if (asset) return assetInner(c, c.req.path, asset, 200); return next(); } @@ -68,16 +70,27 @@ export async function notFound(c: Context) { let pathname = c.req.path; do { const asset = current.map[pathname + "/404"]; - if (asset) return assetInner(c, asset, 404); + if (asset) return assetInner(c, pathname + "/404", asset, 404); pathname = pathname.slice(0, pathname.lastIndexOf("/")); } while (pathname); const asset = current.map["/404"]; - if (asset) return assetInner(c, asset, 404); + if (asset) return assetInner(c, "/404", asset, 404); return c.text("the 'Not Found' page was not found", 404); } -export async function serveAsset(c: Context, id: Key, status: StatusCode) { - return assetInner(c, (current ?? (await reload())).map[id], status); +export async function serveAsset( + c: Context, + id: Key, + status: StatusCode = 200, + cache: CacheMode = 'auto', +) { + return assetInner( + c, + id, + (current ?? (await reload())).map[id], + status, + cache, + ); } /** @deprecated */ @@ -89,21 +102,43 @@ export function etagMatches(etag: string, ifNoneMatch: string) { return ifNoneMatch === etag || ifNoneMatch.split(/,\s*/).indexOf(etag) > -1; } -function assetInner(c: Context, asset: Manifest[Key], status: StatusCode) { +function assetInner( + c: Context, + key: string, + asset: Manifest[Key], + status: StatusCode, + cache: CacheMode = "auto", +) { ASSERT(current); if (asset.type === 0) { - return respondWithBufferAndViews(c, current.static, asset, status); + return respondWithBufferAndViews( + c, + key, + current.static, + asset, + status, + cache, + ); } else { const entry = UNWRAP(current.dynamic.get(asset.id)); - return respondWithBufferAndViews(c, entry.buffer, entry, status); + return respondWithBufferAndViews( + c, + key, + entry.buffer, + entry, + status, + cache, + ); } } function respondWithBufferAndViews( c: Context, + key: string, buffer: Buffer, asset: AssetBase, status: StatusCode, + cache: CacheMode, ) { const ifNoneMatch = c.req.header("If-None-Match"); if (ifNoneMatch) { @@ -136,6 +171,29 @@ function respondWithBufferAndViews( } else { body = buffer.subarray(...asset.raw); } + debug( + `${key} encoding=${headers["Content-Encoding"] ?? "raw"} status=${status}`, + ); + if (!Object.keys(headers).some((x) => x.toLowerCase() === "cache-control")) { + if (cache === "auto") { + if (status < 200 || status >= 300) { + cache = 'no-cache'; + } else if (headers['content-type']?.includes('text/html')) { + cache = 'temporary'; + } else { + cache = 'long-term'; + } + } + if (cache === "no-cache") { + headers["Cache-Control"] = "no-store"; + } else if (cache === "long-term") { + headers["Cache-Control"] = + "public, max-age=7200, stale-while-revalidate=7200, immutable"; + } else if (cache === "temporary") { + headers["Cache-Control"] = + "public, max-age=7200, stale-while-revalidate=7200"; + } + } return (c.res = new Response(body, { headers, status })); } @@ -232,3 +290,4 @@ import type { AssetKey as Key } from "../../.clover/ts/asset.d.ts"; import * as crypto from "node:crypto"; import * as zlib from "node:zlib"; import * as util from "node:util"; +import * as console from "@paperclover/console"; diff --git a/framework/lib/error.ts b/framework/lib/error.ts new file mode 100644 index 0000000..8162491 --- /dev/null +++ b/framework/lib/error.ts @@ -0,0 +1,47 @@ +declare global { + interface Error { + /* Extra fields on errors are extremely helpful for debugging. */ + [metadata: string]: unknown; + } +} + +/** Retrieve an error message from any value */ +export function message(error: unknown): string { + const message = (error as { message: unknown })?.message ?? error; + try { + return typeof message === "string" ? message : JSON.stringify(message); + } catch {} + try { + return String(message); + } catch {} + return `Could not stringify error message ${typeof message}`; +} + +/** Retrieve an error-like object from any value. Useful to check fields. */ +export function obj(error: unknown): { message: string } & Partial { + if (error instanceof Error) return error; + return { + message: message(error), + ...(typeof error === "object" && error), + }; +} + +/* Node.js error codes are strings */ +export function code(error: unknown): NodeErrorCode | null { + const code = (error as { code: unknown })?.code; + return typeof code === "string" ? code : null; +} + +/** Attach extra fields and throw */ +export function rethrowWithMetadata( + err: unknown, + meta: Record, +): never { + const error = err && typeof err === "object" ? err : new Error(message(err)); // no stack trace :/ + Object.assign(error, meta); + throw error; +} + +export type NodeErrorCode = + | keyof typeof import("node:os").constants.errno + | (string & {}); diff --git a/framework/lib/meta.ts b/framework/lib/meta.ts index 511b2a9..0e2c272 100644 --- a/framework/lib/meta.ts +++ b/framework/lib/meta.ts @@ -4,6 +4,9 @@ export interface Meta { openGraph?: OpenGraph; alternates?: Alternates; } +export interface Template extends Omit { + titleTemplate?: (title: string) => string, +} export interface OpenGraph { title?: string; description?: string | undefined; @@ -19,6 +22,6 @@ export interface AlternateType { title: string; } export function renderMeta({ title }: Meta): string { - return `${esc(title)}`; + return `${esc(title)}`; } -import { escapeHtml as esc } from "#engine/render"; +import { escapeHtmlContent as esc } from "#engine/render"; diff --git a/framework/lib/mime.txt b/framework/lib/mime.txt index 7cf5993..ad6220b 100644 --- a/framework/lib/mime.txt +++ b/framework/lib/mime.txt @@ -1,99 +1,100 @@ -# media types -# https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types -.aac audio/x-aac -.aif audio/x-aiff -.aifc audio/x-aiff -.aiff audio/x-aiff -.asm text/x-asm -.avi video/x-msvideo -.bat application/x-msdownload -.c text/x-c -.chat text/x-clover-chatlog -.class application/java-vm -.cmd application/x-msdownload -.com application/x-msdownload -.conf text/plain -.cpp text/x-c -.css text/css -.csv text/csv -.cxx text/x-c -.def text/plain -.diff text/plain -.dll application/x-msdownload -.dmg application/octet-stream -.doc application/msword -.docx application/vnd.openxmlformats-officedocument.wordprocessingml.document -.epub application/epub+zip -.exe application/x-msdownload -.flv video/x-flv -.fbx application/fbx -.gz application/x-gzip -.h text/x-c -.h264 video/h264 -.hh text/x-c -.htm text/html;charset=utf-8 -.html text/html;charset=utf-8 -.ico image/x-icon -.ics text/calendar -.in text/plain -.jar application/java-archive -.java text/x-java-source -.jpeg image/jpeg -.jpg image/jpeg -.jpgv video/jpeg -.jxl image/jxl -.js application/javascript -.json application/json -.latex application/x-latex -.list text/plain -.log text/plain -.m4a audio/mp4 -.man text/troff -.mid audio/midi -.midi audio/midi -.mov video/quicktime -.mp3 audio/mpeg -.mp4 video/mp4 -.msh model/mesh -.msi application/x-msdownload -.obj application/octet-stream -.ogg audio/ogg -.otf application/x-font-otf -.pdf application/pdf -.png image/png -.ppt application/vnd.ms-powerpoint -.pptx application/vnd.openxmlformats-officedocument.presentationml.presentation -.psd image/vnd.adobe.photoshop -.py text/x-python -.rar application/x-rar-compressed -.rss application/rss+xml -.rtf application/rtf -.rtx text/richtext -.s text/x-asm -.pem application/x-pem-file" -.ser application/java-serialized-object -.sh application/x-sh -.sig application/pgp-signature -.silo model/mesh -.svg image/svg+xml -.t text/troff -.tar application/x-tar -.text text/plain -.tgz application/x-gzip -.tif image/tiff -.tiff image/tiff -.torrent application/x-bittorrent -.ttc application/x-font-ttf -.ttf application/x-font-ttf -.txt text/plain -.urls text/uri-list -.v text/x-v -.wav audio/x-wav -.wmv video/x-ms-wmv -.xls application/vnd.ms-excel -.xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet -.xml application/xml -.xps application/vnd.ms-xpsdocument - -# special cased based on file name -rss.xml application/rss+xml +# media types +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types +.aac audio/x-aac +.aif audio/x-aiff +.aifc audio/x-aiff +.aiff audio/x-aiff +.asm text/x-asm +.avi video/x-msvideo +.bat application/x-msdownload +.c text/x-c +.chat text/x-clover-chatlog +.class application/java-vm +.cmd application/x-msdownload +.com application/x-msdownload +.conf text/plain +.cpp text/x-c +.css text/css +.csv text/csv +.cxx text/x-c +.def text/plain +.diff text/plain +.dll application/x-msdownload +.dmg application/octet-stream +.doc application/msword +.docx application/vnd.openxmlformats-officedocument.wordprocessingml.document +.epub application/epub+zip +.exe application/x-msdownload +.flv video/x-flv +.fbx application/fbx +.gz application/x-gzip +.h text/x-c +.h264 video/h264 +.hh text/x-c +.htm text/html;charset=utf-8 +.html text/html;charset=utf-8 +.ico image/x-icon +.ics text/calendar +.in text/plain +.jar application/java-archive +.java text/x-java-source +.jpeg image/jpeg +.jpg image/jpeg +.jpgv video/jpeg +.jxl image/jxl +.js application/javascript +.json application/json +.latex application/x-latex +.list text/plain +.log text/plain +.m4a audio/mp4 +.man text/troff +.mid audio/midi +.midi audio/midi +.mov video/quicktime +.mp3 audio/mpeg +.mp4 video/mp4 +.msh model/mesh +.msi application/x-msdownload +.obj application/octet-stream +.ogg audio/ogg +.otf application/x-font-otf +.pdf application/pdf +.png image/png +.ppt application/vnd.ms-powerpoint +.pptx application/vnd.openxmlformats-officedocument.presentationml.presentation +.psd image/vnd.adobe.photoshop +.py text/x-python +.rar application/x-rar-compressed +.rss application/rss+xml +.rtf application/rtf +.rtx text/richtext +.s text/x-asm +.pem application/x-pem-file" +.ser application/java-serialized-object +.sh application/x-sh +.sig application/pgp-signature +.silo model/mesh +.svg image/svg+xml +.t text/troff +.tar application/x-tar +.text text/plain +.tgz application/x-gzip +.tif image/tiff +.tiff image/tiff +.torrent application/x-bittorrent +.ttc application/x-font-ttf +.ttf application/x-font-ttf +.txt text/plain +.urls text/uri-list +.v text/x-v +.wav audio/x-wav +.wmv video/x-ms-wmv +.woff2 font/woff2 +.xls application/vnd.ms-excel +.xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet +.xml application/xml +.xps application/vnd.ms-xpsdocument + +# special cased based on file name +rss.xml application/rss+xml diff --git a/framework/lib/sitegen.ts b/framework/lib/sitegen.ts index af99dca..e51e5fc 100644 --- a/framework/lib/sitegen.ts +++ b/framework/lib/sitegen.ts @@ -31,6 +31,24 @@ export interface FileItem { export interface Section { root: string; } +export interface Font { + name: string; + /** + * Specify either font name, file path, or URL to fetch it. + * Private fonts do not have a URL and will fail to build if missing. + */ + sources: string[]; + subsets: Array<{ + vars?: Record; + layoutFeatures?: string[]; + unicodes: FontRange | FontRange[]; + /** Include [ext] to autofill 'woff2' */ + asset: string; + }>; +} +export type FontVars = Record; +export type FontVariableAxis = number | [min: number, max: number]; +export type FontRange = string | number | { start: number, end: number }; export const userData = render.userData(() => { throw new Error("This function can only be used in a page (static or view)"); diff --git a/framework/lib/sqlite.ts b/framework/lib/sqlite.ts index 477ab9e..9b49b2f 100644 --- a/framework/lib/sqlite.ts +++ b/framework/lib/sqlite.ts @@ -11,9 +11,9 @@ export function getDb(file: string) { let db = map.get(file); if (db) return db; const fileWithExt = file.includes(".") ? file : file + ".sqlite"; - db = new WrappedDatabase( - path.join(process.env.CLOVER_DB ?? ".clover", fileWithExt), - ); + const dir = process.env.CLOVER_DB ?? ".clover"; + fs.mkdirSync(dir); + db = new WrappedDatabase(path.join(dir, fileWithExt)); map.set(file, db); return db; } @@ -21,7 +21,7 @@ export function getDb(file: string) { export class WrappedDatabase { file: string; node: DatabaseSync; - stmts: Stmt[]; + stmts: Stmt[] = []; stmtTableMigrate: WeakRef | null = null; constructor(file: string) { @@ -153,3 +153,4 @@ export class Stmt { import { DatabaseSync, StatementSync } from "node:sqlite"; import * as path from "node:path"; +import * as fs from "#sitegen/fs"; diff --git a/framework/lib/subprocess.ts b/framework/lib/subprocess.ts new file mode 100644 index 0000000..2d183de --- /dev/null +++ b/framework/lib/subprocess.ts @@ -0,0 +1,15 @@ +const execFileRaw = util.promisify(child_process.execFile); +export const exec: typeof execFileRaw = (( + ...args: Parameters +) => + execFileRaw(...args).catch((e: any) => { + if (e?.message?.startsWith?.("Command failed")) { + if (e.code > 2 ** 31) e.code |= 0; + const code = e.signal ? `signal ${e.signal}` : `code ${e.code}`; + e.message = `${e.cmd.split(" ")[0]} failed with ${code}`; + } + throw e; + })) as any; + +import * as util from 'node:util'; +import * as child_process from 'node:child_process'; diff --git a/framework/watch.ts b/framework/watch.ts index 9fe2157..453dbce 100644 --- a/framework/watch.ts +++ b/framework/watch.ts @@ -25,6 +25,7 @@ function serve() { "--development", ], { stdio: "inherit", + execArgv: ['--no-warnings'], }); subprocess.on("close", onSubprocessClose); } diff --git a/readme.md b/readme.md index cecf111..8301190 100644 --- a/readme.md +++ b/readme.md @@ -11,23 +11,21 @@ that assist building websites. these tools power . - Different languages can be used at the same time. Supports `async function` components, ``, and custom extensions. - **Incremental static site generator and build system.** - - Build entire production site at start, incremental updates when pages - change; Build system state survives coding sessions. - - The only difference in development and production mode is hidden source-maps - and stripped `console.debug` calls. The site you see locally is the same - site you see deployed. + - Build both development and production sites on startup start. Incremental + generator rebuilds changed pages; Build system state survives coding sessions. + - Multiple backend support. - (TODO) Tests, Lints, and Type-checking is run alongside, and only re-runs checks when the files change. For example, changing a component re-tests only pages that use that component and re-lints only the changed file. - **Integrated libraries for building complex, content heavy web sites.** - Static asset serving with ETag and build-time compression. - - Dynamicly rendered pages with static client. (`#import "#sitegen/view"`) + - Dynamicly rendered pages with static client. (`import "#sitegen/view"`) - Databases with a typed SQLite wrapper. (`import "#sitegen/sqlite"`) - TODO: Meta and Open Graph generation. (`export const meta`) - - TODO: Font subsetting tools to reduce bytes downloaded by fonts. -- **Built on the battle-tested Node.js runtime.** + - Font subsetting to reduce page bandwidth. +- **Built on the stable and battle-tested Node.js runtime.** -None of these tools are complex or revolutionary. Rather, this project is the +none of these tools are complex or revolutionary. rather, this project is the sum of many years of experience on managing content heavy websites, and an example on how other over-complicate other frameworks. @@ -35,9 +33,11 @@ example on how other over-complicate other frameworks. Included is `src`, which contains `paperclover.net`. Website highlights: +- TODO: flashy homepage. - [Question/Answer board, custom markdown parser and components][q+a]. - [File viewer with fast ui/ux + optimized media streaming][file]. - [Personal, friends-only blog with password protection][friends]. +- TODO: digital garden styled blog. [q+a]: https://paperclover.net/q+a [file]: https://paperclover.net/file @@ -51,15 +51,21 @@ minimum system requirements: - random access memory. - windows 7 or later, macos, or other operating system. +required software: + +- node.js v24 +- python v3 + my development machine, for example, is Dell Inspiron 7348 with Core i7 npm install - # production generation + # build site using 'run.js' to enable runtime plugins node run generate - node .clover/o/backend + # the built site runs in regular node.js + node --enable-source-maps .clover/o/backend - # "development" watch mode + # watch-rebuild mode node run watch for unix systems, the provided `flake.nix` can be used with `nix develop` to @@ -67,7 +73,7 @@ open a shell with all needed system dependencies. ## Deployment -There are two primary server components to be deployed: the web server and the +there are two primary server components to be deployed: the web server and the sourth of truth server. The latter is a singleton that runs on Clover's NAS, which holds the full contents of the file storage. The web server pulls data from the source of truth and renders web pages, and can be duplicated to @@ -79,7 +85,8 @@ Deployment of the source of truth can be done with Docker Compose: backend: container_name: backend build: - # this uses loopback to hit the self-hosted git server + # this uses loopback to hit the self-hosted git server, + # docker will cache the image to not re-fetch on reboot. context: http://127.0.0.1:3000/clo/sitegen.git dockerfile: src/source-of-truth.dockerfile environment: @@ -96,10 +103,23 @@ Deployment of the source of truth can be done with Docker Compose: - /mnt/storage1/clover/Documents/Config/paperclover:/data - /mnt/storage1/clover/Published:/published -Due to caching, one may need to manually purge images via -`docker image rm ix-clover-backend -f` when an update is desired +Due to caching, images may need to be purged via `docker image rm {image} -f` +when an update is desired. Some docker GUIs support force pulls, some are buggy. -TODO: deployment instructions for a web node +The web server performs rendering. A `Dockerfile` for it is present in +`src/web.dockerfile` but it is currently unused. Deployments are done by +building the project locally, and then using `rsync` to copy files. + + node run generate + rsync .clover/o "$REMOTE_USER:~/paperclover" --exclude=.clover --exclude=.env \ + --delete-after --progress --human-readable + ssh "$REMOTE_USER" /bin/bash << EOF + set -e + cd ~/paperclover + npm ci + pm2 restart site + echo "-> https://paperclover.net" + EOF ## Contributions diff --git a/run.js b/run.js index ea0c1dd..8e46ce1 100644 --- a/run.js +++ b/run.js @@ -3,7 +3,6 @@ import * as util from "node:util"; import * as zlib from "node:zlib"; import * as url from "node:url"; -import * as module from "node:module"; import process from "node:process"; // Disable experimental warnings (Type Stripping, etc) @@ -28,8 +27,8 @@ try { const brand = process.versions.bun ? `bun ${process.versions.bun}` : process.versions.deno - ? `deno ${process.versions.deno}` - : null; + ? `deno ${process.versions.deno}` + : null; const brandText = brand ? ` (${brand})` : ""; globalThis.console.error( `sitegen depends on a node.js v24. your runtime is missing features.\n` + @@ -75,16 +74,26 @@ if (process.argv[1].startsWith(import.meta.filename.slice(0, -".js".length))) { else if (mod.default?.fetch) { const protocol = "http"; const { serve } = hot.load("@hono/node-server"); - serve({ - fetch: mod.default.fetch, - }, ({ address, port }) => { - if (address === "::") address = "::1"; - console.info(url.format({ - protocol, - hostname: address, - port, - })); - }); + serve( + { + fetch: mod.default.fetch, + }, + ({ address, port }) => { + if (address === "::") address = "::1"; + console.info( + url.format({ + protocol, + hostname: address, + port, + }), + ); + }, + ); + } else { + console.warn( + hot.load("path").relative('.', found), + 'does not export a "main" function', + ); } } catch (e) { console.error(util.inspect(e)); diff --git a/src/admin.ts b/src/admin.ts index dd90fda..f9ee787 100644 --- a/src/admin.ts +++ b/src/admin.ts @@ -3,7 +3,11 @@ const cookieAge = 60 * 60 * 24 * 365; // 1 year let lastKnownToken: string | null = null; function compareToken(token: string) { if (token === lastKnownToken) return true; - lastKnownToken = fs.readFileSync(".clover/admin-token.txt", "utf8").trim(); + try { + lastKnownToken = fs.readFileSync(".clover/admin-token.txt", "utf8").trim(); + } catch { + return false; + } return token === lastKnownToken; } diff --git a/src/backend.ts b/src/backend.ts index 48b5a11..74236a9 100644 --- a/src/backend.ts +++ b/src/backend.ts @@ -15,6 +15,20 @@ app.route("", require("./file-viewer/backend.tsx").app); // Asset middleware has least precedence app.use(assets.middleware); +// Asset Aliases +app.get( + "/apple-touch-icon-precomposed.png", + (c) => assets.serveAsset(c, "/apple-touch-icon.png"), +); +app.get( + "/apple-touch-icon-57x57.png", + (c) => assets.serveAsset(c, "/apple-touch-icon.png"), +); +app.get( + "/apple-touch-icon-57x57-precomposed.png", + (c) => assets.serveAsset(c, "/apple-touch-icon.png"), +); + // Handlers app.notFound(assets.notFound); diff --git a/src/file-viewer/bin/scan3.ts b/src/file-viewer/bin/scan3.ts index 261100e..be408d0 100644 --- a/src/file-viewer/bin/scan3.ts +++ b/src/file-viewer/bin/scan3.ts @@ -10,7 +10,6 @@ // This is the third iteration of the scanner, hence its name "scan3"; // Remember that any software you want to be maintainable and high // quality cannot be written with AI. -const root = path.resolve("/Volumes/clover/Published"); const workDir = path.resolve(".clover/derived"); const sotToken = UNWRAP(process.env.CLOVER_SOT_KEY); @@ -404,18 +403,6 @@ interface Process { undo?(mediaFile: MediaFile): Promise; } -const execFileRaw = util.promisify(child_process.execFile); -const execFile: typeof execFileRaw = (( - ...args: Parameters -) => - execFileRaw(...args).catch((e: any) => { - if (e?.message?.startsWith?.("Command failed")) { - if (e.code > 2 ** 31) e.code |= 0; - const code = e.signal ? `signal ${e.signal}` : `code ${e.code}`; - e.message = `${e.cmd.split(" ")[0]} failed with ${code}`; - } - throw e; - })) as any; const ffprobeBin = testProgram("ffprobe", "--help"); const ffmpegBin = testProgram("ffmpeg", "--help"); @@ -426,7 +413,7 @@ const procDuration: Process = { enable: ffprobeBin !== null, include: rules.extsDuration, async run({ absPath, mediaFile }) { - const { stdout } = await execFile(ffprobeBin!, [ + const { stdout } = await subprocess.exec(ffprobeBin!, [ "-v", "error", "-show_entries", @@ -793,11 +780,11 @@ import { Progress } from "@paperclover/console/Progress"; import { Spinner } from "@paperclover/console/Spinner"; import * as async from "#sitegen/async"; import * as fs from "#sitegen/fs"; +import * as subprocess from "#sitegen/subprocess"; import * as path from "node:path"; import * as zlib from "node:zlib"; import * as child_process from "node:child_process"; -import * as util from "node:util"; import * as crypto from "node:crypto"; import * as stream from "node:stream"; @@ -814,3 +801,4 @@ import * as highlight from "@/file-viewer/highlight.ts"; import * as ffmpeg from "@/file-viewer/ffmpeg.ts"; import * as rsync from "@/file-viewer/rsync.ts"; import * as transcodeRules from "@/file-viewer/transcode-rules.ts"; +import { rawFileRoot as root } from "../paths.ts"; diff --git a/src/file-viewer/cache.ts b/src/file-viewer/cache.ts index fd85eeb..74121d1 100644 --- a/src/file-viewer/cache.ts +++ b/src/file-viewer/cache.ts @@ -12,9 +12,9 @@ import { escapeUri } from "./format.ts"; declare const Deno: any; const sourceOfTruth = "https://nas.paperclover.net:43250"; -const caCert = fs.readFileSync("src/file-viewer/cert.pem"); +// const caCert = fs.readFileSync("src/file-viewer/cert.pem"); -const diskCacheRoot = path.join(import.meta.dirname, "../.clover/filecache/"); +const diskCacheRoot = path.join(import.meta.dirname, ".filecache/"); const diskCacheMaxSize = 14 * 1024 * 1024 * 1024; // 14GB const ramCacheMaxSize = 1 * 1024 * 1024 * 1024; // 1.5GB const loadInProgress = new Map< @@ -338,7 +338,7 @@ const agent: any = typeof Bun !== "undefined" // Bun has two non-standard fetch extensions decompress: false, tls: { - ca: caCert, + // ca: caCert, }, } // TODO: https://github.com/denoland/deno/issues/12291 @@ -351,7 +351,7 @@ const agent: any = typeof Bun !== "undefined" // } // Node.js supports node:http : new Agent({ - ca: caCert, + // ca: caCert, }); function fetchFileNode(pathname: string): Promise { diff --git a/src/file-viewer/paths.ts b/src/file-viewer/paths.ts new file mode 100644 index 0000000..8d974d9 --- /dev/null +++ b/src/file-viewer/paths.ts @@ -0,0 +1,12 @@ +export const nasRoot = process.platform === "win32" + ? "\\\\zenith\\clover" + : process.platform === "darwin" + ? "/Volumes/clover" + : "/media/clover"; + +export const rawFileRoot = process.env.CLOVER_FILE_RAW ?? + path.join(nasRoot, "Published"); +export const derivedFileRoot = process.env.CLOVER_FILE_DERIVED ?? + path.join(nasRoot, "Documents/Config/paperclover/derived"); + +import * as path from "node:path"; diff --git a/src/site.ts b/src/site.ts index 3c47ebc..214e8f6 100644 --- a/src/site.ts +++ b/src/site.ts @@ -1,11 +1,11 @@ -// This file defines the different "Sections" of the website. The sections act -// as separate codebases, but hosted as one. This allows me to have -// sub-projects like the file viewer in 'file', or the question answer system -// in 'q+a'. Each section can define configuration, pages, backend routes, and -// contain other files. const join = (...paths: string[]) => path.join(import.meta.dirname, ...paths); -export const siteSections: Section[] = [ +// Different sections of the website are split into their own folders. Acting as +// as separate codebases, they are hosted as one. This allows me to have +// sub-projects like the file viewer in 'file/', and the question answer system +// in 'q+a', but maintain clear boundaries. Each section can define +// configuration, pages, backend routes, and contain other files. +export const siteSections: sg.Section[] = [ { root: join(".") }, { root: join("q+a/") }, { root: join("file-viewer/") }, @@ -14,10 +14,96 @@ export const siteSections: Section[] = [ // { root: join("fiction/"), pageBase: "/fiction" }, ]; +// All backends are bundled. The backend named "backend" is run by "node run watch" export const backends: string[] = [ join("backend.ts"), join("source-of-truth.ts"), ]; +// Font subsets reduce bandwidth and protect against proprietary font theft. +const fontRoot = path.join(nasRoot, 'Documents/Font'); +const ascii = { start: 0x20, end: 0x7E }; +const nonAscii: sg.FontRange[] = [ + { start: 0xC0, end: 0xFF }, + { start: 0x2190, end: 0x2193 }, + { start: 0xA0, end: 0xA8 }, + { start: 0xAA, end: 0xBF }, + { start: 0x2194, end: 0x2199 }, + { start: 0x100, end: 0x17F }, + 0xA9, + 0x2018, + 0x2019, + 0x201C, + 0x201D, + 0x2022, +]; +const recursiveVars: sg.FontVars = { + WGHT: [400, 750], + SLNT: [-15, 0], + CASL: 0.25, + CRSV: 0.5, + MONO: 0, +}; +const recursiveVarsMono = { ...recursiveVars, MONO: 1 }; +const recursiveVarsQuestion: sg.FontVars = { + ...recursiveVars, + MONO: [0, 1], + WGHT: [400, 1000], +}; +const layoutFeatures = ["numr", "dnom", "frac"]; +export const fonts: sg.Font[] = [ + { + name: "Recursive", + sources: [ + "Recursive_VF_1.085.ttf", + path.join(fontRoot, "/ArrowType/Recursive/Recursive_VF_1.085.ttf"), + "https://paperclover.net/file/_unlisted/Recursive_VF_1.085.ttf", + ], + subsets: [ + { + asset: "/recultramin.[ext]", + layoutFeatures, + vars: recursiveVars, + unicodes: ascii, + }, + { + asset: "/recmono.[ext]", + vars: recursiveVarsMono, + unicodes: ascii, + }, + { + asset: "/recqa.[ext]", + vars: recursiveVarsQuestion, + unicodes: ascii, + }, + { + asset: "/recexotic.[ext]", + vars: recursiveVarsQuestion, + unicodes: nonAscii, + }, + ], + }, + { + name: "AT Name Sans Display Hairline", + sources: [ + "ATNameSansDisplay-Hairline.woff2", + path.join(fontRoot, "/ArrowType/Recursive/Recursive_VF_1.085.ttf"), + ], + subsets: [ + { + asset: "/cydn_header.[ext]", + layoutFeatures, + unicodes: "cotlyedon", + }, + ], + }, +]; + +export async function main() { + await font.buildFonts(fonts); +} + import * as path from "node:path"; -import type { Section } from "#sitegen"; +import * as font from "../framework/font.ts"; +import type * as sg from "#sitegen"; +import { nasRoot } from "./file-viewer/paths.ts"; diff --git a/src/source-of-truth.ts b/src/source-of-truth.ts index af2826f..1189ec0 100644 --- a/src/source-of-truth.ts +++ b/src/source-of-truth.ts @@ -22,17 +22,6 @@ export default app; const token = UNWRAP(process.env.CLOVER_SOT_KEY); -const nasRoot = process.platform === "win32" - ? "\\\\zenith\\clover" - : process.platform === "darwin" - ? "/Volumes/clover" - : "/media/clover"; - -const rawFileRoot = process.env.CLOVER_FILE_RAW ?? - path.join(nasRoot, "Published"); -const derivedFileRoot = process.env.CLOVER_FILE_DERIVED ?? - path.join(nasRoot, "Documents/Config/paperclover/derived"); - if (!fs.existsSync(rawFileRoot)) { throw new Error(`${rawFileRoot} does not exist`); } @@ -125,3 +114,4 @@ import * as fsCallbacks from "node:fs"; import * as util from "node:util"; import * as stream from "node:stream"; import * as mime from "#sitegen/mime"; +import { derivedFileRoot, rawFileRoot } from "./file-viewer/paths.ts"; diff --git a/src/static/apple-touch-icon.png b/src/static/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5f24ff6a41358bfc31ee9909f3205d7413223523 GIT binary patch literal 73219 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 literal 0 HcmV?d00001 diff --git a/src/static/favicon.ico b/src/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..738099c56829dee9a6ad2ef5f0a6e7ee92110d49 GIT binary patch literal 67758 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 literal 0 HcmV?d00001 diff --git a/src/web.dockerfile b/src/web.dockerfile new file mode 100644 index 0000000..03c3a6c --- /dev/null +++ b/src/web.dockerfile @@ -0,0 +1,19 @@ +from node:24 as builder + +run apt install -y git + +workdir /tmp/builder +copy package*.json ./ +run npm ci +copy . ./ +run node run generate +run npm prune --production && cp -r .clover/o /app && cp -r node_modules /app/ + +from node:24 as runtime + +workdir /app +copy --from=builder /app/ ./ + +env PORT=80 + +cmd ["node", "--enable-source-maps", "/app/backend.js"]