Something went wrong. Try again.
Personal site and resume on Astro; the PDF is printed from the site. spencergilbert.dev
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133#!/usr/bin/env node//// Build the site, then print its /resume page to a PDF with headless Chromium.// One renderer: the PDF is the same HTML/CSS the site serves, so the two// cannot drift.//// It always builds first because the PDF is derived from the built page — a// stale dist/ would otherwise silently produce a stale PDF.//// node scripts/render-cv.mjs build once, print once// node scripts/render-cv.mjs --watch re-run whenever src/ changes//// Needs `npx playwright install chromium` once.import { execFileSync } from 'node:child_process';import { createServer } from 'node:http';import { copyFile, mkdir, readFile } from 'node:fs/promises';import { existsSync, watch } from 'node:fs';import { dirname, extname, join, normalize } from 'node:path';import { fileURLToPath } from 'node:url';import { chromium } from 'playwright';import { chromiumLaunchOptions } from './chromium.mjs';
const root = dirname(dirname(fileURLToPath(import.meta.url)));const dist = join(root, 'dist');const out = join(root, 'public', 'spencer-gilbert-cv.pdf');const watchMode = process.argv.includes('--watch');
const MIME = { '.html': 'text/html; charset=utf-8', '.css': 'text/css; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.mjs': 'text/javascript; charset=utf-8', '.json': 'application/json', '.svg': 'image/svg+xml', '.ico': 'image/x-icon', '.woff2': 'font/woff2',};
async function render() { execFileSync(join(root, 'node_modules', '.bin', 'astro'), ['build'], { cwd: root, stdio: 'inherit', });
if (!existsSync(join(dist, 'resume', 'index.html'))) { throw new Error('build did not produce dist/resume/index.html'); }
// Any port, served from dist/, so absolute asset paths resolve like production. const server = createServer(async (req, res) => { let pathname = decodeURIComponent( new URL(req.url ?? '/', 'http://x').pathname, ); if (pathname.endsWith('/')) pathname += 'index.html'; const file = normalize(join(dist, pathname)); if (!file.startsWith(dist)) { res.writeHead(403).end(); return; } try { const body = await readFile(file); res.writeHead(200, { 'content-type': MIME[extname(file)] ?? 'application/octet-stream', }); res.end(body); } catch { res.writeHead(404).end('not found'); } });
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)); const { port } = server.address();
const browser = await chromium.launch(chromiumLaunchOptions()); try { const page = await browser.newPage(); await page.goto(`http://127.0.0.1:${port}/resume/`, { waitUntil: 'load' }); await mkdir(dirname(out), { recursive: true }); await page.pdf({ path: out, tagged: true, outline: true, preferCSSPageSize: true, printBackground: true, }); } finally { await browser.close(); server.close(); }
// The build copied the *previous* PDF from public/ into dist/ before this // run, so refresh it there too — dist/ is what the deployed site serves. await copyFile(out, join(dist, 'spencer-gilbert-cv.pdf')); console.log('wrote', out);}
if (watchMode) { const src = join(root, 'src'); let running = false; let queued = false;
const run = async (reason) => { if (running) { queued = true; return; } running = true; try { console.log(`[render:cv] ${reason}`); await render(); } catch (error) { console.error(`[render:cv] failed: ${error.message}`); } finally { running = false; if (queued) { queued = false; await run('change during render'); } } };
await run('initial render');
let timer; watch(src, { recursive: true }, (_event, filename) => { clearTimeout(timer); timer = setTimeout(() => run(`change: ${filename ?? src}`), 200); });
console.log(`[render:cv] watching ${src} (Ctrl+C to stop)`);} else { await render();}