決定不直接依賴 Bamboo 之後,下一個問題是:如何重寫核心,卻不讓作者感覺整套部落格被換掉?

這次改版把「不影響使用者操作方式」列為必要條件。作者仍然把公開文章放進 content/,草稿仍然留在 drafts/;本機仍然執行 ./scripts/build.sh;用手機或桌面版 Codex 時,仍然只要說「發布這篇」。Git push 之後,也仍由 GitHub Actions 部署到同一個 Cloudflare Worker。

真正被替換的,只有這條流程中間的生成器。

一個檔案就是完整核心

新的執行檔直接命名為 poise。它要求 Python 3.9 以上版本,只使用標準函式庫,沒有 requirements.txt、套件鎖定檔或安裝步驟。

單檔不是為了追求最少行數,而是讓核心可以被完整閱讀。設定解析、內容模型、Markdown 渲染、頁面輸出與命令列入口都在同一個可版控檔案中,但內部仍維持清楚的責任分界:

  • SiteConfig 讀取並驗證 poise.json
  • Frontmatter 只解析專案真正使用的 YAML 子集。
  • Document 保存文章、靜態頁、標籤、日期與連結資訊。
  • Markdown 渲染器處理標題、段落、清單、引用、表格與程式碼。
  • 站點建置器生成首頁、分頁、文章、標籤、彙整、RSS 與 sitemap。

這種設計吸收了研究 Bamboo 時確認的分層優點,卻把實作範圍鎖在 Poise 已經承諾的功能裡。

安全規格不再只靠外部稽核

舊流程已經會在建置前驗證原稿、建置後掃描成品。新生成器保留這兩道防線,還把關鍵規則直接放進核心。

文章中的原始 HTML 會被跳脫,圖片語法與媒體 frontmatter 會被拒絕;slug 必須是 ASCII 小寫 kebab-case;有日期的文章標題必須符合 #數字 標題。草稿若被放進公開內容目錄,建置也會失敗。

這代表即使有人繞過發布 Skill,直接執行生成器,也不能輕易產生違反網站政策的內容。原稿驗證、生成器約束、產物稽核與 Cloudflare CSP 共同形成多層保護。

失敗的建置不應破壞上一版網站

這次重構也修正了一個容易被忽略的風險:如果生成器先清空 site/,接著才遇到壞掉的內部連結,開發者可能只剩下一個不完整的輸出目錄。

新的建置會先在暫存目錄生成完整網站。只有內容解析、連結檢查與檔案寫入全部成功,才用原子替換更新目標目錄;若過程失敗,上一版成品仍然保留。外層的 build.sh 也採取同樣的 staging 與備份策略,加入 Cloudflare 控制檔並通過最終稽核後,才讓新網站正式就位。

這讓「建置成功」有了更嚴格的意思:不是某些 HTML 已經被寫出來,而是整個可部署成品已經完成並通過政策檢查。

可重現比速度更值得信任

靜態網站生成器最理想的特性,是相同輸入應該得到相同輸出。Poise 因此固定文章排序、RSS 時間、標籤網址與 sitemap 內容,並加入重複建置結果完全一致的測試。

測試也涵蓋幾個重要失敗情境:圖片或媒體欄位被拒絕、壞掉的內部連結使建置失敗、不安全的輸出路徑不會被清除,以及失敗時原有輸出仍然存在。這些案例比單純確認首頁能生成,更接近實際維護時需要的信心。

改名,但不突然切斷舊流程

設定檔由 light-marmite.json 改名為 poise.json,建置腳本與 GitHub Actions 也全部改用 poise。不過,舊的 ./light-marmite 指令暫時保留為相容入口;它會顯示棄用提示,再把參數交給新生成器。舊設定檔也保留遷移期讀取能力。

這個相容層很小,卻讓改版不必依賴一次完成的環境切換。使用者可以繼續用原本的發布方式,維護者則能逐步移除舊名稱。

輕量的下一個階段

Poise 現在不只是「用一個小工具建置的部落格」。生成器、內容規格、發布 Skill、測試與部署流程都在同一個 repository 裡,共用同一套限制。

研究 Bamboo 的價值也在這裡落地:我們沒有複製一個通用框架,而是用它幫助釐清結構,再把真正需要的部分寫進自己的核心。最後得到的不是功能最多的生成器,而是一個作者不用安裝、維護者可以讀完、部署環境能離線執行,而且輸出始終符合 Poise 原則的單檔程式。