guanghulab/scripts/generate-communication-map.js
2026-05-10 13:12:44 +08:00

182 lines
5.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// scripts/generate-communication-map.js
// 通信地图生成器 · Communication Map Generator
//
// 功能:扫描仓库中的 API 端点、webhook、同步脚本生成 brain/communication-map.json
// 触发方式:
// - GitHub Actions: daily-maintenance.yml
// - 本地node scripts/generate-communication-map.js
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT = path.join(__dirname, '..');
const BRAIN_DIR = path.join(ROOT, 'brain');
const OUT_PATH = path.join(BRAIN_DIR, 'communication-map.json');
const now = new Date();
// ── 工具函数 ──
function safeRead(filePath) {
try { return fs.readFileSync(filePath, 'utf8'); } catch { return ''; }
}
function listFiles(dirPath, ext) {
try {
return fs.readdirSync(dirPath)
.filter(f => !ext || f.endsWith(ext))
.filter(f => !f.startsWith('.'));
} catch { return []; }
}
// ── 扫描 HLI 路由 ──
function scanHLIRoutes() {
const routeDir = path.join(ROOT, 'src/routes/hli');
const routes = [];
function scanDir(dir, prefix) {
const files = listFiles(dir, '.js');
for (const file of files) {
const content = safeRead(path.join(dir, file));
const matches = content.matchAll(/router\.(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]/g);
for (const m of matches) {
routes.push({
method: m[1].toUpperCase(),
path: prefix + m[2],
file: path.relative(ROOT, path.join(dir, file))
});
}
}
try {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const e of entries) {
if (e.isDirectory()) {
scanDir(path.join(dir, e.name), prefix + '/' + e.name);
}
}
} catch { /* ignore */ }
}
scanDir(routeDir, '/hli');
return routes;
}
// ── 扫描 backend 路由 ──
function scanBackendRoutes() {
const serverFile = path.join(ROOT, 'backend/server.js');
const content = safeRead(serverFile);
const routes = [];
const matches = content.matchAll(/app\.use\s*\(\s*['"]([^'"]+)['"]/g);
for (const m of matches) {
routes.push({ method: '*', path: m[1], file: 'backend/server.js' });
}
// Check for webhook
const webhooks = content.matchAll(/app\.(post|get)\s*\(\s*['"]([^'"]+)['"]/g);
for (const m of webhooks) {
routes.push({ method: m[1].toUpperCase(), path: m[2], file: 'backend/server.js' });
}
return routes;
}
// ── 扫描 Notion 同步脚本 ──
function scanSyncScripts() {
const scriptDir = path.join(ROOT, 'scripts');
const files = listFiles(scriptDir, '.js');
const syncScripts = [];
for (const file of files) {
const content = safeRead(path.join(scriptDir, file));
const isNotion = /NOTION|@notionhq|notion/i.test(content);
const isSync = /sync|bridge|push/i.test(file);
const isHTTP = /fetch\(|axios|https?\.request/i.test(content);
if (isNotion || isSync) {
let direction = 'unknown';
if (/bridge/i.test(file)) direction = 'bidirectional';
else if (/push|sync-login|broadcast/i.test(file)) direction = 'github_to_external';
else if (/receive|poll|heartbeat/i.test(file)) direction = 'external_to_github';
else if (isNotion) direction = 'github_to_notion';
syncScripts.push({
script: 'scripts/' + file,
direction,
uses_notion_api: isNotion,
uses_http: isHTTP
});
}
}
return syncScripts;
}
// ── 扫描触发路径 ──
function scanTriggerPaths() {
const wfDir = path.join(ROOT, '.github/workflows');
const files = listFiles(wfDir, '.yml');
const githubToNotion = [];
const notionToGithub = [];
for (const file of files) {
const content = safeRead(path.join(wfDir, file));
const nameMatch = content.match(/^name:\s*(.+)/m);
const name = nameMatch ? nameMatch[1].trim().replace(/^["']|["']$/g, '') : file;
if (/notion.*bridge|bridge.*notion|syslog.*notion|changes.*notion|session.*summary/i.test(name) ||
/notion-bridge|receive-syslog|push-broadcast|sync-login/i.test(content)) {
githubToNotion.push({ workflow: file, name });
}
if (/notion.*poll|heartbeat|persona.*invoke|process.*notion|notion.*order/i.test(name) ||
/notion-signal-bridge|notion-heartbeat|invoke-persona/i.test(content)) {
notionToGithub.push({ workflow: file, name });
}
}
return { github_to_notion: githubToNotion, notion_to_github: notionToGithub };
}
// ── 主生成 ──
function generate() {
if (!fs.existsSync(BRAIN_DIR)) fs.mkdirSync(BRAIN_DIR, { recursive: true });
const hliRoutes = scanHLIRoutes();
const backendRoutes = scanBackendRoutes();
const syncScripts = scanSyncScripts();
const triggerPaths = scanTriggerPaths();
// Read existing for merge
let existing = {};
try { existing = JSON.parse(fs.readFileSync(OUT_PATH, 'utf8')); } catch { /* new file */ }
const map = {
version: existing.version || '4.0',
generated_at: now.toISOString(),
generated_by: 'scripts/generate-communication-map.js',
description: '数字地球系统通信地图 · 所有通信入口与数据流',
api_endpoints: existing.api_endpoints || {},
webhook_endpoints: existing.webhook_endpoints || [],
trigger_paths: triggerPaths,
data_sync_scripts: syncScripts,
automation_flows: existing.automation_flows || [],
scan_results: {
hli_routes_found: hliRoutes.length,
backend_routes_found: backendRoutes.length,
sync_scripts_found: syncScripts.length,
hli_routes: hliRoutes,
backend_routes: backendRoutes
}
};
fs.writeFileSync(OUT_PATH, JSON.stringify(map, null, 2));
console.log(`✅ communication-map.json 已生成 · ${hliRoutes.length} HLI 路由 · ${syncScripts.length} 同步脚本`);
}
generate();