Files
doc_ai_frontend/scripts/build-content.ts
2026-03-25 13:26:33 +08:00

117 lines
3.2 KiB
TypeScript

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<string> {
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<string, ContentMeta[]> = {};
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);
});