前言¶
DSH 的 forge registry 默認面向常規代碼託管服務。如果團隊託管在 CNB(cnb.cool)上,改動請求(merge request)的狀態、檢查、diff、評審與合併這些信息,面板讀不到對應數據源,change request 和 working tree 面板就發揮不了作用。
genius-alray/dsh-plugin-forge-cnb 做的就是這件事:它註冊一個 ForgeProvider 到 ctx.forge,讓 DSH 的 forge 面板能直接讀取 CNB 的合併請求。插件分類爲「聯網工具」,由 genius-alray 維護,GitHub 星標 1,package.json 中版本爲 0.1.0,許可證 MIT。
下面介紹它的定位、能力和安裝啓用方式。
這是什麼¶
一句話定位:一個分發在 GitHub 上的 DSH bundle,爲 DSH forge registry 提供 CNB (cnb.cool) 的 provider。
它通過 ctx.forge 註冊 provider,覆蓋合併請求的讀取與操作:狀態、增刪行數、是否可合併、檢查、評審人、diff、文件內容、發佈評審和合並。
運行時沒有第三方依賴,只使用全局 fetch 和 Node 內置模塊,要求宿主 Node.js >= 22。客戶端和 DSH 包本身都不需要改動——註冊 provider 就夠了。
代碼倉庫同時提供英文 README.md 和中文版 README.zh.md。
核心能力¶
對照 README 的契約方法表,各項能力如下:
| 契約方法 | CNB 來源 |
|---|---|
repositoryFromRemote / changeRequestFromUrl / changeRequestUrl |
解析 remote URL 與 MR URL,支持 https、ssh://、scp 風格 remote、嵌套 group(a/b/c)、.git 後綴、端口;兩個 URL 函數互爲嚴格逆函數 |
fetch |
GET /{repo}/-/pulls/{n} + GET /{repo}/-/pulls/{n}/commit-statuses,外加每個 head commit 的增刪統計(…/files)、head commit 日期(…/git/commits/{sha})、緩存的倉庫合併設置 |
fetchChecks |
與 fetch 相同的讀取,但始終返回完整檢查列表,沒有 50 行的輪詢上限 |
findByHead |
GET /{repo}/-/pulls?state=open&page_size=100&page=N,客戶端側過濾(CNB 沒有按 head 分支過濾的接口) |
diff |
GET /{repo}/-/pulls/{n}/files,拼裝成一個 Git 風格的 unified patch |
fileText / fileUrl |
GET /{repo}/-/git/raw/{commit}/{path}(HTTP 413 時回退到 base64 contents 接口),以及 /-/blob/{commit}/{path} 網頁地址 |
merge |
重新讀取 MR,比對 head.sha 與確認過的 commit,再以 merge_style 調 PUT /{repo}/-/pulls/{n}/merge;從不發送 force |
markReady |
CNB 沒有對應接口,返回明確失敗並提示用戶在 Web UI 編輯 WIP 標題 |
submitReview |
POST /{repo}/-/pulls/{n}/reviews,帶 event 和內聯 comments |
readiness |
GET /user;token 過期或缺失讀爲 unauthenticated,並提示用戶執行 cnb login |
值得留意的映射限制¶
CNB 的部分數據模型與面板預期不一致,插件按固定規則處理,使用前應知道這些差異:
updatedAt:CNB 的 MR 詳情不帶時間戳,插件用 head commit 的 committer 日期(GET /{repo}/-/git/commits/{headSha},按 commit 緩存);該讀取失敗時取第一次觀察到該 head commit 的時間。closedAt:恆爲null,CNB 的 MR 接口沒有closed_at/merged_at。unresolvedThreads:恆爲0,CNB 的評審評論沒有 resolved / unresolved 標記。- additions/deletions:是 MR 變更文件的合計。CNB 對該列表有截斷,超大 MR 的總量可能偏低;沒有 MR 級統計接口。
- mergeability:
conflict/no-merge-base(或blocked_on爲code_conflict/no_merge_base)映射爲conflicting;mergeable/merging/merged映射爲mergeable;其餘爲unknown。 - mergeReady:需
state == open && !is_wip && !is_merged && mergeable_state == mergeable && blocked_on == unblocked。只看mergeable_state不夠——一個mergeable的 MR 仍可能在等評審或 CI。 - reviewDecision:任一評審人給出
changes_requested則取該值,全部通過爲approved,否則review-required;沒有評審人則爲none。 - mergeMethods:來自
GET /{repo}/-/settings/pull-request,按倉庫緩存 5 分鐘。若無權讀取(比如 token 缺repo-manage:r),則提供默認集合[merge, squash, rebase],避免合併按鈕消失。 - checksRollup:直接使用 CNB
commit-statuses的聚合state;單項檢查按created_at/updated_at作爲起止時間。輪詢快照上限 50 條並標記checksTruncated,fetchChecks返回完整列表。
diff 的拼裝方式¶
這個細節決定了面板能否正確顯示改動。面板用 jsdiff 9.0.0 的 parsePatch 解析 diff(),它根據 diff --git / --- / +++ 頭解析路徑,且會靜默丟棄只有 hunks 沒有文件頭的段落。而 CNB 的 /pulls/{n}/files 只返回 hunks,filename 字段只有文件名本身。因此插件:
- 從
blob_url/raw_url/contents_url(…/-/blob/{sha}/{path})恢復倉庫相對完整路徑,從不使用filename; - 每個文件輸出
diff --git a/<path> b/<path>、new file mode 100644/deleted file mode 100644、--- a/<path>++++ b/<path>(缺失一側用/dev/null),再原樣拼接已記錄的 hunks,LF 換行,帶結尾換行; - 空補丁(
add/delete/modify)渲染爲Binary files … differ,jsdiff 記爲isBinary,而不是當成空改動; - payload 暴露
previous_filename時保留重命名來源。
另外,評審正文通過 POST /pulls/{n}/reviews 提交 event + 內聯 comments;空的評審正文會被省略,而不是發送 ""。內聯評論使用與 diff() 相同的倉庫完整路徑。
安裝與啓用¶
插件是 DSH bundle,通過 GitHub 分發而非 npm。README 給出的安裝步驟分兩段:先取回倉庫,再把取出的目錄作爲 bundle 安裝到 profile(可以用 Plugin Manager 的 install-bundle 流程,或 plugin_manager 工具加 action: install_bundle):
git clone https://github.com/genius-alray/dsh-plugin-forge-cnb
bundle 自帶 cordis.patch.yml,負責插入 provider 行;安裝時選 bundle,不用手動挑行。宿主需要 Node.js >= 22,以及有效的 cnb login 會話(~/.cnb/token,CLI 刷新後插件會重新讀取)。
啓用只需要 profile 裏的一條記錄:
- insert:
- id: forge-cnb
name: 'dsh-plugin-forge-cnb'
config:
hosts: ['cnb.cool']
tokenFile: '~/.cnb/token'
timeoutMs: 10000
maxPages: 3
配置字段說明:
| 配置項 | 默認值 | 含義 |
|---|---|---|
hosts |
['cnb.cool'] |
provider 服務的 web 主機,其他主機的倉庫會被忽略 |
tokenFile |
~/.cnb/token |
CNB CLI 憑證文件;環境中的 CNB_TOKEN 可覆蓋 |
timeoutMs |
10000 |
單個 HTTP 請求的截止時間,與調用方的取消機制疊加 |
maxPages |
3 |
findByHead 每次查找讀取的開放 MR 頁數,每頁 100 條 |
插件只在明確執行合併或提交評審時向倉庫寫入數據,其餘操作都是讀取。
典型用法¶
MR 與會話的綁定有兩條路徑,都不需要手工配置倉庫信息。
第一條是自動發現。forge 會話層在 session checkout 裏執行 git,先用 repositoryFromRemote 把 remote 映射到某個倉庫,再調 findByHead(repository, branch) 找 head 指向當前分支的開放 MR。
第二條是粘貼 MR 地址。把 CNB MR URL 直接貼進會話,由 changeRequestFromUrl 立即綁定。綁定關係通過 changeRequestUrl → changeRequestFromUrl 重新解析,這就是那對 URL 函數是嚴格逆函數的原因。
需要注意,gh 命令輸出解析在這裏不適用,那是 GitHub-CLI 專有的。CNB 只有 git remote / 分支發現和粘貼 MR URL 兩條綁定路徑。
憑證方面,provider 讀取 ~/.cnb/token(由官方 cnb CLI 寫入),取其中的 .auths["cnb.cool"].access_token,token 不會被打印或記錄。CNB token 有效期約 8 小時,插件的處理是:
- 文件 mtime 或 size 變化時重新讀取,
cnb login刷新無需重啓; - 401 或
errcode: 16響應報爲unauthenticated,並提示用戶執行cnb login; CNB_TOKEN可以爲單個進程替代文件提供 token。
使用的 scope:repo-pr:r(MR、狀態、文件)、repo-code:r(commit、raw 文件)、repo-manage:r(合併設置)、repo-pr:rw(合併)、repo-notes:rw(評審)。缺少 scope 表現爲 HTTP 403(errcode 10023),按 unavailable / failed 上報,不會讓面板崩潰。
適用場景與注意¶
適合託管在 cnb.cool 上、希望在 DSH forge 面板裏直接看 CNB MR 狀態、檢查、diff 並執行合併的團隊或個人。
幾點提醒:
- 插件以當前 dsh 進程的權限運行,能讀到宿主能讀到的文件和憑證。安裝前應自行檢查源碼與 MIT 許可證,確認來源可信。
- 它通過 bundle 改 profile,安裝和啓用都需要重啓或重載 profile 才生效,變更前建議先備份當前配置。
- 星標 1、版本 0.1.0,說明維護者和用戶基數都很小,遇到問題更適合看源碼或 issue 而非等待修復。
- 上面列出的 CNB 數據模型限制(
closedAt、unresolvedThreads、增刪統計截斷等)是上游接口決定的,短期內不會變化,別把面板數值當作權威。
結尾¶
這個插件的價值在於補齊了 DSH forge 面板對 CNB 的支持:無需改客戶端,註冊 provider 後即可讀寫 CNB 的 MR、檢查、diff、評審和合並。它代碼量不大、零運行時依賴,映射規則寫得很細,適合作爲參考實現來讀,也適合直接用。
目錄頁:https://www.skillhub.cn/plugins/genius-alray/dsh-plugin-forge-cnb
源碼:https://github.com/genius-alray/dsh-plugin-forge-cnb