#!/usr/bin/env node /** * Site Generator * * Reads markdown summaries from summaries/{id}.md and embeds them into * data/books.json, then creates a dist directory with all static assets. * * Usage: node build.js * * Run this after editing any summary markdown files. */ const fs = require('fs'); const path = require('path'); const ROOT_DIR = __dirname; const SUMMARIES_DIR = path.join(ROOT_DIR, 'summaries'); const BOOKS_JSON = path.join(ROOT_DIR, 'data', 'books.json'); const DIST_DIR = path.join(ROOT_DIR, 'dist'); function readSummary(id) { const filePath = path.join(SUMMARIES_DIR, `${id}.md`); if (!fs.existsSync(filePath)) { return null; } const content = fs.readFileSync(filePath, 'utf8').trim(); const lines = content.split('\n'); // Skip the title line (# Title) and return the rest const summaryLines = lines.slice(1).join('\n').trim(); return summaryLines || null; } /** * Recursively copies a directory from src to dest. */ function copyDir(src, dest) { fs.mkdirSync(dest, { recursive: true }); const entries = fs.readdirSync(src, { withFileTypes: true }); for (const entry of entries) { const srcPath = path.join(src, entry.name); const destPath = path.join(dest, entry.name); if (entry.isDirectory()) { copyDir(srcPath, destPath); } else { fs.copyFileSync(srcPath, destPath); } } } /** * Embeds summaries from markdown files into books.json. * Returns the updated books data. */ function embedSummaries() { console.log('Embedding summaries...\n'); const booksData = JSON.parse(fs.readFileSync(BOOKS_JSON, 'utf8')); let updated = 0; let missing = []; for (const book of booksData.books) { const summary = readSummary(book.id); if (summary) { book.summary = summary; updated++; console.log(`✓ ${book.title}`); } else { delete book.summary; missing.push(book.title); console.log(`✗ ${book.title} (no summary file)`); } } fs.writeFileSync(BOOKS_JSON, JSON.stringify(booksData, null, 2)); console.log(`\nSummaries embedded: ${updated}`); console.log(`Missing summaries: ${missing.length}`); if (missing.length > 0) { console.log('\nCreate these files to add summaries:'); missing.forEach(title => { const book = booksData.books.find(b => b.title === title); console.log(` summaries/${book.id}.md`); }); } return booksData; } /** * Creates the dist directory with all static assets for distribution. */ function createDist() { console.log('\n--- Creating dist directory ---\n'); // Remove existing dist directory if (fs.existsSync(DIST_DIR)) { fs.rmSync(DIST_DIR, { recursive: true }); } fs.mkdirSync(DIST_DIR); // Copy static files fs.copyFileSync(path.join(ROOT_DIR, 'index.html'), path.join(DIST_DIR, 'index.html')); console.log('✓ index.html'); // Copy directories copyDir(path.join(ROOT_DIR, 'css'), path.join(DIST_DIR, 'css')); console.log('✓ css/'); copyDir(path.join(ROOT_DIR, 'js'), path.join(DIST_DIR, 'js')); console.log('✓ js/'); copyDir(path.join(ROOT_DIR, 'data'), path.join(DIST_DIR, 'data')); console.log('✓ data/'); copyDir(path.join(ROOT_DIR, 'images'), path.join(DIST_DIR, 'images')); console.log('✓ images/'); console.log('\nDist directory created at: dist/'); } function build() { console.log('=== Building Site ===\n'); embedSummaries(); createDist(); console.log('\n=== Build Complete ==='); console.log('Site is ready. Open dist/index.html in a browser.'); } build();