@aiot-nexus/plugin-sdk 1.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.
Files changed (47) hide show
  1. package/README.md +74 -0
  2. package/dev-host/host.html +344 -0
  3. package/dev-host/main.js +694 -0
  4. package/dev-host/preload.js +22 -0
  5. package/dist/.generated/plugin-framework-version.d.ts +11 -0
  6. package/dist/.generated/plugin-framework-version.js +39 -0
  7. package/dist/.generated/plugin-manifest.schema.json +106 -0
  8. package/dist/browser/frame-client.d.ts +26 -0
  9. package/dist/browser/frame-client.mjs +239 -0
  10. package/dist/src/browser/frame-client.d.ts +26 -0
  11. package/dist/src/browser/frame-client.js +244 -0
  12. package/dist/src/cli/run.d.ts +7 -0
  13. package/dist/src/cli/run.js +270 -0
  14. package/dist/src/cli.d.ts +2 -0
  15. package/dist/src/cli.js +9 -0
  16. package/dist/src/dev.d.ts +36 -0
  17. package/dist/src/dev.js +224 -0
  18. package/dist/src/index.d.ts +7 -0
  19. package/dist/src/index.js +27 -0
  20. package/dist/src/scaffold.d.ts +23 -0
  21. package/dist/src/scaffold.js +306 -0
  22. package/dist/src/types.d.ts +142 -0
  23. package/dist/src/types.js +2 -0
  24. package/dist/src/validation.d.ts +9 -0
  25. package/dist/src/validation.js +160 -0
  26. package/package.json +47 -0
  27. package/templates/minimal/.gitignore.tmpl +4 -0
  28. package/templates/minimal/.vscode/launch.json +22 -0
  29. package/templates/minimal/README.md.tmpl +22 -0
  30. package/templates/minimal/scripts/build.js.tmpl +83 -0
  31. package/templates/minimal/src/main.ts.tmpl +23 -0
  32. package/templates/minimal/src/renderer/app.ts.tmpl +194 -0
  33. package/templates/minimal/src/renderer/index.html +34 -0
  34. package/templates/minimal/src/renderer/plugin.css +164 -0
  35. package/templates/minimal/tsconfig.main.json +15 -0
  36. package/templates/minimal/tsconfig.renderer.json +17 -0
  37. package/templates/sqlite/.gitignore.tmpl +4 -0
  38. package/templates/sqlite/.vscode/launch.json +22 -0
  39. package/templates/sqlite/README.md.tmpl +22 -0
  40. package/templates/sqlite/scripts/build.js.tmpl +143 -0
  41. package/templates/sqlite/src/better-sqlite3.d.ts +22 -0
  42. package/templates/sqlite/src/main.ts.tmpl +190 -0
  43. package/templates/sqlite/src/renderer/app.ts.tmpl +428 -0
  44. package/templates/sqlite/src/renderer/index.html +57 -0
  45. package/templates/sqlite/src/renderer/plugin.css +275 -0
  46. package/templates/sqlite/tsconfig.main.json +15 -0
  47. package/templates/sqlite/tsconfig.renderer.json +17 -0
@@ -0,0 +1,142 @@
1
+ export interface NexusError {
2
+ code: string;
3
+ message: string;
4
+ details?: unknown;
5
+ }
6
+ export type NexusResult<T> = {
7
+ ok: true;
8
+ value: T;
9
+ } | {
10
+ ok: false;
11
+ error: NexusError;
12
+ };
13
+ export type ThemeMode = "light" | "dark" | "system";
14
+ export type LaunchPageParams = Record<string, string>;
15
+ export type PluginPermission = "businessLog" | "settings" | "filesystem" | "udp" | "serial" | "externalProcess" | "dialog";
16
+ export type PluginPermissionSupportState = "implemented" | "routeGuard" | "reserved";
17
+ export interface PluginPermissionSummary {
18
+ permission: PluginPermission;
19
+ supportState: PluginPermissionSupportState;
20
+ statusMessage: string;
21
+ }
22
+ export interface BusinessDescriptor {
23
+ businessId: string;
24
+ titleKey: string;
25
+ iconKey: string;
26
+ section: string;
27
+ order: number;
28
+ component: string;
29
+ descriptionKey?: string;
30
+ pageRoute?: string;
31
+ isFullBleed?: boolean;
32
+ showBusinessLogToggle?: boolean;
33
+ }
34
+ export interface ToolDescriptor {
35
+ toolId: string;
36
+ titleKey: string;
37
+ iconKey: string;
38
+ navGroup: string;
39
+ order: number;
40
+ component: string;
41
+ descriptionKey?: string;
42
+ }
43
+ export interface SettingsSectionDescriptor {
44
+ sectionId: string;
45
+ titleKey: string;
46
+ order: number;
47
+ component: string;
48
+ descriptionKey?: string;
49
+ }
50
+ export interface PluginManifest {
51
+ schemaVersion: number;
52
+ pluginId: string;
53
+ displayNameKey: string;
54
+ descriptionKey: string;
55
+ pluginVersion: string;
56
+ hostApiVersion: string;
57
+ minHostVersion: string;
58
+ supportedCultures: string[];
59
+ permissions: PluginPermission[];
60
+ mainEntry?: string;
61
+ rendererEntry?: string;
62
+ businessEntries?: BusinessDescriptor[];
63
+ tools?: ToolDescriptor[];
64
+ settingsSections?: SettingsSectionDescriptor[];
65
+ resources?: Record<string, Record<string, string>>;
66
+ deferred?: boolean;
67
+ }
68
+ export type PluginRendererSurface = "business" | "tool" | "settings";
69
+ export type PluginRendererMethod = "getContext" | "getSettings" | "updateSettings" | "appendLog" | "invokeMain";
70
+ export interface PluginRendererContext {
71
+ schemaVersion: number;
72
+ pluginId: string;
73
+ surface: PluginRendererSurface;
74
+ contributionId: string;
75
+ launchParams?: LaunchPageParams;
76
+ language: string;
77
+ themeMode: ThemeMode;
78
+ hostApiVersion: string;
79
+ }
80
+ export interface PluginSettingsDocument {
81
+ schemaVersion: number;
82
+ pluginId: string;
83
+ values: Record<string, unknown>;
84
+ }
85
+ export interface BusinessLogEntry {
86
+ timestamp: string;
87
+ level: "debug" | "info" | "warn" | "error";
88
+ category: string;
89
+ message: string;
90
+ exception?: string;
91
+ }
92
+ export interface BusinessLogAppendRequest {
93
+ level: BusinessLogEntry["level"];
94
+ message: string;
95
+ category?: string;
96
+ exception?: string;
97
+ }
98
+ export interface PluginRendererOperationRequest<TInput extends Record<string, unknown> = Record<string, unknown>> {
99
+ readonly pluginId: string;
100
+ readonly surface: PluginRendererSurface;
101
+ readonly contributionId: string;
102
+ readonly operation: string;
103
+ readonly input?: TInput;
104
+ }
105
+ export type PluginRendererOperationHandler<TInput extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (request: PluginRendererOperationRequest<TInput>) => NexusResult<TResult> | TResult;
106
+ export interface PluginUnsupportedCapabilityApi {
107
+ readonly permission: PluginPermission;
108
+ readonly supportState: PluginPermissionSupportState;
109
+ describe(): NexusResult<PluginPermissionSummary>;
110
+ unsupported(operation?: unknown): NexusResult<never>;
111
+ [operation: string]: unknown;
112
+ }
113
+ export interface PluginHostApi {
114
+ readonly hostVersion: string;
115
+ readonly hostApiVersion: string;
116
+ readonly pluginId: string;
117
+ readonly pluginDataDirectory: string;
118
+ 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: BusinessLogEntry["level"], message: string, exception?: string): NexusResult<BusinessLogEntry>;
123
+ getSettings(): NexusResult<PluginSettingsDocument>;
124
+ updateSettings(values: Record<string, unknown>): NexusResult<PluginSettingsDocument>;
125
+ filesystem: PluginUnsupportedCapabilityApi;
126
+ udp: PluginUnsupportedCapabilityApi;
127
+ serial: PluginUnsupportedCapabilityApi;
128
+ dialog: PluginUnsupportedCapabilityApi;
129
+ externalProcess: PluginUnsupportedCapabilityApi;
130
+ }
131
+ export interface PluginMainActivationContext {
132
+ readonly pluginId: string;
133
+ readonly pluginDirectory: string;
134
+ readonly manifest: PluginManifest;
135
+ readonly host: PluginHostApi;
136
+ }
137
+ export interface PluginMainModule {
138
+ activate?: (context: PluginMainActivationContext) => unknown;
139
+ default?: {
140
+ activate?: (context: PluginMainActivationContext) => unknown;
141
+ };
142
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,9 @@
1
+ import type { PluginManifest } from "./types";
2
+ export interface PluginPackageValidationResult {
3
+ valid: boolean;
4
+ packageDirectory: string;
5
+ pluginId?: string;
6
+ manifest?: PluginManifest;
7
+ errors: string[];
8
+ }
9
+ export declare function validatePluginPackage(packageDirectory: string): PluginPackageValidationResult;
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.validatePluginPackage = void 0;
30
+ const _2020_1 = __importDefault(require("ajv/dist/2020"));
31
+ const fs = __importStar(require("fs"));
32
+ const path = __importStar(require("path"));
33
+ const plugin_manifest_schema_json_1 = __importDefault(require("../.generated/plugin-manifest.schema.json"));
34
+ const plugin_framework_version_1 = require("../.generated/plugin-framework-version");
35
+ const manifestValidator = new _2020_1.default({ allErrors: true, strict: true })
36
+ .compile(plugin_manifest_schema_json_1.default);
37
+ function validatePluginPackage(packageDirectory) {
38
+ const resolvedDirectory = path.resolve(packageDirectory);
39
+ try {
40
+ return validateResolvedPluginPackage(resolvedDirectory);
41
+ }
42
+ catch (error) {
43
+ return invalid(resolvedDirectory, "Plugin package could not be inspected: " + errorMessage(error));
44
+ }
45
+ }
46
+ exports.validatePluginPackage = validatePluginPackage;
47
+ function validateResolvedPluginPackage(resolvedDirectory) {
48
+ const errors = [];
49
+ if (!fs.existsSync(resolvedDirectory)) {
50
+ return invalid(resolvedDirectory, "Plugin package directory does not exist.");
51
+ }
52
+ const directoryStat = fs.lstatSync(resolvedDirectory);
53
+ if (!directoryStat.isDirectory()) {
54
+ return invalid(resolvedDirectory, "Plugin package path is not a directory.");
55
+ }
56
+ if (directoryStat.isSymbolicLink()) {
57
+ return invalid(resolvedDirectory, "Plugin package directory cannot be a symbolic link.");
58
+ }
59
+ const manifestPath = path.join(resolvedDirectory, "manifest.json");
60
+ if (!fs.existsSync(manifestPath)) {
61
+ return invalid(resolvedDirectory, "manifest.json is missing.");
62
+ }
63
+ const manifestStat = fs.lstatSync(manifestPath);
64
+ if (!manifestStat.isFile() || manifestStat.isSymbolicLink()) {
65
+ return invalid(resolvedDirectory, "manifest.json must be a regular file and cannot be a symbolic link.");
66
+ }
67
+ if (!isPathInside(fs.realpathSync(resolvedDirectory), fs.realpathSync(manifestPath))) {
68
+ return invalid(resolvedDirectory, "manifest.json resolves outside the plugin package directory.");
69
+ }
70
+ let parsed;
71
+ try {
72
+ parsed = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
73
+ }
74
+ catch (error) {
75
+ return invalid(resolvedDirectory, "manifest.json is not valid JSON: " + errorMessage(error));
76
+ }
77
+ if (!manifestValidator(parsed)) {
78
+ return {
79
+ valid: false,
80
+ packageDirectory: resolvedDirectory,
81
+ errors: formatAjvErrors(manifestValidator.errors)
82
+ };
83
+ }
84
+ const manifest = parsed;
85
+ if (!(0, plugin_framework_version_1.isPluginFrameworkVersionCompatible)(manifest.hostApiVersion, plugin_framework_version_1.PLUGIN_FRAMEWORK_VERSION)) {
86
+ errors.push("Plugin Framework API version is incompatible with " + plugin_framework_version_1.PLUGIN_FRAMEWORK_VERSION + ".");
87
+ }
88
+ if (!(0, plugin_framework_version_1.parsePluginCompatibleVersion)(manifest.minHostVersion)) {
89
+ errors.push("minHostVersion contains an unsupported numeric version.");
90
+ }
91
+ validateEntry(resolvedDirectory, "mainEntry", manifest.mainEntry, errors);
92
+ validateEntry(resolvedDirectory, "rendererEntry", manifest.rendererEntry, errors);
93
+ if (manifest.rendererEntry && path.extname(manifest.rendererEntry).toLowerCase() !== ".html") {
94
+ errors.push("rendererEntry must point to an HTML file.");
95
+ }
96
+ if (usesPluginFrame(manifest) && !manifest.rendererEntry) {
97
+ errors.push("PluginFrame contributions require rendererEntry.");
98
+ }
99
+ return {
100
+ valid: errors.length === 0,
101
+ packageDirectory: resolvedDirectory,
102
+ pluginId: manifest.pluginId,
103
+ manifest,
104
+ errors
105
+ };
106
+ }
107
+ function validateEntry(rootDirectory, fieldName, relativePath, errors) {
108
+ if (!relativePath) {
109
+ return;
110
+ }
111
+ if (path.isAbsolute(relativePath)) {
112
+ errors.push(fieldName + " must be relative to the plugin package directory.");
113
+ return;
114
+ }
115
+ const entryPath = path.resolve(rootDirectory, relativePath);
116
+ if (!isPathInside(rootDirectory, entryPath)) {
117
+ errors.push(fieldName + " escapes the plugin package directory.");
118
+ return;
119
+ }
120
+ if (!fs.existsSync(entryPath)) {
121
+ errors.push(fieldName + " does not exist: " + relativePath);
122
+ return;
123
+ }
124
+ const entryStat = fs.lstatSync(entryPath);
125
+ if (!entryStat.isFile()) {
126
+ errors.push(fieldName + " must point to a file: " + relativePath);
127
+ return;
128
+ }
129
+ if (entryStat.isSymbolicLink()) {
130
+ errors.push(fieldName + " cannot be a symbolic link: " + relativePath);
131
+ return;
132
+ }
133
+ if (!isPathInside(fs.realpathSync(rootDirectory), fs.realpathSync(entryPath))) {
134
+ errors.push(fieldName + " resolves outside the plugin package directory.");
135
+ }
136
+ }
137
+ function usesPluginFrame(manifest) {
138
+ return (manifest.businessEntries || []).some((entry) => entry.component === "PluginFrame")
139
+ || (manifest.tools || []).some((entry) => entry.component === "PluginFrame")
140
+ || (manifest.settingsSections || []).some((entry) => entry.component === "PluginFrame");
141
+ }
142
+ function isPathInside(rootDirectory, childPath) {
143
+ const relative = path.relative(path.resolve(rootDirectory), path.resolve(childPath));
144
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
145
+ }
146
+ function invalid(packageDirectory, message) {
147
+ return { valid: false, packageDirectory, errors: [message] };
148
+ }
149
+ function formatAjvErrors(errors) {
150
+ return (errors || []).map((error) => {
151
+ const location = error.instancePath || "/";
152
+ const missingProperty = typeof error.params === "object" && error.params !== null && "missingProperty" in error.params
153
+ ? String(error.params.missingProperty)
154
+ : "";
155
+ return [location, missingProperty, error.message || "schema validation failed"].filter(Boolean).join(" ");
156
+ });
157
+ }
158
+ function errorMessage(error) {
159
+ return error instanceof Error ? error.message : String(error);
160
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@aiot-nexus/plugin-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Types, PluginFrame client, and CLI for AIoT Nexus independent plugins.",
5
+ "license": "UNLICENSED",
6
+ "main": "./dist/src/index.js",
7
+ "types": "./dist/src/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/src/index.d.ts",
11
+ "require": "./dist/src/index.js",
12
+ "default": "./dist/src/index.js"
13
+ },
14
+ "./browser": {
15
+ "types": "./dist/browser/frame-client.d.ts",
16
+ "import": "./dist/browser/frame-client.mjs",
17
+ "default": "./dist/browser/frame-client.mjs"
18
+ },
19
+ "./manifest-schema": "./dist/.generated/plugin-manifest.schema.json",
20
+ "./package.json": "./package.json"
21
+ },
22
+ "bin": {
23
+ "nexus-plugin": "./dist/src/cli.js"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "dev-host",
28
+ "templates",
29
+ "README.md"
30
+ ],
31
+ "scripts": {
32
+ "build": "node scripts/build.js",
33
+ "test": "npm run build && node tests/package.test.js && node tests/cli.test.js && node tests/frame-client.test.js",
34
+ "pack:sdk": "npm run build && node scripts/pack.js"
35
+ },
36
+ "dependencies": {
37
+ "ajv": "8.17.1"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "16.18.126",
41
+ "typescript": "4.9.4"
42
+ },
43
+ "engines": {
44
+ "node": ">=16.20.0 <17",
45
+ "npm": ">=6.13.4"
46
+ }
47
+ }
@@ -0,0 +1,4 @@
1
+ node_modules/
2
+ dist/
3
+ .build/
4
+ .nexus-plugin-dev/
@@ -0,0 +1,22 @@
1
+ {
2
+ "version": "0.2.0",
3
+ "configurations": [
4
+ {
5
+ "name": "Debug Nexus plugin",
6
+ "type": "node-terminal",
7
+ "request": "launch",
8
+ "command": "npm run dev:debug",
9
+ "cwd": "${workspaceFolder}"
10
+ },
11
+ {
12
+ "name": "Attach Nexus plugin main (9229)",
13
+ "type": "node",
14
+ "request": "attach",
15
+ "address": "127.0.0.1",
16
+ "port": 9229,
17
+ "sourceMaps": true,
18
+ "outFiles": ["${workspaceFolder}/dist/**/*.cjs"],
19
+ "skipFiles": ["<node_internals>/**"]
20
+ }
21
+ ]
22
+ }
@@ -0,0 +1,22 @@
1
+ # __DISPLAY_NAME_MARKDOWN__
2
+
3
+ AIoT Nexus independent PluginFrame plugin generated from the `minimal` template.
4
+
5
+ ```bash
6
+ npm install
7
+ npm run typecheck
8
+ npm run build
9
+ npm run dev
10
+ ```
11
+
12
+ The deployable plugin package is `dist/__PLUGIN_ID__/`. Copy that directory to the Nexus executable's `Plugins/__PLUGIN_ID__/` directory, then restart Nexus.
13
+
14
+ 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.
15
+
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. Add or edit renderer messages in `src/renderer/app.ts`; edit host-shell titles and descriptions in `src/manifest.json` resources.
17
+
18
+ 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
+
20
+ 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.
21
+
22
+ The independent host stores data under `.nexus-plugin-dev/` 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.
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const { spawnSync } = require("child_process");
7
+ const ts = require("typescript");
8
+
9
+ const pluginId = "__PLUGIN_ID__";
10
+ const pluginRoot = path.resolve(__dirname, "..");
11
+ const buildDirectory = path.join(pluginRoot, ".build");
12
+ const outputDirectory = path.join(pluginRoot, "dist", pluginId);
13
+ const sdkModuleSpecifier = "@aiot-nexus/plugin-sdk/browser";
14
+
15
+ run();
16
+
17
+ function run() {
18
+ fs.rmSync(buildDirectory, { recursive: true, force: true });
19
+ fs.rmSync(outputDirectory, { recursive: true, force: true });
20
+ runTypeScript("tsconfig.main.json");
21
+ runTypeScript("tsconfig.renderer.json");
22
+
23
+ fs.mkdirSync(path.join(outputDirectory, "renderer", "assets"), { recursive: true });
24
+ fs.copyFileSync(path.join(pluginRoot, "src", "manifest.json"), path.join(outputDirectory, "manifest.json"));
25
+ fs.copyFileSync(path.join(buildDirectory, "main", "main.js"), path.join(outputDirectory, "main.cjs"));
26
+ fs.copyFileSync(path.join(pluginRoot, "src", "renderer", "index.html"), path.join(outputDirectory, "renderer", "index.html"));
27
+ fs.copyFileSync(path.join(pluginRoot, "src", "renderer", "plugin.css"), path.join(outputDirectory, "renderer", "assets", "plugin.css"));
28
+ rewriteRendererSdkImport(
29
+ path.join(buildDirectory, "renderer", "app.js"),
30
+ path.join(outputDirectory, "renderer", "assets", "app.js")
31
+ );
32
+ copyBrowserSdk();
33
+ fs.rmSync(buildDirectory, { recursive: true, force: true });
34
+ console.log("[" + pluginId + "] built " + outputDirectory);
35
+ }
36
+
37
+ function runTypeScript(configName) {
38
+ const tscPath = require.resolve("typescript/bin/tsc");
39
+ const args = [tscPath, "-p", path.join(pluginRoot, configName)];
40
+ if (process.env.NEXUS_PLUGIN_DEV === "1") {
41
+ args.push("--inlineSourceMap", "--inlineSources");
42
+ }
43
+ const result = spawnSync(process.execPath, args, {
44
+ cwd: pluginRoot,
45
+ stdio: "inherit"
46
+ });
47
+ if (result.error) {
48
+ throw result.error;
49
+ }
50
+ if (result.status !== 0) {
51
+ process.exit(result.status || 1);
52
+ }
53
+ }
54
+
55
+ function rewriteRendererSdkImport(sourcePath, outputPath) {
56
+ const sourceText = fs.readFileSync(sourcePath, "utf8");
57
+ const sourceFile = ts.createSourceFile(sourcePath, sourceText, ts.ScriptTarget.ES2020, true, ts.ScriptKind.JS);
58
+ const matches = [];
59
+ sourceFile.forEachChild(function findSdkImport(node) {
60
+ if (ts.isImportDeclaration(node)
61
+ && ts.isStringLiteral(node.moduleSpecifier)
62
+ && node.moduleSpecifier.text === sdkModuleSpecifier) {
63
+ matches.push(node.moduleSpecifier);
64
+ }
65
+ });
66
+ if (matches.length !== 1) {
67
+ throw new Error("Expected one browser SDK import, found " + matches.length + ".");
68
+ }
69
+ const moduleSpecifier = matches[0];
70
+ const rewritten = sourceText.slice(0, moduleSpecifier.getStart(sourceFile))
71
+ + JSON.stringify("./plugin-sdk.mjs")
72
+ + sourceText.slice(moduleSpecifier.getEnd());
73
+ fs.writeFileSync(outputPath, rewritten, "utf8");
74
+ }
75
+
76
+ function copyBrowserSdk() {
77
+ const sdkPackageRoot = path.dirname(require.resolve("@aiot-nexus/plugin-sdk/package.json"));
78
+ const sourcePath = path.join(sdkPackageRoot, "dist", "browser", "frame-client.mjs");
79
+ if (!fs.existsSync(sourcePath)) {
80
+ throw new Error("Plugin SDK browser module is missing: " + sourcePath);
81
+ }
82
+ fs.copyFileSync(sourcePath, path.join(outputDirectory, "renderer", "assets", "plugin-sdk.mjs"));
83
+ }
@@ -0,0 +1,23 @@
1
+ import type { PluginMainActivationContext } from "@aiot-nexus/plugin-sdk";
2
+
3
+ export function activate(context: PluginMainActivationContext): void {
4
+ const registration = context.host.registerRendererMethod("demo.echo", (request) => {
5
+ const value = request.input && typeof request.input.value === "string"
6
+ ? request.input.value.trim()
7
+ : "";
8
+ if (!value || value.length > 200) {
9
+ return {
10
+ ok: false,
11
+ error: {
12
+ code: "InvalidRequest",
13
+ message: "Echo value must contain 1 to 200 characters."
14
+ }
15
+ };
16
+ }
17
+ return { value, echoedAt: new Date().toISOString() };
18
+ });
19
+ if (!registration.ok) {
20
+ throw new Error(registration.error.message);
21
+ }
22
+ context.host.log("info", "__PLUGIN_ID__ activated.");
23
+ }