import { readdir, readFile, writeFile, access } from 'fs/promises'; import { join, resolve, dirname } from 'path'; import { fileURLToPath } from 'url'; import matter from 'gray-matter'; import { marked } from 'marked'; import nunjucks from 'nunjucks'; const __dir = dirname(fileURLToPath(import.meta.url)); const root = resolve(__dir, '..'); // Nunjucks: load templates relative to project root const env = nunjucks.configure(join(root, 'templates'), { autoescape: true }); async function fileExists(path) { try { await access(path); return true; } catch { return false; } } async function loadStories() { const contentDir = join(root, 'content'); const files = (await readdir(contentDir)).filter(f => f.endsWith('.md')).sort(); const stories = []; let finale = null; for (const file of files) { const raw = await readFile(join(contentDir, file), 'utf-8'); const { data, content } = matter(raw); if (data.type === 'front-matter') continue; if (data.type === 'finale') { finale = data; continue; } // Split body by --- into individual scene texts const sceneTexts = content.split(/\n---\n/).map(t => t.trim()).filter(Boolean); const scenes = await Promise.all( (data.scenes || []).map(async (scene, i) => { const imagePath = join(root, scene.image); const imageExists = await fileExists(imagePath); return { ...scene, imageExists, // Relative path from output/book.html back to project root image: `../${scene.image}`, html: marked.parse(sceneTexts[i] || ''), }; }) ); stories.push({ ...data, scenes }); } return { stories, finale: finale || {} }; } async function build() { const { stories, finale } = await loadStories(); const html = env.render('book.html', { stories, finale }); const outPath = join(root, 'output', 'book.html'); await writeFile(outPath, html, 'utf-8'); const pageCount = stories.reduce((acc, s) => acc + s.scenes.length * 2, 0) + 5; console.log(`Built output/book.html — ${stories.length} stories, ~${pageCount} pages`); } build().catch(err => { console.error(err); process.exit(1); });