Files
dylanmazzei-website/build.mjs
T
Alejandro Veras b2eff3d3be Only show the booking calendars after the lead form is sent
/contact/book/ now needs ?submitted=1 (set in the GHL form redirect) to load the
calendars; other visitors get a prompt that links to the form. The page is noindex
and out of the sitemap, the contact page card points to the form, and visitors are
sent back to the language they filled the form in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:16:14 -04:00

64 lines
3.6 KiB
JavaScript

// Builds the static site into ./dist. No dependencies: `node build.mjs`.
import { mkdir, rm, writeFile, cp, readFile } from 'node:fs/promises';
import { createHash } from 'node:crypto';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { page, eyebrow, btn, SITE } from './src/layout.mjs';
import { LOCALES, DEFAULT_LOCALE, localize, translatePage } from './src/i18n.mjs';
const root = dirname(fileURLToPath(import.meta.url));
const out = join(root, 'dist');
const pages = ['home', 'about', 'portfolio', 'consulting', 'marketing', 'business-setup', 'business-optimization', 'supply-chain', 'cybersecurity', 'education', 'fractional', 'contact', 'book', 'press'];
await rm(out, { recursive: true, force: true });
await mkdir(out, { recursive: true });
await cp(join(root, 'static'), out, { recursive: true });
// Fingerprint CSS/JS so a new deploy never reuses a stale cached copy.
const hashOf = async (f) => createHash('sha256').update(await readFile(join(root, 'static', f))).digest('hex').slice(0, 10);
const assetVersions = Object.fromEntries(await Promise.all(['styles.css', 'site.js', 'home.css', 'home.js', 'fonts.css', 'book.js', 'rtl.css'].map(async (f) => ['/assets/' + f, await hashOf('assets/' + f)])));
const fingerprint = (html) => Object.entries(assetVersions).reduce((h, [u, v]) => h.replaceAll(`"${u}"`, `"${u}?v=${v}"`), html);
// Spanish and Arabic pages are the English pages with their text swapped through src/i18n/<locale>.json.
const dicts = Object.fromEntries(await Promise.all(LOCALES.filter((l) => l !== DEFAULT_LOCALE).map(async (l) => [l, JSON.parse(await readFile(join(root, 'src/i18n', l + '.json'), 'utf8'))])));
const missing = {};
const urls = [];
for (const name of pages) {
const def = (await import(`./src/pages/${name}.mjs`)).default;
for (const locale of LOCALES) {
let html = page(def, locale);
if (locale !== DEFAULT_LOCALE) {
const t = translatePage(html, locale, dicts[locale]);
html = t.html;
for (const s of t.missing) (missing[locale] ??= new Set()).add(s);
}
const file = join(out, localize(def.path, locale), 'index.html');
await mkdir(dirname(file), { recursive: true });
await writeFile(file, fingerprint(html));
}
if (!def.noindex) urls.push(def.path);
console.log('built', def.path);
}
for (const [l, set] of Object.entries(missing)) {
console.warn(`\n${l}: ${set.size} strings without translation (shown in English):`);
for (const s of set) console.warn(' ' + JSON.stringify(s));
}
if (process.argv.includes('--strict') && Object.keys(missing).length) process.exit(1);
await writeFile(join(out, '404.html'), fingerprint(page({
path: '/404/',
title: 'Page Not Found | Dylan Mazzei',
description: 'This page could not be found.',
body: `<section class="page-hero"><div class="wrap-narrow stack center" style="align-items:center;text-align:center">${eyebrow('404')}<h1 class="h-display-lg">Page Not Found</h1><div class="btn-row">${btn('BACK TO HOME', '/')}</div></div></section>`,
})));
const today = new Date().toISOString().slice(0, 10);
await writeFile(join(out, 'sitemap.xml'), `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
${urls.flatMap((u) => LOCALES.map((l) => ` <url><loc>${SITE.url}${localize(u, l)}</loc><lastmod>${today}</lastmod>${LOCALES.map((a) => `<xhtml:link rel="alternate" hreflang="${a}" href="${SITE.url}${localize(u, a)}"/>`).join('')}</url>`)).join('\n')}
</urlset>
`);
await writeFile(join(out, 'robots.txt'), 'User-agent: *\nAllow: /\nSitemap: https://dylanmazzei.com/sitemap.xml\n');
console.log('done →', out);