Files
blog/scripts/purge_list.js
T
zqlit 66c27c7b62 fix(ci): CDN 刷新清单改为「固定入口页 + 本次改动过的内容页」
又拍云对 HTML 的 max-age 是 8 天,而流水线只刷固定白名单(/ sitemap rss
archives posts comment links)。不在清单里的页面改动后,源站已更新、CDN 仍发
旧副本——本次 about.html 就卡住了(源站 47935B 含新板块,CDN 仍是 41454B)。

- 新增 scripts/purge_list.js:读 git diff 找出改动的 content/*.md,按
  slug/url/目录名/文件名推输出页,并校验 public/ 下确实存在才加入
- .cnb.yml 刷新步骤改用它,脚本失败时兜底只刷首页
- git diff 必须带 -z / core.quotepath=false,否则非 ASCII 路径会被转义引号包裹,
  校验必然失败(本次踩过)
2026-10-05 12:19:28 +08:00

81 lines
3.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* 生成「本次部署需要刷新的 CDN URL 清单」(每行一个 URL,输出到 stdout)
*
* 背景(2026-10-05 实测):
* 又拍云 CDN 对 HTML 是 max-age=691200(8 天),流水线里只刷固定入口页
* (/ sitemap rss archives posts comment links)。于是**不在白名单里的页面
* 即使源站已更新,CDN 仍会发最多 8 天的旧副本**:本次 about.html 就是这样
* 卡在旧副本上(源站 47935B / CDN 41454B)。
*
* 策略:固定入口页(保底,逻辑不变)+ 本次改动过的 content/*.md 对应的输出页。
* - 输出页靠「frontmatter 的 url / slug / 目录名 / 文件名」推导,并**校验
* public/ 下确实存在该文件**才加入,避免刷不存在的 URL(刷了也无害,但脏)。
* - 拿不到 git 基线(单提交仓库 / 浅克隆)时自动降级:只输出固定入口页。
*
* 用法:node scripts/purge_list.js [BASE_REF] BASE_REF 默认 HEAD~1
*/
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const SITE = 'https://usj.cc';
// 固定入口页:首页 / 聚合页 / 站点地图(只刷首页会让后面这些一直发旧副本)
const FIXED = [
'/',
'sitemap.xml',
'rss.xml',
'archives.html',
'posts.html',
'comment.html',
'links.html',
'about.html',
'circles.html',
'tiaozhuan.html',
];
function sh(cmd) {
try {
return execSync(cmd, { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8' });
} catch {
return '';
}
}
// ★ 必须用 -z:git 默认会把非 ASCII 路径写成 "\344\270\255..." 的带引号转义形式,
// 那种字符串在 fs.existsSync 上永远查不到,动态部分会静默失效。
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);
const urls = new Set(FIXED.map((p) => SITE + '/' + p.replace(/^\//, '')));
for (const f of changed) {
if (!/^content\/.*\.md$/.test(f) || !fs.existsSync(f)) continue;
const src = fs.readFileSync(f, 'utf8');
const fm = (src.match(/^---\r?\n([\s\S]*?)\r?\n---/) || [])[1] || '';
const grab = (k) => {
const m = fm.match(new RegExp('^' + k + ':[ \\t]*["\']?([^"\'\\s]+)', 'm'));
return m ? m[1] : '';
};
const cands = [];
const rawUrl = grab('url');
if (rawUrl) cands.push(rawUrl.replace(/^\/+|\/+$/g, '') + '.html');
const slug = grab('slug');
if (slug) cands.push(slug + '.html');
const name = path.basename(f, '.md');
const parent = path.basename(path.dirname(f));
if (name === 'index') cands.push(parent + '.html'); // 目录型内容(如 content/posts/xxx/index.md)
else cands.push(name + '.html');
for (const c of cands) {
const rel = c.replace(/^\/+/, '');
if (!rel || rel.includes('..')) continue;
if (fs.existsSync(path.join('public', rel))) urls.add(SITE + '/' + rel);
}
}
process.stdout.write([...urls].join('\n') + '\n');