guanghulab/scripts/bridge-app.js

208 lines
6.7 KiB
JavaScript
Raw Normal View History

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