chore: scripts for decks

This commit is contained in:
Jesús Pérez 2026-07-17 01:00:53 +01:00
parent c0a31e4813
commit 378aa1e064
3 changed files with 74 additions and 32 deletions

View file

@ -27,6 +27,15 @@ def manifest [] {
^nickel export --format json $f | from json
}
# The deck→artifact keys, from lib/variants.mjs — the one authority on the rule. This file used to
# mirror it by hand ("the variant key mirrors lib/project.mjs"), and a mirror is a copy that drifts:
# a THIRD copy in site/justfile never learned that a bilingual deck is one source per language, and
# gated an asset dir that does not exist. Ask, do not re-derive.
def variants [] {
let script = [(outreach) "scripts" "decks" "lib" "variants.mjs"] | path join
^node $script | from json
}
# ONE dist PER DECK. `slidev build` builds a single entry, so a shared dist/ means the second
# deck silently captures the FIRST one's slides — the projection would be wrong and nothing
# would say so. The deck's own build dir is what makes this a pipeline instead of a one-off.
@ -66,14 +75,9 @@ def ensure-build [id: string, source: string, --force] {
def main [--dry-run, --force] {
# One build per SOURCE, not per deck: a bilingual deck is two sources, and sharing a build is
# how an English URL ends up serving Spanish slides. The variant key mirrors lib/project.mjs.
for d in (manifest).decks {
let sources = ($d.pages | each {|p| $p.source? | default $d.source } | uniq)
for src in $sources {
let langs = ($d.pages | where {|p| ($p.source? | default $d.source) == $src } | get lang)
let key = if ($sources | length) == 1 { $d.id } else { $"($d.id)-($langs | str join '-')" }
ensure-build $key $src --force=$force
}
# how an English URL ends up serving Spanish slides.
for v in (variants) {
ensure-build $v.key $v.source --force=$force
}
let flags = if $dry_run { ["--dry-run"] } else { [] }
let script = [(outreach) "scripts" "decks" "lib" "project.mjs"] | path join

View file

@ -8,7 +8,6 @@
// site/public/images/decks/<id>/fonts/* woff2, latin + latin-ext
// site/content/resources/<lang>/decks/<id>.md body replaced, FRONTMATTER PRESERVED
import { readFile, writeFile, mkdir } from 'node:fs/promises'
import { execFileSync } from 'node:child_process'
import vm from 'node:vm'
import { join, dirname } from 'node:path'
import { capture } from './capture.mjs'
@ -17,14 +16,13 @@ import { projectAssets } from './project-assets.mjs'
import { fetchFonts } from './fetch-fonts.mjs'
import { buildBody } from './build-body.mjs'
import { readAnchors } from './anchors.mjs'
import { OUTREACH, distDir, publicBase, publicDir, contentFile } from './paths.mjs'
import { readManifest, variants } from './variants.mjs'
import { distDir, publicBase, publicDir, contentFile } from './paths.mjs'
const DRY = process.argv.includes('--dry-run')
const apply = !DRY
const manifest = JSON.parse(
execFileSync('nickel', ['export', '--format', 'json', join(OUTREACH, 'scripts/decks/decks.ncl')], { encoding: 'utf8' })
)
const manifest = readManifest()
const { boundary_allow: ALLOW, slide: { width: W, height: H }, i18n: I18N, category: CATEGORY } = manifest
// The generated body is fenced. Anything outside the markers is the author's and is left
@ -40,25 +38,6 @@ function replaceBody(existing, generated) {
const report = []
// A deck may have one source per language. Group its pages by the source they actually come
// from: each distinct source is its own build, its own capture and its own asset dir. Sharing a
// capture across two sources is how an English URL ends up serving Spanish slides.
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)
}
// One source → the deck's own id. Several → one asset dir per source, keyed by its languages,
// because the CSS, the fonts and the images of two different decks are two different things.
return [...by.entries()].map(([source, pages]) => ({
source,
pages,
key: by.size === 1 ? deck.id : `${deck.id}-${pages.map(p => p.lang).join('-')}`,
}))
}
for (const deck of manifest.decks) {
for (const variant of variants(deck)) {
const { key: id, pages } = variant

View file

@ -0,0 +1,59 @@
// 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))
}