With posterSources on, the tree shipped only the 28 zone poster sources, so exporting any of the 176 city or 174 country posters said "poster not found (404)": 350 of 378 posters could not be printed. Each poster now ships once: its .html source where one exists, else its .webp. Generating sources for the 350 was not an option: city posters need OSM street tiles to regenerate (publish-html.mjs), and a source is the heavy form (about 1 MB each against about 0.1 MB for a raster). Measured with check:tree-weight: 102.7 MB before, 137.9 MB after, of 160 MB. The 350 rasters are the tracked data/posters/*.webp: all 350 are existing git blobs, so the repository gains no new pack bytes. New gate check:posters-exportable (in gates:post). Every poster id, from data/posters and the wall's previews, must ship a real WebP or a real poster source (its class="stage" element), so a placeholder or a host's fallback page cannot pass. It skips when posters are off by config, which check:poster-degrades covers. Poison-tested: a missing poster and a fake image each fail.
69 lines
3.5 KiB
JavaScript
69 lines
3.5 KiB
JavaScript
#!/usr/bin/env node
|
|
// check-posters-exportable — every poster a reader can ask for ships in a printable form.
|
|
//
|
|
// ★ THE DEFECT (2026-09-29). With posterSources on, the tree shipped only the 28 zone sources.
|
|
// The 176 city-* and 174 country-* posters have no source, so their export said "poster not
|
|
// found (404)": 350 of 378 posters could not be printed. generate-tree now ships each poster
|
|
// ONCE in its lightest printable form, the .html source where one exists, else the .webp.
|
|
// This gate keeps it that way.
|
|
//
|
|
// With posterSources or bundlePosters on, every poster id (from data/posters, and every wall
|
|
// card in the tree's previews/) must have, in <tree>/assets/posters:
|
|
// <id>.webp that really is a WebP (RIFF....WEBP), or
|
|
// <id>.html that really is a poster source (its `class="stage"` element),
|
|
// so a placeholder or a host's fallback page cannot pass for one.
|
|
// With both off, posters are not shipped on purpose and export refuses honestly: SKIP
|
|
// (check:poster-degrades covers that case).
|
|
//
|
|
// Static: reads files, starts nothing. Exit 0 pass or skip, 1 fail.
|
|
import { existsSync, openSync, readSync, closeSync, readFileSync, readdirSync } from 'node:fs'
|
|
import { dirname, join, resolve } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { TREE_NAME } from './_tree-name.mjs'
|
|
|
|
const REPO = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
const cfg = JSON.parse(readFileSync(join(REPO, 'vite.doxa-maps-build-config.json'), 'utf8'))
|
|
if (cfg.posterSources !== true && cfg.bundlePosters !== true) {
|
|
console.log('POSTERS-EXPORTABLE: SKIP — posterSources and bundlePosters are off, so posters are not shipped (export refuses honestly)')
|
|
process.exit(0)
|
|
}
|
|
|
|
const assets = join(REPO, 'public', TREE_NAME, 'assets')
|
|
const posters = join(assets, 'posters')
|
|
if (!existsSync(posters)) {
|
|
console.error(`POSTERS-EXPORTABLE: FAIL — ${posters} is missing; run \`bun run tree\``)
|
|
process.exit(1)
|
|
}
|
|
|
|
const stem = (f) => f.replace(/\.(webp|html)$/, '')
|
|
const ids = new Set(readdirSync(join(REPO, 'data', 'posters')).filter((f) => /\.(webp|html)$/.test(f)).map(stem))
|
|
const previews = join(assets, 'previews')
|
|
if (existsSync(previews)) for (const f of readdirSync(previews)) if (f.endsWith('.webp')) ids.add(stem(f))
|
|
|
|
const isWebp = (path) => {
|
|
const fd = openSync(path, 'r')
|
|
const b = Buffer.alloc(12)
|
|
try { readSync(fd, b, 0, 12, 0) } finally { closeSync(fd) }
|
|
return b.toString('latin1', 0, 4) === 'RIFF' && b.toString('latin1', 8, 12) === 'WEBP'
|
|
}
|
|
const isSource = (path) => readFileSync(path, 'utf8').includes('class="stage"')
|
|
|
|
const missing = []
|
|
const bad = []
|
|
let webp = 0
|
|
let html = 0
|
|
for (const id of [...ids].sort()) {
|
|
const w = join(posters, `${id}.webp`)
|
|
const h = join(posters, `${id}.html`)
|
|
if (existsSync(w)) { if (isWebp(w)) { webp += 1; continue } bad.push(`${id}.webp is not a WebP image`) }
|
|
if (existsSync(h)) { if (isSource(h)) { html += 1; continue } bad.push(`${id}.html is not a poster source (no class="stage")`) }
|
|
if (!existsSync(w) && !existsSync(h)) missing.push(id)
|
|
}
|
|
|
|
if (missing.length || bad.length) {
|
|
console.error(`POSTERS-EXPORTABLE: FAIL — ${missing.length} poster(s) with nothing printable, ${bad.length} not what they claim, of ${ids.size}`)
|
|
for (const id of missing.slice(0, 20)) console.error(` missing ${id} (neither .webp nor .html in ${TREE_NAME}/assets/posters)`)
|
|
for (const b of bad.slice(0, 20)) console.error(` bad ${b}`)
|
|
process.exit(1)
|
|
}
|
|
console.log(`POSTERS-EXPORTABLE: PASS — all ${ids.size} posters print: ${html} from their source (.html), ${webp} from a raster (.webp)`)
|