@aiot-nexus/plugin-sdk 2.1.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +197 -90
  2. package/dev-host/main.js +366 -505
  3. package/dist/.generated/plugin-framework-version.d.ts +1 -1
  4. package/dist/.generated/plugin-framework-version.js +1 -1
  5. package/dist/.generated/plugin-manifest.schema.json +3 -2
  6. package/dist/browser/frame-client.d.ts +1 -0
  7. package/dist/browser/frame-client.mjs +121 -13
  8. package/dist/src/browser/frame-client.d.ts +1 -0
  9. package/dist/src/browser/frame-client.js +121 -13
  10. package/dist/src/cli/run.js +81 -6
  11. package/dist/src/cli/update-notice.js +1 -1
  12. package/dist/src/dev.js +3 -3
  13. package/dist/src/{electron-facade.js → electron-namespaces.js} +2 -3
  14. package/dist/src/index.d.ts +2 -2
  15. package/dist/src/index.js +3 -3
  16. package/dist/src/nexus-dev-control.d.ts +1 -1
  17. package/dist/src/nexus-dev-control.js +2 -2
  18. package/dist/src/nexus-dev.d.ts +0 -3
  19. package/dist/src/nexus-dev.js +118 -53
  20. package/dist/src/scaffold.js +6 -5
  21. package/dist/src/sdk-info.d.ts +17 -0
  22. package/dist/src/sdk-info.js +23 -0
  23. package/dist/src/types.d.ts +15 -98
  24. package/dist/src/upgrade.d.ts +44 -0
  25. package/dist/src/upgrade.js +1189 -0
  26. package/dist/src/validation.js +9 -0
  27. package/package.json +2 -2
  28. package/scripts/sdk-update-check.js +1 -1
  29. package/templates/minimal/.vscode/launch.json +1 -1
  30. package/templates/minimal/README.md.tmpl +18 -17
  31. package/templates/minimal/src/main.ts.tmpl +5 -4
  32. package/templates/sqlite/.vscode/launch.json +1 -1
  33. package/templates/sqlite/README.md.tmpl +15 -13
  34. package/templates/sqlite/src/main.ts.tmpl +24 -20
  35. package/dev-host/plugin-host.js +0 -4
  36. package/dist/internal/src-electron/main/core/runtime-result.js +0 -42
  37. package/dist/internal/src-electron/main/plugins/plugin-electron-facade-broker.js +0 -890
  38. package/dist/internal/src-electron/plugin-host-bootstrap.js +0 -417
  39. package/dist/internal/src-electron/shared/contracts.js +0 -2
  40. package/dist/internal/src-electron/shared/plugin-electron-facade-client.js +0 -181
  41. package/dist/internal/src-electron/shared/plugin-electron-facade-descriptors.js +0 -70
  42. package/dist/internal/src-electron/shared/plugin-electron-facade.js +0 -243
  43. package/dist/internal/src-electron/shared/plugin-rpc.js +0 -245
  44. /package/dist/src/{electron-facade.d.ts → electron-namespaces.d.ts} +0 -0
package/README.md CHANGED
@@ -1,55 +1,73 @@
1
1
  # @aiot-nexus/plugin-sdk
2
2
 
3
- > 版本适用性:当前包版本为 `2.1.1`,面向 Framework API `2.1.0` 的 v2 worker/RPC 模型,并兼容仍声明 `2.0.x` 的插件。1.x SDK 和同步 Host API 已停止适用,但其历史语义保留在本文末尾和 [v1 到 v2 迁移说明](../docs/plugin-framework-v1-v2-migration.md);2.x 不提供 1.x 运行模型兼容层。
3
+ > 当前 SDK 包版本为 `3.1.0`,Framework API 为 `3.1.0`,manifest schema 保持 `1`。Framework 3 将可信插件的 `mainEntry` 直接加载到 Nexus Electron 主进程;它不是安全沙箱。PluginFrame iframe 仍保持 sandbox、无 Node/Electron 访问。宿主运行模型见 [Framework v3 架构](../docs/plugin-framework-v3-architecture.md)。
4
4
 
5
- AIoT Nexus 独立插件开发包,包含公开 TypeScript 契约、PluginFrame 浏览器客户端和 `nexus-plugin` 命令行工具。
5
+ 本包提供公开 TypeScript 契约、PluginFrame 浏览器客户端和 `nexus-plugin` CLI。v2 项目升级前请先阅读 [v2 到 v3 迁移说明](../docs/plugin-framework-v2-v3-migration.md)。完整开发流程见 [独立插件开发指南](../docs/standalone-plugin-development-guide.md)。
6
6
 
7
- 通信边界、`postMessage` 消息格式、`invokeMain` 请求/响应、分层超时和 worker 终止策略、长任务轮询示例见仓库文档:[插件 IPC 与事件通知示例](../docs/plugin-ipc-event-notification-example.md)。文件选择、Node 文件读写、插件窗口、IPC 和 Monaco worker 示例见 [Electron Facade 使用示例](../docs/plugin-electron-facade-examples.md)。Facade 的原生 Electron Event 首参是只读 `PluginElectronFacadeEventSnapshot`,不提供跨异步 RPC 无法成立的 `preventDefault()`。当前 SDK 提供 `onContextChanged()` 用于语言、主题和启动 context 更新,但不提供通用业务事件订阅 API。
7
+ ## 兼容基线
8
8
 
9
- ## 环境
9
+ | 项目 | 固定值 |
10
+ | --- | --- |
11
+ | SDK | `3.1.0` |
12
+ | Framework API | `3.1.0` |
13
+ | Manifest schema | `1` |
14
+ | Node 安装和构建 | `>=16.20.0 <17` |
15
+ | Electron | `22.3.27` |
16
+ | TypeScript | `4.9.4` |
17
+ | 目标系统 | Windows 7 x64 |
10
18
 
11
- - Node.js `>=16.20.0 <17`
12
- - npm `>=6.13.4`
13
- - 插件框架 API `2.1.0`
14
- - manifest schema `1`
19
+ 不要为了新 Electron API、类型或依赖升级这些兼容锁。插件原生模块必须为 Electron 22 ABI 重建。
15
20
 
16
- ## 命令
21
+ ## CLI
17
22
 
18
23
  ```bash
19
- npm run build
20
- npm test
21
- npm run pack:sdk
22
- npm run publish:sdk
23
-
24
- nexus-plugin info
24
+ nexus-plugin info [--json]
25
25
  nexus-plugin validate <plugin-package-directory>
26
26
  nexus-plugin create <plugin-id> --template minimal|sqlite --output <parent-directory>
27
+ nexus-plugin upgrade [plugin-project-directory] [--check] [--sdk-source <version-or-local-tgz>]
27
28
  nexus-plugin dev [plugin-project-directory]
28
29
  nexus-plugin dev:web [plugin-project-directory]
29
30
  nexus-plugin dev:nexus [plugin-project-directory] --host <nexus.exe-or-directory>
30
31
  ```
31
32
 
32
- `npm run pack:sdk` 将离线安装包写入 `package/`。该目录是本地构建产物,不进入 Git。`npm run publish:sdk` 发布到官方 npm registry;`prepack` 会强制运行测试、构建并核对实际 npm 文件清单,缺少任一公开入口时拒绝发布。
33
+ 仓库内 SDK 命令:
33
34
 
34
- ## SDK 版本提示
35
+ ```bash
36
+ npm run build
37
+ npm test
38
+ npm run pack:sdk
39
+ npm run publish:sdk
40
+ ```
41
+
42
+ `pack:sdk` 把离线 tgz 写入 `plugin-sdk/package/`。`publish:sdk` 的 `prepack` 会运行测试、构建和公开文件清单验证。安装阶段和 CLI 启动阶段会非阻塞检查 SDK 更新;设置 `NEXUS_PLUGIN_SDK_UPDATE_CHECK=0` 或 `NO_UPDATE_NOTIFIER=1` 可关闭检查。检查失败不会阻断安装、构建或调试,版本通知本身不会修改依赖;只有显式执行 `upgrade` 才会进入升级事务。
35
43
 
36
- SDK 提供两层非阻塞版本检查:
44
+ ## 升级插件工程 SDK
37
45
 
38
- - 安装阶段的 `postinstall` 会强制查询 `dist-tags.latest` 并写入当前用户缓存。npm 7+ 默认隐藏依赖包的 lifecycle 输出;需要看到安装阶段 notice 时使用 `npm install --foreground-scripts`。`npm install --ignore-scripts` 会跳过该阶段,但不影响后续 CLI 检查。
39
- - `nexus-plugin` 启动时先读取缓存。缓存新鲜期为 24 小时;缓存缺失或过期时在后台检查,不等待网络,不改变命令退出码。检测到普通更新时会显示:
46
+ 标准插件工程的 `nexus-plugin` 来自本地 `@aiot-nexus/plugin-sdk`,因此 CLI 和工程 SDK 是同一个依赖。旧工程应由目标版本的 CLI 执行升级,不能只手工修改 `package.json`,因为 lockfile 和 `node_modules` 仍可能保留旧版本。
40
47
 
41
- ```text
42
- npm notice Update available for @aiot-nexus/plugin-sdk: 2.1.1 -> 2.2.0
43
- npm notice Run npm install --save-dev @aiot-nexus/plugin-sdk@latest
44
- ```
48
+ registry 升级与只读预检:
45
49
 
46
- 检查会优先使用 npm 当前配置的 registry(包括私有 registry),主源失败后回退官方 npm registry;不会发送 token、密码或项目路径。离线、超时、registry 返回非法数据或缓存损坏都静默降级,安装、构建、调试和正式宿主启动不会被阻断。
50
+ ```bash
51
+ npx --package=@aiot-nexus/plugin-sdk@3.1.0 nexus-plugin upgrade . --check
52
+ npx --package=@aiot-nexus/plugin-sdk@3.1.0 nexus-plugin upgrade .
53
+ ```
47
54
 
48
- 大版本更新会额外提示先确认 Nexus Framework API 兼容性;检查不会自动修改 `package.json`、升级依赖或新增交互式命令。确认兼容后,推荐手动执行 `npm install --save-dev @aiot-nexus/plugin-sdk@latest`。
55
+ 发布前或本地 tgz:
49
56
 
50
- 可用环境变量:`NEXUS_PLUGIN_SDK_UPDATE_CHECK=0` 或 `NO_UPDATE_NOTIFIER=1` 会同时关闭安装和 CLI 检查。内部 SDK 打包环境也会自动跳过联网检查。
57
+ ```bash
58
+ npx --package=./aiot-nexus-plugin-sdk-3.1.0.tgz nexus-plugin upgrade . \
59
+ --sdk-source ./aiot-nexus-plugin-sdk-3.1.0.tgz
60
+ ```
51
61
 
52
- 使用本地 `--sdk-source ...tgz` 时,生成工程依赖的是 tgz 内记录的 SDK 版本;安装阶段仍可能按 npm registry 写入该版本的缓存,CLI 后续按“当前版本 + registry”读取。完全离线开发可设置上述禁用变量,或接受检查静默失败;本地 SDK 不会自动替换正式 npm SDK,也不会因为有新版本自动升级。
62
+ CI 可以追加 `--json` 获得 `up-to-date`、`upgrade-available`、`upgraded`、`rolled-back` 或 `blocked`。`--allow-major` 只解除 major 保护,不会改写业务源码;跨 major 必须先阅读迁移指南。`--skip-project-checks` 只跳过项目 typecheck/build,安装后的 SDK、package 和 lockfile 一致性仍会验证。
63
+
64
+ 升级器直接修改的范围只有 SDK 依赖、`package-lock.json`、`node_modules` 和命令管理的 SDK tgz。本地包会保存为 `vendor/aiot-nexus-plugin-sdk-<version>.tgz`,下一次本地升级会安全清理上一份由命令管理的版本化归档。Electron、TypeScript、`@types/node` 和 Node engine 只检查不修改;manifest、模板、README、构建脚本和业务依赖不会被覆盖。
65
+
66
+ 修改前,命令会在项目根目录创建 `.nexus-plugin-upgrade-recovery/`,持久备份 package、lock、受管 tgz 和标准 `dist/<pluginId>`,并写入 `recovery.json` 阶段记录。成功或确定性回滚后该目录自动删除;硬中断、回滚重装失败或清理失败时保留,后续 upgrade 会停止并要求先按 journal 恢复。不要在项目恢复并完成验证前删除它。
67
+
68
+ 安装或项目验证失败时恢复备份,并在原 lockfile 存在时运行 `npm ci`。没有原 lockfile 时会运行 `npm install` 并恢复原 SDK,但无法证明完整传递依赖树与升级前一致,因此返回 `blocked` 并保留恢复数据。标准脚手架 build 的 `dist/<pluginId>` 在回滚范围内;自定义 typecheck/build 若修改其他路径,这些副作用不属于升级事务,项目必须自行管理。
69
+
70
+ SDK `3.0.2` 升级到 `3.1.0` 后,原有 `hostApiVersion: 3.0.0` 可以保持不变。只有实际使用 `showHostStatusBar` 等 3.1 专属字段时,才把 manifest 声明提升到 `3.1.0`。
53
71
 
54
72
  ## 创建插件
55
73
 
@@ -61,91 +79,180 @@ nexus-plugin create my-frame-plugin \
61
79
  --install
62
80
  ```
63
81
 
64
- - `--template` 默认为 `minimal`,也可选择带 `better-sqlite3` 的 `sqlite`。
65
- - `--output` 默认为当前目录,目标固定为 `<output>/<plugin-id>`。
66
- - `--sdk-source` 默认为当前 CLI 的 SDK 版本。也可传入严格的 `X.Y.Z` npm 版本、`@aiot-nexus/plugin-sdk@X.Y.Z` 或本地 `.tgz` 进行覆盖;本地包会复制到生成工程的 `vendor/`,并写入可迁移的相对 `file:` 依赖。
67
- - 默认不安装依赖;传入 `--install` 后执行 `npm install`。SQLite 模板在每次 `npm run build` 前强制执行 Electron 22.3.27 native rebuild。
68
- - 目标目录非空时命令以返回码 `2` 拒绝覆盖。生成或安装失败返回 `1`。
69
- - 两套模板均读取宿主 `PluginRendererContext.language` 和 `themeMode`,通过 `onContextChanged` 在同一 iframe 内响应运行时语言与主题切换,并内置 `zh-CN` / `en-US` renderer 文案;manifest 使用相同文化资源本地化宿主入口。模板页面自身不提供语言或主题选择控件。
82
+ - `minimal` 提供最小 PluginFrame、同步生命周期和原生 Electron 文件对话框示例。
83
+ - `sqlite` 提供 `better-sqlite3@8.7.0` 通用数据库浏览器。
84
+ - `--sdk-source` 可指定严格版本、`@aiot-nexus/plugin-sdk@X.Y.Z` 或本地 `.tgz`。
85
+ - 默认不安装依赖;`--install` 执行 `npm install`。
86
+ - 非空目标目录会被拒绝,不会覆盖现有项目。
87
+ - 两套模板都支持中英文、亮暗主题和同一 iframe 内的 context 更新。
70
88
 
71
- 默认来源从 npm registry 安装。离线开发时先执行 `npm run pack:sdk`,再通过 `--sdk-source /path/to/aiot-nexus-plugin-sdk-2.1.1.tgz` 指定本地包;CLI 会把本地包一并放入生成工程。
89
+ 离线示例:
72
90
 
73
- ### SQLite 数据库浏览器模板
91
+ ```bash
92
+ npm run pack:sdk
93
+ nexus-plugin create my-frame-plugin --sdk-source ./package/aiot-nexus-plugin-sdk-3.1.0.tgz --install
94
+ ```
74
95
 
75
- `sqlite` 模板默认打开插件数据目录中的 `plugin.db`,并保留 `plugin_records` 示例表。工具栏中的“打开数据库”通过 worker 调用 `context.host.electron.dialog.showOpenDialog()`;普通插件无需 `electronHostControl`。iframe 只收到当前数据库信息和参数化业务 operation,不接收 Electron 对象,不提供路径输入、任意 SQL、DDL、`ATTACH` 或 SQL 控制台。外部数据库只在用户选择且通过 `sqlite_schema` 验证后以读写模式替换当前连接,取消或打开失败时继续使用原连接。
96
+ 生成插件后,可以在 `src/manifest.json` 的业务入口中静态控制 Nexus 宿主状态栏。相关 manifest 片段如下,其余必填字段沿用脚手架生成值:
97
+
98
+ ```json
99
+ {
100
+ "hostApiVersion": "3.1.0",
101
+ "businessEntries": [
102
+ {
103
+ "businessId": "my-frame-plugin",
104
+ "titleKey": "plugin.displayName",
105
+ "iconKey": "extension",
106
+ "section": "home.main",
107
+ "order": 50,
108
+ "component": "PluginFrame",
109
+ "pageRoute": "my-frame-plugin",
110
+ "showHostStatusBar": false
111
+ }
112
+ ]
113
+ }
114
+ ```
76
115
 
77
- 最近选择的外部路径会原子写入插件数据目录的 `sqlite-browser-state.json`。下次启动会先尝试恢复;文件缺失、损坏或不再是 SQLite 时清除记录、回退 `plugin.db`,并在页面显示恢复提示。外部数据库不会执行示例表建表或 journal pragma。
116
+ 仅显式设置为 `false` 时隐藏。字段省略或设置为 `true` 时显示;离开该业务入口后,宿主状态栏自动恢复。该字段要求 `hostApiVersion >= 3.1.0`。
117
+
118
+ ## Framework 3.1 mainEntry
119
+
120
+ `mainEntry` 是可信 Electron 主进程代码,可直接导入 Electron、Node 内置模块和插件本地依赖:
121
+
122
+ ```ts
123
+ import { dialog } from "electron";
124
+ import { promises as fs } from "fs";
125
+ import type { PluginMainActivationContext } from "@aiot-nexus/plugin-sdk";
126
+
127
+ const maxTextBytes = 1024 * 1024;
128
+
129
+ export function activate(context: PluginMainActivationContext): void {
130
+ const registered = context.host.registerRendererMethod("editor.chooseText", async () => {
131
+ const selection = await dialog.showOpenDialog({
132
+ properties: ["openFile"],
133
+ filters: [{ name: "Text", extensions: ["txt", "md"] }]
134
+ });
135
+ if (selection.canceled || selection.filePaths.length === 0) {
136
+ return { canceled: true };
137
+ }
138
+ const contentResult = await readTextFile(selection.filePaths[0]);
139
+ return contentResult.ok
140
+ ? { canceled: false, content: contentResult.value }
141
+ : contentResult;
142
+ });
143
+ if (!registered.ok) {
144
+ throw new Error(registered.error.message);
145
+ }
146
+ }
147
+
148
+ export function deactivate(): void {
149
+ // 同步注销 ipcMain/protocol/shortcut/timer,并关闭窗口、数据库和子进程。
150
+ }
151
+
152
+ async function readTextFile(filePath: string) {
153
+ const file = await fs.open(filePath, "r");
154
+ try {
155
+ const stat = await file.stat();
156
+ if (!stat.isFile() || stat.size > maxTextBytes) {
157
+ return failure("FileTooLarge", "Select a regular file no larger than 1 MiB.");
158
+ }
159
+ const buffer = Buffer.alloc(maxTextBytes + 1);
160
+ let bytesRead = 0;
161
+ while (bytesRead < buffer.length) {
162
+ const chunk = await file.read(buffer, bytesRead, buffer.length - bytesRead, bytesRead);
163
+ if (chunk.bytesRead === 0) break;
164
+ bytesRead += chunk.bytesRead;
165
+ }
166
+ return bytesRead > maxTextBytes
167
+ ? failure("FileTooLarge", "File grew beyond 1 MiB while reading.")
168
+ : { ok: true as const, value: buffer.subarray(0, bytesRead).toString("utf8") };
169
+ } finally {
170
+ await file.close();
171
+ }
172
+ }
173
+
174
+ function failure(code: string, message: string) {
175
+ return { ok: false as const, error: { code, message } };
176
+ }
177
+ ```
78
178
 
79
- 模板按 `table_list` / `table_xinfo` 动态显示普通用户表、列类型、SQLite affinity、主键、NULL、默认值和生成列。新增字段可选“使用默认值 / NULL / 输入值”,编辑字段可选“不修改 / NULL / 输入值”;INTEGER 使用精确十进制整数,REAL 必须有限,NUMERIC 可输入整数、浮点或文本,BLOB 输入偶数长度十六进制并按解码后的实际字节数计入 1 MiB/4 MiB 限制。普通 rowid 表优先用内建 rowid 定位;`WITHOUT ROWID` 表或 rowid 别名全部被遮蔽的表只回退到全部非空的单/复合主键。没有唯一 locator 的表仍可浏览和新增,超过 16 KiB 的主键 locator 也按只读行处理;更新和删除在事务内要求恰好命中一行。表格中的 TEXT 预览最多读取 16 KiB UTF-8 数据,BLOB 只读取总字节数和前 32 字节,避免把完整大字段复制进插件 worker 和 iframe。
179
+ 固定契约:
80
180
 
81
- 该模板刻意不依赖 Quasar,以保持独立插件依赖最小化,因此数据库网格使用原生 `<table>`,这是宿主默认 `QTable` 规则之外的 SDK 模板例外。页面仍包含分页、横向表格滚动、中英文、亮暗主题、busy/empty/error 状态和窄窗口布局。
181
+ - `activate(context)` 和 `deactivate(reason)` 必须同步完成,不能声明为 `async`,也不能返回 Promise。
182
+ - 声明为 `async` 的生命周期函数会在调用前被拒绝;普通函数若返回 Promise,函数体已经执行,但 Framework 不等待该 Promise,并将本次生命周期判定为不受支持。
183
+ - `context.host.*` 直接返回 `NexusResult<T>`,不需要 `await`。
184
+ - 注册的 operation handler 可以返回 Promise,可等待异步文件对话框、网络或其他用户交互。
185
+ - operation 默认没有业务超时。浏览器客户端 `requestTimeoutMs` 默认是 `0`;20 秒仅用于 iframe 初始化握手。
186
+ - 同步 Electron API 也能调用,但 `showOpenDialogSync()`、同步文件 I/O、长 CPU 任务会阻塞 Nexus 主进程、所有窗口和插件,应优先使用异步版本。
187
+ - Framework 不跟踪插件直接创建的 Electron/Node 资源。插件必须在 `deactivate` 中自行清理。
188
+ - Node 模块图和原生模块不能安全热卸载。已加载 `mainEntry`、依赖、原生模块或相关 manifest 变化必须重启 Nexus。
82
189
 
83
- ## 独立调试
190
+ Framework 3 不再提供 `context.host.electron` Facade,也没有 `electronHostControl` 权限。manifest 权限只描述 Host API 能力,不限制可信 `mainEntry` 直接调用 Electron/Node。SDK 保留 `createPluginElectronIpcChannel()` 和 `createPluginElectronProtocolScheme()` 作为命名约定帮助函数;宿主不会自动隔离或清理对应注册。
84
191
 
85
- 生成工程安装依赖后,可以完全在插件目录启动调试宿主:
192
+ 原生文件、窗口、IPC 与 Monaco 示例见 [Electron/Node 示例](../docs/plugin-electron-node-examples.md),支持矩阵和风险边界分别见 [兼容矩阵](../docs/plugin-electron-node-compatibility-matrix.md) 与 [安全边界](../docs/plugin-framework-v3-security-boundary.md)。
86
193
 
87
- ```bash
88
- npm run dev:web
89
- npm run dev
90
- npm run dev:debug
91
- ```
194
+ ## Renderer 边界
92
195
 
93
- - `npm run dev:web` 先运行模板独立的 `build:web`,只编译 Renderer 到 `.nexus-plugin-dev/web-package/<plugin-id>/`,再启动仅绑定 `127.0.0.1:4173` 的本地 HTTP 宿主并打开系统浏览器;它不执行 native rebuild、不复制原生运行时、不覆盖正式 `dist`,支持浏览器 DevTools 与源码映射。
94
- - 浏览器模式不加载 `mainEntry`,不创建插件 worker,也不提供 Electron Facade;`invokeMain` 会返回 `PluginCapabilityUnsupported`。设置仅在本次服务进程内保存,日志输出到 CLI。涉及插件 worker、SQLite、Node 或 Electron 时使用 `npm run dev`。
95
- - `npm run dev` 先构建和校验插件,再使用工程本地固定的 `electron@22.3.27` 启动 SDK 调试宿主;宿主渲染 UI,插件 `mainEntry` 在独立 `utilityProcess` worker 中加载,并使用与正式 Nexus 相同的 Electron Facade client、wire codec、Broker、权限分层和资源清理逻辑。Renderer DevTools 默认自动打开。
96
- - Electron 调试宿主的 `host.hostVersion` 默认是 SDK 构建时记录的 Nexus 应用版本;使用 `nexus-plugin dev . --host-version <X.Y.Z>` 可模拟其他应用版本,并按 manifest `minHostVersion` 拒绝不兼容组合。
97
- - `npm run dev:debug` 额外把 `--inspect-brk=9229` 传给插件 worker,可在 `src/main.ts` 激活阶段命中断点;宿主 UI 仍保持可启动。
98
- - 生成工程包含 `.vscode/launch.json`。在 VS Code 中选择 `Debug Nexus plugin` 可直接启动;若本机关闭了子进程自动附加,则先运行 `npm run dev:debug`,再选择 `Attach Nexus plugin worker (9229)`。renderer 断点在自动打开的 DevTools `Sources` 中设置。
99
- - Electron 调试构建会内联 `src/main.ts` 和 `src/renderer/app.ts` source map;浏览器 `build:web` 只内联 Renderer source map。普通 `npm run build` 不内联源码,不改变部署制品形态。模板在收到首个 Host context 前隐藏正文,应用语言和主题后再显示。
100
- - 调试数据保存在工程的 `.nexus-plugin-dev/`,该目录默认被忽略。关闭调试窗口会结束独立 Electron 进程;修改源码后重新运行调试命令。
196
+ PluginFrame iframe 不能直接获得 Node、Electron、数据库对象、任意文件路径或完整 `ipcRenderer`。Renderer 应通过 `client.invokeMain()` 调用经过命名和输入校验的业务 operation。
101
197
 
102
- 可直接使用的 CLI 选项:
198
+ 关键 CSP:
103
199
 
104
200
  ```text
105
- nexus-plugin dev [plugin-project-directory]
106
- [--no-build]
107
- [--inspect[=<port>] | --inspect-brk[=<port>]]
108
- [--host-version <X.Y.Z>]
109
- [--language <culture>]
110
- [--theme system|light|dark]
111
- [--surface business|tool|settings]
112
- [--contribution-id <id>]
113
- [--no-devtools]
201
+ script-src 'self'; style-src 'self' 'unsafe-inline'; worker-src 'self' blob:
114
202
  ```
115
203
 
116
- 调试宿主工具栏中的语言和主题选择器用于模拟 Nexus 主框架 context,不属于插件页面。除该工具栏外,插件 iframe 无边距、无边框地填满窗口剩余区域;宿主不根据插件上报的内容尺寸限制 iframe 宽高,Electron 调试窗口也不设置最小宽高。切换 context 后调试宿主向同一 iframe 重发框架 `2.1.0` 的 `init` 消息;SDK 将后续 `init` 作为 context 更新并原地刷新语言和主题,同时仍接受可选的 `context-changed` 消息。调试宿主还复用正式的 `nexus-plugin://` 资源协议、iframe sandbox、CSP、消息结构、operation 校验、Electron Facade 和当前 Host API 权限语义,但不包含 Nexus 的完整页面路由、Pinia 状态和应用级功能。`mainEntry` 的激活、deactivate、Host API 请求和 renderer operation 调用都通过异步 RPC 连接 worker;交付前仍需把 `dist/<plugin-id>/` 放入正式 Nexus 完成集成验证。
204
+ 这允许 Monaco 运行时注入样式和同源/Blob Worker,但仍阻止内联脚本、远程脚本和 Node iframe 访问。脚本、Monaco、语言包、字体和 worker 必须随插件作为同源资源交付。
117
205
 
118
- 浏览器 UI 命令支持 `--no-build`、`--no-open`、`--port <port>`,以及与 Electron 调试宿主相同的 `--language`、`--theme`、`--surface` 和 `--contribution-id` 参数。它使用普通 `http://127.0.0.1` iframe,不模拟 `nexus-plugin://` 协议或 Electron sandbox,因此只作为 UI 开发入口。Renderer-only 输出不会覆盖正式插件制品。
206
+ `createPluginFrameClient()` 的首次初始化默认等待 20 秒。业务请求默认不设超时;只有插件显式传入正数 `requestTimeoutMs` 时客户端才会主动超时。进度、取消和恢复仍建议使用 `taskId + getStatus + cancel`,但这是任务 UX 和资源管理策略,不是 worker 存活要求。通信契约见 [插件 IPC 与事件通知示例](../docs/plugin-ipc-event-notification-example.md)。
119
207
 
120
- ## 使用正式 Nexus 调试
208
+ ## SQLite 模板
121
209
 
122
- 已经安装并构建插件后,可直接把编译好的正式 Nexus 作为宿主:
210
+ `sqlite` 模板默认打开 `pluginDataDirectory/plugin.db` 并初始化 `plugin_records`。工具栏“打开数据库”由 `mainEntry` 直接调用 `dialog.showOpenDialog()`;iframe 只接收数据库信息和参数化 operation。
123
211
 
124
- ```powershell
125
- npm run dev:nexus -- --host D:\prj\tmp\aiot-nexus\dist\electron\nexus-win32-x64\nexus.exe
126
- ```
212
+ 模板支持:
127
213
 
128
- `dev:nexus` 首次启动会完成正式构建,通过 staging 原子部署到 `<exeDir>/Plugins/<pluginId>/`,再由 CLI 以 `--plugin-dev`、随机控制管道和会话令牌启动 Nexus。开发通道只在这次由 CLI 管理的进程中启用;普通双击或不带 `--plugin-dev` 启动时不会创建控制管道,也不会打开 DevTools。
214
+ - 最近外部数据库路径原子持久化和失效回退。
215
+ - 普通用户表发现、动态 schema、分页和 25/50/100 页大小。
216
+ - rowid、单/复合主键与 `WITHOUT ROWID` 定位。
217
+ - default/null/value 动态插入和 no-change/null/value 更新。
218
+ - 精确 INTEGER、有限 REAL、NUMERIC 文本兼容和 BLOB 十六进制。
219
+ - TEXT 16 KiB 预览、BLOB 字节数和前 32 字节预览。
220
+ - 单字段 1 MiB、单次提交 4 MiB 上限。
129
221
 
130
- | 修改范围 | 行为 |
131
- | --- | --- |
132
- | `src/renderer/**` | 只运行 `build:web` 并替换 renderer;宿主 PID 不变,iframe 重新完成 `ready/init`。 |
133
- | `src/manifest.json` 的展示、贡献点等安全字段 | 构建 renderer、原子替换 manifest 后重扫插件目录;宿主 PID 不变。 |
134
- | `src/main.ts`、依赖、原生模块或 worker 相关 manifest 字段 | 完整构建成功后先停止插件 worker,原子替换完整插件包,再在同一 Nexus 进程中启动新 worker;宿主 PID 不变。 |
222
+ 模板不暴露任意 SQL、DDL、`ATTACH`、路径输入或 SQL 控制台。它刻意不依赖 Quasar,因此数据库网格使用原生 `<table>`;这是独立、依赖最小化 SDK 模板对宿主 `QTable` 规则的明确例外。
135
223
 
136
- 更新构建或 worker 重载失败时保留上一个可运行部署,并回滚插件包后重新激活旧 worker。`mainEntry` 不通过清理 `require.cache` 热替换;每次包更新都执行 `deactivate`、替换包和新的 worker 激活,但不重启整个 Nexus。默认打开 Renderer DevTools;可传 `--no-devtools`。`--inspect[=<port>]` 和 `--inspect-brk[=<port>]` 调试的是插件 worker,贡献点选择支持 `--surface` 与 `--contribution-id`。按 `Ctrl+C` 停止 CLI 时,CLI 会先请求插件 worker 关闭,再关闭其启动的正式宿主。
224
+ ## 调试模式
137
225
 
138
- ## v1 SDK 历史说明(不适用于当前包)
226
+ | 命令 | mainEntry | Electron/Node | 用途 |
227
+ | --- | --- | --- | --- |
228
+ | `npm run dev:web` | 不执行 | 不可用 | 只调试 Renderer;`invokeMain` 返回 `PluginCapabilityUnsupported`。 |
229
+ | `npm run dev` | Electron 调试宿主主进程直接加载 | 可用 | 调试真实生命周期、对话框、SQLite 和插件窗口。 |
230
+ | `npm run dev:debug` | 同 `dev` | 可用 | Electron 主进程以 `--inspect-brk=9229` 启动。 |
231
+ | `npm run dev:nexus -- --host ...` | 正式 Nexus 主进程直接加载 | 可用 | 最终集成和监听更新。 |
139
232
 
140
- SDK 1.x 对应 Framework API `1.0.0`:`mainEntry` 在 Electron 主进程中加载,Host API 直接返回同步 `NexusResult<T>`,完整插件包变化需要重启 Nexus。旧离线包名使用 `aiot-nexus-plugin-sdk-1.1.1.tgz`,旧调试目标是 `Attach Nexus plugin main (9229)`。
233
+ Electron 调试壳仅保留 context 工具栏;插件 iframe 无边距、无边框,填满剩余窗口,不设置最小宽高,也不根据内容上报限制尺寸。Renderer DevTools 默认打开。
141
234
 
142
- | v1 项目 | 历史行为 | v2 替代 |
143
- | --- | --- | --- |
144
- | `npm run dev` | Electron 调试宿主在主进程加载 `mainEntry` | 调试宿主创建独立插件 worker |
145
- | `npm run dev:debug` | `--inspect-brk=9229` 暂停 Electron 主进程 | inspect 端口连接插件 worker,宿主 UI 可独立启动 |
146
- | `npm run dev:nexus` 完整包变化 | 退出旧 Nexus、替换包、启动新 Nexus,PID 改变 | 停止目标 worker、替换包、启动新 worker,Nexus PID 不变 |
147
- | Host API 调用 | 直接获得 `NexusResult<T>` | `await` `Promise<NexusResult<T>>` |
148
- | Renderer-only 调试 | 不执行 `mainEntry`,`invokeMain` 返回 unsupported | 该限制在 v2 继续成立 |
149
- | 更新失败 | 恢复旧包并重新启动宿主 | 恢复旧包和旧 worker,不重启 Nexus |
235
+ VS Code 模板提供 `Debug Nexus plugin` 和 `Attach Nexus plugin main process (9229)`。Renderer 断点在 DevTools Sources 中设置。
150
236
 
151
- 这些约定只用于阅读旧插件和历史报告。迁移到当前 SDK 时必须把 manifest 升到 2.x、异步等待 Host API、实现资源关闭,并改用插件 worker 调试和生命周期;不能只修改 npm 包版本而保留同步调用。
237
+ `dev:nexus` 更新规则:
238
+
239
+ | 修改范围 | 处理方式 |
240
+ | --- | --- |
241
+ | `src/renderer/**` | 只构建 Renderer 并重载 iframe,Nexus PID 不变。 |
242
+ | 无 `mainEntry` 插件的安全 manifest 展示/贡献字段 | 原子替换 manifest 并重扫,PID 不变。 |
243
+ | 有 `mainEntry` 插件的 manifest 变化 | 完整构建并重启 Nexus。 |
244
+ | `src/main.*`、依赖、原生模块、构建脚本 | 完整构建并重启 Nexus。 |
245
+
246
+ 构建失败保留旧部署;新宿主启动或就绪失败会恢复旧包和旧宿主。`--inspect` / `--inspect-brk` 调试的是 Nexus Electron 主进程,因此会同时暂停或暴露宿主代码。按 `Ctrl+C` 时 CLI 关闭自己启动的宿主。
247
+
248
+ ## 发布前检查
249
+
250
+ 1. `hostApiVersion` 为 `3.1.0`,manifest schema 仍为 `1`。
251
+ 2. 业务入口的 `showHostStatusBar` 可静态声明是否保留宿主状态栏;省略时默认保留,显式使用时要求 `hostApiVersion >= 3.1.0`。
252
+ 3. `activate/deactivate` 同步,operation handler 才使用异步。
253
+ 4. Renderer 只使用窄口径 operation,所有输入在 mainEntry 再校验。
254
+ 5. 窗口保持 `contextIsolation: true`、`sandbox: true`、`nodeIntegration: false`、`webSecurity: true`。
255
+ 6. IPC、protocol、快捷键、菜单和文件路径使用插件命名空间并检测冲突。
256
+ 7. `deactivate` 关闭窗口、Session 监听、IPC、protocol、快捷键、定时器、数据库和子进程。
257
+ 8. 没有同步长任务、无限载荷、任意命令/SQL/路径透传或日志敏感信息。
258
+ 9. 运行 `npm test`、插件构建、正式 Nexus 集成和 Windows 7 真机验收。