打包与发布
插件在你自己机器上跑通之后,这一页讲怎么把它交给别人:选包形态 → 打包 → 上架 → 验证。全部动作都不会碰 Notrat 本体。
先定包形态
| 你的插件 | 包形态 | 分发通道 |
|---|---|---|
有 mcpServers(server/*.js 这类真文件) | zip 目录包 | 官方插件市场(走审核) |
有 content.sourceFile / editors[].sourceFile 引用包内文件 | zip 目录包 | 同上 |
纯声明:面板 TSX 内联在 manifest 的 source 字符串里 | 单文件 <id>.json | 自建 / 社区仓库(免审核) |
判断方法很简单:manifest 里有没有 ${pluginDir},或者有没有引用包内真文件。有就必须 zip,${pluginDir} 只对目录包解析,单文件包到别人机器上找不到文件。
目录包里别夹带构建输入
如果面板源码是「模板 + 内核占位符」(source / sourceFile 里含 /*__LPC__*/ 这类全大写注释块),那它是构建输入,装到用户机器上会当场 ReferenceError。
社区货架的安装器会扫 manifest 引用的面板源码,命中占位符形态直接拒装;插件市场的正规通道也要求你上传的是构建产物。发布前自己 grep 一遍最稳妥。
打包
两个脚本都跟着插件项目走(每个插件项目各复制一份,范本是官方插件 notrat-flow 的项目结构),它们复用 Notrat 仓库里的 esbuild / jszip,插件目录本身不装依赖。
开发期:单文件包
node build.mjs编译插件 TSX 成 CJS(经典 JSX,React.createElement 由宿主注入),内联进 manifest,写到 ~/.notrat/plugins/<id>.json,磁盘监听会自动安装并启用。
服务器脚本这类真文件不能进这个包:惯例是放到 ~/.notrat/mcp/,别放 plugins/ 目录(那个目录里只该有插件包文件)。
发布:zip 目录包
node build-release.mjs 1.2.0版本号必须显式传
不传版本会落到默认的 1.0.0。把「默认号包」发上市场会造成版本倒挂:老用户收不到更新提醒,新安装的用户反而装到版本号更低的包(真实踩过的坑)。
产物在 release/<插件>-<版本>/:
<插件>-<版本>/
├── manifest.json # zip 内清单(在根目录;id 必须与目录名一致)
├── panels/*.tsx # 面板源码(manifest 用 sourceFile 引用)
├── server.cjs # MCP stdio 服务(manifest 用 ${pluginDir} 引用)
├── README.md # 插件完整文档
├── readme.md # 市场详情页正文
├── manifest-summary.json # 权限预览(详情页展示用,随包提交)
├── checksums.txt # zip 的 sha256 / 大小
└── <插件>-<版本>.zip # 上传给市场的包打包脚本固定条目顺序与时间戳,同样的内容两次打包哈希相同,别用系统「右键压缩」替代,那样哈希每次都不一样,跟 checksums.txt 就对不上了。
版本号规则
客户端自动检测更新时,唯一依据是注册表条目的 version(点分数字逐段比较,缺位按 0,所以 1.2.10 > 1.2.9)。
- 每次发布都要显式给一个新版本号,且严格递增。
- 重发同一个版本号不会触发用户端更新(注册表按版本留档)。
- manifest 内的
version与注册表不一致时,客户端以注册表为准,发布时保持两者一致。 notrat.minVersion(声明所需的最低应用版本)当前客户端不依据它拦截;市场的版本门槛来自注册表条目的min_app_version。
上架:两条路线
官方货架走「提交 → 人工审核 → 上架」;社区货架免审核,公开到开源社区就自动被客户端抓到。两边的信任口径不一样——官方条目有每版本的哈希锁定与权限预览背书,按你的情况挑。
官方货架:提交与审核
在线提交通道建设中,当前把材料发到 we@nestmold.cn 就进入审核:
| 交什么 | 说明 |
|---|---|
| 插件本体 | 完整 manifest,或可访问的下载链接。带 ${pluginDir} / sourceFile 这类包内真文件的,交 zip 目录包:manifest.json 放 zip 根目录,id 与目录名一致 |
| 说明材料 | 一句话功能介绍 + 权限用途说明 + 开源地址(如有) |
| 条目展示信息 | icon、category、tags、readme(详情页正文)随包一起交,审核通过后录入注册表 |
审核通过后由官方完成上架:包入库 → 计算 SHA-256 → 写入注册表(每个版本带哈希与 downloadUrl)→ 详情页的权限预览自动生成。之后发新版,就是把新版本插到队首,旧版本仍可下载。
社区货架不用等审核节奏,分发方式见下一节。
社区货架:自建仓库分发
不住在官方市场、直接以公开仓库形式分发的插件,安装器会按这个顺序取包:
- Release 资产优先:先取仓库 latest release 上的
.zip资产(你发布脚本的产物),并过一道版本闸,资产里的版本低于仓库 HEAD 的 manifest 版本时不采用(宁可回落源码,也不让用户悄悄降级)。 - 回落仓库分支 zip:这是仓库里的原始源码。
- 装前闸:分支 zip 里 manifest 引用的面板源码若还带构建占位符,直接拒装,把运行时的
ReferenceError提前成安装失败。
所以:带构建步的插件,请务必发 Release 资产,并保证资产里的版本不低于仓库里 manifest 的版本。
包限制与红线
| 限制 | 值 |
|---|---|
| zip 大小 | ≤ 20MB |
| 文件数 | ≤ 200(社区分支包上限 500) |
| 扩展名白名单 | .json .js .mjs .cjs .ts .tsx .css .scss .html .md .txt .svg .png .jpg .jpeg .webp .gif .ico .woff .woff2 .ttf .map .yaml .yml(25 项,白名单外一律拒收) |
内容红线:市场素材(详情页文案、截图、示例)不得包含个人文档内容(剧本、回忆录等),示例一律用中性文案。
安全红线(违反即拒绝上架):
- 不得读取或外传会话令牌。
electronAPI.notratAuth不是插件契约;需要用户身份请走插件作用域身份(electronAPI.pluginIdentity.getIdentity)。 - 不得覆盖认证、权限、订阅等级、强制更新相关的内置组件(这类覆盖会被编译门禁直接拒绝)。
- 不得绕过套餐门禁。被拦截时你会拿到
{ success: false, code: "tier_required", requiredTier },正确做法是提示用户升级并降级。 - 不得把令牌、密钥、用户隐私写进日志。第三方插件的日志只落用户本机,用户随时会把它发出来。
发布后验证
# 1) 注册表里能查到(后端直读)
curl -s https://notrat.cn/plugins/plugins.json | grep -o '"id":"<你的id>"' | head -1
# 2) zip 能下载且哈希对得上(downloadUrl 从条目的 versions[0] 取)
curl -sL "<downloadUrl>" -o /tmp/x.zip && sha256sum /tmp/x.zip # 对照 checksums.txt- 站点详情页搜插件名,看简介、权限预览、版本号。
- 客户端 → 插件市场 → 官方插件 tab → 安装 → 面板 / 入口出现 → 跑一遍冒烟。
缓存
后端响应是 no-cache,但 CDN 与客户端列表可能延迟 30 秒到 5 分钟。复核时带 cache: "no-store"。
客户端会怎么装
zip 会被下载校验(sha256)后解包到 ~/.notrat/plugins/<id>/;写入顺序上 manifest.json 最后落盘(监听以它出现为安装信号),因此不会出现「脚本还没到位就被拉起」。装完默认启用(除非 manifest 里显式写了 "enabled": false)。
更新是怎么被用户收到的
- 客户端启动后拉取官方市场与社区货架的版本号,与本机已装版本对比(6 小时节流)。
- 使用插件时(打开主面板 / 点入口)如发现新版,弹窗询问「立即更新 / 暂不 / 不再提示」;「暂不」本次启动内静默,「不再提示」对该插件持久静音。
- 扩展管理页也可以手动「检测更新」,发现新版时对应条目直接出现更新按钮,一键升级(复用市场安装通道,装完自动重载)。
高频坑
- dev 包与市场包 id 打架:开发机上的
~/.notrat/plugins/<id>.json(build.mjs写的)与市场装的目录包~/.notrat/plugins/<id>/同 id 时,目录包胜出,dev 热更新会失效。测市场安装前先把 dev 包改名,测完改回。 - 版本只升不降:客户端按版本号比较,降号老用户看不到更新。
- 哈希不一致:只可能是打包方式变了(系统压缩、改动了文件时间戳)。用
build-release.mjs重新打包。 - 同一 id 两种包形态并存:目录包与单文件包同 id 时目录包优先;发布时统一只用一种。
