前言¶
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