Files
thptqg/scripts/assemble-site.js
T
tiennm99 a5904a5d78 build: produce the whole site from one Vite build
The four build variants existed only because the app could not resolve its own
dataset. Now that it can, vite.config.js is a single build with an absolute
base and no VARIANT switching, and the workflow compiles Rust once, installs
Node dependencies once, and builds the site once — it previously built the same
Rust crate twice and ran two separate pnpm installs.

Because base is absolute, the emitted index.html references /thptqg/assets/...
regardless of where it is served from, so the same file works as an entry point
at any depth. scripts/assemble-site.js copies it to each dataset path and to
the two legacy nested URLs, giving a real static file at every published route.
That is what removes the need for an SPA 404-fallback redirect, which would
otherwise have rewritten URLs and interfered with the ?q= deep links.

Generated databases move to a gitignored .build/public, which Vite consumes as
its publicDir. build-db.js gzips without -k, and the assemble step then refuses
to finish if any uncompressed database artefact reached the output — .db,
.db-journal, .db-wal or .db-shm. Previously a raw 100+ MB database was written
into the source tree and deleted afterwards by an rm in the workflow, so
shipping one was a missing cleanup step away.

Both build scripts import DATASET_IDS from src/datasets.js rather than
repeating the dataset list in workflow shell, so the four ids are declared in
exactly one place across the frontend, the database build and the assembly.

Verified by running the full pipeline locally and serving the artifact over
HTTP: all nine routes return 200, every entry point is byte-identical, asset
references are absolute, and the databases are fetchable. The guard was tested
by injecting a raw .db and .db-journal into the build, which failed the
assembly as intended.
2026-08-13 11:57:26 +07:00

78 lines
2.9 KiB
JavaScript

#!/usr/bin/env node
/**
* Assemble the GitHub Pages artifact from a single Vite build.
*
* The app resolves its dataset from the URL, so every page is the same
* index.html. Because `base` is absolute (/thptqg/), that file references
* /thptqg/assets/... no matter which directory it is served from — so copying
* it to each dataset path produces a real static file at every URL.
*
* GitHub Pages serves those as directory indexes, which is why this needs no
* SPA 404-fallback redirect. That matters beyond tidiness: the usual fallback
* rewrites the URL and would interfere with the ?q= deep links the app relies
* on.
*
* Usage: node scripts/assemble-site.js
*/
import { cpSync, mkdirSync, rmSync, existsSync, readdirSync, statSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { DATASET_IDS } from "../src/datasets.js";
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const DIST = join(ROOT, "dist");
const SITE = join(ROOT, "_site");
/** URLs published before the switch to flat paths; the router rewrites these. */
const LEGACY_PATHS = ["2017/old", "2017/old2"];
if (!existsSync(join(DIST, "index.html"))) {
console.error(`no build found at ${DIST} — run: npm run build`);
process.exit(1);
}
rmSync(SITE, { recursive: true, force: true });
mkdirSync(SITE, { recursive: true });
// Base build: index.html, assets/, and the gzipped databases from publicDir.
cpSync(DIST, SITE, { recursive: true });
// Unknown paths render the hub rather than the default Pages 404.
cpSync(join(DIST, "index.html"), join(SITE, "404.html"));
// One entry point per dataset, plus the legacy nested URLs.
for (const path of [...DATASET_IDS, ...LEGACY_PATHS]) {
mkdirSync(join(SITE, path), { recursive: true });
cpSync(join(DIST, "index.html"), join(SITE, path, "index.html"));
}
// Only gzipped databases may ship. build-db.js gzips without -k so no raw .db
// should exist, but publicDir is copied wholesale — a leftover from an
// interrupted build would go straight through, and a raw database is 100+ MB.
// SQLite also leaves .db-journal files mid-build, so anything that is not a
// .gz is rejected rather than just files ending in .db.
const stray = [];
(function walk(dir) {
for (const entry of readdirSync(dir)) {
const full = join(dir, entry);
if (statSync(full).isDirectory()) walk(full);
else if (/\.db(-journal|-wal|-shm)?$/.test(entry)) stray.push(full);
}
})(SITE);
if (stray.length) {
console.error("uncompressed database artefact(s) found in the site output:");
for (const f of stray) {
console.error(` ${f} (${(statSync(f).size / 1048576).toFixed(1)} MB)`);
}
console.error("\nremove them from .build/public/db and re-run");
process.exit(1);
}
console.log(`assembled ${SITE}`);
for (const path of ["", "404.html", ...DATASET_IDS, ...LEGACY_PATHS]) {
console.log(` /thptqg/${path}`);
}