本指南面向针对先前 definePlugin() 形态编写的 沙箱 插件作者。请按顺序处理破坏性变更。它们都不会改变 hook 或路由在运行时的行为;它们改变的是插件的声明、构建与发布方式。
有关每个包的完整变更列表,请参阅 发布页面 上的对应条目。
破坏性变更
重命名:@emdash-cms/registry-cli 现为 @emdash-cms/plugin-cli
早期版本将 CLI 作为 @emdash-cms/registry-cli 发布,二进制为 emdash-registry。
该包现为 @emdash-cms/plugin-cli,二进制为 emdash-plugin。旧包不再发布。
我该做什么?
替换依赖:
pnpm remove @emdash-cms/registry-cli
pnpm add -D @emdash-cms/plugin-cli
在所有调用处将 emdash-registry 替换为 emdash-plugin。每个子命令保持名称不变(bundle、publish、login、whoami、switch、validate),并新增 init、build 和 dev。参见 The plugin CLI。
重命名:capability 名称使用 resource-first 拼写
早期清单使用诸如 read:content 和 network:fetch 的 capability 名称。创作清单仅接受当前名称,不过运行时在兼容窗口内仍会规范化已发布包中的旧名称。
我该做什么?
在清单中替换每个旧名称:
| 早期名称 | 当前名称 |
|---|---|
network:fetch | network:request |
network:fetch:any | network:request:unrestricted |
read:content | content:read |
write:content | content:write |
read:media | media:read |
write:media | media:write |
read:users | users:read |
email:provide | hooks.email-transport:register |
email:intercept | hooks.email-events:register |
page:inject | hooks.page-fragments:register |
对 network:request 使用非空的 allowedHosts 列表。仅当操作员在运行时选择目标时,才对 network:request:unrestricted 使用空列表。Capabilities and security 说明了当前权限与网络规则。
变更:沙箱插件使用显式 SandboxedPlugin 注解
早期版本用从 emdash 导入的 definePlugin() 包装插件的 hook 与路由,并由手工注解每个处理程序的参数。
沙箱插件将其定义赋给 SandboxedPlugin 类型的常量,并将该常量作为 default 导出。用 import type 从 emdash/plugin 导入该类型;打包器会擦除该导入。同一子路径还导出轻量运行时辅助函数 pluginRoute() 和 pluginResponse()。TypeScript 从 hook 或路由名称推断每个处理程序的 event 和 ctx,因此处理程序参数无需注解。显式注解还能在隔离的包管理器布局下保持生成声明的可移植性。
我该做什么?
对插件源文件做四处更改。替换导入:
import { definePlugin, type ContentHookEvent, type PluginContext } from "emdash";
import type { SandboxedPlugin } from "emdash/plugin";
将 definePlugin() 包装器替换为显式类型的常量:
export default definePlugin({ /* hooks, routes */ });
const plugin: SandboxedPlugin = { /* hooks, routes */ };
export default plugin;
从每个处理程序中移除参数注解:
handler: async (event: ContentHookEvent, ctx: PluginContext) => {
handler: async (event, ctx) => {
结果是一个 default 导出的对象:
import type { SandboxedPlugin } from "emdash/plugin";
const plugin: SandboxedPlugin = {
hooks: {
"content:beforeSave": {
handler: async (event, ctx) => {
return event.content;
},
},
},
};
export default plugin;
要在辅助函数中命名事件类型,请从 emdash/plugin 导入:
import type { ContentHookEvent, PluginContext } from "emdash/plugin";
处理程序的 event 始终是该 hook 的规范类型。用更窄的接口注解处理程序将不再通过类型检查。对依赖的字段在运行时用 typeof 检查或守卫进行校验,这是对来自类型系统之外的数据的正确做法。
变更:插件是一个 src/plugin.ts 加上 emdash-plugin.jsonc
早期版本将插件拆成两个文件:src/index.ts 返回 PluginDescriptor(id、version、capabilities、storage、entrypoint),src/sandbox-entry.ts 保存 hook 与路由。
插件现在是一个运行时文件 src/plugin.ts(hook 与路由),以及一个手写清单 emdash-plugin.jsonc(身份与信任契约)。entrypoint 和 format 字段已移除;构建会接线它们。
我该做什么?
按上述形态将 hook 与路由移入 src/plugin.ts。将描述符元数据移入 package.json 旁的 emdash-plugin.jsonc。描述符 id 成为清单 slug;capabilities、allowedHosts 和 storage 保持形态;version 从 package.json 读取,因此省略它。
以下示例显示声明了一个存储集合的描述符的清单等价物:
{
"$schema": "./node_modules/@emdash-cms/plugin-cli/schemas/emdash-plugin.schema.json",
"slug": "plugin-hello",
"publisher": "did:plc:abc123def456",
"license": "MIT",
"author": { "name": "Jane Doe", "url": "https://example.com" },
"security": { "email": "security@example.com" },
"capabilities": [],
"allowedHosts": [],
"storage": { "events": { "indexes": ["timestamp"] } }
}
每个字段见 The plugin manifest,publisher 字段见 Publisher pinning。
在 package.json 中,将 "./sandbox" 导出口指向构建后的运行时文件:
"./sandbox": "./dist/sandbox-entry.mjs"
"./sandbox": "./dist/plugin.mjs"
将清单加入 files,以便随包一起分发:
"files": ["dist"]
"files": ["dist", "emdash-plugin.jsonc"]
变更:用 emdash-plugin build 构建
早期版本用手工编写的 tsdown 脚本构建两个源文件。
emdash-plugin build 读取 emdash-plugin.jsonc 和 src/plugin.ts 并输出 dist/ 产物。emdash-plugin dev 监视并重建。
我该做什么?
替换构建脚本并添加监视脚本:
"scripts": {
"build": "tsdown src/index.ts src/sandbox-entry.ts --format esm --dts --clean"
"build": "emdash-plugin build",
"dev": "emdash-plugin dev"
}
然后校验并构建:
emdash-plugin validate
emdash-plugin build
移除:从 emdash 导出的标准格式类型与函数
早期版本从 emdash 导出 StandardPluginDefinition、StandardHookHandler、StandardHookEntry、StandardRouteHandler、StandardRouteEntry 以及函数 isStandardPluginDefinition。
这些已移除。它们是先前 definePlugin 形态的辅助别名。
我该做什么?
对相同目的使用 emdash/plugin 中的 SandboxedPlugin。沙箱插件的导出定义已由其 SandboxedPlugin 注解定型,因此没有 isStandardPluginDefinition 的替代;如需识别插件,请按结构({ hooks?, routes? })判断。
重命名:sandbox-runner 句柄使用 SandboxedPluginInstance
这仅影响自定义 SandboxRunner 的作者,例如 @emdash-cms/cloudflare。大多数插件作者可跳过。
面向作者的 SandboxedPlugin 类型可从 emdash/plugin 创作入口获得。SandboxRunner.load 返回的运行时句柄从 emdash 导出为 SandboxedPluginInstance。
我该做什么?
若你从 emdash 导入 SandboxedPlugin 以类型化 sandbox runner 或持有运行时插件句柄,请将导入改为 SandboxedPluginInstance:
import type { SandboxedPlugin } from "emdash";
import type { SandboxedPluginInstance } from "emdash";
告知用户
安装你插件的站点也需要更改导入。引导他们使用新形态:去掉花括号和 ()。
import { helloPlugin } from "@my-org/plugin-hello";
import hello from "@my-org/plugin-hello";
export default defineConfig({
integrations: [
emdash({
sandboxed: [helloPlugin()],
sandboxed: [hello],
}),
],
});
若插件通过工厂接受配置,请将该配置移到管理设置页面,并从 ctx.settings 读取。沙箱插件描述符是普通对象,无法接收构造函数选项。参见 Settings。
验证已迁移的插件
运行插件测试、校验创作清单,并执行完整的构建与打包检查:
pnpm test
pnpm exec emdash-plugin validate
pnpm exec emdash-plugin build
pnpm exec emdash-plugin bundle --validate-only
然后将本地包安装到开发站点,并演练每个已迁移的 hook 与路由。构建可以确认名称与形态,但无法确认路由返回预期数据,或 hook 正确保留内容。