dsh-plugin-forge-cnb:給 DSH forge 面板接入 CNB 合併請求

前言

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 字段只有文件名本身。因此插件:

  1. 從 blob_url / raw_url / contents_url(…/-/blob/{sha}/{path})恢復倉庫相對完整路徑,從不使用 filename;
  2. 每個文件輸出 diff --git a/<path> b/<path>、new file mode 100644 / deleted file mode 100644、--- a/<path> + +++ b/<path>(缺失一側用 /dev/null),再原樣拼接已記錄的 hunks,LF 換行,帶結尾換行;
  3. 空補丁(add / delete / modify)渲染爲 Binary files … differ,jsdiff 記爲 isBinary,而不是當成空改動;
  4. 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 小時,插件的處理是:

  1. 文件 mtime 或 size 變化時重新讀取,cnb login 刷新無需重啓;
  2. 401 或 errcode: 16 響應報爲 unauthenticated,並提示用戶執行 cnb login;
  3. 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

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜