插件提交与审核(开放生态)

English: plugin-submission.en.md

Minibar 的插件是 npm 包:基座从 npm 源或就近的镜像下载,并按签名索引里锁定的 包名 + 版本 + sha256 校验(10-backend.md §2b)。索引(registry.json)托管在 Vercel(https://minibar.084817.xyz,服务端代码在 server/)。内置插件来自 Sources/BundledMiniApps/Catalog/,由 CI 发布为 @minibar-app/*;社区插件由作者发布到 npm,再在本仓库 registry/plugins/<插件 id>/ 留一份源码副本供审核与索引构建。App 的市场会给社区插件打上"社区"标签,并显示 npm 的下载量。

1. 提交流程

  1. 用 minibar-cli init 或照着 docs/examples/ 写好插件(含 package.json:version 等于 manifest 版本、keywords 含 minibar-plugin、没有 scripts),本地跑通:
    node tools/jsharness/run.js <插件目录>            # 模拟宿主渲染
    node tools/jsharness/scan.js --strict <插件目录>  # 静态扫描(registry 规则,含 package.json)

    在 Mac 上用"设置 › 插件 › Install from Folder…"或开发者模式真机验证。

  2. 发布到 npm:npm publish --access public(任何名字都行,推荐 minibar-plugin-<名字>)。索引锁定的是精确版本和整包哈希,发布后同一版本的内容不可再改,更新就发新版本。
  3. 把插件源码放到 registry/plugins/<插件 id>/(目录名必须等于 manifest 的 id),提交 PR(仓库私有期间通过官网支持页说明的渠道投递)。PR 描述必须回答:
    • 插件做什么、数据来自哪里;
    • 每一项权限的用途(尤其 system、apps、files、clipboard);
    • allowedHosts 里每个域名的用途;"*" 需要单独说明为什么不能列举域名;
    • 是否包含第三方代码(库、压缩产物),来源与许可证。
  4. CI(.github/workflows/plugin-submission.yml)自动跑:静态扫描(--strict)、mock 宿主渲染、索引打包预演。全绿是人工审核的前提。
  5. 维护者按 §3 清单审核,并核对 npm 上发布的 tarball 与 PR 里的源码一致(scripts/build_registry.py --resolve-from https://registry.npmjs.org 会下载已发布的包、校验 manifest 并计算哈希)。通过即合并;合并到默认分支后 Registry 工作流部署索引,App 在下一次刷新时看到它(缓存 6 小时,市场窗口打开时立即刷新)。
  6. 更新:发布新版本到 npm,改 version(semver)再提 PR;App 会提示更新,用户可开启自动更新。

2. 静态扫描查什么(tools/jsharness/scan.js)

类别规则结果
manifestid 反向域名、目录名 = id、version semver、name ≤ 32、description ≤ 200、未知权限、network 无 allowedHosts、allowedSchemes 含禁止 scheme错误
manifest无 author / homepage(--strict)、allowedHosts: ["*"]、申请 system / apps / files / clipboard错误 / 警告(审核重点)
文件扩展名白名单(与 PathSafety.allowedExtensions 一致)、符号链接、> 8 层目录、单文件 > 5 MB、总计 > 20 MB、> 200 个文件错误
脚本直接触碰 __host / __runtime / __meta、eval、Function 构造、require / import()、XMLHttpRequest / WebSocket、Node process、浏览器存储错误(无正当用途)
脚本入口未调用 defineMiniApp、脚本 > 1 MB错误
脚本超长行(压缩/混淆)、String.fromCharCode 长链、\x 转义长串、fetch / atob / setTimeout(运行时不存在)、读剪贴板、源码里的 http 地址或不在 allowedHosts 的域名警告
Web 卡片<script src="http…">错误;其它网络资源引用为警告

扫描器只是第一道门:它把"不可能合法"的东西挡在外面,把"需要人看"的地方标出来。

3. 人工审核清单

审核者逐项打勾后再合并:

拒绝的常见原因:allowedHosts: ["*"] 且无法说明理由;向第三方发送个人数据;混淆代码;与内置插件完全重复且无改进。

4. 下架(revoke)与演练

registry/revoked.json:

{ "revoked": [ { "id": "com.example.bad", "reason": "Sends clipboard contents to a third party" } ] }

合并后 Registry 工作流重新发布 registry.json;每台安装了该插件的 Mac 在下一次刷新 registry 时(≤ 24 小时,打开市场立即)会:

  1. 在市场里把它标为「已从市场移除:<原因>」并禁止安装;
  2. 自动暂停已安装的副本(卡片显示原因,用户可以查看源码、卸载,或明知风险后重新启用);
  3. 写一条日志。

从 revoked.json 删掉该条目后,下一次刷新 registry 时被暂停的副本会自动恢复(只恢复因下架而暂停的;崩溃、超时等原因的暂停不受影响,用户自己的启用/停用选择保持不变)。

演练(每个版本发布前做一次,确认链路没坏):

  1. 在分支上把 app.minibar.community.worldclock 加进 revoked.json,node server/dev.mjs --build 构建并在本机服务(curl http://127.0.0.1:8000/registry.json 确认 revoked 含它;只有本机回环地址允许明文 http,本机 registry 不签名,市场显示"未验证来源");
  2. 在 Mac 上把设置 › General 的 registry 地址改为 http://127.0.0.1:8000/registry.json,打开市场:World Clock 显示已移除,已安装的副本被暂停;
  3. 把地址改回正式 registry,确认恢复;
  4. 不要把演练分支合并。

4a. 不想进官方源?自己开一个外部源

官方源只收审核过的插件。团队内部、私有或实验性插件可以放在你自己的源里:按 03-plugin-api.md §11 生成并托管 registry.json,使用者在「设置 › 市场 › 外部源」添加地址即可。外部源的插件在市场里带来源标签、显示为未签名,但安装校验、沙箱与权限流程与官方插件完全相同。

5. 维护者侧的 registry 设置