#!/usr/bin/env node
/**
* Proves the Open Graph cards, two ways. `npm run og:proof`, after a build.
*
* WHY THIS EXISTS AT ALL. `AGENTS.md` R15: *"Nobody on this project will ever
* see the defect. A link preview is rendered by LinkedIn, Slack and Teams for a
* reader who is not us."* Generating the cards does not fix that — it moves the
* invisible thing from "wrong image" to "wrong image, generated". So the two
* failures that would stay invisible are checked mechanically:
*
* 1. **Every page's `og:image` resolves to a file that exists in `dist/`.** A
* 404 preview image renders as a blank card, and nothing else in this repo
* would notice. Checked by reading the built HTML, not the source.
*
* 2. **Every card's headline and eyebrow are its page's own `
` and first
* `.eyebrow`, character for character.** This is the compliance half. Text
* baked into a JPEG is text `npm run check:claims` cannot grep, and under D20
* that script is the only per-step claims control there is — so a card must
* never carry a claim its page does not already make in auditable HTML. The
* check enforces that structurally rather than trusting an author to
* remember it, and it fails in both directions: editing the page without the
* registry, or the registry without the page.
*
* It also catches the quiet one: a straight apostrophe in the registry
* against the typographic apostrophe the page renders. Found exactly that on
* the first run, on `/practice/insurance/`.
*
* It reads `src/data/og-cards.ts` DIRECTLY — Node strips the types — so there is
* no second list of cards to keep in step with the first.
*
* Optional: `npm run og:proof -- --sheet` writes a contact sheet of every card
* to `dist/og-proof.jpg` so the set can be looked at in one go. Not part of the
* check; a human still has to look.
*/
import { readdir, readFile, stat, writeFile } from 'node:fs/promises';
import { join, relative, sep } from 'node:path';
import sharp from 'sharp';
import {
OG_CARDS,
PORTRAIT_PAGES,
articleCard,
ogCardPath,
} from '../src/data/og-cards.ts';
const ROOT = process.cwd();
const DIST = join(ROOT, 'dist');
const SITE = 'https://adr.smlcompany.ca';
const strip = (html) =>
html
.replace(/<[^>]+>/g, '')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/ /g, ' ')
.replace(/\s+/g, ' ')
.trim();
async function pages(dir = DIST) {
const out = [];
for (const entry of await readdir(dir, { withFileTypes: true })) {
const full = join(dir, entry.name);
if (entry.isDirectory()) out.push(...(await pages(full)));
else if (entry.name === 'index.html') {
const rel = relative(DIST, dir).split(sep).filter(Boolean).join('/');
out.push({ path: rel ? `/${rel}/` : '/', file: full });
}
}
return out.sort((a, b) => a.path.localeCompare(b.path));
}
const problems = [];
const fail = (msg) => problems.push(msg);
try {
await stat(join(DIST, 'index.html'));
} catch {
console.error('dist/ is missing or empty. Run `npm run build` first.');
process.exit(2);
}
const built = await pages();
const seenCards = new Set();
let checkedHeadlines = 0;
let checkedArticles = 0;
/**
* An article's expected card, from the SAME `articleCard()` the endpoint calls,
* given the title in that article's own frontmatter. Read from the `.mdx` rather
* than from the built page, so the comparison has two independent sides: what
* the article says its title is, and what the route rendered as the ``.
*/
async function expectedArticleCard(path) {
const slug = path.replace(/^\/insights\/|\/$/g, '');
for (const ext of ['mdx', 'md']) {
try {
const src = await readFile(
join(ROOT, 'src', 'content', 'insights', `${slug}.${ext}`),
'utf8',
);
const m = /^title:\s*(.*)$/m.exec(src);
if (!m) break;
let title = m[1].trim();
// YAML scalar: strip one layer of quoting and unescape a doubled single
// quote, which is how YAML writes a literal apostrophe inside '…'.
if (
(title.startsWith("'") && title.endsWith("'")) ||
(title.startsWith('"') && title.endsWith('"'))
) {
title = title.slice(1, -1);
}
title = title.replace(/''/g, "'");
return articleCard(title);
} catch {
/* try the next extension */
}
}
return null;
}
for (const { path, file } of built) {
const html = await readFile(file, 'utf8');
// ---- 1. og:image exists -------------------------------------------------
const og = /`. Comparing the card's source of truth against the rendered
* `` is therefore the same check, and the eyebrow is the literal the
* endpoint sets.
*/
const isArticle = /^\/insights\/[^/]+\/$/.test(path);
if (!card && !isArticle) {
fail(`${path}: built, not a portrait page, and has no OG_CARDS entry`);
continue;
}
let expected_card = card;
if (!expected_card) {
expected_card = await expectedArticleCard(path);
if (!expected_card) {
fail(
`${path}: could not read a \`title:\` from this article's own .mdx, so ` +
'its card cannot be compared against anything. That is a failure, not ' +
'a skip — an unchecked card is the one surface check:claims cannot see.',
);
continue;
}
}
const expectedEyebrow = expected_card.eyebrow;
const h1 = /]*>([\s\S]*?)<\/h1>/.exec(html);
if (!h1) {
fail(`${path}: no to compare the card headline against`);
} else {
const text = strip(h1[1]);
const expectedHeadline = expected_card.headline;
if (text !== expectedHeadline) {
fail(
`${path}: card headline is not the page's .\n` +
` : ${JSON.stringify(text)}\n` +
` card: ${JSON.stringify(expectedHeadline)}`,
);
} else {
checkedHeadlines += 1;
if (isArticle) checkedArticles += 1;
}
}
const eyebrow = /
]*>([\s\S]*?)<\/p>/.exec(html);
if (!eyebrow) {
fail(`${path}: no .eyebrow to compare the card eyebrow against`);
} else {
const text = strip(eyebrow[1]);
if (text !== expectedEyebrow) {
fail(
`${path}: card eyebrow is not the page's first .eyebrow.\n` +
` page: ${JSON.stringify(text)}\n` +
` card: ${JSON.stringify(expectedEyebrow)}`,
);
}
}
}
/**
* ⚠️ AND THE COVERAGE IS ASSERTED, NOT REPORTED. Printing "20 headlines matched"
* beside a growing site is how the gap above stayed invisible: the number went up
* and never went up ENOUGH, and nothing said so. Every built page except the
* portrait pages carries a generated card, so the count must equal that or a page
* was silently skipped.
*/
const shouldCheck = built.filter(
({ path }) => !PORTRAIT_PAGES.includes(path),
).length;
if (checkedHeadlines !== shouldCheck) {
fail(
`only ${checkedHeadlines} of ${shouldCheck} non-portrait pages had their ` +
'card headline compared against their
. A page was skipped, which is ' +
'the failure this assertion exists to make loud.',
);
}
// ---- 3. no card generated for a page that does not exist -------------------
// A stray card is not a shipped defect, but it is the signature of a page that
// was renamed or removed and a registry entry that was not — which the next
// person reads as "the card exists, so the page must".
let strays = [];
try {
const files = await readdir(join(DIST, 'og'));
strays = files
.filter((f) => f.endsWith('.jpg'))
.map((f) => `/og/${f}`)
.filter((p) => !seenCards.has(p));
} catch {
fail('dist/og/ does not exist — no cards were generated');
}
for (const s of strays) {
fail(`${s}: generated, but no built page references it`);
}
// ---- optional contact sheet ----------------------------------------------
if (process.argv.includes('--sheet')) {
const files = (await readdir(join(DIST, 'og')))
.filter((f) => f.endsWith('.jpg'))
.sort();
const COLS = 3;
const W = 400;
const H = 210;
const rows = Math.ceil(files.length / COLS);
const tiles = await Promise.all(
files.map(async (f, i) => ({
input: await sharp(join(DIST, 'og', f))
.resize(W, H)
.toBuffer(),
left: (i % COLS) * W,
top: Math.floor(i / COLS) * H,
})),
);
const sheet = await sharp({
create: {
width: COLS * W,
height: rows * H,
channels: 3,
background: '#ffffff',
},
})
.composite(tiles)
.jpeg({ quality: 82 })
.toBuffer();
await writeFile(join(DIST, 'og-proof.jpg'), sheet);
console.log(
`contact sheet: dist/og-proof.jpg — ${files.length} cards, ${COLS}x${rows}`,
);
}
console.log(
`og:proof — ${built.length} built pages, ${seenCards.size} distinct og:image ` +
`targets, ${checkedHeadlines} card headlines matched their page ` +
`(${checkedArticles} of them articles).`,
);
if (problems.length) {
console.error(`\nOG CARD PROBLEMS — ${problems.length}:`);
for (const p of problems) console.error(` - ${p}`);
process.exit(1);
}
console.log(
'OK — every og:image resolves, and no card asserts anything its page does not.',
);