zhizhi/scripts/notify-module-received.js

261 lines
8.9 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
// 铸渊 · 模块收讫通知系统
// 当合作者上传模块后,自动创建 GitHub Issue @提及 → GitHub 自动发邮件给对方
//
// 环境变量(由 GitHub Actions 注入):
// GITHUB_TOKEN — 仓库 token自动注入
// GITHUB_REPO — 格式: owner/repo如 qinfendebingshuo/guanghulab
// PUSHER_LOGIN — github.actor即推送者的 GitHub 用户名
// CHANGED_FILES — 本次 push 中改动的文件列表(换行分隔)
// COMMIT_SHA — 本次 commit SHA前8位
// COMMIT_MESSAGE — 本次 commit 消息
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 || '').slice(0, 8);
const COMMIT_MSG = (process.env.COMMIT_MESSAGE || '').split('\n')[0].slice(0, 80);
if (!GITHUB_TOKEN || !GITHUB_REPO) {
console.error('❌ 缺少 GITHUB_TOKEN 或 GITHUB_REPO');
process.exit(1);
}
if (!PUSHER_LOGIN) {
console.log('⚠️ 未获取到推送者用户名,跳过通知');
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.error('❌ 无法读取 collaborators.json:', e.message);
process.exit(1);
}
// ========== 按推送者 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);
}
// ========== 构建通知 Issue 内容 ==========
function buildIssueContent(collab, changedModules, allFiles) {
const now = new Date().toISOString().replace('T', ' ').slice(0, 16) + ' UTC';
const mention = collab && collab.github_username ? `@${collab.github_username}` : `@${PUSHER_LOGIN}`;
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 title = `📦 铸渊收讫 · [${collab ? collab.dev_id : 'DEV-???'}] ${collab ? collab.name : PUSHER_LOGIN} 模块已收到`;
const body = `## 铸渊收讫通知
${mention} 你好!🎉
铸渊(自动化系统)已确认收到你上传的模块。
---
### 📋 收讫详情
| 字段 | 内容 |
|------|------|
| **合作者** | ${devLabel} |
| **GitHub 账号** | @${PUSHER_LOGIN} |
| **收到时间** | ${now} |
| **Commit** | \`${COMMIT_SHA}\` |
| **提交说明** | ${COMMIT_MSG || '(无)'} |
### 📦 涉及模块
${moduleListStr}
### 📄 上传文件清单
${fileListStr}${extraNote}
---
### 📌 下一步
1. **铸渊将自动更新** [HoloLake Era 操作系统部署模块](https://github.com/${GITHUB_REPO}/blob/main/docs/HoloLake-Era-OS-Modules.md) 总文档
2. **模块结构检查** 已自动运行,请确认 CI 状态绿灯
3. 如需补充 \`SYSLOG.md\`\`package.json\`\`src/\` 目录,请继续推送
> 💡 如有问题,请直接在本 Issue 回复,铸渊会收到通知。
---
*—— 铸渊ZhùYuān· 光湖自动化系统 · 自动通知 #${now}*`;
return { title, body };
}
// ========== 调用 GitHub API 创建 Issue ==========
function createIssue(title, body) {
return new Promise((resolve, reject) => {
const payload = JSON.stringify({ title, body, labels: ['模块收讫'] });
const options = {
hostname: 'api.github.com',
path: `/repos/${OWNER}/${REPO}/issues`,
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) {
const issue = JSON.parse(data);
resolve(issue);
} else {
reject(new Error(`GitHub API 返回 ${res.statusCode}: ${data}`));
}
});
});
req.on('error', reject);
req.write(payload);
req.end();
});
}
// ========== 确保标签存在(不存在则自动创建)==========
function ensureLabel() {
return new Promise((resolve) => {
const labelPayload = JSON.stringify({
name: '模块收讫',
color: '0075ca',
description: '铸渊自动通知:合作者模块收讫确认',
});
const options = {
hostname: 'api.github.com',
path: `/repos/${OWNER}/${REPO}/labels`,
method: 'POST',
headers: {
'Authorization': `Bearer ${GITHUB_TOKEN}`,
'Accept': 'application/vnd.github+json',
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(labelPayload),
'User-Agent': 'ZhuyuanBot/1.0',
'X-GitHub-Api-Version': '2022-11-28',
},
};
const req = https.request(options, res => {
let data = '';
res.on('data', c => { data += c; });
res.on('end', () => {
// 201 = created, 422 = already exists都算OK
resolve();
});
});
req.on('error', () => resolve()); // 标签创建失败不阻断主流程
req.write(labelPayload);
req.end();
});
}
// ========== 主流程 ==========
async function main() {
console.log(`👤 推送者:${PUSHER_LOGIN}`);
console.log(`📁 改动文件:${CHANGED_FILES ? CHANGED_FILES.split('\n').length : 0}`);
const collab = findCollaborator(PUSHER_LOGIN);
if (collab) {
console.log(`✅ 识别合作者:${collab.dev_id} · ${collab.name}`);
if (!collab.github_username) {
console.log(`⚠️ ${collab.dev_id}${collab.name})的 github_username 尚未填写`);
console.log(` 请在 .github/brain/collaborators.json 中补充,以便 @提及 生效`);
console.log(` 通知将仍然创建,但不会有 @提及 邮件通知`);
}
} 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);
}
// 确保标签存在
await ensureLabel();
// 构建并发送通知
const { title, body } = buildIssueContent(collab, changedModules, CHANGED_FILES);
console.log(`📝 创建通知 Issue${title}`);
const issue = await createIssue(title, body);
console.log(`✅ Issue 已创建:#${issue.number}${issue.html_url}`);
console.log(`📧 GitHub 将自动发送邮件通知给 @${collab?.github_username || PUSHER_LOGIN}`);
}
main().catch(err => {
console.error('❌ 通知失败:', err.message);
process.exit(1);
});