#!/usr/bin/env node
/**
* SOLARIA STATIC SITE GENERATOR
*
* Pure recursion. No pain. Just joy.
*
* Input: index.json
* Output: static HTML files in /dist
*/
import { promises as fs } from 'fs';
import path from 'path';
const ROOT = 'public';
const OUTPUT = 'dist';
// Simple markdown to HTML converter
function mdToHtml(md) {
if (!md) return '';
// Remove frontmatter if present
md = md.replace(/^---[\s\S]*?---/, '');
return md
// Headers
.replace(/^### (.*$)/gim, '
$1
')
.replace(/^## (.*$)/gim, '$1
')
.replace(/^# (.*$)/gim, '$1
')
// Bold/Italic
.replace(/\*\*(.*)\*\*/gim, '$1')
.replace(/\*(.*)\*/gim, '$1')
// Links
.replace(/\[(.*?)\]\((.*?)\)/gim, '$1')
// Blockquotes
.replace(/^> (.*$)/gim, '$1
')
// Horizontal rules
.replace(/^---$/gim, '
')
// Line breaks
.replace(/\n/g, '
');
}
// Extract content from fieldnote file
async function readFieldnote(filePath) {
// filePath already includes 'fieldnotes/' prefix
const fullPath = path.join(ROOT, filePath);
try {
const content = await fs.readFile(fullPath, 'utf8');
const body = content.replace(/^---[\s\S]*?---/, '');
return mdToHtml(body.trim());
} catch {
return '';
}
}
// Generate index.html
function generateIndex(data) {
const pinned = data.flat
.filter(f => f.order > 0)
.sort((a, b) => a.order - b.order);
const others = data.flat
.filter(f => f.order === 0 && !f.isIndex)
.sort((a, b) => new Date(b.originalDate) - new Date(a.originalDate));
const pinnedHTML = pinned.map(f => `
${f.originalDate} • ${f.authors.join(', ')}
${f.excerpt ? f.excerpt.substring(0, 200) : ''}
`).join('\n');
return `
The Fold Within Earth
Recent
${others.slice(0, 10).map(f => `
- ${f.originalDate || '—'}
${f.title}
`).join('\n')}
`;
}
// Generate fieldnote page
function generateFieldnoteHtml(file, content) {
return `
${file.title}
${file.title}
${file.originalDate || '—'} • ${file.authors.join(', ')}
${content}
`;
}
// MAIN
async function main() {
console.log('🔮 Solaria Static Site Generator');
console.log('=================================\n');
// Read index data
const indexData = JSON.parse(await fs.readFile(path.join(ROOT, 'index.json'), 'utf8'));
console.log('📄 Loaded', indexData.flat.length, 'items');
console.log('📌 Pinned:', indexData.flat.filter(f => f.order > 0).length);
// Ensure output directory
await fs.mkdir(OUTPUT, { recursive: true });
await fs.mkdir(path.join(OUTPUT, 'fieldnotes'), { recursive: true });
// Copy CSS
await fs.copyFile(path.join(ROOT, 'style.css'), path.join(OUTPUT, 'style.css'));
console.log('📋 style.css copied');
// Generate index.html
const indexHTML = generateIndex(indexData);
await fs.writeFile(path.join(OUTPUT, 'index.html'), indexHTML);
console.log('✅ index.html');
// Generate fieldnote pages
const fieldnotes = indexData.flat.filter(f => !f.isIndex && f.ext === '.md');
console.log('\n📝 Generating', fieldnotes.length, 'fieldnotes...');
for (const file of fieldnotes) {
const content = await readFieldnote(file.path);
if (!content) {
console.log('⚠️ Empty content for:', file.name);
}
const html = generateFieldnoteHtml(file, content);
const outPath = path.join(OUTPUT, 'fieldnotes', file.name.replace('.md', '.html'));
await fs.writeFile(outPath, html);
}
console.log('✅', fieldnotes.length, 'fieldnote pages');
console.log('\n🎉 Done! Static site in /dist');
}
main().catch(console.error);