@aiot-nexus/plugin-sdk 1.0.1 → 1.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.
@@ -6,17 +6,23 @@ AIoT Nexus independent PluginFrame plugin generated from the `sqlite` template.
6
6
  npm install
7
7
  npm run typecheck
8
8
  npm run build
9
+ npm run dev:web
9
10
  npm run dev
11
+ npm run dev:nexus -- --host C:\path\to\nexus.exe
10
12
  ```
11
13
 
12
14
  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.
13
15
 
14
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.
15
17
 
16
- The renderer follows `context.language` from Nexus, subscribes to in-place context updates without reloading the iframe, and includes `en-US` and `zh-CN` message catalogs. 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.
18
+ 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
+
20
+ 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
+
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.
17
23
 
18
24
  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.
19
25
 
20
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.
21
27
 
22
- The independent host stores the SQLite database and settings under `.nexus-plugin-dev/data/` and mirrors the PluginFrame protocol and current Host API surface. Run the deployable `dist/__PLUGIN_ID__/` in Nexus before delivery because the standalone host does not reproduce the full application shell or routing state.
28
+ 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.
@@ -9,7 +9,10 @@ const ts = require("typescript");
9
9
  const pluginId = "__PLUGIN_ID__";
10
10
  const pluginRoot = path.resolve(__dirname, "..");
11
11
  const buildDirectory = path.join(pluginRoot, ".build");
12
- const outputDirectory = path.join(pluginRoot, "dist", pluginId);
12
+ const rendererOnly = readRendererOnlyOption(process.argv.slice(2));
13
+ const outputDirectory = rendererOnly
14
+ ? path.join(pluginRoot, ".nexus-plugin-dev", "web-package", pluginId)
15
+ : path.join(pluginRoot, "dist", pluginId);
13
16
  const nodeModulesDirectory = path.join(pluginRoot, "node_modules");
14
17
  const sdkModuleSpecifier = "@aiot-nexus/plugin-sdk/browser";
15
18
  const runtimePackages = ["better-sqlite3", "bindings", "file-uri-to-path"];
@@ -17,15 +20,21 @@ const runtimePackages = ["better-sqlite3", "bindings", "file-uri-to-path"];
17
20
  run();
18
21
 
19
22
  function run() {
20
- verifyNativeDependency();
23
+ if (!rendererOnly) {
24
+ verifyNativeDependency();
25
+ }
21
26
  fs.rmSync(buildDirectory, { recursive: true, force: true });
22
27
  fs.rmSync(outputDirectory, { recursive: true, force: true });
23
- runTypeScript("tsconfig.main.json");
28
+ if (!rendererOnly) {
29
+ runTypeScript("tsconfig.main.json");
30
+ }
24
31
  runTypeScript("tsconfig.renderer.json");
25
32
 
26
33
  fs.mkdirSync(path.join(outputDirectory, "renderer", "assets"), { recursive: true });
27
34
  fs.copyFileSync(path.join(pluginRoot, "src", "manifest.json"), path.join(outputDirectory, "manifest.json"));
28
- fs.copyFileSync(path.join(buildDirectory, "main", "main.js"), path.join(outputDirectory, "main.cjs"));
35
+ if (!rendererOnly) {
36
+ fs.copyFileSync(path.join(buildDirectory, "main", "main.js"), path.join(outputDirectory, "main.cjs"));
37
+ }
29
38
  fs.copyFileSync(path.join(pluginRoot, "src", "renderer", "index.html"), path.join(outputDirectory, "renderer", "index.html"));
30
39
  fs.copyFileSync(path.join(pluginRoot, "src", "renderer", "plugin.css"), path.join(outputDirectory, "renderer", "assets", "plugin.css"));
31
40
  rewriteRendererSdkImport(
@@ -33,11 +42,23 @@ function run() {
33
42
  path.join(outputDirectory, "renderer", "assets", "app.js")
34
43
  );
35
44
  copyBrowserSdk();
36
- for (const packageName of runtimePackages) {
37
- copyRuntimePackage(packageName);
45
+ if (!rendererOnly) {
46
+ for (const packageName of runtimePackages) {
47
+ copyRuntimePackage(packageName);
48
+ }
38
49
  }
39
50
  fs.rmSync(buildDirectory, { recursive: true, force: true });
40
- console.log("[" + pluginId + "] built " + outputDirectory);
51
+ console.log("[" + pluginId + "] built " + (rendererOnly ? "browser UI package " : "") + outputDirectory);
52
+ }
53
+
54
+ function readRendererOnlyOption(args) {
55
+ if (args.length === 0) {
56
+ return false;
57
+ }
58
+ if (args.length === 1 && args[0] === "--renderer-only") {
59
+ return true;
60
+ }
61
+ throw new Error("Unknown build option: " + args.join(" "));
41
62
  }
42
63
 
43
64
  function verifyNativeDependency() {
@@ -16,6 +16,7 @@ interface SqliteRecord {
16
16
  }
17
17
 
18
18
  type SupportedLanguage = "en-US" | "zh-CN";
19
+ type ThemeMode = "light" | "dark" | "system";
19
20
  type MessageParams = Record<string, string | number>;
20
21
 
21
22
  const englishMessages = {
@@ -133,8 +134,10 @@ const elements = {
133
134
  recordsBody: requireElement<HTMLTableSectionElement>("recordsBody"),
134
135
  statusLog: requireElement<HTMLElement>("statusLog")
135
136
  };
137
+ const systemDarkMode = window.matchMedia("(prefers-color-scheme: dark)");
136
138
 
137
139
  let language: SupportedLanguage = "en-US";
140
+ let themeMode: ThemeMode = "system";
138
141
  let records: SqliteRecord[] = [];
139
142
  let editingRecord: SqliteRecord | undefined;
140
143
  let pendingDeleteId: number | undefined;
@@ -154,19 +157,22 @@ elements.titleInput.addEventListener("keydown", (event) => {
154
157
  }
155
158
  });
156
159
  const stopContextUpdates = client.onContextChanged((context) => {
157
- applyLanguage(resolveLanguage(context.language));
160
+ applyContext(context);
158
161
  });
162
+ systemDarkMode.addEventListener("change", handleSystemThemeChange);
159
163
  window.addEventListener("beforeunload", () => {
160
164
  stopContextUpdates();
165
+ systemDarkMode.removeEventListener("change", handleSystemThemeChange);
161
166
  client.dispose();
162
167
  });
163
168
 
164
- applyLanguage(language);
165
-
166
169
  void client.waitForContext().then((context) => {
167
- applyLanguage(resolveLanguage(context.language));
170
+ applyContext(context);
168
171
  return refreshAll();
169
172
  }).catch((error: unknown) => {
173
+ applyLanguage(language);
174
+ applyTheme(themeMode);
175
+ revealPluginContent();
170
176
  writeStatusMessage("error.initializationFailed", { message: errorMessage(error) });
171
177
  });
172
178
 
@@ -389,6 +395,33 @@ function applyLanguage(nextLanguage: SupportedLanguage): void {
389
395
  client.notifyResize();
390
396
  }
391
397
 
398
+ function applyContext(context: { language: string; themeMode: ThemeMode }): void {
399
+ applyLanguage(resolveLanguage(context.language));
400
+ applyTheme(context.themeMode);
401
+ revealPluginContent();
402
+ }
403
+
404
+ function revealPluginContent(): void {
405
+ document.documentElement.dataset.contextReady = "true";
406
+ document.body.hidden = false;
407
+ client.notifyResize();
408
+ }
409
+
410
+ function applyTheme(nextThemeMode: ThemeMode): void {
411
+ themeMode = nextThemeMode;
412
+ const resolvedTheme = themeMode === "system"
413
+ ? (systemDarkMode.matches ? "dark" : "light")
414
+ : themeMode;
415
+ document.documentElement.dataset.theme = resolvedTheme;
416
+ document.documentElement.style.colorScheme = resolvedTheme;
417
+ }
418
+
419
+ function handleSystemThemeChange(): void {
420
+ if (themeMode === "system") {
421
+ applyTheme(themeMode);
422
+ }
423
+ }
424
+
392
425
  function renderState(state: LocalizedState): string {
393
426
  return state.kind === "message" ? text(state.key, state.params) : state.value;
394
427
  }
@@ -1,12 +1,12 @@
1
1
  <!doctype html>
2
- <html lang="en-US">
2
+ <html lang="en-US" data-context-ready="false">
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title data-i18n="document.title">Plugin SQLite</title>
7
7
  <link rel="stylesheet" href="assets/plugin.css">
8
8
  </head>
9
- <body>
9
+ <body hidden>
10
10
  <main class="plugin-shell">
11
11
  <header class="plugin-header">
12
12
  <div>
@@ -1,5 +1,5 @@
1
1
  :root {
2
- color-scheme: light dark;
2
+ color-scheme: light;
3
3
  font-family: "Segoe UI", Arial, sans-serif;
4
4
  background: #f3f5f7;
5
5
  color: #20252b;
@@ -9,6 +9,10 @@
9
9
  box-sizing: border-box;
10
10
  }
11
11
 
12
+ :root:not([data-context-ready="true"]) .plugin-shell {
13
+ visibility: hidden;
14
+ }
15
+
12
16
  body {
13
17
  margin: 0;
14
18
  min-height: 0;
@@ -24,6 +28,7 @@ body {
24
28
  .plugin-header,
25
29
  .summary,
26
30
  .panel {
31
+ min-width: 0;
27
32
  border: 1px solid #d4d9df;
28
33
  border-radius: 8px;
29
34
  background: #ffffff;
@@ -37,6 +42,10 @@ body {
37
42
  padding: 14px 16px;
38
43
  }
39
44
 
45
+ .plugin-header > div {
46
+ min-width: 0;
47
+ }
48
+
40
49
  h1,
41
50
  h2,
42
51
  p {
@@ -180,6 +189,8 @@ button.button-danger {
180
189
  }
181
190
 
182
191
  .table-wrap {
192
+ min-width: 0;
193
+ max-width: 100%;
183
194
  overflow: auto;
184
195
  border: 1px solid #d4d9df;
185
196
  border-radius: 6px;
@@ -229,47 +240,50 @@ td {
229
240
  }
230
241
  }
231
242
 
232
- @media (prefers-color-scheme: dark) {
233
- :root,
234
- body {
235
- background: #16191d;
236
- color: #edf0f2;
237
- }
243
+ :root[data-theme="dark"] {
244
+ color-scheme: dark;
245
+ background: #16191d;
246
+ color: #edf0f2;
247
+ }
238
248
 
239
- .plugin-header,
240
- .summary,
241
- .panel {
242
- border-color: #454b52;
243
- background: #24282d;
244
- }
249
+ :root[data-theme="dark"] body {
250
+ background: #16191d;
251
+ color: #edf0f2;
252
+ }
245
253
 
246
- .plugin-header p,
247
- .summary span,
248
- #statusLog,
249
- .empty-state {
250
- color: #aeb7c2;
251
- }
254
+ :root[data-theme="dark"] .plugin-header,
255
+ :root[data-theme="dark"] .summary,
256
+ :root[data-theme="dark"] .panel {
257
+ border-color: #454b52;
258
+ background: #24282d;
259
+ }
252
260
 
253
- input,
254
- textarea,
255
- button.button-secondary {
256
- border-color: #59616a;
257
- background: #191c20;
258
- color: #edf0f2;
259
- }
261
+ :root[data-theme="dark"] .plugin-header p,
262
+ :root[data-theme="dark"] .summary span,
263
+ :root[data-theme="dark"] #statusLog,
264
+ :root[data-theme="dark"] .empty-state {
265
+ color: #aeb7c2;
266
+ }
260
267
 
261
- .table-wrap,
262
- .empty-state {
263
- border-color: #454b52;
264
- }
268
+ :root[data-theme="dark"] input,
269
+ :root[data-theme="dark"] textarea,
270
+ :root[data-theme="dark"] button.button-secondary {
271
+ border-color: #59616a;
272
+ background: #191c20;
273
+ color: #edf0f2;
274
+ }
265
275
 
266
- th {
267
- background: #2d3238;
268
- color: #d2d8de;
269
- }
276
+ :root[data-theme="dark"] .table-wrap,
277
+ :root[data-theme="dark"] .empty-state {
278
+ border-color: #454b52;
279
+ }
270
280
 
271
- th,
272
- td {
273
- border-bottom-color: #454b52;
274
- }
281
+ :root[data-theme="dark"] th {
282
+ background: #2d3238;
283
+ color: #d2d8de;
284
+ }
285
+
286
+ :root[data-theme="dark"] th,
287
+ :root[data-theme="dark"] td {
288
+ border-bottom-color: #454b52;
275
289
  }