@aiot-nexus/plugin-sdk 1.1.1 → 2.0.1
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 +27 -10
- package/dev-host/host.html +3 -0
- package/dev-host/main.js +370 -138
- package/dev-host/plugin-host.js +418 -0
- package/dist/.generated/plugin-framework-version.d.ts +1 -1
- package/dist/.generated/plugin-framework-version.js +1 -1
- package/dist/src/cli/run.js +2 -2
- package/dist/src/dev.js +3 -3
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/nexus-dev-control.d.ts +1 -2
- package/dist/src/nexus-dev-control.js +6 -10
- package/dist/src/nexus-dev.d.ts +5 -2
- package/dist/src/nexus-dev.js +64 -93
- package/dist/src/types.d.ts +12 -9
- package/package.json +1 -1
- package/templates/minimal/.vscode/launch.json +1 -1
- package/templates/minimal/README.md.tmpl +4 -4
- package/templates/minimal/src/main.ts.tmpl +6 -3
- package/templates/sqlite/.vscode/launch.json +1 -1
- package/templates/sqlite/README.md.tmpl +3 -3
- package/templates/sqlite/src/better-sqlite3.d.ts +1 -0
- package/templates/sqlite/src/main.ts.tmpl +17 -9
- package/templates/sqlite/src/renderer/index.html +1 -1
- package/templates/sqlite/src/renderer/plugin.css +14 -1
package/dist/src/nexus-dev.js
CHANGED
|
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.shouldSuppressNativeBuildEvent = exports.isRetryablePluginDeploymentError = exports.createNexusHostArguments = exports.mergePluginSourceChange = exports.
|
|
26
|
+
exports.shouldSuppressNativeBuildEvent = exports.isRetryablePluginDeploymentError = exports.createNexusHostArguments = exports.mergePluginSourceChange = exports.manifestChangeRequiresPackageUpdate = exports.classifyPluginSourceChange = exports.recoverPreparedPluginPackageUpdate = exports.runPluginNexusDev = void 0;
|
|
27
27
|
const child_process_1 = require("child_process");
|
|
28
28
|
const fs = __importStar(require("fs"));
|
|
29
29
|
const path = __importStar(require("path"));
|
|
@@ -41,8 +41,6 @@ class PluginNexusDevSession {
|
|
|
41
41
|
this.control = new nexus_dev_control_1.NexusDevControlServer();
|
|
42
42
|
this.deployedPluginDirectory = "";
|
|
43
43
|
this.expectedExits = new Set();
|
|
44
|
-
this.provisionalChildren = new Set();
|
|
45
|
-
this.restarting = false;
|
|
46
44
|
this.stopping = false;
|
|
47
45
|
this.terminalSettled = false;
|
|
48
46
|
this.terminalPromise = new Promise((resolve) => {
|
|
@@ -76,7 +74,7 @@ class PluginNexusDevSession {
|
|
|
76
74
|
try {
|
|
77
75
|
await this.control.start();
|
|
78
76
|
this.disposeDisconnectHandler = this.control.setDisconnectHandler((error) => {
|
|
79
|
-
if (!this.stopping
|
|
77
|
+
if (!this.stopping) {
|
|
80
78
|
this.io.stderr("Packaged Nexus plugin development control disconnected: " + error.message);
|
|
81
79
|
this.settleTerminal(1);
|
|
82
80
|
}
|
|
@@ -88,7 +86,7 @@ class PluginNexusDevSession {
|
|
|
88
86
|
}
|
|
89
87
|
this.watcher = new PluginSourceWatcher(this.projectDirectory, async (kind) => {
|
|
90
88
|
const currentTarget = this.requireTarget();
|
|
91
|
-
await applyPluginSourceChange(kind, this.options, currentTarget, this.deployedPluginDirectory, this.control, this.io, () => this.
|
|
89
|
+
await applyPluginSourceChange(kind, this.options, currentTarget, this.deployedPluginDirectory, this.control, this.io, () => this.updatePluginPackage(), () => this.runBuild(() => (0, dev_1.buildPluginRendererForDevelopment)(currentTarget.projectDirectory)));
|
|
92
90
|
}, this.io);
|
|
93
91
|
this.watcher.start();
|
|
94
92
|
this.io.stdout("Watching plugin source changes. Press Ctrl+C to stop.");
|
|
@@ -105,9 +103,9 @@ class PluginNexusDevSession {
|
|
|
105
103
|
await this.control.close();
|
|
106
104
|
}
|
|
107
105
|
}
|
|
108
|
-
async
|
|
106
|
+
async updatePluginPackage() {
|
|
109
107
|
const previousTarget = this.requireTarget();
|
|
110
|
-
this.io.stdout("Building complete plugin package before restarting
|
|
108
|
+
this.io.stdout("Building complete plugin package before restarting the plugin worker...");
|
|
111
109
|
this.runBuild(() => (0, dev_1.buildPluginForDevelopment)(this.projectDirectory));
|
|
112
110
|
const updatedTarget = (0, dev_1.preparePluginDebugTarget)({
|
|
113
111
|
projectDirectory: this.projectDirectory,
|
|
@@ -119,27 +117,19 @@ class PluginNexusDevSession {
|
|
|
119
117
|
if (updatedTarget.pluginId !== previousTarget.pluginId) {
|
|
120
118
|
throw new dev_1.PluginDevError("Plugin id cannot change during a Nexus development session.");
|
|
121
119
|
}
|
|
122
|
-
this.restarting = true;
|
|
123
120
|
let transaction;
|
|
124
|
-
let
|
|
121
|
+
let prepareAttempted = false;
|
|
125
122
|
try {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
await this.ensureProvisionalHostIsStable(updatedChild);
|
|
131
|
-
this.acceptProvisionalHost(updatedChild);
|
|
123
|
+
prepareAttempted = true;
|
|
124
|
+
const prepareResult = await this.control.sendUpdate("packageUpdatePreparing", previousTarget.pluginId);
|
|
125
|
+
if (!prepareResult.ok) {
|
|
126
|
+
throw new dev_1.PluginDevError(prepareResult.message);
|
|
132
127
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
await this.ensureProvisionalHostIsStable(previousChild);
|
|
138
|
-
this.acceptProvisionalHost(previousChild);
|
|
139
|
-
throw new dev_1.PluginDevError("Updated Nexus host failed to start; the previous plugin package was restored. " + errorMessage(error));
|
|
128
|
+
transaction = await retryFileOperation(() => (0, nexus_dev_deployment_1.beginPluginPackageDeployment)(updatedTarget.packageDirectory, this.nexusExecutable, updatedTarget.pluginId), "replace the plugin package");
|
|
129
|
+
const updateResult = await this.control.sendUpdate("packageUpdated", updatedTarget.pluginId);
|
|
130
|
+
if (!updateResult.ok) {
|
|
131
|
+
throw new dev_1.PluginDevError(updateResult.message);
|
|
140
132
|
}
|
|
141
|
-
transactionCommitted = true;
|
|
142
|
-
this.restarting = false;
|
|
143
133
|
try {
|
|
144
134
|
await retryFileOperation(() => transaction?.commit(), "remove the previous plugin package backup");
|
|
145
135
|
}
|
|
@@ -147,24 +137,21 @@ class PluginNexusDevSession {
|
|
|
147
137
|
this.io.stderr("Updated plugin is active, but its previous package backup could not be removed: " + errorMessage(error));
|
|
148
138
|
}
|
|
149
139
|
this.target = updatedTarget;
|
|
150
|
-
this.io.stdout("
|
|
140
|
+
this.io.stdout("Plugin package reloaded in a new worker; Nexus PID unchanged: " + String(this.child?.pid || "unknown"));
|
|
151
141
|
}
|
|
152
142
|
catch (error) {
|
|
153
|
-
if (
|
|
154
|
-
|
|
155
|
-
await
|
|
143
|
+
if (prepareAttempted) {
|
|
144
|
+
try {
|
|
145
|
+
await recoverPreparedPluginPackageUpdate(transaction, previousTarget.pluginId, this.control);
|
|
146
|
+
}
|
|
147
|
+
catch (recoveryError) {
|
|
148
|
+
throw new dev_1.PluginDevError(errorMessage(error) + " Package update recovery failed: " + errorMessage(recoveryError));
|
|
156
149
|
}
|
|
157
|
-
const previousChild = await this.launchHost(previousTarget, true);
|
|
158
|
-
await this.ensureProvisionalHostIsStable(previousChild);
|
|
159
|
-
this.acceptProvisionalHost(previousChild);
|
|
160
150
|
}
|
|
161
151
|
throw error;
|
|
162
152
|
}
|
|
163
|
-
finally {
|
|
164
|
-
this.restarting = false;
|
|
165
|
-
}
|
|
166
153
|
}
|
|
167
|
-
async launchHost(target
|
|
154
|
+
async launchHost(target) {
|
|
168
155
|
const hostArgs = createNexusHostArguments(this.control, target, this.options);
|
|
169
156
|
this.io.stdout("Starting packaged Nexus host: " + this.nexusExecutable);
|
|
170
157
|
const child = (0, child_process_1.spawn)(this.nexusExecutable, hostArgs, {
|
|
@@ -174,13 +161,10 @@ class PluginNexusDevSession {
|
|
|
174
161
|
windowsHide: false
|
|
175
162
|
});
|
|
176
163
|
this.child = child;
|
|
177
|
-
if (provisional) {
|
|
178
|
-
this.provisionalChildren.add(child);
|
|
179
|
-
}
|
|
180
164
|
const hostExit = waitForHostExit(child);
|
|
181
165
|
this.childExit = hostExit;
|
|
182
166
|
hostExit.then((result) => this.handleHostExit(child, result), (error) => {
|
|
183
|
-
if (!this.
|
|
167
|
+
if (!this.stopping) {
|
|
184
168
|
this.io.stderr(errorMessage(error));
|
|
185
169
|
this.settleTerminal(1);
|
|
186
170
|
}
|
|
@@ -205,8 +189,7 @@ class PluginNexusDevSession {
|
|
|
205
189
|
this.child = undefined;
|
|
206
190
|
this.childExit = undefined;
|
|
207
191
|
}
|
|
208
|
-
|
|
209
|
-
if (this.expectedExits.delete(child) || provisional || this.stopping) {
|
|
192
|
+
if (this.expectedExits.delete(child) || this.stopping) {
|
|
210
193
|
return;
|
|
211
194
|
}
|
|
212
195
|
const exitCode = result.status === null ? 1 : result.status;
|
|
@@ -215,30 +198,6 @@ class PluginNexusDevSession {
|
|
|
215
198
|
+ (result.signal ? " (" + result.signal + ")" : "") + ".");
|
|
216
199
|
this.settleTerminal(exitCode === 0 ? 1 : exitCode);
|
|
217
200
|
}
|
|
218
|
-
async shutdownHostForRestart() {
|
|
219
|
-
const child = this.child;
|
|
220
|
-
const hostExit = this.childExit;
|
|
221
|
-
if (!child || !hostExit) {
|
|
222
|
-
throw new dev_1.PluginDevError("Packaged Nexus host is not running.");
|
|
223
|
-
}
|
|
224
|
-
this.expectedExits.add(child);
|
|
225
|
-
try {
|
|
226
|
-
await this.control.requestShutdown();
|
|
227
|
-
}
|
|
228
|
-
catch (error) {
|
|
229
|
-
this.expectedExits.delete(child);
|
|
230
|
-
throw error;
|
|
231
|
-
}
|
|
232
|
-
try {
|
|
233
|
-
await promiseWithTimeout(hostExit, 10000, "Nexus did not exit after accepting the plugin development restart.");
|
|
234
|
-
}
|
|
235
|
-
catch (error) {
|
|
236
|
-
child.kill();
|
|
237
|
-
await promiseWithTimeout(hostExit, 5000, "Nexus could not be stopped for the plugin development restart.");
|
|
238
|
-
this.io.stderr("Nexus required forced termination before plugin replacement: " + errorMessage(error));
|
|
239
|
-
}
|
|
240
|
-
await this.control.waitForDisconnection();
|
|
241
|
-
}
|
|
242
201
|
async stopCurrentHost() {
|
|
243
202
|
const child = this.child;
|
|
244
203
|
const hostExit = this.childExit;
|
|
@@ -256,22 +215,10 @@ class PluginNexusDevSession {
|
|
|
256
215
|
await promiseWithTimeout(hostExit, 3000, "Nexus did not terminate after plugin development shutdown.");
|
|
257
216
|
}
|
|
258
217
|
catch {
|
|
259
|
-
// The CLI is already stopping; do not start another
|
|
218
|
+
// The CLI is already stopping; do not start another shutdown path.
|
|
260
219
|
}
|
|
261
220
|
}
|
|
262
221
|
}
|
|
263
|
-
async ensureProvisionalHostIsStable(child) {
|
|
264
|
-
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
265
|
-
if (this.child !== child || child.exitCode !== null || child.signalCode !== null || !this.control.isConnected()) {
|
|
266
|
-
throw new dev_1.PluginDevError("Restarted Nexus host did not remain connected after authentication.");
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
acceptProvisionalHost(child) {
|
|
270
|
-
if (this.child !== child || !this.control.isConnected()) {
|
|
271
|
-
throw new dev_1.PluginDevError("Restarted Nexus host disconnected before activation completed.");
|
|
272
|
-
}
|
|
273
|
-
this.provisionalChildren.delete(child);
|
|
274
|
-
}
|
|
275
222
|
runBuild(operation) {
|
|
276
223
|
return this.watcher ? this.watcher.runBuild(operation) : operation();
|
|
277
224
|
}
|
|
@@ -298,6 +245,30 @@ class PluginNexusDevSession {
|
|
|
298
245
|
this.terminalResolve = undefined;
|
|
299
246
|
}
|
|
300
247
|
}
|
|
248
|
+
async function recoverPreparedPluginPackageUpdate(transaction, pluginId, control) {
|
|
249
|
+
const recoveryErrors = [];
|
|
250
|
+
if (transaction) {
|
|
251
|
+
try {
|
|
252
|
+
await retryFileOperation(() => transaction.rollback(), "roll back the plugin package");
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
recoveryErrors.push("package rollback failed: " + errorMessage(error));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
const rollbackResult = await control.sendUpdate("packageUpdated", pluginId);
|
|
260
|
+
if (!rollbackResult.ok) {
|
|
261
|
+
recoveryErrors.push("previous worker activation failed: " + rollbackResult.message);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
recoveryErrors.push("previous worker activation failed: " + errorMessage(error));
|
|
266
|
+
}
|
|
267
|
+
if (recoveryErrors.length > 0) {
|
|
268
|
+
throw new dev_1.PluginDevError(recoveryErrors.join(" "));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
exports.recoverPreparedPluginPackageUpdate = recoverPreparedPluginPackageUpdate;
|
|
301
272
|
function classifyPluginSourceChange(projectDirectory, changedPath) {
|
|
302
273
|
const relativePath = path.relative(path.resolve(projectDirectory), path.resolve(changedPath)).replace(/\\/g, "/");
|
|
303
274
|
if (relativePath.startsWith("../") || relativePath === ".." || path.isAbsolute(relativePath)) {
|
|
@@ -310,7 +281,7 @@ function classifyPluginSourceChange(projectDirectory, changedPath) {
|
|
|
310
281
|
return "renderer";
|
|
311
282
|
}
|
|
312
283
|
if (relativePath.toLowerCase().endsWith(".node") || relativePath === "binding.gyp") {
|
|
313
|
-
return "
|
|
284
|
+
return "package";
|
|
314
285
|
}
|
|
315
286
|
if (relativePath.startsWith("src/")
|
|
316
287
|
|| relativePath.startsWith("scripts/")
|
|
@@ -318,13 +289,13 @@ function classifyPluginSourceChange(projectDirectory, changedPath) {
|
|
|
318
289
|
|| relativePath === "package-lock.json"
|
|
319
290
|
|| relativePath === "npm-shrinkwrap.json"
|
|
320
291
|
|| relativePath === "tsconfig.main.json") {
|
|
321
|
-
return "
|
|
292
|
+
return "package";
|
|
322
293
|
}
|
|
323
294
|
return undefined;
|
|
324
295
|
}
|
|
325
296
|
exports.classifyPluginSourceChange = classifyPluginSourceChange;
|
|
326
|
-
function
|
|
327
|
-
const
|
|
297
|
+
function manifestChangeRequiresPackageUpdate(previous, next) {
|
|
298
|
+
const packageSensitiveFields = [
|
|
328
299
|
"schemaVersion",
|
|
329
300
|
"pluginId",
|
|
330
301
|
"pluginVersion",
|
|
@@ -334,17 +305,17 @@ function manifestChangeRequiresRestart(previous, next) {
|
|
|
334
305
|
"mainEntry",
|
|
335
306
|
"deferred"
|
|
336
307
|
];
|
|
337
|
-
return
|
|
308
|
+
return packageSensitiveFields.some((field) => stableJson(previous[field]) !== stableJson(next[field]));
|
|
338
309
|
}
|
|
339
|
-
exports.
|
|
310
|
+
exports.manifestChangeRequiresPackageUpdate = manifestChangeRequiresPackageUpdate;
|
|
340
311
|
function mergePluginSourceChange(current, incoming) {
|
|
341
|
-
const priority = { renderer: 1, manifest: 2,
|
|
312
|
+
const priority = { renderer: 1, manifest: 2, package: 3 };
|
|
342
313
|
return !current || priority[incoming] > priority[current] ? incoming : current;
|
|
343
314
|
}
|
|
344
315
|
exports.mergePluginSourceChange = mergePluginSourceChange;
|
|
345
|
-
async function applyPluginSourceChange(kind, options, initialTarget, deployedPluginDirectory, control, io,
|
|
346
|
-
if (kind === "
|
|
347
|
-
await
|
|
316
|
+
async function applyPluginSourceChange(kind, options, initialTarget, deployedPluginDirectory, control, io, updatePackage, buildRenderer) {
|
|
317
|
+
if (kind === "package") {
|
|
318
|
+
await updatePackage();
|
|
348
319
|
return;
|
|
349
320
|
}
|
|
350
321
|
try {
|
|
@@ -363,8 +334,8 @@ async function applyPluginSourceChange(kind, options, initialTarget, deployedPlu
|
|
|
363
334
|
const rendererDirectory = path.join(updatedTarget.packageDirectory, "renderer");
|
|
364
335
|
if (kind === "manifest") {
|
|
365
336
|
const previousManifest = readManifest(path.join(deployedPluginDirectory, "manifest.json"));
|
|
366
|
-
if (
|
|
367
|
-
await
|
|
337
|
+
if (manifestChangeRequiresPackageUpdate(previousManifest, updatedTarget.manifest)) {
|
|
338
|
+
await updatePackage();
|
|
368
339
|
return;
|
|
369
340
|
}
|
|
370
341
|
const transaction = (0, nexus_dev_deployment_1.beginManifestAndRendererDeployment)(path.join(updatedTarget.packageDirectory, "manifest.json"), rendererDirectory, deployedPluginDirectory);
|
|
@@ -398,13 +369,13 @@ async function finishUpdateTransaction(transaction, updateType, pluginId, contro
|
|
|
398
369
|
throw new dev_1.PluginDevError(result.message);
|
|
399
370
|
}
|
|
400
371
|
transaction.commit();
|
|
401
|
-
io.stdout(
|
|
402
|
-
+ (result.requiresRestart ? " Existing mainEntry state will remain active until the next host restart." : ""));
|
|
372
|
+
io.stdout(updateType === "rendererUpdated" ? "Plugin renderer reloaded." : "Plugin manifest rescanned.");
|
|
403
373
|
}
|
|
404
374
|
function createNexusHostArguments(control, target, options) {
|
|
405
375
|
const args = [];
|
|
406
376
|
if (options.inspectorMode) {
|
|
407
|
-
args.push("--" + options.inspectorMode + "
|
|
377
|
+
args.push("--plugin-dev-worker-inspector=" + options.inspectorMode + ":" + String(options.inspectorPort || 9229));
|
|
378
|
+
args.push("--plugin-dev-worker-plugin=" + target.pluginId);
|
|
408
379
|
}
|
|
409
380
|
args.push("--plugin-dev", "--plugin-dev-control=" + control.pipePath, "--plugin-dev-token=" + control.token, "--page=" + resolveContributionPage(target));
|
|
410
381
|
if (target.surface !== "business") {
|
package/dist/src/types.d.ts
CHANGED
|
@@ -95,6 +95,7 @@ export interface BusinessLogAppendRequest {
|
|
|
95
95
|
category?: string;
|
|
96
96
|
exception?: string;
|
|
97
97
|
}
|
|
98
|
+
export type PluginHostLogLevel = Exclude<BusinessLogEntry["level"], "debug">;
|
|
98
99
|
export interface PluginRendererOperationRequest<TInput extends Record<string, unknown> = Record<string, unknown>> {
|
|
99
100
|
readonly pluginId: string;
|
|
100
101
|
readonly surface: PluginRendererSurface;
|
|
@@ -102,12 +103,12 @@ export interface PluginRendererOperationRequest<TInput extends Record<string, un
|
|
|
102
103
|
readonly operation: string;
|
|
103
104
|
readonly input?: TInput;
|
|
104
105
|
}
|
|
105
|
-
export type PluginRendererOperationHandler<TInput extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (request: PluginRendererOperationRequest<TInput>) => NexusResult<TResult> | TResult
|
|
106
|
+
export type PluginRendererOperationHandler<TInput extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (request: PluginRendererOperationRequest<TInput>) => NexusResult<TResult> | TResult | Promise<NexusResult<TResult> | TResult>;
|
|
106
107
|
export interface PluginUnsupportedCapabilityApi {
|
|
107
108
|
readonly permission: PluginPermission;
|
|
108
109
|
readonly supportState: PluginPermissionSupportState;
|
|
109
|
-
describe(): NexusResult<PluginPermissionSummary
|
|
110
|
-
unsupported(operation?: unknown): NexusResult<never
|
|
110
|
+
describe(): Promise<NexusResult<PluginPermissionSummary>>;
|
|
111
|
+
unsupported(operation?: unknown): Promise<NexusResult<never>>;
|
|
111
112
|
[operation: string]: unknown;
|
|
112
113
|
}
|
|
113
114
|
export interface PluginHostApi {
|
|
@@ -116,12 +117,12 @@ export interface PluginHostApi {
|
|
|
116
117
|
readonly pluginId: string;
|
|
117
118
|
readonly pluginDataDirectory: string;
|
|
118
119
|
hasPermission(permission: PluginPermission): boolean;
|
|
119
|
-
registerRendererMethod(name: string, handler: PluginRendererOperationHandler): NexusResult<boolean
|
|
120
|
-
getCapabilityStatus(permission: unknown): NexusResult<PluginPermissionSummary
|
|
121
|
-
unsupportedCapability(permission: unknown, operation?: unknown): NexusResult<never
|
|
122
|
-
log(level:
|
|
123
|
-
getSettings(): NexusResult<PluginSettingsDocument
|
|
124
|
-
updateSettings(values: Record<string, unknown>): NexusResult<PluginSettingsDocument
|
|
120
|
+
registerRendererMethod(name: string, handler: PluginRendererOperationHandler): Promise<NexusResult<boolean>>;
|
|
121
|
+
getCapabilityStatus(permission: unknown): Promise<NexusResult<PluginPermissionSummary>>;
|
|
122
|
+
unsupportedCapability(permission: unknown, operation?: unknown): Promise<NexusResult<never>>;
|
|
123
|
+
log(level: PluginHostLogLevel, message: string, exception?: string): Promise<NexusResult<BusinessLogEntry>>;
|
|
124
|
+
getSettings(): Promise<NexusResult<PluginSettingsDocument>>;
|
|
125
|
+
updateSettings(values: Record<string, unknown>): Promise<NexusResult<PluginSettingsDocument>>;
|
|
125
126
|
filesystem: PluginUnsupportedCapabilityApi;
|
|
126
127
|
udp: PluginUnsupportedCapabilityApi;
|
|
127
128
|
serial: PluginUnsupportedCapabilityApi;
|
|
@@ -136,7 +137,9 @@ export interface PluginMainActivationContext {
|
|
|
136
137
|
}
|
|
137
138
|
export interface PluginMainModule {
|
|
138
139
|
activate?: (context: PluginMainActivationContext) => unknown;
|
|
140
|
+
deactivate?: (reason: string) => unknown;
|
|
139
141
|
default?: {
|
|
140
142
|
activate?: (context: PluginMainActivationContext) => unknown;
|
|
143
|
+
deactivate?: (reason: string) => unknown;
|
|
141
144
|
};
|
|
142
145
|
}
|
package/package.json
CHANGED
|
@@ -15,20 +15,20 @@ SDK checks for a newer `@aiot-nexus/plugin-sdk` in two steps. The package `posti
|
|
|
15
15
|
|
|
16
16
|
The check follows the npm-configured registry, falls back to the official npm registry when needed, and silently ignores offline, timeout, malformed-response, or corrupt-cache failures. Use `NEXUS_PLUGIN_SDK_UPDATE_CHECK=0` or `NO_UPDATE_NOTIFIER=1` to disable both checks. A major SDK update requires confirming Nexus Framework API compatibility; upgrades are never automatic. When a notice appears, review compatibility and run `npm install --save-dev @aiot-nexus/plugin-sdk@latest` manually.
|
|
17
17
|
|
|
18
|
-
The deployable plugin package is `dist/__PLUGIN_ID__/`.
|
|
18
|
+
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 host coordinates worker shutdown, atomic replacement, and worker reactivation without changing the Nexus PID.
|
|
19
19
|
|
|
20
|
-
The renderer calls the
|
|
20
|
+
The renderer calls the plugin-worker `demo.echo` operation through `@aiot-nexus/plugin-sdk`. Run `npm run validate` after changing the manifest or output layout.
|
|
21
21
|
|
|
22
22
|
The renderer follows `context.language` and `context.themeMode` from Nexus, subscribes to in-place context updates without reloading the iframe, and includes `en-US` and `zh-CN` message catalogs. The plugin page has no language or theme selector; the controls shown by `npm run dev` belong to the debug host and simulate Nexus context changes. Add or edit renderer messages in `src/renderer/app.ts`; edit host-shell titles and descriptions in `src/manifest.json` resources.
|
|
23
23
|
|
|
24
24
|
Use `npm run dev:web` for renderer-only UI work in the system browser. It runs `build:web`, writes an isolated package under `.nexus-plugin-dev/web-package/`, builds with source maps, serves only on `127.0.0.1:4173`, and opens the browser automatically. It does not execute `mainEntry` or overwrite the deployable `dist` package. Language and theme controls still simulate Nexus context, while `invokeMain` is intentionally unavailable. Pass `-- --no-open` or `-- --port <port>` when needed; use `npm run dev` for Host API integration.
|
|
25
25
|
|
|
26
|
-
Use `npm run dev:nexus -- --host C:\path\to\nexus.exe` for live development against a compiled, packaged Nexus. The CLI atomically deploys this plugin to `<exeDir>/Plugins/__PLUGIN_ID__/`. Renderer changes rebuild and reload the iframe without changing the Nexus PID; safe manifest changes rescan contributions in the same process; `mainEntry`, dependency, native-module, or
|
|
26
|
+
Use `npm run dev:nexus -- --host C:\path\to\nexus.exe` for live development against a compiled, packaged Nexus. The CLI atomically deploys this plugin to `<exeDir>/Plugins/__PLUGIN_ID__/`. Renderer changes rebuild and reload the iframe without changing the Nexus PID; safe manifest changes rescan contributions in the same process; `mainEntry`, dependency, native-module, or worker-sensitive manifest changes rebuild the full package, stop the old plugin worker, replace the package, and start a new worker while keeping the Nexus PID. Failed builds keep the previous deployment; a failed worker reload rolls the package back and restores the previous worker. Add `--no-devtools`, `--inspect[=<port>]`, `--inspect-brk[=<port>]`, `--surface`, or `--contribution-id` after the second `--` as needed.
|
|
27
27
|
|
|
28
28
|
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.
|
|
29
29
|
|
|
30
30
|
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.
|
|
31
31
|
|
|
32
|
-
For
|
|
32
|
+
For plugin-worker breakpoints, open this plugin directory in VS Code, set breakpoints in `src/main.ts`, and run `Debug Nexus plugin`. If child-process auto attach is disabled, start `npm run dev:debug` and select `Attach Nexus plugin worker (9229)`. Renderer DevTools opens automatically, with `src/renderer/app.ts` available through the development source map. `npm run dev` rebuilds before launch; use `npm run dev -- --no-build` only when intentionally reusing the current `dist` output.
|
|
33
33
|
|
|
34
34
|
The independent host stores data under `.nexus-plugin-dev/` 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,7 +1,7 @@
|
|
|
1
1
|
import type { PluginMainActivationContext } from "@aiot-nexus/plugin-sdk";
|
|
2
2
|
|
|
3
|
-
export function activate(context: PluginMainActivationContext): void {
|
|
4
|
-
const registration = context.host.registerRendererMethod("demo.echo", (request) => {
|
|
3
|
+
export async function activate(context: PluginMainActivationContext): Promise<void> {
|
|
4
|
+
const registration = await context.host.registerRendererMethod("demo.echo", (request) => {
|
|
5
5
|
const value = request.input && typeof request.input.value === "string"
|
|
6
6
|
? request.input.value.trim()
|
|
7
7
|
: "";
|
|
@@ -19,5 +19,8 @@ export function activate(context: PluginMainActivationContext): void {
|
|
|
19
19
|
if (!registration.ok) {
|
|
20
20
|
throw new Error(registration.error.message);
|
|
21
21
|
}
|
|
22
|
-
context.host.log("info", "__PLUGIN_ID__ activated.");
|
|
22
|
+
const logResult = await context.host.log("info", "__PLUGIN_ID__ activated.");
|
|
23
|
+
if (!logResult.ok) {
|
|
24
|
+
throw new Error(logResult.error.message);
|
|
25
|
+
}
|
|
23
26
|
}
|
|
@@ -17,18 +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__/`.
|
|
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 host coordinates worker shutdown, atomic replacement, and worker reactivation without changing the Nexus PID. Runtime data is stored in the host-provided plugin data directory and is not written into the plugin package.
|
|
21
21
|
|
|
22
22
|
The renderer follows `context.language` and `context.themeMode` from Nexus, subscribes to in-place context updates without reloading the iframe, and includes `en-US` and `zh-CN` message catalogs. The plugin page has no language or theme selector; the controls shown by `npm run dev` belong to the debug host and simulate Nexus context changes. Static labels, CRUD status messages, action buttons, validation text, and date formatting switch together. Edit renderer messages in `src/renderer/app.ts`; edit host-shell titles and descriptions in `src/manifest.json` resources.
|
|
23
23
|
|
|
24
24
|
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`, 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. Database operations report unsupported; use `npm run dev` for SQLite and Host API integration. Pass `-- --no-open` or `-- --port <port>` when needed.
|
|
25
25
|
|
|
26
|
-
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
|
|
26
|
+
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 worker-sensitive manifest changes run the complete build (including the Electron 22.3.27 native rebuild), stop the old plugin worker, replace the package, and start a new worker in the same Nexus process. The database remains under the host-provided plugin data directory across worker replacements. Failed builds keep the previous deployment, and a failed worker reload rolls the package back and restores the previous worker. Add `--no-devtools`, `--inspect[=<port>]`, `--inspect-brk[=<port>]`, `--surface`, or `--contribution-id` after the second `--` as needed.
|
|
27
27
|
|
|
28
28
|
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.
|
|
29
29
|
|
|
30
30
|
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.
|
|
31
31
|
|
|
32
|
-
For
|
|
32
|
+
For plugin-worker breakpoints, open this plugin directory in VS Code, set breakpoints in `src/main.ts`, and run `Debug Nexus plugin`. If child-process auto attach is disabled, start `npm run dev:debug` and select `Attach Nexus plugin worker (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.
|
|
33
33
|
|
|
34
34
|
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.
|
|
@@ -19,7 +19,7 @@ let database: Database | undefined;
|
|
|
19
19
|
let databasePath = "";
|
|
20
20
|
let databaseCreated = false;
|
|
21
21
|
|
|
22
|
-
export function activate(context: PluginMainActivationContext): void {
|
|
22
|
+
export async function activate(context: PluginMainActivationContext): Promise<void> {
|
|
23
23
|
const fs = require("fs") as typeof import("fs");
|
|
24
24
|
const path = require("path") as typeof import("path");
|
|
25
25
|
fs.mkdirSync(context.host.pluginDataDirectory, { recursive: true });
|
|
@@ -36,16 +36,24 @@ export function activate(context: PluginMainActivationContext): void {
|
|
|
36
36
|
")"
|
|
37
37
|
).run();
|
|
38
38
|
|
|
39
|
-
register(context, "sqlite.getInfo", getInfo);
|
|
40
|
-
register(context, "sqlite.listRecords", listRecords);
|
|
41
|
-
register(context, "sqlite.insertRecord", insertRecord);
|
|
42
|
-
register(context, "sqlite.updateRecord", updateRecord);
|
|
43
|
-
register(context, "sqlite.deleteRecord", deleteRecord);
|
|
44
|
-
context.host.log("info", "__PLUGIN_ID__ database " + (databaseCreated ? "created" : "loaded") + ": " + databasePath);
|
|
39
|
+
await register(context, "sqlite.getInfo", getInfo);
|
|
40
|
+
await register(context, "sqlite.listRecords", listRecords);
|
|
41
|
+
await register(context, "sqlite.insertRecord", insertRecord);
|
|
42
|
+
await register(context, "sqlite.updateRecord", updateRecord);
|
|
43
|
+
await register(context, "sqlite.deleteRecord", deleteRecord);
|
|
44
|
+
const logResult = await context.host.log("info", "__PLUGIN_ID__ database " + (databaseCreated ? "created" : "loaded") + ": " + databasePath);
|
|
45
|
+
if (!logResult.ok) {
|
|
46
|
+
throw new Error(logResult.error.message);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function deactivate(): void {
|
|
51
|
+
database?.close();
|
|
52
|
+
database = undefined;
|
|
45
53
|
}
|
|
46
54
|
|
|
47
|
-
function register(context: PluginMainActivationContext, name: string, handler: PluginRendererOperationHandler): void {
|
|
48
|
-
const result = context.host.registerRendererMethod(name, handler);
|
|
55
|
+
async function register(context: PluginMainActivationContext, name: string, handler: PluginRendererOperationHandler): Promise<void> {
|
|
56
|
+
const result = await context.host.registerRendererMethod(name, handler);
|
|
49
57
|
if (!result.ok) {
|
|
50
58
|
throw new Error(result.error.message);
|
|
51
59
|
}
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
<p id="formError" class="error" hidden></p>
|
|
37
37
|
</section>
|
|
38
38
|
|
|
39
|
-
<section class="panel">
|
|
39
|
+
<section class="panel recent-rows-panel">
|
|
40
40
|
<div class="panel-heading">
|
|
41
41
|
<h2 data-i18n="section.recentRows">Recent Rows</h2>
|
|
42
42
|
<span id="statusLog" data-i18n="status.idle">Idle</span>
|
|
@@ -15,13 +15,15 @@
|
|
|
15
15
|
|
|
16
16
|
body {
|
|
17
17
|
margin: 0;
|
|
18
|
-
min-height:
|
|
18
|
+
min-height: 100vh;
|
|
19
19
|
background: #f3f5f7;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
.plugin-shell {
|
|
23
23
|
display: grid;
|
|
24
|
+
grid-template-rows: auto auto auto minmax(0, 1fr);
|
|
24
25
|
gap: 12px;
|
|
26
|
+
min-height: 100vh;
|
|
25
27
|
padding: 16px;
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -100,6 +102,17 @@ h2 {
|
|
|
100
102
|
padding: 14px 16px;
|
|
101
103
|
}
|
|
102
104
|
|
|
105
|
+
.recent-rows-panel {
|
|
106
|
+
display: flex;
|
|
107
|
+
flex-direction: column;
|
|
108
|
+
min-height: 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.recent-rows-panel .table-wrap {
|
|
112
|
+
flex: 1 1 auto;
|
|
113
|
+
min-height: 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
103
116
|
.entry-form {
|
|
104
117
|
display: grid;
|
|
105
118
|
grid-template-columns: minmax(150px, 1fr) minmax(220px, 2fr) auto;
|