@aiot-nexus/plugin-sdk 1.1.0 → 1.1.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 CHANGED
@@ -27,6 +27,26 @@ nexus-plugin dev:nexus [plugin-project-directory] --host <nexus.exe-or-directory
27
27
 
28
28
  `npm run pack:sdk` 将离线安装包写入 `package/`。该目录是本地构建产物,不进入 Git。`npm run publish:sdk` 发布到官方 npm registry;`prepack` 会强制运行测试、构建并核对实际 npm 文件清单,缺少任一公开入口时拒绝发布。
29
29
 
30
+ ## SDK 版本提示
31
+
32
+ SDK 提供两层非阻塞版本检查:
33
+
34
+ - 安装阶段的 `postinstall` 会强制查询 `dist-tags.latest` 并写入当前用户缓存。npm 7+ 默认隐藏依赖包的 lifecycle 输出;需要看到安装阶段 notice 时使用 `npm install --foreground-scripts`。`npm install --ignore-scripts` 会跳过该阶段,但不影响后续 CLI 检查。
35
+ - `nexus-plugin` 启动时先读取缓存。缓存新鲜期为 24 小时;缓存缺失或过期时在后台检查,不等待网络,不改变命令退出码。检测到普通更新时会显示:
36
+
37
+ ```text
38
+ npm notice Update available for @aiot-nexus/plugin-sdk: 1.1.1 -> 1.2.0
39
+ npm notice Run npm install --save-dev @aiot-nexus/plugin-sdk@latest
40
+ ```
41
+
42
+ 检查会优先使用 npm 当前配置的 registry(包括私有 registry),主源失败后回退官方 npm registry;不会发送 token、密码或项目路径。离线、超时、registry 返回非法数据或缓存损坏都静默降级,安装、构建、调试和正式宿主启动不会被阻断。
43
+
44
+ 大版本更新会额外提示先确认 Nexus Framework API 兼容性;检查不会自动修改 `package.json`、升级依赖或新增交互式命令。确认兼容后,推荐手动执行 `npm install --save-dev @aiot-nexus/plugin-sdk@latest`。
45
+
46
+ 可用环境变量:`NEXUS_PLUGIN_SDK_UPDATE_CHECK=0` 或 `NO_UPDATE_NOTIFIER=1` 会同时关闭安装和 CLI 检查。内部 SDK 打包环境也会自动跳过联网检查。
47
+
48
+ 使用本地 `--sdk-source ...tgz` 时,生成工程依赖的是 tgz 内记录的 SDK 版本;安装阶段仍可能按 npm registry 写入该版本的缓存,CLI 后续按“当前版本 + registry”读取。完全离线开发可设置上述禁用变量,或接受检查静默失败;本地 SDK 不会自动替换正式 npm SDK,也不会因为有新版本自动升级。
49
+
30
50
  ## 创建插件
31
51
 
32
52
  ```bash
@@ -44,7 +64,7 @@ nexus-plugin create my-frame-plugin \
44
64
  - 目标目录非空时命令以返回码 `2` 拒绝覆盖。生成或安装失败返回 `1`。
45
65
  - 两套模板均读取宿主 `PluginRendererContext.language` 和 `themeMode`,通过 `onContextChanged` 在同一 iframe 内响应运行时语言与主题切换,并内置 `zh-CN` / `en-US` renderer 文案;manifest 使用相同文化资源本地化宿主入口。模板页面自身不提供语言或主题选择控件。
46
66
 
47
- 默认来源从 npm registry 安装。离线开发时先执行 `npm run pack:sdk`,再通过 `--sdk-source /path/to/aiot-nexus-plugin-sdk-1.1.0.tgz` 指定本地包;CLI 会把本地包一并放入生成工程。
67
+ 默认来源从 npm registry 安装。离线开发时先执行 `npm run pack:sdk`,再通过 `--sdk-source /path/to/aiot-nexus-plugin-sdk-1.1.1.tgz` 指定本地包;CLI 会把本地包一并放入生成工程。
48
68
 
49
69
  ## 独立调试
50
70
 
@@ -0,0 +1,20 @@
1
+ /// <reference types="node" />
2
+ export interface SdkUpdateResult {
3
+ status: string;
4
+ hasUpdate: boolean;
5
+ packageName: string;
6
+ currentVersion: string | null;
7
+ latestVersion: string | null;
8
+ isMajorUpdate: boolean;
9
+ }
10
+ export interface SdkUpdateCheckModule {
11
+ checkForSdkUpdate(options: {
12
+ currentVersion: string;
13
+ }): Promise<SdkUpdateResult>;
14
+ formatUpdateNotice(result: SdkUpdateResult): string;
15
+ isDisabled(env: NodeJS.ProcessEnv): boolean;
16
+ readCachedSdkUpdate(options: {
17
+ currentVersion: string;
18
+ }): SdkUpdateResult | null;
19
+ }
20
+ export declare function showSdkUpdateNotice(args: string[], stdout: (message: string) => void, updateCheck: SdkUpdateCheckModule, currentVersion: string): void;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.showSdkUpdateNotice = void 0;
4
+ function showSdkUpdateNotice(args, stdout, updateCheck, currentVersion) {
5
+ if (args.includes("--help") || args.includes("-h") || updateCheck.isDisabled(process.env)) {
6
+ return;
7
+ }
8
+ const cached = updateCheck.readCachedSdkUpdate({ currentVersion });
9
+ if (cached && cached.hasUpdate) {
10
+ const notice = updateCheck.formatUpdateNotice(cached);
11
+ if (notice) {
12
+ stdout(notice);
13
+ }
14
+ }
15
+ if (cached) {
16
+ return;
17
+ }
18
+ void updateCheck
19
+ .checkForSdkUpdate({ currentVersion })
20
+ .then((result) => {
21
+ if (result.status !== "checked" || !result.hasUpdate) {
22
+ return;
23
+ }
24
+ const notice = updateCheck.formatUpdateNotice(result);
25
+ if (notice) {
26
+ stdout(notice);
27
+ }
28
+ })
29
+ .catch(() => {
30
+ // Version checks are advisory and must never affect a CLI command.
31
+ });
32
+ }
33
+ exports.showSdkUpdateNotice = showSdkUpdateNotice;
package/dist/src/cli.js CHANGED
@@ -1,12 +1,43 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ /* eslint-disable @typescript-eslint/no-var-requires */
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || function (mod) {
21
+ if (mod && mod.__esModule) return mod;
22
+ var result = {};
23
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
24
+ __setModuleDefault(result, mod);
25
+ return result;
26
+ };
3
27
  Object.defineProperty(exports, "__esModule", { value: true });
28
+ const path = __importStar(require("path"));
29
+ const index_1 = require("./index");
4
30
  const run_1 = require("./cli/run");
5
- const result = (0, run_1.runCli)(process.argv.slice(2), {
31
+ const update_notice_1 = require("./cli/update-notice");
32
+ const sdkUpdateCheck = require(path.resolve(__dirname, "../../scripts/sdk-update-check.js"));
33
+ const args = process.argv.slice(2);
34
+ const io = {
6
35
  cwd: () => process.cwd(),
7
36
  stdout: (message) => process.stdout.write(message + "\n"),
8
37
  stderr: (message) => process.stderr.write(message + "\n")
9
- });
38
+ };
39
+ (0, update_notice_1.showSdkUpdateNotice)(args, io.stdout, sdkUpdateCheck, index_1.PLUGIN_SDK_VERSION);
40
+ const result = (0, run_1.runCli)(args, io);
10
41
  if (typeof result === "number") {
11
42
  process.exitCode = result;
12
43
  }
@@ -1,5 +1,5 @@
1
1
  import pluginManifestSchema from "../.generated/plugin-manifest.schema.json";
2
- export declare const PLUGIN_SDK_VERSION = "1.1.0";
2
+ export declare const PLUGIN_SDK_VERSION = "1.1.1";
3
3
  export { pluginManifestSchema };
4
4
  export * from "../.generated/plugin-framework-version";
5
5
  export * from "./types";
package/dist/src/index.js CHANGED
@@ -20,7 +20,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
20
20
  exports.pluginManifestSchema = exports.PLUGIN_SDK_VERSION = void 0;
21
21
  const plugin_manifest_schema_json_1 = __importDefault(require("../.generated/plugin-manifest.schema.json"));
22
22
  exports.pluginManifestSchema = plugin_manifest_schema_json_1.default;
23
- exports.PLUGIN_SDK_VERSION = "1.1.0";
23
+ exports.PLUGIN_SDK_VERSION = "1.1.1";
24
24
  __exportStar(require("../.generated/plugin-framework-version"), exports);
25
25
  __exportStar(require("./types"), exports);
26
26
  __exportStar(require("./browser/frame-client"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiot-nexus/plugin-sdk",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Types, PluginFrame client, and CLI for AIoT Nexus independent plugins.",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": {
@@ -30,12 +30,14 @@
30
30
  "dist",
31
31
  "dev-host",
32
32
  "templates",
33
- "README.md"
33
+ "README.md",
34
+ "scripts/sdk-update-check.js"
34
35
  ],
35
36
  "scripts": {
36
37
  "build": "node scripts/build.js",
38
+ "postinstall": "node scripts/sdk-update-check.js --install",
37
39
  "verify:package": "node scripts/verify-package.js",
38
- "test": "npm run build && node tests/package.test.js && node tests/cli.test.js && node tests/dev.test.js && node tests/nexus-dev.test.js && node tests/frame-client.test.js && node tests/web-dev.test.js && npm run verify:package",
40
+ "test": "npm run build && node tests/package.test.js && node tests/sdk-update-check.test.js && node tests/cli.test.js && node tests/cli-entry.test.js && node tests/update-notice.test.js && node tests/dev.test.js && node tests/nexus-dev.test.js && node tests/frame-client.test.js && node tests/web-dev.test.js && npm run verify:package",
39
41
  "prepack": "node scripts/prepack.js",
40
42
  "pack:sdk": "npm run build && npm run verify:package && node scripts/pack.js",
41
43
  "publish:sdk": "npm publish"
@@ -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
+ }
@@ -11,6 +11,10 @@ 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
  The deployable plugin package is `dist/__PLUGIN_ID__/`. Copy that directory to the Nexus executable's `Plugins/__PLUGIN_ID__/` directory, then restart Nexus.
15
19
 
16
20
  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.
@@ -23,6 +27,8 @@ Use `npm run dev:nexus -- --host C:\path\to\nexus.exe` for live development agai
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
 
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
+
26
32
  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.
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.
@@ -11,6 +11,10 @@ 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
20
  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.
@@ -23,6 +27,8 @@ Use `npm run dev:nexus -- --host C:\path\to\nexus.exe` to run the plugin inside
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
 
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
+
26
32
  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.
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.