版本升至 1.2.6(1.2.5 审核被拒未发布,整改内容并入本版);README 新增原创声明(4.2.1/4.2.2 不适用);Hero 描述来源默认 none,选一言自动填入官方接口、清空回退本地句子库;文档列出两个 IP 定位候选接口与访客 IP 披露文案;含上一轮 4.4.2 内容渲染安全与 5.2 字体许可证修复。
94 lines
3.7 KiB
JavaScript
94 lines
3.7 KiB
JavaScript
/**
|
||
* 构建后置处理:
|
||
* 1. 校验主题 logo 存在且只保留一份(public/assets/logo.png → templates/assets/logo.png)
|
||
* 2. 合并 compat/assets 下的历史版本静态资源,避免站点/代理缓存中的旧 HTML
|
||
* 仍然引用上一版本的哈希文件名时出现 500(FileNotFoundException)
|
||
* 3. 校验所有 templates/*.html 引用的静态资源真实存在,缺失即构建失败
|
||
*/
|
||
import fs from "node:fs";
|
||
import path from "node:path";
|
||
import { fileURLToPath } from "node:url";
|
||
|
||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||
const assetsDir = path.join(root, "templates", "assets");
|
||
const compatDir = path.join(root, "compat", "assets");
|
||
const templatesDir = path.join(root, "templates");
|
||
|
||
fs.mkdirSync(assetsDir, { recursive: true });
|
||
|
||
// 1. logo:旧实现把 templates/logo.png 再复制一份到 assets,安装包里会有两份 1.5MB 大图。
|
||
// 现在源文件统一放在 public/assets/logo.png(256x256 压缩版),这里只做存在性校验。
|
||
if (!fs.existsSync(path.join(assetsDir, "logo.png"))) {
|
||
console.error("[postbuild] 未找到 templates/assets/logo.png(public/assets/logo.png 应随构建复制)");
|
||
process.exit(1);
|
||
}
|
||
console.log("[postbuild] logo.png 已去重,仅保留 templates/assets/logo.png");
|
||
|
||
// 1b. 第三方字体许可证全文必须随构建产物分发(审核指南 5.2:许可证传递 / 版权声明保留)
|
||
const oflFile = path.join(assetsDir, "fonts", "LXGWWenKai", "OFL.txt");
|
||
if (!fs.existsSync(oflFile)) {
|
||
console.error(
|
||
"[postbuild] 未找到 templates/assets/fonts/LXGWWenKai/OFL.txt" +
|
||
"(public/assets/fonts/LXGWWenKai/OFL.txt 应随构建复制)"
|
||
);
|
||
process.exit(1);
|
||
}
|
||
console.log("[postbuild] LXGW WenKai 的 OFL 1.1 许可证全文已随包");
|
||
|
||
// 2. 历史资源合并(已存在的同名文件以当前构建为准,不覆盖)
|
||
if (fs.existsSync(compatDir)) {
|
||
let copied = 0;
|
||
let skipped = 0;
|
||
for (const entry of fs.readdirSync(compatDir, { withFileTypes: true })) {
|
||
if (!entry.isFile()) continue;
|
||
const src = path.join(compatDir, entry.name);
|
||
const dst = path.join(assetsDir, entry.name);
|
||
if (fs.existsSync(dst)) {
|
||
skipped++;
|
||
continue;
|
||
}
|
||
fs.copyFileSync(src, dst);
|
||
copied++;
|
||
}
|
||
console.log(`[compat] 历史静态资源:复制 ${copied} 个,当前构建已覆盖 ${skipped} 个`);
|
||
}
|
||
|
||
// 3. 资源引用校验
|
||
const missing = [];
|
||
const checked = new Set();
|
||
/** 主题静态资源前缀,必须与 astro.config.mjs 的 base 保持一致 */
|
||
const prefixes = ["/themes/warm-island/assets/"];
|
||
|
||
/** Thymeleaf 的 @{...} 链接表达式同样需要校验 */
|
||
const REFERENCE_RE =
|
||
/(?:href|src)=["']([^"']*?\/(?:themes\/[^/]+)\/assets\/[^"']+?)["']/g;
|
||
|
||
for (const file of fs.readdirSync(templatesDir)) {
|
||
if (!file.endsWith(".html")) continue;
|
||
const html = fs.readFileSync(path.join(templatesDir, file), "utf8");
|
||
REFERENCE_RE.lastIndex = 0;
|
||
let match;
|
||
while ((match = REFERENCE_RE.exec(html)) !== null) {
|
||
let ref = match[1];
|
||
if (ref.includes("@{")) continue; // 由 Halo 运行时解析,跳过
|
||
for (const prefix of prefixes) {
|
||
if (!ref.startsWith(prefix)) continue;
|
||
const rel = ref.slice(prefix.length).split("#")[0].split("?")[0];
|
||
const key = `${file}:${rel}`;
|
||
if (checked.has(key)) continue;
|
||
checked.add(key);
|
||
if (!fs.existsSync(path.join(assetsDir, rel))) {
|
||
missing.push(`${file} -> ${ref}`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (missing.length > 0) {
|
||
console.error(`[verify] 发现 ${missing.length} 处静态资源缺失:`);
|
||
for (const item of missing) console.error(` - ${item}`);
|
||
process.exit(1);
|
||
}
|
||
|
||
console.log(`[verify] 静态资源引用校验通过(${checked.size} 条引用)`);
|