#!/usr/bin/env node /** * Prebuild script: reads content/ files and generates src/generated/content.js * Run automatically via npm prestart / prebuild hooks. */ const fs = require('fs'); const path = require('path'); const CONTENT_DIR = path.resolve(__dirname, '../content'); const OUTPUT_FILE = path.resolve(__dirname, '../src/generated/content.js'); // --- Frontmatter parser --- function parseFrontmatter(raw) { const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n)?([\s\S]*)$/); if (!match) return { frontmatter: {}, content: raw.trim() }; const lines = match[1].split('\n'); const frontmatter = {}; for (const line of lines) { const m = line.match(/^(\w[\w-]*)\s*:\s*(.*)/); if (m) frontmatter[m[1]] = m[2].trim(); } return { frontmatter, content: match[2].trim() }; } // --- Route derivation --- function routeFromDir(dirName) { // content/index.md -> / // content/about/index.md -> /about if (dirName === '') return '/'; return '/' + dirName; } // --- Load site config --- const configPath = path.join(CONTENT_DIR, '_config.json'); if (!fs.existsSync(configPath)) { console.error('Missing content/_config.json'); process.exit(1); } const siteConfig = JSON.parse(fs.readFileSync(configPath, 'utf8')); // --- Discover page dirs --- // Root-level index.md lives directly in content/ // Subdir index.md lives in content// const pages = {}; function processDir(dirPath, slug) { const indexPath = path.join(dirPath, 'index.md'); if (!fs.existsSync(indexPath)) return; const raw = fs.readFileSync(indexPath, 'utf8'); const { frontmatter, content } = parseFrontmatter(raw); // Resolve images: check dirPath/images.json first, then dirPath/images/ subfolder let images = []; const localImagesJsonPath = path.join(dirPath, 'images.json'); if (fs.existsSync(localImagesJsonPath)) { images = JSON.parse(fs.readFileSync(localImagesJsonPath, 'utf8')); } else { const imagesDirPath = path.join(dirPath, 'images'); if (fs.existsSync(imagesDirPath)) { const files = fs.readdirSync(imagesDirPath); images = files.map(f => `/content-images/${slug}/${f}`); } } const route = routeFromDir(slug); pages[route] = { frontmatter, content, images }; } function walkContent(dirPath, slug) { processDir(dirPath, slug); const entries = fs.readdirSync(dirPath, { withFileTypes: true }); for (const entry of entries) { if (!entry.isDirectory() || entry.name.startsWith('_')) continue; const childSlug = slug ? `${slug}/${entry.name}` : entry.name; walkContent(path.join(dirPath, entry.name), childSlug); } } walkContent(CONTENT_DIR, ''); // --- Write output --- const output = `// AUTO-GENERATED by scripts/build-content.js — do not edit by hand. export const siteConfig = ${JSON.stringify(siteConfig, null, 2)}; export const pages = ${JSON.stringify(pages, null, 2)}; `; fs.mkdirSync(path.dirname(OUTPUT_FILE), { recursive: true }); fs.writeFileSync(OUTPUT_FILE, output, 'utf8'); console.log(`[build-content] Generated ${OUTPUT_FILE}`); console.log(`[build-content] Pages: ${Object.keys(pages).join(', ')}`);