npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aiot-nexus/plugin-sdk

v3.2.0

Published

Types, PluginFrame client, and CLI for AIoT Nexus independent plugins.

Readme

@aiot-nexus/plugin-sdk

当前 SDK 包版本为 3.2.0,Framework API 为 3.2.0,manifest schema 保持 1。Framework 3 将可信插件的 mainEntry 直接加载到 Nexus Electron 主进程;它不是安全沙箱。PluginFrame iframe 仍保持 sandbox、无 Node/Electron 访问。宿主运行模型见 Framework v3 架构

本包提供公开 TypeScript 契约、PluginFrame 浏览器客户端和 nexus-plugin CLI。v2 项目升级前请先阅读 v2 到 v3 迁移说明。完整开发流程见 独立插件开发指南

兼容基线

| 项目 | 固定值 | | --- | --- | | SDK | 3.2.0 | | Framework API | 3.2.0 | | Manifest schema | 1 | | Node 安装和构建 | >=16.20.0 <17 | | Electron | 22.3.27 | | TypeScript | 4.9.4 | | 目标系统 | Windows 7 x64 |

不要为了新 Electron API、类型或依赖升级这些兼容锁。插件原生模块必须为 Electron 22 ABI 重建。

CLI

nexus-plugin info [--json]
nexus-plugin validate <plugin-package-directory>
nexus-plugin create <plugin-id> --template minimal|sqlite --output <parent-directory>
nexus-plugin upgrade [plugin-project-directory] [--check] [--sdk-source <version-or-local-tgz>]
nexus-plugin dev [plugin-project-directory]
nexus-plugin dev:web [plugin-project-directory]
nexus-plugin dev:nexus [plugin-project-directory] --host <nexus.exe-or-directory>

仓库内 SDK 命令:

npm run build
npm test
npm run pack:sdk
npm run publish:sdk

pack:sdk 把离线 tgz 写入 plugin-sdk/package/publish:sdkprepack 会运行测试、构建和公开文件清单验证。安装阶段和 CLI 启动阶段会非阻塞检查 SDK 更新;设置 NEXUS_PLUGIN_SDK_UPDATE_CHECK=0NO_UPDATE_NOTIFIER=1 可关闭检查。检查失败不会阻断安装、构建或调试,版本通知本身不会修改依赖;只有显式执行 upgrade 才会进入升级事务。

升级插件工程 SDK

标准插件工程的 nexus-plugin 来自本地 @aiot-nexus/plugin-sdk,因此 CLI 和工程 SDK 是同一个依赖。旧工程应由目标版本的 CLI 执行升级,不能只手工修改 package.json,因为 lockfile 和 node_modules 仍可能保留旧版本。

registry 升级与只读预检:

npx --package=@aiot-nexus/[email protected] nexus-plugin upgrade . --check
npx --package=@aiot-nexus/[email protected] nexus-plugin upgrade .

发布前或本地 tgz:

npx --package=./aiot-nexus-plugin-sdk-3.2.0.tgz nexus-plugin upgrade . \
  --sdk-source ./aiot-nexus-plugin-sdk-3.2.0.tgz

CI 可以追加 --json 获得 up-to-dateupgrade-availableupgradedrolled-backblocked--allow-major 只解除 major 保护,不会改写业务源码;跨 major 必须先阅读迁移指南。--skip-project-checks 只跳过项目 typecheck/build,安装后的 SDK、package 和 lockfile 一致性仍会验证。

升级器直接修改的范围只有 SDK 依赖、package-lock.jsonnode_modules 和命令管理的 SDK tgz。本地包会保存为 vendor/aiot-nexus-plugin-sdk-<version>.tgz,下一次本地升级会安全清理上一份由命令管理的版本化归档。Electron、TypeScript、@types/node 和 Node engine 只检查不修改;manifest、模板、README、构建脚本和业务依赖不会被覆盖。

修改前,命令会在项目根目录创建 .nexus-plugin-upgrade-recovery/,持久备份 package、lock、受管 tgz 和标准 dist/<pluginId>,并写入 recovery.json 阶段记录。成功或确定性回滚后该目录自动删除;硬中断、回滚重装失败或清理失败时保留,后续 upgrade 会停止并要求先按 journal 恢复。不要在项目恢复并完成验证前删除它。

安装或项目验证失败时恢复备份,并在原 lockfile 存在时运行 npm ci。没有原 lockfile 时会运行 npm install 并恢复原 SDK,但无法证明完整传递依赖树与升级前一致,因此返回 blocked 并保留恢复数据。标准脚手架 build 的 dist/<pluginId> 在回滚范围内;自定义 typecheck/build 若修改其他路径,这些副作用不属于升级事务,项目必须自行管理。

SDK 3.0.x / 3.1.x 插件可以由 3.2.x 宿主继续加载。升级到 SDK 3.2.0 只提供主题能力和新的契约;插件必须引用 --nexus-color-* 或读取 context.theme 才会使用宿主主题色。没有引用新变量的插件保持原有颜色;需要自定义主题时传入 autoApplyHostTheme: false。原有 hostApiVersion: 3.0.0 可以保持不变,只有实际使用 showHostStatusBar 时才需要声明 hostApiVersion: 3.1.0 或更高。SDK 3.1.1 修复 registry 升级命令丢失 npm 包名的问题,作为历史兼容说明保留。

创建插件

nexus-plugin create my-frame-plugin \
  --template minimal \
  --output . \
  --display-name "My Frame Plugin" \
  --install
  • minimal 提供最小 PluginFrame、同步生命周期和原生 Electron 文件对话框示例。
  • sqlite 提供 [email protected] 通用数据库浏览器。
  • --sdk-source 可指定严格版本、@aiot-nexus/[email protected] 或本地 .tgz
  • 默认不安装依赖;--install 执行 npm install
  • 非空目标目录会被拒绝,不会覆盖现有项目。
  • 两套模板都支持中英文、亮暗主题和同一 iframe 内的 context 更新。

离线示例:

npm run pack:sdk
nexus-plugin create my-frame-plugin --sdk-source ./package/aiot-nexus-plugin-sdk-3.2.0.tgz --install

生成插件后,可以在 src/manifest.json 的业务入口中静态控制 Nexus 宿主状态栏。相关 manifest 片段如下,其余必填字段沿用脚手架生成值:

{
  "hostApiVersion": "3.2.0",
  "businessEntries": [
    {
      "businessId": "my-frame-plugin",
      "titleKey": "plugin.displayName",
      "iconKey": "extension",
      "section": "home.main",
      "order": 50,
      "component": "PluginFrame",
      "pageRoute": "my-frame-plugin",
      "showHostStatusBar": false
    }
  ]
}

仅显式设置为 false 时隐藏。字段省略或设置为 true 时显示;离开该业务入口后,宿主状态栏自动恢复。该字段要求 hostApiVersion >= 3.1.0

Framework 3.2 mainEntry

mainEntry 是可信 Electron 主进程代码,可直接导入 Electron、Node 内置模块和插件本地依赖:

import { dialog } from "electron";
import { promises as fs } from "fs";
import type { PluginMainActivationContext } from "@aiot-nexus/plugin-sdk";

const maxTextBytes = 1024 * 1024;

export function activate(context: PluginMainActivationContext): void {
  const registered = context.host.registerRendererMethod("editor.chooseText", async () => {
    const selection = await dialog.showOpenDialog({
      properties: ["openFile"],
      filters: [{ name: "Text", extensions: ["txt", "md"] }]
    });
    if (selection.canceled || selection.filePaths.length === 0) {
      return { canceled: true };
    }
    const contentResult = await readTextFile(selection.filePaths[0]);
    return contentResult.ok
      ? { canceled: false, content: contentResult.value }
      : contentResult;
  });
  if (!registered.ok) {
    throw new Error(registered.error.message);
  }
}

export function deactivate(): void {
  // 同步注销 ipcMain/protocol/shortcut/timer,并关闭窗口、数据库和子进程。
}

async function readTextFile(filePath: string) {
  const file = await fs.open(filePath, "r");
  try {
    const stat = await file.stat();
    if (!stat.isFile() || stat.size > maxTextBytes) {
      return failure("FileTooLarge", "Select a regular file no larger than 1 MiB.");
    }
    const buffer = Buffer.alloc(maxTextBytes + 1);
    let bytesRead = 0;
    while (bytesRead < buffer.length) {
      const chunk = await file.read(buffer, bytesRead, buffer.length - bytesRead, bytesRead);
      if (chunk.bytesRead === 0) break;
      bytesRead += chunk.bytesRead;
    }
    return bytesRead > maxTextBytes
      ? failure("FileTooLarge", "File grew beyond 1 MiB while reading.")
      : { ok: true as const, value: buffer.subarray(0, bytesRead).toString("utf8") };
  } finally {
    await file.close();
  }
}

function failure(code: string, message: string) {
  return { ok: false as const, error: { code, message } };
}

固定契约:

  • activate(context)deactivate(reason) 必须同步完成,不能声明为 async,也不能返回 Promise。
  • 声明为 async 的生命周期函数会在调用前被拒绝;普通函数若返回 Promise,函数体已经执行,但 Framework 不等待该 Promise,并将本次生命周期判定为不受支持。
  • context.host.* 直接返回 NexusResult<T>,不需要 await
  • 注册的 operation handler 可以返回 Promise,可等待异步文件对话框、网络或其他用户交互。
  • operation 默认没有业务超时。浏览器客户端 requestTimeoutMs 默认是 0;20 秒仅用于 iframe 初始化握手。
  • 同步 Electron API 也能调用,但 showOpenDialogSync()、同步文件 I/O、长 CPU 任务会阻塞 Nexus 主进程、所有窗口和插件,应优先使用异步版本。
  • Framework 不跟踪插件直接创建的 Electron/Node 资源。插件必须在 deactivate 中自行清理。
  • Node 模块图和原生模块不能安全热卸载。已加载 mainEntry、依赖、原生模块或相关 manifest 变化必须重启 Nexus。

Framework 3 不再提供 context.host.electron Facade,也没有 electronHostControl 权限。manifest 权限只描述 Host API 能力,不限制可信 mainEntry 直接调用 Electron/Node。SDK 保留 createPluginElectronIpcChannel()createPluginElectronProtocolScheme() 作为命名约定帮助函数;宿主不会自动隔离或清理对应注册。

原生文件、窗口、IPC 与 Monaco 示例见 Electron/Node 示例,支持矩阵和风险边界分别见 兼容矩阵安全边界

Renderer 边界

PluginFrame iframe 不能直接获得 Node、Electron、数据库对象、任意文件路径或完整 ipcRenderer。Renderer 应通过 client.invokeMain() 调用经过命名和输入校验的业务 operation。

关键 CSP:

script-src 'self'; style-src 'self' 'unsafe-inline'; worker-src 'self' blob:

这允许 Monaco 运行时注入样式和同源/Blob Worker,但仍阻止内联脚本、远程脚本和 Node iframe 访问。脚本、Monaco、语言包、字体和 worker 必须随插件作为同源资源交付。

createPluginFrameClient() 的首次初始化默认等待 20 秒。业务请求默认不设超时;只有插件显式传入正数 requestTimeoutMs 时客户端才会主动超时。进度、取消和恢复仍建议使用 taskId + getStatus + cancel,但这是任务 UX 和资源管理策略,不是 worker 存活要求。通信契约见 插件 IPC 与事件通知示例

宿主主题色

Framework 3.2 在 PluginRendererContext.theme 中提供 lightdark 两套语义 palette。SDK frame client 默认把当前 palette 的固定白名单字段写入 --nexus-color-*,并设置 data-nexus-theme="light|dark"themeMode: "system" 会跟随系统主题变化。SDK 不会修改插件已有的 --bg--accentdata-themecolor-scheme

SDK 升级本身只提供能力。插件需要在 CSS 中引用 var(--nexus-color-primary, #8fc31f) 等变量,或从 context.theme?.dark.primary 读取值,才会使用宿主主题色。旧宿主或缺少 theme 的上下文仍可正常初始化。自定义主题插件可以显式使用 createPluginFrameClient({ autoApplyHostTheme: false }),此时不注入变量、不设置宿主主题标记,但仍可手动读取 context.theme

SQLite 模板

sqlite 模板默认打开 pluginDataDirectory/plugin.db 并初始化 plugin_records。工具栏“打开数据库”由 mainEntry 直接调用 dialog.showOpenDialog();iframe 只接收数据库信息和参数化 operation。

模板支持:

  • 最近外部数据库路径原子持久化和失效回退。
  • 普通用户表发现、动态 schema、分页和 25/50/100 页大小。
  • rowid、单/复合主键与 WITHOUT ROWID 定位。
  • default/null/value 动态插入和 no-change/null/value 更新。
  • 精确 INTEGER、有限 REAL、NUMERIC 文本兼容和 BLOB 十六进制。
  • TEXT 16 KiB 预览、BLOB 字节数和前 32 字节预览。
  • 单字段 1 MiB、单次提交 4 MiB 上限。

模板不暴露任意 SQL、DDL、ATTACH、路径输入或 SQL 控制台。它刻意不依赖 Quasar,因此数据库网格使用原生 <table>;这是独立、依赖最小化 SDK 模板对宿主 QTable 规则的明确例外。

调试模式

| 命令 | mainEntry | Electron/Node | 用途 | | --- | --- | --- | --- | | npm run dev:web | 不执行 | 不可用 | 只调试 Renderer;invokeMain 返回 PluginCapabilityUnsupported。 | | npm run dev | Electron 调试宿主主进程直接加载 | 可用 | 调试真实生命周期、对话框、SQLite 和插件窗口。 | | npm run dev:debug | 同 dev | 可用 | Electron 主进程以 --inspect-brk=9229 启动。 | | npm run dev:nexus -- --host ... | 正式 Nexus 主进程直接加载 | 可用 | 最终集成和监听更新。 |

Electron 调试壳仅保留 context 工具栏;插件 iframe 无边距、无边框,填满剩余窗口,不设置最小宽高,也不根据内容上报限制尺寸。Renderer DevTools 默认打开。

VS Code 模板提供 Debug Nexus pluginAttach Nexus plugin main process (9229)。Renderer 断点在 DevTools Sources 中设置。

dev:nexus 更新规则:

| 修改范围 | 处理方式 | | --- | --- | | src/renderer/** | 只构建 Renderer 并重载 iframe,Nexus PID 不变。 | | 无 mainEntry 插件的安全 manifest 展示/贡献字段 | 原子替换 manifest 并重扫,PID 不变。 | | 有 mainEntry 插件的 manifest 变化 | 完整构建并重启 Nexus。 | | src/main.*、依赖、原生模块、构建脚本 | 完整构建并重启 Nexus。 |

构建失败保留旧部署;新宿主启动或就绪失败会恢复旧包和旧宿主。--inspect / --inspect-brk 调试的是 Nexus Electron 主进程,因此会同时暂停或暴露宿主代码。按 Ctrl+C 时 CLI 关闭自己启动的宿主。

发布前检查

  1. hostApiVersion 使用当前 3.2.0(或按实际能力保持旧版本),manifest schema 仍为 1
  2. 业务入口的 showHostStatusBar 可静态声明是否保留宿主状态栏;省略时默认保留,显式使用时要求 hostApiVersion >= 3.1.0
  3. activate/deactivate 同步,operation handler 才使用异步。
  4. Renderer 只使用窄口径 operation,所有输入在 mainEntry 再校验。
  5. 窗口保持 contextIsolation: truesandbox: truenodeIntegration: falsewebSecurity: true
  6. IPC、protocol、快捷键、菜单和文件路径使用插件命名空间并检测冲突。
  7. deactivate 关闭窗口、Session 监听、IPC、protocol、快捷键、定时器、数据库和子进程。
  8. 没有同步长任务、无限载荷、任意命令/SQL/路径透传或日志敏感信息。
  9. 主题集成明确引用 --nexus-color-* / context.theme,或显式关闭 autoApplyHostTheme
  10. 运行 npm test、插件构建、正式 Nexus 集成和 Windows 7 真机验收。