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