fix(ci): 主题/layouts/assets 等全局改动时,purge 清单追加全站 HTML

问题:模板用 resources.Fingerprint 生成带 hash 的 JS/CSS 文件名,而 upx sync
带 --delete(远端不存在的文件会被删)。只改主题、不动 content/*.md 时,
purge_list 只输出固定入口页 + content 页 → 文章页的 HTML 仍留在 CDN(TTL 8 天),
里面引用的是**已被删掉的旧指纹 JS** → 评论区/交互断链(实测文章页仍引用
page-only.min.1e47477b…,该文件本轮已不在源站,只是还在 CDN 缓存里)。

修法:diff 命中 themes/ layouts/ assets/ static/ data/ archetypes/ 或
hugo.toml/config.* 时,walk public/ 把所有 .html 加入刷新清单
(本机验证 1336/1336 覆盖)。
This commit is contained in:
zqlit committed 2026-10-05 17:39:59 +08:00
1 parent 79c9f2371c
commit 5a50df6e23
1 file changed
+35 -1
+35 -1
View File
@@ -8,7 +8,9 @@
* 即使源站已更新,CDN 仍会发最多 8 天的旧副本**:本次 about.html 就是这样
* 卡在旧副本上(源站 47935B / CDN 41454B)。
*
* 策略:固定入口页(保底,逻辑不变)+ 本次改动过的 content/*.md 对应的输出页。
* 策略:固定入口页(保底,逻辑不变)
* + 本次改动过的 content/*.md 对应的输出页
* + **全局性改动(themes/layouts/assets/static/data、hugo/config)时,追加全站所有 HTML**。
* - 输出页靠「frontmatter 的 url / slug / 目录名 / 文件名」推导,并**校验
* public/ 下确实存在该文件**才加入,避免刷不存在的 URL(刷了也无害,但脏)。
* - 拿不到 git 基线(单提交仓库 / 浅克隆)时自动降级:只输出固定入口页。
@@ -49,8 +51,40 @@ const base = process.argv[2] || 'HEAD~1';
let changed = sh(`git -c core.quotepath=false diff --name-only -z ${base} HEAD`).split('\0').filter(Boolean);
if (!changed.length) changed = sh('git -c core.quotepath=false diff --name-only -z HEAD').split('\0').filter(Boolean);
// 递归列出 public 下所有 .html
function walkHtml(dir, out = []) {
let entries;
try {
entries = fs.readdirSync(dir, { withFileTypes: true });
} catch {
return out;
}
for (const e of entries) {
const p = path.join(dir, e.name);
if (e.isDirectory()) walkHtml(p, out);
else if (e.name.endsWith('.html')) out.push(p);
}
return out;
}
const urls = new Set(FIXED.map((p) => SITE + '/' + p.replace(/^\//, '')));
// ★ 全局性改动 → 刷新**全部** HTML(2026-10-05 新增)
// 为什么必须这么做:模板用 resources.Fingerprint 给 JS/CSS 生成带 hash 的文件名,
// 而 upx sync 带 --delete(远端不存在的文件会被删)。主题一改,page-only / core 等
// 包的文件名就变了 → 老指纹文件被删。文章页的 HTML 若还留在 CDN(TTL 8 天),
// 它引用的就是**已被删掉的旧 JS** → 评论区/交互直接断链。
// 所以凡是会改变「全站 HTML 引用资源」的改动,都必须让所有 HTML 重新拉取。
const GLOBAL_DIRS = /^(themes|layouts|assets|static|data|archetypes)\//;
const GLOBAL_FILES = /^(hugo|config)\.(toml|yaml|yml|json)$/;
const isGlobal = changed.some((f) => GLOBAL_DIRS.test(f) || GLOBAL_FILES.test(f));
if (isGlobal) {
for (const p of walkHtml('public')) {
const rel = p.replace(/^public[\\/]/, '').split(path.sep).join('/');
if (rel) urls.add(SITE + '/' + rel);
}
}
for (const f of changed) {
if (!/^content\/.*\.md$/.test(f) || !fs.existsSync(f)) continue;
const src = fs.readFileSync(f, 'utf8');