guanghulab/scripts/generate-communication-map.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

182 lines
5.7 KiB
JavaScript
Raw Permalink 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();