維護記錄

完成的工作

  • 拉取 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].mdYYYYMMDD-{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 接口擴展:新增 titleoldUrlhash3 字段(替代 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 + 同名 .svg
    • git 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.ts
    1let 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].mdcontent/posts/YYYYMMDD-{slug}-{hash3}.md
  • 重命名:220 個 static/images/covers/YYYYMMDD[hash].svg → 同名格式
  • 修改:221 個 .md 的 front matter(新增 aliases: 字段)