@aiot-nexus/plugin-sdk 1.1.0 → 2.0.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.
@@ -0,0 +1,388 @@
1
+ "use strict";
2
+ /* eslint-disable @typescript-eslint/no-var-requires */
3
+
4
+ const childProcess = require("child_process");
5
+ const fs = require("fs");
6
+ const http = require("http");
7
+ const https = require("https");
8
+ const os = require("os");
9
+ const path = require("path");
10
+
11
+ const PACKAGE_NAME = "@aiot-nexus/plugin-sdk";
12
+ const DEFAULT_REGISTRY = "https://registry.npmjs.org";
13
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
14
+ const REQUEST_TIMEOUT_MS = 2000;
15
+ const MAX_RESPONSE_BYTES = 256 * 1024;
16
+
17
+ async function checkForSdkUpdate(options) {
18
+ const config = options || {};
19
+ const env = config.env || process.env;
20
+ const packageName = config.packageName || PACKAGE_NAME;
21
+ const currentVersion = config.currentVersion || readPackageVersion();
22
+ const now = typeof config.now === "number" ? config.now : Date.now();
23
+
24
+ if (isDisabled(env)) {
25
+ return createResult("disabled", currentVersion, null, null, packageName);
26
+ }
27
+
28
+ const registries = getRegistryCandidates(config);
29
+ if (registries.length === 0 || !isValidVersion(currentVersion)) {
30
+ return createResult("unavailable", currentVersion, null, registries[0] || null, packageName);
31
+ }
32
+
33
+ const cachePath = config.cachePath || getDefaultCachePath(env, os.platform());
34
+ const cache = readCache(cachePath);
35
+ for (const registry of registries) {
36
+ const cacheMatches =
37
+ cache &&
38
+ cache.packageName === packageName &&
39
+ cache.currentVersion === currentVersion &&
40
+ cache.registry === registry &&
41
+ isValidVersion(cache.latestVersion) &&
42
+ typeof cache.checkedAt === "number";
43
+ const cacheIsFresh = cacheMatches && now - cache.checkedAt < CACHE_TTL_MS;
44
+ if (!config.force && cacheIsFresh) {
45
+ return createResult("cache", currentVersion, cache.latestVersion, registry, packageName);
46
+ }
47
+
48
+ const endpoint = buildMetadataUrl(registry, packageName);
49
+ try {
50
+ const request = config.request || requestJson;
51
+ const metadata = await request(endpoint, {
52
+ timeoutMs: config.timeoutMs || REQUEST_TIMEOUT_MS,
53
+ headers: {
54
+ Accept: "application/vnd.npm.install-v1+json",
55
+ "User-Agent": "@aiot-nexus/plugin-sdk update check"
56
+ }
57
+ });
58
+ const latestVersion = extractLatestVersion(metadata);
59
+ if (!isValidVersion(latestVersion)) {
60
+ continue;
61
+ }
62
+
63
+ writeCache(cachePath, {
64
+ packageName,
65
+ currentVersion,
66
+ registry,
67
+ latestVersion,
68
+ checkedAt: now
69
+ });
70
+ return createResult("checked", currentVersion, latestVersion, registry, packageName);
71
+ } catch (_error) {
72
+ // Try the official registry after a configured registry failure.
73
+ }
74
+ }
75
+ return createResult("unavailable", currentVersion, null, registries[0], packageName);
76
+ }
77
+
78
+ function readCachedSdkUpdate(options) {
79
+ const config = options || {};
80
+ const env = config.env || process.env;
81
+ const packageName = config.packageName || PACKAGE_NAME;
82
+ const currentVersion = config.currentVersion || readPackageVersion();
83
+ if (isDisabled(env)) {
84
+ return createResult("disabled", currentVersion, null, null, packageName);
85
+ }
86
+
87
+ const registries = getRegistryCandidates(config);
88
+ if (registries.length === 0 || !isValidVersion(currentVersion)) {
89
+ return createResult("unavailable", currentVersion, null, registries[0] || null, packageName);
90
+ }
91
+
92
+ const cachePath = config.cachePath || getDefaultCachePath(env, os.platform());
93
+ const cache = readCache(cachePath);
94
+ const now = typeof config.now === "number" ? config.now : Date.now();
95
+ for (const registry of registries) {
96
+ if (
97
+ cache &&
98
+ cache.packageName === packageName &&
99
+ cache.currentVersion === currentVersion &&
100
+ cache.registry === registry &&
101
+ isValidVersion(cache.latestVersion) &&
102
+ typeof cache.checkedAt === "number" &&
103
+ now - cache.checkedAt < CACHE_TTL_MS
104
+ ) {
105
+ return createResult("cache", currentVersion, cache.latestVersion, registry, packageName);
106
+ }
107
+ }
108
+ return null;
109
+ }
110
+
111
+ function getRegistryCandidates(options) {
112
+ const config = options || {};
113
+ const env = config.env || process.env;
114
+ const configured = config.registry || resolveRegistry({ env, execFileSync: config.execFileSync });
115
+ const primary = normalizeRegistry(configured);
116
+ const official = normalizeRegistry(DEFAULT_REGISTRY);
117
+ return Array.from(new Set([primary, official].filter(Boolean)));
118
+ }
119
+
120
+ function createResult(status, currentVersion, latestVersion, registry, packageName) {
121
+ const hasUpdate =
122
+ isValidVersion(currentVersion) &&
123
+ isValidVersion(latestVersion) &&
124
+ compareVersions(latestVersion, currentVersion) > 0;
125
+ return {
126
+ status,
127
+ packageName: packageName || PACKAGE_NAME,
128
+ currentVersion,
129
+ latestVersion,
130
+ registry,
131
+ hasUpdate,
132
+ isMajorUpdate: hasUpdate && getMajor(latestVersion) > getMajor(currentVersion)
133
+ };
134
+ }
135
+
136
+ function isDisabled(env) {
137
+ return (
138
+ env.NEXUS_PLUGIN_SDK_UPDATE_CHECK === "0" ||
139
+ env.NO_UPDATE_NOTIFIER === "1" ||
140
+ env.NEXUS_PLUGIN_SDK_INTERNAL_PACK === "1"
141
+ );
142
+ }
143
+
144
+ function resolveRegistry(options) {
145
+ const config = options || {};
146
+ const env = config.env || process.env;
147
+ const configured = env.npm_config_registry || env.NPM_CONFIG_REGISTRY;
148
+ if (configured) {
149
+ return configured;
150
+ }
151
+
152
+ const execFileSync = config.execFileSync || childProcess.execFileSync;
153
+ try {
154
+ const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
155
+ const output = execFileSync(npmCommand, ["config", "get", "registry"], {
156
+ encoding: "utf8",
157
+ stdio: ["ignore", "pipe", "ignore"]
158
+ });
159
+ if (String(output).trim()) {
160
+ return String(output).trim();
161
+ }
162
+ } catch (_error) {
163
+ // A missing npm executable must not prevent a plugin from running.
164
+ }
165
+ return DEFAULT_REGISTRY;
166
+ }
167
+
168
+ function normalizeRegistry(value) {
169
+ if (!value) {
170
+ return null;
171
+ }
172
+ try {
173
+ const url = new URL(String(value).trim());
174
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
175
+ return null;
176
+ }
177
+ url.username = "";
178
+ url.password = "";
179
+ url.hash = "";
180
+ url.search = "";
181
+ url.pathname = url.pathname.replace(/\/+$/, "");
182
+ return url.toString().replace(/\/$/, "");
183
+ } catch (_error) {
184
+ return null;
185
+ }
186
+ }
187
+
188
+ function buildMetadataUrl(registry, packageName) {
189
+ return registry + "/" + encodeURIComponent(packageName);
190
+ }
191
+
192
+ function requestJson(url, options, redirectCount) {
193
+ const requestOptions = options || {};
194
+ const redirects = typeof redirectCount === "number" ? redirectCount : 0;
195
+ const client = url.startsWith("https:") ? https : http;
196
+ return new Promise((resolve, reject) => {
197
+ let settled = false;
198
+ let responseBytes = 0;
199
+ const finishResolve = (value) => {
200
+ if (!settled) {
201
+ settled = true;
202
+ resolve(value);
203
+ }
204
+ };
205
+ const finishReject = (error) => {
206
+ if (!settled) {
207
+ settled = true;
208
+ reject(error);
209
+ }
210
+ };
211
+ const request = client.get(
212
+ url,
213
+ { headers: requestOptions.headers || {} },
214
+ (response) => {
215
+ if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
216
+ response.resume();
217
+ if (redirects >= 2) {
218
+ finishReject(new Error("registry redirect limit exceeded"));
219
+ return;
220
+ }
221
+ let redirectUrl;
222
+ try {
223
+ redirectUrl = new URL(response.headers.location, url).toString();
224
+ if (!redirectUrl.startsWith("http:") && !redirectUrl.startsWith("https:")) {
225
+ throw new Error("registry redirect protocol is not supported");
226
+ }
227
+ } catch (error) {
228
+ finishReject(error);
229
+ return;
230
+ }
231
+ requestJson(redirectUrl, requestOptions, redirects + 1).then(finishResolve, finishReject);
232
+ return;
233
+ }
234
+ if (response.statusCode !== 200) {
235
+ response.resume();
236
+ finishReject(new Error("registry responded with HTTP " + response.statusCode));
237
+ return;
238
+ }
239
+
240
+ const chunks = [];
241
+ response.setEncoding("utf8");
242
+ response.on("data", (chunk) => {
243
+ responseBytes += Buffer.byteLength(chunk, "utf8");
244
+ if (responseBytes > MAX_RESPONSE_BYTES) {
245
+ finishReject(new Error("registry response is too large"));
246
+ request.destroy();
247
+ return;
248
+ }
249
+ chunks.push(chunk);
250
+ });
251
+ response.once("error", finishReject);
252
+ response.on("end", () => {
253
+ try {
254
+ finishResolve(JSON.parse(chunks.join("")));
255
+ } catch (error) {
256
+ finishReject(error);
257
+ }
258
+ });
259
+ }
260
+ );
261
+ request.once("error", finishReject);
262
+ request.setTimeout(requestOptions.timeoutMs || REQUEST_TIMEOUT_MS, () => {
263
+ request.destroy(new Error("registry request timed out"));
264
+ });
265
+ });
266
+ }
267
+
268
+ function extractLatestVersion(metadata) {
269
+ if (!metadata || typeof metadata !== "object") {
270
+ return null;
271
+ }
272
+ const distTags = metadata["dist-tags"];
273
+ return distTags && typeof distTags.latest === "string" ? distTags.latest : null;
274
+ }
275
+
276
+ function getDefaultCachePath(env, platform) {
277
+ const values = env || process.env;
278
+ if (platform === "win32") {
279
+ const base = values.LOCALAPPDATA || path.join(values.USERPROFILE || os.homedir(), "AppData", "Local");
280
+ return path.join(base, "AIoT-Nexus", "plugin-sdk", "update-check.json");
281
+ }
282
+ if (platform === "darwin") {
283
+ return path.join(values.HOME || os.homedir(), "Library", "Caches", "AIoT-Nexus", "plugin-sdk", "update-check.json");
284
+ }
285
+ return path.join(
286
+ values.XDG_CACHE_HOME || path.join(values.HOME || os.homedir(), ".cache"),
287
+ "aiot-nexus",
288
+ "plugin-sdk",
289
+ "update-check.json"
290
+ );
291
+ }
292
+
293
+ function readCache(cachePath) {
294
+ try {
295
+ const cache = JSON.parse(fs.readFileSync(cachePath, "utf8"));
296
+ return cache && typeof cache === "object" ? cache : null;
297
+ } catch (_error) {
298
+ return null;
299
+ }
300
+ }
301
+
302
+ function writeCache(cachePath, cache) {
303
+ try {
304
+ fs.mkdirSync(path.dirname(cachePath), { recursive: true });
305
+ const temporaryPath = cachePath + ".tmp-" + process.pid;
306
+ fs.writeFileSync(temporaryPath, JSON.stringify(cache) + "\n", "utf8");
307
+ fs.renameSync(temporaryPath, cachePath);
308
+ } catch (_error) {
309
+ try {
310
+ fs.unlinkSync(cachePath + ".tmp-" + process.pid);
311
+ } catch (_ignored) {
312
+ // Cache persistence is best effort only.
313
+ }
314
+ }
315
+ }
316
+
317
+ function readPackageVersion() {
318
+ try {
319
+ return require(path.join(__dirname, "..", "package.json")).version;
320
+ } catch (_error) {
321
+ return null;
322
+ }
323
+ }
324
+
325
+ function isValidVersion(version) {
326
+ return typeof version === "string" && /^\d+\.\d+\.\d+$/.test(version);
327
+ }
328
+
329
+ function compareVersions(left, right) {
330
+ const leftParts = left.split(".").map(Number);
331
+ const rightParts = right.split(".").map(Number);
332
+ for (let index = 0; index < 3; index += 1) {
333
+ if (leftParts[index] !== rightParts[index]) {
334
+ return leftParts[index] > rightParts[index] ? 1 : -1;
335
+ }
336
+ }
337
+ return 0;
338
+ }
339
+
340
+ function getMajor(version) {
341
+ return Number(version.split(".")[0]);
342
+ }
343
+
344
+ function formatUpdateNotice(result) {
345
+ if (!result || !result.hasUpdate) {
346
+ return "";
347
+ }
348
+ const lines = [
349
+ "npm notice",
350
+ "npm notice Update available for " + result.packageName + ": " + result.currentVersion + " -> " + result.latestVersion,
351
+ "npm notice Run npm install --save-dev " + result.packageName + "@latest"
352
+ ];
353
+ if (result.isMajorUpdate) {
354
+ lines.push("npm notice Major update: first confirm Nexus Framework API compatibility.");
355
+ }
356
+ lines.push("npm notice");
357
+ return lines.join("\n");
358
+ }
359
+
360
+ module.exports = {
361
+ CACHE_TTL_MS,
362
+ DEFAULT_REGISTRY,
363
+ PACKAGE_NAME,
364
+ buildMetadataUrl,
365
+ checkForSdkUpdate,
366
+ compareVersions,
367
+ extractLatestVersion,
368
+ formatUpdateNotice,
369
+ getDefaultCachePath,
370
+ isDisabled,
371
+ isValidVersion,
372
+ normalizeRegistry,
373
+ readCachedSdkUpdate,
374
+ resolveRegistry
375
+ };
376
+
377
+ if (require.main === module) {
378
+ checkForSdkUpdate({ force: process.argv.includes("--install") })
379
+ .then((result) => {
380
+ const notice = formatUpdateNotice(result);
381
+ if (notice) {
382
+ process.stdout.write(notice + "\n");
383
+ }
384
+ })
385
+ .catch(() => {
386
+ process.exitCode = 0;
387
+ });
388
+ }
@@ -9,7 +9,7 @@
9
9
  "cwd": "${workspaceFolder}"
10
10
  },
11
11
  {
12
- "name": "Attach Nexus plugin main (9229)",
12
+ "name": "Attach Nexus plugin worker (9229)",
13
13
  "type": "node",
14
14
  "request": "attach",
15
15
  "address": "127.0.0.1",
@@ -11,18 +11,24 @@ npm run dev
11
11
  npm run dev:nexus -- --host C:\path\to\nexus.exe
12
12
  ```
13
13
 
14
- The deployable plugin package is `dist/__PLUGIN_ID__/`. Copy that directory to the Nexus executable's `Plugins/__PLUGIN_ID__/` directory, then restart Nexus.
14
+ SDK checks for a newer `@aiot-nexus/plugin-sdk` in two steps. The package `postinstall` refreshes a user cache; npm 7+ hides dependency lifecycle output by default, so use `npm install --foreground-scripts` to see it. The `nexus-plugin` CLI reads that cache and prints npm-style notices when it is fresh; missing or older-than-24-hours cache data is checked in the background without blocking the command. `npm install --ignore-scripts` skips only the install check, while CLI checks remain available.
15
15
 
16
- The renderer calls the main-process `demo.echo` operation through `@aiot-nexus/plugin-sdk`. Run `npm run validate` after changing the manifest or output layout.
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
+
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
+
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.
17
21
 
18
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.
19
23
 
20
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.
21
25
 
22
- 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 restart-sensitive manifest changes rebuild the full package and restart Nexus. Failed builds keep the previous deployment, and a failed restarted host rolls back the package. Add `--no-devtools`, `--inspect[=<port>]`, `--inspect-brk[=<port>]`, `--surface`, or `--contribution-id` after the second `--` as needed.
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.
23
27
 
24
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.
25
29
 
26
- For main-process 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 main (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.
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
+
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.
27
33
 
28
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
  }
@@ -9,7 +9,7 @@
9
9
  "cwd": "${workspaceFolder}"
10
10
  },
11
11
  {
12
- "name": "Attach Nexus plugin main (9229)",
12
+ "name": "Attach Nexus plugin worker (9229)",
13
13
  "type": "node",
14
14
  "request": "attach",
15
15
  "address": "127.0.0.1",
@@ -11,18 +11,24 @@ npm run dev
11
11
  npm run dev:nexus -- --host C:\path\to\nexus.exe
12
12
  ```
13
13
 
14
+ SDK checks for a newer `@aiot-nexus/plugin-sdk` in two steps. The package `postinstall` refreshes a user cache; npm 7+ hides dependency lifecycle output by default, so use `npm install --foreground-scripts` to see it. The `nexus-plugin` CLI reads that cache and prints npm-style notices when it is fresh; missing or older-than-24-hours cache data is checked in the background without blocking the command. `npm install --ignore-scripts` skips only the install check, while CLI checks remain available.
15
+
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
+
14
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.
15
19
 
16
- The deployable plugin package is `dist/__PLUGIN_ID__/`. Copy that directory to the Nexus executable's `Plugins/__PLUGIN_ID__/` directory, then restart Nexus. Runtime data is stored in the host-provided plugin data directory and is not written into the plugin package.
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.
17
21
 
18
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.
19
23
 
20
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.
21
25
 
22
- 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) before restarting Nexus. The database remains under the host-provided plugin data directory across restarts. Failed builds keep the previous deployment, and a failed restarted host rolls back the package. Add `--no-devtools`, `--inspect[=<port>]`, `--inspect-brk[=<port>]`, `--surface`, or `--contribution-id` after the second `--` as needed.
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.
23
27
 
24
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.
25
29
 
26
- For main-process 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 main (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.
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
+
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.
27
33
 
28
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.
@@ -1,6 +1,7 @@
1
1
  declare module "better-sqlite3" {
2
2
  class Database {
3
3
  constructor(filename: string);
4
+ close(): void;
4
5
  pragma(source: string): unknown;
5
6
  prepare(source: string): Database.Statement;
6
7
  }
@@ -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
  }