docs(plugin): expand development guide and README links
This commit is contained in:
9 files changed
+830
-90
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user