Files
blog/scripts/generate_notification_report.js

125 lines
4.7 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const matter = require('gray-matter');
const POSTS_DIR = path.join(__dirname, '../content/posts');
function parseDateFlexible(input) {
if (input instanceof Date) return Number.isNaN(input.getTime()) ? null : input;
if (input == null) return null;
const raw = String(input).trim();
if (!raw) return null;
const direct = new Date(raw);
if (!Number.isNaN(direct.getTime())) return direct;
const digits = raw.replace(/\D/g, '');
if (digits.length === 14 || digits.length === 12) {
const y = Number(digits.slice(0, 4));
const mo = Number(digits.slice(4, 6));
const d = Number(digits.slice(6, 8));
const h = Number(digits.slice(8, 10));
const mi = Number(digits.slice(10, 12));
const s = digits.length === 14 ? Number(digits.slice(12, 14)) : 0;
if ([y, mo, d, h, mi, s].some(n => !Number.isFinite(n))) return null;
const dt = new Date(y, mo - 1, d, h, mi, s);
return Number.isNaN(dt.getTime()) ? null : dt;
}
if (digits.length === 8) {
const y = Number(digits.slice(0, 4));
const mo = Number(digits.slice(4, 6));
const d = Number(digits.slice(6, 8));
if ([y, mo, d].some(n => !Number.isFinite(n))) return null;
return new Date(y, mo - 1, d);
}
return null;
}
function isWithinDays(date, days) {
if (!(date instanceof Date) || Number.isNaN(date.getTime())) return false;
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - days);
return date >= cutoff;
}
function collectPublicPosts() {
try {
if (!fs.existsSync(POSTS_DIR)) return [];
const posts = [];
function scanDir(dir) {
const files = fs.readdirSync(dir);
for (const file of files) {
const fullPath = path.join(dir, file);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
scanDir(fullPath);
} else if (file.endsWith('.md')) {
try {
const content = fs.readFileSync(fullPath, 'utf8');
const parsed = matter(content);
if (parsed.data.status === 'hidden' || parsed.data.draft === true) continue;
const date = parsed.data.date ? parseDateFlexible(parsed.data.date) : null;
posts.push({
title: parsed.data.title ? String(parsed.data.title) : file.replace('.md', ''),
date,
});
} catch (e) { /* skip */ }
}
}
}
scanDir(POSTS_DIR);
return posts;
} catch (e) {
console.error('Error collecting posts:', e);
return [];
}
}
async function main() {
let report = "";
const nowStr = new Date().toLocaleString();
const tz = process.env.TZ || '';
const repo = process.env.GITHUB_REPOSITORY || '';
const refName = process.env.GITHUB_REF_NAME || '';
const sha = process.env.GITHUB_SHA ? process.env.GITHUB_SHA.slice(0, 7) : '';
const runId = process.env.GITHUB_RUN_ID || '';
const serverUrl = process.env.GITHUB_SERVER_URL || 'https://github.com';
const runUrl = repo && runId ? `${serverUrl}/${repo}/actions/runs/${runId}` : '';
report += `### 🚀 部署信息\n`;
report += `- 时间: ${nowStr}\n`;
if (tz) report += `- 时区: ${tz}\n`;
if (repo) report += `- 仓库: ${repo}\n`;
if (refName) report += `- 分支: ${refName}\n`;
if (sha) report += `- 提交: ${sha}\n`;
if (runUrl) report += `- Actions: ${runUrl}\n`;
report += `\n`;
const posts = collectPublicPosts();
const totalPublic = posts.length;
const dated = posts.filter(p => p.date instanceof Date && !Number.isNaN(p.date.getTime()));
const undatedCount = totalPublic - dated.length;
const recent7 = dated.filter(p => isWithinDays(p.date, 7));
const recent30 = dated.filter(p => isWithinDays(p.date, 30));
report += `### 📦 内容概览\n`;
report += `- 公开文章: ${totalPublic}\n`;
report += `- 近7天更新: ${recent7.length}\n`;
report += `- 近30天更新: ${recent30.length}\n`;
if (undatedCount > 0) report += `- 缺少日期: ${undatedCount}\n`;
report += `\n`;
report += `### 🧩 环境\n`;
report += `- Node: ${process.version}\n`;
if (process.env.RUNNER_OS) report += `- Runner: ${process.env.RUNNER_OS}\n`;
report += `\n`;
console.log(report);
if (process.env.GITHUB_ENV) {
const delimiter = `EOF_${Math.random().toString(36).substr(2, 9)}`;
fs.appendFileSync(process.env.GITHUB_ENV, `DEPLOY_REPORT<<${delimiter}\n${report}\n${delimiter}\n`);
}
}
main();