All checks were successful
Deploy to Production / Deploy to Server (push) Successful in 1m55s
- Build script: scripts/build-pages.js (zero dependencies, Node.js only) - Shared partials: nav.html, footer.html (single source of truth) - 5 page templates in templates/pages/ - Output is byte-for-byte identical to current production - Run: npm run build:pages - Also fixes JSON-LD: 2,500 → 5,000 PDFs/month (was inconsistent)
75 lines
2.5 KiB
JavaScript
75 lines
2.5 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Build-time HTML templating system for DocFast.
|
|
* No dependencies — uses only Node.js built-ins.
|
|
*
|
|
* - Reads page sources from templates/pages/*.html
|
|
* - Reads partials from templates/partials/*.html
|
|
* - Replaces {{> partial_name}} with partial content
|
|
* - Supports <!-- key: value --> metadata comments at top of page files
|
|
* - Replaces {{key}} variables with extracted metadata
|
|
* - Writes output to public/
|
|
*/
|
|
|
|
import { readFileSync, writeFileSync, readdirSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
const ROOT = join(__dirname, '..');
|
|
const PAGES_DIR = join(ROOT, 'templates', 'pages');
|
|
const PARTIALS_DIR = join(ROOT, 'templates', 'partials');
|
|
const OUTPUT_DIR = join(ROOT, 'public');
|
|
|
|
// Load all partials
|
|
const partials = {};
|
|
for (const file of readdirSync(PARTIALS_DIR)) {
|
|
if (!file.endsWith('.html')) continue;
|
|
const name = file.replace('.html', '');
|
|
partials[name] = readFileSync(join(PARTIALS_DIR, file), 'utf-8');
|
|
}
|
|
|
|
console.log(`Loaded ${Object.keys(partials).length} partials: ${Object.keys(partials).join(', ')}`);
|
|
|
|
// Process each page
|
|
const pages = readdirSync(PAGES_DIR).filter(f => f.endsWith('.html'));
|
|
console.log(`Processing ${pages.length} pages...`);
|
|
|
|
for (const file of pages) {
|
|
let content = readFileSync(join(PAGES_DIR, file), 'utf-8');
|
|
|
|
// Extract all <!-- key: value --> metadata comments from the top
|
|
const vars = {};
|
|
while (true) {
|
|
const m = content.match(/^<!--\s*([a-zA-Z_-]+):\s*(.+?)\s*-->\n?/);
|
|
if (!m) break;
|
|
vars[m[1]] = m[2];
|
|
content = content.slice(m[0].length);
|
|
}
|
|
|
|
// Replace {{> partial_name}} with partial content (support nested partials)
|
|
let maxDepth = 5;
|
|
while (maxDepth-- > 0 && content.includes('{{>')) {
|
|
content = content.replace(/\{\{>\s*([a-zA-Z0-9_-]+)\s*\}\}/g, (match, name) => {
|
|
if (!(name in partials)) {
|
|
console.warn(` Warning: partial "${name}" not found in ${file}`);
|
|
return match;
|
|
}
|
|
return partials[name];
|
|
});
|
|
}
|
|
|
|
// Replace {{variable}} with extracted metadata
|
|
content = content.replace(/\{\{([a-zA-Z_-]+)\}\}/g, (match, key) => {
|
|
if (key in vars) return vars[key];
|
|
console.warn(` Warning: variable "${key}" not defined in ${file}`);
|
|
return match;
|
|
});
|
|
|
|
// Write output
|
|
const outPath = join(OUTPUT_DIR, file);
|
|
writeFileSync(outPath, content);
|
|
console.log(` ✓ ${file} (${(content.length / 1024).toFixed(1)}KB)`);
|
|
}
|
|
|
|
console.log('Done!');
|