fix(ci): 全量刷新判定改为「非纯内容改动即全量」+ 刷新分批(又拍云 50/次)

上一版用「themes/layouts/assets 等 dirs 白名单」判全局,实测被绕过:
build 2 的 diff 只有 scripts/purge_list.js(主题改动在上一个 commit),
判成非全局 → 只刷了 10 条固定入口页 → 文章页仍引用已被 --delete 删掉的
旧 page-only.min.1e47477b…(靠 CDN 缓存才 200,缓存一过期即 404)。

改法:
- 判定反转:diff 只含 content/** 才算「纯内容改动」(走精细刷新),
  其它一律全量刷 public 下所有 .html。
- 拿不到基线时:push 事件按全量(宁多刷不漏刷),定时任务只刷固定入口页。
- 输出一行 stderr 诊断(base/变更数/纯内容/全量/清单条数)便于事后核对。
- .cnb.yml:清单按 50 条/批 + 批间 6s 调用 upx purge(又拍云限制
  单次 ≤50、每分钟 ≤600),并逐批打日志;不再一次性提交 1300+ 条。
This commit is contained in:
zqlit committed 2026-10-05 17:57:51 +08:00
1 parent 5a50df6e23
commit c1f9a0c6f0
2 files changed
+44 -18

No files matched your search

+22 -12
View File
@@ -9,11 +9,12 @@
* 卡在旧副本上(源站 47935B / CDN 41454B)。
*
* 策略:固定入口页(保底,逻辑不变)
* + 本次改动过的 content/*.md 对应的输出页
* + **全局性改动(themes/layouts/assets/static/data、hugo/config)时,追加全站所有 HTML**。
* + **非「纯内容改动」时,追加全站所有 HTML**
* + 纯内容改动(diff 只含 content/**)时,只追加改动内容对应的输出页
* - 输出页靠「frontmatter 的 url / slug / 目录名 / 文件名」推导,并**校验
* public/ 下确实存在该文件**才加入,避免刷不存在的 URL(刷了也无害,但脏)。
* - 拿不到 git 基线(单提交仓库 / 浅克隆)时自动降级:只输出固定入口页。
* - 拿不到 git 基线(单提交仓库 / 浅克隆)时:push 事件按「全量」处理(宁多刷
* 不漏刷,代价只是刷新耗时),定时任务只输出固定入口页。
*
* 用法:node scripts/purge_list.js [BASE_REF] BASE_REF 默认 HEAD~1
*/
@@ -69,15 +70,18 @@ function walkHtml(dir, 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));
// ★ 全量刷新判定:**只有「纯内容改动」才走精细刷新,其它一律全量**
// 为什么必须这么严:模板用 resources.Fingerprint 给 JS/CSS 生成带 hash 的
// 文件名,而 upx sync 带 --delete(远端不存在的文件会被删)。只要产物的资源
// 文件名变了,CDN 上还在的旧 HTML(TTL 8 天)就会引用**已被删掉的旧文件**
// → 评论区/交互直接断链。所以凡是产物可能变的改动,都得让 HTML 重新拉取。
// 为什么不用「dirs 白名单」:2026-10-05 踩过 —— 主题改动与 purge 规则改动分成
// 两个 commit 时,后一个 commit 的 diff 里没有主题文件 → 被判成非全局 → 文章页
// 漏刷。改成「非 content 即全量」后,不再依赖改动被切进哪个 commit。
const isCron = process.env.CNB_IS_CRONEVENT === 'true';
const isContentOnly = changed.length > 0 && changed.every((f) => /^content\//.test(f));
const isGlobal = changed.length > 0 ? !isContentOnly : !isCron;
if (isGlobal) {
for (const p of walkHtml('public')) {
const rel = p.replace(/^public[\\/]/, '').split(path.sep).join('/');
@@ -85,6 +89,12 @@ if (isGlobal) {
}
}
// 诊断输出(进构建日志,便于事后核对判定是否正确)
process.stderr.write(
`[purge] base=${base} 变更=${changed.length} 条 纯内容=${isContentOnly} ` +
`全量=${isGlobal}${changed.length ? '' : '(无基线)'} → 清单=${urls.size} 条\n`
);
for (const f of changed) {
if (!/^content\/.*\.md$/.test(f) || !fs.existsSync(f)) continue;
const src = fs.readFileSync(f, 'utf8');