guanghulab/scripts/notify-module-received.js
2026-05-10 13:12:44 +08:00

242 lines
9.3 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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/notify-module-received.js
// 铸渊 · 模块收讫通知系统 v2.0
// 当合作者上传模块后,通过 Commit Comment @推送者 → GitHub 自动发邮件给推送者
//
// v2.0 改动:
// - 改用 Commit Comment而非 Issue通知邮件只发给推送者本人不再通知仓库所有者
// - 通知中包含模块自检结果(通过/未通过 + 详细报告)
// - 直接使用 github.actor 作为 @提及目标,无需 collaborators.json 中填写 github_username
//
// 环境变量(由 GitHub Actions 注入):
// GITHUB_TOKEN — 仓库 token自动注入
// GITHUB_REPO — 格式: owner/repo如 qinfendebingshuo/guanghulab
// PUSHER_LOGIN — github.actor即推送者的 GitHub 用户名
// CHANGED_FILES — 本次 push 中改动的文件列表(换行分隔)
// COMMIT_SHA — 本次 commit 完整 SHA
// COMMIT_MESSAGE — 本次 commit 消息
// TEST_RESULT — 自检结果pass / fail
// TEST_DETAILS — 自检详细报告
const fs = require('fs');
const https = require('https');
// ========== 读取配置 ==========
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
const GITHUB_REPO = process.env.GITHUB_REPO || '';
const PUSHER_LOGIN = (process.env.PUSHER_LOGIN || '').trim();
const CHANGED_FILES = (process.env.CHANGED_FILES || '').trim();
const COMMIT_SHA = (process.env.COMMIT_SHA || '').trim();
const COMMIT_SHA_SHORT = COMMIT_SHA.slice(0, 8);
const COMMIT_MSG = (process.env.COMMIT_MESSAGE || '').split('\n')[0].slice(0, 80);
const TEST_RESULT = (process.env.TEST_RESULT || 'unknown').trim();
const TEST_DETAILS = (process.env.TEST_DETAILS || '').trim();
if (!GITHUB_TOKEN || !GITHUB_REPO) {
console.error('❌ 缺少 GITHUB_TOKEN 或 GITHUB_REPO');
process.exit(1);
}
if (!PUSHER_LOGIN) {
console.log('⚠️ 未获取到推送者用户名,跳过通知');
process.exit(0);
}
if (!COMMIT_SHA) {
console.log('⚠️ 未获取到 Commit SHA跳过通知');
process.exit(0);
}
const [OWNER, REPO] = GITHUB_REPO.split('/');
// ========== 加载合作者配置 ==========
let collaborators = [];
try {
const config = JSON.parse(fs.readFileSync('.github/brain/collaborators.json', 'utf8'));
collaborators = config.collaborators || [];
} catch (e) {
console.log('⚠️ 无法读取 collaborators.json将使用 GitHub 用户名:', e.message);
}
// ========== 按推送者 GitHub 用户名查找合作者 ==========
function findCollaborator(githubLogin) {
// 先按 github_username 精确匹配
const byUsername = collaborators.find(c =>
c.github_username && c.github_username.toLowerCase() === githubLogin.toLowerCase()
);
if (byUsername) return byUsername;
return null;
}
// ========== 分析改动了哪些模块 ==========
function detectChangedModules(changedFilesStr) {
if (!changedFilesStr) return [];
const files = changedFilesStr.split('\n').map(f => f.trim()).filter(Boolean);
const moduleDirs = new Set();
// 已知模块目录前缀列表
const KNOWN_MODULE_PREFIXES = [
'm01-login', 'm03-personality', 'm05-user-center', 'm06-ticket',
'm07-dialogue-ui', 'm10-cloud', 'm11-module', 'm12-kanban',
'm15-cloud-drive', 'dingtalk-bot', 'backend-integration', 'status-board',
];
for (const file of files) {
for (const prefix of KNOWN_MODULE_PREFIXES) {
if (file.startsWith(prefix + '/') || file === prefix) {
moduleDirs.add(prefix);
}
}
}
return Array.from(moduleDirs);
}
// ========== 构建通知内容Commit Comment 格式)==========
function buildNotificationContent(collab, changedModules, allFiles) {
const now = new Date().toISOString().replace('T', ' ').slice(0, 16) + ' UTC';
const devLabel = collab ? `${collab.dev_id} · ${collab.emoji} ${collab.name}` : PUSHER_LOGIN;
const moduleListStr = changedModules.length > 0
? changedModules.map(m => `- \`${m}/\``).join('\n')
: '- (未检测到标准模块目录)';
const fileLines = allFiles.split('\n').filter(Boolean);
const fileListStr = fileLines.slice(0, 20).map(f => ` - \`${f}\``).join('\n');
const extraNote = fileLines.length > 20 ? `\n - ...(共 ${fileLines.length} 个文件)` : '';
const isPass = TEST_RESULT === 'pass';
const isUnknown = TEST_RESULT === 'unknown';
const statusEmoji = isPass ? '✅' : (isUnknown ? '' : '❌');
const statusText = isPass ? '自检通过' : (isUnknown ? '自检未执行' : '自检未通过');
let body = `## 📦 光湖自动化系统 · 模块收讫回执\n\n`;
body += `@${PUSHER_LOGIN} 你好!\n\n`;
if (isPass || isUnknown) {
body += `🎉 **你上传的模块已收到,自检通过!**\n\n`;
} else {
body += `⚠️ **你上传的模块已收到,但自检发现以下问题,请修改后重新上传:**\n\n`;
}
body += `---\n\n`;
body += `### 📋 收讫详情\n\n`;
body += `| 字段 | 内容 |\n`;
body += `|------|------|\n`;
body += `| **合作者** | ${devLabel} |\n`;
body += `| **GitHub 账号** | @${PUSHER_LOGIN} |\n`;
body += `| **收到时间** | ${now} |\n`;
body += `| **Commit** | \`${COMMIT_SHA_SHORT}\` |\n`;
body += `| **提交说明** | ${COMMIT_MSG || '(无)'} |\n`;
body += `| **自检结果** | ${statusEmoji} ${statusText} |\n\n`;
body += `### 📦 涉及模块\n\n${moduleListStr}\n\n`;
body += `### 📄 上传文件清单\n\n${fileListStr}${extraNote}\n\n`;
// 自检报告
body += `### ${statusEmoji} 自检报告\n\n`;
if (TEST_DETAILS) {
body += `\`\`\`\n${TEST_DETAILS}\n\`\`\`\n\n`;
} else {
body += isPass ? '所有检查项目通过。\n\n' : '(未获取到详细检查报告)\n\n';
}
if (!isPass && !isUnknown) {
body += `### 📌 修改指南\n\n`;
body += `1. 请根据以上自检报告修改相关文件\n`;
body += `2. 修改完成后重新推送到仓库\n`;
body += `3. 铸渊将自动重新检查并发送新的回执\n\n`;
} else {
body += `### 📌 下一步\n\n`;
body += `1. 模块结构检查已通过,无需额外操作\n`;
body += `2. 铸渊将自动更新 [部署模块总文档](https://github.com/${GITHUB_REPO}/blob/main/docs/HoloLake-Era-OS-Modules.md)\n`;
body += `3. 如需修改,请继续推送即可\n\n`;
}
body += `---\n`;
body += `*—— 光湖自动化系统 · 铸渊ZhùYuān· ${now}*`;
return body;
}
// ========== 调用 GitHub API 创建 Commit Comment ==========
// Commit Comment 只通知 commit 作者 + @提及的用户,不通知仓库所有者
function createCommitComment(body) {
return new Promise((resolve, reject) => {
const payload = JSON.stringify({ body });
const options = {
hostname: 'api.github.com',
path: `/repos/${OWNER}/${REPO}/commits/${COMMIT_SHA}/comments`,
method: 'POST',
headers: {
'Authorization': `Bearer ${GITHUB_TOKEN}`,
'Accept': 'application/vnd.github+json',
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payload),
'User-Agent': 'ZhuyuanBot/1.0',
'X-GitHub-Api-Version': '2022-11-28',
},
};
const req = https.request(options, res => {
let data = '';
res.on('data', chunk => { data += chunk; });
res.on('end', () => {
if (res.statusCode === 201) {
resolve(JSON.parse(data));
} else {
reject(new Error(`GitHub API 返回 ${res.statusCode}: ${data}`));
}
});
});
req.on('error', reject);
req.write(payload);
req.end();
});
}
// ========== 主流程 ==========
async function main() {
console.log(`👤 推送者:${PUSHER_LOGIN}`);
console.log(`📁 改动文件:${CHANGED_FILES ? CHANGED_FILES.split('\n').length : 0}`);
console.log(`🔍 自检结果:${TEST_RESULT}`);
const collab = findCollaborator(PUSHER_LOGIN);
if (collab) {
console.log(`✅ 识别合作者:${collab.dev_id} · ${collab.name}`);
} else {
console.log(` 未在 collaborators.json 中找到 "${PUSHER_LOGIN}" 的映射,将使用 GitHub 用户名通知`);
}
const changedModules = detectChangedModules(CHANGED_FILES);
console.log(`📦 涉及模块:${changedModules.join(', ') || '(未检测到标准模块目录)'}`);
// 如果改动文件全部在 docs/ 或 scripts/ 或 .github/,说明是系统自动提交,跳过通知
const allFiles = CHANGED_FILES.split('\n').filter(Boolean);
const isSystemCommit = allFiles.length > 0 && allFiles.every(f =>
f.startsWith('docs/') || f.startsWith('scripts/') || f.startsWith('.github/')
);
if (isSystemCommit) {
console.log('🤖 检测到系统自动提交(非模块上传),跳过通知');
process.exit(0);
}
if (changedModules.length === 0) {
console.log('⚠️ 未检测到标准模块目录的改动,跳过通知');
process.exit(0);
}
// 构建并发送通知(通过 Commit Comment直接通知推送者本人
const body = buildNotificationContent(collab, changedModules, CHANGED_FILES);
console.log(`📝 创建 Commit Comment 通知(通知目标:@${PUSHER_LOGIN}...`);
const comment = await createCommitComment(body);
console.log(`✅ Commit Comment 已创建:${comment.html_url}`);
console.log(`📧 GitHub 将自动发送邮件通知给 @${PUSHER_LOGIN}(推送者本人)`);
console.log(`📧 仓库所有者将不会收到此通知`);
}
main().catch(err => {
console.error('❌ 通知失败:', err.message);
process.exit(1);
});