Files
Edu/scripts/arch-scan/scanners/proto-scanner.ts
SpecialX 4629de1926 fix(arch-scan): 修复多语言扫描器并添加 DELETE 清空表
- scanner.ts: 添加 DELETE 清空旧数据语句,避免重复插入
- ts-scanner: 改用 regex 替代 ts-morph 解析(避免解析失败)
- go-scanner/py-scanner/proto-scanner: 完善实现
2026-07-08 12:49:43 +08:00

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 };
}