@bushizhanghan/dsh-publish-gate

中文文档
0.23.0发布于 2026/11/08MITES Module零依赖近 30 天下载 6932

用于DSH插件的前出门:显示,依赖性,艺术品,运行时间,浏览器的一半,可追溯性和市场准备状态——外加一个输入模糊器来驱动每个被宣布的参数与错误的类型. 页:1。

中文使用文档
文档目录22 节
  1. 自检:看不见的 finding
  2. 测试层:门禁真的会跑一遍你的测试
  3. 典型用法(写完插件后)
  4. 安装
  5. Agent 工具
  6. 检查清单
  7. 会真的挡住发布的(error)
  8. 值得在发布前知道的(warn / info)
  9. 产物层:npm 到底会发出什么
  10. 运行时层: apply 之后到底注册了什么
  11. 浏览器半边:侧栏面板到底会不会出现
  12. 市场层:提交之前就能知道会不会被打回
  13. 代码卫生
  14. 依赖真实性: lib/ 里 import 的东西,消费者那边真的会装上吗
  15. 构建产物: src/ 和 lib/ 谁更新
  16. 可追溯性:一周后你还能指出线上那包是哪份代码吗
  17. 本地开发:宿主跑的到底是不是你刚改的代码
  18. 更新之前:这个新版本装上会不会坏
  19. 一个真实的例子
  20. 设计取舍
  21. 开发
  22. License

dsh-publish-gate — 帮你发布 DSH 插件的一键三连门禁

这个插件是给插件作者用的:你写完一个 DSH 插件,想发 GitHub + npm + 插件市场,它帮你把发布前该查的全部查一遍,再给你照抄的命令清单。

发布一个 DSH 插件有七步,每一步都有一个直到你付出代价才说话的坑:files 忘了 lib/ 于是发出去一个空包、PowerShell 写下的 BOM 让 JSON.parse 报一个不提 BOM 的错、patch 的 insert 行缩进错了于是插件装上了但永远不加载、裸包名让 granular token 收到一个既不说 token 也不说 scope 的 403。这个插件把整条链路的每个静默坑变成一条确定性检查。

十个 agent 工具:全量门禁、运行时注册检查、发后校验、patch 专项、市场条目校验、版本推进、多插件体检、发布命令清单、本地开发链路诊断。

门禁分三层:清单层看 package.json / patch / 入口源码,产物层真的跑一遍 npm pack --dry-run 看 npm 到底会发出什么,运行时层在独立子进程里 import() 入口并 apply(ctx) 一遍,看它到底注册了什么。

自检:看不见的 finding

severity 不是 error/warn/info 的 finding 会被所有计数器忽略,等于规则从没触发过。publish_gate 现在会把这类 finding 提升成一条 internal-bad-severity 的 error,测试里还有一条源码扫描盯着 F(...) 的第二个参数。这条网加上当天就抓到了我自己的同一个错误。

测试层:门禁真的会跑一遍你的测试

tests-fail / tests-timeout / tests-not-run 三条规则会以 node --test 在插件目录里真跑一次(不经过 npm test,因为脚本内容由作者决定、可能 shell 出去)。子进程会去掉继承来的 NODE_TEST_CONTEXT——否则当门禁本身跑在 node --test 里时,子进程进入 test-child 模式、一个摘要都不打印,看起来像"没有测试"。

典型用法(写完插件后)

我写好了一个新插件 X,帮我发出去
→ agent 先调 publish_gate(X) 查所有静默坑(清单 + 产物 + 运行时三层)
→ 有 blocker 就修,修完再跑
→ 全绿后调 publish_gate_commands(X, {version}) 拿 GitHub/npm/市场三步的命令,照抄执行
→ 发完调 publish_gate_verify(X) 确认 registry 上那包的 shasum 和你本地这份一致

安装

dsh plugin --profile desktop add @bushizhanghan/dsh-publish-gate

重启 DSH 后生效。只读:这个插件从不写你的插件目录。

Agent 工具

工具作用
publish_gate全量门禁:清单 + 产物 + 运行时 + 浏览器半边 + 网络,返回 safeToPublish(skipPack / skipTools / skipClient / skipNetwork 可逐层跳过)
publish_gate_tools只跑运行时层:在子进程里 apply(ctx),看注册了哪些工具、有没有缺 execute、重名、presentCall 抛错、card 用了宿主不认识的值
publish_gate_client只跑浏览器半边:在子进程里假造 window.__ModuleLoader__,看 bundle 会不会加载、apply 会不会抛、到底往哪个服务注册了东西、inject 有没有漏
publish_gate_verify发后校验:npm pack --dry-run 的 shasum 和 registry 上的 dist.shasum 逐字节比,比版本号猜准
publish_gate_patch只查 cordis.patch.yml——「插件装了但工具没出现」的第一个怀疑对象
publish_gate_market校验市场条目 YAML(url/name/category/description 双语);传 dir 则改查只有线上才能回答的三件事:这个仓库已经在目录里(再开 PR 就是重复条目)、另一个条目也在发同一个 npm 包、仓库还不满 24 小时(提交门禁会失败并自己重跑)
publish_gate_next_version当前版本、是否已被 registry 占用、建议 bump 到多少(本地比 npm 旧时从 npm 的 latest 往上加,不会推荐一个已被占用的版本)
publish_gate_doctor扫整个目录,按 blocker 数排序,附带每个插件的真实包体积,看哪个还不能发
publish_gate_link本地开发链路:不联网、不调包管理器,只读 profile 自己的 node_modules / .modules.yaml / cordis.patch.yml,回答「宿主现在跑的到底是不是我刚改的这份代码」——链接的形态与目标、pnpm store 的 major、patch 行挂不挂载,一次报全。传 profile 可看别的 profile(默认 ~/.dsh/profiles/desktop)
publish_gate_update装之前先回答该不该装。市场里的更新插件都是装完起不来才回滚;这个先把 registry 上那个版本真的下载下来、解包到临时目录,跑同一套产物层与运行时层:入口能不能 import、apply() 能不能注册、tarball 里有没有代码、参数传错会不会抛。只在 checkout 里才有意义的规则(git / 市场 / 测试 / 链接)会被过滤掉,结论只关于代码。传 dir 或 name 都行(只给包名时从已装副本读当前版本)
publish_gate_commands按顺序列出发布命令,并标出这台机器已经走到哪一步。命令是这台机器上真能跑的形式:Windows 下 npm publish 走 cmd /c(PowerShell 的 npm.ps1 常被执行策略拦掉),npm publish 后面紧跟一条 publish_gate_verify;版本是预发版时命令自带 --tag next;最后一步是给发出去的 commit 打 tag

检查清单

会真的挡住发布的(error)

规则抓什么
utf8-bomBOM 让 JSON.parse 抛「Unexpected token」,错误信息里没有 BOM 三个字
json-parsepackage.json 语法错误
missing-fieldname/version/description/main/license/repository/author 缺失
invalid-name / invalid-versionnpm 拒绝的形状
files-omits-lib发出去是空包——npm 不警告
files-omits-patchdsh.bundle.patch 指的文件不在 files 里,装上不挂载
no-exports缺 exports["."],宿主解析不到入口
private-field-set还留着脚手架模板的 "private": true,npm 直接 EPRIVATE 拒发
client-without-export声明了 client 平台却没导出 ./client,宿主半边在、侧栏永不出现
client-without-platform反过来:导出了 ./client 却没写 dsh.client.platform。文件发出去了,宿主不知道要加载它——装上后一个字节都不执行,而且没有任何报错
not-esm没 "type": "module",import 在加载时炸
no-insert-rowpatch 里只有 override,永远不挂载(页面报的仍然是这条——顶层 - id: 是 patch 不是挂载)
bundle-patch-not-declared有 cordis.patch.yml 但清单里没有 dsh.bundle.patch——patch 是死的,装了不挂载
entry-path-missingmain / exports["."] / exports["./client"] 指的文件不存在,宿主解析到空
entry-missing清单里没有任何一个存在的入口文件——连 import 的机会都没有
inject-missing-*用了 ctx.tools / ctx.web / ctx.webServer / ctx.setInterval 却没在 inject 里声明,cordis 永远不激活
patch-not-mountinfo:这些顶层 - id: 是在覆盖已有条目,合法,但不是挂载
insert-missing-name / insert-name-mismatch挂载名缺失或与 package.json 不一致
no-patch-file有 lib/index.js 但没有 cordis.patch.yml
no-name-export / no-apply-export入口协议不完整
require-in-esmESM 里 require() → ERR_AMBIGUOUS_MODULE_SYNTAX,报错不带行号
no-license缺 LICENSE
version-taken这个版本已经在 registry 上,再发是 403
version-behind-latest本地版本比 registry 上的 latest 旧。npm 会照收,但 latest 继续指向新版本——包上传了,按名字装的人永远拿不到,且没有任何报错
publish-tag-not-latest版本已经在 registry 上了,但 dist-tags.latest 还指着旧的。--tag next 发出去的版本就是这样:看着发出去了,按名字装的人拿到的还是旧的
prerelease-published-as-latest版本是 -beta / -rc,却没写 publishConfig.tag。npm 不看后缀,照样发到 latest——所有人 npm i 装到的都是没做完的构建
registry-unreachableregistry 回了 500/429 —— 版本检查没跑,所以别把它当干净
npm-not-logged-in这台机器上 npm 没有可用登录(npm whoami 报 ENEEDAUTH)。npm publish 会在 tarball 打好之后才失败
readme-wrong-packageREADME 的安装命令写的是另一个包名(从别的插件抄过来没改)。照着装的人装到别的插件上,而且不会报错——那个包真的存在

值得在发布前知道的(warn / info)

npm-scope-mismatch(登录的账号不是包 scope 的所有者,PUT 会 403,而错误里既不说 token 也不说 scope——我为一个 403 花了一小时)、unscoped-name(granular token 的 403)、repo-missing、patch-name-unresolvable(junction 放错层级)、inject-unused-service(inject 里写了个拼错的服务名,cordis 一直等它,插件静默不激活)、no-publish-config-access(scoped 包没写 publishConfig.access,只是提醒,因为命令里本来就带 --access public)、publish-config-registry(publishConfig.registry 指到内网 registry,版本发到别人装不到的地方)、no-readme / readme-no-install / readme-no-profile、license-mismatch、no-tests / no-test-files / no-test-script、no-engines、bad-repo-url、no-git / no-gitignore、readme-path-missing(README 里点名的仓库内文件路径已经不存在了,照着敲就是 ENOENT)、readme-tool-undocumented(README 没提这个工具)/ readme-test-count(README 写的测试数和真实数不一致)、changelog-missing-version(要发的这个版本号在 CHANGELOG 里没有对应标题,npm 照样发,发布说明就永远落后一版)、changelog-no-versions(CHANGELOG 里没有版本标题,读者对不上自己装的那版)、no-changelog(只是提醒)、changelog-not-shipped(CHANGELOG 在本地但没进 tarball——npm 只强制带 README 和 LICENSE,你一直更新的那份文件在 registry 页面上看不见),以及市场条目的 market-* 全系列。

产物层:npm 到底会发出什么

清单层只看 files 字段,而 npm 有它自己的规矩(main / exports 指向的文件强制入包、README/LICENSE/package.json 强制入包)。所以这一层真的跑一遍 npm pack --dry-run --json --ignore-scripts,读它返回的文件清单和体积。

规则抓什么
pack-omits-libtarball 里没有 lib/——发出去是空包,npm 不警告
pack-omits-patchdsh.bundle.patch 指的文件没进 tarball,装上不挂载
pack-omits-clientexports["./client"] 指的文件没进 tarball。npm 不会因为它在 exports 里就强制入包(只强制 main),所以 files 里没写它就会静默丢掉——本地能跑、装了没侧栏
pack-omits-readme / pack-omits-licenseREADME / LICENSE 没进包
pack-oversize解包 >5 MB 是 error、>1 MB 是 warn,并点名最大的三个文件。测试 fixture 是常见元凶——我的一个插件曾把 20 MB 的算例目录发上去,装了它的人每次都要下 20 MB
pack-ships-secrettarball 里有 .env / .npmrc / *.pem / *.key / id_rsa。没写 files 字段时 npm 会把 .gitignore 没排除的一切都打包,而包一发布就是公开的、npm 不会帮你撤回
pack-unexpected-dirfixtures / test / tests / src 被打进包里
pack-ships-junk包里带上了 .bak-20261026 这类备份或编辑器残留。files: ["lib"] 会把 lib/ 里所有东西都打进去——包括你自己源码的上一版,而且发上去就永久公开
pack-script-build有 prepack / prepare:dry run 用了 --ignore-scripts,这次清单不代表真正发布时的产物
pack-unavailablenpm 跑不起来(warn)——产物层没跑,别把它当干净

运行时层:apply 之后到底注册了什么

前两层都是读文件,读不出「装上能不能用」。这一层在独立子进程里 import() 入口、用假 ctx 调一次 apply(ctx),把 ctx.tools.register 收到的每一条拿回来检查。20 秒超时,你的插件代码不在宿主进程里跑。

入口按 exports["."] → main → lib/index.js 的顺序解析。**早先写死 lib/index.js,拿三十一个真实已装插件一测,二十个报"入口抛异常"——其中两个的入口是 index.mjs,根本没有 lib/。**误报比漏报更贵:一个满屏假错误的门禁,第二次就没人跑了。

规则抓什么
entry-import-failedimport() 入口就抛——装上必定不加载
entry-import-unresolved入口 import 了一个没装在旁边的包(@deepseek-ai/* 这类宿主提供的 peer)——warn,不是缺陷
apply-threwapply(ctx) 抛
tool-none-registered入口能加载,但一个工具都没注册
tool-missing-execute / tool-missing-output-schema注册项缺 execute / output.schema(宿主用 schema 校验返回值,缺了等于没校验)
tool-duplicate-name两个工具同名,后者静默覆盖前者
tool-present-call-throwspresentCall 抛——调用记录渲染时炸
tool-bad-card-kindcard: 'generic' 的 kind 不在宿主认识的那一组里(other / execute / search / read / edit / delete / fetch)。注册不报错,卡片渲染坏掉——我五个插件一起踩过
tool-bad-parameter-typeparameters 里某个 type 写错("strin")。宿主把 parameters 渲染进 prompt、但从不校验调用值,所以这个 typo 只是悄悄把类型提示弄丢
tool-no-description工具没有 description。模型是靠 name + description + parameters 挑工具的,没有描述就只剩名字——工具发出去了,但基本选不中
tool-description-thindescription 少于 30 字符,或者就是把工具名重复一遍。等于没写
tool-no-parametersexecute 读 args.x,但整个 parameters 都没有。模型拿不到参数清单,不知道该传什么
tool-undeclared-paramexecute 读了 args.x,但 parameters.properties 里没有 x。模型只能传 schema 声明过的参数,所以这个值在真实调用里永远是 undefined——代码在,永远不跑,也不报错。args.x、args['x']、const { x } = args 三种写法都认
tool-output-invalid工具返回的东西自己的 output schema 不收。宿主每个结果都拿 schema 校验,不匹配就抛 ToolOutputError——工具从第一次调用起就不可用,而报错只给一个 JSON 路径,不给是哪行代码返回的。只有点名 call 真的调了才检查。这个插件自己踩过:schema 收紧成 additionalProperties: false,但有一条错误分支还在返回 { ok, error }
tool-input-throws把每个声明过的参数塞一个错类型的值去调,工具直接抛 TypeError 而不是回答。宿主从不拿 parameters 校验入参,所以这个值真的会到你的代码里——调用方拿到的是 The "path" argument must be of type string,插件日志里没有一行说是哪个工具。这条规则是在自己的工作区里跑出 12 个真实崩溃 之后才加的(session/profile/casePath/workspace 各一个入口),现在九个插件全绿。默认关:它会把每个工具都调一遍,用 skip 点名排除那些会开程序、写文件的

浏览器半边:侧栏面板到底会不会出现

清单层只检查「声明了 client 平台就得有 exports["./client"]」,可这一步过了之后还能坏很多次。而且它坏得很安静:宿主半边照样跑、工具照样能用,只是侧栏里什么都没有——看起来像 UI bug,其实是打包/注册 bug。

所以这一层真的把那个 bundle 在一个子进程里跑一遍:造一个假的 window.__ModuleLoader__、假的 require('react'),再给一个 ctx——effect / inject 会立刻执行回调,其余服务(sidebarRightTabs、slots…)每被调用一次就记一条。

规则抓什么
client-file-missingexports["./client"] 指的文件不存在
client-no-module-loaderbundle 里没有 window.__ModuleLoader__.load——宿主根本不会执行它
client-load-not-called文件能解析,但跑完没调用 load(常见:包在 if (typeof window !== 'undefined') 里,判断写错了)
client-factory-throwsfactory(require) 抛
client-no-applyfactory 返回的对象里没有 apply
client-apply-throwsapply(ctx) 抛
client-no-registrationapply 跑完一次服务都没调用——面板不会出现
client-inject-missing-<service>apply 用了 ctx.sidebarRightTabs 但 inject 数组里没写它。cordis 不会把没声明的服务给你,于是 ctx.sidebarRightTabs 是 undefined,apply 一碰就抛(或静默什么都没注册)
client-double-load同一个 bundle 被注入了两次(它自己的 patch 一次,注入 dsh-client-modules 的侧边栏插件再一次)。loader 按 id 建工厂,第二次注册直接抛错,整个 Web 启动被中断——不是这个面板没了,是所有面板都没了。用 if (window.__xLoaded) return; window.__xLoaded = true 挡一下
client-parse-failed文件有语法错误。单独判:SyntaxError 说明 bundle 本身是坏的,别的报错说明是沙箱不够真,两者别混为一谈
client-unavailable子进程没跑起来(warn)——这一层没跑,别把它当干净

市场层:提交之前就能知道会不会被打回

本地全绿之后,发布还会在提交门禁那儿停一次。停的原因通常只有三种,而且三种都能提前查:

规则抓什么
market-duplicate-entry这个仓库已经在目录里。再提一个 PR 就是同一仓库的第二条条目,reviewer 会拒。版本升级根本不需要 PR——目录条目只存 url
market-release-behindwarn
market-npm-name-collision另一个条目也在发同一个 npm 包,安装路径会互相盖
market-repo-too-new仓库创建不满 24 小时。门禁会报 0.0 days old 然后自己重跑,PR 不会丢,但在那之前什么都不会合
market-fork-of-listed这个仓库是某个已经在目录里的仓库的 fork。两条条目装的代码抢同一批 id,两个都装就有一个被静默盖掉——而且 reviewer 会把"没写"当成"隐瞒 fork"
market-fork-undisclosed是 fork,但 README 和 package.json 里都没有上游的 owner/repo
market-no-repopackage.json 里没有 GitHub 地址,没东西可提交
market-unavailable目录拉不到(warn)——这几条没跑,别把它当干净

代码卫生

lib-debugger(error)、lib-console-log(每个文件一条,带次数;浏览器半边豁免——那里 console.log 是调试通道,不是宿主日志残渣)、lib-hardcoded-path(C:\Users\... 硬编码)、lib-url-pathname(new URL(...).pathname 在中文路径上会百分号编码,得用 fileURLToPath)、node-too-old(engines.node 写着 >=18,lib/ 里却用了 Object.groupBy(21)或 Promise.withResolvers(22)。npm 对 engines 只是警告,照样装上,然后在 import 那一刻抛——用户看到的是「插件坏了」,不是版本不匹配)。字符串和注释里的同名文字不算。

依赖真实性:lib/ 里 import 的东西,消费者那边真的会装上吗

npm install 装一个包时只装它的 dependencies,永远不装 devDependencies。所以在你自己仓库里 import 'x' 能跑(依赖树是完整的),到了用户那边 ERR_MODULE_NOT_FOUND——插件装上了,然后加载失败,用户看到的是「这个插件坏了」。

规则抓什么
undeclared-importlib/ 里 import 了某个包,package.json 三个依赖字段里都没有。它在你机器上能解析,只是因为别的包把它提升(hoist)到了 node_modules
dep-in-dev-only只在 devDependencies 里声明。这是最常见的一种:写代码时 npm i -D 装上了,测试也全绿
unused-dependencydependencies 里声明了,lib/ 里从没 import。每个装你的人都白下一份(info,因为有些包是运行时动态加载的)。带浏览器半边的包不查这条:打包器已经把依赖内联进 bundle,代码就在里面,不是没用到

内置模块(node:fs、fs/promises)、相对路径、以及浏览器半边从宿主 require 拿到的 react 都不算。工具描述里那句 require("react") 是散文,也不是依赖——这条规则最早就是被它自己误报出来的。

构建产物:src/ 和 lib/ 谁更新

有 src/ 的插件(TypeScript 源码 + 构建出来的 lib/)最容易在这件事上翻车,而且两种翻车的表现一模一样——发出去的字节没人跑过:

规则抓什么
stale-buildsrc/ 比 lib/ 新:构建没跑就发布了,tarball 里是上一次的产物
hand-edited-buildlib/ 比 src/ 新:有人直接改了构建产物。下一次干净的构建会静默丢掉这些改动,所以发布的字节没法从 src/ 复现出来

两条都只是 warn:有可能你就是要发手改的产物(我干过)。但发之前你得知道自己在发什么。

可追溯性:一周后你还能指出线上那包是哪份代码吗

发布是字节唯一一次离开你机器的时刻。如果这些字节既不在任何 commit 里、也没有 tag,那么一周后——包括你自己在内——没人说得清 registry 上那包是从哪份源码来的。

这不是假想:我发过一版 1.2.7 到 npm,仓库的 GitHub release 还停在 1.2.6,市场条目指向旧 tarball,维护者直接打回。

规则抓什么
git-uncommitted会进 tarball 的文件有未提交改动。发出去的字节不在任何 commit 里,以后复现不出来、也没法 bisect
git-tag-missingHEAD 上没有 v<version> tag。发完之后这些字节的唯一记录就是 registry,GitHub release、changelog、市场条目都没法指向一个从没打过 tag 的 commit
git-tag-elsewheretag 存在但不在 HEAD 上:tag 和 tarball 是两份不同的字节,从那个 tag 构建出来的东西不是你刚发的东西
git-tag-unverified读不到 origin(断网、私有仓库、没配 remote)。这是 info,不是 warn:提交是用 GitHub API 推的时候本地 HEAD 不是发出去的那个 commit,本地 tag 列表证明不了任何事。断网那天我有六个插件被误报「没有 tag」,六个都打了

都是 warn:本地试发、私有包、发完再补 tag 都合理。但「发完之后指不出是哪份代码」这件事,事后补不回来。

本地开发:宿主跑的到底是不是你刚改的代码

发布之前总有一段时间,插件只在你自己机器上跑。这段路比发布更容易踩空:宿主只加载 <profile>/node_modules 下的包,所以你改的是 E:\...\dsh-x,跑的是 profile 里那个入口——它可能是一个 junction、一份早就不再同步的拷贝,或者根本没有。这三种情况在宿主那边长得一模一样:插件不出现,没有报错,日志里也没有一行。

publish_gate_link 只读 profile 自己的三个文件回答这件事,不联网、不调包管理器、不写任何东西。

规则严重度抓什么
link-not-installedinfoprofile 的 node_modules 里没有这个包。新插件第一次接进宿主之前,这是正常状态
link-target-missingerror链接在、目标没了(或目标里没有能解析的 package.json)。死掉的 junction 让宿主把包解析成空,插件静默消失——这台机器的 Web UI 就这样坏过一次
link-stale-copywarnprofile 里是一份拷贝而不是链接,且版本和工作区不同:改 lib/ 永远不会到那边,而你在这边跑测试全绿
pnpm-store-conflictwarnprofile 的 pnpm store 是 v10、随包的 pnpm 是 v11(或反过来)。dsh plugin add link: 会在装任何东西之前以 ERR_PNPM_UNEXPECTED_STORE 失败,而那条错误既不说 profile、也不说要匹配哪个 major
patch-entry-top-levelerror这个包在 profile 的 cordis.patch.yml 里是一条顶层 - id: 行。顶层行覆盖树里已有的条目、不新增一条:装上了、加载了、永远不出现。这个坑藏了我两个插件两周

链接的三种形态是分开报的:Node 对 junction 和目录符号链接都只说 isSymbolicLink() === true,只有 realpathSync(p) !== p 才说明它是 junction;普通目录则是拷贝。mklink /J 建 junction 不需要管理员权限,符号链接需要。

工作流:

1. 改完 lib/,跑 publish_gate_link(dir)
2. link-not-installed        → 建 junction(finding 的 fix 里就是能直接粘的命令)
3. link-target-missing       → 删掉旧入口,重建 junction
   link-stale-copy           → 同上;拷贝不会跟着工作区变
4. patch-entry-top-level     → 把那一行挪进 "- insert:" 块
5. pnpm-store-conflict       → 别用 pnpm,用 mklink

profile 参数可以看别的 profile(默认 ~/.dsh/profiles/desktop)。测试永远用临时 profile,不碰真的那个。

没有做的:它不替你建链接、不改 profile 的 patch——和门禁其余部分一样只读,每条 finding 都带 fix 文案,动手是你的决定。

更新之前:这个新版本装上会不会坏

市场里的更新插件(dsh-market、dsh-update-checker、dsh-onekey-update、dsh-plugin-auto-update、dsh-plugin-steward …)做得都很好,但它们回答的是装完之后的问题:装上了、起不来、回滚。回滚能救回来,代价是这一次更新白做,而且坏的那一下已经发生了。

publish_gate_update 把顺序倒过来:下载 → 解包到临时目录 → 跑门禁的产物层和运行时层 → 再决定要不要装。

publish_gate_update({ name: "@liustack/modlens" })
→ current 0.1.0, latest 3.27.3
→ tarball: 239840 bytes
→ [warn] client-double-load: ./dsh/client.js 二次求值会重复注册工厂

它跑的是门禁已有的那几层,所以答案是逐条可追的:

  • 入口能不能 import(entry-import-failed / entry-missing)
  • apply() 能不能注册工具(tool-none-registered、tool-missing-execute)
  • tarball 里有没有代码(files-omits-lib)、补丁在不在(files-omits-patch)
  • 参数传错会不会抛(tool-input-throws,加 fuzz: true)
  • 浏览器半边会不会加载、会不会重复注册(client-*)

只在 checkout 里才成立的规则(git / 市场条目 / 测试目录 / 本地链接 / 打包体积)被过滤掉——解包出来的目录没有 .git、没有 README 契约、没有 test/,跑那些只会把真正的结论埋掉。

三条自己的规则:

规则严重度说什么
candidate-same-versioninfo已经是最新版,不用下载
candidate-olderwarnregistry 的 latest 比装着的旧。unpublish 或误用 --tag latest 都会让 dist-tags 往回走,跟过去等于拿旧构建换掉能跑的安装
candidate-unavailablewarn没下载成、没解开、没读到版本号。这不是干净的结论,detail 里明写

它不替你装、不替你更新——和门禁其余部分一样只读。想知道该不该更新,然后自己去 dsh plugin add,或者交给已有的更新插件去做。

一个真实的例子

对一个刚从别人那儿接手、准备发布的插件跑 publish_gate:

✗ package.json starts with a UTF-8 BOM  [utf8-bom]
    A byte-order mark makes JSON.parse throw `Unexpected token` and npm publish
    fail with a parse error that never names the BOM.
✗ files does not include "lib"  [files-omits-lib]
    The published tarball will contain no code.
✗ cordis.patch.yml has no insert row  [no-insert-row]
    A patch with only overrides never mounts your plugin. This file has 1
    top-level `- id:` row — those patch entries that already exist in the tree,
    they do not add one.
✗ lib/index.js calls require() inside an ESM module  [require-in-esm]
4 blocker(s) — fix before publishing

这四条里有两条是发布四个插件时真实踩到的。

设计取舍

  • 三层,按代价排序:清单层最便宜、先跑;产物层要起一个 npm(约 1–2 秒);运行时层要起一个 node 子进程。前一层没过的错误通常值得先修,但三层都会跑完——一次给全,比来回问好。
  • 浏览器半边单独一层,不并进运行时层:宿主半边的失败会抛错、会有日志;浏览器半边的失败是「什么都没发生」。两者的排查路径完全不同,所以各起一个子进程、各有一组规则、各自能单独跳过(skipTools / skipClient)。
  • 产物层用 npm pack --dry-run,不用自己推 files 规则:npm 的入包规则(main / exports 强制入包、README/LICENSE 强制入包、.npmignore 优先级)比自己实现的准。唯一的代价是它需要这台机器上有 npm,所以跑不起来时降级成 pack-unavailable 而不是报错。
  • 运行时层真的执行插件代码:这是唯一能回答「装上能不能用」的办法。代价是 apply() 会跑,所以:假 ctx 是 Proxy 不落盘、20 秒硬超时、skipTools 一键跳过。
  • --ignore-scripts:dry run 不跑 prepack,否则检查会触发一次构建。代价是清单可能和真实产物不同,所以有 pack-script-build 把这件事说出来。
  • 发完那几十秒不算失败:npm 收下包会先回 202,然后六十秒到几分钟里这个版本在 registry 上根本不存在。这个插件自己的 0.8.1 实测是四分钟才出现。所以 publish_gate_verify 加了 waitMs 轮询;一轮等不到就返回 pending: true——那是「还没轮到」,不是「没发成功」。单次最多等 90 秒,因为宿主 120 秒就掐掉工具调用,剩下的只能再跑一次。
  • publish_gate_verify 比 shasum,不比版本号:版本号相同而字节不同(发了又改、或本地根本没发)是最常见的自欺。npm pack --dry-run 的 shasum 和 registry 的 dist.shasum 是同一套算法,所以这个比较是精确的。
  • 只读:修不修由你定,每条 finding 都带 fix 文案。门禁的职责是把话说清楚,不是替你做决定——所以没有 --fix。
  • 确定性:全部规则是文件内容、子进程或 registry/GitHub API 检查,没有 LLM 调用——一条规则答错,你可以直接看规则。
  • 自举:这个插件的测试把九个已发布插件当 fixture,断言它们在门禁下零 blocker;规则如果有任何误报倾向,测试先炸。

开发

node --test                      # 137 个单测
node test/integration.test.mjs   # 32 条集成断言(9 个真实插件作 fixture)

License

MIT

版本与发布记录

@bushizhanghan/dsh-publish-gate 共发布 38 个版本,首次发布于 2026/10/24;过去 12 个月发布了 38 个版本。

版本发布日期距今
0.23.0latest2026/11/08今天
0.22.02026/11/08今天
0.21.02026/11/071 天前
0.20.02026/11/071 天前
0.19.02026/11/071 天前
0.18.02026/11/071 天前

关于 @bushizhanghan/dsh-publish-gate 的常见问题

@bushizhanghan/dsh-publish-gate 是什么?

用于DSH插件的前出门:显示,依赖性,艺术品,运行时间,浏览器的一半,可追溯性和市场准备状态——外加一个输入模糊器来驱动每个被宣布的参数与错误的类型. 页:1。

如何安装 @bushizhanghan/dsh-publish-gate?

在项目目录运行 npm install @bushizhanghan/dsh-publish-gate,或使用 pnpm add / yarn add / bun add。

@bushizhanghan/dsh-publish-gate 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/bushizhanghan__dsh-publish-gate 是否存在。

@bushizhanghan/dsh-publish-gate 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@bushizhanghan/dsh-publish-gate 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@bushizhanghan/dsh-publish-gate 可以商用吗?

@bushizhanghan/dsh-publish-gate 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@bushizhanghan/dsh-publish-gate 的下载量有多少?

根据 npm 官方下载统计,@bushizhanghan/dsh-publish-gate 在 2026/10/09 – 2026/11/07 这 30 天内被下载 6,932 次,日均约 231 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bushizhanghan/dsh-publish-gate 还在维护吗?

最新版本 0.23.0 发布于 2026/11/08(今天);至今共发布 38 个版本,其中过去 12 个月发布了 38 个;首次发布于 2026/10/24。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。