ontoref-outreach/scripts/decks/gen-deck-pages.nu
2026-07-17 01:00:53 +01:00

125 lines
5.6 KiB
Text

#!/usr/bin/env nu
# Project the Slidev decks onto the site as real HTML — text, not pixels.
#
# The site's deck pages used to be a hand-written carousel of exported PNGs: the words of
# every slide were an IMAGE of words. Nothing indexed them, no glossary tooltip could
# reach them, no screen reader read them, nobody could select or translate them, and the
# page drifted from the source the moment a slide changed.
#
# The deck is NOT re-parsed. Slidev's markdown is not standard markdown, and its meaning
# does not live in the markdown anyway — it lives in a compiled bundle of UnoCSS
# utilities, theme rules and <style scoped> blocks. Re-implementing that is re-implementing
# a renderer. So Slidev renders, and we capture what it produced (its /print route, the
# same one `slidev export` drives) and re-home it in the site.
#
# Usage:
# nu scripts/decks/gen-deck-pages.nu # build + project + gate
# nu scripts/decks/gen-deck-pages.nu --dry-run # project nothing, report
# nu scripts/decks/gen-deck-pages.nu check --url <page> # gate a served page
def outreach [] { $env.FILE_PWD | path dirname | path dirname }
def presentation [] { [(outreach) "presentation"] | path join }
def manifest [] {
let f = [(outreach) "scripts" "decks" "decks.ncl"] | path join
^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.
#
# --force rebuilds even if the dist exists: the whole point is that the page follows the source,
# and a stale dist is a page that quietly stopped following it.
def ensure-build [id: string, source: string, --force] {
let dest = [(presentation) "builds" $id] | path join
let built = [$dest "index.html"] | path join
let src = [(presentation) $source] | path join
# Rebuild when the SOURCE is newer than the build. Without this, editing a slide and running
# `just decks` silently captured the stale build — the whole point of the projection is that the
# page follows the source, so a build older than its source is a page that stopped following it.
# --force rebuilds regardless (e.g. after a Slidev/theme upgrade the source mtime did not move).
let stale = if ($built | path exists) {
(($src | path exists) and (($src | path expand | path type) == "file")
and (((ls $src).0.modified) > ((ls $built).0.modified)))
} else { true }
if not $force and not $stale { return }
print $"(ansi cyan)slidev build ($source) → builds/($id)(ansi reset)"
cd (presentation)
# `slidev build` IGNORES --output (it is an `export` flag; on `build` it is accepted and does
# nothing). Measured: it wrote to dist/ in both flag positions. So build where it insists, then
# move. Fighting the CLI here would be fragile; moving a directory is not.
rm -rf dist
^./node_modules/.bin/slidev build $source
if not ("dist/index.html" | path exists) {
error make { msg: $"slidev build no produjo dist/ para ($source)" }
}
mkdir builds
rm -rf $dest
mv dist $dest
}
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.
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
cd (presentation)
let out = (^node $script ...$flags | complete)
if $out.exit_code != 0 {
print $out.stdout
print $"(ansi red)($out.stderr)(ansi reset)"
error make { msg: "la proyección del deck falló" }
}
let report = ($out.stdout | from json)
print ($report.decks | select id slides css assets fuentes | table)
if $dry_run { return }
for d in $report.decks {
check-artifact $d.id
}
print $"(ansi green)decks proyectados.(ansi reset) Verifica la PÁGINA servida con: nu scripts/decks/gen-deck-pages.nu check --url <url>"
}
# Gate the CSS artifact: containment, fidelity, no inbound leak, glossary decoration.
def check-artifact [id: string] {
let script = [(outreach) "scripts" "decks" "gates" "artifact.mjs"] | path join
cd (presentation)
let r = (^node $script $id | complete)
print $r.stdout
if $r.exit_code != 0 { error make { msg: $"puertas del artefacto FALLAN para ($id)" } }
}
# Gate the page as served. Verifying the file proves nothing: the site's content pipeline
# sits between this generator and the reader.
def "main check" [--url: string] {
if ($url | is-empty) { error make { msg: "check necesita --url <url-de-la-pagina>" } }
let script = [(outreach) "scripts" "decks" "gates" "served.mjs"] | path join
cd (presentation)
let r = (^node $script $url | complete)
print $r.stdout
if $r.exit_code != 0 {
print $"(ansi red)($r.stderr)(ansi reset)"
error make { msg: "puertas de la página servida FALLAN" }
}
print $"(ansi green)puertas de la página servida: OK(ansi reset)"
}