guanghulab/scripts/bridge-app.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

208 lines
6.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/bridge-app.js
// 铸渊仓库联邦桥接引擎
// 用途:从主仓库向所有开发者仓库推送文件
const https = require('https');
const fs = require('fs');
const path = require('path');
const TOKEN = process.env.MAIN_REPO_TOKEN;
const ORG = process.env.GITHUB_ORG || 'qinfendebingshuo';
const COMMITTER = { name: '铸渊 (ZhùYuān)', email: 'zhuyuan@guanghulab.com' };
const BULLETIN_PATH = 'bulletins/latest.md';
// ========== 开发者仓库路由表 ==========
const FEDERATION = {
'DEV-004': { name: '之之', repo: 'guanghu-zhizhi', persona: '秋秋' },
'DEV-010': { name: '桔子', repo: 'guanghu-juzi', persona: '晨星' },
'DEV-012': { name: 'Awen', repo: 'guanghu-awen', persona: '知秋' },
'DEV-002': { name: '肥猫', repo: 'guanghu-feimao', persona: '舒舒' },
'DEV-001': { name: '页页', repo: 'guanghu-yeye', persona: '小坍缩核' },
'DEV-003': { name: '燕樊', repo: 'guanghu-yanfan', persona: '寂曜' },
// 新增开发者在此追加
};
// GitHub API 封装
function githubRequest(method, apiPath, body) {
return new Promise((resolve, reject) => {
const options = {
hostname: 'api.github.com',
path: apiPath,
method: method,
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Accept': 'application/vnd.github+json',
'User-Agent': 'ZhuYuan-Bridge',
'X-GitHub-Api-Version': '2022-11-28'
}
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try { resolve(JSON.parse(data)); }
catch { resolve(data); }
});
});
req.on('error', reject);
if (body) req.write(JSON.stringify(body));
req.end();
});
}
const githubGet = (p) => githubRequest('GET', p);
const githubPut = (p, b) => githubRequest('PUT', p, b);
const githubPost = (p, b) => githubRequest('POST', p, b);
// 创建或更新文件
async function pushFile(repo, filePath, content, message) {
const url = `/repos/${ORG}/${repo}/contents/${filePath}`;
let sha = null;
try {
const existing = await githubGet(url);
if (existing.sha) sha = existing.sha;
} catch (e) { /* 文件不存在,正常 */ }
const body = {
message,
content: Buffer.from(content).toString('base64'),
committer: COMMITTER
};
if (sha) body.sha = sha;
return githubPut(url, body);
}
// ========== 功能模块 ==========
// 1. 推送公告到所有仓库
async function pushBulletin() {
if (!fs.existsSync(BULLETIN_PATH)) {
console.log('📭 无 bulletins/latest.md');
return;
}
const content = fs.readFileSync(BULLETIN_PATH, 'utf8');
const timestamp = new Date().toISOString().split('T')[0];
for (const [devId, dev] of Object.entries(FEDERATION)) {
try {
await pushFile(dev.repo, 'BULLETIN.md', content,
`📡 铸渊公告推送 · ${timestamp}`);
console.log(`${devId}(${dev.name}) 公告已推送`);
} catch (e) {
console.error(`${devId}(${dev.name}) 推送失败: ${e.message}`);
}
}
}
// 2. 初始化开发者仓库
async function initRepo(devId) {
const dev = FEDERATION[devId];
if (!dev) { console.error(`未知开发者: ${devId}`); return; }
const files = [
{
path: '.github/persona-brain/config.json',
content: JSON.stringify({
persona_name: dev.persona,
dev_id: devId,
dev_name: dev.name,
main_repo: 'guanghulab',
org: ORG,
initialized_by: 'zhuyuan-bridge-app',
initialized_at: new Date().toISOString()
}, null, 2)
},
{
path: '.github/persona-brain/status.json',
content: JSON.stringify({
persona: dev.persona,
dev_id: devId,
initialized: new Date().toISOString(),
last_checkin: null,
current_broadcast: null
}, null, 2)
},
{
path: 'BULLETIN.md',
content: `# 📡 系统公告\n\n> 仓库已由铸渊自动初始化 · ${new Date().toISOString().split('T')[0]}\n> 欢迎 ${dev.name}${dev.persona}\n`
}
];
for (const file of files) {
await pushFile(dev.repo, file.path, file.content,
`🏗️ 铸渊初始化 · ${devId} ${dev.name} · ${file.path}`);
console.log(`📁 ${devId}${file.path}`);
}
console.log(`\n🎉 ${devId}(${dev.name}) 仓库初始化完成`);
}
// 3. 汇总所有仓库进度
async function collectStatus() {
const status = {};
for (const [devId, dev] of Object.entries(FEDERATION)) {
try {
const data = await githubGet(
`/repos/${ORG}/${dev.repo}/contents/.github/persona-brain/status.json`
);
status[devId] = {
name: dev.name,
persona: dev.persona,
...JSON.parse(Buffer.from(data.content, 'base64').toString())
};
console.log(`📊 ${devId}(${dev.name}) 状态已收集`);
} catch (e) {
status[devId] = { name: dev.name, persona: dev.persona, error: '无法读取' };
console.warn(`⚠️ ${devId}(${dev.name}) 读取失败`);
}
}
await pushFile('guanghulab', 'federation-status.json',
JSON.stringify(status, null, 2),
`📊 铸渊联邦状态汇总 · ${new Date().toISOString().split('T')[0]}`);
console.log('\n✅ 联邦状态汇总完成 → federation-status.json');
}
// 4. 跨仓库广播分发
async function distributeBroadcasts() {
const OUTBOX = 'broadcasts-outbox';
if (!fs.existsSync(OUTBOX)) { console.log('📭 无广播待分发'); return; }
const devDirs = fs.readdirSync(OUTBOX).filter(d =>
d.startsWith('DEV-') && fs.statSync(path.join(OUTBOX, d)).isDirectory()
);
for (const devId of devDirs) {
const dev = FEDERATION[devId];
if (!dev) { console.warn(`⚠️ ${devId} 无路由映射`); continue; }
const files = fs.readdirSync(path.join(OUTBOX, devId))
.filter(f => f.endsWith('.md') || f.endsWith('.json'));
for (const file of files) {
const content = fs.readFileSync(path.join(OUTBOX, devId, file), 'utf8');
await pushFile(dev.repo, 'LATEST-BROADCAST.md', content,
`📡 铸渊广播分发 · ${devId} · ${file}`);
console.log(`📡 ${devId}(${dev.name}) ← ${file}`);
}
}
}
// ========== 入口 ==========
async function main() {
const action = process.argv[2];
switch (action) {
case 'bulletin': await pushBulletin(); break;
case 'init': await initRepo(process.argv[3]); break;
case 'init-all':
for (const id of Object.keys(FEDERATION)) await initRepo(id);
break;
case 'status': await collectStatus(); break;
case 'distribute': await distributeBroadcasts(); break;
default: console.log('用法: node bridge-app.js [bulletin|init DEV-XXX|init-all|status|distribute]');
}
}
main().catch(e => { console.error('❌ 桥接执行失败:', e.message); process.exit(1); });