Files
PIG-Demo/scripts/_lib.mjs
T
karti-ai 408ce4a525 Capture harness, fixture verification, CI, and the public README
The site does no live inference. Rollouts are captured once against spark-1 and
replayed at their recorded wall-clock — a public demo with no auth cannot hold
an API key, and a recorded run can be scrubbed, permalinked, blind-compared and
verified in ways a live one cannot. What stops it being a video is that the
browser re-derives every number from the recorded moves.

verify_fixtures.py is the Python half of that: it replays every committed
fixture through the engine and reproduces its own rewards. All 16 land at
delta 0.0. A fixture that cannot be regenerated is a claim with no receipt.

First real measurement, thinking off, 8 seeds: solved 0/8. The model repeats
guesses it has already played, invents words (trape, slith, postt, boomy),
and contradicts its own feedback — consistency 0.09 to 0.17. That is the
published failure taxonomy showing up in our own data on the first run, and it
is why `consistency` is a reward component rather than a footnote.

A capture failure is recorded as a turn with a null reply, never dropped. A
capture that silently discarded failed turns would be reporting a better model
than the one that ran.

CI gates both halves and four things that fail silently in production: the word
lists must rebuild byte-identically, the prerendered routes must carry their own
baked og tags (crawlers do not run JS, so without them every shared link
previews as the homepage), no blob: URL may reach the bundle (the site's CSP has
no worker-src, so it falls back to default-src 'self' and a blob worker is
blocked with no error), and the conformance digest must match across languages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019mt6sHQHEnEYrJZvoMCJSB
2026-08-28 15:47:31 -07:00

827 lines
29 KiB
JavaScript

/**
* Shared plumbing for the contract scripts.
*
* Every `scripts/check-*.mjs` is a gate, not a linter: it exits non-zero with a
* message naming the file and the numbered rule that was broken, so a failure
* in CI reads as an instruction rather than a puzzle.
*
* The demo contract lives in TypeScript and these scripts are plain Node, so
* most of what follows is a small, deliberately dumb TypeScript reader: find a
* named declaration, brace-match its object/array literal, strip the two TS-only
* forms a literal can legally carry (`as const`, `satisfies X`), and evaluate it
* in a bare `vm` context.
*
* That reader is honest about its limits. A literal that references an imported
* constant does not evaluate, and the scripts FAIL rather than skip: the whole
* point of `meta` and `reward.components` is that a human can read the numbers
* off the page beside the code, so a weight hidden behind an indirection is a
* contract problem, not a tooling problem.
*/
import fs from 'node:fs';
import path from 'node:path';
import http from 'node:http';
import zlib from 'node:zlib';
import { fileURLToPath } from 'node:url';
import vm from 'node:vm';
export const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
export const abs = (...parts) => path.join(ROOT, ...parts);
export const rel = (p) => path.relative(ROOT, p) || '.';
export const exists = (p) => fs.existsSync(p);
export const read = (p) => fs.readFileSync(p, 'utf8');
export const readJson = (p) => JSON.parse(read(p));
/* ------------------------------------------------------------------ output */
const ESC = '\u001b[';
const COLOUR = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
const paint = (code, s) => (COLOUR ? `${ESC}${code}m${s}${ESC}0m` : String(s));
export const bold = (s) => paint('1', s);
export const dim = (s) => paint('2', s);
export const red = (s) => paint('31', s);
export const green = (s) => paint('32', s);
export const yellow = (s) => paint('33', s);
export const cyan = (s) => paint('36', s);
/**
* Collects failures instead of throwing on the first one.
*
* A contract check that dies on failure #1 makes the author fix and re-run
* seven times. Every rule that can still be evaluated is evaluated.
*/
export class Report {
/** @param {string} title */
constructor(title) {
this.title = title;
/** @type {{file: string, rule: string, message: string}[]} */
this.failures = [];
/** @type {string[]} */
this.warnings = [];
/** @type {string[]} */
this.staticNotes = [];
this.passed = 0;
}
/**
* @param {string} file repo-relative path the reader should open
* @param {string} rule the numbered rule, e.g. 'rule 10 (reward weights)'
* @param {string} message what is actually wrong
*/
fail(file, rule, message) {
this.failures.push({ file, rule, message });
return false;
}
/** Assert, recording either a pass or a precise failure. */
check(condition, file, rule, message) {
if (condition) {
this.passed += 1;
return true;
}
return this.fail(file, rule, message);
}
warn(message) {
this.warnings.push(message);
}
/** Record that a rule was verified by reading source, not by running it. */
staticOnly(message) {
this.staticNotes.push(message);
}
/** Prints the report and exits the process. Never returns. */
finish() {
const out = [];
if (this.staticNotes.length) {
out.push('');
out.push(dim('Checked by reading source, not by executing it:'));
for (const n of this.staticNotes) out.push(dim(` - ${n}`));
}
if (this.warnings.length) {
out.push('');
for (const w of this.warnings) out.push(`${yellow('warn')} ${w}`);
}
if (this.failures.length) {
out.push('');
for (const f of this.failures) {
out.push(`${red('FAIL')} ${bold(f.file)}`);
out.push(` ${cyan(f.rule)}: ${f.message}`);
}
out.push('');
out.push(red(`${this.title}: ${this.failures.length} failure(s), ${this.passed} check(s) passed.`));
console.error(out.join('\n'));
process.exit(1);
}
out.push('');
out.push(green(`${this.title}: ${this.passed} check(s) passed.`));
console.log(out.join('\n'));
process.exit(0);
}
}
/** Fatal error that is the script's own problem, not the repo's. */
export function die(message) {
console.error(`${red('FAIL')} ${message}`);
process.exit(1);
}
/* ------------------------------------------------------- filesystem walking */
/**
* @param {string} dir
* @param {(file: string) => boolean} [filter]
* @returns {string[]} absolute paths, depth-first, stable order
*/
export function walk(dir, filter = () => true) {
if (!exists(dir)) return [];
/** @type {string[]} */
const out = [];
const entries = fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => (a.name < b.name ? -1 : 1));
for (const entry of entries) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
if (entry.name === 'node_modules' || entry.name === '__pycache__') continue;
out.push(...walk(full, filter));
} else if (entry.isFile() && filter(full)) {
out.push(full);
}
}
return out;
}
export const DEMOS_DIR = abs('src', 'demos');
/** Every directory under `src/demos`, including `_template`. */
export function allDemoDirs() {
if (!exists(DEMOS_DIR)) return [];
return fs
.readdirSync(DEMOS_DIR, { withFileTypes: true })
.filter((e) => e.isDirectory())
.map((e) => e.name)
.sort();
}
/** Shippable demos: a leading underscore marks scaffolding, not a demo. */
export function demoSlugs() {
return allDemoDirs().filter((name) => !name.startsWith('_'));
}
/* ----------------------------------------------------- the small TS reader */
/**
* Splits source into code and non-code (string literal / comment) spans.
*
* Everything below works on this rather than on raw regex, because the metas
* are full of prose: a `description` containing the words "as an executive"
* is otherwise indistinguishable from a TypeScript `as` assertion.
*
* @param {string} src
* @returns {{code: boolean, text: string}[]}
*/
export function segment(src) {
/** @type {{code: boolean, text: string}[]} */
const out = [];
let codeStart = 0;
let i = 0;
const flushCode = (end) => {
if (end > codeStart) out.push({ code: true, text: src.slice(codeStart, end) });
};
while (i < src.length) {
const c = src[i];
const next = src[i + 1];
if (c === '/' && next === '/') {
flushCode(i);
const nl = src.indexOf('\n', i);
const end = nl === -1 ? src.length : nl;
out.push({ code: false, text: src.slice(i, end) });
i = codeStart = end;
continue;
}
if (c === '/' && next === '*') {
flushCode(i);
const close = src.indexOf('*/', i + 2);
const end = close === -1 ? src.length : close + 2;
out.push({ code: false, text: src.slice(i, end) });
i = codeStart = end;
continue;
}
if (c === "'" || c === '"' || c === '`') {
flushCode(i);
const end = endOfString(src, i) + 1;
out.push({ code: false, text: src.slice(i, end) });
i = codeStart = end;
continue;
}
i += 1;
}
flushCode(src.length);
return out;
}
/** Index of the closing quote of the string literal starting at `start`. */
function endOfString(src, start) {
const quote = src[start];
for (let j = start + 1; j < src.length; j += 1) {
const c = src[j];
if (c === '\\') {
j += 1;
continue;
}
if (quote === '`' && c === '$' && src[j + 1] === '{') {
j = scanBalanced(src, j + 1) - 1;
continue;
}
if (c === quote) return j;
}
throw new Error(`unterminated string literal starting at offset ${start}`);
}
/**
* End offset (exclusive) of the bracket pair opening at `start`.
* Skips strings and comments, and recurses through `${...}` in templates.
*/
export function scanBalanced(src, start) {
const open = src[start];
const close = open === '{' ? '}' : open === '[' ? ']' : open === '(' ? ')' : null;
if (!close) throw new Error(`offset ${start} is ${JSON.stringify(open)}, not an opening bracket`);
let depth = 0;
for (let i = start; i < src.length; i += 1) {
const c = src[i];
const next = src[i + 1];
if (c === '/' && next === '/') {
const nl = src.indexOf('\n', i);
if (nl === -1) break;
i = nl;
continue;
}
if (c === '/' && next === '*') {
const end = src.indexOf('*/', i + 2);
i = end === -1 ? src.length : end + 1;
continue;
}
if (c === "'" || c === '"' || c === '`') {
i = endOfString(src, i);
continue;
}
if (c === open) depth += 1;
else if (c === close) {
depth -= 1;
if (depth === 0) return i + 1;
}
}
throw new Error(`unbalanced ${open} starting at offset ${start}`);
}
/** Offsets of every code (non-string, non-comment) span in `src`. */
function codeSpans(src) {
const spans = [];
let at = 0;
for (const s of segment(src)) {
spans.push({ code: s.code, start: at, end: at + s.text.length });
at += s.text.length;
}
return spans;
}
/**
* Finds the literal that follows `anchor` and returns its source text.
*
* @param {string} src
* @param {RegExp} anchor must match immediately before the opening bracket
* @param {'{' | '['} open
* @returns {string | null}
*/
export function literalsAfter(src, anchor, open = '{') {
const flags = anchor.flags.includes('g') ? anchor.flags : `${anchor.flags}g`;
const re = new RegExp(anchor.source, flags);
const spans = codeSpans(src);
const isCode = (offset) => {
const hit = spans.find((s) => offset >= s.start && offset < s.end);
return hit ? hit.code : true;
};
const found = [];
let match;
while ((match = re.exec(src)) !== null) {
// The anchor may legally match inside a comment or a string \u2014 a docblock
// that quotes `const meta = {`. Only a match in real code counts.
if (!isCode(match.index)) continue;
const from = src.indexOf(open, match.index + Math.max(match[0].length - 1, 0));
if (from === -1) continue;
const between = src.slice(match.index + match[0].length, from);
if (/[;=}]/.test(between)) continue;
const end = scanBalanced(src, from);
found.push({ text: src.slice(from, end), start: from, end });
// Skip past this literal so a nested anchor of the same name is not
// reported as a second, overlapping hit.
re.lastIndex = end;
}
return found;
}
/** First literal following `anchor`, or null. See {@link literalsAfter}. */
export function literalAfter(src, anchor, open = '{') {
const [first] = literalsAfter(src, anchor, open);
return first ? first.text : null;
}
/** Marker key on the stand-in an unresolvable identifier evaluates to. */
export const UNRESOLVED = '__pigUnresolvedIdentifier__';
export const isUnresolved = (v) => Boolean(v) && typeof v === 'object' && UNRESOLVED in v;
/**
* A sandbox in which every free identifier resolves to a labelled stand-in.
*
* `RewardSpec.source.code` is legitimately an identifier — the Python is
* imported with `?raw` and cannot exist in plain Node — so the reward literal
* must be readable WITHOUT its `code`. Strict mode is still the default: a
* weight that turns out to be a stand-in is a contract failure, not a skip.
*/
function lenientSandbox() {
return new Proxy(Object.create(null), {
has: () => true,
get: (_target, key) => {
if (key === Symbol.unscopables) return undefined;
if (typeof key !== 'string') return undefined;
return { [UNRESOLVED]: key };
},
});
}
/**
* Evaluates a TypeScript object/array literal as plain data.
*
* `as const` and `satisfies T` are stripped from code spans only. Anything else
* a literal might carry — an identifier, a call, a spread of an import — throws,
* and callers turn that into a contract failure with the file named. Pass
* `{lenient: true}` to get stand-ins for free identifiers instead.
*
* @param {string} text
* @param {string} label
* @param {{lenient?: boolean}} [options]
*/
export function evalLiteral(text, label, options = {}) {
const js = segment(text)
.map((s) =>
s.code
? s.text
.replace(/\bas\s+const\b/g, '')
.replace(/\bsatisfies\s+[A-Za-z_$][\w$.]*(?:<[^>]*>)?(?:\[\])*/g, '')
.replace(/\bas\s+[A-Za-z_$][\w$.]*(?:<[^>]*>)?(?:\[\])*/g, '')
: s.text,
)
.join('');
try {
const sandbox = options.lenient ? lenientSandbox() : Object.create(null);
const value = vm.runInNewContext(`(${js})`, sandbox, { timeout: 2000 });
return { ok: true, value, error: null };
} catch (error) {
return {
ok: false,
value: null,
error:
`the ${label} literal did not evaluate as plain data (${error.message}). ` +
'These literals are read by tooling and by humans reading the page beside the code, ' +
'so they must be written out, not assembled from imported constants.',
};
}
}
/** Members of a string-union type alias, e.g. `export type Vertical = 'a' | 'b'`. */
export function parseStringUnion(src, typeName) {
const match = src.match(new RegExp(`\\btype\\s+${typeName}\\s*=([^;]+);`));
if (!match || !match[1]) return null;
const members = [...match[1].matchAll(/'([^']+)'/g)].map((m) => m[1]);
return members.length ? members : null;
}
/* ------------------------------------------------------------ demo metadata */
const META_ANCHORS = [
/(?:export\s+)?const\s+meta\s*(?::\s*[^=]+)?=\s*/,
/(?:export\s+)?const\s+[A-Za-z_$][\w$]*Meta\s*(?::\s*[^=]+)?=\s*/,
/\bmeta\s*:\s*/,
];
const CANDIDATE_META_FILES = ['meta.ts', 'meta.tsx', 'demo.tsx', 'demo.ts', 'index.ts', 'index.tsx'];
/**
* Reads one demo's `DemoMeta` out of its source.
* @param {string} slug
* @returns {{file: string, meta: Record<string, any>} | {file: string, error: string}}
*/
export function loadMeta(slug) {
const dir = path.join(DEMOS_DIR, slug);
const tried = [];
for (const name of CANDIDATE_META_FILES) {
const file = path.join(dir, name);
if (!exists(file)) continue;
tried.push(rel(file));
const src = read(file);
for (const anchor of META_ANCHORS) {
let text;
try {
text = literalAfter(src, anchor, '{');
} catch (error) {
return { file: rel(file), error: `could not brace-match the meta literal: ${error.message}` };
}
if (!text) continue;
const result = evalLiteral(text, 'DemoMeta');
if (!result.ok) return { file: rel(file), error: result.error };
if (result.value && typeof result.value === 'object' && 'slug' in result.value) {
return { file: rel(file), meta: result.value };
}
}
}
return {
file: tried[0] ?? rel(path.join(dir, 'meta.ts')),
error:
tried.length === 0
? `no meta source found; expected one of ${CANDIDATE_META_FILES.join(', ')} in ${rel(dir)}`
: `no DemoMeta object literal with a \`slug\` key found in ${tried.join(', ')}`,
};
}
/** Every demo whose meta could be read, keyed by directory name. */
export function loadAllMetas() {
/** @type {Map<string, Record<string, any>>} */
const metas = new Map();
/** @type {{slug: string, file: string, error: string}[]} */
const errors = [];
for (const slug of demoSlugs()) {
const result = loadMeta(slug);
if ('meta' in result) metas.set(slug, result.meta);
else errors.push({ slug, file: result.file, error: result.error });
}
return { metas, errors };
}
/** Every .ts/.tsx file that belongs to one demo. */
export function demoFiles(slug) {
return walk(path.join(DEMOS_DIR, slug), (f) => /\.tsx?$/.test(f));
}
/**
* Finds one named literal anywhere inside a demo's own source.
*
* A demo is free to put `reward` in `reward.ts` or inline it in `demo.tsx`;
* the contract is about the values, not the file layout. First match in
* filename order wins, and the file it came from is returned so failures can
* name it.
*
* @param {string} slug
* @param {RegExp[]} anchors
* @param {'{' | '['} open
* @param {string} label
* @param {{lenient?: boolean}} [options]
*/
export function findInDemo(slug, anchors, open, label, options = {}) {
for (const file of demoFiles(slug)) {
const src = read(file);
for (const anchor of anchors) {
let text = null;
try {
text = literalAfter(src, anchor, open);
} catch (error) {
return { file: rel(file), error: `could not brace-match the ${label} literal: ${error.message}` };
}
if (!text) continue;
const result = evalLiteral(text, label, options);
if (!result.ok) return { file: rel(file), error: result.error, text };
return { file: rel(file), value: result.value, text };
}
}
return {
file: null,
error: `no ${label} literal found in any .ts/.tsx file under ${rel(path.join(DEMOS_DIR, slug))}`,
};
}
/* ------------------------------------------------------------------ verticals */
export const VERTICALS_FILE = abs('src', 'content', 'verticals.ts');
const VERTICAL_ANCHORS = [
/(?:export\s+)?const\s+VERTICALS\s*(?::\s*[^=]+)?=\s*/,
/(?:export\s+)?const\s+verticals\s*(?::\s*[^=]+)?=\s*/,
];
const asVertical = (id, v) => ({
id,
title: v?.title ?? v?.label ?? v?.name ?? null,
// `anxiety` is this repo's field for the one line a vertical page leads
// with, and it is the only prose in a VerticalEntry short enough to be a
// meta description.
description: v?.description ?? v?.blurb ?? v?.tagline ?? v?.summary ?? v?.anxiety ?? null,
});
/**
* The vertical records the site groups demos by.
*
* Tolerant about field names on purpose: this file belongs to another lane, and
* a prerender that hard-codes `label` then silently bakes an empty <title> when
* the author wrote `name` is worse than one that looks for both.
*/
export function loadVerticals() {
if (!exists(VERTICALS_FILE)) return { list: [], error: `${rel(VERTICALS_FILE)} does not exist` };
const src = read(VERTICALS_FILE);
for (const anchor of VERTICAL_ANCHORS) {
for (const open of ['[', '{']) {
let text = null;
try {
text = literalAfter(src, anchor, open);
} catch {
text = null;
}
if (!text) continue;
const result = evalLiteral(text, 'verticals');
if (!result.ok) return { list: [], error: result.error };
const value = result.value;
const list = Array.isArray(value)
? value.map((v) => asVertical(v?.id ?? v?.slug ?? v?.key, v))
: value && typeof value === 'object'
? Object.entries(value).map(([id, v]) => asVertical(id, v))
: [];
const clean = list.filter((v) => typeof v.id === 'string' && v.id.length > 0);
if (clean.length) return { list: clean, error: null };
}
}
return { list: [], error: `no \`verticals\` or \`VERTICALS\` literal found in ${rel(VERTICALS_FILE)}` };
}
/* --------------------------------------------------------------------- traces */
export const MANIFEST_FILE = abs('public', 'traces', 'manifest.json');
/**
* Normalises `public/traces/manifest.json` into `slug -> {runs, extras}`.
*
* Four shapes are accepted because the manifest is authored by hand and all
* four are things a reasonable person writes. Anything else fails loudly rather
* than quietly reporting zero runs, which would turn rule 7 into a no-op.
*/
export function loadManifest() {
if (!exists(MANIFEST_FILE)) return { byDemo: new Map(), error: `${rel(MANIFEST_FILE)} does not exist` };
let raw;
try {
raw = readJson(MANIFEST_FILE);
} catch (error) {
return { byDemo: new Map(), error: `${rel(MANIFEST_FILE)} is not valid JSON: ${error.message}` };
}
/** @type {Map<string, {runs: any[], extras: Record<string, any>}>} */
const byDemo = new Map();
// Unwrap the two envelopes the shell's own reader accepts. `{runs: [...]}`
// has to be unwrapped BEFORE the record branch below, or "runs" is read as a
// demo slug and every real demo reports zero runs.
let container = raw;
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
if (raw.demos && typeof raw.demos === 'object') container = raw.demos;
else if (Array.isArray(raw.runs)) container = raw.runs;
}
if (Array.isArray(container)) {
// A flat array of runs, each carrying its own `demo`/`slug`.
for (const run of container) {
const slug = run?.demo ?? run?.slug;
if (typeof slug !== 'string') continue;
if (!byDemo.has(slug)) byDemo.set(slug, { runs: [], extras: {} });
byDemo.get(slug).runs.push(run);
}
} else if (container && typeof container === 'object') {
for (const [slug, value] of Object.entries(container)) {
if (Array.isArray(value)) byDemo.set(slug, { runs: value, extras: {} });
else if (value && typeof value === 'object' && Array.isArray(value.runs)) {
const { runs, ...extras } = value;
byDemo.set(slug, { runs, extras });
}
}
}
if (byDemo.size === 0) {
return {
byDemo,
error:
`${rel(MANIFEST_FILE)} did not parse into any demo runs. Expected one of: ` +
'{"<slug>": RunRef[]}, {"<slug>": {"runs": RunRef[], ...}}, {"demos": {...}}, ' +
'or a flat RunRef[] where each run carries a "demo" field.',
};
}
return { byDemo, error: null };
}
/** Resolve a site-absolute trace path ('/traces/x.json') to a disk path. */
export function traceFile(sitePath) {
const clean = String(sitePath).split('?')[0].split('#')[0];
return abs('public', clean.replace(/^\/+/, ''));
}
/* -------------------------------------------------------------- static server */
const MIME = {
'.html': 'text/html; charset=utf-8',
'.js': 'text/javascript; charset=utf-8',
'.mjs': 'text/javascript; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.svg': 'image/svg+xml',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.webp': 'image/webp',
'.avif': 'image/avif',
'.ico': 'image/x-icon',
'.woff2': 'font/woff2',
'.woff': 'font/woff',
'.ttf': 'font/ttf',
'.txt': 'text/plain; charset=utf-8',
'.map': 'application/json; charset=utf-8',
'.xml': 'application/xml; charset=utf-8',
};
/**
* Serves a directory the way the deploy host will: a real file if one exists,
* `index.html` otherwise.
*
* Prerender MUST hit the SPA fallback, so this deliberately does not 404 on an
* unknown route — but it DOES 404 on a missing file under a path with an
* extension, because serving HTML where a script was requested is how you
* prerender a blank page and never find out why.
*
* @param {string} dir
* @returns {Promise<{origin: string, close: () => Promise<void>}>}
*/
export function serveStatic(dir) {
const root = path.resolve(dir);
const server = http.createServer((req, res) => {
const url = new URL(req.url ?? '/', 'http://localhost');
const decoded = decodeURIComponent(url.pathname);
const safe = path.normalize(decoded).replace(/^(\.\.[/\\])+/, '');
let file = path.join(root, safe);
if (!file.startsWith(root)) {
res.writeHead(403).end('forbidden');
return;
}
if (exists(file) && fs.statSync(file).isDirectory()) file = path.join(file, 'index.html');
if (!exists(file)) {
if (/\.[a-z0-9]+$/i.test(safe)) {
res.writeHead(404, { 'content-type': 'text/plain' }).end(`not found: ${safe}`);
return;
}
file = path.join(root, 'index.html');
}
const body = fs.readFileSync(file);
res.writeHead(200, {
'content-type': MIME[path.extname(file).toLowerCase()] ?? 'application/octet-stream',
'content-length': body.length,
'cache-control': 'no-store',
});
res.end(body);
});
return new Promise((resolve, reject) => {
server.on('error', reject);
server.listen(0, '127.0.0.1', () => {
const address = server.address();
resolve({
origin: `http://127.0.0.1:${address.port}`,
close: () => new Promise((done) => server.close(() => done())),
});
});
});
}
/* ------------------------------------------------------------------- routing */
/** The public origin the baked tags point at. */
export const SITE_ORIGIN = (process.env.PIG_DEMO_ORIGIN ?? 'https://demo.primeintellectgrowth.com').replace(/\/+$/, '');
/**
* Every route pattern the app declares, read out of the router source.
*
* Hard-coding `['/', '/d/:slug']` here is the exact failure this function
* exists to avoid: the router moves, prerender keeps emitting the old paths,
* and every shared link previews as the homepage again — which is the bug
* prerendering was added to fix in the first place.
*/
export function discoverRoutePatterns() {
const files = walk(abs('src'), (f) => /\.tsx?$/.test(f));
/** @type {Set<string>} */
const patterns = new Set();
for (const file of files) {
const src = read(file);
for (const m of src.matchAll(/<Route\b[^>]*?\bpath\s*=\s*(?:"([^"]*)"|'([^']*)'|\{\s*['"]([^'"]*)['"]\s*\})/g)) {
const value = m[1] ?? m[2] ?? m[3];
if (typeof value === 'string') patterns.add(value);
}
// `path:` is a common key name, so only trust it in a file that is
// demonstrably a router config.
if (/createBrowserRouter|createHashRouter|createMemoryRouter|RouteObject/.test(src)) {
for (const m of src.matchAll(/\bpath\s*:\s*(?:"([^"]*)"|'([^']*)')/g)) {
const value = m[1] ?? m[2];
if (typeof value === 'string') patterns.add(value);
}
}
}
return [...patterns];
}
/**
* Which set fills a pattern's one parameter.
*
* Decided by the STATIC segment in front of it, not by the parameter's name.
* This router calls both of them `:slug` — `demos/:slug` and `verticals/:slug`
* — so a name-based reader confidently prerenders `/verticals/wordle`, twelve
* 404s, and no vertical pages at all.
*/
function fillFor(pattern) {
const head = pattern.replace(/^\/+/, '').split('/')[0]?.toLowerCase() ?? '';
if (/^(demos?|d)$/.test(head)) return 'demo';
if (/^(verticals?|v|sectors?)$/.test(head)) return 'vertical';
const param = pattern.match(/:([A-Za-z0-9_]+)/)?.[1] ?? '';
if (/^(vertical|verticalId|sector|category)$/.test(param)) return 'vertical';
if (/^(slug|demo|demoSlug|id)$/.test(param)) return 'demo';
return null;
}
/**
* Expands the router's patterns against the real data into concrete paths.
*
* Child paths are taken as written. This router nests everything exactly one
* level under `/`, so a child's `path` is already the full path; a second level
* of nesting would need the parent prefix joined on, and this would silently
* emit the wrong routes rather than fail. If you nest deeper, fix this.
*
* @param {{metas: Map<string, any>, verticals: {id: string}[]}} data
* @returns {{routes: {path: string, kind: string, slug?: string, id?: string}[], errors: string[]}}
*/
export function expandRoutes(data) {
const patterns = discoverRoutePatterns();
/** @type {string[]} */
const errors = [];
/** @type {Map<string, any>} */
const routes = new Map();
const add = (p, record) => {
const normalised = p === '/' || p === '' ? '/' : `/${p.replace(/^\/+|\/+$/g, '')}`;
if (!routes.has(normalised)) routes.set(normalised, { path: normalised, ...record });
};
if (patterns.length === 0) {
errors.push(
'no route patterns found under src/. Looked for `<Route path=...>` and, in files that mention ' +
'createBrowserRouter/RouteObject, `path: ...`. Refusing to guess a route list.',
);
return { routes: [], errors };
}
const demoSlugList = [...data.metas.keys()];
const verticalIds = data.verticals.map((v) => v.id);
for (const pattern of patterns) {
if (pattern.includes('*')) continue; // the catch-all becomes 404.html, not a route
const params = [...pattern.matchAll(/:([A-Za-z0-9_]+)\??/g)].map((m) => m[1]);
if (params.length === 0) {
add(pattern, pattern === '/' || pattern === '' ? { kind: 'home' } : { kind: 'static' });
continue;
}
if (params.length > 1) {
errors.push(`route pattern "${pattern}" has more than one parameter; prerender cannot expand it.`);
continue;
}
const fill = fillFor(pattern);
const substitute = (value) => pattern.replace(/:[A-Za-z0-9_]+\??/, value);
if (fill === 'vertical') {
if (!verticalIds.length) errors.push(`route "${pattern}" needs verticals, but none were readable from ${rel(VERTICALS_FILE)}.`);
for (const id of verticalIds) add(substitute(id), { kind: 'vertical', id });
} else if (fill === 'demo') {
if (!demoSlugList.length) errors.push(`route "${pattern}" needs demos, but no demo meta was readable under src/demos/.`);
for (const slug of demoSlugList) add(substitute(slug), { kind: 'demo', slug });
} else {
errors.push(
`route pattern "${pattern}" has a parameter prerender cannot fill. Put it under /demos/ or ` +
'/verticals/, or teach fillFor() in scripts/_lib.mjs about it.',
);
}
}
if (!routes.has('/')) add('/', { kind: 'home' });
return { routes: [...routes.values()].sort((a, b) => (a.path < b.path ? -1 : 1)), errors };
}
/* ----------------------------------------------------------------- sizes */
export const gzipSize = (buf) => zlib.gzipSync(buf, { level: 9 }).length;
export const kb = (bytes) => `${(bytes / 1024).toFixed(1)} kB`;
/** Padded plain-text table. Beats taking a dependency to print eight rows. */
export function table(headers, rows) {
const all = [headers, ...rows];
const widths = headers.map((_, i) => Math.max(...all.map((r) => String(r[i] ?? '').length)));
const line = (cells, pad = ' ') =>
cells
.map((c, i) => (i === 0 ? String(c ?? '').padEnd(widths[i], pad) : String(c ?? '').padStart(widths[i], pad)))
.join(' ');
return [line(headers), line(widths.map((w) => '-'.repeat(w)), '-'), ...rows.map((r) => line(r))].join('\n');
}