164 lines
4.8 KiB
JavaScript
164 lines
4.8 KiB
JavaScript
|
|
// scripts/bridge/generate-pdf.js
|
|||
|
|
// 🌉 桥接·Markdown → PDF 生成
|
|||
|
|
//
|
|||
|
|
// 读取 data/broadcasts/pdf/ 下的 Markdown 文件,
|
|||
|
|
// 使用 md-to-pdf 转换为 PDF,套用光湖广播模板
|
|||
|
|
//
|
|||
|
|
// 环境变量:
|
|||
|
|
// MANIFEST_FILE fetch-broadcast.js 生成的清单文件路径
|
|||
|
|
|
|||
|
|
'use strict';
|
|||
|
|
|
|||
|
|
const fs = require('fs');
|
|||
|
|
const path = require('path');
|
|||
|
|
|
|||
|
|
const OUTPUT_DIR = path.join('data', 'broadcasts', 'pdf');
|
|||
|
|
|
|||
|
|
// ══════════════════════════════════════════════════════════
|
|||
|
|
// PDF 样式模板
|
|||
|
|
// ══════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
const PDF_CSS = `
|
|||
|
|
body {
|
|||
|
|
font-family: "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
|
|||
|
|
font-size: 14px;
|
|||
|
|
line-height: 1.8;
|
|||
|
|
color: #333;
|
|||
|
|
padding: 40px;
|
|||
|
|
}
|
|||
|
|
h1 {
|
|||
|
|
font-size: 24px;
|
|||
|
|
border-bottom: 2px solid #1a73e8;
|
|||
|
|
padding-bottom: 10px;
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
}
|
|||
|
|
h2 { font-size: 20px; margin-top: 30px; }
|
|||
|
|
h3 { font-size: 16px; margin-top: 20px; }
|
|||
|
|
blockquote {
|
|||
|
|
border-left: 4px solid #1a73e8;
|
|||
|
|
padding: 10px 20px;
|
|||
|
|
margin: 15px 0;
|
|||
|
|
background: #f8f9fa;
|
|||
|
|
color: #555;
|
|||
|
|
}
|
|||
|
|
code {
|
|||
|
|
background: #f0f0f0;
|
|||
|
|
padding: 2px 6px;
|
|||
|
|
border-radius: 3px;
|
|||
|
|
font-size: 13px;
|
|||
|
|
}
|
|||
|
|
pre code {
|
|||
|
|
display: block;
|
|||
|
|
padding: 15px;
|
|||
|
|
overflow-x: auto;
|
|||
|
|
}
|
|||
|
|
hr {
|
|||
|
|
border: none;
|
|||
|
|
border-top: 1px solid #ddd;
|
|||
|
|
margin: 20px 0;
|
|||
|
|
}
|
|||
|
|
@page {
|
|||
|
|
margin: 20mm;
|
|||
|
|
@top-center { content: "光湖广播 · guanghulab.com"; font-size: 10px; color: #999; }
|
|||
|
|
@bottom-center { content: counter(page) " / " counter(pages); font-size: 10px; color: #999; }
|
|||
|
|
}
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
// ══════════════════════════════════════════════════════════
|
|||
|
|
// 主逻辑
|
|||
|
|
// ══════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
async function main() {
|
|||
|
|
const manifestFile = process.env.MANIFEST_FILE ||
|
|||
|
|
path.join(OUTPUT_DIR, 'manifest.json');
|
|||
|
|
|
|||
|
|
if (!fs.existsSync(manifestFile)) {
|
|||
|
|
console.log('📭 无 manifest.json,跳过 PDF 生成');
|
|||
|
|
process.exit(0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const manifest = JSON.parse(fs.readFileSync(manifestFile, 'utf8'));
|
|||
|
|
if (manifest.length === 0) {
|
|||
|
|
console.log('📭 清单为空,跳过 PDF 生成');
|
|||
|
|
process.exit(0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 动态加载 md-to-pdf(在 workflow 中安装)
|
|||
|
|
let mdToPdf;
|
|||
|
|
try {
|
|||
|
|
mdToPdf = require('md-to-pdf').mdToPdf;
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('❌ md-to-pdf 未安装,请先运行: npm install md-to-pdf');
|
|||
|
|
console.log('⚠️ 回退到纯文本 PDF 模式(将 Markdown 原文保留)');
|
|||
|
|
|
|||
|
|
// 回退:仅保留 .md 文件,标记为待处理
|
|||
|
|
for (const item of manifest) {
|
|||
|
|
if (fs.existsSync(item.file)) {
|
|||
|
|
const pdfPath = item.file.replace(/\.md$/, '.pdf.pending');
|
|||
|
|
fs.copyFileSync(item.file, pdfPath);
|
|||
|
|
console.log(` 📝 ${pdfPath} (待转换)`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log(`📄 开始生成 ${manifest.length} 份 PDF…`);
|
|||
|
|
|
|||
|
|
let ok = 0, failed = 0;
|
|||
|
|
const pdfFiles = [];
|
|||
|
|
|
|||
|
|
for (const item of manifest) {
|
|||
|
|
if (!fs.existsSync(item.file)) {
|
|||
|
|
console.log(` ⚠️ 文件不存在: ${item.file}`);
|
|||
|
|
failed++;
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const pdfPath = item.file.replace(/\.md$/, '.pdf');
|
|||
|
|
|
|||
|
|
const result = await mdToPdf(
|
|||
|
|
{ path: path.resolve(item.file) },
|
|||
|
|
{
|
|||
|
|
stylesheet: [],
|
|||
|
|
css: PDF_CSS,
|
|||
|
|
pdf_options: {
|
|||
|
|
format: 'A4',
|
|||
|
|
margin: { top: '25mm', bottom: '25mm', left: '20mm', right: '20mm' },
|
|||
|
|
printBackground: true,
|
|||
|
|
},
|
|||
|
|
launch_options: { args: ['--no-sandbox', '--disable-setuid-sandbox'] },
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (result && result.content) {
|
|||
|
|
fs.writeFileSync(pdfPath, result.content);
|
|||
|
|
pdfFiles.push({ ...item, pdf: pdfPath });
|
|||
|
|
console.log(` ✅ ${pdfPath}`);
|
|||
|
|
ok++;
|
|||
|
|
} else {
|
|||
|
|
console.error(` ❌ PDF 内容为空: ${item.file}`);
|
|||
|
|
failed++;
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error(` ❌ PDF 生成失败: ${item.file}: ${e.message}`);
|
|||
|
|
failed++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log(`\n✅ PDF 生成完成 · 成功 ${ok} 份 · 失败 ${failed} 份`);
|
|||
|
|
|
|||
|
|
// 更新 manifest 包含 PDF 路径
|
|||
|
|
const pdfManifest = path.join(OUTPUT_DIR, 'pdf-manifest.json');
|
|||
|
|
fs.writeFileSync(pdfManifest, JSON.stringify(pdfFiles, null, 2));
|
|||
|
|
console.log(`📁 PDF 清单: ${pdfManifest}`);
|
|||
|
|
|
|||
|
|
if (process.env.GITHUB_OUTPUT) {
|
|||
|
|
fs.appendFileSync(process.env.GITHUB_OUTPUT,
|
|||
|
|
`pdf_count=${ok}\npdf_manifest=${pdfManifest}\n`
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
main().catch(e => { console.error(e); process.exit(1); });
|