import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import matter from 'gray-matter'; import { unified } from 'unified'; import remarkParse from 'remark-parse'; import remarkMath from 'remark-math'; import remarkGfm from 'remark-gfm'; import remarkRehype from 'remark-rehype'; import rehypeKatex from 'rehype-katex'; import rehypeStringify from 'rehype-stringify'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const CONTENT_DIR = path.resolve(__dirname, '../content'); const OUTPUT_DIR = path.resolve(__dirname, '../public/content'); interface ContentMeta { slug: string; title: string; description: string; date: string; tags: string[]; order?: number; cover?: string; } const processor = unified() .use(remarkParse) .use(remarkGfm) .use(remarkMath) .use(remarkRehype) .use(rehypeKatex) .use(rehypeStringify); async function compileMarkdown(content: string): Promise { const result = await processor.process(content); return String(result); } async function processContentType(type: 'docs' | 'blog') { const typeDir = path.join(CONTENT_DIR, type); const manifest: Record = {}; for (const lang of ['en', 'zh']) { const langDir = path.join(typeDir, lang); if (!fs.existsSync(langDir)) { manifest[lang] = []; continue; } const files = fs.readdirSync(langDir).filter(f => f.endsWith('.md')); const entries: ContentMeta[] = []; for (const file of files) { const filePath = path.join(langDir, file); const raw = fs.readFileSync(filePath, 'utf-8'); const { data, content } = matter(raw); const meta: ContentMeta = { slug: data.slug || file.replace(/\.md$/, ''), title: data.title || '', description: data.description || '', date: data.date ? String(data.date) : '', tags: data.tags || [], order: data.order, cover: data.cover, }; entries.push(meta); // Compile and write individual content file const html = await compileMarkdown(content); const outputPath = path.join(OUTPUT_DIR, type, lang, `${meta.slug}.json`); fs.mkdirSync(path.dirname(outputPath), { recursive: true }); fs.writeFileSync(outputPath, JSON.stringify({ meta, html }, null, 2)); console.log(` ${lang}/${meta.slug}`); } // Sort: docs by order, blog by date descending if (type === 'docs') { entries.sort((a, b) => (a.order ?? 999) - (b.order ?? 999)); } else { entries.sort((a, b) => b.date.localeCompare(a.date)); } manifest[lang] = entries; } // Write manifest const manifestPath = path.join(OUTPUT_DIR, `${type}-manifest.json`); fs.mkdirSync(path.dirname(manifestPath), { recursive: true }); fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2)); console.log(` -> ${type}-manifest.json`); } async function main() { console.log('Building content...'); // Clean output if (fs.existsSync(OUTPUT_DIR)) { fs.rmSync(OUTPUT_DIR, { recursive: true }); } await processContentType('docs'); await processContentType('blog'); console.log('Content build complete!'); } main().catch(err => { console.error('Content build failed:', err); process.exit(1); });