维护记录#
完成的工作#
- 拉取 origin/master 的 18 个新提交(fast-forward),并同步 PaperMod 子模块到
v8.0-139 ✅ - 创建新文章
content/posts/20260817[05hog4].md(标题「文章的变化」),作者口述风格,半角逗号占位符写法,参考近期 20260807[01jma3].md 的日志类 frontmatter ✅ - 用
rename-posts.js 由 body 算出 hash 并重命名占位文件为 20260817[05hog4].md ✅ - 用
gen-covers.js --files ... --force 生成封面 static/images/covers/20260817[05hog4].svg(teal / patternConcentric)✅ - 手动修正 front matter 中
cover.image 引用(rename-posts 不会同步更新 cover 路径,这是已知 bug)⚠️ hugo --minify 构建通过,新页面 public/posts/2026081705hog4/ 正常生成,封面路径正确解析 ✅- 更新
content/claudelog/2026-08-17.md ✅
遇到的问题#
gen-covers.js --files 接受相对路径时会被脚本拼接成 content/posts/content/posts/...,必须传 basename(不带 content/posts/ 前缀) ⚠️ - 已规避rename-posts.js 在重命名占位文件后不会同步更新 front matter 里的 cover.image 路径 ⚠️ - 已手动修正;建议下次让用户或脚本执行后立即检查 front matter 完整性
下次建议#
- 可以考虑给
rename-posts.js 加一个 --sync-cover-fm 开关,自动同步 cover.image 引用,避免遗漏
1raw=$(hashed-slug)
2old="images/covers/<oldSlug>.svg"
3new="images/covers/<newSlug>.svg"
4raw.replace(old, new)
维护记录 (追加 - 命名规则重构 + CI 修复)#
完成的工作#
命名规则改造#
- 目标:改造 posts 命名规则
YYYYMMDD[hash].md → YYYYMMDD-{slug}-{hash3}.md,让文件名一眼能读懂文章标题 ✅ - 方案选型:经 ask_user_question 与用户确认方案 B(日期 + slug + 3 位 hash)、slug 从 title 自动生成、中文保留、迁移全部 221 篇历史文章、生成 Hugo aliases 自动 301
scripts/rename-posts.ts 改造:- 新增
computeHash3 纯函数(SHA-256 前 2 字节 → 16-bit base36) - 新增
slugify 纯函数(保留 CJK unicode + 拉丁字母数字,其余替换为连字符,长度上限 80,空串兜底 'untitled') - 新增
oldUrlFromSlug 纯函数(支持 YYYYMMDD[hash] 和 YYYY-MM-DD-slug 两种历史格式推导旧 URL) RenamePlan 接口扩展:新增 title、oldUrl、hash3 字段(替代 hash6)buildReport 改用 slugify + computeHash3 命名executePlan 无需改动(已 git mv + 同步 cover)
- 新增
scripts/migrate-slug-scheme.ts:一次性迁移脚本- 默认 dry-run,加
--apply 才落地 - 调用
buildReport + executePlan,并在 front matter 注入 aliases: ["/posts/{oldSlug}/"] injectAliases 支持合并已有 aliases(数组/字符串两种格式)- 碰撞检测 + 失败即停
- 测试套件更新:
rename-posts.test.ts 中已有断言 plans[0]!.newSlug.startsWith('20240115') 验证 date 前缀保留 → 收紧为正则 /^20240115-.+-[a-z0-9]{1,4}$/- 新增 13 个测试:
computeHash3 ×3 / slugify ×7 / oldUrlFromSlug ×3 - 测试套件 138 → 151,全部通过 ✅
迁移执行#
- dry-run 报告:221 篇待迁移,0 碰撞
- 执行:
node scripts/dist/migrate-slug-scheme.js --apply --verbose- 221 篇 .md + 220 个 .svg 重命名(全部用
git mv 保留历史) - 441 个文件改动,git 全部识别为
R(rename),内容相似度 54%-100%
- front matter 注入 aliases:每篇文章新加
aliases: ["/posts/{oldSlug}/"] - Hugo 构建验证:
- Pages: 264 → 265
- Aliases: 15 → 224(旧 URL 全部生成了 301 等价页面)
- 抽样
20260817-文章的变化-jtc/index.html 包含:1<link rel=canonical href=https://sdttttt.online/posts/20260817-文章的变化-jtc/>
2<meta http-equiv=refresh content="0; url=...">
- 本地测试:151/151 通过 ✅
Git 提交(多 commit 策略,经 ask_user_question 确认)#
5a417ea chore(scripts): 新增 YYYYMMDD-slug-hash 命名支持 + 一次性迁移脚本5a7ad18 chore(posts): 迁移 221 篇文章到 YYYYMMDD-slug-hash 命名格式
第一次推送与冲突解决#
git push origin master 被远程拒绝:CI 的 chore: auto-fix content(4ace5ca)刚把 20260817[05hog4] 改名为 20260817[03wwo2](因 body 改了导致 SHA-256 变了)git pull --rebase 出现 rename/rename 冲突:CI → 03wwo2,我 → 20260817-文章的变化-jtc- 解决策略:保留我的迁移结果(
jtc),丢弃 CI 中间产物(03wwo2)git rm content/posts/20260817[03wwo2].md + 同名 .svggit add 我的迁移结果git rebase --continue
- 失误 1(诚实交代):rebase 后
20260817-文章的变化-jtc.md 文件里残留冲突标记- 第一次尝试修复时直接
write 文件,差点覆盖掉用户手动润色的版本(末尾换行、空行) - 通过
git show 4ace5ca: 提取用户的精确版本,再叠加新 frontmatter 恢复 ✅ - 用
git commit --fixup=9c99956 + rebase -i --autosquash 把修复折进原 commit,不增加冗余 commit
- 重新编号后的两个 commit:
613cd99(脚本) + 2445eae(迁移) - 推送成功 ✅
CI 失败与修复#
- 推送后 CI deploy 失败:
1ERROR error building site: "/content/posts/20200311-Github-Actions-3fw.md:2:1":
2EOF looking for end YAML front matter delimiter
- 根因:
scripts/gen-covers.ts:injectCoverField line 266 字符串模板 ---\n${newFm}--- 中,闭合符 --- 前缺 \n- 当文件 frontmatter 末尾为
aliases: [...]\n(我迁移后所有文章都是这种结构),走 if 分支(fm.replace)重写 cover 块后 newFm 末尾是 \n(来自 + '\n'),模板拼接产生 ---\n...\n--- 看似正常- 但当文件没有 trailing newline 时,regex
[\s\S]*?\n--- 不匹配(因为根本没有第二个 ---),replace 不替换 → 文件保持原样 → 但原文件 frontmatter 没问题… 矛盾!
- 真正重现:本地用 v0.161.1 + 手动写一个无 trailing newline 的测试文件 + 跑 injectCoverField 老逻辑 → 输出文件 frontmatter
aliases: [...]---(无 \n 分隔)- Hugo 解析:严格模式下视
--- 不在行首 → EOF 找不到结束符 → 报错
- 修复:
scripts/gen-covers.ts1let newFm = fm.replace(/^cover:\s*\n((?: .+\n?)+)/m, newCover + "\n");
2// 确保 newFm 以换行结尾,避免与闭合 --- 之间缺少分隔符
3if (!newFm.endsWith("\n")) newFm += "\n";
4const newRaw = raw.replace(/^---\n[\s\S]*?\n---/, `---\n${newFm}---`);
- 移除 else 分支多余的
+ '\n'(避免双换行)
- 本地验证:
- 复现脚本验证 buggy 输出:
aliases: ["/posts/2020031100oh3i/"]---(无 \n) - 复现脚本验证 fixed 输出:
aliases: ["/posts/2020031100oh3i/"]\n---(有 \n) - 跑
hugo --minify 在 buggy 文件上 → 与 CI 完全一致的 EOF 错误 → 确认根因 - 跑
hugo --minify 在 fixed 文件上 → 通过 ✅ - 测试套件 151/151 通过 ✅
- 诚实交代 - 失误 2:我误以为本地 v0.161 build OK = fix 没问题
- 实际上我没有把 bug 真正跑出来 —— 老代码生成的输出与原文件字节序列相同,所以 git diff 显示 0 改动
- 真正的 bug 只在没有 trailing newline 的输入上才会暴露
- CI 上的 v0.165 比本地 v0.161 严格,所以 CI 才报错
- 教训:纯函数层面 fix 一定要构造能触发 bug 的输入 + 独立验证,不能依赖"diff 为空 = OK"
推送修复与最终状态#
32e7cd4 fix(gen-covers): 修复 injectCoverField 丢失 frontmatter 末尾换行的 bug- CI 重新跑 → 成功 ✅(产生
27fa25b chore: auto-fix content,是部署成功后的文件名微调) - 新文章线上 URL:https://sdttttt.online/posts/20260817-文章的变化-1e69/(后缀 hash 因 prettier 规范化略变,deploy.yml 自动处理)
遇到的问题#
- rename/rename 冲突:本地迁移和 CI auto-fix 在同一文件上往不同方向 rename。教训:涉及大量 rename 的操作前应先 pull 同步 CI 状态 ⚠️
- rebase 后冲突标记残留:git 有时不会自动清理 file content 中的冲突标记,只清理 status。教训:rebase 后必须
hugo --minify 验证 build,不能只看 git status 干净 ⚠️ - bug 漏检:本地 v0.161 与 CI v0.165 行为不一致(后者更严格),导致 bug 在本地测不出来。教训:涉及 frontmatter / 严格解析的代码应该用与 CI 同版本的工具验证 ⚠️
- 不区分大小写的本地 + 区分大小写的远程:APFS 默认 case-insensitive,导致本地文件名
Github-Actions 在 Linux CI 上行为可能不同。本次没踩坑,但值得警惕 ⚠️
下次建议#
- 在 CI 流程开始前加一步
git fetch + check clean 的 sanity check,避免 race - 给
gen-covers.ts 加专门测试:用 writeFile 写出无 trailing newline 的输入,验证输出 frontmatter 闭合 - 在 deploy workflow 加 step:
hugo --minify 失败时打印前 3 个报错的 .md 文件的 head/tail,便于排查 - 考虑给 rename-posts.js 加 dry-run-only 模式(默认 dry-run,避免误操作)
- 新 hash 计算结果应在文件 gitattributes 或 .gitignore 里加 ignore,避免 auto-fix commit 噪声(目前
27fa25b 这种 commit 频率会越来越高)
文件变更清单#
- 新增:
scripts/migrate-slug-scheme.ts - 修改:
scripts/rename-posts.ts(computeHash3 + slugify + oldUrlFromSlug + RenamePlan 字段) - 修改:
scripts/gen-covers.ts(injectCoverField 末尾换行兜底) - 修改:
scripts/__tests__/rename-posts.test.ts(+13 测试) - 修改:
scripts/__tests__/rename-posts-execute.test.ts(RenamePlan 字段适配) - 重命名:221 个
content/posts/YYYYMMDD[hash].md → content/posts/YYYYMMDD-{slug}-{hash3}.md - 重命名:220 个
static/images/covers/YYYYMMDD[hash].svg → 同名格式 - 修改:221 个 .md 的 front matter(新增
aliases: 字段)