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

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

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

小夜