feat: add markdown content pipeline with build script
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
116
scripts/build-content.ts
Normal file
116
scripts/build-content.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user