import fs from 'fs/promises';
import path from 'path';
import * as esprima from 'esprima';
import * as estraverse from 'estraverse';
import * as escodegen from 'escodegen';
async function fileExists(path: string) {
try {
await fs.access(path);
return true;
} catch {
return false;
}
}
async function compressFile(filePath: string) {
try {
const content = await fs.readFile(filePath, 'utf8');
const ast = esprima.parseModule(content, { range: true, comment: true, tokens: true });
const compact = escodegen.generate(ast, { format: { compact: true } });
await fs.writeFile(filePath, compact, 'utf8');
console.log('Compressed', filePath);
} catch (err) {
console.error('Failed to compress', filePath, err);
}
}
async function removeFilesByFilter(dir: string, filter: (name: string) => boolean) {
try {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const e of entries) {
const full = path.join(dir, e.name);
if (e.isDirectory()) {
continue;
}
if (filter(e.name)) {
await fs.unlink(full);
console.log('Deleted', full);
}
}
} catch (err) {
}
}
async function removeFilesByFilterRecursive(dir: string, filter: (name: string) => boolean) {
try {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const e of entries) {
const full = path.join(dir, e.name);
if (e.isDirectory()) {
await removeFilesByFilterRecursive(full, filter);
continue;
}
if (filter(e.name)) {
await fs.unlink(full);
console.log('Deleted', full);
}
}
} catch (err) {
}
}
async function processApis(apisDir: string) {
try {
const entries = await fs.readdir(apisDir, { withFileTypes: true });
for (const e of entries) {
if (!e.isFile()) continue;
if (!e.name.endsWith('.js')) continue;
const full = path.join(apisDir, e.name);
let content = await fs.readFile(full, 'utf8');
try {
const ast = esprima.parseModule(content, { range: true, comment: true, tokens: true });
let exportData: any = null;
estraverse.traverse(ast, {
enter(node) {
if (node.type === 'ExportDefaultDeclaration' && node.declaration) {
const code = escodegen.generate(node.declaration);
try {
exportData = new Function('return ' + code)();
} catch (err) {
console.error('Failed to evaluate', full, err);
}
}
},
});
if (!exportData || !exportData.apis) {
console.log('Skipping', full, '- no apis found');
continue;
}
const mdContent = convertToMarkdown(exportData);
const mdPath = full.replace(/\.js$/, '.md');
await fs.unlink(full);
await fs.writeFile(mdPath, mdContent, 'utf8');
console.log('Processed API', full, '->', mdPath);
} catch (parseErr) {
console.error(
'Failed to process',
full,
'-',
parseErr instanceof Error ? parseErr.message : parseErr
);
}
}
} catch (err) {
console.error('processApis failed', apisDir, err);
}
}
function convertToMarkdown(data: any): string {
const lines: string[] = [];
if (data.apis && Array.isArray(data.apis)) {
for (const api of data.apis) {
lines.push(`## ${api.name}`);
lines.push('');
if (api.props && api.props.length > 0) {
lines.push('### Props');
lines.push('');
lines.push('| 属性名 | 类型 | 默认值 | 说明 |');
lines.push('|--------|------|--------|------|');
for (const prop of api.props) {
const name = prop.name || '';
const type = prop.type || '';
const defaultValue = prop.defaultValue !== undefined ? prop.defaultValue : '';
const desc = prop.desc && prop.desc['zh-CN'] ? prop.desc['zh-CN'] : '';
lines.push(
`| ${escapeTableCell(name)} | ${escapeTableCell(type)} | ${escapeTableCell(
String(defaultValue)
)} | ${escapeTableCell(desc)} |`
);
}
lines.push('');
}
if (api.events && api.events.length > 0) {
lines.push('### Events');
lines.push('');
lines.push('| 事件名 | 回调参数 | 说明 |');
lines.push('|--------|----------|------|');
for (const event of api.events) {
const name = event.name || '';
const type = event.type || '';
const desc = event.desc && event.desc['zh-CN'] ? event.desc['zh-CN'] : '';
lines.push(
`| ${escapeTableCell(name)} | ${escapeTableCell(type)} | ${escapeTableCell(desc)} |`
);
}
lines.push('');
}
if (api.methods && api.methods.length > 0) {
lines.push('### Methods');
lines.push('');
lines.push('| 方法名 | 返回值 | 说明 |');
lines.push('|--------|--------|------|');
for (const method of api.methods) {
const name = method.name || '';
const type = method.type || '';
const desc = method.desc && method.desc['zh-CN'] ? method.desc['zh-CN'] : '';
lines.push(
`| ${escapeTableCell(name)} | ${escapeTableCell(type)} | ${escapeTableCell(desc)} |`
);
}
lines.push('');
}
if (api.slots && api.slots.length > 0) {
lines.push('### Slots');
lines.push('');
lines.push('| 插槽名 | 说明 |');
lines.push('|--------|------|');
for (const slot of api.slots) {
const name = slot.name || '';
const desc = slot.desc && slot.desc['zh-CN'] ? slot.desc['zh-CN'] : '';
lines.push(`| ${escapeTableCell(name)} | ${escapeTableCell(desc)} |`);
}
lines.push('');
}
}
}
if (data.types && Array.isArray(data.types)) {
lines.push('## Types');
lines.push('');
for (const type of data.types) {
lines.push(`### ${type.name}`);
lines.push('');
if (type.code) {
lines.push('```typescript');
lines.push(type.code.trim());
lines.push('```');
lines.push('');
}
}
}
return lines.join('\n');
}
function escapeTableCell(text: string): string {
if (!text) return '';
return String(text).replace(/\|/g, '\\|').replace(/\n/g, '<br>');
}
async function processDemos(demosDir: string) {
try {
const componentDirs = await fs.readdir(demosDir, { withFileTypes: true });
for (const componentDir of componentDirs) {
if (!componentDir.isDirectory()) continue;
const componentName = componentDir.name;
const webdocPath = path.join(demosDir, componentName, 'webdoc');
if (!(await fileExists(webdocPath))) continue;
const jsFiles = await fs.readdir(webdocPath, { withFileTypes: true });
for (const jsFile of jsFiles) {
if (!jsFile.isFile() || !jsFile.name.endsWith('.js')) continue;
const jsFilePath = path.join(webdocPath, jsFile.name);
try {
const content = await fs.readFile(jsFilePath, 'utf8');
const ast = esprima.parseModule(content, { range: true, comment: true, tokens: true });
let demosData: any[] = [];
estraverse.traverse(ast, {
enter(node) {
if (node.type === 'ExportDefaultDeclaration' && node.declaration) {
const code = escodegen.generate(node.declaration);
try {
const exportData = new Function('return ' + code)();
if (exportData && exportData.demos && Array.isArray(exportData.demos)) {
demosData = exportData.demos;
}
} catch (err) {
console.error('Failed to evaluate', jsFilePath, err);
}
}
},
});
if (demosData.length === 0) {
console.log('Skipping', jsFilePath, '- no demos found');
continue;
}
const mdContent = convertDemosToMarkdown(demosData, componentName);
const mdFilePath = jsFilePath.replace(/\.js$/, '.md');
await fs.unlink(jsFilePath);
await fs.writeFile(mdFilePath, mdContent, 'utf8');
console.log('Processed Demo', jsFilePath, '->', mdFilePath);
} catch (parseErr) {
console.error(
'Failed to process demo',
jsFilePath,
'-',
parseErr instanceof Error ? parseErr.message : parseErr
);
}
}
}
} catch (err) {
console.error('processDemos failed', demosDir, err);
}
}
function convertDemosToMarkdown(demos: any[], componentName: string): string {
const lines: string[] = [];
lines.push(`# ${componentName} Demos`);
lines.push('');
lines.push('| demoId | 名称 | 描述 | 代码文件 |');
lines.push('|--------|------|------|----------|');
for (const demo of demos) {
const demoId = demo.demoId || '';
const name = demo.name && demo.name['zh-CN'] ? demo.name['zh-CN'] : '';
const desc = demo.desc && demo.desc['zh-CN'] ? demo.desc['zh-CN'] : '';
let codeFilesStr = '';
if (demo.codeFiles && Array.isArray(demo.codeFiles)) {
codeFilesStr = demo.codeFiles.map((file: string) => `${componentName}/${file}`).join('<br>');
}
lines.push(
`| ${escapeTableCell(demoId)} | ${escapeTableCell(name)} | ${escapeTableCell(
desc
)} | ${escapeTableCell(codeFilesStr)} |`
);
}
lines.push('');
return lines.join('\n');
}
async function process() {
const target = '../skills/tiny-vue-skill';
const menusPath = path.join(target, 'menus.js');
if (await fileExists(menusPath)) {
await compressFile(menusPath);
}
const webdocDir = path.join(target, 'webdoc');
await removeFilesByFilter(webdocDir, (name) => {
if (name.endsWith('-en.md')) return true;
const lower = name.toLowerCase();
return (
lower.startsWith('changelog') || lower.startsWith('aui') || lower.startsWith('introduce')
);
});
const apisDir = path.join(target, 'apis');
await processApis(apisDir);
const demosDir = path.join(target, 'demos');
await removeFilesByFilterRecursive(
demosDir,
(name) => name.endsWith('.md') || name.endsWith('.spec.ts')
);
await processDemos(demosDir);
const iconGroups = path.join(demosDir, 'icon', 'iconGroups.js');
if (await fileExists(iconGroups)) {
await compressFile(iconGroups);
}
console.log('Done.');
}
process().catch((err) => {
console.error(err);
process.exit(1);
});