import { readFile, writeFile, readdir } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import {
buildRouteMetaMap,
routeToOgPath,
DIST,
SITE,
DEFAULT_IMAGE,
DEFAULT_DESC,
} from './lib/route-meta.mjs';
function escapeAttr(s) {
return String(s)
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(//g, '>');
}
function absoluteUrl(maybePath) {
if (!maybePath) return SITE + DEFAULT_IMAGE;
if (/^https?:\/\//.test(maybePath)) return maybePath;
return SITE + (maybePath.startsWith('/') ? maybePath : '/' + maybePath);
}
function resolveImage(route, meta) {
if (meta.image) return absoluteUrl(meta.image);
const generated = routeToOgPath(route);
const distPath = join(DIST, generated);
if (existsSync(distPath)) return absoluteUrl('/' + generated);
return absoluteUrl(DEFAULT_IMAGE);
}
function rewriteHead(html, route, meta) {
const title = meta.title || 'fezcodex';
const description = meta.description || DEFAULT_DESC;
const image = resolveImage(route, meta);
const url = SITE + route;
const type = meta.type || 'website';
const eTitle = escapeAttr(title);
const eDesc = escapeAttr(description);
const eImg = escapeAttr(image);
const eUrl = escapeAttr(url);
const eType = escapeAttr(type);
const subs = [
[/
[^<]*<\/title>/i, `${eTitle}`],
[
//i,
``,
],
[
//i,
``,
],
[
//i,
``,
],
[
//i,
``,
],
[
//i,
``,
],
[
//i,
``,
],
[
//i,
``,
],
[
//i,
``,
],
[
//i,
``,
],
[
//i,
``,
],
];
let out = html;
let hits = 0;
for (const [re, rep] of subs) {
const next = out.replace(re, rep);
if (next !== out) hits++;
out = next;
}
return { html: out, hits };
}
function fileToRoute(file) {
const rel = file.slice(DIST.length).replace(/\\/g, '/');
if (rel === '/index.html') return '/';
if (rel.endsWith('/index.html')) return rel.slice(0, -'/index.html'.length);
return null;
}
async function walkHtml(dir, out = []) {
for (const entry of await readdir(dir, { withFileTypes: true })) {
const full = join(dir, entry.name);
if (entry.isDirectory()) await walkHtml(full, out);
else if (entry.isFile() && entry.name === 'index.html') out.push(full);
}
return out;
}
async function main() {
const t0 = Date.now();
if (!existsSync(DIST)) {
console.error(`inject-meta: ${DIST} does not exist; run vite build first`);
process.exit(1);
}
const routeMap = buildRouteMetaMap();
console.log(`inject-meta: ${routeMap.size} routes have per-page metadata`);
const files = await walkHtml(DIST);
let touched = 0;
let skipped = 0;
let missing = 0;
for (const file of files) {
const route = fileToRoute(file);
if (route == null) {
skipped++;
continue;
}
const meta = routeMap.get(route);
if (!meta) {
missing++;
continue;
}
const html = await readFile(file, 'utf8');
const { html: next, hits } = rewriteHead(html, route, meta);
if (hits === 0) {
skipped++;
continue;
}
await writeFile(file, next, 'utf8');
touched++;
}
console.log(
`inject-meta: rewrote ${touched} file(s), ${missing} without per-page meta, ${skipped} skipped (${Date.now() - t0}ms)`
);
}
await main();