85 lines
2.8 KiB
JavaScript
Raw Permalink Normal View History

/*
* /api/manifest : ()
* 守护: 铸渊 · ICE-GL-ZY001
*
* 数据源:
* 1. 优先 PORTAL_DATA_DIR/manifest-snapshot.json (部署时由 CI 打包注入)
* 2. 回退 repo .github/brain/architecture/function-manifest.json
*
* baton-004 自检 3:
* "右栏要展示模块注册表. 数据从哪儿来? 直接从 GitHub API 拉?"
* : 不从 GitHub API. 国内 2C2G 访问 GitHub API 不稳, 且会暴露主权.
* 直接读本地快照. PR-6 forgejo 装好后, 这一份会从 forgejo .
*/
"use strict";
const express = require("express");
const fs = require("fs");
const path = require("path");
const router = express.Router();
let cache = null;
let cacheTs = 0;
const CACHE_MS = 30 * 1000;
function loadManifest(dataDir, repoRoot) {
// 1. 部署快照
const snap = path.join(dataDir, "manifest-snapshot.json");
let raw = null;
if (fs.existsSync(snap)) {
try { raw = JSON.parse(fs.readFileSync(snap, "utf8")); } catch (_) {}
}
if (!raw) {
// 2. 仓库内 manifest
const repoManifest = path.join(repoRoot, ".github", "brain", "architecture", "function-manifest.json");
if (fs.existsSync(repoManifest)) {
try { raw = JSON.parse(fs.readFileSync(repoManifest, "utf8")); } catch (_) {}
}
}
if (!raw) return { items: [] };
const items = [];
const fns = Array.isArray(raw.functions) ? raw.functions : [];
for (const fn of fns) {
if (!fn || typeof fn !== "object") continue;
const tags = [];
if (fn.id) tags.push(fn.id);
if (fn.host_server) tags.push(fn.host_server);
if (fn.status) tags.push(fn.status);
if (fn.keep_decision === "keep") tags.push("活跃");
items.push({
title: fn.display_name_zh || fn.id || "(未命名)",
tags,
summary:
(fn.description || fn.role || fn.notes || "").toString().replace(/\s+/g, " ").slice(0, 240)
});
}
// 服务器/宿主也作为模块注册表的一部分展示 (开发层关心物理拓扑)
const servers = Array.isArray(raw.servers) ? raw.servers : [];
for (const s of servers) {
if (!s || typeof s !== "object") continue;
const tags = [];
if (s.id) tags.push(s.id);
if (s.spec) tags.push(s.spec);
if (s.status) tags.push(s.status);
items.push({
title: s.display_name_zh || s.id || "(服务器)",
tags,
summary: (s.role || s.notes || "").toString().replace(/\s+/g, " ").slice(0, 240)
});
}
return { items, version: raw._version || raw.version || null };
}
router.get("/", (req, res) => {
const now = Date.now();
if (!cache || now - cacheTs > CACHE_MS) {
cache = loadManifest(req.ctx.dataDir, req.ctx.repoRoot);
cacheTs = now;
}
res.json({ source: "function-manifest", ...(cache || { items: [] }) });
});
module.exports = router;