// The deck → artifact key rule, in ONE place, because it has already drifted once. // // A bilingual deck is TWO sources (decks.ncl: `pages[].source` overrides the deck's), and each // source is its own build, its own capture and its own asset dir — sharing them is how an English // URL ends up serving Spanish slides. So the artifact is keyed by the VARIANT (`ai-infra-es`), // not by the deck (`ai-infra`), whose bare dir holds only thumbnails. // // That rule was re-derived in three places: here (project.mjs), gen-deck-pages.nu, and the // justfile's `jq -r '.decks[].id'`. The third never learned the split. It kept handing `ai-infra` // to the artifact gate, which ENOENT'd on a deck.css that was never written there — and PASSED on // ontoref-ontologia-reflexion, whose PRE-SPLIT bundle was still lying on disk. A green gate over a // file no page loads is worse than the crash: the crash is the only reason anyone looked. // // A rule copied is a rule that drifts. Every consumer asks this module now; nobody re-derives it. // // Usage: node lib/variants.mjs → JSON [{ deck, key, source, langs }] // node lib/variants.mjs --ids → one artifact key per line import { execFileSync } from 'node:child_process' import { fileURLToPath } from 'node:url' import { join } from 'node:path' import { OUTREACH } from './paths.mjs' export function readManifest() { return JSON.parse(execFileSync( 'nickel', ['export', '--format', 'json', join(OUTREACH, 'scripts/decks/decks.ncl')], { encoding: 'utf8' }, )) } // Group a deck's pages by the source they actually come from: each distinct source is its own // build, capture and asset dir. One source → the deck's own id (nothing to disambiguate). // Several → one key per source, named by its languages, because the CSS, the fonts and the images // of two different sources are two different decks' worth of artifact. export function variants(deck) { const by = new Map() for (const p of deck.pages) { const source = p.source ?? deck.source if (!by.has(source)) by.set(source, []) by.get(source).push(p) } return [...by.entries()].map(([source, pages]) => ({ source, pages, key: by.size === 1 ? deck.id : `${deck.id}-${pages.map(p => p.lang).join('-')}`, })) } export function allVariants(manifest = readManifest()) { return manifest.decks.flatMap(d => variants(d).map(v => ({ deck: d.id, key: v.key, source: v.source, langs: v.pages.map(p => p.lang), }))) } if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) { const all = allVariants() console.log(process.argv.includes('--ids') ? all.map(v => v.key).join('\n') : JSON.stringify(all, null, 2)) }