scripts/generate-md/generateMd.mjs (153 lines of code) (raw):
/**
* 使用前需要在env.process中配置IDEALAB_API_KEY和IDEALAB_BASE_URL
* @see https://aliyuque.antfin.com/wushuxuan.wsx/mobfr5/gwebrbfu6dkl0zlk?singleDoc#%20
* @example node scripts/generate-md/generateMd.mjs
* @example node scripts/generate-md/generateMd.mjs --all
* @example node scripts/generate-md/generateMd.mjs --component Button
*/
import OpenAI from 'openai';
import dotenv from 'dotenv';
import minimist from 'minimist';
import ora from 'ora';
import fs from 'fs';
import path from 'path';
import {
getAllCodes,
getComponentName,
CONFIG,
saveMarkdown,
} from './help.mjs';
import { generateSystemPrompt, generateUserPrompt } from './prompt.mjs';
dotenv.config();
// 初始化 OpenAI 客户端
const initOpenAIClient = () => {
return new OpenAI({
apiKey: process.env.IDEALAB_API_KEY,
baseURL: process.env.IDEALAB_BASE_URL,
});
};
// 生成 Markdown
const generateMarkdown = async (params) => {
const spinner = ora(
`AI 正在为 ${params.componentName} 生成markdown文档...`,
).start();
try {
const client = initOpenAIClient();
const response = await client.chat.completions.create({
model: 'claude37_sonnet',
messages: [
{
role: 'system',
content: generateSystemPrompt(params.mdFileName),
},
{
role: 'user',
content: generateUserPrompt({
componentName: params.componentName,
referenceDoc: {
fileName: params.mdFileName,
code: {
component: params.referenceCodes.componentCode,
types: params.referenceCodes.typesCode,
styles: params.referenceCodes.styleCode,
},
format: params.mdFormat,
},
targetDoc: {
code: {
component: params.targetCodes.componentCode,
types: params.targetCodes.typesCode,
styles: params.targetCodes.styleCode,
layout: params.stackCode,
},
},
}),
},
],
stream: true,
});
// 处理流式响应
let fullContent = '';
for await (const chunk of response) {
const content = chunk.choices[0]?.delta?.content || '';
fullContent += content;
}
return fullContent.trim();
} finally {
spinner.stop();
}
};
// 获取所有组件名称
const getAllComponentNames = () => {
const componentsDir = path.resolve(process.cwd(), 'packages/bui-core/src');
try {
const entries = fs.readdirSync(componentsDir, { withFileTypes: true });
return entries
.filter((entry) => entry.isDirectory())
.map((dir) => dir.name)
.filter(
(name) =>
!name.startsWith('.') && name !== 'utils' && name !== 'styles',
);
} catch (error) {
console.error('读取组件目录失败:', error.message);
return [];
}
};
// 为单个组件生成文档
async function generateForComponent(componentName, mdFileName) {
try {
// 获取所有必要的代码
const { targetCodes, referenceCodes, stackCode, mdFormat } =
await getAllCodes(componentName, mdFileName);
// 生成 Markdown
const markdown = await generateMarkdown({
componentName,
mdFileName,
targetCodes,
referenceCodes,
stackCode,
mdFormat,
});
// 保存文件
const outputPath = saveMarkdown(componentName, markdown);
console.log(`${componentName} 的Markdown文档已生成: ${outputPath}`);
return true;
} catch (error) {
console.error(`为 ${componentName} 生成文档失败:`, error.message);
return false;
}
}
async function main() {
try {
// 获取命令行参数
const args = minimist(process.argv.slice(2));
const mdFileName = args.md || CONFIG.DEFAULT_MD_FILE;
const generateAll = args.all || false;
const specifiedComponent = args.component;
if (generateAll) {
// 批量生成所有组件的文档
const componentNames = getAllComponentNames();
console.log(`找到 ${componentNames.length} 个组件,开始批量生成文档...`);
let successCount = 0;
for (const componentName of componentNames) {
const success = await generateForComponent(componentName, mdFileName);
if (success) successCount++;
}
console.log(
`批量生成完成,成功: ${successCount}/${componentNames.length}`,
);
} else if (specifiedComponent) {
// 为指定组件生成文档
await generateForComponent(specifiedComponent, mdFileName);
} else {
// 交互式选择组件
const componentName = await getComponentName();
await generateForComponent(componentName, mdFileName);
}
} catch (error) {
console.error('生成文档失败:', error.message);
process.exit(1);
}
}
main();