Files
starRiverProperty/frontend-source/scripts/extract-api-calls.js
T
反编译工作区 7889f7f3b9 feat: add API call extractor script
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

Former-commit-id: 32eb1a04435e3e1adf760c8a9bd3f499f23e4c3a
2026-04-29 12:01:11 +08:00

125 lines
3.9 KiB
JavaScript

#!/usr/bin/env node
/**
* API 调用提取器
* 从格式化后的 JS 中提取 HTTP 请求模式,输出 API 清单 JSON
*
* 用法: node extract-api-calls.js <formatted-dir> <output.json>
*/
const fs = require('fs');
const path = require('path');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
function extractApiCalls(inputDir, outputFile) {
const apiCalls = [];
const files = fs.readdirSync(inputDir).filter(f => f.endsWith('.formatted.js'));
for (const file of files) {
const code = fs.readFileSync(path.join(inputDir, file), 'utf-8');
try {
const ast = parser.parse(code, {
sourceType: 'script',
plugins: ['jsx', 'typescript', 'classProperties', 'dynamicImport'],
errorRecovery: true,
});
traverse(ast, {
// Match: axios.get('/api/xxx'), axios.post('/api/xxx', data)
CallExpression(nodePath) {
const node = nodePath.node;
const callee = node.callee;
if (
callee.type === 'MemberExpression' &&
callee.object.type === 'Identifier' &&
(callee.object.name === 'axios' || callee.object.name === 'http')
) {
const method = callee.property.name;
if (['get', 'post', 'put', 'delete', 'patch'].includes(method)) {
const url = node.arguments[0];
if (url && url.type === 'StringLiteral') {
apiCalls.push({
file: file,
method: method.toUpperCase(),
url: url.value,
hasBody: method !== 'get' && node.arguments.length > 1,
});
}
}
}
// this.$http.get|post
if (
callee.type === 'MemberExpression' &&
callee.object.type === 'MemberExpression' &&
callee.object.property.name === '$http'
) {
const method = callee.property.name;
const url = node.arguments[0];
if (url && url.type === 'StringLiteral') {
apiCalls.push({
file: file,
method: method.toUpperCase(),
url: url.value,
via: '$http',
});
}
}
},
// Match string constants containing API paths
StringLiteral(nodePath) {
const value = nodePath.node.value;
if (typeof value === 'string' && value.startsWith('/api/') && value.length > 5) {
const parent = nodePath.parent;
if (parent.type === 'CallExpression') return;
apiCalls.push({
file: file,
method: 'REFERENCE',
url: value,
});
}
},
});
} catch (e) {
console.error(`解析失败: ${file}${e.message}`);
}
}
// Deduplicate + sort
const unique = {};
apiCalls.forEach(call => {
const key = `${call.method} ${call.url}`;
if (!unique[key]) {
unique[key] = call;
}
});
const result = Object.values(unique).sort((a, b) => a.url.localeCompare(b.url));
fs.writeFileSync(outputFile, JSON.stringify(result, null, 2));
console.log(`提取完成: ${result.length} 个 API 端点 → ${outputFile}`);
// Also generate Markdown report
const mdPath = outputFile.replace('.json', '.md');
let md = '# API 端点清单\n\n';
md += `| Method | URL | 来源文件 |\n`;
md += `|--------|-----|----------|\n`;
result.forEach(call => {
md += '| ' + call.method + ' | `' + call.url + '` | ' + call.file + ' |\n';
});
fs.writeFileSync(mdPath, md);
console.log(`Markdown 清单: ${mdPath}`);
}
const args = process.argv.slice(2);
if (args.length < 2) {
console.log('用法: node extract-api-calls.js <formatted-dir> <output.json>');
process.exit(1);
}
extractApiCalls(args[0], args[1]);