guanghulab/scripts/generate-daily-report.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

289 lines
8.8 KiB
JavaScript
Raw 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.

/**
* generate-daily-report.js
* 生成「光湖开发日报」内容,并通过 GitHub GraphQL API 发布到 Discussions
*
* 使用方式:
* GITHUB_TOKEN=xxx GITHUB_REPOSITORY=owner/repo node scripts/generate-daily-report.js
*
* 环境变量:
* GITHUB_TOKEN - GitHub token (需要 discussions:write 权限)
* GITHUB_REPOSITORY - 仓库名 (owner/repo)
* DISCUSSION_CATEGORY - Discussions 分类名 (默认: Announcements)
*/
const fs = require('fs');
const path = require('path');
const https = require('https');
const { execSync } = require('child_process');
const GITHUB_TOKEN = process.env.GITHUB_TOKEN || '';
const GITHUB_REPOSITORY = process.env.GITHUB_REPOSITORY || '';
const DISCUSSION_CATEGORY = process.env.DISCUSSION_CATEGORY || 'Announcements';
const DEV_STATUS_PATH = path.join('.github', 'persona-brain', 'dev-status.json');
// ── GitHub GraphQL API 请求 ──
function graphqlRequest(query, variables = {}) {
return new Promise((resolve, reject) => {
const data = JSON.stringify({ query, variables });
const options = {
hostname: 'api.github.com',
path: '/graphql',
method: 'POST',
headers: {
'Authorization': `Bearer ${GITHUB_TOKEN}`,
'Content-Type': 'application/json',
'User-Agent': 'guanghulab-daily-report',
'Content-Length': Buffer.byteLength(data),
},
};
const req = https.request(options, (res) => {
let result = '';
res.on('data', (chunk) => result += chunk);
res.on('end', () => {
try {
resolve(JSON.parse(result));
} catch {
reject(new Error(`GraphQL 解析失败: ${result.slice(0, 200)}`));
}
});
});
req.on('error', reject);
req.write(data);
req.end();
});
}
// ── 获取 Discussion 分类 ID ──
async function getCategoryId(owner, repo, categoryName) {
const query = `
query($owner: String!, $repo: String!) {
repository(owner: $owner, name: $repo) {
discussionCategories(first: 20) {
nodes {
id
name
}
}
}
}
`;
const result = await graphqlRequest(query, { owner, repo });
const categories = result?.data?.repository?.discussionCategories?.nodes || [];
const category = categories.find(c => c.name === categoryName);
return category?.id || null;
}
// ── 获取仓库 ID ──
async function getRepositoryId(owner, repo) {
const query = `
query($owner: String!, $repo: String!) {
repository(owner: $owner, name: $repo) {
id
}
}
`;
const result = await graphqlRequest(query, { owner, repo });
return result?.data?.repository?.id || null;
}
// ── 创建 Discussion ──
async function createDiscussion(repoId, categoryId, title, body) {
const mutation = `
mutation($repoId: ID!, $categoryId: ID!, $title: String!, $body: String!) {
createDiscussion(input: {
repositoryId: $repoId,
categoryId: $categoryId,
title: $title,
body: $body
}) {
discussion {
id
url
}
}
}
`;
return graphqlRequest(mutation, { repoId, categoryId, title, body });
}
// ── 获取最近 24h 的 Git 活动 ──
function getRecentActivity() {
const since = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
let commits = [];
let prActivity = '';
try {
const log = execSync(
`git log --since="${since}" --oneline --no-merges 2>/dev/null || true`,
{ encoding: 'utf8', timeout: 10000 }
).trim();
if (log) {
commits = log.split('\n').filter(Boolean).slice(0, 10);
}
} catch {
// Git log may fail in shallow clone
}
try {
const merges = execSync(
`git log --since="${since}" --oneline --merges 2>/dev/null || true`,
{ encoding: 'utf8', timeout: 10000 }
).trim();
if (merges) {
prActivity = merges.split('\n').filter(Boolean).slice(0, 5).join('\n');
}
} catch {
// Ignore errors
}
return { commits, prActivity };
}
// ── 生成日报 Markdown ──
function generateReport(devStatus, activity) {
const today = new Date();
const bjDate = new Date(today.getTime() + 8 * 60 * 60 * 1000);
const dateStr = bjDate.toISOString().split('T')[0];
const team = devStatus.team || [];
// 团队状态表格
let teamTable = '| 开发者 | 当前模块 | 连胜 | 状态 |\n| --- | --- | --- | --- |\n';
team.forEach(dev => {
const statusEmoji = {
'active': '🟢 活跃',
'waiting_syslog': '🟡 等待SYSLOG',
'waiting_broadcast': '🔵 等待广播',
'paused': '⏸️ 暂停',
};
const status = statusEmoji[dev.status] || dev.status;
const streak = dev.streak > 0 ? `🔥${dev.streak}` : '—';
teamTable += `| ${dev.name} | ${dev.module} | ${streak} | ${status} |\n`;
});
// 今日亮点
let highlights = '';
if (activity.commits.length > 0) {
highlights += activity.commits.slice(0, 5).map(c => `· ${c}`).join('\n') + '\n';
}
// 连胜记录
const topStreakers = team.filter(d => d.streak >= 5).sort((a, b) => b.streak - a.streak);
if (topStreakers.length > 0) {
highlights += topStreakers.map(d => `· 🔥 ${d.name} ${d.streak}连胜 — ${d.module}`).join('\n') + '\n';
}
if (!highlights) {
highlights = '· 团队持续推进中,期待新的突破!\n';
}
// 项目概览
const activeDevs = team.filter(d => d.status !== 'paused').length;
const summary = devStatus.summary || {};
const report = `# 🌊 光湖开发日报 · ${dateStr}
## 📊 团队状态
${teamTable}
## 🔥 今日亮点
${highlights}
## 🏗️ 项目概览
· 活跃开发者:${activeDevs}
· 最高连胜:${summary.top_streak || '无'}
· 代码仓库:[guanghulab](https://github.com/qinfendebingshuo/guanghulab)
· 🏛️ 通感语言核系统编程语言 · 作品著作权认证(中国版权保护中心)
---
⭐ 觉得有意思?给个 Star 支持一下!
💬 有问题或想聊聊?欢迎到 [访客留言板](https://github.com/qinfendebingshuo/guanghulab/discussions/categories/访客留言板) 留言!
`;
return { title: `🌊 光湖开发日报 · ${dateStr}`, body: report };
}
// ── 主流程 ──
async function main() {
console.log('📰 开始生成光湖开发日报...');
// 读取 dev-status
let devStatus = {};
try {
devStatus = JSON.parse(fs.readFileSync(DEV_STATUS_PATH, 'utf8'));
} catch {
console.log('⚠️ dev-status.json 读取失败,使用空数据');
}
// 获取 Git 活动
const activity = getRecentActivity();
// 生成日报内容
const { title, body } = generateReport(devStatus, activity);
console.log(`📝 日报标题: ${title}`);
console.log('---');
console.log(body.slice(0, 500) + '...');
// 保存日报到本地(供调试和存档)
const reportDir = 'docs/daily-reports';
if (!fs.existsSync(reportDir)) {
fs.mkdirSync(reportDir, { recursive: true });
}
const bjDate = new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString().split('T')[0];
fs.writeFileSync(path.join(reportDir, `${bjDate}.md`), `# ${title}\n\n${body}`);
console.log(`✅ 日报已保存到 ${reportDir}/${bjDate}.md`);
// 发布到 Discussions
if (!GITHUB_TOKEN || !GITHUB_REPOSITORY) {
console.log('⚠️ GITHUB_TOKEN 或 GITHUB_REPOSITORY 未配置,跳过 Discussion 发布');
return;
}
const [owner, repo] = GITHUB_REPOSITORY.split('/');
if (!owner || !repo) {
console.log('⚠️ GITHUB_REPOSITORY 格式错误,应为 owner/repo');
return;
}
// 获取仓库和分类 ID
let repoId;
try {
repoId = await getRepositoryId(owner, repo);
} catch (err) {
console.log(`⚠️ 无法连接 GitHub API: ${err.message},跳过 Discussion 发布`);
return;
}
if (!repoId) {
console.log('⚠️ 无法获取仓库 ID跳过 Discussion 发布');
return;
}
let categoryId;
try {
categoryId = await getCategoryId(owner, repo, DISCUSSION_CATEGORY);
} catch (err) {
console.log(`⚠️ 获取分类失败: ${err.message},跳过 Discussion 发布`);
return;
}
if (!categoryId) {
console.log(`⚠️ 未找到 "${DISCUSSION_CATEGORY}" 分类,跳过 Discussion 发布`);
console.log(' 请先在仓库 Settings → Discussions 中创建该分类');
return;
}
// 创建 Discussion
try {
const result = await createDiscussion(repoId, categoryId, title, body);
if (result?.data?.createDiscussion?.discussion?.url) {
console.log(`✅ 日报已发布: ${result.data.createDiscussion.discussion.url}`);
} else {
console.log('⚠️ Discussion 创建结果:', JSON.stringify(result?.errors || result).slice(0, 500));
}
} catch (err) {
console.log(`⚠️ Discussion 发布失败: ${err.message}`);
}
}
main().catch(err => {
console.error('❌ 日报生成失败:', err.message);
process.exit(1);
});