- scanner.ts: 添加 DELETE 清空旧数据语句,避免重复插入 - ts-scanner: 改用 regex 替代 ts-morph 解析(避免解析失败) - go-scanner/py-scanner/proto-scanner: 完善实现
93 lines
2.5 KiB
TypeScript
93 lines
2.5 KiB
TypeScript
import path from "node:path";
|
|
import fs from "node:fs";
|
|
import type { Database as DBType } from "better-sqlite3";
|
|
|
|
interface ScanStats {
|
|
contracts: number;
|
|
}
|
|
|
|
// Proto 符号正则
|
|
const PROTO_SERVICE_RE = /^service\s+(\w+)\s*\{/gm;
|
|
const PROTO_MESSAGE_RE = /^message\s+(\w+)\s*\{/gm;
|
|
const PROTO_RPC_RE = /^\s*rpc\s+(\w+)\s*\(/gm;
|
|
|
|
export function scanProtobuf(db: DBType, root: string): ScanStats {
|
|
const insertContract = db.prepare(
|
|
"INSERT OR IGNORE INTO contracts (name, type, file_path, service, content) VALUES (?, ?, ?, ?, ?)",
|
|
);
|
|
|
|
let contracts = 0;
|
|
|
|
// 扫描 packages/shared-proto/proto/*.proto
|
|
const protoDir = path.join(root, "packages", "shared-proto", "proto");
|
|
if (!fs.existsSync(protoDir)) return { contracts };
|
|
|
|
const entries = fs.readdirSync(protoDir, { withFileTypes: true });
|
|
for (const entry of entries) {
|
|
if (!entry.isFile() || !entry.name.endsWith(".proto")) continue;
|
|
const filePath = path.join(protoDir, entry.name);
|
|
const protoName = path.basename(entry.name, ".proto");
|
|
const serviceName = protoName.replace("_", "-");
|
|
|
|
let content: string;
|
|
try {
|
|
content = fs.readFileSync(filePath, "utf-8");
|
|
} catch {
|
|
continue;
|
|
}
|
|
|
|
// 提取 package 名
|
|
const pkgMatch = content.match(/^package\s+(\S+);/m);
|
|
const pkgName = pkgMatch ? pkgMatch[1] : "";
|
|
|
|
// 记录 proto 文件作为契约
|
|
insertContract.run(protoName, "proto-file", filePath, serviceName, pkgName);
|
|
contracts++;
|
|
|
|
// 提取 service 定义
|
|
let match: RegExpExecArray | null;
|
|
const serviceRe = new RegExp(PROTO_SERVICE_RE);
|
|
while ((match = serviceRe.exec(content)) !== null) {
|
|
const name = match[1];
|
|
insertContract.run(
|
|
`${protoName}.${name}`,
|
|
"service",
|
|
filePath,
|
|
serviceName,
|
|
name,
|
|
);
|
|
contracts++;
|
|
}
|
|
|
|
// 提取 message 定义
|
|
const messageRe = new RegExp(PROTO_MESSAGE_RE);
|
|
while ((match = messageRe.exec(content)) !== null) {
|
|
const name = match[1];
|
|
insertContract.run(
|
|
`${protoName}.${name}`,
|
|
"message",
|
|
filePath,
|
|
serviceName,
|
|
name,
|
|
);
|
|
contracts++;
|
|
}
|
|
|
|
// 提取 rpc 方法
|
|
const rpcRe = new RegExp(PROTO_RPC_RE);
|
|
while ((match = rpcRe.exec(content)) !== null) {
|
|
const name = match[1];
|
|
insertContract.run(
|
|
`${protoName}.rpc.${name}`,
|
|
"rpc",
|
|
filePath,
|
|
serviceName,
|
|
name,
|
|
);
|
|
contracts++;
|
|
}
|
|
}
|
|
|
|
return { contracts };
|
|
}
|