維護記錄#
完成的工作#
- 拉取 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: 字段)