docs(plugin): expand development guide and README links

This commit is contained in:
t8y2
2026-09-14 10:06:18 +08:00
parent 72b8b8f0ac
commit ba65d4a0e0
9 changed files with 830 additions and 90 deletions
+406 -39
View File
@@ -1,10 +1,14 @@
---
title: 开发和提交 DBX 插件
description: 创建 DBX 插件、构建未签名候选包,并明确源码、框架和官方商店分别向哪个仓库提交 Issue 或 Pull Request。
description: 从 CLI、Manifest、Workbench 和 Sidecar 开始开发 DBX 插件,完成本地调试、打包、自动上架和官方签名发布。
---
DBX 插件是独立安装的 `.dbxp` 包,可以提供连接类型、工作台、文件系统和原生 Sidecar。插件源码、DBX 插件框架和官方商店目录由不同仓库负责。
<Callout type="info">
**推荐路径:** 使用 `dbx-plugin create` 创建项目,在自己的仓库开发并发布未签名候选包;`dbx-store` 会为已注册的插件自动创建或更新候选 PR,维护者审核并签名后才会进入官方目录。
</Callout>
<Callout type="warn">
**插件上架 Pull Request 提交到 [`t8y2/dbx-store`](https://github.com/t8y2/dbx-store),不是 `t8y2/dbx`。** 插件自己的功能代码通常保留在开发者自己的源码仓库;只有修改 DBX Host、SDK、CLI、协议、Schema、文档或官方示例时,才向 [`t8y2/dbx`](https://github.com/t8y2/dbx) 提交 PR。
</Callout>
@@ -43,6 +47,165 @@ my-plugin/
- `.dbxp` 是安装包,不是源码仓库。
- 前端插件可以发布一个 `universal` 包;包含原生 Sidecar 的插件需要按操作系统和 CPU 架构分别构建。
## Manifest 完整说明
`manifest.json` 是运行时契约,文件名必须位于插件包根目录。Manifest v1 不接受未声明字段,建议在编辑器中引用仓库内的 [`plugins/manifest.schema.json`](https://github.com/t8y2/dbx/blob/main/plugins/manifest.schema.json)。最小的纯前端 Manifest 如下:
```json
{
"$schema": "https://raw.githubusercontent.com/t8y2/dbx/main/plugins/manifest.schema.json",
"manifest_version": 1,
"id": "com.example.files",
"name": "Example Files",
"version": "0.1.0",
"publisher": "example",
"description": "Browse files from an example service.",
"icon": "assets/plugin.svg",
"source": "https://github.com/example/dbx-plugin-files",
"homepage": "https://github.com/example/dbx-plugin-files",
"engines": { "host_api": "1" },
"permissions": [],
"entrypoints": { "ui": { "root": "ui", "entry": "ui/index.html" } },
"contributions": [
{ "type": "workbench", "id": "com.example.files.main", "label": "Files", "icon": "assets/plugin.svg" }
]
}
```
Manifest 中的路径相对于包根目录,不能以 `/` 开头、不能包含 `..`、反斜杠或重复斜杠。`ui.entry` 必须位于 `ui.root` 内:当 root 为 `ui` 时,入口写 `ui/index.html`,不是 `index.html`。`signingKeyId` 属于商店 artifact metadata,不是 Manifest 字段。
| 字段 | 必需 | 说明 |
| --- | --- | --- |
| `$schema` | 否 | 编辑器使用的 JSON Schema 地址,不参与身份校验。 |
| `manifest_version` | 是 | 当前必须为数字 `1`。 |
| `id` | 是 | 全局稳定 ID,只能使用小写字母、数字、`.`, `_`, `-`,例如 `io.github.example.files`;发布后不要更改。 |
| `name` | 是 | 默认显示名称。 |
| `icon` | 否 | 插件默认图标的包内相对路径;推荐 SVG 或 PNG。 |
| `version` | 是 | `major.minor.patch` 语义化版本,可带 prerelease/build 后缀。相同 ID 和版本的正式包不能覆盖。 |
| `publisher` | 是 | 发布者标识;首次上架后应保持不变。 |
| `description` | 否 | 插件说明。 |
| `source` | 否 | 源码仓库 HTTPS 地址,安装后可在插件信息中查看。 |
| `homepage` | 否 | 项目主页、文档或支持地址。 |
| `engines` | 是 | 运行时兼容范围,至少包含 `host_api`。 |
| `permissions` | 否 | 最小权限集合,见下文。 |
| `entrypoints` | 否 | UI 和原生 Sidecar 入口;贡献点存在时按需声明。 |
| `contributions` | 否 | 连接、工作台、文件系统、连接右键菜单和结果视图声明。 |
| `localizations` | 否 | 按 locale 覆盖名称、说明、贡献点、字段和动作文案。 |
### `engines`、权限和入口
```json
{
"engines": { "dbx": ">=0.6.0", "host_api": "1" },
"permissions": [
"host.workbench",
"host.events",
"host.filesystem",
"host.binary",
"host.network:https://s3.example.com:443"
],
"entrypoints": {
"ui": { "root": "ui", "entry": "ui/index.html" },
"backend": {
"protocol_versions": [1],
"transport": "stdio-jsonl",
"executable": "bin/linux-x64/backend"
}
}
}
```
- `engines.dbx` 是可选的 DBX 版本范围;`engines.host_api` 是必需的 Host API 版本声明。
- `host.workbench` 允许打开另一个工作台,`host.events` 接收宿主事件,`host.binary` 使用二进制通道,`host.filesystem` 使用文件系统入口。
- `host.network:https://host[:port]` 声明浏览器可访问的 HTTPS origin,DBX 将它加入 CSP 的 `connect-src`;最多 8 个,不允许路径、通配符或 Token。仍受目标服务 CORS 规则约束。该权限不是原生 Sidecar 的网络防火墙。
- `backend.transport` 默认是 `stdio-jsonl`;需要二进制帧时使用 `stdio-framed`,并同时声明 `host.binary`。
- `backend.executable`、`ui.root` 和 `ui.entry` 必须指向包内文件;带原生后端的包必须为每个目标平台提供对应可执行文件。
### Connection Provider
连接 Provider 负责声明连接表单,DBX 负责渲染表单和保存生命周期;插件只处理自己的连接协议。`binding` 决定字段语义:`config` 写入外部配置,`secret` 写入 Secret Store,`name`、`host`、`port`、`username`、`password`、`database` 映射到标准连接字段。
```json
{
"type": "connection-provider",
"id": "com.example.files.connection",
"label": "Example Service",
"icon": "assets/connection.svg",
"database_type": "example-files",
"description": "Connect to an Example Service.",
"fields": [
{ "key": "display_name", "label": "Name", "type": "text", "binding": "name", "required": true },
{ "key": "endpoint", "label": "Endpoint", "type": "text", "binding": "host", "required": true },
{ "key": "port", "label": "Port", "type": "number", "binding": "port", "default": 443 },
{ "key": "region", "label": "Region", "type": "radio", "options": [{ "label": "US", "value": "us" }, { "label": "EU", "value": "eu" }], "binding": "config" },
{ "key": "token", "label": "Access token", "type": "password", "binding": "secret", "required": true }
],
"workbench": "com.example.files.main",
"capabilities": ["test", "connect", "disconnect"],
"actions": [
{ "id": "refresh", "label": "Refresh metadata", "variant": "outline", "when": "edit", "requires_valid_form": true, "timeout_ms": 30000 }
]
}
```
字段类型为 `text`、`password`、`number`、`boolean`、`select`、`radio` 或 `textarea`;`select`/`radio` 必须提供非空 `options`,每项包含 `label` 和字符串 `value`。`binding: "port"` 必须使用 `number`。不要把密码、Token 或私钥放进 `config`、Workbench context、事件或日志。
每个字段必须声明 `key`、`label`、`type`,可选 `description`、`placeholder`、`required`、`default`、`options` 和 `binding`;默认值必须符合字段类型。省略 binding 的 `password` 字段默认按 `secret` 处理。`database_type` 是插件自定义类型标识,不会给 DBX 内置数据库枚举添加成员。
连接生命周期使用固定的 `connection/test`、`connection/connect`、`connection/disconnect`,自定义表单动作使用 `connection/action`,参数包含 `provider`、`connection`、`runtime`;动作额外包含 `action: { id }`。后端使用 `connection.id` 保存会话,并使用 `runtime.host`/`runtime.port` 连接经过 DBX 隧道或代理转换后的端点。仅在后端生命周期请求中接收补齐 Secret 的连接;前端通过 `connectionId` 使用已经建立的会话。
动作的 `when` 可为 `always`、`create`、`edit`;`variant` 可为 `default`、`outline`、`secondary`、`destructive`、`ghost`;`timeout_ms` 为 1–120000。`requires_valid_form` 决定是否要求完整表单,`close_on_success` 决定成功后是否关闭。返回 `{ "success": true, "message": "...", "fieldValues": { "port": 443 } }` 可以更新已声明的字段;返回 `success: false` 表示失败。
### 其他 Contributions
| 类型 | 必需字段 | 用途 |
| --- | --- | --- |
| `workbench` | `type`, `id`, `label` | 注册可从侧边栏或插件入口打开的工作台。 |
| `filesystem-provider` | `type`, `id`, `label`, `schemes` | 注册虚拟文件系统;`root_uri` 例如 `s3://bucket`,能力可选 `read`、`write`、`delete`、`rename`、`mkdir`。 |
| `context-menu` | `type`, `id`, `label`, `menu: "connection"` | 向连接的右键菜单增加动作。 |
| `result-view` | `type`, `id`, `label` | 注册查询或任务结果视图。 |
同一插件内通过 `id` 互相引用:Connection Provider 的 `workbench` 必须指向一个已声明的工作台,`filesystem_provider` 必须指向一个已声明的文件系统 Provider。图标优先使用 Contribution 的 `icon`,缺失时回退到插件级 `icon`。
自定义文件浏览器应使用 `workbench`:列表、虚拟滚动、预览、右键菜单和分栏由插件前端实现。只有希望使用 DBX 通用文件管理器时才选择 `filesystem_provider`;同时声明两者时默认打开工作台。`context-menu` 调用后端 `contextMenu/<contribution-id>`,`result-view` 则在工作台 context 中接收结果快照;具体负载见[完整贡献点协议](https://github.com/t8y2/dbx/blob/main/plugins/README.md)。
### 文件系统协议
使用 DBX 通用文件管理器时,后端实现以下方法;自定义工作台也可以自行通过 RPC 使用这些方法,但不能把声明 Contribution 当成已经实现业务逻辑。
| 方法 | 参数与返回 |
| --- | --- |
| `filesystem/list` | `providerId`、可选 `connectionId`、`uri`、可选 `cursor`、`limit`;返回 `{ entries, nextCursor? }`。 |
| `filesystem/read` | 同上身份字段、`uri`、`maxBytes`;返回 `{ dataBase64, contentType?, truncated, etag? }`。 |
| `filesystem/write` | `uri`、`dataBase64`、`create`、`overwrite`、可选 `etag`;需声明 `write`。 |
| `filesystem/createDirectory` | `uri`;需声明 `mkdir`。 |
| `filesystem/delete` | `uri`、`recursive`;需声明 `delete`。 |
| `filesystem/rename` | `sourceUri`、`targetUri`、`overwrite`;需声明 `rename`。 |
所有方法都携带 `providerId` 和可选 `connectionId`。目录项包含单个文件名 `name`、完整 `uri`、`kind`(`file`、`directory`、`symlink`、`other`)以及可选 `size`、`modifiedAt`、`contentType`。`name` 不能包含 `/`、`\\` 或是 `.`/`..`;完整路径放在 `uri`。最后一页省略 `nextCursor`,不要返回已经使用的 cursor。图片预览应返回实际 MIME 和 base64 字节,不能把二进制当作 UTF-8 文本。
写操作返回 `{ success, message?, entry? }`;内联读写限制为 4 MiB。大文件传输应采用分块与进度/取消协议,而不是一个巨大的 JSON/base64 值。
### 国际化
`localizations` 的 key 使用 `zh-CN`、`en` 等 locale。可覆盖插件名称、说明以及每个 Contribution 的 `label`、`description`、字段和动作文案;它不会翻译插件 UI 自己的文本。
```json
{
"localizations": {
"zh-CN": {
"name": "示例文件",
"contributions": {
"com.example.files.connection": {
"label": "示例服务",
"fields": { "token": { "label": "访问令牌" } }
}
}
}
}
}
```
## Workbench 上下文契约
Workbench 上下文跨越 DBX 与插件边界时是 JSON 数据快照。宿主会递归移除 Vue 响应式包装并发送独立副本,因此插件不能依赖 Vue ref、Proxy、DOM 节点、函数、组件实例或凭据。
@@ -51,6 +214,101 @@ Workbench 上下文跨越 DBX 与插件边界时是 JSON 数据快照。宿主
连接 Provider 使用 `binding: "config"` 的字段会保存到 `external_config`;使用 `binding: "secret"` 的字段会保存到 Secret Store。插件连接的 `external_config` 会在新建、编辑、保存和重新连接流程中保留。
## 前端 Host API
UI 在隔离的 iframe 中运行,DBX 通过 `window.dbxPlugin` 提供桥接对象。不要导入 DBX 的 Vue/Tauri 模块,也不要假设浏览器页面能直接访问 Node.js、文件系统或网络。
以下自定义 `objects/list` 和 `objects/changed` 需要由插件后端实现,资源示例假设包内存在 `ui/assets/empty-state.svg`:
```js
await window.dbxPlugin.ready;
const context = window.dbxPlugin.context;
const result = await window.dbxPlugin.invoke("objects/list", { prefix: "docs/" }, { timeoutMs: 30000 });
const off = window.dbxPlugin.onContext((nextContext) => render(nextContext));
await window.dbxPlugin.notify("objects/changed", { count: result.items.length });
const assetUrl = await window.dbxPlugin.readAssetUrl("assets/empty-state.svg");
```
| API | 作用 |
| --- | --- |
| `ready` | 等待 Host 完成初始化;应用启动逻辑放在 `await dbxPlugin.ready` 之后。 |
| `context` / `onContext(fn)` | 读取或监听当前工作台上下文。 |
| `locale` | 读取当前语言;插件 UI 需自行完成翻译。 |
| `theme` | 读取 `appearance` 和 DBX 设计 tokens,适配明暗主题。 |
| `request(method, params)` | 调用 Host API;常用内部方法包括 `host.getContext`、`ui.readAsset`。 |
| `invoke(method, params, { timeoutMs })` | 向自己的 Sidecar 发送有响应的 RPC。 |
| `notify(method, params)` | 向 Sidecar 发送无需返回业务结果的通知。 |
| `sendBinary(channel, data)` / `onBinary(fn)` | 发送或接收二进制数据;需要 `stdio-framed` 和 `host.binary`。 |
| `readAsset(path)` / `readAssetUrl(path)` | 读取包内资源;路径不能越出插件资源根目录。 |
| `openWorkbench(id, context)` | 打开本插件的另一个工作台;需要 `host.workbench`。 |
| `openFilesystem(id, context)` | 打开本插件的文件系统入口;需要 `host.filesystem`。 |
| `onInit(fn)` / `onEvent(fn)` | 监听初始化、环境变化或后端事件;转发后端事件需要 `host.events`。 |
调用失败会以 Promise rejection 返回,处理时应展示可理解的错误并允许重试。通过 `request` 调用的宿主方法、参数和返回值以当前 Host API 版本为准,不要调用未在协议中声明的 DBX 内部函数。
`readAsset` 的路径相对于 `ui.root`,所以这里的 `assets/empty-state.svg` 对应包内 `ui/assets/empty-state.svg`。资源不用时调用 `URL.revokeObjectURL(assetUrl)`;组件销毁时调用 `onContext`/`onEvent` 等返回的取消订阅函数,避免重复监听。
### 主题、布局和静态资源
DBX 将主题 tokens 注入文档根元素,并更新 `data-dbx-theme`。自定义组件库(包括 Svelte、shadcn-svelte)应使用这些变量,不要依赖父页面 CSS 自动穿透 iframe:
```css
body {
margin: 0;
background: var(--color-background, #fff);
color: var(--color-foreground, #18181b);
}
button {
background: var(--color-primary, #2563eb);
color: var(--color-primary-foreground, #fff);
cursor: pointer;
}
```
使用 `window.addEventListener("dbx-plugin-env", handler)` 响应语言或主题变化。先读取 `dbxPlugin.locale`/`theme` 初始化,再在事件中刷新自己的状态。DBX 提供轻量 `dbx-btn`、`dbx-input` 等样式,但开发宿主不模拟完整组件 kit,自定义 token 样式在两者中更一致。
UI 必须构建为包内静态资源,不要把 Vite 开发服务器或 CDN 的脚本地址写入发行包。宿主把入口及资源转换成沙箱可加载内容;CSP 报错时检查构建产物是否存在、路径是否落在 `ui.root` 内,并使用匹配版本的 CLI 复现。用插件自己的对话框组件处理删除/重命名,不要依赖沙箱中的原生 `alert`/`confirm`/`prompt`。
## 原生 Sidecar 和 RPC
只有浏览器沙箱无法完成的能力才需要原生后端,例如 S3/SSH 协议、系统凭据、长连接或高性能计算。Sidecar 不是操作系统沙箱,会以当前用户权限运行。
Rust 和 Go SDK 都实现 Sidecar Protocol v1:
```json
{"jsonrpc":"2.0","id":1,"method":"plugin/initialize","params":{"host":{"protocolVersions":[1]}}}
{"jsonrpc":"2.0","id":2,"method":"objects/list","params":{"prefix":"docs/"}}
```
响应必须使用相同的 `id`,成功返回 `result`,失败返回 JSON-RPC `error`。初始化响应至少包含 `protocolVersion`、`capabilities` 和 `plugin: { id, version }`;ID 和版本必须与 Manifest 一致,协议必须在双方支持范围内,否则会出现 `Sidecar identity or protocol does not match manifest`。初始化 capabilities 描述后端能力,不等于 Manifest 的 Host 权限列表。
```rust
use dbx_plugin_sdk::{PluginEmitter, PluginError, PluginHandler, PluginMetadata, PluginServer, RequestContext};
use serde_json::{json, Value};
struct Plugin;
impl PluginHandler for Plugin {
fn handle(&self, _context: RequestContext, method: &str, _params: Value, _emitter: &PluginEmitter) -> Result<Value, PluginError> {
match method {
"example/ping" => Ok(json!({ "ok": true })),
_ => Err(PluginError::method_not_found(method)),
}
}
}
fn main() -> std::io::Result<()> {
let metadata = PluginMetadata::new("com.example.files", env!("CARGO_PKG_VERSION"));
PluginServer::new(metadata, Plugin).serve()
}
```
Sidecar 约束:stdout 只能输出协议消息,日志写 stderr;请求 ID 必须支持并发关联;连接和断开应幂等;长任务应设计超时、取消和分块确认;不要在事件、context 或错误消息中泄露 Secret。
上例可替换 Rust 模板的 `backend/src/main.rs`,同时让 Cargo 包版本与 Manifest 一致。Go 使用模板生成的 `dbxpluginsdk.NewServer(metadata, handler).Serve()`;当前 Go SDK 支持 JSONL,Rust SDK 还支持 framed transport。不要仅修改 Manifest transport 就认为 Go 已具备二进制帧处理能力。完整接口见 [Rust SDK](https://github.com/t8y2/dbx/tree/main/plugins/sdk/rust/dbx-plugin-sdk) 和 [Go SDK](https://github.com/t8y2/dbx/tree/main/plugins/sdk/go/dbx-plugin-sdk)。
## 创建插件
直接安装预编译 CLI,不需要 Rust,也不需要克隆 DBX 源码:
@@ -74,6 +332,9 @@ npm 包会自动选择当前平台的预编译二进制,并携带匹配版本
# 纯前端、跨平台
dbx-plugin create my-ui-plugin --template frontend
# Svelte + Vite、跨平台
dbx-plugin create my-svelte-plugin --template svelte
# Rust Sidecar + 前端
dbx-plugin create my-rust-plugin --template rust
@@ -81,6 +342,28 @@ dbx-plugin create my-rust-plugin --template rust
dbx-plugin create my-go-plugin --template go
```
四种模板的选择:`frontend` 适合原生 HTML 或自行接入 Vue/React 的静态 UI;`svelte` 自带 Svelte 5 + Vite;`rust` 和 `go` 额外生成原生 Sidecar。`--backend none|svelte|rust|go` 可以作为模板别名,`--language rust|go` 是原生项目兼容别名。脚本或 CI 中可以用 `--yes` 跳过交互向导:
```bash
dbx-plugin create my-plugin \
--template svelte \
--id com.example.my-plugin \
--name "My Plugin" \
--publisher example \
--description "A DBX plugin." \
--version 0.1.0 \
--yes
```
Svelte 模板构建脚本会把 Vite 输出到 `ui/`,并在成功构建后输出 `DBX_UI_BUILD_SUCCESS`:
```bash
cd my-svelte-plugin
npm install
npm run build
dbx-plugin dev --port 5190
```
生成项目后,先在自己的插件源码仓库开发、测试和维护版本记录。不要为了发布一个普通插件而把整个源码目录复制进 `t8y2/dbx`。
## 构建候选包
@@ -98,10 +381,60 @@ dist/<plugin-id>-<version>-<target>.dbxp
dist/<plugin-id>-<version>-<target>.artifact.json
```
前端-only 项目默认使用 `universal` target;Rust/Go 项目使用当前平台 target。`--output-dir` 可以改变输出目录,`--artifact-url` 可以写入发布后的 HTTPS 地址:
```bash
dbx-plugin package . --target universal --output-dir dist --artifact-url https://downloads.example.com/my-plugin.dbxp
```
CLI 会只打包 `dbx-plugin.toml` 的 `[package].include` 声明目录,并拒绝符号链接、越界路径、`.dbx-dev`、过大的文件和不安全的输出位置。不要把 `dist/` 或开发数据再次作为输入目录。
官方插件作者不创建或持有 DBX Store 私钥。生成的 GitHub Release 工作流会按目标平台构建候选包,并合并产生 `release-candidates.json`。
本地测试未签名包时,需要在插件中心显式开启“允许安装未签名开发包”。该选项不会放宽官方商店安装校验。
## 本地调试和验证
需要实时观察前端时,在插件项目根目录运行独立开发宿主;它不会启动 DBX 桌面端:
```bash
dbx-plugin dev --path . --port 5190
```
打开终端输出的 `http://127.0.0.1:5190/`。`--port 0` 或端口被占用时会选择可用端口,`--data-dir` 可以把开发数据移出项目目录:
```bash
dbx-plugin dev --path . --data-dir /tmp/my-plugin-dev
```
`dbx-plugin dev` 不会自动安装插件依赖;前端构建由 `dbx-plugin.toml` 的 `[dev]` 命令决定:
```toml
[dev]
ui_build = ["npm", "run", "build"]
ui_watch = ["npm", "run", "build:watch"]
```
配置 `ui_watch` 时,命令只有在一次完整成功构建后输出独立一行 `DBX_UI_BUILD_SUCCESS`,开发宿主才会重载 UI。不要在失败路径或无条件的退出钩子中输出这个标记。
调试页可以切换语言、主题、自动重载和日志面板。日志只保留内存中的最近 500 条,密码、Token 和 Manifest 中的 Secret 字段会脱敏,二进制和过大的值会省略或截断;开发目录 `.dbx-dev/` 可能包含明文凭据,必须加入 `.gitignore`,不要提交或共享。
需要从脚本读取脱敏日志时:
```bash
curl -sS 'http://127.0.0.1:5190/api/diagnostics?after=0&limit=100&level=error'
```
后续请求使用响应里的 `nextAfter` 和 `instanceId`。开发宿主只模拟受支持的 Host API 子集;安装、签名、真实 Secret Store、桌面端生命周期和生产权限必须再用 DBX 验证。
### 本地验收清单
- 关闭未使用的权限,确认 Manifest 中的权限与实际调用一致。
- 用空配置、错误凭据、超时、断网和 Sidecar 重启分别测试失败路径。
- 测试 DBX 浅色/深色主题和 `zh-CN`/`en`;不要依赖宿主替你翻译插件 UI。
- 测试重新连接、关闭工作台、重复打开同一工作台以及上下文为空的情况。
- 检查包内所有资源路径、可执行文件权限和最终包是否包含不应发布的文件。
## 提交官方商店的完整流程
### 第一步:发布源码和候选 Release
@@ -115,17 +448,61 @@ dist/<plugin-id>-<version>-<target>.artifact.json
候选包可以放在插件源码仓库的 GitHub Releases、CDN 或对象存储中,但不要把 `.dbxp` 提交到 Git 历史。
### 第二步:在 dbx-store 发起审核 Issue
### 第二步:让 dbx-store 自动创建候选 PR
前往 [`t8y2/dbx-store` Issues](https://github.com/t8y2/dbx-store/issues/new/choose),选择 **Plugin submission**,填写:
`dbx-store` 的同步 Workflow 会定期检查已在 `automation/plugin-sources.json` 注册且 `autoUpdate: true` 的公开插件仓库。它读取最新 Release 的 `release-candidates.json`,创建或更新唯一的:
- 插件 ID、版本和发布者;
- 源码仓库和对应 Tag;
- `release-candidates.json` 的 HTTPS 地址;
- 权限、数据访问、网络访问和原生 Sidecar 行为;
- 许可证、隐私政策和支持地址。
```text
candidates/<plugin-id>.json
```
审核 Issue 属于签名前的审核入口。此时不要向 `t8y2/dbx` 提交插件源码 PR,也不要自行添加官方 `signingKeyId`。
因此,已启用自动更新的插件作者**不需要每个版本手工创建 Issue 或 PR**,也不需要在插件仓库配置商店签名 Secret。插件仓库的 Release、候选 metadata 和可访问的源码 Tag 是同步所需的主要信息。首次上架或需要修改商店展示字段时,才按 [`dbx-store` 的贡献指南](https://github.com/t8y2/dbx-store/blob/main/CONTRIBUTING.md) 提供发布者和候选信息。
`.dbx-store.json` 是可选的商店元数据文件:首次登记或主动修改商店名称、标签、许可证、主页等信息时使用;仅发布新版本时不需要重复修改它。它不是插件运行时 Manifest,也不能放签名私钥。
例如:
```json
{
"name": "Example Files",
"description": "Browse files from an example service.",
"icon": "assets/plugin.svg",
"tags": ["files", "storage"],
"permissions": ["host.workbench"],
"source": "https://github.com/example/dbx-plugin-files",
"homepage": "https://github.com/example/dbx-plugin-files",
"license": "Apache-2.0",
"releaseNotes": "Initial release."
}
```
允许的商店字段是 `name`、`description`、`icon`、`tags`、`permissions`、`source`、`homepage`、`license`、`releaseNotes` 和 `localizations`;`icon` 可以是包内相对路径,自动同步时会转换成源码 Tag 下的 HTTPS 地址。`autoUpdate` 是 `dbx-store/automation/plugin-sources.json` 的注册字段,不是这个文件的字段。
自动同步只创建或更新候选 PR,**不会自动批准、签名或合并**。候选 PR 的目标仓库是 [`t8y2/dbx-store`](https://github.com/t8y2/dbx-store),目标分支是 `main`,而不是 `t8y2/dbx`。
如果插件还没有登记到自动同步列表,首次上架可以手动 Fork `dbx-store`,向 `main` 提交**一个** PR,包含发布者首次需要的 `publishers/<publisher-id>.json` 和 `candidates/<plugin-id>.json`。不需要先创建单独的 Issue;CI 会在签名前有意保持 `open candidate(s) awaiting DBX Store signing`,签名 Workflow 回写正式目录后才会变绿。插件作者可以在 `dbx-store` 提交登记自动同步源的 PR,或请维护者登记;无需在插件仓库增加 App 私钥。
候选文件的关键结构如下。它只引用插件仓库自己的未签名 Release 资产,不能填写官方 `signingKeyId`:
```json
{
"schemaVersion": 1,
"id": "com.example.files",
"publisher": "example",
"version": "0.1.0",
"name": "Example Files",
"source": "https://github.com/example/dbx-plugin-files/tree/v0.1.0",
"license": "Apache-2.0",
"targets": [{
"target": "universal",
"url": "https://github.com/example/dbx-plugin-files/releases/download/v0.1.0/com.example.files-0.1.0-universal.dbxp",
"sha256": "<64 位十六进制 SHA-256>",
"size": 123456
}]
}
```
新版本仅提交新增版本;生成的 `plugins/<plugin-id>.json` 和 `catalog/index.json` 不要手工编辑。
### 第三步:DBX Store 审核并签名
@@ -138,34 +515,11 @@ dist/<plugin-id>-<version>-<target>.artifact.json
开发者不会接触官方仓库私钥。
### 第四步:向 dbx-store 提交上架 PR
### 第四步:合并候选 PR
获得最终签名 artifact metadata 后,Fork [`t8y2/dbx-store`](https://github.com/t8y2/dbx-store),添加或更新:
维护者运行受保护的签名 Workflow 后,Workflow 会把最终的 `plugins/<plugin-id>.json` 和生成的 `catalog/index.json` 回写到同一个候选 PR,并将签名包、artifact metadata 和 signing receipt 发布到 `dbx-store` Release。校验通过后,维护者再审核并合并这个 PR。
```text
publishers/<publisher-id>.json # 仅发布者首次提交时需要
plugins/<plugin-id>.json
catalog/index.json # 由验证脚本生成
```
运行:
```bash
node scripts/validate.mjs
```
然后创建 Pull Request:
```text
目标仓库:t8y2/dbx-store
目标分支:main
```
<Callout type="info">
一个新插件的正式上架 PR **只提交到 `t8y2/dbx-store`**。`t8y2/dbx` PR 只用于修改插件平台本身,不用于登记普通第三方插件版本。
</Callout>
上架 PR 中不要包含:
上架或更新 PR 中不要包含:
- `.dbxp` 二进制;
- 插件完整源码副本;
@@ -177,10 +531,12 @@ node scripts/validate.mjs
每次更新都发布新语义版本,不允许覆盖旧版本的 Release 资产:
1. 在插件源码仓库发布新的源码 Tag 和候选 Release。
2. 在 `t8y2/dbx-store` 创建新的 Plugin submission Issue,并引用旧插件和新版本。
3. 等待新候选包完成审核和仓库签名。
4. 向 `t8y2/dbx-store` 提交更新 `plugins/<plugin-id>.json` 的 PR。
1. 修改插件源码,并递增 `manifest.json` 的 `version`;不要复用旧版本号。
2. 发布新的源码 Tag 和候选 Release;旧 Release 资产不可覆盖。
3. 等待同步 Workflow 创建或更新 `automation/plugin-release/<plugin-id>/<version>` 候选 PR。
4. 维护者审核、签名并合并该 PR;目录随后包含新版本和对应平台包。
如果仓库未启用自动同步,才需要按照 `dbx-store` 贡献指南手动提交 `candidates/<plugin-id>.json`。新版本只提交新版本候选信息,不要手工重写已生成的 `plugins/` 或 `catalog/` 文件。
插件代码问题继续在插件源码仓库修复;只有目录、审核状态、最终下载地址、哈希、大小或商店文案变更才进入 `dbx-store`。
@@ -196,6 +552,17 @@ DBX 当前使用单一仓库签名:
人工审核决定插件能否展示,Ed25519 仓库签名保证审核后的安装包没有被替换。签名不能替代源码审查,也不是原生 Sidecar 的操作系统沙箱。
官方商店插件作者不要运行 `keygen`,也不要自行填写 `signingKeyId`。如果你运营自己的私有插件仓库,才使用 CLI 的高级签名工具:
```bash
dbx-plugin keygen company.plugins.release
source .dbx-repository-signing-key.env
```
该命令生成 32 字节 Ed25519 seed 的受保护环境文件(Unix 权限为 `0600`),并输出 Key ID 和 Base64 公钥;私钥文件绝不能提交 Git。自定义仓库还必须把公钥加入自己的 DBX 信任配置。官方商店的私钥只存在 `dbx-store` 受保护的 Environment Secret 中,公钥由 DBX 客户端随官方信任列表发布。
`artifact.json` 中的 `target`、`url`、`sha256` 和 `size` 绑定确切字节;官方签名后才会增加 `signingKeyId`。已发布资产不可覆盖,任何字节变化都必须递增插件版本并重新审核。
## 相关资料
- [JDBC 插件与数据库驱动](/cn/docs/plugins)
+406 -39
View File
@@ -1,10 +1,14 @@
---
title: Develop and Submit DBX Plugins
description: Create DBX plugins, build unsigned candidates, and identify the correct repository for source, framework, and official Marketplace pull requests.
description: Build DBX plugins with the CLI, Manifest, Workbench and Sidecar APIs, then debug, package, automatically submit, and publish them through the official Marketplace.
---
DBX plugins are independently installed `.dbxp` packages that can contribute connection types, workbenches, filesystems, and native sidecars. Plugin source, the DBX plugin platform, and the official Marketplace catalog are owned by different repositories.
<Callout type="info">
**Recommended path:** create a project with `dbx-plugin create`, develop and publish unsigned candidates from the plugin's own repository, then let `dbx-store` create or update the candidate PR. A maintainer reviews and signs the candidates before they enter the official catalog.
</Callout>
<Callout type="warn">
**Marketplace listing pull requests go to [`t8y2/dbx-store`](https://github.com/t8y2/dbx-store), not `t8y2/dbx`.** A plugin's feature code normally remains in its author's source repository. Open a PR against [`t8y2/dbx`](https://github.com/t8y2/dbx) only when changing the DBX host, SDK, CLI, protocol, schemas, documentation, or official examples.
</Callout>
@@ -43,6 +47,165 @@ my-plugin/
- `.dbxp` is an installable artifact, not the source repository.
- Frontend-only plugins can publish one `universal` package. Native sidecars require packages for each operating-system and CPU target.
## Complete Manifest Reference
`manifest.json` is the runtime contract and must be at the root of the plugin package. Manifest v1 rejects undeclared fields. Reference the repository's [`plugins/manifest.schema.json`](https://github.com/t8y2/dbx/blob/main/plugins/manifest.schema.json) from your editor. A minimal frontend-only Manifest looks like this:
```json
{
"$schema": "https://raw.githubusercontent.com/t8y2/dbx/main/plugins/manifest.schema.json",
"manifest_version": 1,
"id": "com.example.files",
"name": "Example Files",
"version": "0.1.0",
"publisher": "example",
"description": "Browse files from an example service.",
"icon": "assets/plugin.svg",
"source": "https://github.com/example/dbx-plugin-files",
"homepage": "https://github.com/example/dbx-plugin-files",
"engines": { "host_api": "1" },
"permissions": [],
"entrypoints": { "ui": { "root": "ui", "entry": "ui/index.html" } },
"contributions": [
{ "type": "workbench", "id": "com.example.files.main", "label": "Files", "icon": "assets/plugin.svg" }
]
}
```
Manifest paths are relative to the package root. They cannot start with `/`, contain `..`, backslashes, or duplicate slashes. `ui.entry` must be inside `ui.root`: with root `ui`, use `ui/index.html`, not `index.html`. `signingKeyId` belongs to Marketplace artifact metadata, not the Manifest.
| Field | Required | Meaning |
| --- | --- | --- |
| `$schema` | No | JSON Schema URL for editor support; it does not establish identity. |
| `manifest_version` | Yes | Must currently be the number `1`. |
| `id` | Yes | Stable global ID using lowercase letters, digits, `.`, `_`, and `-`, such as `io.github.example.files`; never change it after publishing. |
| `name` | Yes | Default display name. |
| `icon` | No | Relative path to the default package icon; SVG or PNG is recommended. |
| `version` | Yes | Semantic version `major.minor.patch`, optionally with prerelease/build suffixes. Official packages cannot overwrite the same ID and version. |
| `publisher` | Yes | Publisher identifier; keep it stable after the first listing. |
| `description` | No | Plugin description. |
| `source` | No | HTTPS source repository URL, also shown from installed-plugin details. |
| `homepage` | No | Project, documentation, or support URL. |
| `engines` | Yes | Compatibility declaration; it must contain `host_api`. |
| `permissions` | No | Minimal capability set, described below. |
| `entrypoints` | No | UI and native-sidecar entrypoints. |
| `contributions` | No | Connection, workbench, filesystem, connection-menu, and result-view declarations. |
| `localizations` | No | Locale-specific names, descriptions, fields, and action labels. |
### Engines, Permissions, and Entrypoints
```json
{
"engines": { "dbx": ">=0.6.0", "host_api": "1" },
"permissions": [
"host.workbench",
"host.events",
"host.filesystem",
"host.binary",
"host.network:https://s3.example.com:443"
],
"entrypoints": {
"ui": { "root": "ui", "entry": "ui/index.html" },
"backend": {
"protocol_versions": [1],
"transport": "stdio-jsonl",
"executable": "bin/linux-x64/backend"
}
}
}
```
- `engines.dbx` is an optional DBX version range; `engines.host_api` is required.
- `host.workbench` opens another workbench, `host.events` receives host events, `host.binary` enables binary channels, and `host.filesystem` enables filesystem entrypoints.
- `host.network:https://host[:port]` declares a browser-accessible HTTPS origin added to CSP `connect-src`; at most eight origins are allowed, with no paths, wildcards, or tokens. The service's CORS rules still apply. This permission is not a native Sidecar network firewall.
- `backend.transport` defaults to `stdio-jsonl`. Use `stdio-framed` for binary frames and declare `host.binary` as well.
- `backend.executable`, `ui.root`, and `ui.entry` must point to package files. Native packages need a matching executable for every target.
### Connection Providers
A connection provider declares a form; DBX renders the form and owns the connection lifecycle, while the plugin handles its protocol. `binding` gives a field its meaning: `config` is persisted as external configuration, `secret` goes to the Secret Store, and `name`, `host`, `port`, `username`, `password`, and `database` map to standard connection fields.
```json
{
"type": "connection-provider",
"id": "com.example.files.connection",
"label": "Example Service",
"icon": "assets/connection.svg",
"database_type": "example-files",
"description": "Connect to an Example Service.",
"fields": [
{ "key": "display_name", "label": "Name", "type": "text", "binding": "name", "required": true },
{ "key": "endpoint", "label": "Endpoint", "type": "text", "binding": "host", "required": true },
{ "key": "port", "label": "Port", "type": "number", "binding": "port", "default": 443 },
{ "key": "region", "label": "Region", "type": "radio", "options": [{ "label": "US", "value": "us" }, { "label": "EU", "value": "eu" }], "binding": "config" },
{ "key": "token", "label": "Access token", "type": "password", "binding": "secret", "required": true }
],
"workbench": "com.example.files.main",
"capabilities": ["test", "connect", "disconnect"],
"actions": [
{ "id": "refresh", "label": "Refresh metadata", "variant": "outline", "when": "edit", "requires_valid_form": true, "timeout_ms": 30000 }
]
}
```
Field types are `text`, `password`, `number`, `boolean`, `select`, `radio`, and `textarea`. `select` and `radio` require non-empty `options`, each with a `label` and string `value`. `binding: "port"` requires `number`. Never put passwords, tokens, or private keys in config, workbench context, events, or logs.
Fields require `key`, `label`, and `type`; optional fields are `description`, `placeholder`, `required`, `default`, `options`, and `binding`. Defaults must match the field type. A password field without an explicit binding defaults to `secret`. `database_type` is a plugin-defined identifier, not an extension to DBX's built-in database enum.
The fixed lifecycle methods are `connection/test`, `connection/connect`, and `connection/disconnect`; custom form actions use `connection/action`. Parameters include `provider`, `connection`, and `runtime`, plus `action: { id }` for actions. Keep sessions keyed by `connection.id` and connect to `runtime.host`/`runtime.port`, which include DBX tunnel/proxy resolution. Hydrated secrets are provided only to backend lifecycle requests; the frontend uses `connectionId` to access an established session.
Action `when` accepts `always`, `create`, or `edit`; `variant` accepts `default`, `outline`, `secondary`, `destructive`, or `ghost`; `timeout_ms` is 1–120000. `requires_valid_form` requires a complete form and `close_on_success` closes the dialog on success. Return `{ "success": true, "message": "...", "fieldValues": { "port": 443 } }` to update declared fields; `success: false` is a failure.
### Other Contributions
| Type | Required fields | Purpose |
| --- | --- | --- |
| `workbench` | `type`, `id`, `label` | Registers a workbench opened from the sidebar or plugin entry. |
| `filesystem-provider` | `type`, `id`, `label`, `schemes` | Registers a virtual filesystem; `root_uri` can be `s3://bucket`, with optional `read`, `write`, `delete`, `rename`, and `mkdir` capabilities. |
| `context-menu` | `type`, `id`, `label`, `menu: "connection"` | Adds an action to a connection context menu. |
| `result-view` | `type`, `id`, `label` | Registers a query or task result view. |
Reference contributions by ID: a connection provider's `workbench` must point to a declared workbench and `filesystem_provider` to a declared filesystem provider. A contribution icon takes precedence over the plugin-level icon.
Choose `workbench` for a custom file browser: the plugin owns its list, virtualization, previews, context menus, and split panes. Choose `filesystem_provider` only to use DBX's generic file manager; declaring both defaults to the workbench. `context-menu` invokes `contextMenu/<contribution-id>` on the backend; `result-view` receives a result snapshot in workbench context. See the [full contribution protocol](https://github.com/t8y2/dbx/blob/main/plugins/README.md) for payloads.
### Filesystem Protocol
Implement these backend methods when using the DBX generic file manager. A custom workbench can use them through its own RPC calls as well; declaring a contribution does not implement business logic.
| Method | Parameters and return values |
| --- | --- |
| `filesystem/list` | `providerId`, optional `connectionId`, `uri`, optional `cursor`, `limit`; returns `{ entries, nextCursor? }`. |
| `filesystem/read` | The same identity fields, `uri`, `maxBytes`; returns `{ dataBase64, contentType?, truncated, etag? }`. |
| `filesystem/write` | `uri`, `dataBase64`, `create`, `overwrite`, optional `etag`; requires `write`. |
| `filesystem/createDirectory` | `uri`; requires `mkdir`. |
| `filesystem/delete` | `uri`, `recursive`; requires `delete`. |
| `filesystem/rename` | `sourceUri`, `targetUri`, `overwrite`; requires `rename`. |
Every method carries `providerId` and optional `connectionId`. Entries contain a single filename `name`, full `uri`, `kind` (`file`, `directory`, `symlink`, or `other`), and optional `size`, `modifiedAt`, and `contentType`. Names cannot include `/` or `\\`, or equal `.`/`..`; put full paths in `uri`. Omit `nextCursor` on the last page and never repeat a consumed cursor. Image previews need the actual MIME type and base64 bytes, not UTF-8-decoded binary content.
Mutations return `{ success, message?, entry? }`; inline read/write payloads are capped at 4 MiB. Use a chunked transfer protocol with progress/cancellation for large files, not one giant JSON/base64 value.
### Localization
Use locale keys such as `zh-CN` and `en` in `localizations`. You can override the plugin name, description, and every contribution's labels, descriptions, fields, and actions. This does not translate text inside the plugin UI.
```json
{
"localizations": {
"zh-CN": {
"name": "示例文件",
"contributions": {
"com.example.files.connection": {
"label": "示例服务",
"fields": { "token": { "label": "访问令牌" } }
}
}
}
}
}
```
## Workbench Context Contract
Workbench context is a JSON data snapshot across the DBX/plugin boundary. The host recursively removes Vue reactivity and sends an independent copy, so plugins must not expect Vue refs, proxies, DOM nodes, functions, component instances, or credentials.
@@ -51,6 +214,101 @@ Context may contain `null`, booleans, finite numbers, strings, arrays, and plain
Connection provider fields using `binding: "config"` are persisted in `external_config`; fields using `binding: "secret"` are persisted in the Secret Store. Plugin connection configuration keeps `external_config` through create, edit, save, and reconnect flows.
## Frontend Host API
The UI runs in an isolated iframe. DBX exposes the bridge as `window.dbxPlugin`; do not import DBX Vue/Tauri modules or assume that Node.js, local files, or arbitrary network access are available.
The following custom `objects/list` and `objects/changed` methods must be implemented by your backend. The asset example assumes the package contains `ui/assets/empty-state.svg`:
```js
await window.dbxPlugin.ready;
const context = window.dbxPlugin.context;
const result = await window.dbxPlugin.invoke("objects/list", { prefix: "docs/" }, { timeoutMs: 30000 });
const off = window.dbxPlugin.onContext((nextContext) => render(nextContext));
await window.dbxPlugin.notify("objects/changed", { count: result.items.length });
const assetUrl = await window.dbxPlugin.readAssetUrl("assets/empty-state.svg");
```
| API | Purpose |
| --- | --- |
| `ready` | Waits for Host initialization; start application logic after `await dbxPlugin.ready`. |
| `context` / `onContext(fn)` | Reads or observes the current workbench context. |
| `locale` | Reads the current DBX locale; the plugin translates its own UI. |
| `theme` | Reads `appearance` and DBX design tokens for light/dark support. |
| `request(method, params)` | Calls a Host API method, including `host.getContext` and `ui.readAsset`. |
| `invoke(method, params, { timeoutMs })` | Sends an RPC request to the plugin's Sidecar. |
| `notify(method, params)` | Sends a notification without a business result. |
| `sendBinary(channel, data)` / `onBinary(fn)` | Sends or receives binary data; requires framed transport and `host.binary`. |
| `readAsset(path)` / `readAssetUrl(path)` | Reads a packaged asset within the plugin resource root. |
| `openWorkbench(id, context)` | Opens another workbench from this plugin; requires `host.workbench`. |
| `openFilesystem(id, context)` | Opens a filesystem entry; requires `host.filesystem`. |
| `onInit(fn)` / `onEvent(fn)` | Observes initialization, environment changes, or backend events; forwarding backend events requires `host.events`. |
Failed calls reject their Promise; show a useful error and offer retry. Host methods, parameters, and results are versioned API surface—do not call undocumented DBX internals.
`readAsset` paths are relative to `ui.root`: `assets/empty-state.svg` resolves to the package's `ui/assets/empty-state.svg`. Call `URL.revokeObjectURL(assetUrl)` when done, and call the unsubscribe functions returned by `onContext`/`onEvent` when a component is destroyed.
### Themes, Layout, and Static Assets
DBX injects theme tokens on the document root and updates `data-dbx-theme`. Custom libraries, including Svelte and shadcn-svelte, should consume these variables; parent-page CSS does not cross the iframe:
```css
body {
margin: 0;
background: var(--color-background, #fff);
color: var(--color-foreground, #18181b);
}
button {
background: var(--color-primary, #2563eb);
color: var(--color-primary-foreground, #fff);
cursor: pointer;
}
```
Listen with `window.addEventListener("dbx-plugin-env", handler)` for locale/theme changes. Initialize from `dbxPlugin.locale`/`theme`, then refresh your state on events. DBX includes lightweight `dbx-btn` and `dbx-input` styles, but the development host does not emulate the complete component kit; custom token-based styles work more consistently in both.
Build the UI as packaged static assets, not references to a Vite server or CDN scripts. The host converts the entry and assets into sandbox-loadable content. For CSP errors, verify build outputs and confinement under `ui.root`, and reproduce with the matching CLI version. Use plugin-owned dialogs for delete/rename rather than relying on native `alert`/`confirm`/`prompt` inside the sandbox.
## Native Sidecars and RPC
Use a native backend only when the browser sandbox cannot provide the capability, such as S3/SSH protocols, system credentials, long-lived connections, or CPU-heavy work. A Sidecar is not an OS sandbox and runs with the current user's privileges.
The Rust and Go SDKs implement Sidecar Protocol v1:
```json
{"jsonrpc":"2.0","id":1,"method":"plugin/initialize","params":{"host":{"protocolVersions":[1]}}}
{"jsonrpc":"2.0","id":2,"method":"objects/list","params":{"prefix":"docs/"}}
```
Responses must reuse the request `id`; success uses `result` and failures use a JSON-RPC `error`. Initialization includes `protocolVersion`, `capabilities`, and `plugin: { id, version }`. ID/version must match the Manifest and the protocol must be supported by both sides, otherwise `Sidecar identity or protocol does not match manifest` is reported. Backend capabilities are not the Manifest's Host permission list.
```rust
use dbx_plugin_sdk::{PluginEmitter, PluginError, PluginHandler, PluginMetadata, PluginServer, RequestContext};
use serde_json::{json, Value};
struct Plugin;
impl PluginHandler for Plugin {
fn handle(&self, _context: RequestContext, method: &str, _params: Value, _emitter: &PluginEmitter) -> Result<Value, PluginError> {
match method {
"example/ping" => Ok(json!({ "ok": true })),
_ => Err(PluginError::method_not_found(method)),
}
}
}
fn main() -> std::io::Result<()> {
let metadata = PluginMetadata::new("com.example.files", env!("CARGO_PKG_VERSION"));
PluginServer::new(metadata, Plugin).serve()
}
```
Sidecar rules: reserve stdout for protocol messages and write logs to stderr; correlate concurrent requests by ID; make connect/disconnect idempotent; design timeouts, cancellation, and chunk acknowledgements for long tasks; never expose secrets through events, context, or error messages.
The example can replace the Rust template's `backend/src/main.rs`; keep its Cargo package version equal to the Manifest. Go templates use `dbxpluginsdk.NewServer(metadata, handler).Serve()`. The current Go SDK supports JSONL; the Rust SDK also supports framed transport. Changing a Go Manifest's transport alone does not implement binary framing. See the [Rust SDK](https://github.com/t8y2/dbx/tree/main/plugins/sdk/rust/dbx-plugin-sdk) and [Go SDK](https://github.com/t8y2/dbx/tree/main/plugins/sdk/go/dbx-plugin-sdk) for complete interfaces.
## Create a Plugin
Install the precompiled CLI without Rust or a DBX source checkout:
@@ -74,6 +332,9 @@ Create a project:
# Frontend only and cross-platform
dbx-plugin create my-ui-plugin --template frontend
# Svelte + Vite and cross-platform
dbx-plugin create my-svelte-plugin --template svelte
# Rust sidecar plus frontend
dbx-plugin create my-rust-plugin --template rust
@@ -81,6 +342,28 @@ dbx-plugin create my-rust-plugin --template rust
dbx-plugin create my-go-plugin --template go
```
Choose `frontend` for plain HTML or a self-managed Vue/React UI, `svelte` for the Svelte 5 + Vite starter, or `rust`/`go` when a native Sidecar is needed. `--backend none|svelte|rust|go` is a template alias and `--language rust|go` remains a native-project compatibility alias. Use `--yes` for scripts and CI:
```bash
dbx-plugin create my-plugin \
--template svelte \
--id com.example.my-plugin \
--name "My Plugin" \
--publisher example \
--description "A DBX plugin." \
--version 0.1.0 \
--yes
```
The Svelte template writes Vite output to `ui/` and emits `DBX_UI_BUILD_SUCCESS` after a successful build:
```bash
cd my-svelte-plugin
npm install
npm run build
dbx-plugin dev --port 5190
```
Develop, test, and version the generated project in the plugin's own source repository. Do not copy an ordinary plugin source tree into `t8y2/dbx` merely to publish it.
## Build Review Candidates
@@ -98,10 +381,60 @@ dist/<plugin-id>-<version>-<target>.dbxp
dist/<plugin-id>-<version>-<target>.artifact.json
```
Frontend-only projects default to the `universal` target; Rust/Go projects use the current platform target. Use `--output-dir` to change the output location and `--artifact-url` to record the eventual HTTPS URL:
```bash
dbx-plugin package . --target universal --output-dir dist --artifact-url https://downloads.example.com/my-plugin.dbxp
```
The CLI packages only directories declared by `[package].include` in `dbx-plugin.toml` and rejects symlinks, traversal paths, `.dbx-dev`, oversized files, and unsafe output locations. Do not use `dist/` or development data as a nested package input.
Official plugin authors do not create or possess the DBX Store private key. The generated GitHub Release workflow builds candidates for each target and merges their metadata into `release-candidates.json`.
To install an unsigned package during local development, explicitly enable **Allow unsigned development packages** in Plugin Center. This option never relaxes official Marketplace verification.
## Local Development and Verification
Run the standalone browser development host from the plugin root when you need live UI and Sidecar diagnostics; it does not start the DBX desktop application:
```bash
dbx-plugin dev --path . --port 5190
```
Open the `http://127.0.0.1:5190/` URL printed by the command. Use `--port 0`, or let a busy port fall back to an available one. Move development data outside the project with `--data-dir`:
```bash
dbx-plugin dev --path . --data-dir /tmp/my-plugin-dev
```
`dbx-plugin dev` does not install project dependencies. Configure frontend builds in `dbx-plugin.toml`:
```toml
[dev]
ui_build = ["npm", "run", "build"]
ui_watch = ["npm", "run", "build:watch"]
```
When `ui_watch` is configured, it must print a standalone `DBX_UI_BUILD_SUCCESS` line only after a complete successful build has written every output. Do not print it on failure or from an unconditional exit hook.
The debug page can switch locale, theme, auto-reload, and diagnostic views. It keeps only the latest 500 in-memory entries, redacts passwords, tokens, and Manifest secret fields, and omits or truncates binary and oversized values. `.dbx-dev/` may contain plaintext credentials: keep it in `.gitignore` and never share it.
Read redacted diagnostics from a script:
```bash
curl -sS 'http://127.0.0.1:5190/api/diagnostics?after=0&limit=100&level=error'
```
Pass `nextAfter` and `instanceId` from the response to the next request. The development host implements a supported Host API subset; validate installation, signing, the real Secret Store, desktop lifecycle, and production permissions in DBX itself.
### Local Acceptance Checklist
- Remove unused permissions and verify that declared permissions match actual calls.
- Test empty configuration, invalid credentials, timeouts, offline behavior, and Sidecar restarts.
- Test DBX light/dark themes and `zh-CN`/`en`; the host does not translate plugin UI text.
- Test reconnect, closing a workbench, opening the same workbench twice, and empty context.
- Check packaged paths, executable permissions, and that no development data or secrets enter the artifact.
## Complete Official Marketplace Flow
### Step 1: Publish Source and a Candidate Release
@@ -115,17 +448,61 @@ Create a version tag and GitHub Release in the plugin's source repository. The R
Candidate packages may live in GitHub Releases, a CDN, or object storage. Do not commit `.dbxp` binaries to Git history.
### Step 2: Open a Review Issue in dbx-store
### Step 2: Let dbx-store Create the Candidate PR
Open [`t8y2/dbx-store` Issues](https://github.com/t8y2/dbx-store/issues/new/choose), choose **Plugin submission**, and provide:
The `dbx-store` synchronizer periodically checks public plugin repositories registered in `automation/plugin-sources.json` with `autoUpdate: true`. It reads the newest Release's `release-candidates.json` and creates or updates exactly one:
- plugin ID, version, and publisher;
- source repository and exact tag;
- the HTTPS URL of `release-candidates.json`;
- permissions, data access, network access, and native-sidecar behavior;
- license, privacy policy, and support links.
```text
candidates/<plugin-id>.json
```
The Issue is the pre-signing review entrypoint. Do not submit the plugin source to `t8y2/dbx`, and do not invent an official `signingKeyId` yourself.
For an auto-updated plugin, authors **do not need to create a manual Issue or PR for every version**, and do not configure Marketplace signing secrets in the plugin repository. The source Release, candidate metadata, and an accessible source tag are the important inputs. For a first listing or an intentional Marketplace metadata change, follow the [`dbx-store` contribution guide](https://github.com/t8y2/dbx-store/blob/main/CONTRIBUTING.md).
`.dbx-store.json` is optional Marketplace metadata. Use it for first registration or deliberate changes to the store name, tags, license, homepage, or similar listing fields. A version-only release does not need a new `.dbx-store.json`; it is not the runtime Manifest and must never contain a private signing key.
Example:
```json
{
"name": "Example Files",
"description": "Browse files from an example service.",
"icon": "assets/plugin.svg",
"tags": ["files", "storage"],
"permissions": ["host.workbench"],
"source": "https://github.com/example/dbx-plugin-files",
"homepage": "https://github.com/example/dbx-plugin-files",
"license": "Apache-2.0",
"releaseNotes": "Initial release."
}
```
Allowed store fields are `name`, `description`, `icon`, `tags`, `permissions`, `source`, `homepage`, `license`, `releaseNotes`, and `localizations`. A package-relative `icon` is converted to an HTTPS URL under the source tag during synchronization. `autoUpdate` belongs to the repository registration in `dbx-store/automation/plugin-sources.json`, not this file.
Automation only creates or updates a candidate PR; it **never approves, signs, or merges** it. The PR targets [`t8y2/dbx-store`](https://github.com/t8y2/dbx-store) on `main`, not `t8y2/dbx`.
If the plugin is not registered for automatic synchronization, fork `dbx-store` and open **one** PR against `main` containing `publishers/<publisher-id>.json` for a first publisher submission and `candidates/<plugin-id>.json`. There is no separate submission Issue. Before signing, CI intentionally stays red with `open candidate(s) awaiting DBX Store signing`; the signing workflow writes the finalized catalog back to the PR and makes it green. Authors can submit a PR to register their public repository for synchronization or ask a maintainer to register it; no App private key belongs in the plugin repository.
The candidate's essential shape is below. It references unsigned assets from the plugin repository and must not contain an official `signingKeyId`:
```json
{
"schemaVersion": 1,
"id": "com.example.files",
"publisher": "example",
"version": "0.1.0",
"name": "Example Files",
"source": "https://github.com/example/dbx-plugin-files/tree/v0.1.0",
"license": "Apache-2.0",
"targets": [{
"target": "universal",
"url": "https://github.com/example/dbx-plugin-files/releases/download/v0.1.0/com.example.files-0.1.0-universal.dbxp",
"sha256": "<64 hexadecimal SHA-256 characters>",
"size": 123456
}]
}
```
For a new version, submit only the new version's candidate data. Never hand-edit generated `plugins/<plugin-id>.json` or `catalog/index.json`.
### Step 3: DBX Store Reviews and Signs
@@ -138,34 +515,11 @@ Maintainers review the source, Manifest, permissions, candidate SHA-256 values,
Plugin authors never receive the official repository private key.
### Step 4: Open the Listing PR Against dbx-store
### Step 4: Merge the Candidate PR
After receiving final signed artifact metadata, fork [`t8y2/dbx-store`](https://github.com/t8y2/dbx-store) and add or update:
After signing, the workflow writes finalized `plugins/<plugin-id>.json` and generated `catalog/index.json` back to the same candidate PR. It publishes the signed package, artifact metadata, and signing receipt to a `dbx-store` Release. Once checks pass, a maintainer reviews and merges the PR.
```text
publishers/<publisher-id>.json # only for a publisher's first submission
plugins/<plugin-id>.json
catalog/index.json # generated by validation
```
Run:
```bash
node scripts/validate.mjs
```
Then open the pull request with:
```text
Base repository: t8y2/dbx-store
Base branch: main
```
<Callout type="info">
A new plugin's final listing PR goes **only to `t8y2/dbx-store`**. A `t8y2/dbx` PR is for changes to the plugin platform itself, not for registering ordinary third-party plugin versions.
</Callout>
Do not include the following in a listing PR:
Do not include the following in a submission or update PR:
- `.dbxp` binaries;
- a copied plugin source tree;
@@ -177,10 +531,12 @@ Do not include the following in a listing PR:
Every update uses a new semantic version. Existing Release assets cannot be replaced:
1. Publish a new source tag and candidate Release in the plugin source repository.
2. Open a new Plugin submission Issue in `t8y2/dbx-store` and reference the existing plugin and new version.
3. Wait for the new candidates to complete review and repository signing.
4. Open a PR against `t8y2/dbx-store` that updates `plugins/<plugin-id>.json`.
1. Change the source and increment `manifest.json`'s `version`; never reuse a published version.
2. Publish a new source tag and candidate Release; existing Release assets are immutable.
3. Wait for the synchronizer to create or update `automation/plugin-release/<plugin-id>/<version>`.
4. A maintainer reviews, signs, and merges the PR; the catalog then contains the new version and target artifacts.
If the repository is not registered for automatic synchronization, submit `candidates/<plugin-id>.json` manually according to the `dbx-store` contribution guide. For an update, submit only the new version's candidate data; do not hand-edit generated `plugins/` or `catalog/` files.
Fix plugin-code problems in the plugin source repository. Only catalog metadata, review status, final URLs, hashes, sizes, and Marketplace copy belong in `dbx-store`.
@@ -196,6 +552,17 @@ DBX currently uses one repository signature:
Human review determines whether a plugin may appear in the Marketplace. Ed25519 repository signing ensures that the reviewed package was not replaced afterward. A signature does not replace source review and is not an operating-system sandbox for native sidecars.
Official Marketplace plugin authors must not run `keygen` or invent a `signingKeyId`. Private or custom repository operators can use the advanced CLI signing tool:
```bash
dbx-plugin keygen company.plugins.release
source .dbx-repository-signing-key.env
```
The command creates a protected 32-byte Ed25519 seed environment file (mode `0600` on Unix) and prints the Key ID and Base64 public key; never commit the private file. A custom repository must add the public key to its own DBX trust configuration. The official Marketplace private key remains in a protected `dbx-store` Environment Secret, while DBX ships the public key in its official trust list.
`artifact.json` fields `target`, `url`, `sha256`, and `size` bind the exact bytes. Official signing adds `signingKeyId` only afterward. Published assets are immutable; any byte change requires a new plugin version and another review.
## Related Documentation
- [JDBC Plugin and Database Drivers](/en/docs/plugins)
+2 -2
View File
@@ -11,7 +11,7 @@
插件可以在 `manifest.json` 中声明可选的 `source`(源码仓库)和 `homepage`(项目主页)。这两个字段会随插件包签名并在“已安装”页面本地显示,因此不依赖联网;市场目录可以重复这些字段用于展示未安装插件。
正式文档和仓库提交流程参考 [`docs/content/docs/plugin-development.cn.mdx`](../docs/content/docs/plugin-development.cn.mdx)。完整协议和贡献点参考 [`README.md`](./README.md),底层发布流程参考 [`RELEASING.md`](./RELEASING.md)。
完整的[中文插件开发文档](https://dbxio.com/cn/docs/plugin-development)覆盖 Manifest、权限、Host API、Sidecar、Svelte、调试和商店发布流程;对应的仓库源文件是 [`docs/content/docs/plugin-development.cn.mdx`](../docs/content/docs/plugin-development.cn.mdx)。完整协议和贡献点参考 [`README.md`](./README.md),底层发布流程参考 [`RELEASING.md`](./RELEASING.md)。
## 先说结论:SDK 不需要“启动”
@@ -360,7 +360,7 @@ cargo run --release \
- 每个候选包对应的 `.artifact.json`。
- 合并后的 `release-candidates.json`。
这些 `.dbxp` 不能直接作为官方商店安装地址。审核通过后,DBX Store 的受保护工作流生成最终签名包并发布到仓库控制的 Release/CDN。源码放在插件自己的 Git 仓库;`dbx-store` Git 仓库只保存商店元数据、最终下载地址、哈希、大小、仓库公钥和审核信息。
这些 `.dbxp` 不能直接作为官方商店安装地址。若插件仓库已登记 `autoUpdate: true`,发布 Release 后 DBX Store 会自动创建或更新候选 PR;未登记时,开发者手动向 [`t8y2/dbx-store`](https://github.com/t8y2/dbx-store) 提交一个候选 PR 即可,不需要先创建 Issue。审核通过后,DBX Store 的受保护工作流在同一个 PR 流程中生成最终签名包并更新目录。源码放在插件自己的 Git 仓库;`dbx-store` Git 仓库只保存商店元数据、最终下载地址、哈希、大小、仓库公钥和审核信息。
## 10. 常用命令
+1 -1
View File
@@ -2,7 +2,7 @@
DBX plugins are optional, versioned `.dbxp` packages. They can add native backend behavior, sandboxed workbench UI, saved connection types, and filesystem providers without increasing the base DBX installation size. Declarative extension metadata consumed at build time lives here as well.
New plugin developers can read the official [Chinese](../docs/content/docs/plugin-development.cn.mdx) or [English](../docs/content/docs/plugin-development.mdx) documentation. The lower-level Chinese CLI walkthrough remains available in [`GETTING_STARTED.zh-CN.md`](./GETTING_STARTED.zh-CN.md).
New plugin developers can read the official [Chinese documentation](https://dbxio.com/cn/docs/plugin-development) or [English documentation](https://dbxio.com/en/docs/plugin-development). The source files are also available as [Chinese MDX](../docs/content/docs/plugin-development.cn.mdx) and [English MDX](../docs/content/docs/plugin-development.mdx); the lower-level Chinese CLI walkthrough remains available in [`GETTING_STARTED.zh-CN.md`](./GETTING_STARTED.zh-CN.md).
Install the precompiled development CLI without cloning or compiling DBX:
+1 -1
View File
@@ -2,7 +2,7 @@
Creates and packages DBX plugins as frontend-only universal bundles or full-stack projects with Rust or Go sidecars. The CLI also includes a Svelte + Vite workbench starter.
For a complete Chinese walkthrough covering installation, templates, local DBX testing, signing, and publishing, see [`../../GETTING_STARTED.zh-CN.md`](../../GETTING_STARTED.zh-CN.md).
For the complete [English plugin development guide](https://dbxio.com/en/docs/plugin-development), see the [Chinese quickstart](../../GETTING_STARTED.zh-CN.md) for a step-by-step walkthrough covering installation, templates, local DBX testing, signing, and publishing.
```bash
npm install --global @dbx-app/plugin-cli
+3 -1
View File
@@ -1824,7 +1824,9 @@ mod tests {
assert!(directory.join(if template == ProjectTemplate::Svelte { "index.html" } else { "ui/index.html" }).is_file());
assert!(directory.join(".github/workflows/plugin-release.yml").is_file());
let readme = std::fs::read_to_string(directory.join("README.md")).unwrap();
assert!(readme.contains("Plugin submission Issue"));
assert!(readme.contains("autoUpdate: true"));
assert!(readme.contains("submission Issue is not required"));
assert!(readme.contains("https://dbxio.com/en/docs/plugin-development"));
assert!(readme.contains("t8y2/dbx-store:main"));
assert!(readme.contains("Do not submit ordinary plugin source to `t8y2/dbx`"));
assert!(std::fs::read_to_string(directory.join(".gitignore"))
+4 -3
View File
@@ -4,6 +4,8 @@
Generated by `dbx-plugin create` with the **{{TEMPLATE_LABEL}}** template.
For the complete [English plugin development guide](https://dbxio.com/en/docs/plugin-development), including manifest fields, Host API, sidecars, debugging, packaging, and marketplace publishing, see the [Chinese quickstart](../../../../GETTING_STARTED.zh-CN.md).
## Develop
With Node.js 22+ and the backend compiler installed, run the browser development host without DBX:
@@ -25,9 +27,8 @@ The command builds the native backend for the current host, stages `manifest.jso
## Release
1. Publish a GitHub Release. The generated workflow builds unsigned candidates for every target.
2. Open a **Plugin submission Issue** in `t8y2/dbx-store` with the source tag and `release-candidates.json` URL.
3. After review, DBX Store signs approved candidates with the official repository key and publishes the installable assets.
4. Open the final catalog PR against **`t8y2/dbx-store:main`** using the signed artifact metadata.
2. If this repository is registered with `autoUpdate: true`, DBX Store automatically creates or updates a candidate PR. Otherwise, open one candidate PR against **`t8y2/dbx-store:main`** with the release and `release-candidates.json` URL; a submission Issue is not required.
3. After review, DBX Store signs approved candidates with the official repository key and updates the same PR with the installable assets and signed metadata.
Source code and unsigned candidates stay in this repository. DBX users install the DBX Store-signed assets exposed by the official catalog.
+4 -3
View File
@@ -4,6 +4,8 @@
Generated by `dbx-plugin create` with the **{{TEMPLATE_LABEL}}** template. This project has no native backend and produces one cross-platform `universal` package.
For the complete [English plugin development guide](https://dbxio.com/en/docs/plugin-development), including manifest fields, Host API, debugging, packaging, and marketplace publishing, see the [Chinese quickstart](../../../../GETTING_STARTED.zh-CN.md).
## Develop
Edit `ui/index.html` and run the browser development host with Node.js 22+:
@@ -25,9 +27,8 @@ The command stages `manifest.json`, `assets/`, and `ui/`, then writes an unsigne
## Release
1. Publish a GitHub Release. The generated workflow builds an unsigned universal candidate.
2. Open a **Plugin submission Issue** in `t8y2/dbx-store` with the source tag and `release-candidates.json` URL.
3. After review, DBX Store signs the approved candidate with the official repository key and publishes the installable asset.
4. Open the final catalog PR against **`t8y2/dbx-store:main`** using the signed artifact metadata.
2. If this repository is registered with `autoUpdate: true`, DBX Store automatically creates or updates a candidate PR. Otherwise, open one candidate PR against **`t8y2/dbx-store:main`** with the release and `release-candidates.json` URL; a submission Issue is not required.
3. After review, DBX Store signs the approved candidate with the official repository key and updates the same PR with the installable asset and signed metadata.
Source code and the unsigned candidate stay in this repository. DBX users install the DBX Store-signed asset exposed by the official catalog.
+3 -1
View File
@@ -4,6 +4,8 @@
Generated by `dbx-plugin create` with the **Svelte + Vite (universal)** template.
For the complete [English plugin development guide](https://dbxio.com/en/docs/plugin-development), including manifest fields, Svelte/Vite integration, Host API, debugging, packaging, and marketplace publishing, see the [Chinese quickstart](../../../../GETTING_STARTED.zh-CN.md).
## Develop
Install dependencies and run the browser development host:
@@ -19,4 +21,4 @@ Build an unsigned universal candidate with `dbx-plugin package .`.
## Release
Publish the unsigned candidate for review, then open a **Plugin submission Issue** and submit the approved signed artifact metadata to `t8y2/dbx-store:main`. DBX Store signs approved official releases and publishes the installable package. Do not submit ordinary plugin source to `t8y2/dbx`; that repository accepts plugin host, SDK, CLI, schema, documentation, and official-example changes.
Publish the unsigned candidate for review. If this repository is registered with `autoUpdate: true`, DBX Store automatically creates or updates a candidate PR; otherwise, submit one candidate PR to `t8y2/dbx-store:main` with the release and artifact metadata. A submission Issue is not required. DBX Store signs approved official releases and updates the same PR with the installable package. Do not submit ordinary plugin source to `t8y2/dbx`; that repository accepts plugin host, SDK, CLI, schema, documentation, and official-example changes.