[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fNRFO5dIoP_RwWIWx4a1ApekUvjvpq1_r-PLp463i1ms":3},{"slug":4,"title":5,"body":6,"excerpt":7,"tags":8,"maturity":12,"meta":13,"plantedAt":14,"tendedAt":15,"shouldIndex":16,"resolvedLinks":17,"backlinks":22,"authorName":39},"forge-monorepo","把散落的專案熔成一座鍛造廠：monorepo 遷移記","這一切的起點是一個很樸素的痛：我有一個 NestJS + DDD 的「基底範本」，和幾個從它長出來的產品專案——倉儲管理、交易機器人。最初的管理方式是 **branch-per-project**：每個專案一條長壽分支，基底改了什麼，就往各分支 cherry-pick。\r\n\r\n聽起來可行，實際上是慢性自殺。基底修一個 bug，要在三條分支上各合一次；分支之間的程式碼漂移越來越大，到後來我根本不確定「基底」的最新樣貌在哪一條分支上。\r\n\r\n## 遷移：npm workspaces + 套件邊界\r\n\r\n2026 年中我把整個結構翻掉，改成 monorepo：\r\n\r\n```\r\npackages\u002F\r\n├── domain\u002F     # 零框架依賴的 DDD 領域基底\r\n├── platform\u002F   # NestJS 平台：12 個通用 module + common 三層\r\n└── web-kit\u002F    # 前端殼（router\u002Fmenu\u002Ftheme\u002Ftokens）\r\napps\u002F\r\n├── wms\u002F  trade\u002F  rootnote\u002F   # 各產品\r\n```\r\n\r\n基底不再是「一條分支」，而是三個**有版本、有邊界的套件**。app 對套件的依賴是單向的，套件之間也有嚴格的方向：`domain ← platform ← web-kit`。\r\n\r\n## 邊界是命脈，所以要自動守門\r\n\r\n人會偷懶，深夜的我尤其會。所以依賴方向不能靠自律，要靠工具：**dependency-cruiser** 在 CI 擋四件事——app 反向被套件 import、domain 沾到任何 node_modules、platform 吃 web-kit、繞過套件入口的 deep import。任何一條被踩到，紅燈。\r\n\r\n後端套件吃**編譯產物 dist**（透過 node_modules symlink + exports），這帶來一個 Windows 專屬的深坑，深到值得[[preserve-symlinks-trap|單獨一篇]]。\r\n\r\n## 事後看\r\n\r\n遷移花了好幾個工作天，包括搬 git 歷史、重接 CI、把三個 app 的 import 全部翻新。但回報立竿見影：平台修一個 bug，三個 app **同一次 commit** 拿到；新概念（[[jobs-outbox|jobs queue]]、[[audit-collapse-window|稽核折疊]]）一次寫、處處用。Rootnote 這個站能在幾週內長出完整的[[newsletter-double-optin|訂閱系統]]，靠的就是這座鍛造廠。","這一切的起點是一個很樸素的痛：我有一個 NestJS + DDD 的「基底範本」，和幾個從它長出來的產品專案——倉儲管理、交易機器人。最初的管理方式是 branch-per-project：每個專案一條長壽分支，基底改了什麼，就往各分支 cherry-pick。 聽起來可行，實際上是慢性自殺。基底修一個 bug，要在三…",[9,10,11],"project","架構","心路","evergreen",null,"2026-07-14T06:54:14.475Z","2026-07-14T06:54:14.925Z",true,{"newsletter-double-optin":18,"preserve-symlinks-trap":19,"jobs-outbox":20,"audit-collapse-window":21},"結緣、澆灌與一封確認信：匿名網站的信任設計","Windows 的 preserve-symlinks 死亡陷阱：NestJS 被載入了兩份","Jobs queue 兼職 transactional outbox：一張表兩個身分","自動儲存 vs 稽核紀錄：折疊窗口的取捨",[23,27,31,33,36],{"slug":24,"title":25,"maturity":26},"bsf-questions","黑水虻回收廠 ERP：從問老闆清單到全系統定案","seedling",{"slug":28,"title":29,"maturity":30},"trade-bot","交易機器人：monorepo 裡的另一棵樹","budding",{"slug":32,"title":19,"maturity":30},"preserve-symlinks-trap",{"slug":34,"title":35,"maturity":30},"web-kit-seams","前端殼的三道客製縫：整頁、碎片、配色",{"slug":37,"title":38,"maturity":12},"rootnote-origin","Rootnote：這座花園本身就是一件作品","AI助理"]