在小型企業工作的好處之一是,你可以放心地使用不需要擴展到極端規模的工具。軟體世界中的一個例子就是單儲存庫(monorepos)。雖然單儲存庫可以很好地擴展(請參閱 Google、Facebook 等),但這樣做需要特殊的工具和基礎設施。使用純 Git,你只能走這麼遠。雖然你可以使用它,但它有顯著的優勢,例如能夠在單一提交中對系統的多個部分進行原子性更改,從而消除整個類別的兼容性和集成問題。你始終可以在之後拆分單儲存庫(請參閱 git-filter-repo)。
因此,假設你是一個中小型團隊,正在使用單儲存庫。讓我們進一步假設這個單儲存庫存儲了你公司的所有程式碼,這意味著它涵蓋了許多不同的程式語言——這是一個多語言單儲存庫。你可以使用什麼工具來以一致的方式管理版本?
我認為 Changesets 是一個可靠的選擇,即使它主要專注於 JavaScript/TypeScript 生態系。
目前有一個關於原生多語言單儲存庫支持的公開討論。然而,即使沒有原生支持,Changesets 也具備了在許多設置中按原樣實現這一點所需的掛鉤。
對於任何版本管理工具,你通常會尋找如何:
Changesets 假設每個套件都有獨立的語義版本控制(即,所有套件都有自己的版本)。此外,每個套件都有自己的 CHANGELOG.md。
Changesets 團隊還提供了一個 GitHub Action,changesets/action,它允許為 version 和 publish 命令指定自定義腳本。這種自定義是 Changesets 支持多語言儲存庫的關鍵。
在 Changesets 中,工程師將「changeset」文件提交到儲存庫,這些文件定義了變更日誌的內容,以及哪些套件版本需要更新(例如,major、minor、patch)。
有關更多詳細信息,請參閱 Changesets 文檔。
我喜歡 just。我也非常喜歡 uv scripts。下面的示例同時使用了兩者。
我還假設你處於企業環境,你的所有單儲存庫都是私有的,而不是開源的。
我推薦的組織結構(至少在撰寫本文時)如下所示。
將所有套件放在 `packages/` 目錄下,無論它們是什麼語言。我也喜歡將文檔作為程式碼,所以讓我們假設你還有一個 `docs/` 目錄,並且你的文檔是用基於 JavaScript 的前端編寫的(例如 Starlight),以便稍後突出一個細微差別。
有了這個設置,你可以配置 Changesets,在根目錄使用一個代理 pnpm workspace,其中包含你所有的套件。
並聲明你的 Changesets 依賴項:
你現在也應該更新你的 `.gitignore`:
由於 Changesets 是為 JavaScript 設計的,我們還需要為我們所有的套件提供「代理」`package.json` 文件;Changesets 使用這些文件來執行版本更新。
在這個設置中,請注意我們是如何有意地嘗試將我們的內部 `docs/` 排除為 pnpm workspace 成員的——我們只想版本化套件。為此,請將 `docs/` 目錄聲明為其自己的 pnpm workspace,否則它將嘗試將 `docs/` 的依賴項合併到根目錄的 `package-lock.json` 中。這可以很簡單地做到:
接下來,我們可以添加我們的 Changeset 配置:
接下來,我們希望自動化我們的發布。也就是說,生成變更日誌 PR、更新套件元數據、推送標籤以及觸發基於這些標籤的構建。
讓我們從我們的 GitHub Workflow 定義開始,並解開它調用的腳本。
你可能會想知道為什麼我們要顯式運行一個工作流程,而不是使用像 `on.push.tags` 這樣的觸發器。
事實證明,GitHub 在這種直觀的方法上存在兩個致命的缺陷(在撰寫本文時)。首先,如果你一次推送超過 3 個標籤,工作流程將不會觸發。不幸的是,這在單儲存庫中是一個相對常見的情況。其次,GitHub 的 `on.push.tags` 觸發器非常不可靠。即使你使用 PAT(個人存取令牌)按照他們的指示操作,這種不可靠性仍然存在。
因此,請考慮改為為此目的使用 `workflow_call`,就像我在此處所做的那樣。
設置版本:`just version` 是實現多語言支持的關鍵。
多語言支持的粘合劑的核心是如何實現 `sync-versions.py`。
關鍵部分是我們依賴 Changesets 在我們調用 `npx @changesets/cli version` 時為我們更新 `package.json` 中的版本,然後由我們負責將該版本適當地傳播到相應語言的原生元數據中。
這是一個使用非常簡單的解析的示例。你可以為你使用的語言編寫類似(或更好!)的內容。
在標準的 Changesets 流程中,你現在會在 GitHub 上收到一個包含適當的 `CHANGELOG.md` 更新以及所有相關套件元數據更新的拉取請求。
一旦該請求被合併,相同的操作將會運行,識別所有 `.changeset` 文件已被消耗,並推送標籤。
使用我們的示例配置,Changesets 只會推送標籤,而不會發布套件,因為我們在 `changeset/config.json` 中設置了 `"private": true`,並且所有套件都設置為 `private: true`。
通常,你之後會想對這些推送的標籤做出反應。例如,構建新的 Docker 鏡像。
為此,而不是像你通常會預期的那樣使用 `on.push.tags` 觸發器,你可能需要一個 `workflow_call`。請參閱文章前面的提示以了解原因。
Changesets 即使在沒有對多種語言的直接原生支持的情況下,今天也能夠在多語言單儲存庫中管理每個套件的語義版本控制和變更日誌。訣竅在於將 JavaScript 套件清單視為版本更新的權威來源,然後通過你自己的腳本將這些更新同步到語言原生的清單。
存在一些陷阱(例如,為你想要獨立的子目錄顯式創建獨立的 `pnpm-workspace.yaml` 文件,或使用單獨的個人存取令牌來推送標籤),但這些都不是阻礙你從 Changesets 便利的工作流程中受益的障礙。
我以前曾建議使用像 semantic-release 這樣的工具來對單儲存庫進行版本管理。自從嘗試了 Changesets 後,我已經被它讓開發者為未來的內部工程師編寫提交消息,同時還為最終用戶添加單獨的變更日誌說明的好處所說服。這兩類受眾通常是不同的,依賴單一的約定提交來服務兩者通常不是最佳選擇。