@aiot-nexus/plugin-sdk 2.1.1 → 3.0.2
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.
- package/README.md +151 -96
- package/dev-host/main.js +362 -504
- package/dist/.generated/plugin-framework-version.d.ts +1 -1
- package/dist/.generated/plugin-framework-version.js +1 -1
- package/dist/.generated/plugin-manifest.schema.json +1 -1
- package/dist/browser/frame-client.d.ts +1 -0
- package/dist/browser/frame-client.mjs +121 -13
- package/dist/src/browser/frame-client.d.ts +1 -0
- package/dist/src/browser/frame-client.js +121 -13
- package/dist/src/cli/run.js +2 -2
- package/dist/src/dev.js +3 -3
- package/dist/src/{electron-facade.js → electron-namespaces.js} +2 -3
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +2 -2
- package/dist/src/nexus-dev-control.d.ts +1 -1
- package/dist/src/nexus-dev-control.js +2 -2
- package/dist/src/nexus-dev.d.ts +0 -3
- package/dist/src/nexus-dev.js +118 -53
- package/dist/src/types.d.ts +14 -98
- package/package.json +1 -1
- package/templates/minimal/.vscode/launch.json +1 -1
- package/templates/minimal/README.md.tmpl +15 -14
- package/templates/minimal/src/main.ts.tmpl +5 -4
- package/templates/sqlite/.vscode/launch.json +1 -1
- package/templates/sqlite/README.md.tmpl +12 -10
- package/templates/sqlite/src/main.ts.tmpl +24 -20
- package/dev-host/plugin-host.js +0 -4
- package/dist/internal/src-electron/main/core/runtime-result.js +0 -42
- package/dist/internal/src-electron/main/plugins/plugin-electron-facade-broker.js +0 -890
- package/dist/internal/src-electron/plugin-host-bootstrap.js +0 -417
- package/dist/internal/src-electron/shared/contracts.js +0 -2
- package/dist/internal/src-electron/shared/plugin-electron-facade-client.js +0 -181
- package/dist/internal/src-electron/shared/plugin-electron-facade-descriptors.js +0 -70
- package/dist/internal/src-electron/shared/plugin-electron-facade.js +0 -243
- package/dist/internal/src-electron/shared/plugin-rpc.js +0 -245
- /package/dist/src/{electron-facade.d.ts → electron-namespaces.d.ts} +0 -0
|
@@ -17,16 +17,18 @@ The check follows the npm-configured registry, falls back to the official npm re
|
|
|
17
17
|
|
|
18
18
|
Every `npm run build` rebuilds `better-sqlite3@8.7.0` for Electron 22.3.27 with `@electron/rebuild@3.7.2` before packaging. `npm run rebuild:native` remains available as a standalone diagnostic command.
|
|
19
19
|
|
|
20
|
-
The deployable plugin package is `dist/__PLUGIN_ID__/`. For a manual installation, stop Nexus before replacing `Plugins/__PLUGIN_ID__/`, then start it again. During development, use `npm run dev:nexus` so the
|
|
20
|
+
The deployable plugin package is `dist/__PLUGIN_ID__/`. For a manual installation, stop Nexus before replacing `Plugins/__PLUGIN_ID__/`, then start it again. During development, use `npm run dev:nexus` so the CLI coordinates Nexus shutdown, atomic replacement, and restart for trusted `mainEntry` changes. Runtime data is stored in the host-provided plugin data directory and is not written into the plugin package.
|
|
21
21
|
|
|
22
|
-
The template is a schema-aware SQLite database browser. It opens `plugin.db` in the plugin data directory by default and initializes the `plugin_records` sample table there. **Open Database** calls `
|
|
22
|
+
The template is a schema-aware SQLite database browser. It opens `plugin.db` in the plugin data directory by default and initializes the `plugin_records` sample table there. **Open Database** calls Electron's directly imported `dialog.showOpenDialog()` from trusted `mainEntry`; it accepts a single existing SQLite file, including files without an extension. The renderer only displays the selected path and invokes parameterized operations. It never receives an Electron object and does not expose a path input, arbitrary SQL, DDL, `ATTACH`, or a SQL console.
|
|
23
23
|
|
|
24
|
-
## Electron
|
|
24
|
+
## Native Electron database dialog
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
Framework API 3.0 loads trusted `mainEntry` code in the Electron main process. The `sqlite.chooseDatabase` operation imports Electron 22 directly; the essential call in `src/main.ts` is:
|
|
27
27
|
|
|
28
28
|
```ts
|
|
29
|
-
|
|
29
|
+
import { dialog } from "electron";
|
|
30
|
+
|
|
31
|
+
const result = await dialog.showOpenDialog({
|
|
30
32
|
title: "Open SQLite database",
|
|
31
33
|
properties: ["openFile"],
|
|
32
34
|
filters: [
|
|
@@ -41,24 +43,24 @@ if (!result.canceled && result.filePaths[0]) {
|
|
|
41
43
|
}
|
|
42
44
|
```
|
|
43
45
|
|
|
44
|
-
For related workflows, use `properties: ["openDirectory", "createDirectory"]` to choose a folder or `
|
|
46
|
+
For related workflows, use `properties: ["openDirectory", "createDirectory"]` to choose a folder or `dialog.showSaveDialog()` to choose a save path. These calls belong in trusted `mainEntry`; the renderer should invoke a registered business operation such as `sqlite.chooseDatabase` through `client.invokeMain()` and receive only the result it needs. Native dialogs are unavailable in `npm run dev:web`; use `npm run dev` or a formal Nexus host.
|
|
45
47
|
|
|
46
48
|
External files are opened read/write only after a separate candidate connection can query `sqlite_schema`. Canceling the dialog or selecting a corrupt, locked, or inaccessible file keeps the current connection. The latest external path is atomically stored as `sqlite-browser-state.json` in the plugin data directory. Startup restores it when valid; otherwise the template clears the saved selection, falls back to `plugin.db`, and returns a visible notice. External databases do not receive the sample schema or journal pragma.
|
|
47
49
|
|
|
48
|
-
The table selector lists ordinary user tables from the `main` schema. Dynamic headers show declared type, SQLite affinity, primary-key order, NULL, default, and generated-column metadata. The editor supports default/NULL/value modes for inserts and unchanged/NULL/value modes for updates. INTEGER input uses exact decimal integers, REAL must be finite, NUMERIC accepts integer, real, or text values, and BLOB input uses even-length hexadecimal text. The 1 MiB field and 4 MiB row limits count decoded BLOB bytes rather than hexadecimal characters. Ordinary rowid tables prefer an unshadowed built-in rowid; `WITHOUT ROWID` tables, or tables that shadow every rowid alias, fall back only to primary-key columns that are all non-NULL. A table without a unique locator remains browsable and insertable, but update and delete are unavailable. Primary-key locators larger than 16 KiB are also treated as browse-only so a page cannot copy unbounded keys into the
|
|
50
|
+
The table selector lists ordinary user tables from the `main` schema. Dynamic headers show declared type, SQLite affinity, primary-key order, NULL, default, and generated-column metadata. The editor supports default/NULL/value modes for inserts and unchanged/NULL/value modes for updates. INTEGER input uses exact decimal integers, REAL must be finite, NUMERIC accepts integer, real, or text values, and BLOB input uses even-length hexadecimal text. The 1 MiB field and 4 MiB row limits count decoded BLOB bytes rather than hexadecimal characters. Ordinary rowid tables prefer an unshadowed built-in rowid; `WITHOUT ROWID` tables, or tables that shadow every rowid alias, fall back only to primary-key columns that are all non-NULL. A table without a unique locator remains browsable and insertable, but update and delete are unavailable. Primary-key locators larger than 16 KiB are also treated as browse-only so a page cannot copy unbounded keys into the trusted main process and iframe. Update and delete run in a transaction and must affect exactly one row. TEXT previews are capped at 16 KiB of UTF-8 data; BLOB previews show the byte length and first 32 bytes.
|
|
49
51
|
|
|
50
52
|
The renderer follows `context.language` and `context.themeMode` from Nexus and preserves unfinished editor values during in-place context updates. The plugin page has no language or theme selector; the controls shown by `npm run dev` belong to the debug host. Below that toolbar, the iframe has no host padding, border, minimum size, or content-driven height and fills the remaining host viewport. This SDK template deliberately has no Quasar dependency, so its data grid uses a native `<table>` as a dependency-minimized template exception. It still provides pagination, contained horizontal scrolling, light/dark themes, `en-US` / `zh-CN`, and busy/empty/error states. Edit renderer messages in `src/renderer/app.ts`; edit host-shell titles and descriptions in `src/manifest.json` resources.
|
|
51
53
|
|
|
52
54
|
The current SDK has no generic business-event subscription API. For a long-running operation, register a fast `job.start` operation that returns a `taskId`, then expose `job.getStatus` and optional `job.cancel` for renderer polling. See the Nexus repository's plugin IPC and event notification example for the full contract and code.
|
|
53
55
|
|
|
54
|
-
Use `npm run dev:web` for renderer-only layout, styling, localization, and responsive checks in the system browser. It runs `build:web`, writes an isolated package under `.nexus-plugin-dev/web-package/`, and does not rebuild or load `better-sqlite3`, execute `mainEntry`,
|
|
56
|
+
Use `npm run dev:web` for renderer-only layout, styling, localization, and responsive checks in the system browser. It runs `build:web`, writes an isolated package under `.nexus-plugin-dev/web-package/`, and does not rebuild or load `better-sqlite3`, execute `mainEntry`, provide Node or Electron main-process APIs, copy native runtime files, or overwrite the deployable `dist` package. It builds with source maps, serves only on `127.0.0.1:4173`, and opens the browser automatically. The page explicitly reports `PluginCapabilityUnsupported`; file selection, table loading, and CRUD require `npm run dev` or a formal Nexus host. Pass `-- --no-open` or `-- --port <port>` when needed.
|
|
55
57
|
|
|
56
|
-
Use `npm run dev:nexus -- --host C:\path\to\nexus.exe` to run the plugin inside a compiled, packaged Nexus. The initial deployment and every full package replacement are atomic. Renderer-only and safe manifest updates keep the Nexus PID; `mainEntry`, dependency, native-module, or
|
|
58
|
+
Use `npm run dev:nexus -- --host C:\path\to\nexus.exe` to run the plugin inside a compiled, packaged Nexus. The initial deployment and every full package replacement are atomic. Renderer-only and safe manifest updates keep the Nexus PID; `mainEntry`, dependency, native-module, or restart-sensitive manifest changes run the complete build (including the Electron 22.3.27 native rebuild), stop Nexus, replace the package, and start a new Nexus process. The database remains under the host-provided plugin data directory across host restarts. Failed builds keep the previous deployment, and launch or readiness failures roll the package back and restore the previous host. Add `--no-devtools`, `--inspect[=<port>]`, `--inspect-brk[=<port>]`, `--surface`, or `--contribution-id` after the second `--` as needed.
|
|
57
59
|
|
|
58
60
|
When this project was created from a local SDK archive, `vendor/aiot-nexus-plugin-sdk.tgz` is part of the source project and must be kept when the project is moved or committed.
|
|
59
61
|
|
|
60
62
|
For a local SDK archive, the tgz version remains the plugin's current SDK version for comparison. It is not replaced automatically by a newer npm package; fully offline work can disable checks with the environment variables above.
|
|
61
63
|
|
|
62
|
-
For
|
|
64
|
+
For trusted-main breakpoints, open this plugin directory in VS Code, set breakpoints in `src/main.ts`, and run `Debug Nexus plugin`. To attach manually, start `npm run dev:debug` and select `Attach Nexus plugin main process (9229)`. Renderer DevTools opens automatically, with `src/renderer/app.ts` available through the development source map. The default debug build also runs the Electron 22.3.27 native rebuild before launch. Use `npm run dev -- --no-build` only when intentionally reusing a compatible current `dist` output.
|
|
63
65
|
|
|
64
66
|
The independent host stores the SQLite database and settings under `.nexus-plugin-dev/data/` and mirrors the PluginFrame protocol and current Host API surface. Its simulated Nexus application version defaults to the version recorded by the SDK build; pass `npm run dev -- --host-version <X.Y.Z>` to exercise another compatibility branch. Run the deployable `dist/__PLUGIN_ID__/` in Nexus before delivery because the standalone host does not reproduce the full application shell or routing state.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import Database = require("better-sqlite3");
|
|
4
|
+
import { app, dialog } from "electron";
|
|
4
5
|
import type {
|
|
5
6
|
NexusResult,
|
|
6
7
|
PluginMainActivationContext,
|
|
@@ -149,24 +150,24 @@ let stateFilePath = "";
|
|
|
149
150
|
let databaseNotice: string | undefined;
|
|
150
151
|
let databaseSelectionPending = false;
|
|
151
152
|
|
|
152
|
-
export
|
|
153
|
+
export function activate(context: PluginMainActivationContext): void {
|
|
153
154
|
activationContext = context;
|
|
154
155
|
fs.mkdirSync(context.host.pluginDataDirectory, { recursive: true });
|
|
155
156
|
defaultDatabasePath = path.join(context.host.pluginDataDirectory, defaultDatabaseName);
|
|
156
157
|
stateFilePath = path.join(context.host.pluginDataDirectory, stateFileName);
|
|
157
158
|
activeDatabase = openInitialDatabase();
|
|
158
159
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const applicationName =
|
|
169
|
-
const logResult =
|
|
160
|
+
register(context, "sqlite.getDatabaseInfo", handled(getDatabaseInfo));
|
|
161
|
+
register(context, "sqlite.chooseDatabase", handledAsync(chooseDatabase));
|
|
162
|
+
register(context, "sqlite.useDefaultDatabase", handledAsync(useDefaultDatabase));
|
|
163
|
+
register(context, "sqlite.listTables", handled(listTables));
|
|
164
|
+
register(context, "sqlite.readTable", handled(readTable));
|
|
165
|
+
register(context, "sqlite.insertRow", handled(insertRow));
|
|
166
|
+
register(context, "sqlite.updateRow", handled(updateRow));
|
|
167
|
+
register(context, "sqlite.deleteRow", handled(deleteRow));
|
|
168
|
+
|
|
169
|
+
const applicationName = app.getName();
|
|
170
|
+
const logResult = context.host.log(
|
|
170
171
|
"info",
|
|
171
172
|
"__PLUGIN_ID__ database " + (activeDatabase.created ? "created" : "loaded") + " in " + applicationName + ": " + activeDatabase.databasePath
|
|
172
173
|
);
|
|
@@ -182,8 +183,8 @@ export function deactivate(): void {
|
|
|
182
183
|
databaseSelectionPending = false;
|
|
183
184
|
}
|
|
184
185
|
|
|
185
|
-
|
|
186
|
-
const result =
|
|
186
|
+
function register(context: PluginMainActivationContext, name: string, handler: PluginRendererOperationHandler): void {
|
|
187
|
+
const result = context.host.registerRendererMethod(name, handler);
|
|
187
188
|
if (!result.ok) {
|
|
188
189
|
throw new Error(result.error.message);
|
|
189
190
|
}
|
|
@@ -286,11 +287,11 @@ async function chooseDatabase(): Promise<{ canceled: boolean; database: Database
|
|
|
286
287
|
if (databaseSelectionPending) {
|
|
287
288
|
throw codedError("Busy", "A database selection dialog is already open.");
|
|
288
289
|
}
|
|
289
|
-
|
|
290
|
+
requireActivationContext();
|
|
290
291
|
databaseSelectionPending = true;
|
|
291
292
|
try {
|
|
292
293
|
const current = requireDatabase();
|
|
293
|
-
const result = await
|
|
294
|
+
const result = await dialog.showOpenDialog({
|
|
294
295
|
title: "Open SQLite database",
|
|
295
296
|
defaultPath: path.dirname(current.databasePath),
|
|
296
297
|
properties: ["openFile"],
|
|
@@ -311,7 +312,7 @@ async function chooseDatabase(): Promise<{ canceled: boolean; database: Database
|
|
|
311
312
|
candidate.connection.close();
|
|
312
313
|
throw error;
|
|
313
314
|
}
|
|
314
|
-
|
|
315
|
+
logDatabaseSwitch("external", path.basename(candidate.databasePath));
|
|
315
316
|
return { canceled: false, database: getDatabaseInfo() };
|
|
316
317
|
} finally {
|
|
317
318
|
databaseSelectionPending = false;
|
|
@@ -328,16 +329,19 @@ async function useDefaultDatabase(): Promise<{ database: DatabaseInfo }> {
|
|
|
328
329
|
candidate.connection.close();
|
|
329
330
|
throw error;
|
|
330
331
|
}
|
|
331
|
-
|
|
332
|
+
logDatabaseSwitch("default", defaultDatabaseName);
|
|
332
333
|
return { database: getDatabaseInfo() };
|
|
333
334
|
}
|
|
334
335
|
|
|
335
|
-
|
|
336
|
+
function logDatabaseSwitch(source: DatabaseSource, databaseName: string): void {
|
|
336
337
|
const context = activationContext;
|
|
337
338
|
if (!context) {
|
|
338
339
|
return;
|
|
339
340
|
}
|
|
340
|
-
|
|
341
|
+
const result = context.host.log("info", "__PLUGIN_ID__ switched to " + source + " database: " + databaseName);
|
|
342
|
+
if (!result.ok) {
|
|
343
|
+
throw new Error(result.error.message);
|
|
344
|
+
}
|
|
341
345
|
}
|
|
342
346
|
|
|
343
347
|
function replaceDatabase(candidate: ActiveDatabase): void {
|
package/dev-host/plugin-host.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.fail = exports.ok = exports.StatusCodes = void 0;
|
|
4
|
-
exports.StatusCodes = {
|
|
5
|
-
InvalidRequest: "InvalidRequest",
|
|
6
|
-
NotFound: "NotFound",
|
|
7
|
-
UnexpectedFailure: "UnexpectedFailure",
|
|
8
|
-
ExternalAppDefinitionMissing: "ExternalAppDefinitionMissing",
|
|
9
|
-
ExternalAppDisabled: "ExternalAppDisabled",
|
|
10
|
-
ExternalAppLaunchSettingsMissing: "ExternalAppLaunchSettingsMissing",
|
|
11
|
-
ExternalAppExecutableNotConfigured: "ExternalAppExecutableNotConfigured",
|
|
12
|
-
ExternalAppExecutableInvalid: "ExternalAppExecutableInvalid",
|
|
13
|
-
ExternalAppExecutableMissing: "ExternalAppExecutableMissing",
|
|
14
|
-
ExternalAppWorkingDirectoryInvalid: "ExternalAppWorkingDirectoryInvalid",
|
|
15
|
-
ExternalAppWorkingDirectoryMissing: "ExternalAppWorkingDirectoryMissing",
|
|
16
|
-
ExternalAppStartFailed: "ExternalAppStartFailed",
|
|
17
|
-
DirectoryMissing: "DirectoryMissing",
|
|
18
|
-
PluginBlocked: "PluginBlocked",
|
|
19
|
-
PluginManifestInvalid: "PluginManifestInvalid",
|
|
20
|
-
PluginIncompatible: "PluginIncompatible",
|
|
21
|
-
PluginPermissionDenied: "PluginPermissionDenied",
|
|
22
|
-
PluginCapabilityUnsupported: "PluginCapabilityUnsupported",
|
|
23
|
-
PluginMainEntryFailed: "PluginMainEntryFailed",
|
|
24
|
-
ToolFileReadFailed: "ToolFileReadFailed",
|
|
25
|
-
XxbpDeferred: "XxbpDeferred",
|
|
26
|
-
XxbpInvalidRemotePath: "XxbpInvalidRemotePath",
|
|
27
|
-
XxbpToolUnavailable: "XxbpToolUnavailable",
|
|
28
|
-
XxbpCommandFailed: "XxbpCommandFailed",
|
|
29
|
-
XxbpCommandTimeout: "XxbpCommandTimeout",
|
|
30
|
-
XxbpOperationCancelled: "XxbpOperationCancelled",
|
|
31
|
-
XxbpDevicesSessionMissing: "XxbpDevicesSessionMissing",
|
|
32
|
-
XxbpResultFileMissing: "XxbpResultFileMissing",
|
|
33
|
-
XxbpNonTextFile: "XxbpNonTextFile"
|
|
34
|
-
};
|
|
35
|
-
function ok(value) {
|
|
36
|
-
return { ok: true, value };
|
|
37
|
-
}
|
|
38
|
-
exports.ok = ok;
|
|
39
|
-
function fail(code, message, details) {
|
|
40
|
-
return { ok: false, error: { code, message, details } };
|
|
41
|
-
}
|
|
42
|
-
exports.fail = fail;
|