Files
kaleidoskop/scripts/build.js
T
valknar 364cc249dc Remove finale page from book interior
The cover PDF serves this purpose. Removes the page--finale template
block, the finale data loading path in build.js, and all finale CSS.
Page count: 100 → 99 (imprint + title + TOC + 96 scene pages).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 19:55:52 +02:00

77 lines
2.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 = [];
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;
// 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;
}
async function loadFrontMatter() {
const raw = await readFile(join(root, 'content', '00-front-matter.md'), 'utf-8');
return matter(raw).data;
}
async function build() {
const [stories, frontMatter] = await Promise.all([loadStories(), loadFrontMatter()]);
const html = env.render('book.html', { stories, frontMatter });
const outPath = join(root, 'output', 'book.html');
await writeFile(outPath, html, 'utf-8');
// imprint + title page + TOC + (4 scenes × 2 pages per story)
const pageCount = 3 + stories.reduce((acc, s) => acc + s.scenes.length * 2, 0);
await writeFile(
join(root, 'output', 'book-meta.json'),
JSON.stringify({ pageCount, storyCount: stories.length, builtAt: new Date().toISOString() }, null, 2),
'utf-8'
);
console.log(`Built output/book.html — ${stories.length} stories, ${pageCount} pages`);
}
build().catch(err => { console.error(err); process.exit(1); });